feat(example): sandbox-acp-agent — the live composition; RFCs to implemented

The three-entry cordis.yml (dsh-sandbox-local + dsh-bash-sandbox at a
read-only default + dsh-approval) served over ACP: the first live approval
composition. Recorded snapshot scenarios pin the wire end to end —
config-options advertisement, the mode-switching arc as the suite pinned
header (both switches, the prompt-section delta, one changed-by-the-user
notice per knob, a confined write landing under the switched mode), and
both escalation branches over scripted permissionAnswers (a grant runs
confined under workspace-write; a rejection executes nothing and pins the
fail-closed text). The with-key escalation e2e drives a real model +
real runner + the real bridge answerer, world-verified; ci.yml snapshot
lane and e2e.yml install bubblewrap so the confined replays actually
execute.

Both RFCs move to implemented/ (Decision/Consequences form, deferred
phases tracked in their own sections), with every cross-reference flipped.
This commit is contained in:
kingwl
2026-07-09 16:44:32 +08:00
parent 3f663c9154
commit ca39fd89b0
59 changed files with 1579 additions and 117 deletions

View File

@@ -73,6 +73,13 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
'list(): Agent[]',
],
},
{
key: 'approval',
summary: 'The `ctx.approval` service: dispatches ApprovalRequests to the `approval/request` waterfall and audits every ask/outcome pair to the requesting agent\'s session log.',
methods: [
'async request(req: ApprovalRequest): Promise<ApprovalOutcome>',
],
},
{
key: 'bash',
summary: 'Abstract bash execution service.',
@@ -125,6 +132,13 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
'stream(options: GenerateOptions): AsyncIterable<StreamChunk>',
],
},
{
key: 'sandbox',
summary: 'Abstract process-sandbox service.',
methods: [
'abstract confine(argv: readonly string[], policy: SandboxPolicy): ConfinedArgv',
],
},
{
key: 'sessionPersistence',
summary: 'Abstract durable session-persistence service.',
@@ -279,6 +293,12 @@ export const EVENT_API: readonly EventApiEntry[] = [
signature: '\'agent/turn-continuation\'(agent: Agent, turn: number, defaultDecision: ContinuationDecision, next: () => Promise<ContinuationDecision>): Promise<ContinuationDecision>',
summary: 'Waterfall: override the turn-continuation decision via a typed ContinuationDecision.',
},
{
name: 'approval/request',
mode: 'waterfall',
signature: '\'approval/request\'(this: ApprovalService, req: ApprovalRequest, next: () => Promise<ApprovalOutcome>): Promise<ApprovalOutcome>',
summary: 'Waterfall asking the composed answerers to decide one approval request.',
},
{
name: 'fs/edit-intent',
mode: 'waterfall',
@@ -445,6 +465,14 @@ export const TYPE_API: readonly TypeApiEntry[] = [
name: 'AgentStatus',
declaration: 'export type AgentStatus = \'idle\' | \'running\' | \'disposed\';',
},
{
name: 'ApprovalOutcome',
declaration: 'export type ApprovalOutcome = \'allowed-once\' | \'rejected\' | \'cancelled\' | \'unavailable\';',
},
{
name: 'ApprovalRequest',
declaration: 'export interface ApprovalRequest {\n agent: Agent;\n toolName: string;\n callId?: CallId;\n reason?: string;\n signal?: AbortSignal;\n}',
},
{
name: 'AskUserQuestionAnswer',
declaration: 'export interface AskUserQuestionAnswer {\n answers: AskUserQuestionAnswerItem[];\n}',
@@ -475,19 +503,23 @@ export const TYPE_API: readonly TypeApiEntry[] = [
},
{
name: 'BashExecRequest',
declaration: 'export interface BashExecRequest {\n command: string;\n workdir?: string | undefined;\n timeoutMs?: number | undefined;\n signal?: AbortSignal | undefined;\n stdin?: string | undefined;\n env?: Record<string, string> | undefined;\n owner?: OwnerToken | undefined;\n}',
declaration: 'export interface BashExecRequest {\n command: string;\n workdir?: string | undefined;\n timeoutMs?: number | undefined;\n signal?: AbortSignal | undefined;\n stdin?: string | undefined;\n env?: Record<string, string> | undefined;\n owner?: OwnerToken | undefined;\n sandboxMode?: SandboxMode | undefined;\n}',
},
{
name: 'BashExecSpec',
declaration: 'export interface BashExecSpec {\n command: string;\n workdir: string;\n timeoutMs: number;\n signal?: AbortSignal | undefined;\n stdin?: string | undefined;\n env?: Record<string, string> | undefined;\n owner: OwnerToken | undefined;\n}',
declaration: 'export interface BashExecSpec {\n command: string;\n workdir: string;\n timeoutMs: number;\n signal?: AbortSignal | undefined;\n stdin?: string | undefined;\n env?: Record<string, string> | undefined;\n owner: OwnerToken | undefined;\n sandboxMode: SandboxMode | undefined;\n}',
},
{
name: 'BashRunResult',
declaration: 'export interface BashRunResult {\n exitCode: number | null;\n signal: NodeJS.Signals | null;\n timedOut: boolean;\n aborted: boolean;\n timeoutMs: number;\n stdout: CollectedOutput;\n stderr: CollectedOutput;\n}',
declaration: 'export interface BashRunResult {\n exitCode: number | null;\n signal: NodeJS.Signals | null;\n timedOut: boolean;\n aborted: boolean;\n timeoutMs: number;\n stdout: CollectedOutput;\n stderr: CollectedOutput;\n sandbox?: BashSandboxInfo;\n}',
},
{
name: 'BashSandboxInfo',
declaration: 'export interface BashSandboxInfo {\n mode: SandboxMode;\n denied: boolean;\n enforcement?: SandboxEnforcement;\n runnerFailed?: boolean;\n}',
},
{
name: 'BashTask',
declaration: 'export interface BashTask {\n readonly id: BashTaskId;\n readonly command: string;\n status: BashTaskStatus;\n exitCode: number | null;\n signal: NodeJS.Signals | null;\n readonly done: Promise<void>;\n}',
declaration: 'export interface BashTask {\n readonly id: BashTaskId;\n readonly command: string;\n status: BashTaskStatus;\n exitCode: number | null;\n signal: NodeJS.Signals | null;\n readonly done: Promise<void>;\n sandbox?: BashSandboxInfo;\n}',
},
{
name: 'BashTaskId',
@@ -549,6 +581,14 @@ export const TYPE_API: readonly TypeApiEntry[] = [
name: 'CompactionResult',
declaration: 'export interface CompactionResult {\n startSeq: number;\n summarySeq: number;\n endSeq: number;\n summary: ContentBlock[];\n shadowedRange: {\n start: number;\n end: number;\n };\n shadowedSeqs: number[];\n shadowedTokenCount: number;\n}',
},
{
name: 'ConfinedArgv',
declaration: 'export interface ConfinedArgv {\n argv: string[];\n enforcement: SandboxEnforcement;\n denialSignatures: readonly string[];\n runnerFailureSignatures: readonly string[];\n}',
},
{
name: 'ConfinedSandboxMode',
declaration: 'export type ConfinedSandboxMode = Exclude<SandboxMode, \'danger-full-access\'>;',
},
{
name: 'ContentBlockMap',
declaration: 'export interface ContentBlockMap {\n \'text\': TextBlock;\n \'reasoning\': ReasoningBlock;\n \'tool-call\': ToolCallBlock;\n \'tool-result\': ToolResultBlock;\n}',
@@ -673,6 +713,18 @@ export const TYPE_API: readonly TypeApiEntry[] = [
name: 'ResumeAgentOptions',
declaration: 'export interface ResumeAgentOptions {\n agentId: AgentId;\n resumeSessionId: SessionId;\n agentOptions?: AgentOptions;\n}',
},
{
name: 'SandboxEnforcement',
declaration: 'export type SandboxEnforcement = \'full\' | \'partial\';',
},
{
name: 'SandboxMode',
declaration: 'export type SandboxMode = \'read-only\' | \'workspace-write\' | \'danger-full-access\';',
},
{
name: 'SandboxPolicy',
declaration: 'export interface SandboxPolicy {\n mode: ConfinedSandboxMode;\n workspaceRoot: string;\n}',
},
{
name: 'SendOptions',
declaration: 'export interface SendOptions {\n source?: MessageSource;\n}',