zidane 6.5.1 → 6.5.2

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 (87) hide show
  1. package/dist/{acp-o-LQAjFw.js → acp-565X8nVP.js} +4 -4
  2. package/dist/{acp-o-LQAjFw.js.map → acp-565X8nVP.js.map} +1 -1
  3. package/dist/acp-cli.js +2 -2
  4. package/dist/acp.d.ts +1 -1
  5. package/dist/acp.d.ts.map +1 -1
  6. package/dist/acp.js +1 -1
  7. package/dist/{agent-BVNkC1h4.js → agent-CvO7UOxv.js} +755 -586
  8. package/dist/agent-CvO7UOxv.js.map +1 -0
  9. package/dist/agent.d.ts +1 -1
  10. package/dist/agent.js +1 -1
  11. package/dist/chat/pure.d.ts +4 -4
  12. package/dist/chat/pure.js +3 -3
  13. package/dist/chat.d.ts +114 -5
  14. package/dist/chat.d.ts.map +1 -1
  15. package/dist/chat.js +5 -5
  16. package/dist/{context-breakdown-BLF10tOO.js → context-segments-1mcqSCM3.js} +110 -2
  17. package/dist/context-segments-1mcqSCM3.js.map +1 -0
  18. package/dist/{context-transport-D-daWOmp.js → context-transport-ClvadrlH.js} +3 -3
  19. package/dist/{context-transport-D-daWOmp.js.map → context-transport-ClvadrlH.js.map} +1 -1
  20. package/dist/contexts/daytona.d.ts +1 -1
  21. package/dist/contexts/e2b.d.ts +1 -1
  22. package/dist/eval.d.ts +1 -1
  23. package/dist/extensions.d.ts +1 -1
  24. package/dist/extensions.js +1 -1
  25. package/dist/headless.d.ts +1 -1
  26. package/dist/headless.js +1 -1
  27. package/dist/{index-CWBSrqI6.d.ts → index-DfERTrql.d.ts} +178 -6
  28. package/dist/index-DfERTrql.d.ts.map +1 -0
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.js +3 -3
  31. package/dist/{login-CR3fDHXh.js → login-Cl6ML2XN.js} +2 -2
  32. package/dist/{login-CR3fDHXh.js.map → login-Cl6ML2XN.js.map} +1 -1
  33. package/dist/{mcp-C6IkXNOS.js → mcp-DdntU47g.js} +3 -3
  34. package/dist/{mcp-C6IkXNOS.js.map → mcp-DdntU47g.js.map} +1 -1
  35. package/dist/mcp.d.ts +1 -1
  36. package/dist/mcp.js +1 -1
  37. package/dist/output/stream-json.d.ts +1 -1
  38. package/dist/output/terminal.d.ts +1 -1
  39. package/dist/plugins.d.ts +1 -1
  40. package/dist/presets.d.ts +1 -1
  41. package/dist/presets.js +1 -1
  42. package/dist/providers/anthropic.d.ts +1 -1
  43. package/dist/providers/arcee.d.ts +1 -1
  44. package/dist/providers/baseten.d.ts +1 -1
  45. package/dist/providers/cerebras.d.ts +1 -1
  46. package/dist/providers/local.d.ts +1 -1
  47. package/dist/providers/openai-compat.d.ts +1 -1
  48. package/dist/providers/openai.d.ts +1 -1
  49. package/dist/providers/openrouter.d.ts +1 -1
  50. package/dist/providers/xai.d.ts +1 -1
  51. package/dist/providers.d.ts +2 -2
  52. package/dist/restate.d.ts +1 -1
  53. package/dist/restate.js +1 -1
  54. package/dist/session/sqlite.d.ts +1 -1
  55. package/dist/session.d.ts +1 -1
  56. package/dist/skills.d.ts +1 -1
  57. package/dist/{stdio-DzUUkkhS.js → stdio-BQeCMmuK.js} +2 -2
  58. package/dist/{stdio-DzUUkkhS.js.map → stdio-BQeCMmuK.js.map} +1 -1
  59. package/dist/{stdio-loader-BILyUGmi.js → stdio-loader-BN1l1ZAG.js} +2 -2
  60. package/dist/{stdio-loader-BILyUGmi.js.map → stdio-loader-BN1l1ZAG.js.map} +1 -1
  61. package/dist/{tool-formatters-gFqQbzPY.d.ts → tool-formatters-B2gBbKaS.d.ts} +2 -2
  62. package/dist/{tool-formatters-gFqQbzPY.d.ts.map → tool-formatters-B2gBbKaS.d.ts.map} +1 -1
  63. package/dist/tools/fetch-url.d.ts +1 -1
  64. package/dist/tools/web-search.d.ts +1 -1
  65. package/dist/tools.d.ts +1 -1
  66. package/dist/tools.js +1 -1
  67. package/dist/{transcript-anchors-qCs3wZpu.d.ts → transcript-anchors-CeIxhKP7.d.ts} +2 -2
  68. package/dist/{transcript-anchors-qCs3wZpu.d.ts.map → transcript-anchors-CeIxhKP7.d.ts.map} +1 -1
  69. package/dist/{transcript-anchors-DEIr0tJC.js → transcript-anchors-CmFKUw8M.js} +138 -18
  70. package/dist/transcript-anchors-CmFKUw8M.js.map +1 -0
  71. package/dist/tui.d.ts +74 -3
  72. package/dist/tui.d.ts.map +1 -1
  73. package/dist/tui.js +816 -80
  74. package/dist/tui.js.map +1 -1
  75. package/dist/{turn-operations-M9-jUyDd.d.ts → turn-operations-Dyv3Z-Ff.d.ts} +3 -3
  76. package/dist/{turn-operations-M9-jUyDd.d.ts.map → turn-operations-Dyv3Z-Ff.d.ts.map} +1 -1
  77. package/dist/{turn-operations-C3IijXMW.js → turn-operations-iGu5J08j.js} +2 -2
  78. package/dist/{turn-operations-C3IijXMW.js.map → turn-operations-iGu5J08j.js.map} +1 -1
  79. package/dist/types.d.ts +1 -1
  80. package/docs/CHAT.md +42 -1
  81. package/docs/INTERACTIONS.md +39 -0
  82. package/docs/TUI.md +28 -2
  83. package/package.json +1 -1
  84. package/dist/agent-BVNkC1h4.js.map +0 -1
  85. package/dist/context-breakdown-BLF10tOO.js.map +0 -1
  86. package/dist/index-CWBSrqI6.d.ts.map +0 -1
  87. package/dist/transcript-anchors-DEIr0tJC.js.map +0 -1
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  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";
2
- import { $h as SkillsConfig, $m as SessionContentBlock, Bf as TurnResult, Bh as StagedToolState, Bm as ProgressNudgeMessage, C as SuccessfulMutationOutcome, Ch as TurnUsage, Cm as ChildRunStats, Dh as ToolContext, Eh as toolResultToText, Ff as StreamOptions, Gi as AgentHookMap, Gm as PromptTextPart, Hh as ToolStateOp, Hm as PromptDocumentPart, Hp as AnthropicParams, Jm as RepeatGuardToolMatcher, Ki as AgentHooks, Km as PromptVideoPart, Lf as ToolCall, Mf as Provider, Mh as ReadStateMap, Nf as ProviderCapabilities, Nm as McpServerConfig, Np as CerebrasParams, Ns as SpawnToolOptions, Oh as ToolDef, Om as DEFAULT_AGENT_CLOCK, Os as ChildAgent, Pf as StreamCallbacks, Pm as McpToolHookContext, Ps as SpawnToolState, Qm as RunHookMap, Rf as ToolResult, Rm as OAuthRefreshHookContext, Rr as InteractionToolOptions, S as MutationValidationResult, Sh as TurnFinishReason, Th as toolOutputByteLength, Uh as ToolStateStore, Um as PromptImagePart, Vh as ToolStateController, Vm as PromptAudioPart, Wh as ToolStateView, Wi as Agent, Wm as PromptPart, Xm as ResolveContentRefBlock, Yg as McpConnection, Yh as SkillConfig, Ym as ResolveContentRef, Zf as OpenRouterParams, Zh as SkillResource, Zm as RetryConfig, _ as statsByModel, _h as ToolResultImageContent, _m as AgentClock, ag as SessionRun, b as MutationValidationHooksOptions, bh as ToolResultTextContent, dh as ToolHookContext, eg as CreateSessionOptions, eh as SessionEndStatus, fh as ToolOutcome, gg as RemoteStoreOptions, gh as ToolResultDocumentRefContent, gm as AgentBehavior, h as flattenTurns, hh as ToolResultDocumentContent, ih as SpawnHookContext, jh as ReadStateEntry, kh as ToolMap, lh as ThinkingLevel, mh as ToolResultContent, ng as Session, nh as SessionMessage, og as SessionStore, p as ModelUsage, ph as ToolResultAudioContent, qi as AgentOptions, qm as RepeatGuardConfig, rg as SessionData, rh as SessionTurn, sh as StreamHookContext, t as Preset, th as SessionHookContext, v as MutationValidationBatch, vh as ToolResultImageRefContent, vm as AgentRunOptions, vr as ValidationResult, wh as toolOutputBudgetByteLength, x as MutationValidationMutation, xh as ToolResultVideoContent, xp as OpenAIParams, y as MutationValidationFailure, yh as ToolResultMetadata, ym as AgentStats, zf as ToolSpec, zm as ProgressNudgeKind } from "./index-CWBSrqI6.js";
2
+ import { $m as RepeatGuardConfig, Ah as toolOutputBudgetByteLength, Bf as StreamCallbacks, Bm as McpToolHookContext, C as SuccessfulMutationOutcome, Ch as ToolResultImageContent, Cm as AgentClock, Dh as ToolResultVideoContent, Dp as OpenAIParams, Eh as ToolResultTextContent, Fh as ToolMap, Gf as ToolSpec, Gi as AgentHookMap, Gm as ProgressNudgeKind, Jh as ToolStateOp, Jm as PromptDocumentPart, Jp as AnthropicParams, Kf as TurnResult, Kh as StagedToolState, Ki as AgentHooks, Km as ProgressNudgeMessage, Lf as Provider, Lh as ReadStateEntry, Mh as toolResultToText, Nh as ToolContext, Ns as SpawnToolOptions, Oh as TurnFinishReason, Os as ChildAgent, Ph as ToolDef, Pm as DEFAULT_AGENT_CLOCK, Ps as SpawnToolState, Qm as PromptVideoPart, Rf as ProviderCapabilities, Rh as ReadStateMap, Rr as InteractionToolOptions, S as MutationValidationResult, Sg as RemoteStoreOptions, Sh as ToolResultDocumentRefContent, Sm as AgentBehavior, Th as ToolResultMetadata, Tm as AgentStats, Uf as ToolCall, Vf as StreamOptions, Wf as ToolResult, Wi as Agent, Wm as OAuthRefreshHookContext, Xh as ToolStateView, Xm as PromptPart, Yh as ToolStateStore, Ym as PromptImagePart, Zm as PromptTextPart, _ as statsByModel, _h as ToolHookContext, ag as SkillsConfig, ah as SessionContentBlock, b as MutationValidationHooksOptions, bh as ToolResultContent, cg as Session, ch as SessionMessage, dg as SessionRun, eh as RepeatGuardToolMatcher, fg as SessionStore, h as flattenTurns, hh as ThinkingLevel, ih as RunHookMap, jh as toolOutputByteLength, kh as TurnUsage, km as ChildRunStats, lg as SessionData, lh as SessionTurn, nh as ResolveContentRefBlock, og as CreateSessionOptions, oh as SessionEndStatus, p as ModelUsage, ph as StreamHookContext, qh as ToolStateController, qi as AgentOptions, qm as PromptAudioPart, rg as SkillResource, rh as RetryConfig, rp as OpenRouterParams, sh as SessionHookContext, t as Preset, t_ as McpConnection, tg as SkillConfig, th as ResolveContentRef, uh as SpawnHookContext, v as MutationValidationBatch, vh as ToolOutcome, vr as ValidationResult, wh as ToolResultImageRefContent, wm as AgentRunOptions, x as MutationValidationMutation, xh as ToolResultDocumentContent, y as MutationValidationFailure, yh as ToolResultAudioContent, zm as McpServerConfig, zp as CerebrasParams } from "./index-DfERTrql.js";
3
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-M2DauK2D.js";
4
4
  import { t as SandboxProvider } from "./sandbox-DLY6uah7.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 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 };
package/docs/CHAT.md CHANGED
@@ -783,10 +783,51 @@ A categorized view of what's consuming the context window — system prompt, rul
783
783
  - **`buildContextBreakdown(snapshot)`** (`zidane/chat` + `zidane/chat/pure`, node-free) is the pure shaper. Live categories always reconcile to the real last-turn total (`conversation = used − Σ(other live)`), so the bar can never contradict the footer's `ctx` gauge. Free space and the autocompact buffer are exact; per-section counts are heuristic (`estimateTokens`) unless the provider supplies exact counts.
784
784
  - **Exact counts (optional).** When a provider implements `Provider.countTokens(payload)` (Anthropic `messages.countTokens`, OpenAI `responses.input_tokens`), `getContextBreakdown` differences a system-only and a system+tools probe to attribute the System prompt + Tool definitions buckets exactly; categories with no exact source render with a `~` prefix. `countTokens` returns `null` when unavailable (no endpoint / wrong auth) and the heuristic fills in — it can never break the breakdown.
785
785
  - **Autocompact zone.** Pass `opts.compactThreshold` (the `Settings.autoCompactThreshold` fraction) and the "Autocompact buffer" bucket becomes the whole tail from the threshold to the end of the window, so the bar shows exactly where compaction will fire.
786
- - **Rules attribution.** The agent can't isolate the rules block from the opaque host-composed system string, so the host passes `AgentRunOptions.contextSections = { rulesBlock, rulesFiles }` (the rendered AGENTS.md block + its source files) for a dedicated, per-file "Rules" row. The TUI and GUI both discover rules via `discoverAgentsMd` and thread this through.
786
+ - **Rules attribution.** The agent can't isolate the rules block from the opaque host-composed system string, so the host passes `AgentRunOptions.contextSections = { rulesBlock, rulesFiles }` (the rendered AGENTS.md block + its source files) for a dedicated, per-file "Rules" row. Both hosts discover rules via `discoverAgentsMd`, but only the **GUI** currently threads `contextSections` into `agent.run()` — the TUI does not, so its breakdown has no "Rules" bucket. (The TUI's Settings → Instructions tab gets its per-file rows from the host side instead; see below.)
787
787
 
788
788
  `ContextBreakdown`, `ContextBreakdownOptions`, `ContextSnapshot`, `ContextCategory`, `ContextItem`, `ContextUsageSplit`, `ContextExactCounts`, `ContextMcpGroup`, and `buildContextBreakdown` are all exported from both `zidane/chat` and `zidane/chat/pure`.
789
789
 
790
+ ## `SYSTEM.md` — overriding the built-in prompt
791
+
792
+ `discoverSystemMd` (`chat/system-md`) finds a `SYSTEM.md` that **replaces** the built-in doctrine. Deliberately inverted semantics vs `AGENTS.md`:
793
+
794
+ | | `AGENTS.md` | `SYSTEM.md` |
795
+ |------------|-----------------------------|-----------------------------------|
796
+ | effect | appends to the doctrine | replaces the doctrine |
797
+ | multiples | every match loads, in order | exactly one wins |
798
+ | precedence | broad → narrow, all present | project beats user, rest shadowed |
799
+
800
+ Discovery honors the same `userInstructionsScope` setting as `AGENTS.md`, with the same per-scope rules — `DiscoverSystemMdOptions.scope` takes the `AgentsMdScope` itself, not a boolean. `'none'` skips discovery before any filesystem probe (it exists to debug the base prompt "without any user doctrine bleeding in", and an override is the most invasive form of user doctrine there is). `'user'` drops the project candidate and `'project'` drops the user one, mirroring how `discoverAgentsMd` builds its list. That matters most for `'user'`: it is how someone says "do not trust this checkout", so a repo-root `SYSTEM.md` must not load there.
801
+
802
+ > **A project-scope `SYSTEM.md` deletes safety doctrine.** `AGENTS.md` can only append, and is rendered under a header marking it as user-authored guidance. An override REPLACES the built-in prompt — `ACTIONS_WITH_CARE_DOCTRINE` and `GIT_DOCTRINE` included — so a file that merely exists at the root of a checkout can drop "destructive actions need explicit confirmation" and "never force-push without confirmation". Appending to doctrine and deleting doctrine are different risk profiles. Because discovery is automatic at session activation, the TUI paints a one-line transcript warning naming the file whenever a *project*-scope override is in force; the user-scope file stays quiet, since the user wrote it in their own home directory. Set `userInstructionsScope` to `'user'` or `'none'` to refuse repo-supplied overrides entirely.
803
+
804
+ Search order, winner first: `<project root>/SYSTEM.md`, then `~/.{prefix}/SYSTEM.md`. Concatenating overrides would be incoherent — two files each claiming to *be* the system prompt can't both be it — so one wins and the losers come back in `SystemMdResult.shadowed` for the UI to mark inactive. An empty or whitespace-only file counts as absent, so a created-but-unwritten file can't hand the model a blank prompt.
805
+
806
+ `BuildSystemOptions.systemOverride` is what applies it: `buildBuildSystem` / `buildPlanSystem` swap it in for identity + doctrine + guidance. Two things still compose around it:
807
+
808
+ - **`userInstructions` is appended below it** — `AGENTS.md` stays an additive layer, so overriding the prompt doesn't silently disable your project instructions.
809
+ - **The `<env>` block still lands below the cache boundary** — it carries cwd + project root, which the file tools' path resolution depends on. Dropping it would break `read_file` in ways that look like model error rather than configuration.
810
+
811
+ `allowInteraction` is not consulted for the override text (the built-in fragments swap in no-prompts variants of themselves; your text is yours). One file overrides whichever profile is active — there is no plan-specific override.
812
+
813
+ A host-supplied custom profile **cannot** be overridden: it keeps its own `preset.system`, so there is no built-in doctrine for the file to replace and the file is never read. Hosts must not report such a file as the prompt's source — the TUI threads a `supportsOverride` flag out of `composeHostContext` for exactly this, and refuses to create an override on those profiles rather than writing one that could never apply.
814
+
815
+ `renderSystemMdSeed(staticPrompt)` builds the contents for a newly created override: an HTML-comment header explaining the semantics, followed by the doctrine that was in effect. Seeding with the real prompt makes creating the file a behavioral no-op until edited — an empty seed would strip ~1.2k tokens of doctrine the instant the file appeared, with no visible cause. Callers pass `splitSystemPrompt(system).static` so the per-turn `<env>` block never gets frozen into the file.
816
+
817
+ ## Context segments
818
+
819
+ Where the breakdown above answers "how many tokens is each part of my context", **context segments** answer "what IS each part, and where did it come from" — the data behind the TUI's Settings → Instructions tab.
820
+
821
+ - **`agent.getContextSegments(opts?)`** returns `ContextSegment[] | null` from the same assembly the breakdown uses (live run snapshot, falling back to the pre-run assembly), so it's populated before the first message and `null` only after `destroy()`. No provider round-trip and no tool serialization — just the assembled strings.
822
+ - **`buildContextSegments(input)`** (`zidane/chat` + `zidane/chat/pure`, node-free) is the pure shaper. Each `ContextSegment` carries `{ id, label, origin, detail?, text, path?, tokens }`. The base "System prompt" segment is `baseSystem` minus `rulesBlock`, so instruction text is never reported twice; empty / whitespace-only pieces are dropped.
823
+ - **Origins are honest about openability.** `'file'` is the only origin that carries a `path`, and therefore the only one a UI can hand to an editor. `'built-in'` is compiled in (base doctrine, subagent defs), `'generated'` is assembled at prompt time (the skills catalog), `'mcp'` is served by a connected server.
824
+ - **`systemPath` promotes the base prompt to a file segment.** Pass it when a `SYSTEM.md` override produced the prompt and the "System prompt" segment flips from `'built-in'` to `'file'`, becoming openable. Host-supplied for the same reason as `rulesFiles`: the agent receives an already-composed system string with no provenance attached.
825
+ - **`rulesBlock` is what makes the de-dup work.** The base-prompt segment subtracts it so instruction text isn't reported twice (once inline, once as its own file rows). `getContextSegments` falls back to the assembly's copy, but that only exists when the host threaded `AgentRunOptions.contextSections` into `run()` — which the TUI does not — so a host that renders its own instruction block should pass it explicitly.
826
+ - **Instruction files are host-supplied.** Pass `opts.rulesFiles` — a host that already called `discoverAgentsMd` passes its `files` straight through, since `AgentsMdFile` is structurally compatible with `ContextSegmentFile`. The agent never reads those files itself (it receives the rendered block as an opaque substring), so without this it knows paths at best and never text.
827
+ - **Skill bodies are not here.** Activating a skill puts its body in the *conversation* as a tool result, not the system prompt; only the catalog blurbs are default context. `ContextBreakdown.activeSkills` reports which skills are live.
828
+
829
+ `ContextSegment`, `ContextSegmentFile`, `ContextSegmentOrigin`, `ContextSegmentsInput`, and `buildContextSegments` are exported from `zidane/chat` and `zidane/chat/pure` (types also from the root barrel).
830
+
790
831
  ## Edit-diff rendering
791
832
 
792
833
  Renderer-agnostic helpers for turning `edit` / `multi_edit` / `write_file` tool calls into a unified diff. Two tiers:
@@ -88,11 +88,50 @@ In the question wizard (multi-question batches):
88
88
  the whole selection, at least one pick required unless `required: false`)
89
89
  - `shift+↹` — back one question (preserves your answers, pre-fills the
90
90
  input with the prior value)
91
+ - `pagedown` / `pageup` — scroll the form down / up by one viewport, keeping a
92
+ 1-row overlap so the line at the fold stays visible. The form's scrollbox is
93
+ deliberately not focusable (the option list and text inputs own the keyboard),
94
+ so these are how you read a long intro or peek at a later question without
95
+ reaching for the mouse. Advancing a question also scrolls it into view
96
+ automatically. The plan panel uses the same keys and the same page size.
97
+
98
+ Not `ctrl+d` / `ctrl+u`: OpenTUI's textarea bindings map those to
99
+ delete-to-line-start and delete, and a textarea question is focused whenever
100
+ this handler is, so they would destroy your typed answer as they scrolled.
101
+ While a form is up the transcript releases these keys, so one press moves one
102
+ surface.
91
103
  - `esc` — leave the form
92
104
  - Live (agent waiting): aborts the run
93
105
  - Resumed (you reopened the session): leaves the form, the session
94
106
  re-enqueues the same interaction on next activation
95
107
 
108
+ ### Sizing
109
+
110
+ The wizard is content-sized inside a ceiling, and all three numbers come from
111
+ the terminal rather than from percentages of a parent:
112
+
113
+ - **Ceiling** — `wizardMaxRows(terminalRows)`, roughly 75% of the terminal,
114
+ never below 8 rows and never within 8 rows of the top so some transcript stays
115
+ visible. It used to be a `maxHeight: '60%'`, which resolves against the
116
+ parent's height; the wizard's parent is content-sized, so Yoga had nothing to
117
+ take 60% of and the cap silently never applied — the shell grew to whatever
118
+ its content estimate claimed and drew its border there, leaving questions
119
+ rendered below a closed frame.
120
+ - **Content height** — `introRows` + `estimateQuestionRowHeight` per question,
121
+ both **wrap-aware** (`wrappedRows`) and both given the panel's content width.
122
+ They previously counted `\n` only and ignored choice descriptions, so any
123
+ wrapped markdown intro or wrapped description under-sized the panel.
124
+ - **Floor** — the shell takes `minHeight` equal to the rows its content needs.
125
+ The shell is `flexShrink: 1` (it was `0`, which is why it could neither cap
126
+ nor shrink), and shrinkability alone let a `flexGrow` transcript sibling
127
+ squeeze the form to a fraction of its content; the floor makes the transcript
128
+ yield instead.
129
+
130
+ The height is also **monotonic**: answering a question collapses its row to a
131
+ one-line summary, which would otherwise shrink the panel mid-form. A high-water
132
+ mark keyed by request id means the panel settles at the largest size it has
133
+ needed and stays there.
134
+
96
135
  In the plan picker:
97
136
 
98
137
  - Decision picker → `revise` / `reject` → comment textarea
package/docs/TUI.md CHANGED
@@ -99,7 +99,7 @@ import {
99
99
  | `Modal`, `ModalRoot`, `useModal`, `useModalAwareFocus` | Overlay layer. `Modal` owns its own esc-to-close handler. `useModalAwareFocus(preferred)` returns `false` while a modal is open so background inputs blur. **Single-slot:** `modal.open(node)` replaces the active node; modals do not stack. |
100
100
  | `ModelPickerModal` | Cross-provider searchable model picker. Two commit modes: **single pick** (`onPick`) — ↵ returns `{ providerKey, modelId }`, used by the compaction-model picker; **ordered multi-select** (`onApplyChain`) — ↵ toggles the row in a numbered fallback chain (the number is the row's 1-based position; pick order = fallback order), esc applies. Entry 1 is the primary (provider swap, effort, options); entries 2+ become run-level fallback targets (`agent.run({ model: […] })`). Esc with an unchanged or emptied chain is a plain cancel. Cross-provider chains trigger a provider swap + session re-activation in the host; the footer model chip shows `+N` while fallbacks are configured. When a run actually falls back (`model:fallback`), the chip switches to the model NOW serving the run, warn-colored as the "temporary" cue, and a transcript info line records the switch; both snap back at the next run (fallback is run-sticky per run). |
101
101
  | `EffortPickerModal` | Reasoning-effort picker. Lists `'off' / 'minimal' / 'low' / 'medium' / 'high'` plus `'adaptive'` when `supportsAdaptive` is true (Anthropic only). Surfaced via `ctrl+l` when `modelSupportsReasoning(descriptor, model)`. |
102
- | `SettingsModal` | Tabbed: **Agent** (behaviour, persistence), **UI** (display, theming, animations), **Keybindings** (read-only catalog · `↵` opens `keybindings.json`), **Authentication** (current provider + detected providers · `↵` pops the AuthScreen), **Skills**, **MCP servers**. Category tab order is data-driven by `SETTINGS_CATEGORIES` from `zidane/chat`. Keybindings + Authentication tabs only appear when the host wires the `keybindings` / `authentication` data props; otherwise the legacy "open keybindings file" / "re-authenticate" rows render in the UI / Agent tabs as a fallback. One shared search input filters the active tab; `←/→` cycle tabs (preventDefault keeps the input cursor put), `↑/↓` navigate, `↵` toggle/cycle/pick/edit. MCP tab adds `ctrl+L` login, `ctrl+O` logout, and `esc` cancel-authorizing (Modal's esc-close still fires alongside). Sized at `maxWidth: 160, maxHeight: 50` — wider than the legacy modal so the Keybindings catalog + Authentication status block sit comfortably. |
102
+ | `SettingsModal` | Tabbed: **Agent** (behaviour, persistence), **UI** (display, theming, animations), **Keybindings** (read-only catalog · `↵` opens `keybindings.json`), **Authentication** (current provider + detected providers · `↵` pops the AuthScreen), **Instructions** (live default-context segments · `tab` reads inline, `↵` opens file-backed sources), **Skills**, **MCP servers**. Category tab order is data-driven by `SETTINGS_CATEGORIES` from `zidane/chat`. Keybindings + Authentication tabs only appear when the host wires the `keybindings` / `authentication` data props; otherwise the legacy "open keybindings file" / "re-authenticate" rows render in the UI / Agent tabs as a fallback. One shared search input filters the active tab; `←/→` cycle tabs (preventDefault keeps the input cursor put), `↑/↓` navigate, `↵` toggle/cycle/pick/edit. MCP tab adds `ctrl+L` login, `ctrl+O` logout, and `esc` cancel-authorizing (Modal's esc-close still fires alongside). Sized at `maxWidth: 160, maxHeight: 50` — wider than the legacy modal so the Keybindings catalog + Authentication status block sit comfortably. |
103
103
  | `AgentPickerModal` | Multi-agent profile picker. Hidden when only one profile is registered. |
104
104
  | `SkillsSettingsModal`, `McpsSettingsModal` | Standalone surfaces re-exported for embedders. The main `SettingsModal` integrates the same lists as tabs — prefer that for the in-app flow. `McpsSettingsModal` uses single-letter shortcuts (`l` / `o`); the integrated tab uses `ctrl+L` / `ctrl+O`. |
105
105
  | `ToggleListModal` | Generic checkbox-list modal for `settings.enabledX` allowlists. Powers both the Skills + MCP server pickers; reuse with `keyOf` + `settingKey` + `renderDetail` to add a third toggle surface. |
@@ -378,6 +378,28 @@ Because top-level runs share `session.metadata.todos.session`, a list written in
378
378
 
379
379
  The Settings modal is tabbed. The **Agent** and **UI** tabs each render three row kinds (toggle / choice / action) sourced from the chat-level tables, filtered by their `category` field. The **Keybindings** tab embeds the action catalog from `chat/keybindings.ts` (shared with the standalone `KeybindingsModal` via `groupBindings` + `KEYBINDING_KEY_COL_WIDTH`); `↵` opens the JSON file. The **Authentication** tab shows the active provider + a list of detected ones (from `detectAuth`); `↵` pops the full-screen `AuthScreen` (the multi-step wizard doesn't embed cleanly inside a modal). **Skills** and **MCP servers** render the discovered catalogs as enable/disable checklists.
380
380
 
381
+ The **Instructions** tab answers "what text is in my context, and where did it come from". Each row is a `ContextSegment` (`chat/context-segments`) — the base system prompt, each discovered `AGENTS.md` / `CLAUDE.md`, the skills catalog, the MCP instructions block, the subagent definitions — labelled with its origin (`built-in` / `file` / `generated` / `mcp server`) and heuristic token size. `tab` expands a row to read its text inline (clamped to `INSTRUCTION_PREVIEW_LINE_CAP` lines); `↵` opens the source in `$EDITOR`, but only for `origin: 'file'` rows — the other origins have no file, and the tab says so rather than no-op'ing. The shared search box matches each segment's full body, so it doubles as "which instruction source told the agent this?".
382
+
383
+ The data is split by who knows what: the agent supplies the segments it assembled via `agent.getContextSegments()`, while the host supplies the instruction files (path **and** text) it loaded at session activation — the agent only ever sees the rendered rules block as an opaque substring of the system prompt, so it can't attribute text back to a path. `App` holds those files in `sessionInstructionFilesRef`, captured alongside the prompt they were rendered into, so the tab reports what the session is actually running on rather than what happens to be on disk now. Because `SettingsModal` is opened with snapshot props, the fetch is an *action* (`actions.getContextSegments`) that the modal calls on every activation of the tab and whose loading / error state it owns; wiring that action is what makes the tab appear.
384
+
385
+ **`SYSTEM.md` override.** When a `SYSTEM.md` is active (see `docs/CHAT.md`), the System prompt row reports `origin: 'file'` and `↵` opens it like any other file row. When none exists, `↵` **creates** `~/.{prefix}/SYSTEM.md` seeded with the doctrine currently in effect and opens that — the one Instructions action that writes, so the footer hint reads `create SYSTEM.md override` rather than `open file`. Creation is idempotent (an existing file is opened, never clobbered) and the seed makes it a behavioral no-op until edited. A `SYSTEM.md` that exists on disk but isn't loaded — beaten on precedence, or found on a profile that ignores overrides — is reported in a note above the list, so editing an inactive file doesn't look ignored for no reason.
386
+
387
+ Two things the seed gets right, both of which were bugs worth naming:
388
+
389
+ - **It seeds from the DOCTRINE-ONLY prompt** (`composeHostContext().doctrineOnlySystem()`), not from the composed one. The composed prompt already contains the rendered `AGENTS.md` block in its static half, and an override composes *above* a freshly-appended copy of that block — so seeding from it would duplicate every instruction on the next session.
390
+ - **It refuses on profiles that can't be overridden.** `composeHostContext` returns `system: null` for any profile that isn't `build` or `plan`, which would seed a comment-only file. That file is still a *valid* override, so switching to `build` later would silently reduce the entire doctrine to an HTML comment. The create path throws instead, and the row explains that the profile supplies its own prompt.
391
+
392
+ `supportsOverride` also gates the hint and the row action, so a profile that ignores overrides never advertises a create action or claims a file is the prompt's source.
393
+
394
+ **`ctrl+r` reload.** Matches the refresh key on the Skills and MCPs tabs. Re-discovery is implicit — `composeHostContext` hits the filesystem on every call, so re-fetching picks up new files — and the real work is `onRefreshInstructions` rebuilding the live agent through the existing `rebuildActiveAgent()` so a running session adopts the new prompt without a TUI restart. While a run is in flight the rebuild is deferred onto `pendingAgentRebuildRef` and applied by `drainFollowUps` once the turn ends, because swapping the system prompt underneath a streaming turn is not safe. The row notice reports which happened (`agent rebuilt` vs `rebuild queued until the current run finishes`) since the consequences differ.
395
+
396
+ A mid-session rebuild costs more than it looks like it should, which is why this is an explicit keypress rather than a filesystem watcher:
397
+
398
+ - It replaces the system prompt on an existing conversation, guaranteeing a **prompt-cache miss** on the next turn, and the model sees different doctrine mid-conversation.
399
+ - `rebuildActiveAgent` destroys the stale agent, which **closes its MCP connections**. The fresh agent reconnects lazily on its next run, so the turn after a reload pays that reconnect. The notice says so (`MCP servers reconnect on the next turn`).
400
+
401
+ **No active session.** Settings opens on every screen but `auth`, so the tab is reachable from home / sessions / model-picker before `agentRef.current` exists. The action then composes a preview of the *next* session instead of reporting an empty context: `composeHostContext(profile, allowInteraction)` — the same helper `buildAgent` uses at activation, so the two can't drift — supplies the system prompt and the discovered instruction files, and `buildCatalog` over the enabled discovered skills supplies the catalog. The result carries `live: false` and the tab renders a note, because this view necessarily omits what the agent appends at run time (MCP instructions, extension system fragments, subagent definitions) and extension-contributed skills, which are only merged during `buildAgent`.
402
+
381
403
  | Kind | Source | Activation |
382
404
  |---|---|---|
383
405
  | `toggle` | `SETTINGS_TOGGLES` (`BooleanSettingKey`) | `enter` flips the boolean |
@@ -389,6 +411,8 @@ The Settings modal is tabbed. The **Agent** and **UI** tabs each render three ro
389
411
 
390
412
  `App` wires two general actions: `Keybindings` (open `keybindings.json` in `$EDITOR`) and `Authentication` (open `AuthScreen`). Authentication is gated on `!busy && !pendingApproval` so re-config can't tear down an in-flight run.
391
413
 
414
+ **Editor launching.** `launchEditor(path)` resolves `$VISUAL` / `$EDITOR`, falling back to the platform opener (`open` / `xdg-open` / `cmd /c start`). Those variables hold a command *line*, not a bare executable — `cursor --wait`, `code --wait`, `subl -w` — so `parseEditorCommand` (`src/tui/editor-command.ts`, quote-aware, no backslash escaping so Windows paths survive) splits the flags off and keeps them before the path. The promise resolves on the child's `'spawn'` event and rejects on `'error'`: `child_process.spawn` reports ENOENT through an event rather than a throw, so without that funnel a missing binary escaped every caller's `try`/`catch` as an unhandled emitter error and crashed into OpenTUI's error console. Callers therefore get a real rejection — the Instructions tab renders it as a row notice, the keybindings action logs it under `ZIDANE_DEBUG`.
415
+
392
416
  A single shared search input filters the active tab's list (`label + description` for general items, `name + description` for skills, `name + transport + command/URL` for MCP servers). Typing resets the cursor on the active tab to 0; tab switches (`←/→`) preserve each tab's cursor (clamped against the current filtered size).
393
417
 
394
418
  For the live source of truth, see `SETTINGS_TOGGLES` / `SETTINGS_CHOICES` in `src/chat/settings-context.tsx`. The current shape includes:
@@ -630,7 +654,9 @@ src/tui/
630
654
  modal.tsx ModalRoot + Modal + useModalAwareFocus
631
655
  model-picker.tsx Cross-provider searchable model list modal
632
656
  effort-picker.tsx Reasoning-effort modal
633
- settings-modal.tsx Tabbed settings panel (Agent / UI / Keybindings / Authentication / Skills / MCPs)
657
+ settings-modal.tsx Tabbed settings panel (Agent / UI / Keybindings / Authentication / Instructions / Skills / MCPs)
658
+ instruction-rows.ts Instructions-tab row model + preview clamping (pure)
659
+ editor-command.ts $EDITOR / $VISUAL command-line tokenizer (pure)
634
660
  agent-picker.tsx Multi-agent profile modal (accentColor re-exported from zidane/chat)
635
661
  skills-settings.tsx Skills list + toggle modal (standalone for embedders)
636
662
  mcps-settings.tsx MCP servers list + toggle modal (standalone for embedders)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zidane",
3
- "version": "6.5.1",
3
+ "version": "6.5.2",
4
4
  "description": "an agent that goes straight to the goal",
5
5
  "type": "module",
6
6
  "private": false,