@lucascouts/claude-agent-tui 0.5.2 → 0.7.0

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 (108) hide show
  1. package/NOTICE +1 -1
  2. package/README.md +1 -1
  3. package/dist/acp-agent.d.ts +249 -21
  4. package/dist/acp-agent.js +573 -73
  5. package/dist/agent-catalog.d.ts +95 -0
  6. package/dist/agent-catalog.js +287 -0
  7. package/dist/ansi-mirror.d.ts +0 -1
  8. package/dist/besteffort.d.ts +0 -1
  9. package/dist/billing/entrypoint-guard.d.ts +0 -1
  10. package/dist/claude-path.d.ts +0 -1
  11. package/dist/claude-path.js +6 -0
  12. package/dist/command-catalog.d.ts +84 -0
  13. package/dist/command-catalog.js +339 -0
  14. package/dist/diff-enriched-reader.d.ts +0 -1
  15. package/dist/diff-source.d.ts +0 -1
  16. package/dist/drift-checks.d.ts +0 -1
  17. package/dist/end-of-turn.d.ts +6 -1
  18. package/dist/end-of-turn.js +8 -1
  19. package/dist/engine-lifecycle.d.ts +66 -2
  20. package/dist/engine-lifecycle.js +43 -4
  21. package/dist/engine-pty.d.ts +70 -3
  22. package/dist/engine-pty.js +80 -6
  23. package/dist/engine-watcher.d.ts +0 -1
  24. package/dist/engine.d.ts +0 -1
  25. package/dist/event-switch.d.ts +0 -1
  26. package/dist/gate/port.d.ts +0 -1
  27. package/dist/gate/settings-writer.d.ts +14 -1
  28. package/dist/gate/settings-writer.js +49 -0
  29. package/dist/image-input.d.ts +30 -0
  30. package/dist/image-input.js +79 -0
  31. package/dist/image-vision-smoke.d.ts +51 -0
  32. package/dist/image-vision-smoke.js +111 -0
  33. package/dist/index.d.ts +0 -1
  34. package/dist/index.js +6 -0
  35. package/dist/jsonl.d.ts +0 -1
  36. package/dist/lib.d.ts +0 -1
  37. package/dist/linearize.d.ts +1 -2
  38. package/dist/linearize.js +1 -1
  39. package/dist/live-diff-env.d.ts +0 -1
  40. package/dist/live-subagent-env.d.ts +0 -1
  41. package/dist/mcp-config-writer.d.ts +60 -0
  42. package/dist/mcp-config-writer.js +172 -0
  43. package/dist/model-catalog.d.ts +68 -3
  44. package/dist/model-catalog.js +123 -13
  45. package/dist/permissions/allow-inject.d.ts +0 -1
  46. package/dist/permissions/deny.d.ts +12 -1
  47. package/dist/permissions/deny.js +18 -0
  48. package/dist/permissions/elicitation-bridge.d.ts +71 -0
  49. package/dist/permissions/elicitation-bridge.js +146 -0
  50. package/dist/permissions/gate-wiring.d.ts +23 -3
  51. package/dist/permissions/gate-wiring.js +123 -1
  52. package/dist/permissions/hook-server.d.ts +11 -3
  53. package/dist/permissions/hook-server.js +10 -1
  54. package/dist/permissions/permission-mode.d.ts +0 -1
  55. package/dist/permissions/request-permission.d.ts +0 -1
  56. package/dist/settings.d.ts +0 -1
  57. package/dist/settings.js +9 -0
  58. package/dist/stop-reason-map.d.ts +0 -1
  59. package/dist/subagent-gate.d.ts +0 -1
  60. package/dist/subagent-source.d.ts +0 -1
  61. package/dist/subagent-watcher.d.ts +0 -1
  62. package/dist/tools.d.ts +0 -1
  63. package/dist/tools.js +5 -1
  64. package/dist/usage-env.d.ts +0 -1
  65. package/dist/usage.d.ts +3 -1
  66. package/dist/usage.js +3 -0
  67. package/dist/utils.d.ts +0 -1
  68. package/dist/zed-register.d.ts +0 -1
  69. package/package.json +12 -9
  70. package/dist/acp-agent.d.ts.map +0 -1
  71. package/dist/ansi-mirror.d.ts.map +0 -1
  72. package/dist/besteffort.d.ts.map +0 -1
  73. package/dist/billing/entrypoint-guard.d.ts.map +0 -1
  74. package/dist/claude-path.d.ts.map +0 -1
  75. package/dist/diff-enriched-reader.d.ts.map +0 -1
  76. package/dist/diff-source.d.ts.map +0 -1
  77. package/dist/drift-checks.d.ts.map +0 -1
  78. package/dist/end-of-turn.d.ts.map +0 -1
  79. package/dist/engine-lifecycle.d.ts.map +0 -1
  80. package/dist/engine-pty.d.ts.map +0 -1
  81. package/dist/engine-watcher.d.ts.map +0 -1
  82. package/dist/engine.d.ts.map +0 -1
  83. package/dist/event-switch.d.ts.map +0 -1
  84. package/dist/gate/port.d.ts.map +0 -1
  85. package/dist/gate/settings-writer.d.ts.map +0 -1
  86. package/dist/index.d.ts.map +0 -1
  87. package/dist/jsonl.d.ts.map +0 -1
  88. package/dist/lib.d.ts.map +0 -1
  89. package/dist/linearize.d.ts.map +0 -1
  90. package/dist/live-diff-env.d.ts.map +0 -1
  91. package/dist/live-subagent-env.d.ts.map +0 -1
  92. package/dist/model-catalog.d.ts.map +0 -1
  93. package/dist/permissions/allow-inject.d.ts.map +0 -1
  94. package/dist/permissions/deny.d.ts.map +0 -1
  95. package/dist/permissions/gate-wiring.d.ts.map +0 -1
  96. package/dist/permissions/hook-server.d.ts.map +0 -1
  97. package/dist/permissions/permission-mode.d.ts.map +0 -1
  98. package/dist/permissions/request-permission.d.ts.map +0 -1
  99. package/dist/settings.d.ts.map +0 -1
  100. package/dist/stop-reason-map.d.ts.map +0 -1
  101. package/dist/subagent-gate.d.ts.map +0 -1
  102. package/dist/subagent-source.d.ts.map +0 -1
  103. package/dist/subagent-watcher.d.ts.map +0 -1
  104. package/dist/tools.d.ts.map +0 -1
  105. package/dist/usage-env.d.ts.map +0 -1
  106. package/dist/usage.d.ts.map +0 -1
  107. package/dist/utils.d.ts.map +0 -1
  108. package/dist/zed-register.d.ts.map +0 -1
package/NOTICE CHANGED
@@ -10,5 +10,5 @@ This product is a derivative work of:
10
10
 
11
11
  The engine was rewritten to drive the Claude Code subscription TUI over a
12
12
  pseudo-terminal (PTY) instead of calling the Claude Agent SDK. See
13
- .fork-provenance.json (fork point: v0.39.0) and CHANGELOG.md for the scope
13
+ .fork-provenance.json (fork point: v0.53.0) and CHANGELOG.md for the scope
14
14
  of modifications.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  An [ACP](https://agentclientprotocol.com)-compatible agent that drives the **Claude Code subscription TUI** over a PTY, so your Claude Code threads render natively in [Zed](https://zed.dev) and other ACP clients.
6
6
 
7
- > **Fork** of [`@agentclientprotocol/claude-agent-acp`](https://github.com/agentclientprotocol/claude-agent-acp) v0.39.0. Where the upstream adapter calls the Claude Agent **SDK**, this fork spawns the `claude` **subscription CLI** in a pseudo-terminal and translates its JSONL transcript into ACP `session/update` notifications. See [`.fork-provenance.json`](.fork-provenance.json) for the exact fork point.
7
+ > **Fork** of [`@agentclientprotocol/claude-agent-acp`](https://github.com/agentclientprotocol/claude-agent-acp) v0.53.0. Where the upstream adapter calls the Claude Agent **SDK**, this fork spawns the `claude` **subscription CLI** in a pseudo-terminal and translates its JSONL transcript into ACP `session/update` notifications. See [`.fork-provenance.json`](.fork-provenance.json) for the exact fork point.
8
8
 
9
9
  ## Why this exists
10
10
 
@@ -1,4 +1,4 @@
1
- import { Agent, AgentSideConnection, AuthenticateRequest, CancelNotification, ClientCapabilities, ForkSessionRequest, ForkSessionResponse, InitializeRequest, InitializeResponse, ListSessionsRequest, ListSessionsResponse, LoadSessionRequest, LoadSessionResponse, NewSessionRequest, NewSessionResponse, PromptRequest, PromptResponse, ReadTextFileRequest, ReadTextFileResponse, ResumeSessionRequest, ResumeSessionResponse, SessionConfigOption, SessionModeState, SessionNotification, SetSessionConfigOptionRequest, SetSessionConfigOptionResponse, SetSessionModeRequest, SetSessionModeResponse, CloseSessionRequest, CloseSessionResponse, DeleteSessionRequest, DeleteSessionResponse, TerminalHandle, TerminalOutputResponse, WriteTextFileRequest, WriteTextFileResponse } from "@agentclientprotocol/sdk";
1
+ import { Agent, AgentSideConnection, AuthenticateRequest, CancelNotification, ClientCapabilities, ForkSessionRequest, ForkSessionResponse, InitializeRequest, InitializeResponse, ListSessionsRequest, ListSessionsResponse, LoadSessionRequest, LoadSessionResponse, NewSessionRequest, NewSessionResponse, PromptRequest, PromptResponse, ReadTextFileRequest, ReadTextFileResponse, ResumeSessionRequest, ResumeSessionResponse, SessionConfigOption, SessionModeState, SessionNotification, SetSessionConfigOptionRequest, SetSessionConfigOptionResponse, SetSessionModeRequest, SetSessionModeResponse, CloseSessionRequest, CloseSessionResponse, DeleteSessionRequest, DeleteSessionResponse, LogoutRequest, LogoutResponse, TerminalHandle, TerminalOutputResponse, WriteTextFileRequest, WriteTextFileResponse, AvailableCommand } from "@agentclientprotocol/sdk";
2
2
  import { ModelInfo, Options, PermissionMode, PermissionUpdate, SDKMessageOrigin, SDKPartialAssistantMessage } from "@anthropic-ai/claude-agent-sdk";
3
3
  import { ContentBlockParam } from "@anthropic-ai/sdk/resources";
4
4
  import { BetaContentBlock, BetaRawContentBlockDelta } from "@anthropic-ai/sdk/resources/beta.mjs";
@@ -15,6 +15,7 @@ import type { ListSubagents, GetSubagentMessages } from "./subagent-source.js";
15
15
  import { type SidechainToolUse } from "./subagent-gate.js";
16
16
  import type { SubagentWatcher } from "./subagent-watcher.js";
17
17
  import type { DetectorSchedule, EndOfTurnDetector } from "./end-of-turn.js";
18
+ import { type AgentCatalogEntry } from "./agent-catalog.js";
18
19
  import type { SessionGate, SessionGateOptions } from "./permissions/gate-wiring.js";
19
20
  export declare const CLAUDE_CONFIG_DIR: string;
20
21
  /**
@@ -30,6 +31,7 @@ type AccumulatedUsage = {
30
31
  cachedReadTokens: number;
31
32
  cachedWriteTokens: number;
32
33
  };
34
+ export declare const DEFAULT_CONTEXT_WINDOW = 200000;
33
35
  type Session = {
34
36
  /** The live PTY handle (story 013/014) running the subscription `claude` TUI for this session. */
35
37
  pty: IPty;
@@ -49,6 +51,13 @@ type Session = {
49
51
  * was surfaced in an earlier pump). Per-session — sub-agent row uuids are session-scoped.
50
52
  */
51
53
  emittedNested: Set<string>;
54
+ /**
55
+ * Story 056 (#812) — the last session title pushed to the client via `session_info_update` at the
56
+ * story-024 turn boundary. Used to DEDUP: `emitSessionTitleUpdate` skips the push when the freshly
57
+ * sanitized `getSessionInfo().summary` equals this. Undefined until the first non-empty title is
58
+ * pushed; per-session (titles are session-scoped).
59
+ */
60
+ lastEmittedTitle?: string;
52
61
  /** Story 054 — per-session dedup Set of sidechain inner tool_use ids already fed to the gate
53
62
  * correlator (R3 exactly-once). Lazy-init on the gated pump path; absent on a no-gate session. */
54
63
  registeredSidechain?: Set<string>;
@@ -59,19 +68,54 @@ type Session = {
59
68
  engine?: SessionEngine;
60
69
  cancelled: boolean;
61
70
  cwd: string;
62
- /** Serialized snapshot of session-defining params (cwd, mcpServers) used to
71
+ /**
72
+ * Story 057 (R1.3) — the RESOLVED additional-directory list for this session (the request's
73
+ * `additionalDirectories`, else `_meta.additionalRoots`, else `[]`), stored so sub-task 2.3's
74
+ * {@link respawnSession} can re-thread the SAME `--add-dir` scope into the in-place re-spawn. This
75
+ * is the RAW list (per-dir sanitization is the engine's job); absent on pre-057 / replay records.
76
+ */
77
+ additionalDirectories?: string[];
78
+ /**
79
+ * Story 057 (R2.3/R2.4, sub-task 2.3) — the CURRENT MCP scratch path ({@link writeMcpScratch})
80
+ * threaded into this session's spawn as `--mcp-config "<file>"`. Retained so {@link teardownSession}
81
+ * can REMOVE it (R2.3, no orphan/secret leak) and {@link respawnSession} can REGENERATE it (R2.4:
82
+ * write-new-then-remove-old, swapping this to the new path). Absent when the session declared no
83
+ * MCP servers (and on replay-only records, which spawn nothing).
84
+ */
85
+ mcpConfigFile?: string;
86
+ /**
87
+ * Story 057 (R2.4, sub-task 2.3) — the RAW ACP `mcpServers` array the client declared at create
88
+ * time, retained so {@link respawnSession} can RE-translate ({@link translateMcpServers}) and
89
+ * regenerate the scratch for the re-spawned `claude`. (The translation output is not stored —
90
+ * re-translating from the source keeps the regenerated scratch faithful to the original request.)
91
+ * Absent/empty when no MCP servers were declared.
92
+ */
93
+ mcpServers?: NewSessionRequest["mcpServers"];
94
+ /** Serialized snapshot of session-defining params (cwd, mcpServers, additionalDirectories) used to
63
95
  * detect when loadSession/resumeSession is called with changed values. */
64
96
  sessionFingerprint: string;
65
97
  settingsManager: SettingsManager;
66
98
  accumulatedUsage: AccumulatedUsage;
67
99
  modes: SessionModeState;
68
100
  modelInfos: ModelInfo[];
101
+ /**
102
+ * Story 056 (R3.2) — the main-thread agent personas discovered for this session's cwd at
103
+ * create time ({@link discoverAgents}, glob-only). Stored so the model-change reconcile in
104
+ * {@link setSessionConfigOption} can REBUILD the `agent` configOption without re-globbing the
105
+ * disk. The CURRENT agent is NOT held here — it lives in the `agent` configOption's
106
+ * `currentValue` (mirroring `currentEffort`); `[]`/absent means no personas were discovered and
107
+ * the `agent` option is omitted entirely (upstream #794 `agents.length > 0` gate).
108
+ */
109
+ agents?: AgentCatalogEntry[];
69
110
  configOptions: SessionConfigOption[];
70
- /** Context window size of the last top-level assistant model, carried across
71
- * prompts so mid-stream usage_update notifications report a correct `size`
72
- * before the turn's first result message arrives. Defaults to
73
- * DEFAULT_CONTEXT_WINDOW, refreshed from each result's modelUsage, and
74
- * invalidated when the user switches the session's model. */
111
+ /** Context window size for the session, carried across prompts so mid-stream
112
+ * usage_update notifications report a correct `size` before the turn's first
113
+ * result message arrives. Seeded by `inferContextWindowFromModel` (the static
114
+ * `MODEL_CONTEXT_WINDOWS` curation) and re-resolved when the user switches the
115
+ * session's model. NOTE (story 068): there is NO `result.modelUsage` refresh —
116
+ * the JSONL `usage` block carries only token counts, never a window; the window
117
+ * comes from static curation (the Models API `max_input_tokens` is the real
118
+ * authority, which this PTY/JSONL fork does not call). */
75
119
  contextWindowSize: number;
76
120
  /** Accumulated task list for the session, keyed by task ID. Task IDs are
77
121
  * per-session, so this state must not be shared across sessions. */
@@ -111,6 +155,19 @@ type Session = {
111
155
  * nothing is queued.
112
156
  */
113
157
  pendingModelInjection?: string;
158
+ /**
159
+ * Story 056 v4 — an effort change (`/effort <level>`) requested WHILE a turn is in flight is deferred
160
+ * here (last-write-wins) and flushed as a side-channel PTY write once the turn settles (mirrors
161
+ * {@link pendingModelInjection}). Undefined when nothing is queued.
162
+ */
163
+ pendingEffortInjection?: string;
164
+ /**
165
+ * Story 060 (R2.1/R2.2) — true WHILE the "ultracode" effort-selector sentinel is selected for this
166
+ * session. The LIVE activation it drives is a keyword prefix on the OUTGOING prompt (the binary's
167
+ * documented per-turn Workflow opt-in) — Option A, no re-spawn. Cleared when a real effort level (or
168
+ * `default`) is re-selected. The declarative spawn-time complement is {@link applyUltracodeSettings}.
169
+ */
170
+ ultracodeActive?: boolean;
114
171
  /**
115
172
  * Story 046 (R3.8) — true WHILE an in-place re-spawn (R3.4 dontAsk/bypass switch) is between the old
116
173
  * PTY teardown and the new PTY being ready. Selector changes arriving in this window are rejected
@@ -148,6 +205,13 @@ type Session = {
148
205
  * sourced fresh inside `emitLinearizedWithNested`; the main chain is whatever the last pump saw.
149
206
  */
150
207
  lastMessages?: SessionMessage[];
208
+ /**
209
+ * Story 058 (R2.1/R2.2) — the temp image files materialized for the in-flight turn (pushed by
210
+ * {@link promptToClaude}'s sink when the prompt is assembled, BEFORE it is sent), unlinked at turn
211
+ * settle (prompt()'s catch + finally, covering resolve AND cancel) via {@link cleanupMaterializedImages}
212
+ * and again on teardown as an idempotent backstop. Undefined when the turn materialized no image.
213
+ */
214
+ turnTempImagePaths?: string[];
151
215
  };
152
216
  /** What {@link StartEngine} returns: the authoritative session id (engine-spawn-generated for a
153
217
  * fresh session, the resumed id otherwise), the live PTY, the started watcher, the owning engine,
@@ -226,6 +290,14 @@ export interface StartEngineArgs {
226
290
  * <level>` (non-"default" only). Threaded to BOTH the fresh and resume spawn paths.
227
291
  */
228
292
  effortLevel?: string;
293
+ /**
294
+ * Story 056 (R3.2): the seeded/re-spawn main-thread agent persona, forwarded to the spawn as
295
+ * `--agent "<name>"` (non-"default" only — the spawn layer drops the literal "default" sentinel,
296
+ * exactly like `--effort`/`--permission-mode`). Threaded to BOTH the fresh ({@link
297
+ * createSessionEngine}) and resume ({@link spawnResumePty}) paths so the agent-selecting re-spawn
298
+ * carries it. The persona name is allowlist-safe at the catalog boundary ({@link discoverAgents}).
299
+ */
300
+ agent?: string;
229
301
  /**
230
302
  * Story 046 (R3.4 LIVE FIX): this resume is an IN-PLACE re-spawn ({@link respawnSession} for a
231
303
  * dontAsk/bypass mode or an effort change), NOT a fork/resume of an already-lived session. An
@@ -237,6 +309,28 @@ export interface StartEngineArgs {
237
309
  * absent) keeps its blocking 2000ms watchdog (R2.1, resume-discovery-unchanged.test.ts).
238
310
  */
239
311
  inPlaceRespawn?: boolean;
312
+ /**
313
+ * Story 057 (R1.3/R3.1): the RESOLVED additional-directory list (session `additionalDirectories`,
314
+ * else `_meta.additionalRoots`), forwarded to BOTH the fresh ({@link createSessionEngine}) and
315
+ * resume ({@link spawnResumePty}) spawn paths so a re-spawn re-threads it. Each safe entry becomes
316
+ * one `--add-dir "<dir>"` on the interactive TUI argv; sanitization (per-dir drop of unsafe paths)
317
+ * is the ENGINE's job ({@link buildAddDirFlags}/`isSafeDir`, sub-task 1.1), so the list threaded
318
+ * here is RAW. ALWAYS-ON (no `FORK_*` opt-in gate, R3.1). Interactive-only — never on a `-p`/
319
+ * `stream-json` invocation (the fork has no headless path).
320
+ */
321
+ additionalDirectories?: string[];
322
+ /**
323
+ * Story 057 (R2.2/R2.3, sub-task 2.3): the fork-controlled uuid-namespaced MCP scratch path
324
+ * ({@link writeMcpScratch}), forwarded to BOTH the fresh ({@link createSessionEngine}) and resume
325
+ * ({@link spawnResumePty}) spawn paths and emitted as `--mcp-config "<file>"` (never `--strict` —
326
+ * R2.2 MERGE: claude folds these servers IN alongside any project/user `.mcp.json` rather than
327
+ * replacing them). Written BEFORE the spawn — claude reads it at startup, exactly like
328
+ * {@link settingsFile}, so it gates the first MCP use. ALWAYS-ON (no `FORK_*` gate); present only
329
+ * when the session declared ≥1 MCP server. The scratch may carry MCP auth headers/env (0600 +
330
+ * never logged, R2.3); the caller removes it on teardown ({@link removeMcpScratch}) and regenerates
331
+ * it on re-spawn ({@link respawnSession}, R2.4).
332
+ */
333
+ mcpConfigFile?: string;
240
334
  }
241
335
  /** The createSession injection seam: spawn the PTY engine + JSONL watcher + locate the transcript. */
242
336
  export type StartEngine = (args: StartEngineArgs) => Promise<StartedEngine> | StartedEngine;
@@ -318,6 +412,32 @@ export interface AgentDeps {
318
412
  * cannot be overridden. Tests inject short windows here; production passes nothing.
319
413
  */
320
414
  gateOptions?: Omit<SessionGateOptions, "client" | "onWarn">;
415
+ /**
416
+ * Story 056 (R3.2) — override the main-thread agent-persona discovery `createSession` seeds the
417
+ * `agent` configOption from (default: the glob-only {@link discoverAgents}). Injected by the unit
418
+ * tests with an in-memory fake so the surface is exercised hermetically, never touching the real
419
+ * `~/.claude/agents`. Production passes nothing → the real disk glob.
420
+ */
421
+ discoverAgents?: (cwd: string) => AgentCatalogEntry[];
422
+ /**
423
+ * Story 063 (R1/R1.1) — override the offline command discovery `sendAvailableCommandsUpdate` sources
424
+ * the `available_commands_update` set from (default: the disk-only {@link discoverCommands}, keyed on
425
+ * the session cwd). Injected by the wiring test with an in-memory fake so the surface is exercised
426
+ * hermetically, never touching the real `~/.claude`. Production passes nothing → the real disk scan.
427
+ */
428
+ discoverCommands?: (cwd: string) => AvailableCommand[];
429
+ /**
430
+ * Story 056 (#812) — override the SDK session-metadata reader the end-of-turn `session_info_update`
431
+ * push sources the title from (default: the pure {@link getSessionInfo} from the agent SDK, which
432
+ * resolves the session's `summary` from its JSONL — custom title, auto-summary, or first prompt).
433
+ * Injected by the unit tests with an in-memory fake so the push is exercised hermetically, never
434
+ * touching the real `~/.claude` transcript tree. Production passes nothing → the real SDK reader.
435
+ */
436
+ getSessionInfo?: (sessionId: string, options?: {
437
+ dir?: string;
438
+ }) => Promise<{
439
+ summary: string;
440
+ } | undefined>;
321
441
  }
322
442
  /**
323
443
  * Production default for the {@link StartEngine} seam. Spawns the subscription `claude` TUI under a
@@ -451,6 +571,14 @@ export declare class ClaudeAcpAgent implements Agent {
451
571
  [key: string]: BackgroundTerminal;
452
572
  };
453
573
  clientCapabilities?: ClientCapabilities;
574
+ /**
575
+ * Story 065 (R1/R3) — did the client advertise `clientCapabilities.elicitation.form`
576
+ * at initialize? Presence-based (a present `form` may legitimately be an empty `{}`,
577
+ * so this is derived with `!= null`, NOT property truthiness). The 065 gate (task 3.1)
578
+ * reads this to decide relay-via-elicitation (R1) vs the story-064 deny fallback (R3).
579
+ * Defaults `false` so a client that never advertised elicitation falls back safely.
580
+ */
581
+ clientSupportsElicitationForm: boolean;
454
582
  logger: Logger;
455
583
  gatewayAuthRequest?: GatewayAuthRequest;
456
584
  engine: Engine;
@@ -476,6 +604,13 @@ export declare class ClaudeAcpAgent implements Agent {
476
604
  private readonly gateEnabled;
477
605
  /** Story 034 — gate tuning knobs forwarded to setupSessionGate; see {@link AgentDeps.gateOptions}. */
478
606
  private readonly gateOptions?;
607
+ /** Story 056 (R3.2) — main-thread agent-persona discovery seam; see {@link AgentDeps.discoverAgents}. */
608
+ private readonly discoverAgents;
609
+ /** Story 063 (R1/R1.1) — offline `available_commands` discovery seam; see {@link AgentDeps.discoverCommands}. */
610
+ private readonly discoverCommands;
611
+ /** Story 056 (#812) — SDK session-metadata reader for the end-of-turn title push; see
612
+ * {@link AgentDeps.getSessionInfo}. */
613
+ private readonly getSessionInfo;
479
614
  /** Live PTY-engine registry shared with the per-session engines (story 014 cleanup map). */
480
615
  private readonly engines;
481
616
  constructor(client: AgentSideConnection, logger?: Logger, engine?: Engine, deps?: AgentDeps);
@@ -486,7 +621,27 @@ export declare class ClaudeAcpAgent implements Agent {
486
621
  loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse>;
487
622
  listSessions(params: ListSessionsRequest): Promise<ListSessionsResponse>;
488
623
  authenticate(_params: AuthenticateRequest): Promise<void>;
624
+ /**
625
+ * ACP `logout` (acp-sdk 1.0.0, acp.d.ts:1646). Under the PTY engine the bridge
626
+ * authenticates lazily and only tracks an in-memory `gatewayAuthRequest`; the
627
+ * interactive `claude` TUI owns the on-disk credential lifecycle. So `logout`
628
+ * here drops the in-memory auth intent and re-offers a clean handshake on the
629
+ * next `initialize()` (authMethods are recomputed there, unconditioned by this
630
+ * field). It does NOT read/write/delete `~/.claude` (billing seam — story 062
631
+ * R2) and never bridges `/logout` to the PTY (R3). Idempotent with no prior
632
+ * authenticate() (R4); active sessions are untouched (R6).
633
+ */
634
+ logout(_params: LogoutRequest): Promise<LogoutResponse | void>;
489
635
  prompt(params: PromptRequest): Promise<PromptResponse>;
636
+ /**
637
+ * Story 056 (#812) — push the sanitized session title to the client via `session_info_update`,
638
+ * fired (fire-and-forget) by the story-024 end-of-turn boundary ONLY (never on cancel/watchdog,
639
+ * via {@link TurnResolverOptions.onTurnResolved}). DEDUPED against {@link Session.lastEmittedTitle}
640
+ * so an unchanged title is not re-emitted, and silent when `getSessionInfo` finds no transcript /
641
+ * the title is empty. Every error is swallowed and logged — this MUST NEVER reject the turn (it is
642
+ * never awaited in `prompt()`), and a slow/never-resolving reader cannot delay the PromptResponse.
643
+ */
644
+ private emitSessionTitleUpdate;
490
645
  cancel(params: CancelNotification): Promise<void>;
491
646
  /** Cleanly tear down a session: cancel in-flight work, dispose resources,
492
647
  * and remove it from the session map. */
@@ -494,7 +649,7 @@ export declare class ClaudeAcpAgent implements Agent {
494
649
  /** Tear down all active sessions. Called when the ACP connection closes. */
495
650
  dispose(): Promise<void>;
496
651
  closeSession(params: CloseSessionRequest): Promise<CloseSessionResponse>;
497
- unstable_deleteSession(params: DeleteSessionRequest): Promise<DeleteSessionResponse>;
652
+ deleteSession(params: DeleteSessionRequest): Promise<DeleteSessionResponse>;
498
653
  setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse>;
499
654
  setSessionConfigOption(params: SetSessionConfigOptionRequest): Promise<SetSessionConfigOptionResponse>;
500
655
  /**
@@ -559,19 +714,60 @@ export declare class ClaudeAcpAgent implements Agent {
559
714
  * Order is load-bearing for R3.7: re-spawn FIRST, and swap in + tear down the old PTY ONLY once the new
560
715
  * one is live — so a failed re-spawn leaves the prior PTY/currentValue intact (never
561
716
  * torn-down-without-replacement). Re-spawn runs only while idle, so the old PTY has no pending turn to
562
- * double-resolve. The `respawning` latch defers concurrent selector changes (R3.8).
717
+ * double-resolve. The `respawning` latch defers concurrent selector changes (R3.8). Re-spawning for
718
+ * ONE selector preserves the OTHER two (mode / effort / agent — Story 056 added agent) by reading
719
+ * their current values, so the resume argv always carries all three flags.
563
720
  */
564
721
  private respawnSession;
565
722
  /** Story 046 — the session's current effort configOption value (undefined when no effort option). */
566
723
  private currentEffort;
724
+ /** Story 056 — the session's current agent configOption value (undefined when no agent option). */
725
+ private currentAgent;
567
726
  /**
568
- * Story 046 (R2.2, design §7) — apply a reasoning-effort change. Probe B verdict: effort has no live
569
- * mid-session mechanism (`--effort` is a spawn flag), so a change re-spawns in place with the flag
570
- * (mirroring the dontAsk/bypass mode path), idle-guarded, with the R3.7 failure path and R3.8 latch.
571
- * A no-op change applies nothing. Throwing here leaves the caller's applyConfigOptionValue unrun, so
572
- * the prior currentValue is left unchanged on failure (R3.7).
727
+ * Story 060 (R2/R3.2) — apply an effort-selector choice, special-casing the `ultracode` sentinel.
728
+ *
729
+ * Selecting `ultracode` (Option A keyword + scratch, NO re-spawn): activate the session flag (which
730
+ * makes {@link prompt} prefix the OUTGOING prompt with the `ultracode` keyword — the binary's per-turn
731
+ * Workflow opt-in, the effective live mechanism), write the scratch ultracode keys via
732
+ * {@link applyUltracodeSettings} (the declarative spawn-time complement), and set the effort to xhigh
733
+ * through the SAME live `/effort` inject as every other level — NEVER `/effort ultracode` (R1.2). The
734
+ * `already` guard suppresses a redundant `/effort xhigh` re-inject when ultracode is re-selected while
735
+ * already active.
736
+ *
737
+ * Selecting a real level (or `default`) DEACTIVATES ultracode: clear the flag, remove the scratch keys,
738
+ * then apply that level through {@link applyEffortChange} (whose own no-op guard handles a same-level
739
+ * pick). `applyConfigOptionValue` (the caller, after this returns) commits the selector's currentValue,
740
+ * which for `ultracode` correctly stays `"ultracode"` (the {@link buildConfigOptions} `includes` guard
741
+ * keeps it valid across rebuilds).
742
+ */
743
+ private applyEffortSelection;
744
+ /**
745
+ * Story 046 (R2.2) + Story 056 v4 — apply a reasoning-effort change LIVE via `/effort <level>`.
746
+ * SUPERSEDES the 046 Probe-B re-spawn: `claude` 2.1.195 DOES have a live `/effort <level>` local TUI
747
+ * command (LIVE-VERIFIED — "Set effort level to high…", applied inline, NO "Switch?" dialog unlike
748
+ * /model). So effort now mirrors {@link applyModelSwitch}: a side-channel write, no re-spawn, no turn —
749
+ * which means it ALSO works BEFORE the first interaction (the re-spawn's --resume idle-guard was why
750
+ * effort silently failed pre-first-prompt). Mid-turn it defers (pendingEffortInjection) and flushes
751
+ * when the turn settles. A no-op change applies nothing; effort stays preserved across mode/agent
752
+ * re-spawns (currentEffort → --effort flag), so the spawn-flag path remains as the seed/preserve route.
573
753
  */
574
754
  private applyEffortChange;
755
+ /**
756
+ * Side-channel `/effort <level>` write — synchronous, resolves immediately (never a turn). Unlike
757
+ * `/model`, `/effort` applies INLINE with no blocking "Switch?" dialog (LIVE-VERIFIED 2.1.195), so
758
+ * sendPrompt's own submit `\r` is sufficient and NO confirm Enter is scheduled.
759
+ */
760
+ private injectEffortCommand;
761
+ /**
762
+ * Story 056 (R3.3/R3.4) — apply a main-thread agent-persona change. Like effort, the persona has no
763
+ * live mid-session mechanism (`--agent "<name>"` is a spawn flag), so a change re-spawns in place
764
+ * carrying the flag (mirroring {@link applyEffortChange}), idle-guarded, with the R3.7 failure path
765
+ * and the R3.8 latch. A no-op change (same persona, with the "default" sentinel as the no-persona
766
+ * baseline) applies nothing. Throwing here leaves the caller's applyConfigOptionValue unrun, so the
767
+ * prior currentValue is left unchanged on failure (R3.7). Optimistic-on-apply, like effort: there is
768
+ * no transcript drift event for the agent persona, so it is NOT reconciled afterward (R4.3).
769
+ */
770
+ private applyAgentChange;
575
771
  /**
576
772
  * Story 046 (R4.1/R4.2/R4.3, design §8) — reconcile the `mode` configOption from the latest
577
773
  * permission-mode event in the exactly-once `messages` slice. Emits current_mode_update EXACTLY ONCE
@@ -694,13 +890,23 @@ export declare const EMBEDDED_RESOURCE_INLINE_THRESHOLD = 2048;
694
890
  * anything below the threshold — or large but path-less — is
695
891
  * inlined directly so the context is not lost.
696
892
  *
697
- * `resource` (blob) / `image` / `audio` blocks are SILENT no-ops here (R4.1): they
698
- * emit no PTY bytes and are NOT logged they are expected-but-unsupported media in
699
- * v1, not errors. An UNKNOWN block `type` (the `default` branch) and any block whose
700
- * mapping THROWS are treated as malformed: skipped, recorded via the `logger`, and the
701
- * remaining valid blocks still map one bad block never aborts the whole prompt (R1.3).
893
+ * - image materialize the base64 to a uuid-named temp file (extension from mimeType)
894
+ * and emit `@<temp-path>` (Story 058 / R1.1). Once at least one image is
895
+ * materialized, a single Read-inducing directive is appended after the loop so
896
+ * the TUI's Read tool fires and vision-encodes it (R1.2). Each temp path is
897
+ * pushed into `materializedSink` (when provided) so the caller can clean it up.
898
+ *
899
+ * `resource` (blob) / `audio` blocks are SILENT no-ops here (R4.1): they emit no PTY bytes
900
+ * and are NOT logged — they are expected-but-unsupported media in v1, not errors. An UNKNOWN
901
+ * block `type` (the `default` branch) and any block whose mapping THROWS are treated as
902
+ * malformed: skipped, recorded via the `logger`, and the remaining valid blocks still map —
903
+ * one bad block never aborts the whole prompt (R1.3). A `materializeImage` failure is caught
904
+ * by that same per-block isolation, so a broken image is skipped, never aborting the prompt.
905
+ *
906
+ * `materializedSink`, when passed, receives every materialized temp path (in order) so the
907
+ * caller owns their lifecycle (cleanup is a later task). The return type stays `string`.
702
908
  */
703
- export declare function promptToClaude(prompt: PromptRequest, logger?: Logger): string;
909
+ export declare function promptToClaude(prompt: PromptRequest, logger?: Logger, materializedSink?: string[]): string;
704
910
  /**
705
911
  * Convert an SDKAssistantMessage (Claude) to a SessionNotification (ACP).
706
912
  * Only handles text, image, and thinking chunks for now.
@@ -721,5 +927,27 @@ export declare function runAcp(deps?: AgentDeps): {
721
927
  connection: AgentSideConnection;
722
928
  agent: ClaudeAcpAgent;
723
929
  };
930
+ /** Resolve a model alias's context window (the usage_update `size` denominator).
931
+ * NOTE (story 068): there is NO `result.modelUsage` window to refresh from — the
932
+ * JSONL `usage` carries only token counts; the window comes from static curation
933
+ * (the Models API `max_input_tokens` is the real authority, which this fork does
934
+ * not call), as detailed below.
935
+ *
936
+ * Story 068 (R1, R1.1, R1.2): consults the static {@link MODEL_CONTEXT_WINDOWS}
937
+ * alias→window map FIRST (an exact catalog-`value` hit — `opus`=1M, `sonnet`=200K,
938
+ * `sonnet[1m]`=1M, `haiku`=200K, `default`/`opusplan`=200K conservative). This
939
+ * fixes `opus` having wrongly reported 200K. An alias absent from the map then
940
+ * falls back to the legacy `\b1m\b` inference: Anthropic 1M-context variants
941
+ * encode "1m" as a distinct token in the SDK model ID (e.g., "claude-opus-4-6-1m"),
942
+ * which `\b1m\b` catches without also matching "10m" or embedded substrings.
943
+ * `null` (fully unknown) is intentional — the two call sites apply
944
+ * `?? DEFAULT_CONTEXT_WINDOW`. */
945
+ export declare function inferContextWindowFromModel(model: string): number | null;
946
+ /** Story 069 (R1) — AUTHORITATIVE context window from a turn's REAL model ID (the JSONL `model`
947
+ * field), used by the pump to refine the alias seed once the model is known. Exact-ID lookup first
948
+ * (MODEL_ID_CONTEXT_WINDOWS), then a family+version heuristic for dated snapshots / future variants
949
+ * (Opus is NOT uniform: 4.6 and earlier = 200K, 4.7+ = 1M; Sonnet 4.x = 200K but Sonnet 5+ = 1M;
950
+ * haiku = 200K; fable = 1M — story 071), then a
951
+ * `\b1m\b` suffix, then null (R1.3: a missing / non-string id never refines). */
952
+ export declare function inferContextWindowFromModelId(id: string): number | null;
724
953
  export {};
725
- //# sourceMappingURL=acp-agent.d.ts.map