zidane 7.0.31 → 7.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +21 -2
  2. package/dist/{acp-m3TLCY_C.js → acp-B5UfEhMd.js} +4 -4
  3. package/dist/{acp-m3TLCY_C.js.map → acp-B5UfEhMd.js.map} +1 -1
  4. package/dist/acp-cli.js +6 -6
  5. package/dist/acp.d.ts +2 -2
  6. package/dist/acp.js +1 -1
  7. package/dist/{agent-9gf8ZlBR.js → agent-_xxElOgH.js} +307 -33
  8. package/dist/agent-_xxElOgH.js.map +1 -0
  9. package/dist/agent.d.ts +1 -1
  10. package/dist/agent.js +1 -1
  11. package/dist/{anthropic-B4UmPm4H.js → anthropic-DPPs3wjJ.js} +111 -7
  12. package/dist/anthropic-DPPs3wjJ.js.map +1 -0
  13. package/dist/{approvals-DaCEPCTV.js → approvals-u2T19Dc-.js} +297 -95
  14. package/dist/approvals-u2T19Dc-.js.map +1 -0
  15. package/dist/{auth-CJogE9q7.js → auth-HRKkOErn.js} +3 -3
  16. package/dist/{auth-CJogE9q7.js.map → auth-HRKkOErn.js.map} +1 -1
  17. package/dist/chat/pure.d.ts +3 -3
  18. package/dist/chat/pure.js +1 -1
  19. package/dist/chat.d.ts +6 -6
  20. package/dist/chat.js +8 -8
  21. package/dist/contexts/daytona.d.ts +3 -3
  22. package/dist/contexts/docker.d.ts +1 -1
  23. package/dist/contexts/e2b.d.ts +2 -2
  24. package/dist/contexts/sandbox.d.ts +1 -1
  25. package/dist/{contexts-BSFQVclW.js → contexts-CRE9pw07.js} +544 -17
  26. package/dist/contexts-CRE9pw07.js.map +1 -0
  27. package/dist/contexts.d.ts +4 -4
  28. package/dist/contexts.js +2 -2
  29. package/dist/daemon-cli.js +1 -1
  30. package/dist/eval.d.ts +1 -1
  31. package/dist/eval.js +3 -3
  32. package/dist/extensions.d.ts +1 -1
  33. package/dist/extensions.js +1 -1
  34. package/dist/headless.d.ts +1 -1
  35. package/dist/headless.js +3 -3
  36. package/dist/{index-B6FkK6ZN.d.ts → index-C37_ppWv.d.ts} +2 -2
  37. package/dist/index-C37_ppWv.d.ts.map +1 -0
  38. package/dist/{index-JkEO3MBV.d.ts → index-D3TTaaQ5.d.ts} +39 -7
  39. package/dist/index-D3TTaaQ5.d.ts.map +1 -0
  40. package/dist/{index-hd4zLhp8.d.ts → index-DZhrTn-v.d.ts} +307 -16
  41. package/dist/index-DZhrTn-v.d.ts.map +1 -0
  42. package/dist/index.d.ts +5 -5
  43. package/dist/index.js +8 -8
  44. package/dist/{ipc-BtN5SIc0.js → ipc-DwauKEE-.js} +3 -3
  45. package/dist/{ipc-BtN5SIc0.js.map → ipc-DwauKEE-.js.map} +1 -1
  46. package/dist/ipc.d.ts +5 -5
  47. package/dist/ipc.js +3 -3
  48. package/dist/{types-U-rIVUDw.d.ts → isolation-Kfoof0MM.d.ts} +93 -2
  49. package/dist/isolation-Kfoof0MM.d.ts.map +1 -0
  50. package/dist/mcp.d.ts +1 -1
  51. package/dist/output/stream-json.d.ts +1 -1
  52. package/dist/output/terminal.d.ts +1 -1
  53. package/dist/{plugins-B531Tz5b.js → plugins-gbI7SBA6.js} +3 -3
  54. package/dist/{plugins-B531Tz5b.js.map → plugins-gbI7SBA6.js.map} +1 -1
  55. package/dist/plugins.d.ts +1 -1
  56. package/dist/plugins.js +1 -1
  57. package/dist/presets.d.ts +1 -1
  58. package/dist/presets.js +1 -1
  59. package/dist/providers/anthropic.d.ts +2 -2
  60. package/dist/providers/anthropic.js +2 -2
  61. package/dist/providers/arcee.d.ts +1 -1
  62. package/dist/providers/baseten.d.ts +1 -1
  63. package/dist/providers/cerebras.d.ts +1 -1
  64. package/dist/providers/local.d.ts +1 -1
  65. package/dist/providers/openai-compat.d.ts +1 -1
  66. package/dist/providers/openai.d.ts +1 -1
  67. package/dist/providers/openrouter.d.ts +1 -1
  68. package/dist/providers/xai.d.ts +1 -1
  69. package/dist/providers.d.ts +2 -2
  70. package/dist/providers.js +1 -1
  71. package/dist/restate.d.ts +2 -2
  72. package/dist/restate.js +1 -1
  73. package/dist/{sandbox-CDl9tfTo.d.ts → sandbox-BH0WQkXn.d.ts} +2 -2
  74. package/dist/{sandbox-CDl9tfTo.d.ts.map → sandbox-BH0WQkXn.d.ts.map} +1 -1
  75. package/dist/{server-ktOMu5gk.js → server-DvPnPSZb.js} +8 -8
  76. package/dist/{server-ktOMu5gk.js.map → server-DvPnPSZb.js.map} +1 -1
  77. package/dist/session/sqlite.d.ts +1 -1
  78. package/dist/session/sqlite.d.ts.map +1 -1
  79. package/dist/session/sqlite.js +7 -0
  80. package/dist/session/sqlite.js.map +1 -1
  81. package/dist/{session-CVy-2mAy.js → session-BqRP1hS7.js} +33 -3
  82. package/dist/session-BqRP1hS7.js.map +1 -0
  83. package/dist/session.d.ts +1 -1
  84. package/dist/session.js +1 -1
  85. package/dist/{skills-PJyiSRCp.js → skills-BC3dodWp.js} +2 -2
  86. package/dist/{skills-PJyiSRCp.js.map → skills-BC3dodWp.js.map} +1 -1
  87. package/dist/skills.d.ts +1 -1
  88. package/dist/skills.js +1 -1
  89. package/dist/{src-CjkY6fBj.js → src-CkNQKFKr.js} +3 -3
  90. package/dist/{src-CjkY6fBj.js.map → src-CkNQKFKr.js.map} +1 -1
  91. package/dist/{tool-formatters-C4QIFCNa.d.ts → tool-formatters-C5s88MUd.d.ts} +2 -2
  92. package/dist/{tool-formatters-C4QIFCNa.d.ts.map → tool-formatters-C5s88MUd.d.ts.map} +1 -1
  93. package/dist/tools/fetch-url.d.ts +1 -1
  94. package/dist/tools/web-search.d.ts +1 -1
  95. package/dist/tools.d.ts +1 -1
  96. package/dist/tools.js +1 -1
  97. package/dist/{transcript-anchors-Bbn1HX5D.d.ts → transcript-anchors-DxofXw_y.d.ts} +3 -3
  98. package/dist/transcript-anchors-DxofXw_y.d.ts.map +1 -0
  99. package/dist/tui.d.ts +21 -28
  100. package/dist/tui.d.ts.map +1 -1
  101. package/dist/tui.js +200 -74
  102. package/dist/tui.js.map +1 -1
  103. package/dist/{turn-operations-BgqpLlOG.js → turn-operations-6JDu2ewy.js} +24 -2
  104. package/dist/{turn-operations-BgqpLlOG.js.map → turn-operations-6JDu2ewy.js.map} +1 -1
  105. package/dist/{turn-operations-BhHCpCkP.d.ts → turn-operations-BaQnxzhr.d.ts} +14 -4
  106. package/dist/{turn-operations-BhHCpCkP.d.ts.map → turn-operations-BaQnxzhr.d.ts.map} +1 -1
  107. package/dist/types-CWgENVLY.js.map +1 -1
  108. package/dist/types.d.ts +3 -3
  109. package/docs/ARCHITECTURE.md +13 -3
  110. package/docs/CHAT.md +9 -0
  111. package/docs/EXECUTION_CONTEXT.md +64 -3
  112. package/docs/INTERACTIONS.md +14 -2
  113. package/docs/SKILL.md +31 -1
  114. package/docs/TUI.md +6 -5
  115. package/package.json +1 -1
  116. package/dist/agent-9gf8ZlBR.js.map +0 -1
  117. package/dist/anthropic-B4UmPm4H.js.map +0 -1
  118. package/dist/approvals-DaCEPCTV.js.map +0 -1
  119. package/dist/contexts-BSFQVclW.js.map +0 -1
  120. package/dist/index-B6FkK6ZN.d.ts.map +0 -1
  121. package/dist/index-JkEO3MBV.d.ts.map +0 -1
  122. package/dist/index-hd4zLhp8.d.ts.map +0 -1
  123. package/dist/session-CVy-2mAy.js.map +0 -1
  124. package/dist/transcript-anchors-Bbn1HX5D.d.ts.map +0 -1
  125. package/dist/types-U-rIVUDw.d.ts.map +0 -1
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { $_ as SessionData, B_ as ToolStateView, Bg as PromptVideoPart, Br as InteractionToolOptions, C as SuccessfulMutationOutcome, C_ as ToolContext, D_ as ReadStateEntry, Dg as McpServerConfig, Dm as TurnResult, Em as ToolSpec, Fg as PromptAudioPart, G_ as SkillConfig, Gg as RetryConfig, Hg as RepeatGuardToolMatcher, I_ as StagedToolState, Ig as PromptDocumentPart, Jg as SessionEndStatus, Ji as AgentHookMap, Kg as RunHookMap, L_ as ToolStateController, Lg as PromptImagePart, Mg as OAuthRefreshHookContext, Ng as ProgressNudgeKind, O_ as ReadStateMap, Og as McpToolHookContext, Pg as ProgressNudgeMessage, Q_ as Session, Qg as SpawnHookContext, R_ as ToolStateOp, Rg as PromptPart, Rm as OpenRouterParams, S as MutationValidationResult, S_ as toolResultToText, Sg as DEFAULT_AGENT_CLOCK, Sm as StreamOptions, T_ as ToolMap, Tm as ToolResult, Ug as ResolveContentRef, Vg as RepeatGuardConfig, Wg as ResolveContentRefBlock, X_ as CreateSessionOptions, Xg as SessionMessage, Xi as AgentOptions, Y_ as SkillsConfig, Yg as SessionHookContext, Yi as AgentHooks, Zg as SessionTurn, Zi as AgentToolState, _ as statsByModel, __ as ToolResultVideoContent, _g as ChildRunStats, a_ as ThinkingLevel, b as MutationValidationHooksOptions, b_ as toolOutputBudgetByteLength, bh as CerebrasParams, c_ as ToolOutcome, ch as OpenAIParams, d_ as ToolResultDocumentContent, dg as AgentClock, f_ as ToolResultDocumentRefContent, fg as AgentRunOptions, fv as RemoteStoreOptions, g_ as ToolResultTextContent, gc as SpawnToolState, h as flattenTurns, h_ as ToolResultMetadata, hc as SpawnToolOptions, kh as AnthropicParams, l_ as ToolResultAudioContent, m_ as ToolResultImageRefContent, nv as SessionStore, p as ModelUsage, p_ as ToolResultImageContent, pg as AgentStats, q_ as SkillResource, qg as SessionContentBlock, qi as Agent, ry as McpConnection, s_ as ToolHookContext, t as Preset, t_ as StreamHookContext, tv as SessionRun, u_ as ToolResultContent, uc as ChildAgent, ug as AgentBehavior, v as MutationValidationBatch, v_ as TurnFinishReason, vm as Provider, vr as ValidationResult, w_ as ToolDef, wm as ToolCall, x as MutationValidationMutation, x_ as toolOutputByteLength, xm as StreamCallbacks, y as MutationValidationFailure, y_ as TurnUsage, ym as ProviderCapabilities, z_ as ToolStateStore, zg as PromptTextPart } from "./index-hd4zLhp8.js";
1
+ import { $_ as SkillsConfig, $g as SessionHookContext, Ah as AnthropicParams, B_ as StagedToolState, Bg as PromptDocumentPart, Br as InteractionToolOptions, C as SuccessfulMutationOutcome, C_ as TurnUsage, Cm as StreamOptions, D_ as ToolContext, Dm as ToolSpec, E_ as toolResultToText, Eg as DEFAULT_AGENT_CLOCK, Em as ToolResult, Gg as RepeatGuardConfig, H_ as ToolStateOp, Hg as PromptPart, Ig as OAuthRefreshHookContext, Jg as ResolveContentRefBlock, Ji as AgentHookMap, Kg as RepeatGuardToolMatcher, Lg as ProgressNudgeKind, M_ as ReadStateMap, Mg as McpToolHookContext, O_ as ToolDef, Om as TurnResult, Qg as SessionEndStatus, Rg as ProgressNudgeMessage, S as MutationValidationResult, S_ as TurnFinishReason, Sm as StreamCallbacks, T_ as toolOutputByteLength, Tm as ToolCall, U_ as ToolStateStore, Ug as PromptTextPart, V_ as ToolStateController, Vg as PromptImagePart, W_ as ToolStateView, Wg as PromptVideoPart, Xg as RunHookMap, Xi as AgentOptions, Y_ as SkillConfig, Yg as RetryConfig, Yi as AgentHooks, Z_ as SkillResource, Zg as SessionContentBlock, Zi as AgentToolState, _ as statsByModel, __ as ToolResultImageContent, _g as AgentStats, a_ as StreamHookContext, av as SessionRun, b as MutationValidationHooksOptions, b_ as ToolResultTextContent, bm as ProviderCapabilities, d_ as ToolHookContext, e_ as SessionMessage, ev as CreateSessionOptions, f_ as ToolOutcome, g_ as ToolResultDocumentRefContent, gc as SpawnToolState, gg as AgentRunOptions, gv as RemoteStoreOptions, h as flattenTurns, h_ as ToolResultDocumentContent, hc as SpawnToolOptions, hg as AgentClock, j_ as ReadStateEntry, jg as McpServerConfig, k_ as ToolMap, l_ as ThinkingLevel, lh as OpenAIParams, m_ as ToolResultContent, mg as AgentBehavior, n_ as SpawnHookContext, nv as Session, ov as SessionStore, p as ModelUsage, p_ as ToolResultAudioContent, qg as ResolveContentRef, qi as Agent, rv as SessionData, sy as McpConnection, t as Preset, t_ as SessionTurn, uc as ChildAgent, v as MutationValidationBatch, v_ as ToolResultImageRefContent, vr as ValidationResult, w_ as toolOutputBudgetByteLength, x as MutationValidationMutation, x_ as ToolResultVideoContent, xg as ChildRunStats, xh as CerebrasParams, y as MutationValidationFailure, y_ as ToolResultMetadata, ym as Provider, zg as PromptAudioPart, zm as OpenRouterParams } from "./index-DZhrTn-v.js";
2
2
  import { d as ClassifiedErrorKind, i as AgentContextExceededError, l as CONTEXT_EXCEEDED_MESSAGE_PATTERNS, n as AgentAbortedError, o as AgentProviderError, p as matchesContextExceeded, r as AgentBudgetExceededError, s as AgentToolNotAllowedError, u as ClassifiedError } from "./timeout-DEaqslLO.js";
3
- import { _ as TaskHandle, a as ContextType, c as ExecutionContext, g as TaskExitInfo, h as TaskEntry, l as ExecutionHandle, n as ContextCapabilities, o as DetachedTasksCapability, p as SpawnConfig, r as ContextHardening, s as ExecResult, t as BackgroundTaskStatus, v as TaskStallInfo } from "./types-U-rIVUDw.js";
4
- import { t as SandboxProvider } from "./sandbox-CDl9tfTo.js";
3
+ import { C as TaskHandle, S as TaskExitInfo, c as ContextCapabilities, d as ContextType, f as DetachedTasksCapability, h as ExecutionHandle, l as ContextHardening, m as ExecutionContext, p as ExecResult, s as BackgroundTaskStatus, w as TaskStallInfo, x as TaskEntry, y as SpawnConfig } from "./isolation-Kfoof0MM.js";
4
+ import { t as SandboxProvider } from "./sandbox-BH0WQkXn.js";
5
5
  export { type Agent, AgentAbortedError, type AgentBehavior, AgentBudgetExceededError, type AgentClock, AgentContextExceededError, type AgentHookMap, type AgentHooks, type AgentOptions, AgentProviderError, type AgentRunOptions, type AgentStats, AgentToolNotAllowedError, type AgentToolState, type AnthropicParams, type BackgroundTaskStatus, CONTEXT_EXCEEDED_MESSAGE_PATTERNS, type CerebrasParams, type ChildAgent, type ChildRunStats, type ClassifiedError, type ClassifiedErrorKind, type ContextCapabilities, type ContextHardening, type ContextType, type CreateSessionOptions, DEFAULT_AGENT_CLOCK, type DetachedTasksCapability, type ExecResult, type ExecutionContext, type ExecutionHandle, type InteractionToolOptions, type McpConnection, type McpServerConfig, type McpToolHookContext, type ModelUsage, type MutationValidationBatch, type MutationValidationFailure, type MutationValidationHooksOptions, type MutationValidationMutation, type MutationValidationResult, type OAuthRefreshHookContext, type OpenAIParams, type OpenRouterParams, type Preset, type ProgressNudgeKind, type ProgressNudgeMessage, type PromptAudioPart, type PromptDocumentPart, type PromptImagePart, type PromptPart, type PromptTextPart, type PromptVideoPart, type Provider, type ProviderCapabilities, type ReadStateEntry, type ReadStateMap, type RemoteStoreOptions, type RepeatGuardConfig, type RepeatGuardToolMatcher, type ResolveContentRef, type ResolveContentRefBlock, type RetryConfig, type RunHookMap, type SandboxProvider, type Session, type SessionContentBlock, type SessionData, type SessionEndStatus, type SessionHookContext, type SessionMessage, type SessionRun, type SessionStore, type SessionTurn, type SkillConfig, type SkillResource, type SkillsConfig, type SpawnConfig, type SpawnHookContext, type SpawnToolOptions, type SpawnToolState, type StagedToolState, type StreamCallbacks, type StreamHookContext, type StreamOptions, type SuccessfulMutationOutcome, type TaskEntry, type TaskExitInfo, type TaskHandle, type TaskStallInfo, type ThinkingLevel, type ToolCall, type ToolContext, type ToolDef, type ToolHookContext, type ToolMap, type ToolOutcome, type ToolResult, type ToolResultAudioContent, type ToolResultContent, type ToolResultDocumentContent, type ToolResultDocumentRefContent, type ToolResultImageContent, type ToolResultImageRefContent, type ToolResultMetadata, type ToolResultTextContent, type ToolResultVideoContent, type ToolSpec, type ToolStateController, type ToolStateOp, type ToolStateStore, type ToolStateView, type TurnFinishReason, type TurnResult, type TurnUsage, type ValidationResult, flattenTurns, matchesContextExceeded, statsByModel, toolOutputBudgetByteLength, toolOutputByteLength, toolResultToText };
@@ -312,6 +312,7 @@ A prefix cache breaks at the first differing byte — the wire must be append-on
312
312
  | `thinkingDecay` | Step form `{ afterTurn, floor }` | Every distinct budget is one cache loss; step emits one change per run, geometric emits one per halving, a per-turn function disables caching for the rest of the run. |
313
313
  | `cacheTtl` | `'1h'` for interactive hosts, `'5m'` for headless back-to-back runs | The 5-minute default TTL is the dominant cache cost for a human-paced session. |
314
314
  | `compactStrategy` / `mediaKeepTurns` | Keep windows equal, chunk-aligned (`COMPACT_CHUNK_TURNS`) | A window then costs one invalidation, not two. |
315
+ | `compactThresholdWindowFraction` | `0.25` for interactive hosts | Each tail-compaction frontier step rewrites an early message and re-bills the whole suffix (~50x the cost of keeping the bytes cached). Scaling the byte threshold with the model's window removes the steps on large-context models; ≤128k models keep the byte rule. Live A/B in `test-inference/cache-stability.inference.test.ts` § tail compaction vs context window. |
315
316
  | `thinking` level | Constant for the whole run | The thinking config is part of the cache key like the budget; toggling it mid-run forfeits the prefix. |
316
317
  | `system:transform` handlers | Byte-stable output only | Per-turn counters/timestamps belong in the transcript, not the system prompt. |
317
318
 
@@ -439,16 +440,25 @@ installLazyDisclosureGate // tool:gate refuses lazy ∉ u
439
440
 
440
441
  per turn — buildFormattedTools() emits in two phases:
441
442
  1. registry-order walk filtered by initialUnlocked ← byte-stable cached prefix
442
- 2. dynamicUnlockOrder, in unlock order ← append-only tail
443
+ 2. dynamicUnlockOrder, in unlock order ← append-only tail (omitted in dispatch mode)
443
444
 
444
445
  tool_search({ names | query | server, limit? }): unlocks matches, returns inlined input_schema JSON
445
446
 
447
+ dispatch mode (toolSearch.mode: 'dispatch'):
448
+ tools.call_tool = createCallToolTool() ← eager, fixed { name, arguments } envelope
449
+ model: call_tool({ name: 'mcp_srv_a', arguments })
450
+ loop (after wire→canonical rewrite): unwrapDispatchCalls()
451
+ → ToolCall { id, name: 'mcp_srv_a', input: arguments } for gate / validation / hooks / events
452
+ persisted assistant turn keeps the call_tool block ← wire-valid history
453
+ store.eventsFromTurns: unwrapCallTool() on replay ← reload matches the live transcript
454
+
446
455
  run end: uninstallLazyDisclosureGate; per-run state GC-eligible on next run()
447
456
  ```
448
457
 
449
458
  - **Aliasing.** `LazyToolEntry.name` is the wire name; `.canonicalName` keys `unlocked` and dispatches `ctx.tools[name]`. `session.turns` / `ToolHookContext.name` stay canonical throughout.
450
459
  - **Hard gate vs. provider validation.** Anthropic/OpenAI already refuse `tool_use` blocks for tools absent from the request; the gate exists for lenient/custom/mock providers and makes the lazy boundary observable via `tool:gate`.
451
- - **Cache-stable injection.** Every `tool_search` call invalidates only the wire tools array's SUFFIX starting at the first new schema — the eager + `tool_search` prefix never moves.
460
+ - **Cache cost of unlock mode.** The eager + `tool_search` prefix of the wire array never moves, but providers hash the request as `tools → system → messages`: a grown tail still re-bills the system prompt and every message on that turn. That is one full prompt-cache rewrite per discovery wave.
461
+ - **Dispatch mode.** `toolSearch.mode: 'dispatch'` keeps the tools array byte-stable: surfaced tools are invoked through `call_tool` (`src/tools/call-tool.ts`). The unwrap happens once, in `runTurn` right after the alias rewrite, so the whole downstream pipeline (lazy gate, MCP gates, `validateToolArgs` against the INNER schema, `tool:before/after`, tracing, run counts, both dispatch paths) is unchanged and sees the inner tool. Only lazy catalog tools are unwrapped: an envelope around an eager tool stays a `call_tool` call and its `execute` says to call the tool directly (history scanners keyed on `block.name`, read-state seeding, task tracking, keep seeing eager tools under their own names). A malformed envelope is left as `call_tool` so its own schema validation reports the shape error. A host-defined `call_tool` disables dispatch mode (the run falls back to unlock). The chat hosts expose this as `Settings.stableToolList`.
452
462
  - **Host-defined `tool_search`.** A host tool named `tool_search` shadows auto-injection; it must call `createToolSearchTool({ catalog, unlocked, addUnlock? })` to drive unlocks, or use `toolSearch.tool: false` for a fully custom flow.
453
463
 
454
464
  ## Hook Firing Order
@@ -661,7 +671,7 @@ Use `agent.hooks.hook(event, fn)` for dynamic post-creation registration (return
661
671
  **Defaults:** unlimited turns, model-driven output tokens, level-based thinking, `cache: true`, `dedupReads: true`, `requireReadBeforeEdit: false`, `maxConcurrentTools: 10`, `compactStrategy: 'off'`, `compactThreshold: 128 KiB`, `compactKeepTurns: 4`, `toolDisclosure: 'eager'`, `readLineNumbers: true`, `elideStaleReads: false`, `surfaceMcpInstructions: true`, `maxConsecutivePauseTurns: 0` (unlimited), `persistTurns: true`, `providerStreamStartTimeoutMs: 300000`, `providerStreamTimeoutMs: 1800000`, `mcpConnectTimeoutMs: 120000`. All other knobs below default to `undefined`/off unless noted. The chat-layer profiles (`BUILD_AGENT`/`PLAN_AGENT`) override several — see `docs/CHAT.md` § Agents.
662
672
 
663
673
  - **`toolOutputBudget`** (off). Sums every result's `outputBytes` after `tool:transform`. On overshoot, appends a synthetic user message (`[Tool output budget exceeded: N bytes … cap: M]`) and fires `budget:exceeded` — the model self-compacts next turn instead of being cut.
664
- - **`compactStrategy: 'tail' | CompactFunction'`** — client-side, wire-only trim for non-Anthropic providers. `'tail'` stubs `tool_result` blocks older than `compactKeepTurns` past a byte threshold; `session.turns` is untouched. Anthropic should prefer server-side `context-management-2025-06-27` (token-accurate). A custom function must preserve `tool_use ↔ tool_result` adjacency and the trailing user message.
674
+ - **`compactStrategy: 'tail' | CompactFunction'`** — client-side, wire-only trim for non-Anthropic providers. `'tail'` stubs `tool_result` blocks older than `compactKeepTurns` past a byte threshold (`compactThreshold`, raised to `compactThresholdWindowFraction × contextWindow × 4 B` when the run knows its window — `autoCompact.contextWindow`, the active `ModelTarget`, or `provider.meta.contextWindow`); `session.turns` is untouched. Anthropic should prefer server-side `context-management-2025-06-27` (token-accurate). A custom function must preserve `tool_use ↔ tool_result` adjacency and the trailing user message.
665
675
  - **`mediaKeepTurns`** — wire-only media age-out. Inline `image`/`document` blocks older than N messages become an `attachment://<hash>` marker on the wire; `session.turns` keeps the bytes, `read_file path="attachment://<hash>"` re-inlines. Chunk-snapped for cache stability; requires `read_file` registered to be actionable.
666
676
  - **`autoCompact`** (`boolean | AutoCompactConfig`, off). At each turn boundary, once effective input (`input + cacheRead + cacheCreation`) crosses `threshold` (default `0.85`) of the context window, summarizes older turns and continues. Window source: `autoCompact.contextWindow` → `provider.meta.contextWindow`, else inert. `minGrowthFraction` (default `0.1`) stops post-compact thrashing; `maxFailures` (default `2`) disables it after consecutive summary failures. Chat profiles enable at `{ threshold: 0.85 }`.
667
677
  - **`modelCatalog`** — override model metadata keyed `"<provider>:<modelId>"` or via `{ get, list }`. Resolution: run → agent `behavior.modelCatalog` → process-wide `setModelCatalog()` → bundled registry → local fallback tables, merged field-level. `list` returning `[]` empties a provider's picker; `get` returning `undefined` falls through. Unset by default.
package/docs/CHAT.md CHANGED
@@ -166,6 +166,10 @@ const SHARED_BEHAVIOR: AgentBehavior = {
166
166
  // context / token discipline
167
167
  compactStrategy: 'tail',
168
168
  compactThreshold: 128 * 1024,
169
+ compactThresholdWindowFraction: 0.25, // effective threshold = max(128 KiB,
170
+ // 0.25 × window × 4 B/tok): 1M-context
171
+ // runs never trip tail compaction on
172
+ // ordinary sessions; ≤128k stays on bytes
169
173
  compactKeepTurns: 8, // floor, not a cap — see below
170
174
  elideStaleReads: 'notice', // append-only staleness note; never rewrites
171
175
  // cached prefix bytes ('stub' re-bills the
@@ -247,9 +251,14 @@ Both join `[...doctrine, userInstructions]` as the **static** half and `[envSect
247
251
  | `showAllProjects` | `boolean` | `false` | Sessions list scope. |
248
252
  | `resumeLastSession` | `boolean` | `true` | |
249
253
  | `persistToolResults` | `boolean` | `true` | See **Tool-result persistence**. |
254
+ | `stableToolList` | `boolean` | `false` | Lazy MCP tools surfaced by `tool_search` are invoked through `call_tool` instead of growing the model's tool list (`behavior.toolSearch.mode: 'dispatch'`), so discovery never rewrites the prompt cache. Transcript shows the inner tool as with a direct call. Anthropic always uses dispatch (replayed thinking is bound to the tool list — see Preserved thinking in `SKILL.md`); the toggle only affects other providers. |
255
+ | `sandbox` | `boolean` | `false` | Run the model's `shell` commands under the OS sandbox (macOS Seatbelt / Linux bwrap). Writes limited to cwd, temp dirs, a worktree's git dir and `sandboxAllowWrite`; credential dirs unreadable; `write_file`/`edit` follow the same roots. Not covered: `!` commands, MCP servers, `fetch_url`, reads. Fails closed when the backend is missing (a `sandbox` notice explains on activation). Resolved by `chat/sandbox-settings.ts` → `SpawnConfig.isolation`; see `docs/EXECUTION_CONTEXT.md`. Next session activation. |
256
+ | `sandboxNetwork` | `'allow'\|'deny'` | `'allow'` | Network for sandboxed commands. `deny` removes all sockets — the only way to stop exfiltration of readable files, at the cost of `bun install` / `git fetch`. |
257
+ | `sandboxAllowWrite` | `string` | `''` | Extra writable paths, comma/newline separated, `~` expanded, relative to cwd. |
250
258
  | `autoCompact` | `boolean` | `true` | See **Auto-compaction**. |
251
259
  | `autoCompactThreshold` | `number` | `0.8` | Cycles 0.6/0.7/0.8/0.9 in the settings modal. |
252
260
  | `resumeOnMaxTokens`, `maxOutputTokens`, `maxTurns`, `retryMaxAttempts`, `maxCostUsd`, `maxTotalTokens`, `maxWallMinutes` | mixed | off/`0` | Runaway ceilings; all off by default (model-driven, uncapped). |
261
+ | `thinkingBinding` | `'drop_block' \| 'error' \| 'off'` | `'drop_block'` | Anthropic only. What the API does with replayed thinking whose prefix changed: drop server-side and continue, fail the run with the API's 400 (no retry, history untouched — debugging), or account default. Published to `ZIDANE_THINKING_BINDING_SETTING` by the TUI on change and by the chat host per send; the provider reads it per request. Precedence: `anthropic({ thinkingBinding })` > `ZIDANE_THINKING_BINDING` set in the launching shell > this setting > `'drop_block'`. |
253
262
  | `showEditDiffs` | `boolean` | `true` | See **Edit-diff rendering**. |
254
263
  | `editDiffDisplay` | `'full'\|'compact'` | `'full'` | Approval modal always shows the full diff regardless. |
255
264
  | `showCompactionSummary` | `boolean` | `false` | Render the LLM summary text under the compaction card, not just metadata. |
@@ -275,9 +275,70 @@ bun run headless "fix the lint errors" --cwd /tmp/project
275
275
  Applicable flags: `--cwd` (defaults to the current directory), `--env`, and
276
276
  `--pass-env`. The `--image`, `--pregame`, and `--sandbox` flags don't apply.
277
277
 
278
- Because there's no isolation, the agent's commands can read and modify anything
279
- your own shell can. Prefer `docker` or `e2b` when you don't fully trust what the
280
- agent might run.
278
+ Because there's no isolation by default, the agent's commands can read and
279
+ modify anything your own shell can. Prefer `docker` or `e2b` when you don't
280
+ fully trust what the agent might run, or turn on the OS sandbox below for a
281
+ lightweight middle ground.
282
+
283
+ ### OS sandbox (`isolation`)
284
+
285
+ `createProcessContext({ cwd, isolation })` wraps every shell command in a
286
+ kernel-enforced sandbox — the same primitive Claude Code and Codex use:
287
+
288
+ | Platform | Backend | Mechanism |
289
+ |---|---|---|
290
+ | macOS | `/usr/bin/sandbox-exec` (Seatbelt) | deny-default SBPL profile derived from Codex's base policy; paths are passed as `-D` params, never inlined |
291
+ | Linux | `bwrap` (bubblewrap) | `--ro-bind / /`, per-root `--bind`, `--tmpfs` over credential dirs, `--unshare-net` when denied |
292
+ | Windows | — | unsupported; requesting isolation fails closed |
293
+
294
+ ```ts
295
+ import { createProcessContext, resolveWritableRoots } from 'zidane'
296
+
297
+ const execution = createProcessContext({
298
+ cwd,
299
+ isolation: {
300
+ writableRoots: resolveWritableRoots(cwd, { extra: ['~/.cache'] }),
301
+ network: 'allow', // or 'deny'
302
+ },
303
+ })
304
+ ```
305
+
306
+ **Policy.** `writableRoots` is where commands may create / modify / delete;
307
+ `resolveWritableRoots` (sync) / `resolveWritableRootsAsync` build the default set: `cwd`, `$TMPDIR`, `/tmp`,
308
+ `/var/tmp` (+ `/private` aliases on macOS), git's common dir when `cwd` is a
309
+ linked worktree (otherwise `git commit` fails there), plus extras — all
310
+ canonicalized and deduped. Reads are allowed everywhere except
311
+ `unreadableDirs` (default: `~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.kube`,
312
+ `~/.azure`, `~/.config/gcloud`, `~/.docker/config.json`, `~/.netrc`,
313
+ zidane's `credentials.json` and `mcp-credentials.json`; the chat hosts
314
+ resolve the latter two from the actual data dir). `network` is
315
+ all-or-nothing; per-domain allowlists need a proxy and are not implemented.
316
+
317
+ **Scope.** Kernel enforcement covers `exec`, `execStream` and
318
+ `execBackground` — i.e. the model's `shell` tool and anything else routed
319
+ through the context (stdio MCP servers bound to it, subagents). `writeFile`
320
+ applies the same `writableRoots` in-process, so `write_file` / `edit` /
321
+ `multi_edit` cannot write where a shell command couldn't. `readFile`, `glob`
322
+ and `listFiles` are unrestricted (persisted tool outputs and skills under
323
+ `~/.zidane` / `~/.agents` must stay reachable). Not covered: MCP servers
324
+ spawned by the host, `fetch_url`, the `!` shell in the TUI, `$EDITOR`.
325
+
326
+ **Fail closed.** If the backend is missing (`bwrap` not installed,
327
+ non-darwin/linux), every shell call returns exit `127` with the reason and
328
+ `execStream` / `execBackground` throw; nothing runs unsandboxed. Probed once
329
+ at context creation; `capabilities.isolated` is `true` only when isolation is
330
+ actually enforced (requested AND backend available), `false` when it was
331
+ requested but the context is in this fail-closed state.
332
+
333
+ **Denials.** A failed command whose stderr matches `Operation not permitted`
334
+ / `Read-only file system` gets one appended `[sandbox] …` line naming the
335
+ writable roots and the settings that widen them, so the model asks instead
336
+ of guessing. Process-group kill semantics are unchanged: `sandbox-exec`
337
+ execs the shell in place and `bwrap` runs without `--new-session`.
338
+
339
+ In the chat hosts this is `Settings.sandbox` / `sandboxNetwork` /
340
+ `sandboxAllowWrite` (see `docs/CHAT.md`). Docker / e2b / daytona contexts
341
+ ignore it — they are already isolated.
281
342
 
282
343
  The process context also implements the optional `fileSize` capability — a
283
344
  containment-aware size probe that lets whole-file tools (`read_file`, `edit`,
@@ -87,7 +87,10 @@ user's own selection rather than resetting it.
87
87
  Response shape:
88
88
 
89
89
  ```json
90
- { "answers": { "name": "Jane", "notes": "...", "fw": "react", "go": true } }
90
+ {
91
+ "answers": { "name": "Jane", "notes": "...", "fw": "react", "go": true },
92
+ "instructions": "Also keep the old endpoint alive until the migration lands."
93
+ }
91
94
  ```
92
95
 
93
96
  Missing keys = question was skipped (only possible when `required:
@@ -95,12 +98,21 @@ false`). Empty-string answers and empty multi-select picks are dropped
95
98
  at serialization; a multi-select (`type: 'select'`, `multi: true`)
96
99
  answers as an array of choice ids.
97
100
 
101
+ `instructions` is free text the user attached to the whole form. Every
102
+ surface offers it unconditionally (the TUI wizard ends on an "additional
103
+ instructions" step, the GUI modal has a permanent textarea under the
104
+ questions), so the user can always refine or override the questions
105
+ instead of being boxed into the model's choices. Omitted when left empty.
106
+ The tool description tells the model it is authoritative.
107
+
98
108
  ## Keyboard
99
109
 
100
110
  In the question wizard (multi-question batches):
101
111
 
102
112
  - `↑/↓` (or `j/k`) — navigate options
103
- - `↵` — submit the active question / pick the focused option
113
+ - `↵` — submit the active question / pick the focused option. The last
114
+ step is always "additional instructions" (optional textarea): `↵` on an
115
+ empty box sends the answers as they are.
104
116
  - `space` — toggle the focused option (multi-selects only; `↵` submits
105
117
  the whole selection, at least one pick required unless `required: false`)
106
118
  - `shift+↹` — back one question (preserves your answers, pre-fills the
package/docs/SKILL.md CHANGED
@@ -49,6 +49,7 @@ const agent = createAgent({
49
49
  toolBudgets: { run_query: { max: 20, onExceed: 'steer' } },
50
50
  compactStrategy: 'off', // 'off' | 'tail' (non-Anthropic compaction)
51
51
  compactThreshold: 131_072, // 128 KiB
52
+ compactThresholdWindowFraction: 0.25, // raise it to 25% of the model's window (large-context runs stop re-billing the prefix)
52
53
  compactKeepTurns: 4,
53
54
  toolDisclosure: 'eager', // 'eager' | 'lazy'
54
55
  toolSearch: { limit: 20 },
@@ -182,6 +183,32 @@ Flatten via `toolResultToText(result)`. MCP image blocks are auto-normalized via
182
183
 
183
184
  Cache hits surface on `TurnUsage.cacheRead` / `cacheCreation`. Anthropic + OpenRouter populate them per turn. Live break/drift telemetry is a hook concern — see `cache:break` / `cache:drift` in the [Hook reference](#hook-reference).
184
185
 
186
+ ### Preserved thinking (Anthropic)
187
+
188
+ Claude Fable 5.1 and later bind every replayed `thinking` block to the exact prefix it was produced under — `system`, `tools`, and every earlier message. If any of those bytes changed since, that block and every later one are invalid. By default the API rejects the whole request with a 400; on accounts created after 2026-08-31 this is enforced out of the box, on older accounts only when the field below is set. (`platform.claude.com/docs/en/build-with-claude/preserved-thinking`)
189
+
190
+ Zidane opts into the safe behavior on every thinking-enabled Anthropic request: `thinking.block_binding.prefix_mismatch_behavior: "drop_block"` plus the `thinking-binding-controls-2026-08-01` beta. Invalid blocks are dropped server-side (unbilled), the request succeeds, and history is never mutated. Thinking-off requests and Bedrock are untouched.
191
+
192
+ ```ts
193
+ anthropic({ apiKey: 'sk-ant-...', thinkingBinding: 'drop_block' }) // default
194
+ anthropic({ apiKey: 'sk-ant-...', thinkingBinding: 'error' }) // fail the run on a prefix edit — debugging / CI
195
+ anthropic({ apiKey: 'sk-ant-...', thinkingBinding: 'off' }) // send nothing; pre-feature wire bytes, account default applies
196
+ ```
197
+
198
+ Precedence: `thinkingBinding` param > shell `ZIDANE_THINKING_BINDING` > chat setting `thinkingBinding` (published on `ZIDANE_THINKING_BINDING_SETTING` before each run by the TUI / daemon) > `'drop_block'`. Read per request, so a flip lands on the next call without rebuilding the provider. The setting is labelled "Preserved thinking (Anthropic only)" and is inert on every other provider.
199
+
200
+ Effect per mode:
201
+
202
+ | Mode | On a prefix edit | History | Visible as |
203
+ |---|---|---|---|
204
+ | `drop_block` | Stale blocks dropped server-side; model answers without that reasoning; cache restarts at the edit | untouched | `provider:input-transformations` (`prefixMismatches` / `modelMismatches`) |
205
+ | `error` | 400; the run rejects, no retry | untouched | run error `Invalid \`signature\` in \`thinking\` block … bound to a different conversation` |
206
+ | `off` | Account default: old accounts tolerate, new accounts 400 → the signature-scrub ladder strips **all** history thinking and retries | scrubbed on 400 | `history:repair` |
207
+
208
+ What zidane already keeps prefix-safe: append-only turns, nudges and stale-read notices, tail truncation, compaction (`applyCompactSummaryCutoff` strips thinking from the kept tail), the `__output__` structured-output call, lazy MCP disclosure (defaults to `toolSearch.mode: 'dispatch'` on Anthropic so `tools` stays byte-stable), the TUI `<env> Date:` (pinned per session). What still costs reasoning once per edit: rewriting `system` between turns (cwd change, interaction-mode flip, rules-file edit, skill activation), a run-level `tools` change, `elideStaleReads: 'stub'`, `compactStrategy: 'tail'`, `mediaKeepTurns`. A model downgrade mid-conversation drops the newer model's blocks with `model_binding_mismatch`; that is not an edit and needs no action. Every `cache:break` attributed to `system`, `tools` or `messagePrefix` is also a preserved-thinking drop, so wire the two hooks to the same telemetry.
209
+
210
+ Live coverage: `test-inference/preserved-thinking.inference.test.ts`.
211
+
185
212
  ### Provider error classification
186
213
 
187
214
  Provider failures are wrapped via `toTypedError(err)` before leaving `agent.run()` — each provider implements `Provider.classifyError(err): ClassifiedError | null`; unrecognized errors fall back to `AgentProviderError`. Aborts always produce `AgentAbortedError`. Match on `instanceof`, not on `err.message`.
@@ -339,6 +366,7 @@ Every event in `AgentHooks` (`src/agent.ts`), one row each. `child:*` bubble eve
339
366
  | `compact:progress` | Throttled, while the compaction summary streams | — | `+ estOutputTokens` |
340
367
  | `compact:end` | Exactly once per `compact:start`, on success or failure | — | `+ status: 'success' \| 'error'` (success: replaced/dropped/restored counts + `effectiveTokens`; error: `error`) |
341
368
  | `history:repair` | After a provider stream settles when the loop scrubbed provider-rejected reasoning state (e.g. Anthropic signature-400 poisoning) | — | `reason: 'invalid-anthropic-thinking-signature', runId, sessionId?, turnIds, removedBlocks` |
369
+ | `provider:input-transformations` | Provider dropped replayed thinking blocks server-side before sampling (Anthropic preserved-thinking binding, `input_transformations`). History untouched; `prefix_binding_mismatch` entries mean this host edited system/tools/an earlier message | — | `runId, turnId, sessionId?, transformations[{type,path,reason}], prefixMismatches, modelMismatches` |
342
370
  | `oauth:refresh` | OAuth token refreshed | — | `provider, providerId, source: 'params'\|'file', previousCredentials, credentials` |
343
371
  | `content-ref:resolve` | Wire assembly sees a block carrying `ref`; resolves persisted content refs into provider-bound bytes | **yes** (`data`) | `block, data?, childId?, depth?` |
344
372
  | `tool:gate` | Before each tool call | **yes** (`block, reason, result?`) | `turnId, callId, name, displayName, input, runToolCounts` — `block` refuses with `tool_result.content = "Blocked: <reason>"` + `is_error: true`; `result` substitutes and skips execute; `block` wins over `result` |
@@ -688,7 +716,9 @@ createAgent({
688
716
 
689
717
  The system prompt gains a `<searchable_tools>` block. `tool_search({ query, names, server, limit })` returns matching schemas and unlocks them for the rest of the run. A `tool:gate` middleware refuses un-surfaced lazy tools. Catalog + search results show the **wire** name; the unlock set keys on canonical so dispatch / `session.turns` stay alias-stable.
690
718
 
691
- `behavior.toolSearch` knobs (no-op in eager mode): `tool: false` opts out of auto-injection; `limit: 20` caps default results. Per-server `disclosure: 'eager' | 'lazy'` overrides the agent default; run-level overrides agent-level. Cost: one cache miss per discovery wave; subsequent turns hit cache normally.
719
+ `behavior.toolSearch` knobs (no-op in eager mode): `tool: false` opts out of auto-injection; `limit: 20` caps default results; `mode: 'unlock' | 'dispatch'` picks how a surfaced tool becomes callable. Per-server `disclosure: 'eager' | 'lazy'` overrides the agent default; run-level overrides agent-level.
720
+
721
+ Cost: in `'unlock'` mode (default) every discovery wave grows the tools array, and since providers hash `tools → system → messages` that is a **full** prompt-cache rewrite each time. `'dispatch'` mode keeps the tools array byte-stable: a fixed `call_tool { name, arguments }` native tool is added, surfaced tools are invoked through it, and the loop unwraps the envelope before dispatch so gating, schema validation (against the inner tool), hooks and transcript events see the inner tool exactly as a direct call. Chat hosts expose it as `Settings.stableToolList`.
692
722
 
693
723
  ## Skills
694
724
 
package/docs/TUI.md CHANGED
@@ -108,7 +108,7 @@ Not re-exported: the renderer-agnostic engine (settings, theme, safe-mode, store
108
108
  Layered slots, in priority order:
109
109
 
110
110
  1. **Transcript-slot takeover** — a file-edit approval (`edit`/`multi_edit`/`write_file`) renders `FileEditApprovalModal` inline in the transcript slot (`<Transcript visible={!fileEditPending}>` so scroll position and memoized turn anchors survive open→close). See [File-edit approval modal](#file-edit-approval-modal).
111
- 2. **Bottom slot**, one of: non-file-edit `pendingApproval` → `ApprovalBlock` (accept once / for session / + remember / deny, esc aborts the run) · `pendingInteraction` → `InteractionBlock` (plan picker or Q&A wizard; esc aborts if live, exits the form if resumed) · else `PromptBlock` (textarea + history), kept mounted (`Display.None` when hidden) so draft/cursor/attachments survive an approval round-trip.
111
+ 2. **Bottom slot**, one of: non-file-edit `pendingApproval` → `ApprovalBlock` (accept once / for session / + remember / deny, `ctrl+esc` aborts the run) · `pendingInteraction` → `InteractionBlock` (plan picker or Q&A wizard; `ctrl+esc` aborts if live, esc leaves for the sessions list) · else `PromptBlock` (textarea + history), kept mounted (`Display.None` when hidden) so draft/cursor/attachments survive an approval round-trip.
112
112
 
113
113
  When the queue has entries, a `QueuedMessagesBlock` renders between transcript and prompt:
114
114
 
@@ -140,7 +140,7 @@ Every action routes through `<userDir>/keybindings.json`, matched via `matchesBi
140
140
  | `ctrl+shift+m` | `openMainSession` | any, when `Settings.orchestration` is on | jump to (or lazily create) the pinned Main orchestration session for the cwd; requires the daemon runtime. Silent no-op when the setting/runtime is off |
141
141
  | `ctrl+p` | `openUsagePanel` | chat | subscription usage panel; also opens by clicking the footer's `▰ 5h 43%` segment |
142
142
  | `ctrl+y` | `openKeybindings` | non-auth | keybindings panel, `↵` opens `keybindings.json` |
143
- | `ctrl+k` | `cancelToolCall` | chat (busy, ≥1 in-flight call) | cancel-tool picker (`agent.cancelTool(callId)`); distinct from `esc` (whole-run abort) |
143
+ | `ctrl+k` | `cancelToolCall` | chat (busy, ≥1 in-flight call) | cancel-tool picker (`agent.cancelTool(callId)`); distinct from `ctrl+esc` (whole-run abort) |
144
144
  | `ctrl+h` | `changeCwd` | non-auth | open the directory picker to change cwd |
145
145
  | `ctrl+v` | `attachClipboardImage` | textarea | paste text or an image from the OS clipboard, read by the process itself (bypasses a frozen terminal paste path) |
146
146
  | `ctrl+g` | `openExternalEditor` | textarea | edit the prompt draft in `$EDITOR`/`$VISUAL`; gated on `externalEditor` setting |
@@ -151,7 +151,8 @@ Every action routes through `<userDir>/keybindings.json`, matched via `matchesBi
151
151
  | — (unbound) | `enterQueueSelection` | textarea | move focus into the queue box |
152
152
  | `enter` / `shift+enter` | — | textarea | submit / newline |
153
153
  | `↑`/`↓` at buffer edges | — | textarea (popup closed) | cycle prompt history |
154
- | `esc` | — | popup / run in flight / modal open | dismiss popup, or abort run + drain approval+interaction queues, or close modal |
154
+ | `ctrl+esc` | `abortRun` | chat (busy or pending approval) | abort run + drain approval+interaction queues. Rebind (e.g. `ctrl+x`) if the terminal cannot report ctrl on esc |
155
+ | `esc` | — | popup / chat / modal open | dismiss popup, or leave the chat for the sessions list (busy or not — the run continues), or close modal |
155
156
  | `pageup`/`pagedown`, `shift+pageup`/`shift+pagedown` | — | chat, no modal | scroll transcript one page / jump to top-bottom |
156
157
  | `ctrl+a` | (textarea built-in) | textarea | select-all |
157
158
 
@@ -176,7 +177,7 @@ Every action routes through `<userDir>/keybindings.json`, matched via `matchesBi
176
177
 
177
178
  `<userDir>/keybindings.json` is JSONC. `ensureKeybindingsFile` never overwrites an existing file. Invalid/unknown entries silently fall back to defaults (`ZIDANE_DEBUG` logs why). Settings → General → "Keybindings" opens the file in `$EDITOR`/`$VISUAL`; changes apply on restart. Full alias/parsing rules: [CHAT.md § Keybindings](./CHAT.md#keybindings).
178
179
 
179
- **Esc semantics.** While busy, `esc` runs `denyAll → interactions.cancelAll → abort → stream.reset → agent.destroy` (shared with session-switching/re-auth teardown). `applyGate` does **not** cascade a single deny through the rest of the queue: denying one approval blocks only that call (`Blocked: …`), other pending approvals stay queued. The global `esc abort` is the only path that denies everything + aborts the run. Esc is consumed by the completion popup first when one is open.
180
+ **Abort semantics.** `abortRun` (`ctrl+esc`) runs `denyAll → interactions.cancelAll → abort → stream.reset → agent.destroy` (shared with session-switching/re-auth teardown). `applyGate` does **not** cascade a single deny through the rest of the queue: denying one approval blocks only that call (`Blocked: …`), other pending approvals stay queued. The global `abortRun` is the only path that denies everything + aborts the run. Plain `esc` never aborts: under the daemon a window can reattach to a session mid-run, and esc must always be able to return to the sessions list. Local runtime only: picking another session (or new) while a run is in flight is refused with a notice under the list — the window's single agent would otherwise be torn down by `activateSession`; abort first, or run under the daemon where runs outlive the window's session choice. Esc is consumed by the completion popup first when one is open.
180
181
 
181
182
  ## Modals
182
183
 
@@ -199,7 +200,7 @@ Inline-mounted in `ChatScreen`'s transcript slot (not stacked); keyed on `reques
199
200
 
200
201
  ### Tool-call cancellation
201
202
 
202
- `CancelToolModal` lists the in-flight tool registry (`App.tsx` tracks it via `tool:before`/`tool:after`/`tool:error`/`tool:cancelled`, mirrored for `child:*`), opened by `ctrl+k` when `busy && !pendingApproval && inFlightTools.length > 0`. Row: `›` cursor, tool name, truncated `callId`, optional `· child-N`, elapsed time. `↑`/`↓` navigate, `↵` cancels the focused row, `a` cancels every call, `esc` dismisses. Auto-dismisses 500ms after the in-flight list empties. Cancellation resolves that call with `TOOL_USE_CANCELLED_MESSAGE` (`isError: true`); other in-flight tools keep running — distinct from the run-wide `esc abort`.
203
+ `CancelToolModal` lists the in-flight tool registry (`App.tsx` tracks it via `tool:before`/`tool:after`/`tool:error`/`tool:cancelled`, mirrored for `child:*`), opened by `ctrl+k` when `busy && !pendingApproval && inFlightTools.length > 0`. Row: `›` cursor, tool name, truncated `callId`, optional `· child-N`, elapsed time. `↑`/`↓` navigate, `↵` cancels the focused row, `a` cancels every call, `esc` dismisses. Auto-dismisses 500ms after the in-flight list empties. Cancellation resolves that call with `TOOL_USE_CANCELLED_MESSAGE` (`isError: true`); other in-flight tools keep running — distinct from the run-wide `ctrl+esc` abort.
203
204
 
204
205
  ### TodosModal
205
206
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zidane",
3
- "version": "7.0.31",
3
+ "version": "7.1.1",
4
4
  "description": "an agent that goes straight to the goal",
5
5
  "type": "module",
6
6
  "private": false,