Merge remote-tracking branch 'origin/master' into worktree/web-multimodal-image-input
# Conflicts: # .agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml # docs/core-data-structures/core.i18n.yaml # docs/module-graph.md # packages/client/ui-conversation/src/client/chat/AssistantMarkdown.tsx # packages/client/ui-conversation/src/client/index.ts # packages/compact/compact-basic/README.i18n.yaml
This commit is contained in:
841 files changed
+13085
-5978
No files matched your search
@@ -0,0 +1,50 @@
|
||||
# Keyless counterpart to child-question.cordis.yml: keep the real interaction
|
||||
# seam, model-facing tool, and tripwire provider while replacing DeepSeek with
|
||||
# per-session replay.
|
||||
- id: base
|
||||
name: '@cordisjs/plugin-include'
|
||||
config:
|
||||
path: ./cordis.yml
|
||||
patches:
|
||||
- id: llm-deepseek
|
||||
name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
disabled: true
|
||||
- id: sandbox
|
||||
name: '@deepseek-ai/dsh-sandbox-local'
|
||||
config:
|
||||
runnerCommand:
|
||||
- bash
|
||||
- -c
|
||||
- while [ "$1" != "--" ]; do shift; done; shift; exec "$@"
|
||||
- passthrough-runner
|
||||
runnerFailureSignatures:
|
||||
- 'passthrough-runner: profile rejected'
|
||||
- id: acp-agent
|
||||
name: '@deepseek-ai/dsh-acp-demo'
|
||||
config:
|
||||
provider: deepseek-official
|
||||
model: deepseek-v4-flash
|
||||
persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
|
||||
persistenceCompression: none
|
||||
workspaceContext:
|
||||
maxBytes: 65536
|
||||
persona: |
|
||||
You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
|
||||
|
||||
Verify your work by running the code or tests. Keep answers brief and factual.
|
||||
- insert:
|
||||
- id: llm-replay
|
||||
name: '@deepseek-ai/dsh-llm-replay'
|
||||
config:
|
||||
providers:
|
||||
- id: deepseek-official
|
||||
name: DeepSeek
|
||||
models:
|
||||
- id: deepseek-v4-flash
|
||||
- id: deepseek-v4-pro
|
||||
- id: user-interaction
|
||||
name: '@deepseek-ai/dsh-user-interaction'
|
||||
- id: tool-ask-user
|
||||
name: '@deepseek-ai/dsh-tool-ask-user'
|
||||
- id: child-question-tripwire
|
||||
name: './tests/fixtures/child-question-tripwire.ts'
|
||||
@@ -0,0 +1,14 @@
|
||||
# Snapshot-only human-interaction composition. The provider is a tripwire: the
|
||||
# runtime-owned child must be rejected by the seam before any UI wait begins.
|
||||
- id: base
|
||||
name: '@cordisjs/plugin-include'
|
||||
config:
|
||||
path: ./cordis.yml
|
||||
patches:
|
||||
- insert:
|
||||
- id: user-interaction
|
||||
name: '@deepseek-ai/dsh-user-interaction'
|
||||
- id: tool-ask-user
|
||||
name: '@deepseek-ai/dsh-tool-ask-user'
|
||||
- id: child-question-tripwire
|
||||
name: './tests/fixtures/child-question-tripwire.ts'
|
||||
@@ -40,6 +40,7 @@ const FS_CONFIG = fileURLToPath(new URL('../fs.cordis.yml', import.meta.url))
|
||||
const SESSION_QUERY_CONFIG = fileURLToPath(new URL('../session-query.cordis.yml', import.meta.url))
|
||||
const PTY_CONFIG = fileURLToPath(new URL('../pty.cordis.yml', import.meta.url))
|
||||
const DEPTH_TWO_CONFIG = fileURLToPath(new URL('../depth-two.cordis.yml', import.meta.url))
|
||||
const CHILD_QUESTION_CONFIG = fileURLToPath(new URL('../child-question.cordis.yml', import.meta.url))
|
||||
const SESSION_SANDBOX_ROOT_CONFIG = fileURLToPath(new URL('../session-sandbox-root.cordis.yml', import.meta.url))
|
||||
const RETRY_CONFIG = fileURLToPath(new URL('../retry.cordis.yml', import.meta.url))
|
||||
const SESSION_TITLE_CONFIG = fileURLToPath(new URL('../session-title.cordis.yml', import.meta.url))
|
||||
@@ -90,8 +91,8 @@ async function prepareFsSearchWorkspace(cwd: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Migrate backend-oriented scenarios to the headless stream-json suite;
|
||||
// this ACP suite should eventually retain only automation-protocol contracts.
|
||||
// TODO(acp-snapshot-ownership): Move backend/product scenarios to headless while
|
||||
// retaining ACP protocol contracts here.
|
||||
|
||||
function fixtureRecords(name: string): unknown[] {
|
||||
return readFileSync(join(SNAPSHOTS_DIR, name, 'session.jsonl'), 'utf8')
|
||||
@@ -351,6 +352,19 @@ const SCENARIOS: Scenario[] = [
|
||||
overridden: true,
|
||||
configPath: DEPTH_TWO_CONFIG,
|
||||
},
|
||||
// Authored keyless replay through the assembled app: a one-shot child calls
|
||||
// the real ask_user_question tool, the runtime-ownership guard rejects before
|
||||
// the tripwire provider, and the child carries the unresolved decision in its
|
||||
// final result so the parent can complete instead of waiting forever.
|
||||
{
|
||||
name: 'subagent-child-question-rejection',
|
||||
hasModelTurn: true,
|
||||
recorded: false,
|
||||
pinsHeader: true,
|
||||
headerClass: 'child-question',
|
||||
systemPromptSource: 'text-turn',
|
||||
configPath: CHILD_QUESTION_CONFIG,
|
||||
},
|
||||
// The workflow tool: the model writes a one-child orchestration script; the
|
||||
// child runs as a spawn subagent under the worker-thread engine (its session is the
|
||||
// child fixture), and the tool result carries the script's return value.
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { Context } from 'cordis'
|
||||
import '@deepseek-ai/dsh-user-interaction'
|
||||
|
||||
/** Snapshot-only provider whose invocation means the child guard failed. */
|
||||
export const name = 'child-question-tripwire'
|
||||
|
||||
/** User-interaction service required by the tripwire provider. */
|
||||
export const inject = ['userInteraction']
|
||||
|
||||
/** Register a provider that must remain unreachable for the delegated call. */
|
||||
export function apply(ctx: Context): void {
|
||||
ctx.userInteraction.registerProvider({
|
||||
async ask() {
|
||||
throw new Error('snapshot tripwire: delegated question reached the UI provider')
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -34,12 +34,22 @@
|
||||
# budget, so the local numeric default cannot apply here.
|
||||
maxDepth: 'provider-managed'
|
||||
|
||||
- id: cli-agent
|
||||
name: '@deepseek-ai/dsh-cli-demo'
|
||||
- id: agent-spine
|
||||
name: '@deepseek-ai/dsh-agent-spine-demo'
|
||||
config:
|
||||
provider: mock
|
||||
model: mock-delegate
|
||||
agents:
|
||||
- id: main
|
||||
provider: mock
|
||||
model: mock-delegate
|
||||
cwd: !!js process.cwd()
|
||||
persona: 'Test ACP subagent cwd inheritance.'
|
||||
persistenceRoot: './.sessions'
|
||||
persistenceCompression: 'none'
|
||||
workspaceContext: false
|
||||
|
||||
- id: persistence
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: './.sessions'
|
||||
compression: 'none'
|
||||
|
||||
- id: checkpoint-policy
|
||||
name: '@deepseek-ai/dsh-session-checkpoint-policy'
|
||||
@@ -2,14 +2,14 @@
|
||||
/** Test driver: one delegation turn through a headless Loader composition. */
|
||||
|
||||
import { boot, resolveConfigPath } from '@deepseek-ai/dsh-app-boot'
|
||||
import { runOneShot } from '@deepseek-ai/dsh-cli-demo/src/cli.ts'
|
||||
import { runFixtureTurn } from '@deepseek-ai/dsh-loader-smoke'
|
||||
|
||||
const configPath = process.argv[2]
|
||||
if (configPath === undefined) throw new Error('acp-subagent cwd driver requires a config path')
|
||||
|
||||
const ctx = await boot('acp-subagent-cwd-e2e', resolveConfigPath(configPath, undefined))
|
||||
try {
|
||||
await runOneShot(ctx, { task: 'delegate' })
|
||||
await runFixtureTurn(ctx, { task: 'delegate' })
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
}
|
||||
+15
-4
@@ -31,10 +31,21 @@
|
||||
enableRunInBackground: false
|
||||
maxDepth: 'provider-managed'
|
||||
|
||||
- id: cli-agent
|
||||
name: '@deepseek-ai/dsh-cli-demo'
|
||||
- id: agent-spine
|
||||
name: '@deepseek-ai/dsh-agent-spine-demo'
|
||||
config:
|
||||
provider: mock
|
||||
model: mock-delegate
|
||||
agents:
|
||||
- id: main
|
||||
provider: mock
|
||||
model: mock-delegate
|
||||
cwd: !!js process.cwd()
|
||||
persona: 'This composition test must not start a model turn.'
|
||||
workspaceContext: false
|
||||
|
||||
- id: persistence
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: './.sessions'
|
||||
|
||||
- id: checkpoint-policy
|
||||
name: '@deepseek-ai/dsh-session-checkpoint-policy'
|
||||
@@ -20,10 +20,21 @@
|
||||
enableRunInBackground: false
|
||||
maxDepth: 'provider-managed'
|
||||
|
||||
- id: cli-agent
|
||||
name: '@deepseek-ai/dsh-cli-demo'
|
||||
- id: agent-spine
|
||||
name: '@deepseek-ai/dsh-agent-spine-demo'
|
||||
config:
|
||||
provider: mock
|
||||
model: mock-delegate
|
||||
agents:
|
||||
- id: main
|
||||
provider: mock
|
||||
model: mock-delegate
|
||||
cwd: !!js process.cwd()
|
||||
persona: 'This composition test must not start a model turn.'
|
||||
workspaceContext: false
|
||||
|
||||
- id: persistence
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: './.sessions'
|
||||
|
||||
- id: checkpoint-policy
|
||||
name: '@deepseek-ai/dsh-session-checkpoint-policy'
|
||||
@@ -5,7 +5,7 @@
|
||||
{"type":"step/start","seq":3,"time":1785498773754,"data":{"turn":1,"step":1}}
|
||||
{"type":"user/message","seq":4,"time":1785498773754,"data":{"content":[{"type":"text","text":"Load the snapshot-skill skill with the skill tool, then reply DONE."}],"source":{"kind":"user"},"role":"user","id":"0ca31b92-27ac-451d-98d3-d1e5f605454b"},"surfaceOp":"append"}
|
||||
{"type":"user/message","seq":5,"time":1785498773755,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"3fc7e2f8-90fc-496c-b516-700cef1d86f1"},"surfaceOp":"append"}
|
||||
{"type":"user/message","seq":6,"time":1785730426818,"data":{"content":[{"type":"text","text":"<system-reminder>\nA skill is a reusable set of task-specific instructions. The following skills are available in this session:\n\n<available_skills>\n- `model-only-skill`: Prove user-disabled skills remain available to the model.\n- `snapshot-skill`: Exercise project skill discovery and loading in snapshot tests.\n</available_skills>\n\nIf the user names a skill, or the task clearly matches a skill's description, call the `skill` tool with the exact skill name before taking task actions. Load all applicable skills, then follow their full instructions. This catalog contains summaries only; do not infer or follow a skill's instructions until it has been loaded.\n</system-reminder>"}],"source":{"kind":"skill-catalog","form":"catalog","entries":[{"name":"model-only-skill","description":"Prove user-disabled skills remain available to the model."},{"name":"snapshot-skill","description":"Exercise project skill discovery and loading in snapshot tests."}]},"role":"user","id":"60880315-9799-44c8-8a99-e6fe9ee5bdc5"},"surfaceOp":"append"}
|
||||
{"type":"user/message","seq":6,"time":1785730426818,"data":{"content":[{"type":"text","text":"<system-reminder>\nA skill is a reusable set of task-specific instructions. The following skills are available in this session:\n\n<available_skills>\n- `model-only-skill`: Prove user-disabled skills remain available to the model.\n- `snapshot-skill`: Exercise project skill discovery and loading in snapshot tests.\n</available_skills>\n\nIf the user names a skill, or the task clearly matches a skill's description, call the `skill` tool with the exact skill name before taking task actions. Load all applicable skills, then follow their full instructions. This catalog contains summaries only; do not infer or follow a skill's instructions until it has been loaded.\nA user may also invoke a skill directly; its <skill_content> block then appears in this conversation. Follow it, and do not call the `skill` tool again for that skill.\n</system-reminder>"}],"source":{"kind":"skill-catalog","form":"catalog","entries":[{"name":"model-only-skill","description":"Prove user-disabled skills remain available to the model."},{"name":"snapshot-skill","description":"Exercise project skill discovery and loading in snapshot tests."}]},"role":"user","id":"60880315-9799-44c8-8a99-e6fe9ee5bdc5"},"surfaceOp":"append"}
|
||||
{"type":"session/title","seq":7,"time":1785730426818,"data":{"title":"Load the snapshot-skill skill with","messageSeqs":[4],"source":{"kind":"fallback"}}}
|
||||
{"type":"request/header","seq":8,"time":1785498773756,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
|
||||
{"type":"request/context","seq":9,"time":1785730426819,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"steps": [
|
||||
{ "op": "initialize" },
|
||||
{ "op": "newSession" },
|
||||
{ "op": "prompt", "text": "Delegate one question check. Ask the child to call ask_user_question once about the CUDA fallback and return any unresolved question in its final result." }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{"type":"session","version":0,"id":"55555555-5555-4555-8555-555555555555","createdAt":2001,"cwd":"{{cwd}}","parentSession":"44444444-4444-4444-8444-444444444444","origin":"subagent","delegationDepth":1}
|
||||
{"type":"agent/inbox/spliced","seq":0,"time":1786173701247,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Call ask_user_question once to ask whether deployment should use the CUDA fallback. If the tool returns an error, include the unresolved question verbatim in your final result."}],"source":{"kind":"user"},"role":"user","id":"106c2785-219e-46e8-8386-497ac6a98f68"}]}}
|
||||
{"type":"turn/start","seq":1,"time":1786173701247,"data":{"turn":1}}
|
||||
{"type":"agent/inbox/spliced","seq":2,"time":1786173701247,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
|
||||
{"type":"subagent/descriptor","seq":3,"time":1786173701270,"data":{"version":2,"mode":"one-shot","provider":"spawn","label":"Check deployment question"}}
|
||||
{"type":"step/start","seq":4,"time":1786173701272,"data":{"turn":1,"step":1}}
|
||||
{"type":"user/message","seq":5,"time":1786173701272,"data":{"content":[{"type":"text","text":"Call ask_user_question once to ask whether deployment should use the CUDA fallback. If the tool returns an error, include the unresolved question verbatim in your final result."}],"source":{"kind":"user"},"role":"user","id":"106c2785-219e-46e8-8386-497ac6a98f68"},"surfaceOp":"append"}
|
||||
{"type":"user/message","seq":6,"time":1786173701272,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"fafefa37-7640-4c80-a00a-6a0c3ce46281"},"surfaceOp":"append"}
|
||||
{"type":"session/title","seq":7,"time":1786173701272,"data":{"title":"Call ask_user_question once to ask","messageSeqs":[5],"source":{"kind":"fallback"}}}
|
||||
{"type":"request/header","seq":8,"time":1786173701272,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
|
||||
{"type":"request/context","seq":9,"time":1786173701273,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
|
||||
{"type":"assistant/chunk","seq":10,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
|
||||
{"type":"assistant/chunk","seq":11,"time":1786173701278,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"call_child_question","name":"ask_user_question","argumentsDelta":"{\"questions\":[{\"id\":\"cuda-fallback\",\"header\":\"Deployment\",\"question\":\"Should deployment use the CUDA fallback?\"}]}"}}}
|
||||
{"type":"assistant/chunk","seq":12,"time":1786173701279,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_child_question","name":"ask_user_question","arguments":"{\"questions\":[{\"id\":\"cuda-fallback\",\"header\":\"Deployment\",\"question\":\"Should deployment use the CUDA fallback?\"}]}"}}}}
|
||||
{"type":"assistant/chunk","seq":13,"time":1786173701279,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}
|
||||
{"type":"assistant/chunk","seq":14,"time":1786173701279,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
|
||||
{"type":"assistant/message","seq":15,"time":1786173701279,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_child_question","name":"ask_user_question","arguments":"{\"questions\":[{\"id\":\"cuda-fallback\",\"header\":\"Deployment\",\"question\":\"Should deployment use the CUDA fallback?\"}]}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"301e1969-74b2-45d8-a764-604b806f1c01"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[10,11,12,13,14],"surfaceOp":"append"}
|
||||
{"type":"tool/call","seq":16,"time":1786173701279,"data":{"turn":1,"step":1,"callId":"call_child_question","name":"ask_user_question","arguments":"{\"questions\":[{\"id\":\"cuda-fallback\",\"header\":\"Deployment\",\"question\":\"Should deployment use the CUDA fallback?\"}]}"}}
|
||||
{"type":"tool/result","seq":17,"time":1786173701292,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_child_question"},"content":[{"type":"tool-result","toolCallId":"call_child_question","content":[{"type":"text","text":"Error: human interaction is unavailable while the calling agent is owned by another live agent; include the unresolved question or decision in the child agent's final result"}],"isError":true}],"role":"user","id":"b9fc0a38-47bb-4335-a8e4-c881ed66bbc3"},"error":{"name":"UserInteractionError","code":"DELEGATED_CALLER"}},"sourceEventSeqs":[16],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":18,"time":1786173701292,"data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","seq":19,"time":1786173701309,"data":{"turn":1,"step":2}}
|
||||
{"type":"assistant/chunk","seq":20,"time":1786173701314,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
|
||||
{"type":"assistant/chunk","seq":21,"time":1786173701314,"data":{"turn":1,"step":2,"chunk":{"type":"text-delta","index":0,"text":"UNRESOLVED: Should deployment use the CUDA fallback?"}}}
|
||||
{"type":"assistant/chunk","seq":22,"time":1786173701314,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"UNRESOLVED: Should deployment use the CUDA fallback?"}}}}
|
||||
{"type":"assistant/chunk","seq":23,"time":1786173701314,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":4}}}}
|
||||
{"type":"assistant/chunk","seq":24,"time":1786173701315,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
|
||||
{"type":"assistant/message","seq":25,"time":1786173701315,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"UNRESOLVED: Should deployment use the CUDA fallback?"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"5f2ada85-5967-4ed8-9e16-eaff2af847b5"},"usage":{"inputTokens":10,"outputTokens":4}},"sourceEventSeqs":[20,21,22,23,24],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":26,"time":1786173701315,"data":{"turn":1,"step":2}}
|
||||
{"type":"turn/end","seq":27,"time":1786173701315,"data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
@@ -0,0 +1,28 @@
|
||||
{"type":"session","version":0,"id":"44444444-4444-4444-8444-444444444444","createdAt":2000,"cwd":"{{cwd}}","delegationDepth":0}
|
||||
{"type":"agent/inbox/spliced","seq":0,"time":1786173701174,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Delegate one question check. Ask the child to call ask_user_question once about the CUDA fallback and return any unresolved question in its final result."}],"source":{"kind":"user"},"role":"user","id":"851bea02-2961-471a-84ec-3b068c451db0"}]}}
|
||||
{"type":"turn/start","seq":1,"time":1786173701175,"data":{"turn":1}}
|
||||
{"type":"agent/inbox/spliced","seq":2,"time":1786173701175,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
|
||||
{"type":"step/start","seq":3,"time":1786173701216,"data":{"turn":1,"step":1}}
|
||||
{"type":"user/message","seq":4,"time":1786173701216,"data":{"content":[{"type":"text","text":"Delegate one question check. Ask the child to call ask_user_question once about the CUDA fallback and return any unresolved question in its final result."}],"source":{"kind":"user"},"role":"user","id":"851bea02-2961-471a-84ec-3b068c451db0"},"surfaceOp":"append"}
|
||||
{"type":"user/message","seq":5,"time":1786173701217,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"8ef74c46-9e80-475c-9093-0e85ba92e346"},"surfaceOp":"append"}
|
||||
{"type":"session/title","seq":6,"time":1786173701217,"data":{"title":"Delegate one question check. Ask","messageSeqs":[4],"source":{"kind":"fallback"}}}
|
||||
{"type":"request/header","seq":7,"time":1786173701218,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
|
||||
{"type":"request/context","seq":8,"time":1786173701219,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
|
||||
{"type":"assistant/chunk","seq":9,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
|
||||
{"type":"assistant/chunk","seq":10,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"call_question_child","name":"subagent","argumentsDelta":"{\"description\":\"Check deployment question\",\"prompt\":\"Call ask_user_question once to ask whether deployment should use the CUDA fallback. If the tool returns an error, include the unresolved question verbatim in your final result.\"}"}}}
|
||||
{"type":"assistant/chunk","seq":11,"time":1786173701233,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_question_child","name":"subagent","arguments":"{\"description\":\"Check deployment question\",\"prompt\":\"Call ask_user_question once to ask whether deployment should use the CUDA fallback. If the tool returns an error, include the unresolved question verbatim in your final result.\"}"}}}}
|
||||
{"type":"assistant/chunk","seq":12,"time":1786173701233,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}
|
||||
{"type":"assistant/chunk","seq":13,"time":1786173701233,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
|
||||
{"type":"assistant/message","seq":14,"time":1786173701233,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_question_child","name":"subagent","arguments":"{\"description\":\"Check deployment question\",\"prompt\":\"Call ask_user_question once to ask whether deployment should use the CUDA fallback. If the tool returns an error, include the unresolved question verbatim in your final result.\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"f8909de9-23ae-4dbe-a8c1-eaf1e8f2aba5"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[9,10,11,12,13],"surfaceOp":"append"}
|
||||
{"type":"tool/call","seq":15,"time":1786173701234,"data":{"turn":1,"step":1,"callId":"call_question_child","name":"subagent","arguments":"{\"description\":\"Check deployment question\",\"prompt\":\"Call ask_user_question once to ask whether deployment should use the CUDA fallback. If the tool returns an error, include the unresolved question verbatim in your final result.\"}"}}
|
||||
{"type":"tool/result","seq":16,"time":1786173701316,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_question_child"},"content":[{"type":"tool-result","toolCallId":"call_question_child","content":[{"type":"text","text":"UNRESOLVED: Should deployment use the CUDA fallback?"}],"isError":false}],"role":"user","id":"fdc5b075-574b-48c6-bdda-1b6442edfbef"}},"sourceEventSeqs":[15],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":17,"time":1786173701317,"data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","seq":18,"time":1786173701334,"data":{"turn":1,"step":2}}
|
||||
{"type":"assistant/chunk","seq":19,"time":1786173701339,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
|
||||
{"type":"assistant/chunk","seq":20,"time":1786173701339,"data":{"turn":1,"step":2,"chunk":{"type":"text-delta","index":0,"text":"PARENT_COMPLETED"}}}
|
||||
{"type":"assistant/chunk","seq":21,"time":1786173701340,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"PARENT_COMPLETED"}}}}
|
||||
{"type":"assistant/chunk","seq":22,"time":1786173701340,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":2}}}}
|
||||
{"type":"assistant/chunk","seq":23,"time":1786173701340,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
|
||||
{"type":"assistant/message","seq":24,"time":1786173701340,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"PARENT_COMPLETED"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"700b9e56-965e-406a-bf5c-2db06b96c536"},"usage":{"inputTokens":10,"outputTokens":2}},"sourceEventSeqs":[19,20,21,22,23],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":25,"time":1786173701340,"data":{"turn":1,"step":2}}
|
||||
{"type":"turn/end","seq":26,"time":1786173701340,"data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentInfo":{"name":"deepseek-harness-acp","version":"0.0.1"},"agentCapabilities":{"promptCapabilities":{"image":false,"audio":false,"embeddedContext":false}},"authMethods":[]}}
|
||||
{"jsonrpc":"2.0","id":2,"result":{"sessionId":"{{sessionId}}"}}
|
||||
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"PARENT_COMPLETED"}}}}
|
||||
{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}
|
||||
+565
@@ -0,0 +1,565 @@
|
||||
{
|
||||
"initial": [
|
||||
{
|
||||
"name": "ask_user_question",
|
||||
"description": "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. Send one or more questions, each with a stable id that will be echoed in the answer.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"questions": {
|
||||
"type": "array",
|
||||
"description": "Questions to ask the user before continuing.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Stable id for this question; echoed in the answer."
|
||||
},
|
||||
"question": {
|
||||
"type": "string",
|
||||
"description": "The specific question to ask the user."
|
||||
},
|
||||
"header": {
|
||||
"type": "string",
|
||||
"description": "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"."
|
||||
},
|
||||
"options": {
|
||||
"type": "array",
|
||||
"description": "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Short user-facing option label."
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "One sentence explaining the tradeoff or impact."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"label"
|
||||
]
|
||||
}
|
||||
},
|
||||
"multi_select": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the user may select more than one option. Defaults to false."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"question"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"questions"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bash",
|
||||
"description": "Execute a bash command (`bash -c`) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under <mode> mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. Set `run_in_background: true` for long-running commands: the call returns a task id immediately; read its output with `task_output` and stop it with `task_kill`. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "The bash command to execute."
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"npm install\" → \"Install package dependencies\"."
|
||||
},
|
||||
"timeoutMs": {
|
||||
"type": "number",
|
||||
"description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry."
|
||||
},
|
||||
"workdir": {
|
||||
"type": "string",
|
||||
"description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Run in the background and return a task id immediately (collect with task_output, stop with task_kill). No timeout applies."
|
||||
},
|
||||
"sandbox_permissions": {
|
||||
"type": "string",
|
||||
"description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.",
|
||||
"enum": [
|
||||
"workspace-write",
|
||||
"danger-full-access"
|
||||
]
|
||||
},
|
||||
"justification": {
|
||||
"type": "string",
|
||||
"description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"command",
|
||||
"description"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "create_goal",
|
||||
"description": "Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say \"create a goal\". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"objective": {
|
||||
"type": "string",
|
||||
"description": "The concrete completion objective inferred from the direct human request."
|
||||
},
|
||||
"max_goal_rounds": {
|
||||
"type": "number",
|
||||
"description": "Optional positive safe-integer limit on automatic continuation rounds."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"objective"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "edit",
|
||||
"description": "Edit an existing UTF-8 text file by replacing literal text.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file_path": {
|
||||
"type": "string",
|
||||
"description": "Path to edit, resolved by the filesystem backend."
|
||||
},
|
||||
"old_string": {
|
||||
"type": "string",
|
||||
"description": "Literal text to replace. Must match exactly."
|
||||
},
|
||||
"new_string": {
|
||||
"type": "string",
|
||||
"description": "Literal replacement text. Use an empty string to delete the match."
|
||||
},
|
||||
"replace_all": {
|
||||
"type": "boolean",
|
||||
"description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once."
|
||||
},
|
||||
"sandbox_permissions": {
|
||||
"type": "string",
|
||||
"description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.",
|
||||
"enum": [
|
||||
"workspace-write",
|
||||
"danger-full-access"
|
||||
]
|
||||
},
|
||||
"justification": {
|
||||
"type": "string",
|
||||
"description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file_path",
|
||||
"old_string",
|
||||
"new_string"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_goal",
|
||||
"description": "Read the current same-session goal, including its exact id/revision, objective, phase, completed continuation rounds, round limit, blocker reason when present, and whether another continuation is armed. Call this before updating a goal.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_agents",
|
||||
"description": "List your continuable background subagents by durable id and label. Status is a snapshot of the stored record: running means the subagent session is currently live in this process, complete means it exists only in storage and a `send_message` starts a new turn on the same conversation. The snapshot is not a delivery promise — `send_message` performs the authoritative check and may still fail. Children that could not be read are reported as diagnostics instead of being silently dropped.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ralph",
|
||||
"description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"objective": {
|
||||
"type": "string",
|
||||
"description": "The immutable completion objective for every fresh Ralph round."
|
||||
},
|
||||
"maxRounds": {
|
||||
"type": "number",
|
||||
"description": "Optional positive safe-integer round cap, bounded by the deployment ceiling."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"objective"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "read",
|
||||
"description": "Read a UTF-8 text file and return line-numbered content.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file_path": {
|
||||
"type": "string",
|
||||
"description": "Path to read, resolved by the filesystem backend."
|
||||
},
|
||||
"offset": {
|
||||
"type": "number",
|
||||
"description": "1-based first line to return. Defaults to 1."
|
||||
},
|
||||
"limit": {
|
||||
"type": "number",
|
||||
"description": "Maximum number of lines to return. Defaults to 2000."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file_path"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "send_message",
|
||||
"description": "Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"subagent_id": {
|
||||
"type": "string",
|
||||
"description": "The subagent id returned when the background subagent was started."
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "The message to deliver to the subagent."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"subagent_id",
|
||||
"message"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "skill",
|
||||
"description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The exact skill name from the available skills list."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "subagent",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) and return its final result. Use this to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent runs to completion and you receive only its final answer, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. Set `run_in_background: true` to start a background subagent that keeps its conversation: you receive only its subagent id, never its result, and it works on its own. Use this for work whose result you do not need returned by this call; `send_message` sends it more work.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "A short (3-5 word) description of the delegated task, for display."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Run as a background subagent that keeps its conversation and return only its subagent id. This call never returns its result; send it more work with send_message."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"description",
|
||||
"prompt"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "subagent_fork",
|
||||
"description": "Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn), returning only its final result. Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive only its final answer, not its intermediate steps. Set `run_in_background: true` to start a background subagent that keeps its conversation: you receive only its subagent id, never its result, and it works on its own. Use this for work whose result you do not need returned by this call; `send_message` sends it more work.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "A short (3-5 word) description of the delegated task, for display."
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
"description": "The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Run as a background subagent that keeps its conversation and return only its subagent id. This call never returns its result; send it more work with send_message."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"description",
|
||||
"prompt"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "task_kill",
|
||||
"description": "Request cancellation of a running background task by task id. Returns immediately; the task settles as killed once its work actually stops.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"task_id": {
|
||||
"type": "string",
|
||||
"description": "Task id returned by the tool that started the background work."
|
||||
},
|
||||
"reason": {
|
||||
"type": "string",
|
||||
"description": "Optional short reason, recorded in the log and forwarded to the task."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"task_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "task_list",
|
||||
"description": "List your background tasks (running and finished) with their ids, kinds, and statuses.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "task_output",
|
||||
"description": "Read a background task. Stream tasks return only output since the previous read; final-output tasks return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"task_id": {
|
||||
"type": "string",
|
||||
"description": "Task id returned by the tool that started the background work."
|
||||
},
|
||||
"wait": {
|
||||
"type": "boolean",
|
||||
"description": "Block until the task reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the task alive."
|
||||
},
|
||||
"timeout_ms": {
|
||||
"type": "number",
|
||||
"description": "Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"task_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "todo_write",
|
||||
"description": "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"todos": {
|
||||
"type": "array",
|
||||
"description": "The COMPLETE task list, replacing any previous list.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "What the task is — a short imperative line."
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "pending (not started) | in_progress (now) | completed (done).",
|
||||
"enum": [
|
||||
"pending",
|
||||
"in_progress",
|
||||
"completed"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"content",
|
||||
"status"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"todos"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "update_goal",
|
||||
"description": "Update the exact current goal revision. edit, pause, and resume require a direct top-level human request. During an automatic continuation of the current goal, complete and blocked are also allowed. blocked is rejected before the configured minimum round count; the model remains responsible for judging that the same condition persisted across those rounds and must explain it in blocked_reason.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"goal_id": {
|
||||
"type": "string",
|
||||
"description": "Exact id returned by get_goal."
|
||||
},
|
||||
"revision": {
|
||||
"type": "number",
|
||||
"description": "Exact positive revision returned by get_goal."
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"description": "edit | pause | resume | complete | blocked",
|
||||
"enum": [
|
||||
"edit",
|
||||
"pause",
|
||||
"resume",
|
||||
"complete",
|
||||
"blocked"
|
||||
]
|
||||
},
|
||||
"objective": {
|
||||
"type": "string",
|
||||
"description": "Replacement objective; valid only with action edit."
|
||||
},
|
||||
"max_goal_rounds": {
|
||||
"type": "number",
|
||||
"description": "Replacement cap; valid only with action edit."
|
||||
},
|
||||
"blocked_reason": {
|
||||
"type": "string",
|
||||
"description": "Concrete blocking condition; required only with action blocked."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"goal_id",
|
||||
"revision",
|
||||
"action"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "workflow",
|
||||
"description": "Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return <value>` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise<any>` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise<any[]>` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise<any[]>` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"script": {
|
||||
"type": "string",
|
||||
"description": "The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return <json-value>`)."
|
||||
},
|
||||
"meta": {
|
||||
"type": "object",
|
||||
"description": "The workflow identity block (plain JSON — never code).",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Short kebab-case workflow name."
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "One-line description of what the workflow does."
|
||||
},
|
||||
"whenToUse": {
|
||||
"type": "string",
|
||||
"description": "Optional guidance on when this workflow applies."
|
||||
},
|
||||
"phases": {
|
||||
"type": "array",
|
||||
"description": "Optional phase declarations matched by phase() calls.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The phase title phase() calls match by exact string."
|
||||
},
|
||||
"detail": {
|
||||
"type": "string",
|
||||
"description": "Optional one-line description of the phase."
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "Optional provider override this phase is expected to use."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Optional model override this phase is expected to use."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"title"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"description"
|
||||
]
|
||||
},
|
||||
"args": {
|
||||
"type": "object",
|
||||
"description": "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"script",
|
||||
"meta"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "write",
|
||||
"description": "Create or fully replace a UTF-8 text file.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file_path": {
|
||||
"type": "string",
|
||||
"description": "Path to write, resolved by the filesystem backend."
|
||||
},
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "Full UTF-8 text content to write."
|
||||
},
|
||||
"sandbox_permissions": {
|
||||
"type": "string",
|
||||
"description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.",
|
||||
"enum": [
|
||||
"workspace-write",
|
||||
"danger-full-access"
|
||||
]
|
||||
},
|
||||
"justification": {
|
||||
"type": "string",
|
||||
"description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"file_path",
|
||||
"content"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"changes": []
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write examples/headless-agent/README.md
|
||||
README.md: 670a91ab402cf98585f2ece70787beb3b4aaf4dc
|
||||
README.zh.md: 6c8b3b5694403c5e09f2904f5c3ca18fe569163e
|
||||
README.md: e00f3d2d4fd21a860239f3d3a3e5eb2d7520f14a
|
||||
README.zh.md: 6cd845783b1c112ba73676474b176ec28a4d0b78
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
Headless one-shot agent wiring: DeepSeek V4 + local bash and filesystem tools + subagent delegation + workflows and fresh-agent Ralph iteration + `todo_write` + JSONL persistence, with [`@deepseek-ai/dsh-cli-demo`](../../packages/examples/cli-demo) as the app front door.
|
||||
This directory owns the replay and real-model test composition for a headless coding agent: DeepSeek V4 + local bash and filesystem tools + subagent delegation + workflows and fresh-agent Ralph iteration + `todo_write` + JSONL persistence. It explicitly mounts the shared agent spine, one root agent, persistence, and checkpoint policy; it is not a second product front door.
|
||||
|
||||
## Run it
|
||||
|
||||
@@ -10,17 +10,13 @@ Headless one-shot agent wiring: DeepSeek V4 + local bash and filesystem tools +
|
||||
# repo root .env (gitignored) or exported env:
|
||||
# DEEPSEEK_API_KEY=sk-…
|
||||
# DEEPSEEK_BASE_URL=https://… # optional; defaults to the public API
|
||||
pnpm run demo:headless "fix the failing test in this workspace"
|
||||
pnpm run demo:headless --output-format json -- "summarize the implementation"
|
||||
pnpm run demo:headless --output-format stream-json -- "run the focused tests"
|
||||
pnpm run dsh run "fix the failing test in this workspace"
|
||||
```
|
||||
|
||||
Exactly one nonblank positional task is required; quote tasks containing spaces. There is no `-p` flag. `text` prints the last text-bearing assistant message, `json` prints one DSH-native result record, and `stream-json` emits the top-level session's canonical task-turn events before that record. Child sessions surface only through parent tool events and results.
|
||||
The product command is [`dsh run`](../../apps/cli/README.md): it accepts one nonblank task, creates and persists a fresh session, prints the final assistant text, and exits. The root `demo:headless` script is only an alias of that command.
|
||||
|
||||
Each invocation creates and persists a fresh session, runs all model and tool steps in one turn, flushes, disposes, and exits. This is non-interactive automation: there is no prompt, approval, resume, second turn, or stdin context. The configured tools can mutate the launch workspace, run commands, spawn child agents, and consume provider tokens.
|
||||
Snapshot suites run this directory's configuration through [`tests/fixtures/headless-driver.ts`](tests/fixtures/headless-driver.ts), an unexported test-only process that emits canonical session events as JSONL before its result record. That stream is test infrastructure, not a supported CLI output format. Child sessions surface only through parent tool events and results.
|
||||
|
||||
## Advanced configuration
|
||||
|
||||
[`advanced.cordis.yml`](advanced.cordis.yml) adds Code Mode and the Cordis tools to the shipped leaf.
|
||||
|
||||
The package-level [CLI contract](../../packages/examples/cli-demo/README.md) documents output records, exit status, cancellation, persistence, and model/token effects.
|
||||
[`advanced.cordis.yml`](advanced.cordis.yml) adds Code Mode and the Cordis tools to the test composition.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
无头单次 agent(智能体)接线:DeepSeek V4 + 本地 bash 与文件系统工具 + subagent 委托 + 工作流与全新 agent Ralph 迭代 + `todo_write` + JSONL 持久化,并以 [`@deepseek-ai/dsh-cli-demo`](../../packages/examples/cli-demo) 作为应用入口。
|
||||
本目录负责 headless coding agent(智能体)的回放和真实模型测试组装:DeepSeek V4 + 本地 bash 与文件系统工具 + subagent 委托 + 工作流与全新 agent Ralph 迭代 + `todo_write` + JSONL 持久化。本目录显式挂载共享 agent 主干、一个根 agent、持久化和检查点策略;它不是第二个产品入口。
|
||||
|
||||
## 运行
|
||||
|
||||
@@ -10,17 +10,13 @@
|
||||
# repo root .env (gitignored) or exported env:
|
||||
# DEEPSEEK_API_KEY=sk-…
|
||||
# DEEPSEEK_BASE_URL=https://… # optional; defaults to the public API
|
||||
pnpm run demo:headless "fix the failing test in this workspace"
|
||||
pnpm run demo:headless --output-format json -- "summarize the implementation"
|
||||
pnpm run demo:headless --output-format stream-json -- "run the focused tests"
|
||||
pnpm run dsh run "fix the failing test in this workspace"
|
||||
```
|
||||
|
||||
必须提供一个且仅一个非空的任务位置参数;含空格的任务需要加引号。没有 `-p` 标志。`text` 打印最后一条包含文本的 assistant 消息,`json` 打印一条 DSH 原生结果记录,`stream-json` 则在该记录之前发出顶层会话的规范任务轮次事件。子会话只通过父会话的工具事件和结果对外显示。
|
||||
产品命令是 [`dsh run`](../../apps/cli/README.md):它接受一项非空任务,创建并持久化新会话,打印最终 assistant 文本,然后退出。根目录的 `demo:headless` 脚本只是该命令的别名。
|
||||
|
||||
每次调用都会创建并持久化新会话,在一个轮次中运行所有模型和工具步骤,然后刷写持久化数据、执行 dispose(资源释放),再退出。这是非交互式自动化:没有提示符、批准、恢复、第二轮次或 stdin 上下文。已配置工具可以修改启动时所在的工作区、运行命令、spawn 子 agent,并消耗提供方 token。
|
||||
快照套件通过 [`tests/fixtures/headless-driver.ts`](tests/fixtures/headless-driver.ts) 运行本目录的配置。这个未导出且仅供测试使用的进程会在结果记录之前,以 JSONL 发出规范会话事件。该事件流属于测试基础设施,不是受支持的 CLI(命令行界面)输出格式。子会话只通过父会话的工具事件和结果对外显示。
|
||||
|
||||
## 高级配置
|
||||
|
||||
[`advanced.cordis.yml`](advanced.cordis.yml) 在已交付叶节点上添加 Code Mode 和 Cordis 工具。
|
||||
|
||||
这份包级 [CLI(命令行界面)契约](../../packages/examples/cli-demo/README.md) 说明输出记录、退出状态、取消、持久化以及模型/token 影响。
|
||||
[`advanced.cordis.yml`](advanced.cordis.yml) 在测试组装中添加 Code Mode 和 Cordis 工具。
|
||||
@@ -1,6 +1,6 @@
|
||||
# Replay counterpart to advanced.cordis.yml. It includes the base `cordis.yml`
|
||||
# directly — a config patch cannot target an entry behind a nested include — and
|
||||
# restates advanced.cordis.yml's overlay (the cli-agent config plus the
|
||||
# restates advanced.cordis.yml's overlay (the agent and persistence configs plus the
|
||||
# code-runtime and tool-cordis inserts) so the whole app config lives in one patch.
|
||||
# It re-pins `deepseek-v4-flash`: `cordis.yml` ships `deepseek-v4-pro`, but the
|
||||
# recorded corpus (request headers, provenance) was captured on flash, so replay
|
||||
@@ -15,15 +15,14 @@
|
||||
- id: llm-deepseek
|
||||
name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
disabled: true
|
||||
- id: cli-agent
|
||||
name: '@deepseek-ai/dsh-cli-demo'
|
||||
- id: agent-spine
|
||||
name: '@deepseek-ai/dsh-agent-spine-demo'
|
||||
config:
|
||||
provider: deepseek-official
|
||||
model: deepseek-v4-flash
|
||||
persistenceRoot: './.sessions'
|
||||
# Replay fixtures are raw JSONL; the whole-config patch must restate
|
||||
# the compression choice or the default zstd frames hide the logs.
|
||||
persistenceCompression: none
|
||||
agents:
|
||||
- id: main
|
||||
provider: deepseek-official
|
||||
model: deepseek-v4-flash
|
||||
cwd: !!js process.cwd()
|
||||
workspaceContext:
|
||||
maxBytes: 65536
|
||||
tools:
|
||||
@@ -32,6 +31,13 @@
|
||||
You are headless-agent, a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
|
||||
|
||||
Verify your work by running the code or tests. Keep answers brief and factual.
|
||||
- id: persistence
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: './.sessions'
|
||||
# Replay fixtures are raw JSONL; the whole-config patch must restate
|
||||
# the compression choice or the default zstd frames hide the logs.
|
||||
compression: none
|
||||
- insert:
|
||||
- id: code-runtime
|
||||
name: '@deepseek-ai/dsh-code-runtime-worker'
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
config:
|
||||
path: ./cordis.yml
|
||||
patches:
|
||||
- id: cli-agent
|
||||
name: '@deepseek-ai/dsh-cli-demo'
|
||||
- id: agent-spine
|
||||
name: '@deepseek-ai/dsh-agent-spine-demo'
|
||||
config:
|
||||
provider: deepseek-official
|
||||
model: deepseek-v4-pro
|
||||
persistenceRoot: './.sessions'
|
||||
persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'"
|
||||
agents:
|
||||
- id: main
|
||||
provider: deepseek-official
|
||||
model: deepseek-v4-pro
|
||||
cwd: !!js process.cwd()
|
||||
workspaceContext:
|
||||
maxBytes: 65536
|
||||
tools:
|
||||
@@ -19,6 +20,11 @@
|
||||
You are headless-agent, a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
|
||||
|
||||
Verify your work by running the code or tests. Keep answers brief and factual.
|
||||
- id: persistence
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: './.sessions'
|
||||
compression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'"
|
||||
- insert:
|
||||
- id: code-runtime
|
||||
name: '@deepseek-ai/dsh-code-runtime-worker'
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
thresholdRatio: 0.99
|
||||
retainTokens: 20
|
||||
maxTokens: 32
|
||||
compactionRetries: 1
|
||||
- insert:
|
||||
- id: llm-replay
|
||||
name: '@deepseek-ai/dsh-llm-replay'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<!-- Generated by scripts/gen-doc-graphs.ts - do not edit by hand.
|
||||
Run `pnpm run gen-doc-graphs` to regenerate. -->
|
||||
|
||||
# Headless Agent App Composition
|
||||
# Headless Agent Snapshot Composition
|
||||
|
||||
The headless demo combines the real DeepSeek adapter and coding capabilities with the one-shot app package, format-pure stdout, and one fresh persisted top-level session.
|
||||
The headless snapshot composition combines the real DeepSeek adapter and coding capabilities with one explicitly configured persisted top-level agent; its JSONL driver is test-only.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -18,15 +18,12 @@ flowchart LR
|
||||
cfg --> plugin_headless_subprocess
|
||||
plugin_headless_bash["bash<br/>@deepseek-ai/dsh-bash-local"]
|
||||
cfg --> plugin_headless_bash
|
||||
plugin_headless_cli_agent["cli-agent<br/>@deepseek-ai/dsh-cli-demo"]
|
||||
cfg --> plugin_headless_cli_agent
|
||||
plugin_headless_cli_agent --> bundle_agent_core["@deepseek-ai/dsh-agent-spine-demo"]
|
||||
plugin_headless_cli_agent --> bundle_jsonl["@deepseek-ai/dsh-session-persistence-jsonl"]
|
||||
plugin_headless_cli_agent --> frontdoor_cli["one-shot driver<br/>format-pure stdout<br/>fresh top-level agent"]
|
||||
bundle_agent_core --> spine_llm["ctx.llm"]
|
||||
bundle_agent_core --> spine_sessions["ctx.sessions"]
|
||||
bundle_agent_core --> spine_tools["ctx.tools + tool-bash"]
|
||||
bundle_agent_core --> spine_loop["ctx.agents + ctx.agentLoop"]
|
||||
plugin_headless_agent_spine["agent-spine<br/>@deepseek-ai/dsh-agent-spine-demo"]
|
||||
cfg --> plugin_headless_agent_spine
|
||||
plugin_headless_persistence["persistence<br/>@deepseek-ai/dsh-session-persistence-jsonl"]
|
||||
cfg --> plugin_headless_persistence
|
||||
plugin_headless_checkpoint_policy["checkpoint-policy<br/>@deepseek-ai/dsh-session-checkpoint-policy"]
|
||||
cfg --> plugin_headless_checkpoint_policy
|
||||
plugin_headless_token_meter["token-meter<br/>@deepseek-ai/dsh-token-meter"]
|
||||
cfg --> plugin_headless_token_meter
|
||||
plugin_headless_compact_basic["compact-basic<br/>@deepseek-ai/dsh-compact-basic"]
|
||||
@@ -70,7 +67,9 @@ flowchart LR
|
||||
| `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` |
|
||||
| `subprocess` | `@deepseek-ai/dsh-subprocess-local` |
|
||||
| `bash` | `@deepseek-ai/dsh-bash-local` |
|
||||
| `cli-agent` | `@deepseek-ai/dsh-cli-demo` |
|
||||
| `agent-spine` | `@deepseek-ai/dsh-agent-spine-demo` |
|
||||
| `persistence` | `@deepseek-ai/dsh-session-persistence-jsonl` |
|
||||
| `checkpoint-policy` | `@deepseek-ai/dsh-session-checkpoint-policy` |
|
||||
| `token-meter` | `@deepseek-ai/dsh-token-meter` |
|
||||
| `compact-basic` | `@deepseek-ai/dsh-compact-basic` |
|
||||
| `session-projection` | `@deepseek-ai/dsh-session-projection` |
|
||||
|
||||
@@ -40,17 +40,18 @@
|
||||
config:
|
||||
timeoutMs: 60000
|
||||
|
||||
# The app bundle pre-creates one fresh `main` agent per invocation.
|
||||
- id: cli-agent
|
||||
name: '@deepseek-ai/dsh-cli-demo'
|
||||
# The example composition pre-creates one fresh `main` agent for its test driver.
|
||||
- id: agent-spine
|
||||
name: '@deepseek-ai/dsh-agent-spine-demo'
|
||||
config:
|
||||
provider: deepseek-official
|
||||
# Stays on flash: the goal/ralph replay corpora were recorded on it, and
|
||||
# their nested-include overlays cannot re-pin the app config (a config
|
||||
# patch cannot target an entry behind a nested include).
|
||||
model: deepseek-v4-flash
|
||||
persistenceRoot: './.sessions'
|
||||
persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'"
|
||||
agents:
|
||||
- id: main
|
||||
provider: deepseek-official
|
||||
# Stays on flash: the goal/ralph replay corpora were recorded on it, and
|
||||
# their nested-include overlays cannot re-pin the app config (a config
|
||||
# patch cannot target an entry behind a nested include).
|
||||
model: deepseek-v4-flash
|
||||
cwd: !!js process.cwd()
|
||||
workspaceContext:
|
||||
maxBytes: 65536
|
||||
persona: |
|
||||
@@ -59,6 +60,15 @@
|
||||
Verify your work by running the code or tests. Keep answers brief and
|
||||
factual.
|
||||
|
||||
- id: persistence
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: './.sessions'
|
||||
compression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'"
|
||||
|
||||
- id: checkpoint-policy
|
||||
name: '@deepseek-ai/dsh-session-checkpoint-policy'
|
||||
|
||||
# Summarize an older range when derived history approaches the context window.
|
||||
- id: token-meter
|
||||
name: '@deepseek-ai/dsh-token-meter'
|
||||
|
||||
+11
-5
@@ -12,15 +12,21 @@
|
||||
- id: llm-deepseek
|
||||
name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
disabled: true
|
||||
- id: cli-agent
|
||||
name: '@deepseek-ai/dsh-cli-demo'
|
||||
- id: agent-spine
|
||||
name: '@deepseek-ai/dsh-agent-spine-demo'
|
||||
config:
|
||||
provider: cli-mock
|
||||
model: cli-mock
|
||||
persistenceRoot: './.sessions'
|
||||
agents:
|
||||
- id: main
|
||||
provider: cli-mock
|
||||
model: cli-mock
|
||||
cwd: !!js process.cwd()
|
||||
workspaceContext: false
|
||||
dshHome: './.dsh-home'
|
||||
skills:
|
||||
local:
|
||||
agentsHome: './.agents-home'
|
||||
persona: 'Keyless headless-agent smoke.'
|
||||
- id: persistence
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: './.sessions'
|
||||
@@ -7,10 +7,15 @@
|
||||
config:
|
||||
baseURL: !!js process.env.DSH_SNAPSHOT_BASE_URL
|
||||
thinking: disabled
|
||||
- id: cli-agent
|
||||
- id: agent-spine
|
||||
config:
|
||||
provider: deepseek-official
|
||||
model: deepseek-v4-flash
|
||||
persistenceRoot: './.sessions'
|
||||
agents:
|
||||
- id: main
|
||||
provider: deepseek-official
|
||||
model: deepseek-v4-flash
|
||||
cwd: !!js process.cwd()
|
||||
workspaceContext: false
|
||||
persona: 'Keyless DeepSeek adapter defaults snapshot.'
|
||||
- id: persistence
|
||||
config:
|
||||
root: './.sessions'
|
||||
@@ -0,0 +1,8 @@
|
||||
- id: api-gateway
|
||||
config:
|
||||
provider: cli-mock
|
||||
model: cli-mock
|
||||
|
||||
- insert:
|
||||
- id: cli-mock-llm
|
||||
name: './snapshot-fixtures/cli-mock-llm.ts'
|
||||
@@ -17,12 +17,22 @@
|
||||
- id: seed-goal
|
||||
name: './seed-goal.ts'
|
||||
|
||||
- id: cli-agent
|
||||
name: '@deepseek-ai/dsh-cli-demo'
|
||||
- id: agent-spine
|
||||
name: '@deepseek-ai/dsh-agent-spine-demo'
|
||||
config:
|
||||
provider: cli-mock
|
||||
model: cli-mock
|
||||
agents:
|
||||
- id: main
|
||||
provider: cli-mock
|
||||
model: cli-mock
|
||||
cwd: !!js process.cwd()
|
||||
persona: 'Test the persisted goal domain.'
|
||||
persistenceRoot: './.sessions'
|
||||
persistenceCompression: none
|
||||
workspaceContext: false
|
||||
|
||||
- id: persistence
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: './.sessions'
|
||||
compression: none
|
||||
|
||||
- id: checkpoint-policy
|
||||
name: '@deepseek-ai/dsh-session-checkpoint-policy'
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env node
|
||||
/** Snapshot-only Loader driver: stream one fixture turn as canonical JSONL. */
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import { boot, installFailLoud, loadEnv, resolveConfigPath } from '@deepseek-ai/dsh-app-boot'
|
||||
import { runFixtureTurn } from '@deepseek-ai/dsh-loader-smoke'
|
||||
import type { SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
|
||||
const NAME = 'headless-test-driver'
|
||||
const [configPath, ...taskParts] = process.argv.slice(2)
|
||||
if (configPath === undefined || taskParts.length === 0 || taskParts.every(part => part.trim() === '')) {
|
||||
throw new Error(`${NAME}: expected <config-path> <task...>`)
|
||||
}
|
||||
|
||||
const uninstallFailLoud = installFailLoud(NAME)
|
||||
let ctx: Context | undefined
|
||||
try {
|
||||
loadEnv(NAME)
|
||||
ctx = await boot(NAME, resolveConfigPath(configPath, undefined))
|
||||
const result = await runFixtureTurn(ctx, {
|
||||
task: taskParts.join(' '),
|
||||
onEvent: (sessionId: string, event: SessionEvent) => {
|
||||
process.stdout.write(`${JSON.stringify({ type: 'session_event', sessionId, event })}\n`)
|
||||
},
|
||||
})
|
||||
process.stdout.write(`${JSON.stringify(result)}\n`)
|
||||
} catch (error: unknown) {
|
||||
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
|
||||
process.exitCode = 1
|
||||
} finally {
|
||||
await ctx?.fiber.dispose()
|
||||
uninstallFailLoud()
|
||||
}
|
||||
@@ -10,8 +10,8 @@ import { writeFile } from 'node:fs/promises'
|
||||
import { createServer } from 'node:http'
|
||||
import { once } from 'node:events'
|
||||
import { boot, resolveConfigPath } from '@deepseek-ai/dsh-app-boot'
|
||||
import { runOneShot } from '@deepseek-ai/dsh-cli-demo/src/cli.ts'
|
||||
import { recordFeedback } from '@deepseek-ai/dsh-command-feedback'
|
||||
import { runFixtureTurn } from '@deepseek-ai/dsh-loader-smoke'
|
||||
|
||||
const configPath = process.argv[2]
|
||||
if (configPath === undefined) throw new Error('telemetry-otel driver requires a config path')
|
||||
@@ -35,14 +35,14 @@ const ctx = await boot('telemetry-otel-e2e', resolveConfigPath(configPath, undef
|
||||
try {
|
||||
// The fixture credential rides the model-visible user message; the exported
|
||||
// copy must scrub it while the canonical log keeps the original bytes.
|
||||
await runOneShot(ctx, { task: 'prove telemetry with key sk-e2efixture1234567890' })
|
||||
await runFixtureTurn(ctx, { task: 'prove telemetry with key sk-e2efixture1234567890' })
|
||||
const mode = process.env.DSH_TELEMETRY_E2E_MODE ?? 'FULL'
|
||||
if (mode !== 'FULL') {
|
||||
const [agent] = ctx.get('agents')?.roots() ?? []
|
||||
if (agent === undefined) throw new Error('telemetry-otel driver requires one root agent')
|
||||
recordFeedback(agent.session, 'fixture feedback')
|
||||
if (mode === 'FEEDBACK_ONLY') {
|
||||
await runOneShot(ctx, { task: 'post-feedback private suffix' })
|
||||
await runFixtureTurn(ctx, { task: 'post-feedback private suffix' })
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -30,12 +30,22 @@
|
||||
exporter:
|
||||
url: !!js process.env.DSH_TELEMETRY_E2E_URL
|
||||
|
||||
- id: cli-agent
|
||||
name: '@deepseek-ai/dsh-cli-demo'
|
||||
- id: agent-spine
|
||||
name: '@deepseek-ai/dsh-agent-spine-demo'
|
||||
config:
|
||||
provider: cli-mock
|
||||
model: cli-mock
|
||||
agents:
|
||||
- id: main
|
||||
provider: cli-mock
|
||||
model: cli-mock
|
||||
cwd: !!js process.cwd()
|
||||
persona: 'Test the session-telemetry-otel plugin.'
|
||||
persistenceRoot: './.sessions'
|
||||
persistenceCompression: 'none'
|
||||
workspaceContext: false
|
||||
|
||||
- id: persistence
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: './.sessions'
|
||||
compression: 'none'
|
||||
|
||||
- id: checkpoint-policy
|
||||
name: '@deepseek-ai/dsh-session-checkpoint-policy'
|
||||
@@ -2,15 +2,15 @@
|
||||
/** Test driver that sends two turns through one Headless Loader composition. */
|
||||
|
||||
import { boot, resolveConfigPath } from '@deepseek-ai/dsh-app-boot'
|
||||
import { runOneShot } from '@deepseek-ai/dsh-cli-demo/src/cli.ts'
|
||||
import { runFixtureTurn } from '@deepseek-ai/dsh-loader-smoke'
|
||||
|
||||
const configPath = process.argv[2]
|
||||
if (configPath === undefined) throw new Error('time-context driver requires a config path')
|
||||
|
||||
const ctx = await boot('time-context-e2e', resolveConfigPath(configPath, undefined))
|
||||
try {
|
||||
await runOneShot(ctx, { task: 'first' })
|
||||
await runOneShot(ctx, { task: 'second' })
|
||||
await runFixtureTurn(ctx, { task: 'first' })
|
||||
await runFixtureTurn(ctx, { task: 'second' })
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
}
|
||||
@@ -12,12 +12,22 @@
|
||||
- id: time-context
|
||||
name: '@deepseek-ai/dsh-time-context'
|
||||
|
||||
- id: cli-agent
|
||||
name: '@deepseek-ai/dsh-cli-demo'
|
||||
- id: agent-spine
|
||||
name: '@deepseek-ai/dsh-agent-spine-demo'
|
||||
config:
|
||||
provider: time-context-mock
|
||||
model: time-context-mock
|
||||
agents:
|
||||
- id: main
|
||||
provider: time-context-mock
|
||||
model: time-context-mock
|
||||
cwd: !!js process.cwd()
|
||||
persona: 'Test the time-context plugin.'
|
||||
persistenceRoot: './.sessions'
|
||||
persistenceCompression: 'none'
|
||||
workspaceContext: false
|
||||
|
||||
- id: persistence
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: './.sessions'
|
||||
compression: 'none'
|
||||
|
||||
- id: checkpoint-policy
|
||||
name: '@deepseek-ai/dsh-session-checkpoint-policy'
|
||||
@@ -1,4 +1,4 @@
|
||||
import { readFile, readdir, writeFile } from 'node:fs/promises'
|
||||
import { copyFile, mkdir, readFile, readdir, writeFile } from 'node:fs/promises'
|
||||
import { createServer } from 'node:http'
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http'
|
||||
import { delimiter, dirname, join } from 'node:path'
|
||||
@@ -14,6 +14,10 @@ import {
|
||||
type NormalizeContext,
|
||||
} from '@deepseek-ai/dsh-acp-snapshot'
|
||||
import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
|
||||
import {
|
||||
decompressZstdFrame,
|
||||
scanZstdFrames,
|
||||
} from '@deepseek-ai/dsh-session-persistence-jsonl/src/zstd.ts'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const snapshotsDir = join(dirname(fileURLToPath(import.meta.url)), 'snapshots')
|
||||
@@ -43,10 +47,14 @@ const ralphScenarioDir = join(snapshotsDir, 'ralph-loop')
|
||||
const ralphConfigPath = fileURLToPath(new URL('../ralph.cordis.snapshot.yml', import.meta.url))
|
||||
const startupFailureConfigPath = fileURLToPath(new URL('./fixtures/startup-activation-error/cordis.yml', import.meta.url))
|
||||
const startupFailureExpected = join(snapshotsDir, 'startup-activation-error', 'stderr.expected.txt')
|
||||
const binScript = fileURLToPath(new URL('../../../packages/examples/cli-demo/src/bin.ts', import.meta.url))
|
||||
const binScript = fileURLToPath(new URL('./fixtures/headless-driver.ts', import.meta.url))
|
||||
const dshBinScript = fileURLToPath(new URL('../../../apps/cli/src/bin.ts', import.meta.url))
|
||||
const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
|
||||
const reasoningConfigPath = fileURLToPath(new URL('./fixtures/cli.cordis.yml', import.meta.url))
|
||||
const deepseekDefaultsConfigPath = fileURLToPath(new URL('./fixtures/deepseek-defaults.cordis.yml', import.meta.url))
|
||||
const dshRunOverlayPath = fileURLToPath(new URL('./fixtures/dsh-run.cordis.yml', import.meta.url))
|
||||
const dshRunSessionExpected = join(snapshotsDir, 'dsh-run', 'session.expected.jsonl')
|
||||
const cliMockLlmPluginPath = fileURLToPath(new URL('./fixtures/cli-mock-llm.ts', import.meta.url))
|
||||
const refreshing = process.env.DSH_SNAPSHOT === 'refresh'
|
||||
|
||||
interface JsonObject {
|
||||
@@ -167,23 +175,76 @@ async function scenarioPrompt(dir: string, label: string): Promise<string> {
|
||||
return prompt
|
||||
}
|
||||
|
||||
async function persistedLogs(cwd: string): Promise<PersistedLog[]> {
|
||||
const root = join(cwd, '.sessions')
|
||||
const files = (await readdir(root, { recursive: true })).filter(file => file.endsWith('.jsonl'))
|
||||
async function readPersistedLog(file: string): Promise<string> {
|
||||
const content = await readFile(file)
|
||||
if (!file.endsWith('.zstd')) return content.toString('utf8')
|
||||
const scan = scanZstdFrames(content)
|
||||
if (scan.tornStart !== undefined) throw new Error(`persisted snapshot log has a torn Zstandard frame: ${file}`)
|
||||
const decoded: Buffer[] = []
|
||||
for (const frame of scan.frames) {
|
||||
decoded.push(await decompressZstdFrame(content.subarray(frame.start, frame.end)))
|
||||
}
|
||||
return Buffer.concat(decoded).toString('utf8')
|
||||
}
|
||||
|
||||
async function persistedLogs(cwd: string, root: string = join(cwd, '.sessions')): Promise<PersistedLog[]> {
|
||||
const files = (await readdir(root, { recursive: true }))
|
||||
.filter(file => file.endsWith('.jsonl') || file.endsWith('.jsonl.zstd'))
|
||||
return Promise.all(files.map(async (file) => {
|
||||
const content = await readFile(join(root, file), 'utf8')
|
||||
const content = await readPersistedLog(join(root, file))
|
||||
return { content, header: parseJsonl(content)[0] ?? {} }
|
||||
}))
|
||||
}
|
||||
|
||||
describe('headless stream-json snapshots', () => {
|
||||
it('runs one task through the product dsh run command', async () => {
|
||||
const task = 'Prove the product dsh run path with one real tool round trip.'
|
||||
const result = await runLoaderSmoke({
|
||||
label: 'product dsh run snapshot',
|
||||
tempDirPrefix: 'headless-snapshot-dsh-run-',
|
||||
binScript: dshBinScript,
|
||||
configPath: dshRunOverlayPath,
|
||||
binArgs: ['run', '--patch', dshRunOverlayPath, task],
|
||||
tsconfigPath,
|
||||
env: {
|
||||
DSH_PERMISSION_MODE: 'danger-full-access',
|
||||
DSH_TELEMETRY_DISABLED: '1',
|
||||
NODE_OPTIONS: [process.env.NODE_OPTIONS, '--disable-warning=ExperimentalWarning'].filter(Boolean).join(' '),
|
||||
},
|
||||
prepare: async (cwd) => {
|
||||
const fixtureDir = join(cwd, '.dsh', 'profiles', 'headless', 'snapshot-fixtures')
|
||||
await mkdir(fixtureDir, { recursive: true })
|
||||
await Promise.all([
|
||||
copyFile(cliMockLlmPluginPath, join(fixtureDir, 'cli-mock-llm.ts')),
|
||||
writeFile(join(fixtureDir, 'package.json'), '{"type":"module"}\n'),
|
||||
])
|
||||
},
|
||||
inspect: async (cwd) => {
|
||||
const logs = await persistedLogs(cwd, join(cwd, '.dsh', 'sessions'))
|
||||
expect(logs).toHaveLength(1)
|
||||
const actual = logs[0]
|
||||
if (actual === undefined) throw new Error('dsh run did not persist its session')
|
||||
const context = contextFromLogs([actual.content])
|
||||
const session = scrubRequestHeaders(normalizeSessionLog(actual.content, context))
|
||||
if (refreshing) await writeFile(dshRunSessionExpected, session)
|
||||
expect(session).toBe(await readFile(dshRunSessionExpected, 'utf8'))
|
||||
expect(session).toContain(task)
|
||||
expect(session).toContain('CLI tool round trip complete: CLI_TOOL_ROUND_TRIP')
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.stdout).toBe('CLI tool round trip complete: CLI_TOOL_ROUND_TRIP\n')
|
||||
expect(result.stderr).toMatch(/^dsh: observing at http:\/\/127\.0\.0\.1:\d+\n$/u)
|
||||
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
||||
|
||||
it('prints the original Loader activation error through the assembled one-shot app', async () => {
|
||||
const result = await runLoaderSmoke({
|
||||
label: 'headless startup activation error snapshot',
|
||||
tempDirPrefix: 'headless-snapshot-startup-error-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath: startupFailureConfigPath,
|
||||
binArgs: ['--config', startupFailureConfigPath, '--output-format', 'stream-json', 'unreachable task'],
|
||||
binArgs: [startupFailureConfigPath, 'unreachable task'],
|
||||
tsconfigPath,
|
||||
expectedExitCode: 1,
|
||||
})
|
||||
@@ -199,8 +260,9 @@ describe('headless stream-json snapshots', () => {
|
||||
label: 'provider retry headless stream-json snapshot',
|
||||
tempDirPrefix: 'headless-snapshot-provider-retry-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath: retryConfigPath,
|
||||
binArgs: ['--config', retryConfigPath, '--output-format', 'stream-json', prompt],
|
||||
binArgs: [retryConfigPath, prompt],
|
||||
tsconfigPath,
|
||||
env: {
|
||||
DSH_SNAPSHOT: 'replay',
|
||||
@@ -239,8 +301,9 @@ describe('headless stream-json snapshots', () => {
|
||||
label: 'compaction recovery headless stream-json snapshot',
|
||||
tempDirPrefix: 'headless-snapshot-compaction-recovery-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath: compactionConfigPath,
|
||||
binArgs: ['--config', compactionConfigPath, '--output-format', 'stream-json', prompt],
|
||||
binArgs: [compactionConfigPath, prompt],
|
||||
tsconfigPath,
|
||||
env: {
|
||||
DSH_SNAPSHOT: 'replay',
|
||||
@@ -307,8 +370,9 @@ describe('headless stream-json snapshots', () => {
|
||||
label: 'missing-credential headless stream-json snapshot',
|
||||
tempDirPrefix: 'headless-snapshot-missing-credential-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath: credentialsConfigPath,
|
||||
binArgs: ['--config', credentialsConfigPath, '--output-format', 'stream-json', 'say pong'],
|
||||
binArgs: [credentialsConfigPath, 'say pong'],
|
||||
tsconfigPath,
|
||||
env: {
|
||||
// First-run posture: no key in the environment, none under ./.dsh.
|
||||
@@ -344,8 +408,9 @@ describe('headless stream-json snapshots', () => {
|
||||
label: 'invalid-credential headless stream-json snapshot',
|
||||
tempDirPrefix: 'headless-snapshot-invalid-credential-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath: credentialsConfigPath,
|
||||
binArgs: ['--config', credentialsConfigPath, '--output-format', 'stream-json', 'say pong'],
|
||||
binArgs: [credentialsConfigPath, 'say pong'],
|
||||
tsconfigPath,
|
||||
env: {
|
||||
// A key that exists but no HTTP header can carry — the paste this
|
||||
@@ -378,8 +443,9 @@ describe('headless stream-json snapshots', () => {
|
||||
label: 'reasoning effort headless stream-json snapshot',
|
||||
tempDirPrefix: 'headless-snapshot-reasoning-effort-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath: reasoningConfigPath,
|
||||
binArgs: ['--config', reasoningConfigPath, '--output-format', 'stream-json', 'prove dynamic reasoning effort'],
|
||||
binArgs: [reasoningConfigPath, 'prove dynamic reasoning effort'],
|
||||
tsconfigPath,
|
||||
})
|
||||
|
||||
@@ -420,12 +486,10 @@ describe('headless stream-json snapshots', () => {
|
||||
label: 'DeepSeek adapter defaults headless stream-json snapshot',
|
||||
tempDirPrefix: 'headless-snapshot-deepseek-defaults-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath: deepseekDefaultsConfigPath,
|
||||
binArgs: [
|
||||
'--config',
|
||||
deepseekDefaultsConfigPath,
|
||||
'--output-format',
|
||||
'stream-json',
|
||||
'return the deterministic response',
|
||||
],
|
||||
tsconfigPath,
|
||||
@@ -480,8 +544,9 @@ describe('headless stream-json snapshots', () => {
|
||||
label: 'advanced headless stream-json snapshot',
|
||||
tempDirPrefix: 'headless-snapshot-advanced-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath: advancedConfigPath,
|
||||
binArgs: ['--config', advancedConfigPath, '--output-format', 'stream-json', prompt],
|
||||
binArgs: [advancedConfigPath, prompt],
|
||||
tsconfigPath,
|
||||
env: {
|
||||
DSH_SNAPSHOT: 'replay',
|
||||
@@ -551,8 +616,9 @@ describe('headless stream-json snapshots', () => {
|
||||
label: 'goal tools headless stream-json snapshot',
|
||||
tempDirPrefix: 'headless-snapshot-goal-tools-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath: goalConfigPath,
|
||||
binArgs: ['--config', goalConfigPath, '--output-format', 'stream-json', prompt],
|
||||
binArgs: [goalConfigPath, prompt],
|
||||
tsconfigPath,
|
||||
env: {
|
||||
DSH_SNAPSHOT: 'replay',
|
||||
@@ -607,8 +673,9 @@ describe('headless stream-json snapshots', () => {
|
||||
label: 'Ralph loop headless stream-json snapshot',
|
||||
tempDirPrefix: 'headless-snapshot-ralph-loop-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath: ralphConfigPath,
|
||||
binArgs: ['--config', ralphConfigPath, '--output-format', 'stream-json', prompt],
|
||||
binArgs: [ralphConfigPath, prompt],
|
||||
tsconfigPath,
|
||||
env: {
|
||||
DSH_SNAPSHOT: 'replay',
|
||||
@@ -688,8 +755,9 @@ describe('headless stream-json snapshots', () => {
|
||||
label: 'headless persistent PTY snapshot',
|
||||
tempDirPrefix: 'headless-snapshot-pty-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath: ptyConfigPath,
|
||||
binArgs: ['--config', ptyConfigPath, '--output-format', 'stream-json', prompt],
|
||||
binArgs: [ptyConfigPath, prompt],
|
||||
tsconfigPath,
|
||||
env: {
|
||||
DSH_SNAPSHOT: 'replay',
|
||||
|
||||
@@ -9,7 +9,7 @@ import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-l
|
||||
import { PREPARED_ENTRY_FILENAME, prepareDshPlugin } from '@deepseek-ai/dsh-repository-plugin'
|
||||
import type { SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
|
||||
const binScript = fileURLToPath(new URL('../../../packages/examples/cli-demo/src/bin.ts', import.meta.url))
|
||||
const binScript = fileURLToPath(new URL('./fixtures/headless-driver.ts', import.meta.url))
|
||||
const configPath = fileURLToPath(new URL('./fixtures/cli.cordis.yml', import.meta.url))
|
||||
const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
|
||||
const decompress = promisify(zstdDecompress)
|
||||
@@ -21,8 +21,9 @@ describe('headless-agent keyless smoke', () => {
|
||||
label: 'headless-agent',
|
||||
tempDirPrefix: 'headless-agent-smoke-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath,
|
||||
binArgs: ['--config', configPath, '--output-format', 'stream-json', 'prove the tool path'],
|
||||
binArgs: [configPath, 'prove the tool path'],
|
||||
tsconfigPath,
|
||||
inspect: async (cwd) => {
|
||||
const files = await readdir(cwd, { recursive: true })
|
||||
|
||||
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
|
||||
|
||||
const binScript = fileURLToPath(new URL('../../../packages/examples/cli-demo/src/bin.ts', import.meta.url))
|
||||
const binScript = fileURLToPath(new URL('./fixtures/headless-driver.ts', import.meta.url))
|
||||
const configPath = fileURLToPath(new URL('../cordis.yml', import.meta.url))
|
||||
const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
|
||||
const hasKey = Boolean(process.env.DEEPSEEK_API_KEY)
|
||||
@@ -16,9 +16,9 @@ describe.skipIf(!hasKey)('headless-agent with real model', () => {
|
||||
label: 'headless-agent real model',
|
||||
tempDirPrefix: 'headless-agent-real-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath,
|
||||
binArgs: [
|
||||
'--config',
|
||||
configPath,
|
||||
'Read task.txt, replace its complete contents with exactly "value=after" followed by a newline, read it again, and report briefly.',
|
||||
],
|
||||
|
||||
@@ -14,7 +14,7 @@ const replayFixture = join(fixtureDir, 'replay.jsonl')
|
||||
const replayOverride = join(fixtureDir, 'replay.override.json')
|
||||
const sessionExpected = join(fixtureDir, 'session.expected.jsonl')
|
||||
const configPath = fileURLToPath(new URL('../semantic-checkpoint.cordis.snapshot.yml', import.meta.url))
|
||||
const binScript = fileURLToPath(new URL('../../../packages/examples/cli-demo/src/bin.ts', import.meta.url))
|
||||
const binScript = fileURLToPath(new URL('./fixtures/headless-driver.ts', import.meta.url))
|
||||
const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
|
||||
const sessionId = SessionId('semantic-checkpoint-unknown-outcome')
|
||||
const refreshing = process.env.DSH_SNAPSHOT === 'refresh'
|
||||
@@ -87,8 +87,9 @@ describe('semantic checkpoint recovery snapshot', () => {
|
||||
label: 'semantic checkpoint headless stream-json snapshot',
|
||||
tempDirPrefix: 'dsh-semantic-snapshot-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath,
|
||||
binArgs: ['--config', configPath, '--output-format', 'stream-json', task],
|
||||
binArgs: [configPath, task],
|
||||
tsconfigPath,
|
||||
env: {
|
||||
DSH_SNAPSHOT_FILE: replayFixture,
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
{"type":"step/start","seq":17,"time":1786123401710,"data":{"turn":1,"step":2}}
|
||||
{"type":"assistant/chunk","seq":18,"time":1786123401715,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"snapshot request exceeded the model context window","code":"CONTEXT_WINDOW_EXCEEDED"}}}}}
|
||||
{"type":"compact/start","seq":19,"time":1786123401715,"data":{"turn":1}}
|
||||
{"type":"compact/summary","seq":20,"time":1786123401725,"data":{"summary":[{"type":"text","text":"The request established a durable compaction premise."}],"rawOutput":[{"type":"text","text":"The request established a durable compaction premise."}],"shadowedRange":{"start":4,"end":4},"shadowedSeqs":[4],"shadowedTokenCount":264,"provider":"deepseek-official","model":"deepseek-v4-flash","maxTokens":32,"usage":{"inputTokens":20,"outputTokens":4}}}
|
||||
{"type":"compact/summary","seq":20,"time":1786123401725,"data":{"summary":[{"type":"text","text":"The request established a durable compaction premise."}],"rawOutput":[{"type":"text","text":"The request established a durable compaction premise."}],"llmStreamCall":true,"shadowedRange":{"start":4,"end":4},"shadowedSeqs":[4],"shadowedTokenCount":264,"provider":"deepseek-official","model":"deepseek-v4-flash","maxTokens":32,"usage":{"inputTokens":20,"outputTokens":4}}}
|
||||
{"type":"user/message","seq":21,"time":1786123401725,"data":{"content":[{"type":"text","text":"This is an automatically generated checkpoint condensing an earlier span of the conversation to free up context. Treat the captured context as established background and build on it without restating it. Continue the task directly from the messages that follow, without acknowledging this checkpoint.\n\n<compacted-summary>"},{"type":"text","text":"The request established a durable compaction premise."},{"type":"text","text":"</compacted-summary>"}],"source":{"kind":"plugin","plugin":"compact"},"role":"user","id":"6d2afb13-a37b-48d6-9ea5-fc8734127377"},"sourceEventSeqs":[19,20,4],"surfaceOp":{"op":"replace","start":4,"end":4}}
|
||||
{"type":"compact/end","seq":22,"time":1786123401725,"data":{"turn":1}}
|
||||
{"type":"assistant/chunk","seq":23,"time":1786123401730,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"step/start","seq":17,"time":0,"data":{"turn":1,"step":2}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":18,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"snapshot request exceeded the model context window","code":"CONTEXT_WINDOW_EXCEEDED"}}}}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"compact/start","seq":19,"time":0,"data":{"turn":1}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"compact/summary","seq":20,"time":0,"data":{"summary":[{"type":"text","text":"The request established a durable compaction premise."}],"rawOutput":[{"type":"text","text":"The request established a durable compaction premise."}],"shadowedRange":{"start":4,"end":4},"shadowedSeqs":[4],"shadowedTokenCount":264,"provider":"deepseek-official","model":"deepseek-v4-flash","maxTokens":32,"usage":{"inputTokens":20,"outputTokens":4}}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"compact/summary","seq":20,"time":0,"data":{"summary":[{"type":"text","text":"The request established a durable compaction premise."}],"rawOutput":[{"type":"text","text":"The request established a durable compaction premise."}],"llmStreamCall":true,"shadowedRange":{"start":4,"end":4},"shadowedSeqs":[4],"shadowedTokenCount":264,"provider":"deepseek-official","model":"deepseek-v4-flash","maxTokens":32,"usage":{"inputTokens":20,"outputTokens":4}}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"user/message","seq":21,"time":0,"data":{"content":[{"type":"text","text":"This is an automatically generated checkpoint condensing an earlier span of the conversation to free up context. Treat the captured context as established background and build on it without restating it. Continue the task directly from the messages that follow, without acknowledging this checkpoint.\n\n<compacted-summary>"},{"type":"text","text":"The request established a durable compaction premise."},{"type":"text","text":"</compacted-summary>"}],"source":{"kind":"plugin","plugin":"compact"},"role":"user","id":"{{sessionId}}"},"sourceEventSeqs":[19,20,4],"surfaceOp":{"op":"replace","start":4,"end":4}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"compact/end","seq":22,"time":0,"data":{"turn":1}}}
|
||||
{"type":"session_event","sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":23,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
{"type":"session","version":0,"id":"{{sessionId}}","createdAt":0,"cwd":"{{cwd}}","delegationDepth":0}
|
||||
{"type":"permission/preset","seq":0,"time":0,"data":{"preset":"danger-full-access"}}
|
||||
{"type":"sandbox/mode","seq":1,"time":0,"data":{"mode":"danger-full-access"}}
|
||||
{"type":"approval/policy","seq":2,"time":0,"data":{"policy":"never"}}
|
||||
{"type":"agent/inbox/spliced","seq":3,"time":0,"data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Prove the product dsh run path with one real tool round trip."}],"source":{"kind":"user","rpcId":"{{sessionId}}"},"role":"user","id":"{{sessionId}}"}]}}
|
||||
{"type":"turn/start","seq":4,"time":0,"data":{"turn":1}}
|
||||
{"type":"agent/inbox/spliced","seq":5,"time":0,"data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
|
||||
{"type":"step/start","seq":6,"time":0,"data":{"turn":1,"step":1}}
|
||||
{"type":"user/message","seq":7,"time":0,"data":{"content":[{"type":"text","text":"Prove the product dsh run path with one real tool round trip."}],"source":{"kind":"user","rpcId":"{{sessionId}}"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
|
||||
{"type":"user/message","seq":8,"time":0,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}
|
||||
{"type":"session/title","seq":9,"time":0,"data":{"title":"Prove the product dsh run","messageSeqs":[7],"source":{"kind":"fallback"}}}
|
||||
{"type":"request/header","seq":10,"time":0,"data":{"header":{"config":{"provider":"cli-mock","model":"cli-mock","reasoningEffort":"high"},"adapterDefaults":{"reasoningEffort":true},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
|
||||
{"type":"request/context","seq":11,"time":0,"data":{"provider":"cli-mock","model":"cli-mock"}}
|
||||
{"type":"session/title-llm-request","seq":12,"time":0,"data":{"titleProvider":"session-title-first-message-llm","messageSeqs":[7],"route":{"provider":"cli-mock","model":"cli-mock"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":7,\"text\":\"Prove the product dsh run path with one real tool round trip.\"}]"}],"source":{"kind":"plugin","plugin":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":64}}
|
||||
{"type":"assistant/chunk","seq":13,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
|
||||
{"type":"assistant/chunk","seq":14,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"cli-smoke-call","name":"bash","argumentsDelta":"{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"}}}
|
||||
{"type":"assistant/chunk","seq":15,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"cli-smoke-call","name":"bash","arguments":"{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"}}}}
|
||||
{"type":"assistant/chunk","seq":16,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":11,"outputTokens":3,"cacheReadTokens":2}}}}
|
||||
{"type":"assistant/chunk","seq":17,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
|
||||
{"type":"assistant/message","seq":18,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"cli-smoke-call","name":"bash","arguments":"{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"}],"source":{"kind":"model","provider":"cli-mock","model":"cli-mock"},"id":"{{sessionId}}"},"usage":{"inputTokens":11,"outputTokens":3,"cacheReadTokens":2}},"sourceEventSeqs":[13,14,15,16,17],"surfaceOp":"append"}
|
||||
{"type":"tool/call","seq":19,"time":0,"data":{"turn":1,"step":1,"callId":"cli-smoke-call","name":"bash","arguments":"{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"}}
|
||||
{"type":"tool/result","seq":20,"time":0,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"cli-smoke-call"},"content":[{"type":"tool-result","toolCallId":"cli-smoke-call","content":[{"type":"text","text":"CLI_TOOL_ROUND_TRIP"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[19],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":21,"time":0,"data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","seq":22,"time":0,"data":{"turn":1,"step":2}}
|
||||
{"type":"request/header","seq":23,"time":0,"data":{"header":{"config":{"provider":"cli-mock","model":"cli-mock","reasoningEffort":"off"},"system":"{{system}}","tools":"{{tools}}"},"reason":"change"}}
|
||||
{"type":"assistant/chunk","seq":24,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
|
||||
{"type":"assistant/chunk","seq":25,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"text-delta","index":0,"text":"CLI tool round trip complete: CLI_TOOL_ROUND_TRIP"}}}
|
||||
{"type":"assistant/chunk","seq":26,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"CLI tool round trip complete: CLI_TOOL_ROUND_TRIP"}}}}
|
||||
{"type":"assistant/chunk","seq":27,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":7,"outputTokens":5,"reasoningTokens":1}}}}
|
||||
{"type":"assistant/chunk","seq":28,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
|
||||
{"type":"assistant/message","seq":29,"time":0,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"CLI tool round trip complete: CLI_TOOL_ROUND_TRIP"}],"source":{"kind":"model","provider":"cli-mock","model":"cli-mock"},"id":"{{sessionId}}"},"usage":{"inputTokens":7,"outputTokens":5,"reasoningTokens":1}},"sourceEventSeqs":[24,25,26,27,28],"surfaceOp":"append"}
|
||||
{"type":"step/end","seq":30,"time":0,"data":{"turn":1,"step":2}}
|
||||
{"type":"turn/end","seq":31,"time":0,"data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
dsh-cli-demo: dsh-cli-demo: plugin tree failed to load: failed to apply loader entry include (cordis:include): failed to apply loader entry activation-error (./activation-error.mjs): startup activation snapshot failure
|
||||
headless-test-driver: plugin tree failed to load: failed to apply loader entry include (cordis:include): failed to apply loader entry activation-error (./activation-error.mjs): startup activation snapshot failure
|
||||
Error: startup activation snapshot failure
|
||||
at activation-error-fixture
|
||||
@@ -19,7 +19,7 @@ const fixtureDir = fileURLToPath(new URL('./subagent-diagnostic-snapshots/descri
|
||||
const replayOverride = join(fixtureDir, 'replay.override.json')
|
||||
const parentExpected = join(fixtureDir, 'parent.expected.jsonl')
|
||||
const configPath = fileURLToPath(new URL('../subagent-diagnostic.cordis.snapshot.yml', import.meta.url))
|
||||
const binScript = fileURLToPath(new URL('../../../packages/examples/cli-demo/src/bin.ts', import.meta.url))
|
||||
const binScript = fileURLToPath(new URL('./fixtures/headless-driver.ts', import.meta.url))
|
||||
const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
|
||||
const parentId = SessionId('subagent-diagnostic-parent')
|
||||
const childId = SessionId('subagent-diagnostic-child')
|
||||
@@ -77,8 +77,9 @@ describe('descriptor-less cold child diagnostic snapshot', () => {
|
||||
label: 'subagent diagnostic headless stream-json snapshot',
|
||||
tempDirPrefix: 'dsh-subagent-diag-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath,
|
||||
binArgs: ['--config', configPath, '--output-format', 'stream-json', task],
|
||||
binArgs: [configPath, task],
|
||||
tsconfigPath,
|
||||
env: {
|
||||
DSH_SNAPSHOT_FILE: replayOverride,
|
||||
|
||||
@@ -20,7 +20,7 @@ const childReplay = join(fixtureDir, 'child.replay.jsonl')
|
||||
const parentExpected = join(fixtureDir, 'parent.expected.jsonl')
|
||||
const childExpected = join(fixtureDir, 'child.expected.jsonl')
|
||||
const configPath = fileURLToPath(new URL('../subagent-inheritance.cordis.snapshot.yml', import.meta.url))
|
||||
const binScript = fileURLToPath(new URL('../../../packages/examples/cli-demo/src/bin.ts', import.meta.url))
|
||||
const binScript = fileURLToPath(new URL('./fixtures/headless-driver.ts', import.meta.url))
|
||||
const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
|
||||
const sessionId = SessionId('subagent-inheritance-parent')
|
||||
const refreshing = process.env.DSH_SNAPSHOT === 'refresh'
|
||||
@@ -59,8 +59,9 @@ describe('parent-only override inheritance snapshot', () => {
|
||||
label: 'subagent inheritance headless stream-json snapshot',
|
||||
tempDirPrefix: 'dsh-subagent-inherit-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath,
|
||||
binArgs: ['--config', configPath, '--output-format', 'stream-json', task],
|
||||
binArgs: [configPath, task],
|
||||
tsconfigPath,
|
||||
env: {
|
||||
// The primary fixture path must exist for llm-replay's config guard;
|
||||
|
||||
@@ -28,7 +28,7 @@ const replayOverride = join(fixtureDir, 'replay.override.json')
|
||||
const sessionExpected = join(fixtureDir, 'session.expected.jsonl')
|
||||
const precedenceExpected = join(dirname(fixtureDir), 'precedence-change/session.expected.jsonl')
|
||||
const configPath = fileURLToPath(new URL('../workspace-context-resume.cordis.snapshot.yml', import.meta.url))
|
||||
const binScript = fileURLToPath(new URL('../../../packages/examples/cli-demo/src/bin.ts', import.meta.url))
|
||||
const binScript = fileURLToPath(new URL('./fixtures/headless-driver.ts', import.meta.url))
|
||||
const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
|
||||
const sessionId = SessionId('workspace-context-resume')
|
||||
const refreshing = process.env.DSH_SNAPSHOT === 'refresh'
|
||||
@@ -119,8 +119,9 @@ describe('workspace-context resume snapshot', () => {
|
||||
label: 'workspace-context resume headless stream-json snapshot',
|
||||
tempDirPrefix: 'dsh-workspace-context-resume-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath,
|
||||
binArgs: ['--config', configPath, '--output-format', 'stream-json', 'Acknowledge the current workspace instruction.'],
|
||||
binArgs: [configPath, 'Acknowledge the current workspace instruction.'],
|
||||
tsconfigPath,
|
||||
env: {
|
||||
DSH_SNAPSHOT_FILE: replayFixture,
|
||||
@@ -175,8 +176,9 @@ describe('workspace-context resume snapshot', () => {
|
||||
label: 'workspace-context precedence-change resume snapshot',
|
||||
tempDirPrefix: 'dsh-workspace-context-precedence-',
|
||||
binScript,
|
||||
libBinScript: binScript,
|
||||
configPath,
|
||||
binArgs: ['--config', configPath, '--output-format', 'stream-json', 'Acknowledge the current workspace instruction.'],
|
||||
binArgs: [configPath, 'Acknowledge the current workspace instruction.'],
|
||||
tsconfigPath,
|
||||
env: {
|
||||
DSH_SNAPSHOT_FILE: replayFixture,
|
||||
|
||||
+16
-6
@@ -32,12 +32,22 @@
|
||||
# recursion budget, so the local numeric default cannot apply here.
|
||||
maxDepth: 'provider-managed'
|
||||
|
||||
- id: cli-agent
|
||||
name: '@deepseek-ai/dsh-cli-demo'
|
||||
- id: agent-spine
|
||||
name: '@deepseek-ai/dsh-agent-spine-demo'
|
||||
config:
|
||||
provider: mock
|
||||
model: mock-delegate
|
||||
agents:
|
||||
- id: main
|
||||
provider: mock
|
||||
model: mock-delegate
|
||||
cwd: !!js process.cwd()
|
||||
persona: 'Test SDK subagent cwd inheritance.'
|
||||
persistenceRoot: './.sessions'
|
||||
persistenceCompression: 'none'
|
||||
workspaceContext: false
|
||||
|
||||
- id: persistence
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: './.sessions'
|
||||
compression: 'none'
|
||||
|
||||
- id: checkpoint-policy
|
||||
name: '@deepseek-ai/dsh-session-checkpoint-policy'
|
||||
@@ -2,14 +2,14 @@
|
||||
/** Test driver: one delegation turn through a headless Loader composition. */
|
||||
|
||||
import { boot, resolveConfigPath } from '@deepseek-ai/dsh-app-boot'
|
||||
import { runOneShot } from '@deepseek-ai/dsh-cli-demo/src/cli.ts'
|
||||
import { runFixtureTurn } from '@deepseek-ai/dsh-loader-smoke'
|
||||
|
||||
const configPath = process.argv[2]
|
||||
if (configPath === undefined) throw new Error('sdk-subagent cwd driver requires a config path')
|
||||
|
||||
const ctx = await boot('sdk-subagent-cwd-e2e', resolveConfigPath(configPath, undefined))
|
||||
try {
|
||||
await runOneShot(ctx, { task: 'delegate' })
|
||||
await runFixtureTurn(ctx, { task: 'delegate' })
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
* fixtures and rewrites expected outputs.
|
||||
*/
|
||||
|
||||
import { existsSync } from 'node:fs'
|
||||
import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { basename, delimiter, join } from 'node:path'
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
normalizeStdout,
|
||||
refreshFixtureReplacements,
|
||||
scrubRequestHeaders,
|
||||
stabilizeFixtureMessageIds,
|
||||
stabilizeRefreshLog,
|
||||
tokenizeSessionFixtureCwd,
|
||||
type HarvestedLog,
|
||||
@@ -319,20 +321,25 @@ describe('TypeScript SDK snapshots over the jsonrpc runtime', () => {
|
||||
const { result, notifications, logs, observedFiles, cwd } = await runScenario(scenario)
|
||||
const ordered = orderLogs(logs, scenario)
|
||||
const actualContext = contextOf(ordered, cwd)
|
||||
const files = fixtureFiles(scenario)
|
||||
|
||||
if (recording) {
|
||||
// Fixtures carry tokenized request headers; llm-replay reads only
|
||||
// assistant output and tool traffic, so scrubbing keeps prompts and
|
||||
// schemas out of the corpus without affecting replay.
|
||||
await mkdir(scenarioDir, { recursive: true })
|
||||
await Promise.all(ordered.map(async (log, index) => {
|
||||
const file = fixtureFiles(scenario)[index]
|
||||
const existing = await Promise.all(files.map(async file => existsSync(file) ? readFile(file, 'utf8') : ''))
|
||||
const fixtures = stabilizeFixtureMessageIds(
|
||||
ordered.map(log => scrubRequestHeaders(tokenizeSessionFixtureCwd(log.content))),
|
||||
existing,
|
||||
)
|
||||
await Promise.all(fixtures.map(async (fixture, index) => {
|
||||
const file = files[index]
|
||||
if (file === undefined) throw new Error(`no fixture path for persisted log ${index}`)
|
||||
await writeFile(file, scrubRequestHeaders(tokenizeSessionFixtureCwd(log.content)))
|
||||
await writeFile(file, fixture)
|
||||
}))
|
||||
}
|
||||
|
||||
const files = fixtureFiles(scenario)
|
||||
let expectedContents = await Promise.all(files.map(file => readFile(file, 'utf8')))
|
||||
|
||||
if (refreshing) {
|
||||
@@ -343,15 +350,18 @@ describe('TypeScript SDK snapshots over the jsonrpc runtime', () => {
|
||||
content: log.content,
|
||||
}))
|
||||
const replacements = refreshFixtureReplacements(harvested, expectedContents)
|
||||
expectedContents = await Promise.all(ordered.map(async (log, index) => {
|
||||
const refreshed = ordered.map((log, index) => {
|
||||
const existing = expectedContents[index]
|
||||
const file = files[index]
|
||||
if (existing === undefined || file === undefined) throw new Error(`no fixture for persisted log ${index}`)
|
||||
const stable = scrubRequestHeaders(tokenizeSessionFixtureCwd(
|
||||
if (existing === undefined) throw new Error(`no fixture for persisted log ${index}`)
|
||||
return scrubRequestHeaders(tokenizeSessionFixtureCwd(
|
||||
stabilizeRefreshLog(log.content, existing, replacements, actualContext),
|
||||
))
|
||||
})
|
||||
expectedContents = stabilizeFixtureMessageIds(refreshed, expectedContents)
|
||||
await Promise.all(expectedContents.map(async (stable, index) => {
|
||||
const file = files[index]
|
||||
if (file === undefined) throw new Error(`no fixture for persisted log ${index}`)
|
||||
await writeFile(file, stable)
|
||||
return stable
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write examples/mcp-memory/README.md
|
||||
README.md: 023e6aefce0e78cbbf52620426376e1dd0a6b8cf
|
||||
README.zh.md: 44ace680cd583f41903437a69c62e30817308ba2
|
||||
README.md: 792bb31b668b427c8734286878a9ec98071190d8
|
||||
README.zh.md: 51020f5288c4fbd245914280b8e7e4772e8cad69
|
||||
@@ -36,7 +36,7 @@ Without a repository checkout, download the selected overlay directly:
|
||||
mkdir -p "${DSH_HOME:-$HOME/.dsh}"
|
||||
curl --fail --location \
|
||||
--output "${DSH_HOME:-$HOME/.dsh}/memory.cordis.yml" \
|
||||
https://raw.githubusercontent.com/deepseek-harness/deepseek-harness/master/examples/mcp-memory/memorix.cordis.yml
|
||||
https://raw.githubusercontent.com/deepseek-ai/deepseek-harness-sdk/master/examples/mcp-memory/memorix.cordis.yml
|
||||
dsh web --patch "${DSH_HOME:-$HOME/.dsh}/memory.cordis.yml"
|
||||
```
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ dsh web --patch "$PWD/examples/mcp-memory/memorix.cordis.yml"
|
||||
mkdir -p "${DSH_HOME:-$HOME/.dsh}"
|
||||
curl --fail --location \
|
||||
--output "${DSH_HOME:-$HOME/.dsh}/memory.cordis.yml" \
|
||||
https://raw.githubusercontent.com/deepseek-harness/deepseek-harness/master/examples/mcp-memory/memorix.cordis.yml
|
||||
https://raw.githubusercontent.com/deepseek-ai/deepseek-harness-sdk/master/examples/mcp-memory/memorix.cordis.yml
|
||||
dsh web --patch "${DSH_HOME:-$HOME/.dsh}/memory.cordis.yml"
|
||||
```
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
"@deepseek-ai/dsh-bash-env": "workspace:*",
|
||||
"@deepseek-ai/dsh-bash-local": "workspace:*",
|
||||
"@deepseek-ai/dsh-bash-sandbox": "workspace:*",
|
||||
"@deepseek-ai/dsh-cli-demo": "workspace:*",
|
||||
"@deepseek-ai/dsh-code-runtime-worker": "workspace:*",
|
||||
"@deepseek-ai/dsh-command-feedback": "workspace:*",
|
||||
"@deepseek-ai/dsh-command-goal": "workspace:*",
|
||||
@@ -40,6 +39,7 @@
|
||||
"@deepseek-ai/dsh-llm-deepseek": "workspace:*",
|
||||
"@deepseek-ai/dsh-llm-pi-ai": "workspace:*",
|
||||
"@deepseek-ai/dsh-llm-replay": "workspace:*",
|
||||
"@deepseek-ai/dsh-loader-smoke": "workspace:*",
|
||||
"@deepseek-ai/dsh-lsp": "workspace:*",
|
||||
"@deepseek-ai/dsh-lsp-local": "workspace:*",
|
||||
"@deepseek-ai/dsh-permission": "workspace:*",
|
||||
|
||||
Reference in New Issue
Block a user