Merge remote-tracking branch 'origin/master' into worktree/provider-routed-llm-adapters

# Conflicts:
#	docs/config-catalog.md
#	packages/examples/acp-demo/tests/acp-agent.spec.ts
#	packages/examples/stdio-demo/README.md
#	packages/examples/stdio-demo/src/index.ts
#	packages/examples/stdio-demo/tests/stdio-agent.spec.ts
This commit is contained in:
Yichen Jiang
2026-07-15 20:14:09 +08:00
138 changed files with 613 additions and 530 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ const root = resolve(import.meta.dirname, '..')
/** The closure manifest whose dependencies define the executable. */
const DEPLOY_ROOT_PACKAGE = 'dsh-jsonrpc-agent-pkg'
/** The app entry inside the deployed closure. */
const ENTRY_BIN = 'node_modules/@deepseek-ai/dsh-jsonrpc-agent/lib/bin.js'
const ENTRY_BIN = 'node_modules/@deepseek-ai/dsh-jsonrpc-demo/lib/bin.js'
const OUTPUT_BASENAME = 'dsh-jsonrpc-agent-pkg'
/** Default Node major; SEA mode requires at least Node 22. */
const DEFAULT_NODE_RANGE = 'node24'
+2 -2
View File
@@ -9,8 +9,8 @@ import { spawn } from 'node:child_process'
// 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', '--config', 'examples/acp-agent/code-mode.cordis.yml']],
['repl', ['--expose-internals', '--import', 'tsx', 'packages/examples/stdio-demo/src/bin.ts', 'examples/coding-agent/code-mode.cordis.yml']],
['acp', ['--import', 'tsx', 'packages/examples/acp-demo/src/bin.ts', '--config', 'examples/acp-agent/code-mode.cordis.yml']],
])
const ui = process.argv[2] ?? 'repl'
+1 -1
View File
@@ -7,5 +7,5 @@
"docs/testing.md": 800,
"examples/AGENTS.md": 200,
"packages/AGENTS.md": 290,
"packages/README.md": 710
"packages/README.md": 760
}
+6 -1
View File
@@ -125,7 +125,12 @@ try {
})
try {
execFileSync('node_modules/.bin/tsc', ['-b', join(tmp, 'tsconfig.json')], { cwd: root, stdio: 'pipe' })
// tsc's JS entry via the current node, not the .bin shim: the extensionless
// shim is not spawnable on Windows (the CVE-2024-27980 class the sibling
// scripts hit), and the .cmd variant would need shell:true, which
// concatenates args UNESCAPED — a hazard for the temp project path. The JS
// entry behaves identically on every platform.
execFileSync(process.execPath, ['node_modules/typescript/bin/tsc', '-b', join(tmp, 'tsconfig.json')], { cwd: root, stdio: 'pipe' })
} catch (error: unknown) {
const failed = error as { stdout?: Buffer; stderr?: Buffer }
const out = `${failed.stdout?.toString() ?? ''}${failed.stderr?.toString() ?? ''}`
+8 -8
View File
@@ -129,8 +129,8 @@ const SERVICE_ROLES: ServiceRole[] = [
pkg: 'user-interaction',
title: 'Human question/answer seam',
mode: 'seam',
implementations: ['stdio-agent', 'acp'],
consumers: ['tool-ask-user', 'stdio-agent', 'acp'],
implementations: ['stdio-demo', 'acp'],
consumers: ['tool-ask-user', 'stdio-demo', 'acp'],
note: 'UI front doors provide the active human-answer provider; tool-ask-user pauses a tool call on the provider-neutral ask() promise.',
},
{
@@ -147,7 +147,7 @@ const SERVICE_ROLES: ServiceRole[] = [
pkg: 'agent',
title: 'Agent registry',
mode: 'core',
consumers: ['agent-loop', 'acp', 'subagent-inprocess', 'stdio-agent', 'invariants'],
consumers: ['agent-loop', 'acp', 'subagent-inprocess', 'stdio-demo', 'invariants'],
note: 'Owns live Agent handles and the create/resume factory seam.',
},
{
@@ -155,7 +155,7 @@ const SERVICE_ROLES: ServiceRole[] = [
pkg: 'agent-loop',
title: 'Concrete loop driver',
mode: 'bundle',
consumers: ['agent-core'],
consumers: ['agent-spine-demo'],
note: 'The one concrete loop plugin; extension packages depend on dsh-agent events and services, not on this package.',
},
{
@@ -422,11 +422,11 @@ type AppExample = typeof APP_EXAMPLES[number]
function renderAppExpansion(lines: string[], appNode: string, pluginName: string): void {
const agentCore = nodeId('bundle', 'agent_core')
const jsonl = nodeId('bundle', 'jsonl')
lines.push(` ${appNode} --> ${agentCore}["@deepseek-ai/dsh-agent-core"]`)
lines.push(` ${appNode} --> ${agentCore}["@deepseek-ai/dsh-agent-spine-demo"]`)
lines.push(` ${appNode} --> ${jsonl}["@deepseek-ai/dsh-session-persistence-jsonl"]`)
if (pluginName === '@deepseek-ai/dsh-stdio-agent') {
if (pluginName === '@deepseek-ai/dsh-stdio-demo') {
lines.push(` ${appNode} --> ${nodeId('frontdoor', 'stdio')}["readline UI<br/>console logger<br/>pre-created main agent"]`)
} else if (pluginName === '@deepseek-ai/dsh-acp-agent') {
} else if (pluginName === '@deepseek-ai/dsh-acp-demo') {
lines.push(` ${appNode} --> ${nodeId('frontdoor', 'acp')}["@deepseek-ai/dsh-acp<br/>JSON-RPC stdio bridge<br/>sessions created by client"]`)
}
lines.push(
@@ -452,7 +452,7 @@ function renderAppComposition(example: AppExample): string {
const pluginNode = nodeId(`plugin_${example.id}`, plugin.id)
lines.push(` ${pluginNode}["${escLabel(plugin.id)}<br/>${escLabel(plugin.name)}"]`)
lines.push(` cfg --> ${pluginNode}`)
if (plugin.name === '@deepseek-ai/dsh-stdio-agent' || plugin.name === '@deepseek-ai/dsh-acp-agent') {
if (plugin.name === '@deepseek-ai/dsh-stdio-demo' || plugin.name === '@deepseek-ai/dsh-acp-demo') {
renderAppExpansion(lines, pluginNode, plugin.name)
}
}
+10 -2
View File
@@ -6,8 +6,16 @@ import { join } from 'node:path'
const git = spawnSync('git', ['rev-parse', '--git-dir'], { stdio: 'ignore' })
if (git.status !== 0) process.exit(0)
const lefthook = join(process.cwd(), 'node_modules', '.bin', process.platform === 'win32' ? 'lefthook.cmd' : 'lefthook')
const isWindows = process.platform === 'win32'
const lefthook = join(process.cwd(), 'node_modules', '.bin', isWindows ? 'lefthook.cmd' : 'lefthook')
if (!existsSync(lefthook)) process.exit(0)
const result = spawnSync(lefthook, ['install', '--force'], { stdio: 'inherit' })
// On Windows the bin shim is a `.cmd` file, and recent Node (CVE-2024-27980)
// refuses to launch `.cmd`/`.bat` via spawn without `shell: true` — it returns
// `EINVAL` with a null status, which would otherwise fail postinstall. Quote
// the path because a shell re-parses the command line and the path may contain
// spaces. POSIX needs no shell: the extensionless shim is directly executable.
const result = isWindows
? spawnSync(`"${lefthook}"`, ['install', '--force'], { stdio: 'inherit', shell: true })
: spawnSync(lefthook, ['install', '--force'], { stdio: 'inherit' })
process.exit(result.status ?? 1)
+8 -1
View File
@@ -12,6 +12,13 @@ const CONCURRENCY_ENV = 'DSH_PUBLINT_CONCURRENCY'
const root = resolve(import.meta.dirname, '..')
const packagesRoot = resolve(root, 'packages')
// Run publint's JS CLI through the current node, not the .bin shim: the
// extensionless shim isn't spawnable on Windows (CVE-2024-27980) and the .cmd
// variant needs shell:true, which space-joins args UNESCAPED (DEP0190) and
// breaks when the repo path contains spaces. The JS entry is identical on every
// platform (`bin` is `./src/cli.js` per publint's package.json).
const publintCli = resolve(root, 'node_modules/publint/src/cli.js')
type PublintResult =
| { path: string; status: 'passed'; stdout: string; stderr: string }
| { path: string; status: 'failed'; stdout: string; stderr: string; message: string }
@@ -50,7 +57,7 @@ function outputText(value: unknown): string {
async function runPublint(path: string): Promise<PublintResult> {
try {
const { stdout, stderr } = await execFileAsync('node_modules/.bin/publint', [path], {
const { stdout, stderr } = await execFileAsync(process.execPath, [publintCli, path], {
cwd: root,
encoding: 'utf8',
maxBuffer: 10 * 1024 * 1024,
+2 -2
View File
@@ -335,8 +335,8 @@ function builtBinSmokeGate(): Gate {
'run',
'--config',
'vitest.e2e.config.ts',
'packages/ui/stdio-agent/tests/built-bin.e2e.ts',
'packages/ui/acp-agent/tests/built-bin.e2e.ts',
'packages/examples/stdio-demo/tests/built-bin.e2e.ts',
'packages/examples/acp-demo/tests/built-bin.e2e.ts',
// The worker-entry packages' built bundles: the only automated proof
// that lib/index.js resolves its sibling lib/worker.cjs under plain node
// (the e2e lane runs unbuilt, so these files self-skip there).
+1 -1
View File
@@ -55,7 +55,7 @@ CUSTOM_CORDIS = """\
- id: jsonrpc
name: '@deepseek-ai/dsh-jsonrpc'
- id: agent-core
name: '@deepseek-ai/dsh-agent-core'
name: '@deepseek-ai/dsh-agent-spine-demo'
config:
tools:
mode: both
+5 -1
View File
@@ -143,7 +143,11 @@ try {
.join('\n')
writeFileSync(resolve(tmp, 'index.ts'), `${imports}\n`)
execFileSync(resolve(root, 'node_modules/.bin/tsc'), ['-p', resolve(tmp, 'tsconfig.json'), '--pretty', 'false'], {
// tsc's JS entry via the current node, not the .bin shim: the extensionless
// shim isn't spawnable on Windows (CVE-2024-27980) and the .cmd variant needs
// shell:true, which space-joins args UNESCAPED (DEP0190) — a hazard for the
// temp tsconfig path. The JS entry behaves identically on every platform.
execFileSync(process.execPath, ['node_modules/typescript/bin/tsc', '-p', resolve(tmp, 'tsconfig.json'), '--pretty', 'false'], {
cwd: root,
stdio: 'pipe',
})
@@ -42,7 +42,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/bash/bash-local': { kind: 'indirect', reason: 'The executor backend delegates model rendering to dsh-tool-bash.' },
'packages/code-runtime/code-runtime': { kind: 'indirect', reason: 'The service interface delegates model rendering to Code Mode in dsh-tools.' },
'packages/code-runtime/code-runtime-worker': { kind: 'indirect', reason: 'The worker backend delegates model rendering to Code Mode in dsh-tools.' },
'packages/core/agent-core': { kind: 'indirect', reason: 'The bundle only mounts model-facing child plugins.' },
'packages/examples/agent-spine-demo': { kind: 'indirect', reason: 'The bundle only mounts model-facing child plugins.' },
'packages/fs/fs': { kind: 'indirect', reason: 'The service interface delegates model rendering to dsh-tool-fs.' },
'packages/fs/fs-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-fs.' },
'packages/hooks/hook-protocol': { kind: 'indirect', reason: 'Only the hook bridge plugins render decoded hook output to a model.' },
@@ -58,9 +58,9 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/support/loader-smoke': { kind: 'none', reason: 'The test harness observes child-process streams without changing live requests.' },
'packages/support/llm-replay': { kind: 'none', reason: 'The keyless adapter invokes no provider model.' },
'packages/support/subagent-mock': { kind: 'indirect', reason: 'Only dsh-tool-subagent renders its configured test outcome.' },
'packages/ui/acp-agent': { kind: 'indirect', reason: 'The app bundle delegates request composition to dsh-agent-core and dsh-acp.' },
'packages/examples/acp-demo': { kind: 'indirect', reason: 'The app bundle delegates request composition to dsh-agent-spine-demo and dsh-acp.' },
'packages/ui/app-boot': { kind: 'indirect', reason: 'Only the loaded plugin tree contributes model context.' },
'packages/ui/jsonrpc-agent': { kind: 'indirect', reason: 'Only the externally configured plugin tree contributes model context.' },
'packages/examples/jsonrpc-demo': { kind: 'indirect', reason: 'Only the externally configured plugin tree contributes model context.' },
'packages/ui/permission': { kind: 'indirect', reason: 'The service writes mechanism events rendered by dsh-user-approval and dsh-tool-bash.' },
'packages/ui/user-interaction': { kind: 'indirect', reason: 'Model-facing consumers render provider answers and seam errors.' },
'packages/util/timeout': { kind: 'indirect', reason: 'Only timeout consumers render timeout outcomes.' },