Merge remote-tracking branch 'origin/master' into xtr/react-loop-simplification
# Conflicts: # .agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.i18n.yaml # .agents/notes/implemented/architecture/2026-07-31-goal-owned-durable-events.i18n.yaml # .agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.i18n.yaml # .agents/notes/implemented/feature/2026-07-22-docked-web-goal-bar.i18n.yaml # docs/architecture.i18n.yaml # docs/architecture.md # docs/architecture.zh.md # docs/config-catalog.md # docs/cordis-catalog/events.md # docs/cordis-catalog/services.md # docs/core-data-structures/core.i18n.yaml # docs/core-data-structures/core.md # docs/core-data-structures/core.zh.md # docs/event-producer-consumer.md # docs/module-graph.md # docs/persistence-catalog.md # examples/acp-agent/tests/snapshots/advanced-toolchain/session.1.jsonl # examples/acp-agent/tests/snapshots/advanced-toolchain/session.2.jsonl # examples/acp-agent/tests/snapshots/code-mode-workspace-context/session.jsonl # examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl # examples/acp-agent/tests/snapshots/skill-load/session.jsonl # examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/session.1.jsonl # examples/acp-agent/tests/snapshots/subagent-depth-two-rejection/session.2.jsonl # examples/acp-agent/tests/snapshots/subagent-fork/session.1.jsonl # examples/acp-agent/tests/snapshots/subagent-mixed/session.1.jsonl # examples/acp-agent/tests/snapshots/subagent-mixed/session.2.jsonl # examples/acp-agent/tests/snapshots/subagent-multi/session.1.jsonl # examples/acp-agent/tests/snapshots/subagent-multi/session.2.jsonl # examples/acp-agent/tests/snapshots/subagent-spawn/session.1.jsonl # examples/acp-agent/tests/snapshots/workflow-run/session.1.jsonl # examples/acp-agent/tests/snapshots/workspace-context/session.jsonl # examples/headless-agent/tests/snapshots/advanced-toolchain/session.1.jsonl # examples/headless-agent/tests/snapshots/advanced-toolchain/session.2.jsonl # examples/headless-agent/tests/snapshots/advanced-toolchain/session.jsonl # examples/headless-agent/tests/snapshots/pty-tools/session.jsonl # examples/headless-agent/tests/subagent-inheritance-snapshots/parent-override/child.expected.jsonl # examples/jsonrpc-agent/tests/snapshots/subagent-spawn/notifications.expected.jsonl # examples/jsonrpc-agent/tests/snapshots/subagent-spawn/session.1.jsonl # packages/acp/acp/README.i18n.yaml # packages/acp/acp/src/index.ts # packages/client/connection/src/client/index.ts # packages/client/runtime/README.i18n.yaml # packages/client/runtime/README.md # packages/client/runtime/README.zh.md # packages/client/runtime/src/client/sessions/conversation.ts # packages/client/runtime/src/client/sessions/session.ts # packages/client/runtime/tests/queue-store.spec.ts # packages/client/ui-conversation/src/client/input/contract.ts # packages/client/ui-goal/README.i18n.yaml # packages/compact/compact-basic/src/index.ts # packages/context/time-context/tests/time-context.spec.ts # packages/context/tmux-context/tests/tmux-context.spec.ts # packages/context/workspace-context/tests/workspace-context.spec.ts # packages/cordis/tool-cordis/src/api-catalog.ts # packages/core/agent-loop/README.i18n.yaml # packages/core/agent-loop/README.md # packages/core/agent-loop/README.zh.md # packages/core/agent-loop/src/agent.ts # packages/core/agent-loop/tests/agent.spec.ts # packages/core/agent-loop/tests/loop.spec.ts # packages/core/agent/README.i18n.yaml # packages/core/agent/README.md # packages/core/agent/README.zh.md # packages/core/agent/src/types.ts # packages/core/session/README.i18n.yaml # packages/core/session/README.md # packages/core/session/README.zh.md # packages/goal/command-goal/tests/command-goal.spec.ts # packages/goal/goal/tests/goal.spec.ts # packages/goal/tool-goal/README.i18n.yaml # packages/goal/tool-goal/README.md # packages/goal/tool-goal/README.zh.md # packages/host/apiproxy/README.i18n.yaml # packages/host/apiproxy/README.md # packages/host/apiproxy/README.zh.md # packages/host/apiproxy/src/api-proxy.ts # packages/host/apiproxy/src/api/events.schema.ts # packages/host/apiproxy/src/api/events.ts # packages/host/apiproxy/src/api/rpc.ts # packages/host/apiproxy/tests/api-proxy-commands.spec.ts # packages/host/apiproxy/tests/api-proxy-projections.spec.ts # packages/host/apiproxy/tests/rpc-schemas.spec.ts # packages/pty/pty-local/tests/index.spec.ts # packages/pty/pty-local/tests/local.spec.ts # packages/pty/tool-pty/tests/loader-composition.spec.ts # packages/pty/tool-pty/tests/tools.spec.ts # packages/session-persistence/session-checkpoint-policy/src/index.ts # packages/session-persistence/session-persistence/tests/coordinator-contract.ts # packages/skill/tool-skill/tests/tool-skill.spec.ts # packages/subagent/subagent-inprocess/README.i18n.yaml # packages/subagent/subagent-inprocess/README.md # packages/subagent/subagent-inprocess/README.zh.md # packages/subagent/subagent-inprocess/src/index.ts # packages/subagent/subagent-inprocess/tests/subagent-inprocess.spec.ts # packages/support/acp-snapshot/src/harness.ts # packages/ui/tui/tests/tui.spec.ts
This commit is contained in:
@@ -94,6 +94,13 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
|
||||
expect(stdout).toContain('model: deepseek-v4-pro')
|
||||
expect(stdout).toContain('cwd: !!js process.cwd()')
|
||||
expect(stdout).toContain("name: '@deepseek-ai/dsh-tui'")
|
||||
expect(stdout).toContain([
|
||||
'- id: tool-web',
|
||||
" name: '@deepseek-ai/dsh-tool-web'",
|
||||
' config:',
|
||||
' fetch: false',
|
||||
' searchTimeoutMs: 60000',
|
||||
].join('\n'))
|
||||
// Provenance comment separators name each section's source file.
|
||||
expect(stdout).toContain('# == base.cordis.yml')
|
||||
expect(stdout).toContain('# == base.cordis.yml, patched by tui.cordis.yml')
|
||||
|
||||
@@ -33,13 +33,10 @@ const EXPECTED_TUI_TOOLS = [
|
||||
'edit',
|
||||
'exit_plan_mode',
|
||||
'get_goal',
|
||||
'list_agents',
|
||||
'ralph',
|
||||
'read',
|
||||
'session_event_read',
|
||||
'session_event_search',
|
||||
'session_event_trace',
|
||||
'session_search',
|
||||
'session_trace',
|
||||
'send_message',
|
||||
'skill',
|
||||
'str_replace_editor',
|
||||
'subagent',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createUserMessage, createMessage } from '@deepseek-ai/dsh-llm'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { realpathSync } from 'node:fs'
|
||||
import { mkdir, mkdtemp, readdir, readFile, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
@@ -6,6 +7,7 @@ import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { LOADER_SMOKE_TEST_TIMEOUT_MS } from '@deepseek-ai/dsh-loader-smoke'
|
||||
import { PREPARED_ENTRY_FILENAME, prepareDshPlugin } from '@deepseek-ai/dsh-repository-plugin'
|
||||
import { packChunkRuns, SessionId, type SessionEvent, type SessionHeader } from '@deepseek-ai/dsh-session'
|
||||
import { logPath, toHeaderLine } from '../../../packages/session-persistence/session-persistence-jsonl/src/format.ts'
|
||||
import { runTuiPtySmoke, type TuiPtySmokeOptions } from './pty-harness.ts'
|
||||
@@ -66,6 +68,38 @@ function seedWorkspace(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the real `prepareDshPlugin` over an equivalent one-skill `.dsh-plugin`
|
||||
* package and return the generated wrapper text, so the smoke's cache-seeded
|
||||
* wrapper can never drift from the generator's template.
|
||||
*/
|
||||
async function generatePreparedWrapper(pluginName: string): Promise<string> {
|
||||
const root = await mkdtemp(join(tmpdir(), 'dsh-smoke-wrapper-'))
|
||||
try {
|
||||
const plugin = join(root, '.dsh-plugin')
|
||||
await mkdir(join(root, 'skills', 'config-only-repository'), { recursive: true })
|
||||
await writeFile(join(root, 'skills', 'config-only-repository', 'SKILL.md'), [
|
||||
'---',
|
||||
'name: config-only-repository',
|
||||
'description: Generator input; the seeded cache copy owns the visible text.',
|
||||
'---',
|
||||
'',
|
||||
'Repository instructions.',
|
||||
'',
|
||||
].join('\n'))
|
||||
await mkdir(plugin, { recursive: true })
|
||||
await writeFile(join(plugin, 'package.json'), `${JSON.stringify({
|
||||
name: pluginName,
|
||||
version: '0.0.0',
|
||||
dsh: { skills: ['../skills'] },
|
||||
}, undefined, 2)}\n`)
|
||||
await prepareDshPlugin(plugin)
|
||||
return await readFile(join(plugin, PREPARED_ENTRY_FILENAME), 'utf8')
|
||||
} finally {
|
||||
await rm(root, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
|
||||
/** Seed one real plaintext JSONL session for the `/resume` selector and host handoff smoke. */
|
||||
async function seedResumeSession(cwd: string): Promise<void> {
|
||||
const sessionCwd = realpathSync.native(cwd)
|
||||
@@ -640,6 +674,54 @@ describe('dsh CLI keyless smoke (apps/cli through the same PTY)', () => {
|
||||
expect(output).toContain('\u001B[?2004l')
|
||||
}, PTY_SMOKE_TEST_TIMEOUT_MS)
|
||||
|
||||
it('loads a cached repository Plugin from personal config alone', async () => {
|
||||
const source = 'github:fixture/repository#fixed-ref'
|
||||
const specifier = `${source}&path:/.dsh-plugin`
|
||||
const key = createHash('sha256').update(specifier).digest('hex')
|
||||
const packageRoot = `cache/repository-plugins/${key}/node_modules/repository`
|
||||
// Produced by the real generator (prepareDshPlugin over an equivalent
|
||||
// .dsh-plugin package) rather than hand-written, so a wrapper-template
|
||||
// change cannot leave this smoke exercising a stale shape. The cache
|
||||
// LAYOUT below (sha256 key, marker, node_modules/repository) remains a
|
||||
// deliberate external pin of the durable on-disk format.
|
||||
const wrapper = await generatePreparedWrapper('config-only-fixture')
|
||||
const output = await smoke({
|
||||
label: 'dsh personal repository Plugin',
|
||||
tempDirPrefix: 'dsh-personal-repository-plugin-',
|
||||
binScript: dshBinScript,
|
||||
configArgs: [],
|
||||
prepare: seedWorkspace({
|
||||
personal: {
|
||||
'config.yaml': [
|
||||
'- id: repository-plugins',
|
||||
" name: '@deepseek-ai/dsh-repository-plugin'",
|
||||
' config:',
|
||||
' repositories:',
|
||||
` - '${source}'`,
|
||||
'',
|
||||
].join('\n'),
|
||||
[`cache/repository-plugins/${key}/.repository-cache.json`]: `${JSON.stringify({ specifier })}\n`,
|
||||
[`${packageRoot}/dsh-plugin.mjs`]: wrapper,
|
||||
[`${packageRoot}/dsh-plugin-assets/skills/0/config-only-repository/SKILL.md`]: [
|
||||
'---',
|
||||
'name: config-only-repository',
|
||||
'description: CONFIG_ONLY_REPOSITORY_SKILL',
|
||||
'---',
|
||||
'',
|
||||
'Repository instructions.',
|
||||
'',
|
||||
].join('\n'),
|
||||
},
|
||||
}),
|
||||
actions: [
|
||||
{ waitFor: 'main-session-', send: '/skill:config-only' },
|
||||
{ waitFor: 'CONFIG_ONLY_REPOSITORY_SKILL', send: '\x03/exit\r' },
|
||||
],
|
||||
})
|
||||
expect(output).toContain('CONFIG_ONLY_REPOSITORY_SKILL')
|
||||
expect(output).toContain('\u001B[?2004l')
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
|
||||
it('fails loud instead of booting when the personal config.yaml is invalid', async () => {
|
||||
const output = await smoke({
|
||||
label: 'dsh invalid personal config',
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { sep } from 'node:path'
|
||||
import { Context } from 'cordis'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import { HARNESS_SOURCE_SECTION } from '@deepseek-ai/dsh-app-boot'
|
||||
import type {} from '@deepseek-ai/dsh-host-webserver'
|
||||
import { prepareWebRuntimeContext } from '../src/web.ts'
|
||||
|
||||
describe('prepareWebRuntimeContext', () => {
|
||||
it('installs both sections before a later systemPrompt consumer activates', async () => {
|
||||
const ctx = new Context()
|
||||
const sourceRoot = `${sep}opt${sep}harness-src`
|
||||
let observedSections: { name: string; text: string }[] | undefined
|
||||
try {
|
||||
prepareWebRuntimeContext(ctx, sourceRoot, 'production')
|
||||
ctx.provide('httpServer', { port: 3080 } as Context['httpServer'])
|
||||
const consumer = ctx.inject(['systemPrompt'], async (promptCtx) => {
|
||||
const assembly = await promptCtx.systemPrompt.assemble()
|
||||
observedSections = assembly.sections
|
||||
})
|
||||
|
||||
await ctx.plugin(SystemPrompt, { persona: 'You are a coding agent.' })
|
||||
await consumer
|
||||
|
||||
expect(observedSections?.map(section => section.name)).toContain(HARNESS_SOURCE_SECTION)
|
||||
expect(observedSections?.find(section => section.name === 'app:web-surface')?.text)
|
||||
.toContain('http://127.0.0.1:3080')
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user