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:
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"docs/testing.md": 800,
|
||||
"examples/AGENTS.md": 200,
|
||||
"packages/AGENTS.md": 290,
|
||||
"packages/README.md": 710
|
||||
"packages/README.md": 760
|
||||
}
|
||||
@@ -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() ?? ''}`
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.' },
|
||||
|
||||
Reference in New Issue
Block a user