Merge remote-tracking branch 'origin/master' into codex/skill-system

# Conflicts:
#	docs/architecture.md
#	docs/config-catalog.md
#	docs/module-graph.md
#	docs/rfc/INDEX.md
#	examples/acp-agent/tests/snapshots/text-turn/session.jsonl
#	packages/core/agent-core/src/index.ts
#	packages/core/tools/tests/gen-tool-catalog.spec.ts
#	packages/support/acp-snapshot/src/suite.ts
#	packages/ui/acp-agent/src/index.ts
This commit is contained in:
Yichen Jiang
2026-07-10 14:43:33 +08:00
192 changed files with 15401 additions and 5568 deletions

View File

@@ -105,12 +105,6 @@ const dshBinPackageFiles = [
'src',
] as const
// Packages that ship a worker-thread entry as a sibling runtime bundle
// (lib/worker.js, its own tsdown entry): the bootstrap is loaded via
// `new Worker(new URL('./worker.js', import.meta.url))`, so it cannot live
// inside the index bundle and must be published alongside it.
const workerEntryPackages = new Set(['@deepseek-ai/dsh-code-runtime-worker'])
const dshWorkerPackageFiles = [
'lib/index.js',
'lib/worker.js',
@@ -124,8 +118,12 @@ function sameStringList(actual: readonly string[] | undefined, expected: readonl
}
function expectedDshPackageFiles(manifest: PackageManifest): readonly string[] {
if (manifest.name && workerEntryPackages.has(manifest.name)) return dshWorkerPackageFiles
return manifest.bin ? dshBinPackageFiles : dshPackageFiles
if (manifest.bin) return dshBinPackageFiles
// A declared "./worker" subpath export sanctions the one extra runtime
// bundle a worker-thread entry needs (and NodeNext/publint then validate
// that subpath's targets like any other export).
if (manifest.exports?.['./worker']) return dshWorkerPackageFiles
return dshPackageFiles
}
function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] {

View File

@@ -0,0 +1,29 @@
/**
* Boot the Code Mode demo under the UI named on the command line:
* `pnpm run demo:code-mode [repl|acp]`, default `repl`. Code Mode is the
* point — the UI is just the surface it happens to wear: each UI boots its
* base example through that example's `code-mode.cordis.yml` overlay
* (include ./cordis.yml, flip `tools.mode` to `code`, insert the
* worker-thread code runtime). Both need DEEPSEEK_API_KEY (repo-root .env
* works). Anything else on the command line is a misconfiguration and
* fails loud with usage.
*/
import { spawn } from 'node:child_process'
// Each UI's node invocation, verbatim what its base demo script runs plus
// the overlay config (the stdio bin keeps --expose-internals for the cordis
// Loader's HMR path).
const UIS = new Map([
['repl', ['--expose-internals', '--import', 'tsx', 'packages/ui/stdio-agent/src/bin.ts', 'examples/coding-agent/code-mode.cordis.yml']],
['acp', ['--import', 'tsx', 'packages/ui/acp-agent/src/bin.ts', 'examples/acp-agent/code-mode.cordis.yml']],
])
const ui = process.argv[2] ?? 'repl'
const args = UIS.get(ui)
if (!args || process.argv.length > 3) {
console.error('usage: pnpm run demo:code-mode [repl|acp]')
process.exit(2)
}
const child = spawn(process.execPath, args, { stdio: 'inherit' })
child.on('exit', (code, signal) => { process.exit(signal !== null ? 1 : code ?? 1) })

View File

@@ -1,7 +1,7 @@
{
"AGENTS.md": 1802,
"docs/AGENTS.md": 1315,
"docs/architecture.md": 1640,
"docs/architecture.md": 1642,
"docs/cordis-primer.md": 550,
"docs/defensive-patterns.md": 550,
"docs/testing.md": 800,

View File

@@ -175,8 +175,8 @@ const SERVICE_ROLES: ServiceRole[] = [
title: 'Code-execution seam',
mode: 'seam',
implementations: ['code-runtime-worker'],
consumers: [],
note: 'Runs one model-written program against host-provided async bindings; backends differ by substrate and language (the Code Mode RFC specifies the worker-thread backend and the tool-registry consumer).',
consumers: ['tools'],
note: 'Runs one model-written program against host-provided async bindings; backends differ by substrate and language (the tool registry consumes it for Code Mode).',
},
{
key: 'fs',
@@ -215,6 +215,15 @@ const SERVICE_ROLES: ServiceRole[] = [
consumers: ['tool-web'],
note: 'Search and fetch providers register into one ctx.web seam; tool-web owns the stable model-facing names.',
},
{
key: 'workflows',
pkg: 'workflow',
title: 'Workflow script engine',
mode: 'seam',
implementations: ['workflow-workerthread'],
consumers: ['tool-workflow'],
note: 'One engine per context (bash shape, no named-provider registry); the worker-thread engine fans agent() calls out through ctx.subagents.',
},
]
const DYNAMIC_EVENT_DISPATCHERS: Array<{ event: string; pkg: string; method: string }> = [
@@ -223,6 +232,14 @@ const DYNAMIC_EVENT_DISPATCHERS: Array<{ event: string; pkg: string; method: str
// listeners or strand an already-started child run.
{ event: 'subagent/start', pkg: 'subagent', method: 'events.dispatch' },
{ event: 'subagent/end', pkg: 'subagent', method: 'events.dispatch' },
// The workflow/* lifecycle events dispatch the same way, for the same
// per-listener-containment reason (WorkflowService.emitWorkflowEvent).
{ event: 'workflow/start', pkg: 'workflow', method: 'events.dispatch' },
{ event: 'workflow/phase', pkg: 'workflow', method: 'events.dispatch' },
{ event: 'workflow/log', pkg: 'workflow', method: 'events.dispatch' },
{ event: 'workflow/agent-start', pkg: 'workflow', method: 'events.dispatch' },
{ event: 'workflow/agent-end', pkg: 'workflow', method: 'events.dispatch' },
{ event: 'workflow/end', pkg: 'workflow', method: 'events.dispatch' },
]
function generatedHeader(title: string): string[] {
@@ -670,7 +687,7 @@ function renderToolPipeline(): string {
` around["${mermaidCode('tools/execute')} waterfall<br/>timeout, retry, metrics (around dispatch)"]`,
' toolBody["Registered tool execute() body"]',
` fsGate["${mermaidCode('fs/write-intent')} or ${mermaidCode('fs/edit-intent')}<br/>tool-fs mutations only"]`,
` owned["Tool-owned session events<br/>${mermaidCode('todo/write')}, ${mermaidCode('fs/observed')}, ${mermaidCode('hook/invoked')}, ${mermaidCode('hook/result')}"]`,
` owned["Tool-owned session events<br/>${mermaidCode('todo/write')}, ${mermaidCode('fs/observed')}, ${mermaidCode('hook/invoked')}, ${mermaidCode('hook/result')}, ${mermaidCode('tool/code-dispatch')}"]`,
` post["${mermaidCode('tools/post-execute')} waterfall<br/>accept, block, replace, add context"]`,
' context["Buffered additionalContext<br/>context/message after all tool results"]',
` toolResult["Session event: ${mermaidCode('tool/result')}<br/>single model-facing outcome"]`,
@@ -692,7 +709,7 @@ function renderToolPipeline(): string {
' toolResult --> presentResult',
'```',
'',
'Filesystem read-before-edit checks live below `tool-fs` on the `fs/*` event gate; hook bridges and future permission prompts live on the generic pre/post tool waterfalls; and around-dispatch concerns like the tool-call timeout policy (`@deepseek-ai/dsh-timeout-policy`) wrap core dispatch on `tools/execute`. That split lets the same hooks observe bash, fs, web, todo, and subagent calls without coupling those tools to one policy service.',
'Filesystem read-before-edit checks live below `tool-fs` on the `fs/*` event gate; hook bridges and future permission prompts live on the generic pre/post tool waterfalls; and around-dispatch concerns like the tool-call timeout policy (`@deepseek-ai/dsh-timeout-policy`) wrap core dispatch on `tools/execute`. That split lets the same hooks observe bash, fs, web, todo, and subagent calls without coupling those tools to one policy service. Code Mode rides the same pipeline twice over: `run_code` is itself a registered tool body, and each tool call its program makes re-enters `ctx.tools.execute()` through BOTH waterfalls — serialized one at a time, logged as a `tool/code-dispatch` session event, with a deny surfacing to the program as a binding rejection (a sub-call\'s `additionalContext` is deliberately dropped — no safe outlet mid-run preserves call/result adjacency).',
'',
...maintenanceFooter(maintenance),
].join('\n')

View File

@@ -38,7 +38,7 @@ import { basename, resolve } from 'node:path'
import { Context } from 'cordis'
import type { ToolSchema } from '@deepseek-ai/dsh-llm'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry from '@deepseek-ai/dsh-tools'
import ToolRegistry, { type Config as ToolsConfig } from '@deepseek-ai/dsh-tools'
import LocalBashExecutor from '@deepseek-ai/dsh-bash-local'
import LocalFileSystem from '@deepseek-ai/dsh-fs-local'
import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
@@ -57,6 +57,8 @@ import * as ToolSkill from '@deepseek-ai/dsh-tool-skill'
import * as ToolTodo from '@deepseek-ai/dsh-tool-todo'
import * as ToolSubagent from '@deepseek-ai/dsh-tool-subagent'
import * as ToolWeb from '@deepseek-ai/dsh-tool-web'
import VmWorkflowEngine from '@deepseek-ai/dsh-workflow-workerthread'
import * as ToolWorkflow from '@deepseek-ai/dsh-tool-workflow'
const root = resolve(import.meta.dirname, '..')
const OUT = 'docs/tool-catalog.md'
@@ -90,6 +92,13 @@ interface ToolPackage {
/** Plug the injected seams + the tool plugin onto a context that already
* carries `systemPrompt` + `tools`. */
mount: (ctx: Context) => Promise<void>
/**
* Config for the caller's `ToolRegistry` mount. The registry itself ships a
* model-facing tool (`run_code`, registered under a non-native `mode`), so
* ITS catalog entry boots the registry in the mode that surfaces it;
* every other entry uses the default (native) registry.
*/
toolsConfig?: ToolsConfig
/**
* A deployment note rendered after the package's tools, for a fact that
* booting the package alone cannot show. The registered tool NAME can be a
@@ -119,6 +128,20 @@ const TOOL_PACKAGES: ToolPackage[] = [
note:
'ask_user_question pauses the tool call until the active UI provider returns a human answer.',
},
{
pkg: '@deepseek-ai/dsh-tools',
dir: 'tools',
source: 'packages/core/tools/src/code-mode.ts',
requires: ['ctx.tools', 'ctx.codeRuntime (execution time)', 'ctx.systemPrompt'],
writes: ['tool/call', 'one tool/code-dispatch per bridged sub-call', 'tool/result'],
// The registry's OWN tool: run_code exists only under a non-native mode
// (the registry registers it in its constructor; the code runtime is read
// at assembly/execution time, so the schema harvest needs none mounted).
toolsConfig: { mode: 'code' },
async mount() {},
note:
'Registered by the tool registry itself under `mode: code` / `mode: both` (see the Code Mode RFC). Under `code` it is the ONLY wire tool; the other registered tools are declared to the model as a generated TypeScript SDK prompt section instead, and a program calls them through port-bridged bindings that dispatch through the ordinary tools/pre-execute → tools/post-execute pipeline, one at a time.',
},
{
pkg: '@deepseek-ai/dsh-tool-bash',
dir: 'tool-bash',
@@ -203,6 +226,22 @@ const TOOL_PACKAGES: ToolPackage[] = [
note:
'todo_write is session-owned state; UIs render the latest todo/write event as a checklist or ACP plan.',
},
{
pkg: '@deepseek-ai/dsh-tool-workflow',
dir: 'tool-workflow',
source: 'packages/workflow/tool-workflow/src/index.ts',
requires: ['ctx.tools', 'ctx.workflows', 'ctx.systemPrompt', 'a calling Agent (exec.agent parents the script children)'],
writes: ['tool/call', 'tool/result'],
async mount(ctx) {
// The tool injects `workflows`; boot the vm engine over a scripted
// subagent provider to satisfy it. The schema does not depend on which
// provider backs the engine.
await ctx.plugin(SubagentService)
await ctx.plugin(SubagentMock, { name: 'mock' })
await ctx.plugin(VmWorkflowEngine, { provider: 'mock' })
await ctx.plugin(ToolWorkflow)
},
},
{
pkg: '@deepseek-ai/dsh-tool-web',
dir: 'tool-web',
@@ -277,7 +316,7 @@ export async function collectToolCatalog(packages: ToolPackage[] = TOOL_PACKAGES
// fiber) — the repo's "dispose must reach quiescence" rule.
try {
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
await ctx.plugin(ToolRegistry, entry.toolsConfig ?? {})
await entry.mount(ctx)
const schemas = ctx.tools.schemas().sort((a, b) => a.name.localeCompare(b.name))
catalog.push({

View File

@@ -310,6 +310,10 @@ function builtBinSmokeGate(): Gate {
'vitest.e2e.config.ts',
'packages/ui/stdio-agent/tests/built-bin.e2e.ts',
'packages/ui/acp-agent/tests/built-bin.e2e.ts',
// The worker-entry packages' built bundles: the only automated proof
// that lib/index.js resolves its sibling lib/worker.js under plain node
// (the e2e lane runs unbuilt, so these files self-skip there).
'packages/workflow/workflow-workerthread/tests/built-worker.e2e.ts',
'packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts',
], {
label: 'built-bin smoke',

View File

@@ -108,6 +108,11 @@
{ "doc": "docs/core-data-structures/web.md", "symbol": "WebFetchRequest", "source": "packages/web/web/src/types.ts" },
{ "doc": "docs/core-data-structures/web.md", "symbol": "WebFetchResult", "source": "packages/web/web/src/types.ts" },
{ "doc": "docs/core-data-structures/web.md", "symbol": "WebFetchBody", "source": "packages/web/web/src/types.ts" },
{ "doc": "docs/core-data-structures/web.md", "symbol": "WebProviderStatus", "source": "packages/web/web/src/types.ts" }
{ "doc": "docs/core-data-structures/web.md", "symbol": "WebProviderStatus", "source": "packages/web/web/src/types.ts" },
{ "doc": "docs/core-data-structures/workflow.md", "symbol": "WorkflowStartRequest", "source": "packages/workflow/workflow/src/types.ts" },
{ "doc": "docs/core-data-structures/workflow.md", "symbol": "WorkflowMeta", "source": "packages/workflow/workflow/src/types.ts" },
{ "doc": "docs/core-data-structures/workflow.md", "symbol": "WorkflowResult", "source": "packages/workflow/workflow/src/types.ts" },
{ "doc": "docs/core-data-structures/workflow.md", "symbol": "WorkflowRun", "source": "packages/workflow/workflow/src/types.ts" }
]
}