feat(modes): per-session sandbox/approval switching — the session log as the store, ACP config options
effective(session) = findLast(the session own knob events)?.value ?? the composition-config default. One log-only event per knob, owned by its domain (bash/sandbox-mode in dsh-bash, approval/policy in dsh-approval), each exporting the same three-piece kit: the event declaration, a pure fold, and THE write path — a switch IS its event; no owner service, no facts map. Restart immunity and multi-session isolation fall out of the log replay by construction. Execution follows the fold on both sides: the bash tool stamps escalation grant > session override > executor default, and the approval seam prepends the never-gate that auto-rejects before any interactive answerer. Visibility is two layers per knob: a per-agent prompt section states the effective value on every request (logged through request/header*, so what-the-model-was-told replays from the log), and an agent/pre-step narrator injects at most one coalesced delta notice with positional attribution (user switch vs operator/config drift). The ACP bridge advertises one capability-gated select per composable knob with currentValue folded per session, validates set_config_option against the closed vocabularies, and anchors idle switches at the next turn prompt-submit under the turn-enclosure contract.
This commit is contained in:
@@ -31,7 +31,7 @@ export interface AcpConfig {
|
||||
|
||||
Depends on: `Stream` (`@agentclientprotocol/sdk`)
|
||||
|
||||
Source: [`packages/ui/acp/src/index.ts:241`](../packages/ui/acp/src/index.ts)
|
||||
Source: [`packages/ui/acp/src/index.ts:248`](../packages/ui/acp/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-acp-agent`
|
||||
|
||||
@@ -130,6 +130,37 @@ Depends on: [`AgentId`](../packages/core/agent/src/index.ts) · [`AgentOptions`]
|
||||
|
||||
Source: [`packages/core/agent-loop/src/index.ts:36`](../packages/core/agent-loop/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-approval`
|
||||
|
||||
```ts config-catalog
|
||||
/** Plugin config. All optional — `static Config` supplies the defaults. */
|
||||
export interface Config {
|
||||
/**
|
||||
* The deployment's default {@link ApprovalPolicy} for sessions without an
|
||||
* `approval/policy` override — `'ask'` delegates to the composed answerers
|
||||
* (fail-closed with none); `'never'` auto-rejects every ask without
|
||||
* prompting (the deterministic CI/unattended stance).
|
||||
*/
|
||||
policy?: ApprovalPolicy
|
||||
}
|
||||
|
||||
/**
|
||||
* A session's approval policy — what happens to an {@link ApprovalService}
|
||||
* ask BEFORE any interactive answerer sees it:
|
||||
*
|
||||
* - `'ask'` (the default) — delegate to the composed answerers; with none
|
||||
* composed the chain falls through to the fail-closed `'unavailable'`
|
||||
* (exactly today's behavior).
|
||||
* - `'never'` — never prompt anyone: every ask resolves `'rejected'`
|
||||
* deterministically. The strict headless stance (CI, unattended runs) and
|
||||
* the only policy value stated in the system prompt — unlike `'ask'`, its
|
||||
* outcome is knowable without asking, so stating it cannot overclaim.
|
||||
*/
|
||||
export type ApprovalPolicy = 'ask' | 'never'
|
||||
```
|
||||
|
||||
Source: [`packages/approval/approval/src/index.ts:244`](../packages/approval/approval/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-bash-local`
|
||||
|
||||
```ts config-catalog
|
||||
@@ -1029,7 +1060,6 @@ Source: [`packages/workflow/workflow-workerthread/src/index.ts:69`](../packages/
|
||||
These load from a `cordis.yml` entry with no `config:` block; they declare no config surface.
|
||||
|
||||
- `@deepseek-ai/dsh-agent` ([`packages/core/agent/src/index.ts`](../packages/core/agent/src/index.ts))
|
||||
- `@deepseek-ai/dsh-approval` ([`packages/approval/approval/src/index.ts`](../packages/approval/approval/src/index.ts))
|
||||
- `@deepseek-ai/dsh-fs-policy` ([`packages/fs/fs-policy/src/index.ts`](../packages/fs/fs-policy/src/index.ts))
|
||||
- `@deepseek-ai/dsh-llm` ([`packages/llm/llm/src/index.ts`](../packages/llm/llm/src/index.ts))
|
||||
- `@deepseek-ai/dsh-session` ([`packages/core/session/src/index.ts`](../packages/core/session/src/index.ts))
|
||||
|
||||
@@ -173,7 +173,7 @@ Waterfall asking the composed answerers to decide one approval request. Dispatch
|
||||
'approval/request'(this: ApprovalService, req: ApprovalRequest, next: () => Promise<ApprovalOutcome>): Promise<ApprovalOutcome>
|
||||
```
|
||||
|
||||
Source: [`packages/approval/approval/src/index.ts:52`](../../packages/approval/approval/src/index.ts)
|
||||
Source: [`packages/approval/approval/src/index.ts:64`](../../packages/approval/approval/src/index.ts)
|
||||
|
||||
## `fs/*`
|
||||
|
||||
|
||||
@@ -44,11 +44,13 @@ Source: [`packages/core/agent/src/index.ts:117`](../../packages/core/agent/src/i
|
||||
|
||||
The `ctx.approval` service: dispatches ApprovalRequests to the `approval/request` waterfall and audits every ask/outcome pair to the requesting agent's session log. Stateless between requests — grants are returned to the caller, never stored here.
|
||||
|
||||
Owns the policy tier too (`effective = fold(the session's 'approval/policy' events) ?? config.policy`): a PREPENDED decide-or-delegate gate resolves `'never'` sessions to `'rejected'` before any interactive answerer is prompted, a per-agent prompt section states a `'never'` policy (and only that one — an `'ask'` promise could overclaim an answerer that headless compositions do not have), and an `agent/pre-step` narrator injects at most one coalesced notice when a session's effective policy moved past what the model was last told.
|
||||
|
||||
```ts cordis-catalog
|
||||
async request(req: ApprovalRequest): Promise<ApprovalOutcome>
|
||||
```
|
||||
|
||||
Source: [`packages/approval/approval/src/index.ts:168`](../../packages/approval/approval/src/index.ts)
|
||||
Source: [`packages/approval/approval/src/index.ts:269`](../../packages/approval/approval/src/index.ts)
|
||||
|
||||
## `ctx.bash` — `BashExecutor` (abstract seam)
|
||||
|
||||
@@ -75,7 +77,7 @@ onTaskDone(listener: BashTaskListener): () => void
|
||||
|
||||
Types: [BashExecRequest](../core-data-structures/bash.md) · [BashExecSpec](../core-data-structures/bash.md) · [BashRunResult](../core-data-structures/bash.md) · [BashTask](../core-data-structures/bash.md) · [BashTaskRead](../core-data-structures/bash.md)
|
||||
|
||||
Source: [`packages/bash/bash/src/index.ts:61`](../../packages/bash/bash/src/index.ts)
|
||||
Source: [`packages/bash/bash/src/index.ts:62`](../../packages/bash/bash/src/index.ts)
|
||||
|
||||
## `ctx.codeRuntime` — `CodeRuntime` (abstract seam)
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ This matrix shows which packages dispatch each harness-owned event and which pac
|
||||
| `agent/created` | `emit` | [`packages/core/agent/src/types.ts:265`](../packages/core/agent/src/types.ts) | [`agent`](../packages/core/agent) (`emit`) | [`stdio-agent`](../packages/ui/stdio-agent) |
|
||||
| `agent/disposed` | `emit` | [`packages/core/agent/src/types.ts:272`](../packages/core/agent/src/types.ts) | [`agent`](../packages/core/agent) (`emit`) | [`stdio-agent`](../packages/ui/stdio-agent) |
|
||||
| `agent/error` | `emit` | [`packages/core/agent/src/types.ts:476`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | - |
|
||||
| `agent/pre-step` | `serial` | [`packages/core/agent/src/types.ts:357`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`compact-basic`](../packages/compact/compact-basic) |
|
||||
| `agent/prompt-submit` | `waterfall` | [`packages/core/agent/src/types.ts:370`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) |
|
||||
| `agent/pre-step` | `serial` | [`packages/core/agent/src/types.ts:357`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`approval`](../packages/approval/approval), [`compact-basic`](../packages/compact/compact-basic) |
|
||||
| `agent/prompt-submit` | `waterfall` | [`packages/core/agent/src/types.ts:370`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`acp`](../packages/ui/acp), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) |
|
||||
| `agent/queued` | `emit` | [`packages/core/agent/src/types.ts:290`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | - |
|
||||
| `agent/request` | `waterfall` | [`packages/core/agent/src/types.ts:394`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | - |
|
||||
| `agent/session-prefix` | `waterfall` | [`packages/core/agent/src/types.ts:441`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | - |
|
||||
@@ -19,7 +19,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
|
||||
| `agent/status` | `emit` | [`packages/core/agent/src/types.ts:281`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/ui/acp), [`invariants`](../packages/support/invariants), [`repeat-tool-guard`](../packages/guard/repeat-tool-guard), [`stdio-agent`](../packages/ui/stdio-agent) |
|
||||
| `agent/step-result` | `waterfall` | [`packages/core/agent/src/types.ts:451`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | - |
|
||||
| `agent/turn-continuation` | `waterfall` | [`packages/core/agent/src/types.ts:464`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) |
|
||||
| `approval/request` | `waterfall` | [`packages/approval/approval/src/index.ts:52`](../packages/approval/approval/src/index.ts) | [`approval`](../packages/approval/approval) (`waterfall`) | [`acp`](../packages/ui/acp) |
|
||||
| `approval/request` | `waterfall` | [`packages/approval/approval/src/index.ts:63`](../packages/approval/approval/src/index.ts) | [`approval`](../packages/approval/approval) (`waterfall`) | [`acp`](../packages/ui/acp), [`approval`](../packages/approval/approval) |
|
||||
| `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:123`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`) | [`fs-policy`](../packages/fs/fs-policy) |
|
||||
| `fs/observed` | `emit` | [`packages/fs/fs/src/index.ts:138`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`emit`) | [`fs-policy`](../packages/fs/fs-policy) |
|
||||
| `fs/write-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:109`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`) | [`fs-policy`](../packages/fs/fs-policy) |
|
||||
|
||||
@@ -161,6 +161,7 @@ flowchart TD
|
||||
pkg_approval --> pkg_brand
|
||||
pkg_approval --> pkg_llm
|
||||
pkg_approval --> pkg_session
|
||||
pkg_approval --> pkg_system_prompt
|
||||
pkg_workflow --> pkg_agent
|
||||
pkg_workflow --> pkg_brand
|
||||
pkg_workflow --> pkg_llm
|
||||
@@ -180,6 +181,7 @@ flowchart TD
|
||||
pkg_agent_loop --> pkg_system_prompt
|
||||
pkg_agent_loop --> pkg_tools
|
||||
pkg_tool_bash --> pkg_agent
|
||||
pkg_tool_bash --> pkg_approval
|
||||
pkg_tool_bash --> pkg_bash
|
||||
pkg_tool_bash --> pkg_llm
|
||||
pkg_tool_bash --> pkg_sandbox
|
||||
@@ -211,7 +213,9 @@ flowchart TD
|
||||
pkg_hooks_codex --> pkg_tools
|
||||
pkg_acp --> pkg_agent
|
||||
pkg_acp --> pkg_approval
|
||||
pkg_acp --> pkg_bash
|
||||
pkg_acp --> pkg_llm
|
||||
pkg_acp --> pkg_sandbox
|
||||
pkg_acp --> pkg_session
|
||||
pkg_acp --> pkg_session_persistence
|
||||
pkg_acp --> pkg_tools
|
||||
@@ -320,12 +324,12 @@ flowchart TD
|
||||
| [`session-persistence-sqlite`](../packages/session-persistence/session-persistence-sqlite) | `session-persistence` | [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence) |
|
||||
| [`invariants`](../packages/support/invariants) | `support` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||
| [`user-interaction`](../packages/ui/user-interaction) | `ui` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm) |
|
||||
| [`approval`](../packages/approval/approval) | `approval` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
|
||||
| [`approval`](../packages/approval/approval) | `approval` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) |
|
||||
| [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm) |
|
||||
| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`approval`](../packages/approval/approval), [`code-runtime`](../packages/code-runtime/code-runtime), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) |
|
||||
| [`bash-sandbox`](../packages/bash/bash-sandbox) | `bash` | [`bash`](../packages/bash/bash), [`bash-local`](../packages/bash/bash-local), [`sandbox`](../packages/sandbox/sandbox) |
|
||||
| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`tool-bash`](../packages/bash/tool-bash) | `bash` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`tool-bash`](../packages/bash/tool-bash) | `bash` | [`agent`](../packages/core/agent), [`approval`](../packages/approval/approval), [`bash`](../packages/bash/bash), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`tools`](../packages/core/tools) |
|
||||
| [`tool-web`](../packages/web/tool-web) | `web` | [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`web`](../packages/web/web) |
|
||||
@@ -333,7 +337,7 @@ flowchart TD
|
||||
| [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
|
||||
| [`tool-cordis`](../packages/cordis/tool-cordis) | `cordis` | [`tools`](../packages/core/tools) |
|
||||
| [`hooks-codex`](../packages/hooks/hooks-codex) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
|
||||
| [`acp`](../packages/ui/acp) | `ui` | [`agent`](../packages/core/agent), [`approval`](../packages/approval/approval), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) |
|
||||
| [`acp`](../packages/ui/acp) | `ui` | [`agent`](../packages/core/agent), [`approval`](../packages/approval/approval), [`bash`](../packages/bash/bash), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) |
|
||||
| [`tool-ask-user`](../packages/ui/tool-ask-user) | `ui` | [`agent`](../packages/core/agent), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) |
|
||||
| [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) | `guard` | [`agent`](../packages/core/agent), [`tools`](../packages/core/tools) |
|
||||
| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
|
||||
|
||||
@@ -23,7 +23,7 @@ An approval question was put to the answerer chain — log-only audit (like `hoo
|
||||
|
||||
Types: [CallId](core-data-structures/core.md)
|
||||
|
||||
Source: [`packages/approval/approval/src/index.ts:66`](../packages/approval/approval/src/index.ts)
|
||||
Source: [`packages/approval/approval/src/index.ts:77`](../packages/approval/approval/src/index.ts)
|
||||
|
||||
#### `approval/decided` — log-only
|
||||
|
||||
@@ -33,7 +33,17 @@ The outcome of a prior `approval/asked` (same `id`) — log-only audit. Exactly
|
||||
'approval/decided': { id: ApprovalRequestId; outcome: ApprovalOutcome }
|
||||
```
|
||||
|
||||
Source: [`packages/approval/approval/src/index.ts:77`](../packages/approval/approval/src/index.ts)
|
||||
Source: [`packages/approval/approval/src/index.ts:88`](../packages/approval/approval/src/index.ts)
|
||||
|
||||
#### `approval/policy` — log-only
|
||||
|
||||
The session's approval policy was switched — log-only, durable, replayable, never in the model transcript (the model learns the policy from the prompt section and the narrator's notices). The LAST such event is the session's override (effectiveApprovalPolicy); who asked for it is derivable from position (an event after the log's last `request/header*` was a runtime switch by the user).
|
||||
|
||||
```ts persistence-catalog
|
||||
'approval/policy': { policy: ApprovalPolicy }
|
||||
```
|
||||
|
||||
Source: [`packages/approval/approval/src/index.ts:100`](../packages/approval/approval/src/index.ts)
|
||||
|
||||
### `assistant/*`
|
||||
|
||||
@@ -61,6 +71,18 @@ Types: [ContentBlock](core-data-structures/core.md) · [TokenUsage](core-data-st
|
||||
|
||||
Source: [`packages/core/session/src/types.ts:320`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `bash/*`
|
||||
|
||||
#### `bash/sandbox-mode` — log-only
|
||||
|
||||
The session's sandbox mode was switched — log-only (like `approval/*`; NOT a surface event, carries no `surfaceOp`): durable and replayable, never in the model transcript. The LAST such event is the session's override (effectiveSandboxMode); who asked for it is derivable from position (an event after the log's last `request/header*` was a runtime switch by the user; see the tool layer's narrator).
|
||||
|
||||
```ts persistence-catalog
|
||||
'bash/sandbox-mode': { mode: SandboxMode }
|
||||
```
|
||||
|
||||
Source: [`packages/bash/bash/src/session-mode.ts:31`](../packages/bash/bash/src/session-mode.ts)
|
||||
|
||||
### `compact/*`
|
||||
|
||||
#### `compact/end` — log-only
|
||||
|
||||
Reference in New Issue
Block a user