Merge branch 'worktree-session-modes-rfc' (master: sandbox stack #169, skills #109, prompt snapshots #254)
The stack rebases onto a moved master through its base branch. Beyond mechanical unions (both branches' demo scripts, example rows, service roles, tool lists, acp deps, doc budgets — each side fit alone, the union needs the higher ceilings), three semantic reconciliations: - The ACP bridge now carries BOTH per-session surfaces: the sandbox stack's config options + approval answerer and this branch's session modes; session/new and session/load advertise modes AND configOptions side by side. - The feature matrix supersedes the sandbox stance per the RFC's second-lander rule: session/set_mode and current_mode_update flip to shipped-by-dsh-mode, config-option rows stay as #169 wrote them, and §6 records both landed features under the picker-to-modes / knobs-to-config-options division. - The snapshot pin grammar (#254: one header snapshot + declared deltas + a Markdown prompt golden) gains a symmetric declaration for what a delta cannot express: expectedHeaderSnapshots — a plan-mode flip resorts the canonical tool list, so its widening lands as a second full snapshot, now its own Markdown section. The pin-less-class and model-turn-only-pin amendments carry over; new fixtures cover the extended writer paths, and the plan-acp-agent scenarios re-recorded under the merged composition (the app now bundles the skill tool) with the suite's refresh mode wired through.
This commit is contained in:
@@ -70,7 +70,9 @@ const GROUP_ORDER = [
|
||||
'llm',
|
||||
'core',
|
||||
'bash',
|
||||
'sandbox',
|
||||
'fs',
|
||||
'skill',
|
||||
'compact',
|
||||
'subagent',
|
||||
'web',
|
||||
@@ -123,7 +125,7 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
pkg: 'tools',
|
||||
title: 'Tool registry and execution waterfall',
|
||||
mode: 'core',
|
||||
consumers: ['agent-loop', 'tool-ask-user', 'tool-bash', 'tool-cordis', 'tool-fs', 'tool-subagent', 'tool-todo', 'tool-web', 'acp'],
|
||||
consumers: ['agent-loop', 'tool-ask-user', 'tool-bash', 'tool-cordis', 'tool-fs', 'tool-skill', 'tool-subagent', 'tool-todo', 'tool-web', 'acp'],
|
||||
note: 'Registers tool definitions, exposes schemas to the prompt, and routes calls through tools/pre-execute and tools/post-execute.',
|
||||
},
|
||||
{
|
||||
@@ -143,6 +145,15 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
consumers: ['stdio-agent', 'acp'],
|
||||
note: 'Folds the logged per-agent mode (mode/set), flushes user flips at turn boundaries, and enforces the mode through the assemble filter and the tools/pre-execute gate.',
|
||||
},
|
||||
{
|
||||
key: 'skills',
|
||||
pkg: 'skill',
|
||||
title: 'Skill provider registry',
|
||||
mode: 'seam',
|
||||
implementations: ['skill-local'],
|
||||
consumers: ['tool-skill'],
|
||||
note: 'Merges provider skill catalogs; tool-skill renders the session-prefix catalog and loads complete skill bodies.',
|
||||
},
|
||||
{
|
||||
key: 'agents',
|
||||
pkg: 'agent',
|
||||
@@ -164,9 +175,27 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
pkg: 'bash',
|
||||
title: 'Bash executor seam',
|
||||
mode: 'seam',
|
||||
implementations: ['bash-local'],
|
||||
implementations: ['bash-local', 'bash-sandbox'],
|
||||
consumers: ['tool-bash', 'hooks-claude', 'hooks-codex'],
|
||||
note: 'The model-facing bash tools and hook bridges consume this seam; sandboxed or remote executors can replace bash-local.',
|
||||
note: 'The model-facing bash tools and hook bridges consume this seam; sandboxed or remote executors replace bash-local without touching them.',
|
||||
},
|
||||
{
|
||||
key: 'sandbox',
|
||||
pkg: 'sandbox',
|
||||
title: 'Process-sandbox seam',
|
||||
mode: 'seam',
|
||||
implementations: ['sandbox-local'],
|
||||
consumers: ['bash-sandbox'],
|
||||
note: 'Consumers hand over the exact argv they are about to spawn; same-world backends wrap it under a per-call policy and report enforcement.',
|
||||
},
|
||||
{
|
||||
key: 'approval',
|
||||
pkg: 'approval',
|
||||
title: 'Approval seam',
|
||||
mode: 'seam',
|
||||
implementations: ['acp'],
|
||||
consumers: ['tools', 'tool-bash'],
|
||||
note: 'One-shot permission decisions dispatched over the `approval/request` waterfall; answerers are listeners (the ACP bridge for its own agents), absence fails closed to `unavailable`.',
|
||||
},
|
||||
{
|
||||
key: 'codeRuntime',
|
||||
@@ -214,6 +243,15 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
consumers: ['tool-web'],
|
||||
note: 'Search and fetch providers register into one ctx.web seam; tool-web owns the stable model-facing names.',
|
||||
},
|
||||
{
|
||||
key: 'workflows',
|
||||
pkg: 'workflow',
|
||||
title: 'Workflow script engine',
|
||||
mode: 'seam',
|
||||
implementations: ['workflow-workerthread'],
|
||||
consumers: ['tool-workflow'],
|
||||
note: 'One engine per context (bash shape, no named-provider registry); the worker-thread engine fans agent() calls out through ctx.subagents.',
|
||||
},
|
||||
]
|
||||
|
||||
const DYNAMIC_EVENT_DISPATCHERS: Array<{ event: string; pkg: string; method: string }> = [
|
||||
@@ -222,6 +260,14 @@ const DYNAMIC_EVENT_DISPATCHERS: Array<{ event: string; pkg: string; method: str
|
||||
// listeners or strand an already-started child run.
|
||||
{ event: 'subagent/start', pkg: 'subagent', method: 'events.dispatch' },
|
||||
{ event: 'subagent/end', pkg: 'subagent', method: 'events.dispatch' },
|
||||
// The workflow/* lifecycle events dispatch the same way, for the same
|
||||
// per-listener-containment reason (WorkflowService.emitWorkflowEvent).
|
||||
{ event: 'workflow/start', pkg: 'workflow', method: 'events.dispatch' },
|
||||
{ event: 'workflow/phase', pkg: 'workflow', method: 'events.dispatch' },
|
||||
{ event: 'workflow/log', pkg: 'workflow', method: 'events.dispatch' },
|
||||
{ event: 'workflow/agent-start', pkg: 'workflow', method: 'events.dispatch' },
|
||||
{ event: 'workflow/agent-end', pkg: 'workflow', method: 'events.dispatch' },
|
||||
{ event: 'workflow/end', pkg: 'workflow', method: 'events.dispatch' },
|
||||
]
|
||||
|
||||
function generatedHeader(title: string): string[] {
|
||||
@@ -673,7 +719,8 @@ function renderToolPipeline(): string {
|
||||
` toolCall["Session event: ${mermaidCode('tool/call')}<br/>logged before execution"]`,
|
||||
' presentCall["UI pending card<br/>presentCall(args)"]',
|
||||
` pre["${mermaidCode('tools/pre-execute')} waterfall<br/>hooks, permission, sandbox"]`,
|
||||
' denied["deny or ask<br/>tool body skipped"]',
|
||||
' denied["denied<br/>tool body skipped"]',
|
||||
` approval["${mermaidCode('ctx.approval')} one-shot prompt<br/>absent or unanswerable: deny"]`,
|
||||
` around["${mermaidCode('tools/execute')} waterfall<br/>timeout, retry, metrics (around dispatch)"]`,
|
||||
' toolBody["Registered tool execute() body"]',
|
||||
` fsGate["${mermaidCode('fs/write-intent')} or ${mermaidCode('fs/edit-intent')}<br/>tool-fs mutations only"]`,
|
||||
@@ -687,7 +734,10 @@ function renderToolPipeline(): string {
|
||||
' toolCall --> pre',
|
||||
' pre -->|allow| around',
|
||||
' around --> toolBody',
|
||||
' pre -->|deny or ask| denied',
|
||||
' pre -->|deny| denied',
|
||||
' pre -->|ask| approval',
|
||||
' approval -->|allowed-once| around',
|
||||
' approval -->|rejected, cancelled, unavailable| denied',
|
||||
' denied --> post',
|
||||
' toolBody --> fsGate',
|
||||
' fsGate --> toolBody',
|
||||
@@ -699,7 +749,7 @@ function renderToolPipeline(): string {
|
||||
' toolResult --> presentResult',
|
||||
'```',
|
||||
'',
|
||||
'Filesystem read-before-edit checks live below `tool-fs` on the `fs/*` event gate; hook bridges and future permission prompts live on the generic pre/post tool waterfalls; and around-dispatch concerns like the tool-call timeout policy (`@deepseek-ai/dsh-timeout-policy`) wrap core dispatch on `tools/execute`. That split lets the same hooks observe bash, fs, web, todo, and subagent calls without coupling those tools to one policy service. Code Mode rides the same pipeline twice over: `run_code` is itself a registered tool body, and each tool call its program makes re-enters `ctx.tools.execute()` through BOTH waterfalls — serialized one at a time, logged as a `tool/code-dispatch` session event, with a deny surfacing to the program as a binding rejection (a sub-call\'s `additionalContext` is deliberately dropped — no safe outlet mid-run preserves call/result adjacency).',
|
||||
'Filesystem read-before-edit checks live below `tool-fs` on the `fs/*` event gate; hook bridges and the approval seam\'s permission prompts live on the generic pre/post tool waterfalls; and around-dispatch concerns like the tool-call timeout policy (`@deepseek-ai/dsh-timeout-policy`) wrap core dispatch on `tools/execute`. That split lets the same hooks observe bash, fs, web, todo, and subagent calls without coupling those tools to one policy service.',
|
||||
'',
|
||||
...maintenanceFooter(maintenance),
|
||||
].join('\n')
|
||||
|
||||
Reference in New Issue
Block a user