@lucascouts/claude-agent-tui 0.2.1 → 0.3.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.
- package/README.md +26 -0
- package/dist/acp-agent.d.ts +126 -0
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +373 -21
- package/dist/engine-lifecycle.d.ts +20 -1
- package/dist/engine-lifecycle.d.ts.map +1 -1
- package/dist/engine-lifecycle.js +13 -3
- package/dist/engine-pty.d.ts +16 -6
- package/dist/engine-pty.d.ts.map +1 -1
- package/dist/engine-pty.js +16 -7
- package/dist/model-catalog.d.ts +11 -0
- package/dist/model-catalog.d.ts.map +1 -0
- package/dist/model-catalog.js +45 -0
- package/dist/permissions/gate-wiring.d.ts.map +1 -1
- package/dist/permissions/gate-wiring.js +14 -0
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -6,6 +6,16 @@ An [ACP](https://agentclientprotocol.com)-compatible agent that drives the **Cla
|
|
|
6
6
|
|
|
7
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.
|
|
8
8
|
|
|
9
|
+
## Why this exists
|
|
10
|
+
|
|
11
|
+
On **June 15, 2026**, [Anthropic split Claude subscription billing](https://zed.dev/blog/anthropic-subscription-changes) into two pools: Anthropic's **first-party tools** (chat and the official **Claude Code CLI**) keep using your **Pro/Max subscription**, while **third-party agents and SDK usage** are billed separately at **API rates** — roughly **15–30× more expensive** (separate monthly credits: $20 Pro / $100 Max 5x / $200 Max 20x).
|
|
12
|
+
|
|
13
|
+
In practice, Zed's built-in Claude integration now draws from those expensive third-party credits instead of your subscription. The official `claude` CLI/TUI, however, **still runs on your subscription**.
|
|
14
|
+
|
|
15
|
+
This project bridges the two: it drives the **official Claude Code TUI** over a PTY and exposes it through the **ACP** protocol, so Zed renders it as a native agent panel — giving you the full Claude Code experience inside Zed **on your existing subscription**, not API credits.
|
|
16
|
+
|
|
17
|
+
> Not affiliated with or endorsed by Anthropic or Zed Industries. Use of the `claude` CLI remains subject to Anthropic's terms.
|
|
18
|
+
|
|
9
19
|
## Requirements
|
|
10
20
|
|
|
11
21
|
- The **`claude` CLI** (Claude Code subscription) available on your `PATH`.
|
|
@@ -45,6 +55,22 @@ npm test
|
|
|
45
55
|
- Token-usage updates
|
|
46
56
|
- Prompt input, cancellation, and session load/replay
|
|
47
57
|
|
|
58
|
+
## Supply-chain notes
|
|
59
|
+
|
|
60
|
+
[Socket](https://socket.dev/npm/package/@lucascouts/claude-agent-tui) flags some
|
|
61
|
+
dependency alerts (`Native code`, `Install scripts`, `Shell access`, plus
|
|
62
|
+
heuristic flags on the Anthropic SDK tree). **None are CVEs or malware** — they
|
|
63
|
+
are expected capability flags:
|
|
64
|
+
|
|
65
|
+
- `node-pty` (the same PTY that powers VS Code) needs `Native code` /
|
|
66
|
+
`Install scripts` / `Shell access` — driving the `claude` TUI in a real PTY is
|
|
67
|
+
the whole point of this bridge.
|
|
68
|
+
- The remaining flags come from the minified bundles inside
|
|
69
|
+
`@anthropic-ai/claude-agent-sdk`, of which this fork uses only two pure,
|
|
70
|
+
billing-free functions (`getSessionMessages`, `resolveSettings`).
|
|
71
|
+
|
|
72
|
+
All direct dependencies are first-party publishers (Microsoft, Anthropic, Zed).
|
|
73
|
+
|
|
48
74
|
## License
|
|
49
75
|
|
|
50
76
|
[Apache-2.0](LICENSE). This is a derivative work; original copyright belongs to Zed Industries and the Agent Client Protocol authors.
|
package/dist/acp-agent.d.ts
CHANGED
|
@@ -97,6 +97,28 @@ type Session = {
|
|
|
97
97
|
* once and a prior turn's terminal boundary is never re-observed.
|
|
98
98
|
*/
|
|
99
99
|
detectorCursor?: number;
|
|
100
|
+
/**
|
|
101
|
+
* Story 046 (R1.3, design §5/§9) — a model-switch (`/model <alias>`) requested WHILE a turn is in
|
|
102
|
+
* flight is deferred here (last-write-wins, §9 coalescing) and flushed as a side-channel PTY write
|
|
103
|
+
* once the turn settles (prompt()'s finally → {@link flushPendingControlInjections}). Undefined when
|
|
104
|
+
* nothing is queued.
|
|
105
|
+
*/
|
|
106
|
+
pendingModelInjection?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Story 046 (R3.8) — true WHILE an in-place re-spawn (R3.4 dontAsk/bypass switch) is between the old
|
|
109
|
+
* PTY teardown and the new PTY being ready. Selector changes arriving in this window are rejected
|
|
110
|
+
* rather than written to a dead PTY.
|
|
111
|
+
*/
|
|
112
|
+
respawning?: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Story 046 (R3.4 LIVE FIX) — true once the session's transcript has materialised (the watcher armed
|
|
115
|
+
* and pumped at least once, i.e. the FIRST interaction happened). An in-place re-spawn reattaches via
|
|
116
|
+
* `claude --resume <id>`, which needs that transcript to exist; before it, --resume falls back
|
|
117
|
+
* (buildResumeArgv `|| claude`) to a NEW untracked id and stalls. {@link respawnSession} refuses while
|
|
118
|
+
* this is falsy — a boot-time default_config_options dontAsk/bypass stays at the fresh spawn's mode
|
|
119
|
+
* until the user sends the first prompt.
|
|
120
|
+
*/
|
|
121
|
+
interacted?: boolean;
|
|
100
122
|
/**
|
|
101
123
|
* Story 034 (§9 / R3.3) — the per-session HYBRID permission-gate runtime: loopback `PreToolUse`
|
|
102
124
|
* hook server + scratch `--settings` backup + `tool_use.id` correlator. Present only when the
|
|
@@ -186,6 +208,28 @@ export interface StartEngineArgs {
|
|
|
186
208
|
* (`buildResumeArgv`) is NOT extended here; the replay-only load path spawns nothing.
|
|
187
209
|
*/
|
|
188
210
|
settingsFile?: string;
|
|
211
|
+
/**
|
|
212
|
+
* Story 046 (R3.2, choose-before-start): the seeded permission mode, forwarded to the spawn as
|
|
213
|
+
* `--permission-mode <mode>` (non-"default" only). Threaded to BOTH the fresh ({@link
|
|
214
|
+
* createSessionEngine}) and resume ({@link spawnResumePty}) paths so the R3.4 re-spawn carries it too.
|
|
215
|
+
*/
|
|
216
|
+
permissionMode?: string;
|
|
217
|
+
/**
|
|
218
|
+
* Story 046 (R2.2): the seeded/re-spawn reasoning effort, forwarded to the spawn as `--effort
|
|
219
|
+
* <level>` (non-"default" only). Threaded to BOTH the fresh and resume spawn paths.
|
|
220
|
+
*/
|
|
221
|
+
effortLevel?: string;
|
|
222
|
+
/**
|
|
223
|
+
* Story 046 (R3.4 LIVE FIX): this resume is an IN-PLACE re-spawn ({@link respawnSession} for a
|
|
224
|
+
* dontAsk/bypass mode or an effort change), NOT a fork/resume of an already-lived session. An
|
|
225
|
+
* in-place re-spawn can fire BEFORE the first interaction (e.g. a boot-time `bypassPermissions`
|
|
226
|
+
* switch driven by Zed's `default_config_options`), when the re-spawned `claude` has NOT written its
|
|
227
|
+
* transcript yet. So this branch DEFERS discovery like the fresh path (`watchdogMs: Infinity`,
|
|
228
|
+
* arm-on-appearance) instead of the resume default's 2000ms FATAL watchdog — which would otherwise
|
|
229
|
+
* throw not-found and stall the next turn until the 120s turn watchdog. The fork/resume path (flag
|
|
230
|
+
* absent) keeps its blocking 2000ms watchdog (R2.1, resume-discovery-unchanged.test.ts).
|
|
231
|
+
*/
|
|
232
|
+
inPlaceRespawn?: boolean;
|
|
189
233
|
}
|
|
190
234
|
/** The createSession injection seam: spawn the PTY engine + JSONL watcher + locate the transcript. */
|
|
191
235
|
export type StartEngine = (args: StartEngineArgs) => Promise<StartedEngine> | StartedEngine;
|
|
@@ -446,7 +490,89 @@ export declare class ClaudeAcpAgent implements Agent {
|
|
|
446
490
|
unstable_deleteSession(params: DeleteSessionRequest): Promise<DeleteSessionResponse>;
|
|
447
491
|
setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse>;
|
|
448
492
|
setSessionConfigOption(params: SetSessionConfigOptionRequest): Promise<SetSessionConfigOptionResponse>;
|
|
493
|
+
/**
|
|
494
|
+
* Story 046 (R3, Bug A fix) — drive a permission-mode change INTO the claude TUI (the load-bearing
|
|
495
|
+
* half). Cyclable modes (default/acceptEdits/plan/auto) drive via closed-loop Shift+Tab; dontAsk/
|
|
496
|
+
* bypassPermissions re-spawn with `--permission-mode`. Idle-guarded (R3.8); a no-op change applies
|
|
497
|
+
* nothing. SHARED by setSessionMode AND setSessionConfigOption(configId:"mode") — Zed sends mode
|
|
498
|
+
* changes via the latter, so the driving MUST live on both paths (it previously lived only on
|
|
499
|
+
* setSessionMode, while the config-option path was read-only → claude stuck on its spawn mode). The
|
|
500
|
+
* caller has already validated `target` via {@link applySessionMode}.
|
|
501
|
+
*/
|
|
502
|
+
private driveModeIntoTui;
|
|
449
503
|
private applySessionMode;
|
|
504
|
+
/**
|
|
505
|
+
* Story 046 (R1.2–R1.4, design §5) — apply a live model switch by injecting `/model <alias>` into
|
|
506
|
+
* the PTY as a SIDE-CHANNEL write. `/model` is a local TUI command: no assistant turn, no
|
|
507
|
+
* stop_reason — so it is NEVER routed through prompt()/createTurnResolver (that would hang) and
|
|
508
|
+
* never sets turnDetector (R1.4). The write goes through sendPrompt for its Ctrl+U input-clear but
|
|
509
|
+
* with a SYNCHRONOUS schedule so the command + `\r` commit immediately (resolves now; it awaits no
|
|
510
|
+
* turn). Idle-guard: inject only when no turn is in flight; otherwise defer (last-write-wins, §9)
|
|
511
|
+
* and flush when the turn settles (R1.3).
|
|
512
|
+
*/
|
|
513
|
+
private applyModelSwitch;
|
|
514
|
+
/**
|
|
515
|
+
* Side-channel `/model <alias>` write — synchronous, resolves immediately (never a turn). claude
|
|
516
|
+
* 2.1.176 (GrowthBook `tengu_immediate_model_command=false`) does NOT apply `/model` inline mid-
|
|
517
|
+
* conversation: it opens a blocking **"Switch model?" → 1. Yes / 2. No** dialog and leaves it OPEN.
|
|
518
|
+
* Unconfirmed, the dialog survives until the NEXT prompt — whose `\r` then confirms the switch AND
|
|
519
|
+
* discards the prompt text, so no turn is born and the story-024 stall watchdog trips (the live
|
|
520
|
+
* 39a93bfc hang; root cause proved headless by experiments/probe-c-model-then-prompt.mjs). So after
|
|
521
|
+
* the command we schedule ONE Enter to accept the default "Yes, switch" once the dialog has rendered;
|
|
522
|
+
* if no dialog appears (same model / flag flipped on) Enter-on-empty-input is a harmless no-op.
|
|
523
|
+
*/
|
|
524
|
+
private injectModelCommand;
|
|
525
|
+
/**
|
|
526
|
+
* Story 046 (R1.3) — flush a deferred model-switch once the session is idle again (called from
|
|
527
|
+
* prompt()'s finally, the moment the turn settles). Last-write-wins: only the most recent queued
|
|
528
|
+
* alias is injected; the field is cleared so a settled session with nothing queued injects nothing.
|
|
529
|
+
*/
|
|
530
|
+
private flushPendingControlInjections;
|
|
531
|
+
/**
|
|
532
|
+
* Story 046 (R3.3, design §6b) — drive the TUI to `target` with closed-loop raw Shift+Tab. Writes
|
|
533
|
+
* `\x1b[Z` DIRECTLY to the PTY (NOT sendPrompt — its Ctrl+U clear corrupts the escape), then awaits
|
|
534
|
+
* the confirming `permission-mode` transcript event (the tail-as-truth fence: mode is read from the
|
|
535
|
+
* transcript, NEVER from p.onData). A per-step Δt budget + a one-full-cycle safety stop guarantee the
|
|
536
|
+
* loop ABORTS rather than hangs/false-stalls (Probe A gated this; story-044 awareness).
|
|
537
|
+
*/
|
|
538
|
+
private driveCyclableMode;
|
|
539
|
+
/**
|
|
540
|
+
* Story 046 (R3.3) — poll the transcript (the pump's getMessages seam) for a `permission-mode` event
|
|
541
|
+
* whose mode differs from `before`, up to MODE_CYCLE_STEP_TIMEOUT_MS. Returns the new mode, or
|
|
542
|
+
* undefined on the Δt timeout (the caller aborts rather than hangs). The confirming event is usually
|
|
543
|
+
* already present right after the TUI processes the keystroke, so this returns at once in tests.
|
|
544
|
+
*/
|
|
545
|
+
private awaitModeChange;
|
|
546
|
+
/** Story 046 (R3.3/R4.1) — the most recent `permission-mode` event's mode from the transcript (the
|
|
547
|
+
* same getMessages seam the pump reads), or undefined when none is present yet. */
|
|
548
|
+
private readLatestPermissionMode;
|
|
549
|
+
/**
|
|
550
|
+
* Story 046 (R3.4/R3.7/R3.8, design §6c) — apply a non-cyclable mode (dontAsk/bypassPermissions) by
|
|
551
|
+
* re-spawning the SAME sessionId in place with a flag-carrying resume argv, preserving the transcript.
|
|
552
|
+
* Order is load-bearing for R3.7: re-spawn FIRST, and swap in + tear down the old PTY ONLY once the new
|
|
553
|
+
* one is live — so a failed re-spawn leaves the prior PTY/currentValue intact (never
|
|
554
|
+
* torn-down-without-replacement). Re-spawn runs only while idle, so the old PTY has no pending turn to
|
|
555
|
+
* double-resolve. The `respawning` latch defers concurrent selector changes (R3.8).
|
|
556
|
+
*/
|
|
557
|
+
private respawnSession;
|
|
558
|
+
/** Story 046 — the session's current effort configOption value (undefined when no effort option). */
|
|
559
|
+
private currentEffort;
|
|
560
|
+
/**
|
|
561
|
+
* Story 046 (R2.2, design §7) — apply a reasoning-effort change. Probe B verdict: effort has no live
|
|
562
|
+
* mid-session mechanism (`--effort` is a spawn flag), so a change re-spawns in place with the flag
|
|
563
|
+
* (mirroring the dontAsk/bypass mode path), idle-guarded, with the R3.7 failure path and R3.8 latch.
|
|
564
|
+
* A no-op change applies nothing. Throwing here leaves the caller's applyConfigOptionValue unrun, so
|
|
565
|
+
* the prior currentValue is left unchanged on failure (R3.7).
|
|
566
|
+
*/
|
|
567
|
+
private applyEffortChange;
|
|
568
|
+
/**
|
|
569
|
+
* Story 046 (R4.1/R4.2/R4.3, design §8) — reconcile the `mode` configOption from the latest
|
|
570
|
+
* permission-mode event in the exactly-once `messages` slice. Emits current_mode_update EXACTLY ONCE
|
|
571
|
+
* and only when the transcript's mode differs from the advertised currentModeId (no spurious emit when
|
|
572
|
+
* already in sync, or when no permission-mode event is present). Model/effort have no transcript drift
|
|
573
|
+
* event, so they are NOT reconciled here — optimistic-on-apply only (R4.3).
|
|
574
|
+
*/
|
|
575
|
+
private reconcileModeFromTranscript;
|
|
450
576
|
private replaySessionHistory;
|
|
451
577
|
/**
|
|
452
578
|
* Shared per-turn ACP emission used by BOTH the `session/load` replay ({@link replaySessionHistory})
|
package/dist/acp-agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"acp-agent.d.ts","sourceRoot":"","sources":["../src/acp-agent.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,mBAAmB,EACnB,mBAAmB,EAEnB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EAEnB,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EAEpB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACnB,6BAA6B,EAC7B,8BAA8B,EAC9B,qBAAqB,EACrB,sBAAsB,EAEtB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAGL,SAAS,EACT,OAAO,EACP,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,0BAA0B,EAC3B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAKlG,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAOL,SAAS,EAKV,MAAM,YAAY,CAAC;AAKpB,OAAO,EAAE,KAAK,MAAM,EAAoB,MAAM,aAAa,CAAC;AAM5D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAuC,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGhD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAQ3E,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAM7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"acp-agent.d.ts","sourceRoot":"","sources":["../src/acp-agent.ts"],"names":[],"mappings":"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"}
|
package/dist/acp-agent.js
CHANGED
|
@@ -9,7 +9,7 @@ import { applyTaskCreate, applyTaskUpdate, parseTaskCreateOutput, planEntries, r
|
|
|
9
9
|
import { nodeToWebReadable, nodeToWebWritable, unreachable } from "./utils.js";
|
|
10
10
|
// === SEAM(011): engine boundary — inject a temporary no-op engine so the agent
|
|
11
11
|
// boots without the cut SDK query() path; the real PTY engine arrives in 013–015/023.
|
|
12
|
-
// See SEAM-MAP.md (createSession/prompt CUT→023) and src/engine.ts. ===
|
|
12
|
+
// See fork/SEAM-MAP.md (createSession/prompt CUT→023) and src/engine.ts. ===
|
|
13
13
|
import { createStubEngine } from "./engine.js";
|
|
14
14
|
import { createSessionEngine, spawnResumePty, SessionEngine } from "./engine-lifecycle.js";
|
|
15
15
|
import { createJsonlWatcher } from "./engine-watcher.js";
|
|
@@ -23,6 +23,7 @@ import { guardEvent } from "./billing/entrypoint-guard.js";
|
|
|
23
23
|
import { usageUpdatesFor } from "./usage.js";
|
|
24
24
|
import { createTurnResolver } from "./end-of-turn.js";
|
|
25
25
|
import { sendPrompt } from "./engine-pty.js";
|
|
26
|
+
import { MODEL_CATALOG, DEFAULT_MODEL_INFO } from "./model-catalog.js";
|
|
26
27
|
import { setupSessionGate } from "./permissions/gate-wiring.js";
|
|
27
28
|
export const CLAUDE_CONFIG_DIR = process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
|
|
28
29
|
const MAX_TITLE_LENGTH = 256;
|
|
@@ -92,7 +93,55 @@ export async function defaultStartEngine(args) {
|
|
|
92
93
|
cwd: args.cwd,
|
|
93
94
|
baseEnv: args.baseEnv,
|
|
94
95
|
spawn: args.spawn,
|
|
96
|
+
// Story 046 (R3.4/R2.2): the in-place re-spawn (dontAsk/bypass or an effort change) reattaches the
|
|
97
|
+
// SAME sessionId carrying its mode/effort flags through the resume argv (buildResumeArgv).
|
|
98
|
+
permissionMode: args.permissionMode,
|
|
99
|
+
effortLevel: args.effortLevel,
|
|
95
100
|
});
|
|
101
|
+
if (args.inPlaceRespawn) {
|
|
102
|
+
// === SEAM(046 R3.4 LIVE FIX): DEFER discovery for an in-place re-spawn ======================
|
|
103
|
+
// An in-place re-spawn (respawnSession: dontAsk/bypass or effort) can fire BEFORE the first
|
|
104
|
+
// interaction — e.g. Zed sends set_config_option(mode:bypassPermissions) at boot from
|
|
105
|
+
// default_config_options, so the re-spawned `claude` has not written its transcript yet. The
|
|
106
|
+
// blocking 2000ms watchdog below would then throw not-found, fail the re-spawn, and the next
|
|
107
|
+
// turn would stall until the 120s turn watchdog. Mirror the fresh path: return as soon as the
|
|
108
|
+
// PTY is live and discover in the BACKGROUND under watchdogMs:Infinity (cancellable), arming the
|
|
109
|
+
// watcher + firing the first onEvent only when the transcript APPEARS (the first interaction).
|
|
110
|
+
const engine = new SessionEngine({ handle, watcher: undefined, sessions: args.sessions });
|
|
111
|
+
const ac = new AbortController();
|
|
112
|
+
engine.setPendingDiscovery(ac);
|
|
113
|
+
void (async () => {
|
|
114
|
+
try {
|
|
115
|
+
const { transcriptPath, cwd } = await resolveWatchTarget(args.sessionId, {
|
|
116
|
+
watchdogMs: Infinity,
|
|
117
|
+
...args.locateOptions,
|
|
118
|
+
signal: ac.signal,
|
|
119
|
+
});
|
|
120
|
+
const watcher = createJsonlWatcher({
|
|
121
|
+
sessionId: args.sessionId,
|
|
122
|
+
transcriptPath,
|
|
123
|
+
dir: cwd ?? args.cwd,
|
|
124
|
+
onEvent: () => args.onEvent?.(args.sessionId),
|
|
125
|
+
});
|
|
126
|
+
engine.watcher = watcher;
|
|
127
|
+
args.onEvent?.(args.sessionId);
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
// Swallow ONLY the abort sentinel (the session was torn down before any interaction).
|
|
131
|
+
// SURFACE everything else (multi-match ambiguity, IO error) — never silently drop it.
|
|
132
|
+
if (err?.name === "AbortError")
|
|
133
|
+
return;
|
|
134
|
+
console.error(`[acp-agent] in-place re-spawn transcript discovery failed for ${args.sessionId}:`, err);
|
|
135
|
+
}
|
|
136
|
+
})();
|
|
137
|
+
return {
|
|
138
|
+
sessionId: args.sessionId,
|
|
139
|
+
pty: handle.pty,
|
|
140
|
+
watcher: undefined,
|
|
141
|
+
engine,
|
|
142
|
+
cwd: args.cwd,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
96
145
|
const { transcriptPath, cwd } = await resolveWatchTarget(args.sessionId, {
|
|
97
146
|
...args.locateOptions,
|
|
98
147
|
});
|
|
@@ -119,6 +168,10 @@ export async function defaultStartEngine(args) {
|
|
|
119
168
|
baseEnv: args.baseEnv,
|
|
120
169
|
sessions: args.sessions,
|
|
121
170
|
spawn: args.spawn,
|
|
171
|
+
// Story 046 (R3.2/R2.2): the seeded permission mode + effort → `--permission-mode`/`--effort` on
|
|
172
|
+
// the fresh spawn (non-"default" only; "default"/undefined keep the byte-for-byte pre-046 argv).
|
|
173
|
+
permissionMode: args.permissionMode,
|
|
174
|
+
effortLevel: args.effortLevel,
|
|
122
175
|
// Story 034 (§9): the per-session gate scratch settings, already on disk — claude reads them at
|
|
123
176
|
// startup, so the hook gates the FIRST tool call (blocker c). Absent → ungated (pre-034) spawn.
|
|
124
177
|
settingsFile: args.settingsFile,
|
|
@@ -174,13 +227,6 @@ export async function defaultStartEngine(args) {
|
|
|
174
227
|
cwd: args.cwd,
|
|
175
228
|
};
|
|
176
229
|
}
|
|
177
|
-
/** A single default Degrau-1 model entry. The TUI owns real model selection in Degrau-1; this is an
|
|
178
|
-
* honest non-interactive default so configOptions/modes have a coherent current model to anchor on. */
|
|
179
|
-
const DEGRAU1_DEFAULT_MODEL_INFO = {
|
|
180
|
-
value: "default",
|
|
181
|
-
displayName: "Default",
|
|
182
|
-
description: "Default model (selection is owned by the interactive TUI in Degrau-1)",
|
|
183
|
-
};
|
|
184
230
|
/** Compute a stable fingerprint of the session-defining params so we can
|
|
185
231
|
* detect when a loadSession/resumeSession call requires tearing down and
|
|
186
232
|
* recreating the underlying Query process. MCP servers are sorted by name
|
|
@@ -194,7 +240,7 @@ function computeSessionFingerprint(params) {
|
|
|
194
240
|
// spawns the subscription `claude` through the login shell (`bash -lc 'claude …'`), so it resolves
|
|
195
241
|
// from PATH — the same E1 keystone (experiments/DEGRAU0-RESULTS.md), via the shell rather than an
|
|
196
242
|
// explicit resolveClaudePath() call here. resolveClaudePath() (story 012) is retained for the
|
|
197
|
-
// `--cli` auth spawn in index.ts. See src/claude-path.ts, SEAM-MAP.md, IMPLEMENTACAO §3/§5. ===
|
|
243
|
+
// `--cli` auth spawn in index.ts. See fork/src/claude-path.ts, fork/SEAM-MAP.md, IMPLEMENTACAO §3/§5. ===
|
|
198
244
|
function shouldHideClaudeAuth() {
|
|
199
245
|
return process.argv.includes("--hide-claude-auth");
|
|
200
246
|
}
|
|
@@ -282,6 +328,25 @@ export function resolvePermissionMode(defaultMode, logger = console) {
|
|
|
282
328
|
}
|
|
283
329
|
return mapped;
|
|
284
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* Story 046 (R3.3): the permission modes the TUI cycles through with Shift+Tab (`\x1b[Z`), reachable
|
|
333
|
+
* by the closed-loop driver by stepping. `dontAsk`/`bypassPermissions` are NOT on this cycle — they
|
|
334
|
+
* are applied by an in-place re-spawn instead (R3.4).
|
|
335
|
+
*/
|
|
336
|
+
const CYCLABLE_MODES = new Set(["default", "acceptEdits", "plan", "auto"]);
|
|
337
|
+
/** Story 046 (R3.3): the raw Shift+Tab bytes that cycle the TUI permission mode. Written DIRECTLY to
|
|
338
|
+
* the PTY — NEVER via sendPrompt, whose leading Ctrl+U clear would corrupt the escape (design GOTCHA). */
|
|
339
|
+
const MODE_CYCLE_KEY = "\x1b[Z";
|
|
340
|
+
/** Story 046 (R3.3): per-step budget for the closed-loop to see the confirming permission-mode
|
|
341
|
+
* transcript event before aborting (no hang, story-044 awareness); polled every MODE_CYCLE_POLL_MS. */
|
|
342
|
+
const MODE_CYCLE_STEP_TIMEOUT_MS = 2000;
|
|
343
|
+
const MODE_CYCLE_POLL_MS = 50;
|
|
344
|
+
/** Story 046 (hang fix): claude 2.1.176 opens a blocking "Switch model?" confirm dialog on a mid-
|
|
345
|
+
* conversation `/model <alias>` (GrowthBook `tengu_immediate_model_command=false`). Delay before the
|
|
346
|
+
* blind confirm Enter so the dialog has rendered first; the dialog stays open until confirmed, so a
|
|
347
|
+
* late Enter still lands while an early (pre-render) one would be lost. 800 ms validated headless
|
|
348
|
+
* (experiments/probe-c-model-then-prompt.mjs: the dialog rendered + the switch applied within it). */
|
|
349
|
+
const MODEL_CONFIRM_DELAY_MS = 800;
|
|
285
350
|
/**
|
|
286
351
|
* Builds the label for the "Always Allow" permission option so the user can see
|
|
287
352
|
* the exact scope they are committing to. Uses the SDK-provided suggestions
|
|
@@ -644,6 +709,8 @@ export class ClaudeAcpAgent {
|
|
|
644
709
|
// so the in-turn sub-agent watcher dies with it (covers turn-resolve AND markCancelled paths).
|
|
645
710
|
sessionRecord.subagentWatcher?.stop();
|
|
646
711
|
sessionRecord.subagentWatcher = undefined;
|
|
712
|
+
// Story 046 (R1.3): the session is idle again — flush any model switch deferred mid-turn.
|
|
713
|
+
this.flushPendingControlInjections(sessionRecord);
|
|
647
714
|
}
|
|
648
715
|
}
|
|
649
716
|
async cancel(params) {
|
|
@@ -741,10 +808,15 @@ export class ClaudeAcpAgent {
|
|
|
741
808
|
return {};
|
|
742
809
|
}
|
|
743
810
|
async setSessionMode(params) {
|
|
744
|
-
|
|
811
|
+
const session = this.sessions[params.sessionId];
|
|
812
|
+
if (!session) {
|
|
745
813
|
throw new Error("Session not found");
|
|
746
814
|
}
|
|
815
|
+
// Validate the requested mode against the session's availableModes (throws on an unknown/unavailable
|
|
816
|
+
// mode — preserved). No state change here (Degrau-1 shim); the drive/re-spawn below applies it.
|
|
747
817
|
await this.applySessionMode(params.sessionId, params.modeId);
|
|
818
|
+
// Drive the validated mode INTO the claude TUI (shared with the set_config_option path, Bug A fix).
|
|
819
|
+
await this.driveModeIntoTui(params.sessionId, session, params.modeId);
|
|
748
820
|
await this.updateConfigOption(params.sessionId, "mode", params.modeId);
|
|
749
821
|
return {};
|
|
750
822
|
}
|
|
@@ -786,6 +858,10 @@ export class ClaudeAcpAgent {
|
|
|
786
858
|
const resolvedValue = validValue.value;
|
|
787
859
|
if (params.configId === "mode") {
|
|
788
860
|
await this.applySessionMode(params.sessionId, resolvedValue);
|
|
861
|
+
// Bug A fix (Story 046 R3) — Zed sends mode changes via set_config_option(configId:"mode"), so
|
|
862
|
+
// the DRIVE must happen HERE too. This path used to only validate (read-only), leaving claude
|
|
863
|
+
// stuck on its spawn mode — the live permission mode never changed (bypass/acceptEdits no-op'd).
|
|
864
|
+
await this.driveModeIntoTui(params.sessionId, session, resolvedValue);
|
|
789
865
|
await this.client.sessionUpdate({
|
|
790
866
|
sessionId: params.sessionId,
|
|
791
867
|
update: {
|
|
@@ -794,12 +870,51 @@ export class ClaudeAcpAgent {
|
|
|
794
870
|
},
|
|
795
871
|
});
|
|
796
872
|
}
|
|
797
|
-
// === SEAM(023) Group 1: the
|
|
798
|
-
//
|
|
799
|
-
//
|
|
873
|
+
// === SEAM(023→046) Group 1: the dropped SDK `query.setModel` is replaced by a PTY side-channel.
|
|
874
|
+
// `/model <alias>` is a LOCAL TUI command (no assistant turn, no stop_reason) — inject it as a
|
|
875
|
+
// write that resolves immediately; NEVER route it through prompt()/the turn-resolver (it would
|
|
876
|
+
// hang forever) and never set turnDetector (R1.4). Idle-guard on turnDetector === undefined
|
|
877
|
+
// (design §5): mid-turn, defer behind pendingModelInjection and flush when the turn settles (R1.3).
|
|
878
|
+
// resolvedValue is already the canonical catalog alias. ===
|
|
879
|
+
if (params.configId === "model") {
|
|
880
|
+
this.applyModelSwitch(session, resolvedValue);
|
|
881
|
+
}
|
|
882
|
+
else if (params.configId === "effort") {
|
|
883
|
+
// Story 046 (R2.2): Probe B verdict — effort has no live mid-session path, so an effort change
|
|
884
|
+
// re-spawns in place with `--effort <level>` (idle-guarded, R3.7 failure path). On throw, the
|
|
885
|
+
// applyConfigOptionValue below is skipped so the prior currentValue stays unchanged.
|
|
886
|
+
await this.applyEffortChange(params.sessionId, session, resolvedValue);
|
|
887
|
+
}
|
|
800
888
|
await this.applyConfigOptionValue(params.sessionId, session, params.configId, resolvedValue);
|
|
801
889
|
return { configOptions: session.configOptions };
|
|
802
890
|
}
|
|
891
|
+
/**
|
|
892
|
+
* Story 046 (R3, Bug A fix) — drive a permission-mode change INTO the claude TUI (the load-bearing
|
|
893
|
+
* half). Cyclable modes (default/acceptEdits/plan/auto) drive via closed-loop Shift+Tab; dontAsk/
|
|
894
|
+
* bypassPermissions re-spawn with `--permission-mode`. Idle-guarded (R3.8); a no-op change applies
|
|
895
|
+
* nothing. SHARED by setSessionMode AND setSessionConfigOption(configId:"mode") — Zed sends mode
|
|
896
|
+
* changes via the latter, so the driving MUST live on both paths (it previously lived only on
|
|
897
|
+
* setSessionMode, while the config-option path was read-only → claude stuck on its spawn mode). The
|
|
898
|
+
* caller has already validated `target` via {@link applySessionMode}.
|
|
899
|
+
*/
|
|
900
|
+
async driveModeIntoTui(sessionId, session, target) {
|
|
901
|
+
if (target === session.modes.currentModeId)
|
|
902
|
+
return; // no-op change applies nothing to the TUI
|
|
903
|
+
// Idle-guard (design §9 / R3.8): driving/re-spawning is mutually exclusive with a turn in flight
|
|
904
|
+
// (incl. the story-031 cancel ladder, observed as a live turnDetector) and with a re-spawn already
|
|
905
|
+
// underway. Reject rather than write to a busy/dead PTY — the user retries when idle.
|
|
906
|
+
if (session.turnDetector !== undefined || session.respawning) {
|
|
907
|
+
throw new Error("Cannot change permission mode while the session is busy (a turn is in flight or a re-spawn is underway); retry when idle");
|
|
908
|
+
}
|
|
909
|
+
if (CYCLABLE_MODES.has(target)) {
|
|
910
|
+
// R3.3: drive the TUI with closed-loop raw Shift+Tab until the transcript confirms `target`.
|
|
911
|
+
await this.driveCyclableMode(sessionId, session, target);
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
// R3.4: dontAsk/bypassPermissions are not on the Shift+Tab cycle — re-spawn in place.
|
|
915
|
+
await this.respawnSession(sessionId, session, { permissionMode: target });
|
|
916
|
+
}
|
|
917
|
+
}
|
|
803
918
|
async applySessionMode(sessionId, modeId) {
|
|
804
919
|
switch (modeId) {
|
|
805
920
|
case "auto":
|
|
@@ -824,6 +939,214 @@ export class ClaudeAcpAgent {
|
|
|
824
939
|
// emitted by the caller. No SDK `query.setPermissionMode`.
|
|
825
940
|
// Degrau 2 (030/032): PTY-backed control — drive the TUI to apply the permission mode. ===
|
|
826
941
|
}
|
|
942
|
+
/**
|
|
943
|
+
* Story 046 (R1.2–R1.4, design §5) — apply a live model switch by injecting `/model <alias>` into
|
|
944
|
+
* the PTY as a SIDE-CHANNEL write. `/model` is a local TUI command: no assistant turn, no
|
|
945
|
+
* stop_reason — so it is NEVER routed through prompt()/createTurnResolver (that would hang) and
|
|
946
|
+
* never sets turnDetector (R1.4). The write goes through sendPrompt for its Ctrl+U input-clear but
|
|
947
|
+
* with a SYNCHRONOUS schedule so the command + `\r` commit immediately (resolves now; it awaits no
|
|
948
|
+
* turn). Idle-guard: inject only when no turn is in flight; otherwise defer (last-write-wins, §9)
|
|
949
|
+
* and flush when the turn settles (R1.3).
|
|
950
|
+
*/
|
|
951
|
+
applyModelSwitch(session, alias) {
|
|
952
|
+
if (session.turnDetector !== undefined) {
|
|
953
|
+
// A turn is in flight — injecting mid-turn corrupts the PTY input. Defer (coalesce, §9).
|
|
954
|
+
session.pendingModelInjection = alias;
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
// Re-selecting the model the session is already on is a no-op: claude shows no "Switch model?"
|
|
958
|
+
// dialog for it, so skip the redundant /model (and its confirm Enter) entirely.
|
|
959
|
+
const current = session.configOptions.find((o) => o.id === "model")?.currentValue;
|
|
960
|
+
if (current === alias)
|
|
961
|
+
return;
|
|
962
|
+
this.injectModelCommand(session, alias);
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Side-channel `/model <alias>` write — synchronous, resolves immediately (never a turn). claude
|
|
966
|
+
* 2.1.176 (GrowthBook `tengu_immediate_model_command=false`) does NOT apply `/model` inline mid-
|
|
967
|
+
* conversation: it opens a blocking **"Switch model?" → 1. Yes / 2. No** dialog and leaves it OPEN.
|
|
968
|
+
* Unconfirmed, the dialog survives until the NEXT prompt — whose `\r` then confirms the switch AND
|
|
969
|
+
* discards the prompt text, so no turn is born and the story-024 stall watchdog trips (the live
|
|
970
|
+
* 39a93bfc hang; root cause proved headless by experiments/probe-c-model-then-prompt.mjs). So after
|
|
971
|
+
* the command we schedule ONE Enter to accept the default "Yes, switch" once the dialog has rendered;
|
|
972
|
+
* if no dialog appears (same model / flag flipped on) Enter-on-empty-input is a harmless no-op.
|
|
973
|
+
*/
|
|
974
|
+
injectModelCommand(session, alias) {
|
|
975
|
+
sendPrompt(session.pty, `/model ${alias}`, (fn) => fn());
|
|
976
|
+
// Confirm the "Switch model?" dialog — blind + scheduled, like the story-031 cancel ladder's Esc.
|
|
977
|
+
this.schedule(() => {
|
|
978
|
+
if (session.engine?.isDisposed)
|
|
979
|
+
return; // PTY exited meanwhile → nothing to confirm
|
|
980
|
+
session.pty.write("\r");
|
|
981
|
+
}, MODEL_CONFIRM_DELAY_MS);
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Story 046 (R1.3) — flush a deferred model-switch once the session is idle again (called from
|
|
985
|
+
* prompt()'s finally, the moment the turn settles). Last-write-wins: only the most recent queued
|
|
986
|
+
* alias is injected; the field is cleared so a settled session with nothing queued injects nothing.
|
|
987
|
+
*/
|
|
988
|
+
flushPendingControlInjections(session) {
|
|
989
|
+
if (session.turnDetector !== undefined)
|
|
990
|
+
return; // still not idle (defensive)
|
|
991
|
+
const alias = session.pendingModelInjection;
|
|
992
|
+
if (alias !== undefined) {
|
|
993
|
+
session.pendingModelInjection = undefined;
|
|
994
|
+
this.injectModelCommand(session, alias);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Story 046 (R3.3, design §6b) — drive the TUI to `target` with closed-loop raw Shift+Tab. Writes
|
|
999
|
+
* `\x1b[Z` DIRECTLY to the PTY (NOT sendPrompt — its Ctrl+U clear corrupts the escape), then awaits
|
|
1000
|
+
* the confirming `permission-mode` transcript event (the tail-as-truth fence: mode is read from the
|
|
1001
|
+
* transcript, NEVER from p.onData). A per-step Δt budget + a one-full-cycle safety stop guarantee the
|
|
1002
|
+
* loop ABORTS rather than hangs/false-stalls (Probe A gated this; story-044 awareness).
|
|
1003
|
+
*/
|
|
1004
|
+
async driveCyclableMode(sessionId, session, target) {
|
|
1005
|
+
const cyclableCount = session.modes.availableModes.filter((m) => CYCLABLE_MODES.has(m.id)).length;
|
|
1006
|
+
const maxSteps = Math.max(cyclableCount, 1) + 1; // one-full-cycle safety stop
|
|
1007
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
1008
|
+
if (session.modes.currentModeId === target)
|
|
1009
|
+
return; // converged
|
|
1010
|
+
const before = session.modes.currentModeId;
|
|
1011
|
+
session.pty.write(MODE_CYCLE_KEY); // raw \x1b[Z (Shift+Tab) — never via sendPrompt
|
|
1012
|
+
const observed = await this.awaitModeChange(sessionId, session, before);
|
|
1013
|
+
if (observed === undefined)
|
|
1014
|
+
return; // Δt elapsed with no confirming event → abort (no hang)
|
|
1015
|
+
session.modes.currentModeId = observed; // reconcile local state from the transcript truth
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Story 046 (R3.3) — poll the transcript (the pump's getMessages seam) for a `permission-mode` event
|
|
1020
|
+
* whose mode differs from `before`, up to MODE_CYCLE_STEP_TIMEOUT_MS. Returns the new mode, or
|
|
1021
|
+
* undefined on the Δt timeout (the caller aborts rather than hangs). The confirming event is usually
|
|
1022
|
+
* already present right after the TUI processes the keystroke, so this returns at once in tests.
|
|
1023
|
+
*/
|
|
1024
|
+
async awaitModeChange(sessionId, session, before) {
|
|
1025
|
+
let waited = 0;
|
|
1026
|
+
for (;;) {
|
|
1027
|
+
const mode = await this.readLatestPermissionMode(sessionId, session);
|
|
1028
|
+
if (mode !== undefined && mode !== before)
|
|
1029
|
+
return mode;
|
|
1030
|
+
if (waited >= MODE_CYCLE_STEP_TIMEOUT_MS)
|
|
1031
|
+
return undefined;
|
|
1032
|
+
await new Promise((resolve) => this.schedule(() => resolve(), MODE_CYCLE_POLL_MS));
|
|
1033
|
+
waited += MODE_CYCLE_POLL_MS;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
/** Story 046 (R3.3/R4.1) — the most recent `permission-mode` event's mode from the transcript (the
|
|
1037
|
+
* same getMessages seam the pump reads), or undefined when none is present yet. */
|
|
1038
|
+
async readLatestPermissionMode(sessionId, session) {
|
|
1039
|
+
// Mirror the pump's seam resolution: this.getMessages is the injectable reader, defaulting to
|
|
1040
|
+
// defaultGetMessages when not overridden (it is optional at the constructor seam).
|
|
1041
|
+
const read = this.getMessages ?? defaultGetMessages;
|
|
1042
|
+
const messages = await read(sessionId, { dir: session.cwd });
|
|
1043
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1044
|
+
const m = messages[i];
|
|
1045
|
+
if (m.type === "permission-mode" && typeof m.permissionMode === "string")
|
|
1046
|
+
return m.permissionMode;
|
|
1047
|
+
}
|
|
1048
|
+
return undefined;
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Story 046 (R3.4/R3.7/R3.8, design §6c) — apply a non-cyclable mode (dontAsk/bypassPermissions) by
|
|
1052
|
+
* re-spawning the SAME sessionId in place with a flag-carrying resume argv, preserving the transcript.
|
|
1053
|
+
* Order is load-bearing for R3.7: re-spawn FIRST, and swap in + tear down the old PTY ONLY once the new
|
|
1054
|
+
* one is live — so a failed re-spawn leaves the prior PTY/currentValue intact (never
|
|
1055
|
+
* torn-down-without-replacement). Re-spawn runs only while idle, so the old PTY has no pending turn to
|
|
1056
|
+
* double-resolve. The `respawning` latch defers concurrent selector changes (R3.8).
|
|
1057
|
+
*/
|
|
1058
|
+
async respawnSession(sessionId, session, change) {
|
|
1059
|
+
// Story 046 (R3.4 LIVE FIX guard): a re-spawn reattaches via `claude --resume <id>`, which needs the
|
|
1060
|
+
// transcript to ALREADY exist. Before the first interaction it is absent, so --resume falls back
|
|
1061
|
+
// (buildResumeArgv `|| claude`) to a NEW id the fork no longer tracks — stalling the turn until the
|
|
1062
|
+
// 120s watchdog AND discarding the live fresh PTY. Refuse until the session has interacted at least
|
|
1063
|
+
// once. The user/Zed retries after the first prompt; a boot-time default_config_options dontAsk/
|
|
1064
|
+
// bypass therefore stays at the fresh spawn's mode (use a fresh-spawn --permission-mode seed for
|
|
1065
|
+
// start-in-bypass, a documented follow-up). The OTHER selector's currentValue is left unchanged.
|
|
1066
|
+
if (!session.interacted) {
|
|
1067
|
+
throw new Error("Cannot switch to a re-spawn mode/effort before the first interaction (no transcript to resume yet); send a prompt first, then switch.");
|
|
1068
|
+
}
|
|
1069
|
+
session.respawning = true;
|
|
1070
|
+
try {
|
|
1071
|
+
const oldEngine = session.engine;
|
|
1072
|
+
// Preserve the OTHER selector's current value so re-spawning for one (mode OR effort) does not
|
|
1073
|
+
// reset the other — the resume argv carries both flags.
|
|
1074
|
+
const permissionMode = change.permissionMode ?? session.modes.currentModeId;
|
|
1075
|
+
const effortLevel = change.effortLevel ?? this.currentEffort(session);
|
|
1076
|
+
// Re-spawn through the SAME startEngine seam createSession uses, reusing the sessionId so the
|
|
1077
|
+
// transcript is reattached (R3.4); the flags flow into the flag-carrying resume argv.
|
|
1078
|
+
const started = await this.startEngine({
|
|
1079
|
+
sessionId,
|
|
1080
|
+
cwd: session.cwd,
|
|
1081
|
+
resume: true,
|
|
1082
|
+
// Story 046 (R3.4 LIVE FIX): an in-place re-spawn may run before the first interaction (boot
|
|
1083
|
+
// bypass), so DEFER discovery instead of the 2000ms fatal watchdog — see defaultStartEngine.
|
|
1084
|
+
inPlaceRespawn: true,
|
|
1085
|
+
permissionMode,
|
|
1086
|
+
effortLevel,
|
|
1087
|
+
sessions: this.engines,
|
|
1088
|
+
onEvent: (sid) => void this.pumpUpdates(sid),
|
|
1089
|
+
});
|
|
1090
|
+
// New PTY is live — only now retire the old one (idle ⇒ no pending turn to double-resolve) and swap.
|
|
1091
|
+
oldEngine?.cleanup();
|
|
1092
|
+
oldEngine?.kill();
|
|
1093
|
+
session.pty = started.pty;
|
|
1094
|
+
session.engine = started.engine;
|
|
1095
|
+
session.watcher = started.watcher;
|
|
1096
|
+
if (change.permissionMode) {
|
|
1097
|
+
session.modes = { ...session.modes, currentModeId: change.permissionMode };
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
finally {
|
|
1101
|
+
session.respawning = false;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
/** Story 046 — the session's current effort configOption value (undefined when no effort option). */
|
|
1105
|
+
currentEffort(session) {
|
|
1106
|
+
const opt = session.configOptions.find((o) => o.id === "effort");
|
|
1107
|
+
return typeof opt?.currentValue === "string" ? opt.currentValue : undefined;
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Story 046 (R2.2, design §7) — apply a reasoning-effort change. Probe B verdict: effort has no live
|
|
1111
|
+
* mid-session mechanism (`--effort` is a spawn flag), so a change re-spawns in place with the flag
|
|
1112
|
+
* (mirroring the dontAsk/bypass mode path), idle-guarded, with the R3.7 failure path and R3.8 latch.
|
|
1113
|
+
* A no-op change applies nothing. Throwing here leaves the caller's applyConfigOptionValue unrun, so
|
|
1114
|
+
* the prior currentValue is left unchanged on failure (R3.7).
|
|
1115
|
+
*/
|
|
1116
|
+
async applyEffortChange(sessionId, session, level) {
|
|
1117
|
+
if (level === this.currentEffort(session))
|
|
1118
|
+
return; // no value change → no-op
|
|
1119
|
+
if (session.turnDetector !== undefined || session.respawning) {
|
|
1120
|
+
throw new Error("Cannot change effort while the session is busy (a turn is in flight or a re-spawn is underway); retry when idle");
|
|
1121
|
+
}
|
|
1122
|
+
await this.respawnSession(sessionId, session, { effortLevel: level });
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Story 046 (R4.1/R4.2/R4.3, design §8) — reconcile the `mode` configOption from the latest
|
|
1126
|
+
* permission-mode event in the exactly-once `messages` slice. Emits current_mode_update EXACTLY ONCE
|
|
1127
|
+
* and only when the transcript's mode differs from the advertised currentModeId (no spurious emit when
|
|
1128
|
+
* already in sync, or when no permission-mode event is present). Model/effort have no transcript drift
|
|
1129
|
+
* event, so they are NOT reconciled here — optimistic-on-apply only (R4.3).
|
|
1130
|
+
*/
|
|
1131
|
+
async reconcileModeFromTranscript(sessionId, session, messages) {
|
|
1132
|
+
let latestMode;
|
|
1133
|
+
for (const m of messages) {
|
|
1134
|
+
const w = m;
|
|
1135
|
+
if (w.type === "permission-mode" && typeof w.permissionMode === "string") {
|
|
1136
|
+
latestMode = w.permissionMode;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
if (latestMode === undefined || latestMode === session.modes.currentModeId)
|
|
1140
|
+
return;
|
|
1141
|
+
session.modes = { ...session.modes, currentModeId: latestMode };
|
|
1142
|
+
session.configOptions = session.configOptions.map((o) => o.id === "mode" && typeof o.currentValue === "string"
|
|
1143
|
+
? { ...o, currentValue: latestMode }
|
|
1144
|
+
: o);
|
|
1145
|
+
await this.client.sessionUpdate({
|
|
1146
|
+
sessionId,
|
|
1147
|
+
update: { sessionUpdate: "current_mode_update", currentModeId: latestMode },
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
827
1150
|
async replaySessionHistory(sessionId) {
|
|
828
1151
|
const session = this.sessions[sessionId];
|
|
829
1152
|
if (!session)
|
|
@@ -1053,6 +1376,11 @@ export class ClaudeAcpAgent {
|
|
|
1053
1376
|
const session = this.sessions[sessionId];
|
|
1054
1377
|
if (!session)
|
|
1055
1378
|
return; // watcher fired before the handle was registered, or after teardown
|
|
1379
|
+
// Story 046 (R3.4 LIVE FIX): the pump only fires once the watcher has armed against the REAL
|
|
1380
|
+
// transcript, so reaching here proves the transcript exists (the first interaction happened). Mark
|
|
1381
|
+
// it idempotently — respawnSession gates the --resume re-spawn on this (a pre-interaction re-spawn
|
|
1382
|
+
// would --resume a non-existent transcript and fall back to a new untracked id).
|
|
1383
|
+
session.interacted = true;
|
|
1056
1384
|
const messages = await readOrderedMessages(sessionId, session.cwd, {
|
|
1057
1385
|
getMessages: this.getMessages,
|
|
1058
1386
|
});
|
|
@@ -1104,6 +1432,15 @@ export class ClaudeAcpAgent {
|
|
|
1104
1432
|
for (const m of fed)
|
|
1105
1433
|
registerGateToolUses(m, session.gate);
|
|
1106
1434
|
}
|
|
1435
|
+
// === SEAM(046) §8 — reconcile the `mode` configOption from transcript permission-mode events.
|
|
1436
|
+
// `permission-mode` is lifecycle-classified (event-switch.ts) and emits no SessionUpdate of its own,
|
|
1437
|
+
// and there is no other transcript-driven mode consumer — so intercept it HERE, over the SAME
|
|
1438
|
+
// exactly-once `fed` slice the detector/gate consume. If the latest permission-mode event's mode
|
|
1439
|
+
// differs from the advertised currentModeId, reconcile the mode configOption currentValue and emit
|
|
1440
|
+
// current_mode_update EXACTLY ONCE (covers a manual Shift+Tab in a mirrored TUI, plan-approval, and
|
|
1441
|
+
// the R3.4 re-spawn). Model and effort have NO transcript drift event — they stay optimistic-on-apply
|
|
1442
|
+
// (R4.3, documented). Additive: it never blocks the emit loop below.
|
|
1443
|
+
await this.reconcileModeFromTranscript(sessionId, session, fed);
|
|
1107
1444
|
// === SEAM(041) §sidechain — source + merge + linearize + emit (BOTH main turns and nested
|
|
1108
1445
|
// sub-agent rows). Factored into the shared {@link emitLinearizedWithNested} so the `session/load`
|
|
1109
1446
|
// replay path (`replaySessionHistory`) runs the IDENTICAL loop — loaded == live with no replay-only
|
|
@@ -1373,6 +1710,12 @@ export class ClaudeAcpAgent {
|
|
|
1373
1710
|
logger: this.logger,
|
|
1374
1711
|
});
|
|
1375
1712
|
await settingsManager.initialize();
|
|
1713
|
+
// Story 046 (R3.1/R3.6, choose-before-start): seed the permission mode from
|
|
1714
|
+
// settings.permissions.defaultMode, normalized through resolvePermissionMode (returns "default"
|
|
1715
|
+
// on undefined/invalid AND strips bypassPermissions under the root guard — so R3.1 reconciles
|
|
1716
|
+
// with R3.6). Drives both the spawn flag (--permission-mode, fresh path) and the advertised
|
|
1717
|
+
// currentModeId, replacing the old hardcoded "default".
|
|
1718
|
+
const seededMode = resolvePermissionMode(settingsManager.getSettings().permissions?.defaultMode, this.logger);
|
|
1376
1719
|
// Per-session task state — still surfaced via plan notifications by the Group 2 pump / hooks.
|
|
1377
1720
|
const taskState = new Map();
|
|
1378
1721
|
// === SEAM(034) §9 hybrid gate: set up the per-session permission gate BEFORE the spawn =======
|
|
@@ -1405,6 +1748,9 @@ export class ClaudeAcpAgent {
|
|
|
1405
1748
|
replayOnly: creationOpts.replayOnly,
|
|
1406
1749
|
sessions: this.engines,
|
|
1407
1750
|
onEvent: (sid) => void this.pumpUpdates(sid),
|
|
1751
|
+
// Story 046 (R3.1/R3.2): seed the fresh spawn with the resolved permission mode (the resume
|
|
1752
|
+
// path's mode is carried by the R3.4 re-spawn argv, not here).
|
|
1753
|
+
permissionMode: seededMode,
|
|
1408
1754
|
// Story 034: the gate's scratch settings file, consumed as `--settings "<file>"` (fresh path).
|
|
1409
1755
|
settingsFile: gate?.settingsPath,
|
|
1410
1756
|
});
|
|
@@ -1438,13 +1784,16 @@ export class ClaudeAcpAgent {
|
|
|
1438
1784
|
boundGate.bindPty(started.pty);
|
|
1439
1785
|
started.pty.onExit(() => void boundGate.teardown());
|
|
1440
1786
|
}
|
|
1441
|
-
//
|
|
1442
|
-
|
|
1787
|
+
// Story 046: advertise the full curated model catalog (was a single static "Default" entry),
|
|
1788
|
+
// with the current model seeded to the safe `default`. Populating the catalog also unlocks the
|
|
1789
|
+
// effort selector via buildConfigOptions (§5/§7). The current MODE is still seeded "default" here;
|
|
1790
|
+
// Task 4.1 reseeds it from settings.permissions.defaultMode.
|
|
1791
|
+
const availableModes = buildAvailableModes(DEFAULT_MODEL_INFO);
|
|
1443
1792
|
const modes = {
|
|
1444
|
-
currentModeId:
|
|
1793
|
+
currentModeId: seededMode,
|
|
1445
1794
|
availableModes,
|
|
1446
1795
|
};
|
|
1447
|
-
const configOptions = buildConfigOptions(modes,
|
|
1796
|
+
const configOptions = buildConfigOptions(modes, DEFAULT_MODEL_INFO.value, MODEL_CATALOG, settingsManager.getSettings().effortLevel);
|
|
1448
1797
|
// Runtime cwd is read from inside the JSONL (story 015); fall back to the requested host cwd
|
|
1449
1798
|
// until the first transcript line carries `.cwd` (the seam may return cwd === undefined early).
|
|
1450
1799
|
const runtimeCwd = started.cwd ?? params.cwd;
|
|
@@ -1468,9 +1817,9 @@ export class ClaudeAcpAgent {
|
|
|
1468
1817
|
cachedWriteTokens: 0,
|
|
1469
1818
|
},
|
|
1470
1819
|
modes,
|
|
1471
|
-
modelInfos:
|
|
1820
|
+
modelInfos: MODEL_CATALOG,
|
|
1472
1821
|
configOptions,
|
|
1473
|
-
contextWindowSize: inferContextWindowFromModel(
|
|
1822
|
+
contextWindowSize: inferContextWindowFromModel(DEFAULT_MODEL_INFO.value) ?? DEFAULT_CONTEXT_WINDOW,
|
|
1474
1823
|
taskState,
|
|
1475
1824
|
gate,
|
|
1476
1825
|
};
|
|
@@ -1512,13 +1861,13 @@ function buildAvailableModes(modelInfo) {
|
|
|
1512
1861
|
}, {
|
|
1513
1862
|
id: "dontAsk",
|
|
1514
1863
|
name: "Don't Ask",
|
|
1515
|
-
description: "Don't prompt for permissions, deny if not pre-approved",
|
|
1864
|
+
description: "Don't prompt for permissions, deny if not pre-approved. Selecting this restarts the session.",
|
|
1516
1865
|
});
|
|
1517
1866
|
if (ALLOW_BYPASS) {
|
|
1518
1867
|
modes.push({
|
|
1519
1868
|
id: "bypassPermissions",
|
|
1520
1869
|
name: "Bypass Permissions",
|
|
1521
|
-
description: "Bypass all permission checks",
|
|
1870
|
+
description: "Bypass all permission checks. Selecting this restarts the session.",
|
|
1522
1871
|
});
|
|
1523
1872
|
}
|
|
1524
1873
|
return modes;
|
|
@@ -2050,6 +2399,9 @@ export function toAcpNotifications(content, role, sessionId, toolUseCache, clien
|
|
|
2050
2399
|
case "compaction_delta":
|
|
2051
2400
|
case "advisor_tool_result":
|
|
2052
2401
|
case "mid_conv_system":
|
|
2402
|
+
case "fallback":
|
|
2403
|
+
// `fallback`: model-routing marker (@anthropic-ai/sdk >= 0.104) — from/to
|
|
2404
|
+
// model hops, no renderable content. No-op like the control blocks above.
|
|
2053
2405
|
break;
|
|
2054
2406
|
default:
|
|
2055
2407
|
unreachable(chunk, logger);
|
|
@@ -142,8 +142,13 @@ export declare class SessionEngine {
|
|
|
142
142
|
* `|| claude` makes a FAILED `--resume` fall back to a fresh interactive session rather than
|
|
143
143
|
* hanging. The id is double-quoted so it survives shell word-splitting. There is deliberately
|
|
144
144
|
* NO `-p`/`--print`/`stream-json` — those select the SDK/credit non-interactive path.
|
|
145
|
+
*
|
|
146
|
+
* Story 046 (R3.4): an optional non-"default" `permissionMode` is carried into BOTH the `--resume`
|
|
147
|
+
* launch AND the `|| claude` fresh fallback as `--permission-mode <mode>`, so an in-place re-spawn
|
|
148
|
+
* (a dontAsk/bypass switch) reattaches the SAME sessionId/transcript under the new mode. Still no
|
|
149
|
+
* `-p`/`--print`/`stream-json` — billing stays subscription `cli`.
|
|
145
150
|
*/
|
|
146
|
-
export declare function buildResumeArgv(sessionId: string): [string, string];
|
|
151
|
+
export declare function buildResumeArgv(sessionId: string, permissionMode?: string, effortLevel?: string): [string, string];
|
|
147
152
|
/** Options for {@link spawnResumePty}. */
|
|
148
153
|
export interface SpawnResumeOptions {
|
|
149
154
|
/** The prior session id to reattach to (== the JSONL transcript basename). */
|
|
@@ -154,6 +159,13 @@ export interface SpawnResumeOptions {
|
|
|
154
159
|
baseEnv?: Record<string, string | undefined>;
|
|
155
160
|
/** Injectable spawn function (defaults to node-pty's `spawn`); tests pass a fake. */
|
|
156
161
|
spawn?: typeof pty.spawn;
|
|
162
|
+
/**
|
|
163
|
+
* Story 046 (R3.4): carry `--permission-mode <mode>` into the resume argv for an in-place re-spawn
|
|
164
|
+
* (a dontAsk/bypass switch). Non-"default" only; preserves the same sessionId/transcript.
|
|
165
|
+
*/
|
|
166
|
+
permissionMode?: string;
|
|
167
|
+
/** Story 046 (R2.2): carry `--effort <level>` into the resume argv for an effort re-spawn. */
|
|
168
|
+
effortLevel?: string;
|
|
157
169
|
}
|
|
158
170
|
/**
|
|
159
171
|
* Spawn the resume PTY, reusing story 013's sanitized env (R4.2) so the resumed turn keeps the
|
|
@@ -172,6 +184,13 @@ export interface CreateSessionEngineOptions {
|
|
|
172
184
|
baseEnv?: Record<string, string | undefined>;
|
|
173
185
|
/** Injectable spawn function (defaults to node-pty's `spawn`); tests pass a fake. */
|
|
174
186
|
spawn?: typeof pty.spawn;
|
|
187
|
+
/**
|
|
188
|
+
* Story 046 (R3.2): forwarded to {@link spawnClaudePty} as `--permission-mode <mode>` for a fresh
|
|
189
|
+
* spawn seeded to a non-"default" mode. Absent/"default" → no flag (byte-for-byte pre-046 spawn).
|
|
190
|
+
*/
|
|
191
|
+
permissionMode?: string;
|
|
192
|
+
/** Story 046 (R2.2): forwarded to {@link spawnClaudePty} as `--effort <level>` for a non-"default" seed. */
|
|
193
|
+
effortLevel?: string;
|
|
175
194
|
/**
|
|
176
195
|
* Story 034 (§9 hybrid gate): per-session SCRATCH settings file carrying the fork's
|
|
177
196
|
* `PreToolUse` hook, forwarded to {@link spawnClaudePty} as `--settings "<file>"`. The
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine-lifecycle.d.ts","sourceRoot":"","sources":["../src/engine-lifecycle.ts"],"names":[],"mappings":"AAuBA,OAAO,GAAG,MAAM,UAAU,CAAC;AAQ3B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,oFAAoF;IACpF,IAAI,IAAI,IAAI,CAAC;CACd;AAED,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY,MAAM,CAAC;AAChC,eAAO,MAAM,YAAY,KAAK,CAAC;AAE/B;;;GAGG;AACH,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC,wDAAwD;AACxD,MAAM,WAAW,oBAAoB;IACnC,gGAAgG;IAChG,MAAM,EAAE,eAAe,CAAC;IACxB,gFAAgF;IAChF,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACtC,yEAAyE;IACzE,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;IACxC,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gGAAgG;IAChG,YAAY,CAAC,EAAE,OAAO,UAAU,CAAC;IACjC,cAAc,CAAC,EAAE,OAAO,YAAY,CAAC;IACrC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAED;;;;;;;;GAQG;AACH,qBAAa,aAAa;IACxB,qFAAqF;IACrF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,4DAA4D;IAC5D,QAAQ,CAAC,GAAG,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC;IACrC,8DAA8D;IAC9D,OAAO,CAAC,EAAE,cAAc,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAA6B;IACvD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAA8B;IACzD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;IACjD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAsB;IACrD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,WAAW,CAAC,CAAgC;IACpD,OAAO,CAAC,WAAW,CAAC,CAAiC;IACrD,OAAO,CAAC,QAAQ,CAAS;IACzB;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB,CAAC,CAAkB;IAC3C;;;;;OAKG;IACH,OAAO,CAAC,UAAU,CAAC,CAAc;gBAErB,IAAI,EAAE,oBAAoB;IAwBtC;;;;;OAKG;IACH,aAAa,CAAC,IAAI,GAAE,MAAqB,EAAE,IAAI,GAAE,MAAqB,GAAG,IAAI;IAa7E;;;;;;OAMG;IACH,SAAS,IAAI,IAAI;IAKjB,qFAAqF;IACrF,MAAM,IAAI,IAAI;IAKd;;;OAGG;IACH,IAAI,IAAI,IAAI;IAKZ;;;;;OAKG;IACH,OAAO,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAiC5D;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,EAAE,eAAe,GAAG,IAAI;IAI9C,gEAAgE;IAChE,IAAI,UAAU,IAAI,OAAO,CAExB;CACF;AAKD
|
|
1
|
+
{"version":3,"file":"engine-lifecycle.d.ts","sourceRoot":"","sources":["../src/engine-lifecycle.ts"],"names":[],"mappings":"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"}
|
package/dist/engine-lifecycle.js
CHANGED
|
@@ -167,9 +167,17 @@ const RESUME_PTY_NAME = "xterm-256color";
|
|
|
167
167
|
* `|| claude` makes a FAILED `--resume` fall back to a fresh interactive session rather than
|
|
168
168
|
* hanging. The id is double-quoted so it survives shell word-splitting. There is deliberately
|
|
169
169
|
* NO `-p`/`--print`/`stream-json` — those select the SDK/credit non-interactive path.
|
|
170
|
+
*
|
|
171
|
+
* Story 046 (R3.4): an optional non-"default" `permissionMode` is carried into BOTH the `--resume`
|
|
172
|
+
* launch AND the `|| claude` fresh fallback as `--permission-mode <mode>`, so an in-place re-spawn
|
|
173
|
+
* (a dontAsk/bypass switch) reattaches the SAME sessionId/transcript under the new mode. Still no
|
|
174
|
+
* `-p`/`--print`/`stream-json` — billing stays subscription `cli`.
|
|
170
175
|
*/
|
|
171
|
-
export function buildResumeArgv(sessionId) {
|
|
172
|
-
|
|
176
|
+
export function buildResumeArgv(sessionId, permissionMode, effortLevel) {
|
|
177
|
+
const pm = permissionMode && permissionMode !== "default" ? ` --permission-mode ${permissionMode}` : "";
|
|
178
|
+
const ef = effortLevel && effortLevel !== "default" ? ` --effort ${effortLevel}` : "";
|
|
179
|
+
const flags = `${pm}${ef}`;
|
|
180
|
+
return ["-c", `claude --resume "${sessionId}"${flags} || claude${flags}`];
|
|
173
181
|
}
|
|
174
182
|
/**
|
|
175
183
|
* Spawn the resume PTY, reusing story 013's sanitized env (R4.2) so the resumed turn keeps the
|
|
@@ -182,7 +190,7 @@ export function buildResumeArgv(sessionId) {
|
|
|
182
190
|
export function spawnResumePty(opts) {
|
|
183
191
|
const { sessionId, cwd, baseEnv = process.env, spawn = pty.spawn } = opts;
|
|
184
192
|
const shell = resolveShell(baseEnv);
|
|
185
|
-
const argv = buildResumeArgv(sessionId);
|
|
193
|
+
const argv = buildResumeArgv(sessionId, opts.permissionMode, opts.effortLevel);
|
|
186
194
|
const env = buildSanitizedEnv(baseEnv);
|
|
187
195
|
// §10 refuse-to-spawn guard (R4.2): abort if any forbidden billing var survived sanitization,
|
|
188
196
|
// rather than resume a credit-billed run. Checks the SPAWN env, not process.env.
|
|
@@ -218,6 +226,8 @@ export function createSessionEngine(opts) {
|
|
|
218
226
|
cwd: opts.cwd,
|
|
219
227
|
baseEnv: opts.baseEnv,
|
|
220
228
|
spawn: opts.spawn,
|
|
229
|
+
permissionMode: opts.permissionMode,
|
|
230
|
+
effortLevel: opts.effortLevel,
|
|
221
231
|
settingsFile: opts.settingsFile,
|
|
222
232
|
});
|
|
223
233
|
// ONE watcher, started by the story 015 factory and bound to that same PTY.
|
package/dist/engine-pty.d.ts
CHANGED
|
@@ -44,15 +44,20 @@ export declare function resolveShell(baseEnv?: Record<string, string | undefined
|
|
|
44
44
|
* billing). The path is double-quoted so it survives the `-lc` shell word-splitting.
|
|
45
45
|
* Like `--permission-mode plan`, this flag changes only TUI behavior — it adds no
|
|
46
46
|
* `-p`/`stream-json`, so billing stays subscription `cli` (§10).
|
|
47
|
+
*
|
|
48
|
+
* Story 046 (R3.2): the `planMode` boolean seam is GENERALIZED to a `permissionMode` string. A
|
|
49
|
+
* non-"default" mode is emitted as `--permission-mode <mode>`; "default"/undefined emit nothing;
|
|
50
|
+
* `permissionMode: "plan"` reproduces the old planMode=true path. Still interactive-only — adds no
|
|
51
|
+
* `-p`/`stream-json`, billing stays subscription `cli`.
|
|
47
52
|
*/
|
|
48
|
-
export declare function buildClaudeCmd(sessionId: string,
|
|
53
|
+
export declare function buildClaudeCmd(sessionId: string, permissionMode?: string, settingsFile?: string, effortLevel?: string): string;
|
|
49
54
|
/**
|
|
50
55
|
* Login-shell argv (§5, R1.3). The `-lc` flag is MANDATORY: node-pty's `posix_spawnp`
|
|
51
56
|
* does not execute the `claude` npm launcher's `#!/usr/bin/env node` shebang, so the
|
|
52
57
|
* launcher must run *through* a login shell. (The compiled native-binary is the only
|
|
53
58
|
* thing that could be spawned directly — §5 / IMPLEMENTACAO-FORK-ACP.md §17.)
|
|
54
59
|
*/
|
|
55
|
-
export declare function buildSpawnArgv(sessionId: string,
|
|
60
|
+
export declare function buildSpawnArgv(sessionId: string, permissionMode?: string, settingsFile?: string, effortLevel?: string): [string, string];
|
|
56
61
|
/** Options for {@link spawnClaudePty}. */
|
|
57
62
|
export interface SpawnPtyOptions {
|
|
58
63
|
/** Host working directory the TUI runs in; passed straight through to the PTY (§5). */
|
|
@@ -65,11 +70,16 @@ export interface SpawnPtyOptions {
|
|
|
65
70
|
*/
|
|
66
71
|
spawn?: typeof pty.spawn;
|
|
67
72
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
73
|
+
* Story 046 (R3.2): a non-"default" permission mode adds `--permission-mode <mode>` to the spawn
|
|
74
|
+
* command (generalizes the story-029 planMode=plan seam). `"plan"` reproduces the old planMode
|
|
75
|
+
* path. Interactive-only — adds no `-p`/`stream-json`; billing stays subscription `cli`.
|
|
76
|
+
*/
|
|
77
|
+
permissionMode?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Story 046 (R2.2): a non-"default" reasoning effort adds `--effort <level>` to the spawn command
|
|
80
|
+
* (Probe B verdict — effort has no live mid-session path, so it is seeded/re-spawned). Interactive-only.
|
|
71
81
|
*/
|
|
72
|
-
|
|
82
|
+
effortLevel?: string;
|
|
73
83
|
/**
|
|
74
84
|
* Story 034 (§9 hybrid gate): absolute path of the per-session SCRATCH settings file
|
|
75
85
|
* carrying the fork's `PreToolUse` hook; appended as `--settings "<file>"`. The file
|
package/dist/engine-pty.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine-pty.d.ts","sourceRoot":"","sources":["../src/engine-pty.ts"],"names":[],"mappings":"AAiBA,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAQrC;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,8GAKzB,CAAC;AAEX;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACxD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAWpC;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,IAAI,CASrF;AAED,0FAA0F;AAC1F,wBAAgB,YAAY,CAAC,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GAAG,MAAM,CAE9F;AAED
|
|
1
|
+
{"version":3,"file":"engine-pty.d.ts","sourceRoot":"","sources":["../src/engine-pty.ts"],"names":[],"mappings":"AAiBA,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAQrC;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,8GAKzB,CAAC;AAEX;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACxD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAWpC;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,IAAI,CASrF;AAED,0FAA0F;AAC1F,wBAAgB,YAAY,CAAC,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GAAG,MAAM,CAE9F;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,cAAc,CAAC,EAAE,MAAM,EACvB,YAAY,CAAC,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAQR;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,cAAc,CAAC,EAAE,MAAM,EACvB,YAAY,CAAC,EAAE,MAAM,EACrB,WAAW,CAAC,EAAE,MAAM,GACnB,CAAC,MAAM,EAAE,MAAM,CAAC,CAElB;AAED,0CAA0C;AAC1C,MAAM,WAAW,eAAe;IAC9B,uFAAuF;IACvF,GAAG,EAAE,MAAM,CAAC;IACZ,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC7C;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,CAAC,KAAK,CAAC;IACzB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,SAAS,EAAE,MAAM,CAAC;IAClB,2BAA2B;IAC3B,GAAG,EAAE,IAAI,CAAC;CACX;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,eAAe,CAsCrE;AA6BD,gFAAgF;AAChF,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,WAAS,CAAC;AAEtC;;;GAGG;AACH,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,UAAU,CACxB,CAAC,EAAE,IAAI,EACP,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,IAAqC,GAC9E,IAAI,CAON"}
|
package/dist/engine-pty.js
CHANGED
|
@@ -89,11 +89,20 @@ export function resolveShell(baseEnv = process.env) {
|
|
|
89
89
|
* billing). The path is double-quoted so it survives the `-lc` shell word-splitting.
|
|
90
90
|
* Like `--permission-mode plan`, this flag changes only TUI behavior — it adds no
|
|
91
91
|
* `-p`/`stream-json`, so billing stays subscription `cli` (§10).
|
|
92
|
+
*
|
|
93
|
+
* Story 046 (R3.2): the `planMode` boolean seam is GENERALIZED to a `permissionMode` string. A
|
|
94
|
+
* non-"default" mode is emitted as `--permission-mode <mode>`; "default"/undefined emit nothing;
|
|
95
|
+
* `permissionMode: "plan"` reproduces the old planMode=true path. Still interactive-only — adds no
|
|
96
|
+
* `-p`/`stream-json`, billing stays subscription `cli`.
|
|
92
97
|
*/
|
|
93
|
-
export function buildClaudeCmd(sessionId,
|
|
98
|
+
export function buildClaudeCmd(sessionId, permissionMode, settingsFile, effortLevel) {
|
|
94
99
|
let cmd = `claude --session-id ${sessionId}`;
|
|
95
|
-
if (
|
|
96
|
-
cmd +=
|
|
100
|
+
if (permissionMode && permissionMode !== "default")
|
|
101
|
+
cmd += ` --permission-mode ${permissionMode}`;
|
|
102
|
+
// Story 046 (R2.2): effort is a spawn flag (Probe B verdict — no live mid-session path), so a
|
|
103
|
+
// non-"default" level is seeded/re-spawned with `--effort <level>`. Interactive-only — no credit path.
|
|
104
|
+
if (effortLevel && effortLevel !== "default")
|
|
105
|
+
cmd += ` --effort ${effortLevel}`;
|
|
97
106
|
if (settingsFile)
|
|
98
107
|
cmd += ` --settings "${settingsFile}"`;
|
|
99
108
|
return cmd;
|
|
@@ -104,8 +113,8 @@ export function buildClaudeCmd(sessionId, planMode, settingsFile) {
|
|
|
104
113
|
* launcher must run *through* a login shell. (The compiled native-binary is the only
|
|
105
114
|
* thing that could be spawned directly — §5 / IMPLEMENTACAO-FORK-ACP.md §17.)
|
|
106
115
|
*/
|
|
107
|
-
export function buildSpawnArgv(sessionId,
|
|
108
|
-
return ["-lc", buildClaudeCmd(sessionId,
|
|
116
|
+
export function buildSpawnArgv(sessionId, permissionMode, settingsFile, effortLevel) {
|
|
117
|
+
return ["-lc", buildClaudeCmd(sessionId, permissionMode, settingsFile, effortLevel)];
|
|
109
118
|
}
|
|
110
119
|
/**
|
|
111
120
|
* Spawn the interactive `claude` TUI under a real PTY with the §5 dimensions and a
|
|
@@ -124,10 +133,10 @@ export function buildSpawnArgv(sessionId, planMode, settingsFile) {
|
|
|
124
133
|
* process-supervisor or non-PTY spawn variant is introduced.
|
|
125
134
|
*/
|
|
126
135
|
export function spawnClaudePty(opts) {
|
|
127
|
-
const { cwd, baseEnv = process.env, spawn = pty.spawn,
|
|
136
|
+
const { cwd, baseEnv = process.env, spawn = pty.spawn, permissionMode, settingsFile, effortLevel, } = opts;
|
|
128
137
|
const sessionId = randomUUID(); // pre-generated → correlates to the JSONL transcript
|
|
129
138
|
const shell = resolveShell(baseEnv);
|
|
130
|
-
const argv = buildSpawnArgv(sessionId,
|
|
139
|
+
const argv = buildSpawnArgv(sessionId, permissionMode, settingsFile, effortLevel);
|
|
131
140
|
// Sanitized, subscription-billing env (§5/§10) via the single shared definition.
|
|
132
141
|
const env = buildSanitizedEnv(baseEnv);
|
|
133
142
|
// §10 no-spoof contract (R3.1/R3.3): the 'cli' entrypoint label MUST be produced
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ModelInfo } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
/**
|
|
3
|
+
* The static curated catalog advertised to the Zed Agent Panel's `model` selector. Each `value` is a
|
|
4
|
+
* `claude` TUI alias accepted by `/model <alias>` (live) and `--model <alias>` (spawn). `default` is
|
|
5
|
+
* first and is the safe fallback. Effort-capable models (`sonnet`, `opus`) carry the metadata that
|
|
6
|
+
* surfaces the effort selector; `default`/`haiku`/`opusplan` advertise no effort levels.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MODEL_CATALOG: ModelInfo[];
|
|
9
|
+
/** The safe fallback entry, kept as a named export so callers can seed/anchor on it without a lookup. */
|
|
10
|
+
export declare const DEFAULT_MODEL_INFO: ModelInfo;
|
|
11
|
+
//# sourceMappingURL=model-catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-catalog.d.ts","sourceRoot":"","sources":["../src/model-catalog.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAShE;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,EA8BpC,CAAC;AAEF,yGAAyG;AACzG,eAAO,MAAM,kBAAkB,EAAE,SAA4B,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Effort levels offered for reasoning-capable models — the SDK's `supportedEffortLevels` vocabulary. */
|
|
2
|
+
const REASONING_EFFORT_LEVELS = [
|
|
3
|
+
"low",
|
|
4
|
+
"medium",
|
|
5
|
+
"high",
|
|
6
|
+
];
|
|
7
|
+
/**
|
|
8
|
+
* The static curated catalog advertised to the Zed Agent Panel's `model` selector. Each `value` is a
|
|
9
|
+
* `claude` TUI alias accepted by `/model <alias>` (live) and `--model <alias>` (spawn). `default` is
|
|
10
|
+
* first and is the safe fallback. Effort-capable models (`sonnet`, `opus`) carry the metadata that
|
|
11
|
+
* surfaces the effort selector; `default`/`haiku`/`opusplan` advertise no effort levels.
|
|
12
|
+
*/
|
|
13
|
+
export const MODEL_CATALOG = [
|
|
14
|
+
{
|
|
15
|
+
value: "default",
|
|
16
|
+
displayName: "Default",
|
|
17
|
+
description: "Use the model the claude TUI is configured with (safe fallback).",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
value: "sonnet",
|
|
21
|
+
displayName: "Sonnet",
|
|
22
|
+
description: "Claude Sonnet — balanced speed and capability; supports reasoning effort.",
|
|
23
|
+
supportsEffort: true,
|
|
24
|
+
supportedEffortLevels: REASONING_EFFORT_LEVELS,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
value: "opus",
|
|
28
|
+
displayName: "Opus",
|
|
29
|
+
description: "Claude Opus — highest capability; supports reasoning effort.",
|
|
30
|
+
supportsEffort: true,
|
|
31
|
+
supportedEffortLevels: REASONING_EFFORT_LEVELS,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
value: "haiku",
|
|
35
|
+
displayName: "Haiku",
|
|
36
|
+
description: "Claude Haiku — fastest and most economical; no reasoning effort levels.",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
value: "opusplan",
|
|
40
|
+
displayName: "Opus Plan",
|
|
41
|
+
description: "Opus while planning, Sonnet for execution (plan-mode workflow).",
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
/** The safe fallback entry, kept as a named export so callers can seed/anchor on it without a lookup. */
|
|
45
|
+
export const DEFAULT_MODEL_INFO = MODEL_CATALOG[0];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate-wiring.d.ts","sourceRoot":"","sources":["../../src/permissions/gate-wiring.ts"],"names":[],"mappings":"AAqCA,OAAO,EAEL,iBAAiB,EACjB,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAqB,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAErF;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,EAAE,SAAS,MAAM,EAO7D,CAAC;AAQF,kFAAkF;AAClF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAG3D;AAED;;;+EAG+E;AAC/E,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAChD,2DAA2D;AAC3D,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAC9C;oGACoG;AACpG,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C,sEAAsE;AACtE,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,0FAA0F;AAC1F,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAC9C;;gDAEgD;AAChD,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAI7C,2FAA2F;AAC3F,eAAO,MAAM,uBAAuB,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"gate-wiring.d.ts","sourceRoot":"","sources":["../../src/permissions/gate-wiring.ts"],"names":[],"mappings":"AAqCA,OAAO,EAEL,iBAAiB,EACjB,KAAK,gBAAgB,EACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAqB,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAErF;;;;;;GAMG;AACH,eAAO,MAAM,gCAAgC,EAAE,SAAS,MAAM,EAO7D,CAAC;AAQF,kFAAkF;AAClF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAG3D;AAED;;;+EAG+E;AAC/E,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAChD,2DAA2D;AAC3D,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAC9C;oGACoG;AACpG,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C,sEAAsE;AACtE,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,0FAA0F;AAC1F,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAC9C;;gDAEgD;AAChD,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAI7C,2FAA2F;AAC3F,eAAO,MAAM,uBAAuB,4BAA4B,CAAC;AAMjE;;0FAE0F;AAC1F,MAAM,WAAW,OAAQ,SAAQ,SAAS;IACxC,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG;QAAE,OAAO,IAAI,IAAI,CAAA;KAAE,CAAC;CAC1D;AAED,2FAA2F;AAC3F,MAAM,WAAW,kBAAkB;IACjC,gGAAgG;IAChG,MAAM,EAAE,gBAAgB,CAAC;IACzB,gGAAgG;IAChG,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,gGAAgG;IAChG,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,mFAAmF;IACnF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uFAAuF;IACvF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,uFAAuF;IACvF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,+CAA+C;IAC/C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,+CAA+C;IAC/C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,4CAA4C;IAC5C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6CAA6C;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,4CAA4C;IAC5C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CAClC;AAED,+FAA+F;AAC/F,MAAM,WAAW,WAAW;IAC1B,mGAAmG;IACnG,IAAI,EAAE,MAAM,CAAC;IACb,kGAAkG;IAClG,YAAY,EAAE,MAAM,CAAC;IACrB;mGAC+F;IAC/F,UAAU,EAAE,iBAAiB,CAAC;IAC9B;;;;;OAKG;IACH,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACzD;oFACgF;IAChF,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5B;8EAC0E;IAC1E,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,0CAA0C;IAC1C,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAqSD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,WAAW,CAAC,CAIrF"}
|
|
@@ -83,6 +83,9 @@ export const DEFAULT_CLOSE_TIMEOUT_MS = 2000;
|
|
|
83
83
|
const OUTPUT_TAIL_CAP = 16384;
|
|
84
84
|
/** Filename prefix of the per-session scratch settings file (diagnosable in `ls /tmp`). */
|
|
85
85
|
export const SCRATCH_SETTINGS_PREFIX = "fork-acp-gate-settings-";
|
|
86
|
+
/** Story 046 (R3) — the file-edit tools that `acceptEdits` auto-allows, mirroring claude's native
|
|
87
|
+
* acceptEdits semantics (edits proceed without prompting; every other tool still asks). */
|
|
88
|
+
const EDIT_TOOLS = new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
86
89
|
const defaultSchedule = (fn, ms) => {
|
|
87
90
|
setTimeout(fn, ms);
|
|
88
91
|
};
|
|
@@ -161,6 +164,17 @@ class SessionGateImpl {
|
|
|
161
164
|
async decide(call) {
|
|
162
165
|
if (this.torndown)
|
|
163
166
|
return "deny"; // a hook racing teardown is never approved
|
|
167
|
+
// === Story 046 (R3) — honor the live permission mode BEFORE relaying to Zed. =================
|
|
168
|
+
// The hook payload carries the current mode (probe-d confirmed `permission_mode` matches the
|
|
169
|
+
// selected mode in acceptEdits/bypassPermissions). Without this branch the gate raises
|
|
170
|
+
// session/request_permission for EVERY tool, overriding the panel's mode selector — the user sees
|
|
171
|
+
// a Zed prompt in every mode, even bypass. bypassPermissions auto-allows ALL tools; acceptEdits
|
|
172
|
+
// auto-allows edit-class tools; default/plan/auto/dontAsk fall through to the ACP relay (ask Zed).
|
|
173
|
+
// No #52822 sweep on this path: these modes make claude auto-proceed, so no native prompt renders.
|
|
174
|
+
if (call.permissionMode === "bypassPermissions")
|
|
175
|
+
return "allow";
|
|
176
|
+
if (call.permissionMode === "acceptEdits" && EDIT_TOOLS.has(call.toolName))
|
|
177
|
+
return "allow";
|
|
164
178
|
// Kick the pump so the freshest JSONL (carrying this call's `tool_use` line) is re-read NOW.
|
|
165
179
|
try {
|
|
166
180
|
this.nudge?.();
|
package/package.json
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
7
|
-
"description": "
|
|
6
|
+
"version": "0.3.0",
|
|
7
|
+
"description": "Run the official Claude Code TUI in Zed's agent panel on your Claude Pro/Max subscription — an ACP-over-PTY bridge created for Anthropic's June 2026 billing split.",
|
|
8
8
|
"main": "dist/lib.js",
|
|
9
9
|
"types": "dist/lib.d.ts",
|
|
10
10
|
"bin": {
|
|
@@ -60,20 +60,20 @@
|
|
|
60
60
|
"license": "Apache-2.0",
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@agentclientprotocol/sdk": "0.25.0",
|
|
63
|
-
"@anthropic-ai/claude-agent-sdk": "0.3.
|
|
63
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.175",
|
|
64
64
|
"node-pty": "1.1.0",
|
|
65
65
|
"zod": "^3.25.0 || ^4.0.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@anthropic-ai/sdk": "0.
|
|
68
|
+
"@anthropic-ai/sdk": "0.104.1",
|
|
69
69
|
"@eslint/js": "10.0.1",
|
|
70
|
-
"@types/node": "25.9.
|
|
71
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
72
|
-
"@typescript-eslint/parser": "8.
|
|
70
|
+
"@types/node": "25.9.3",
|
|
71
|
+
"@typescript-eslint/eslint-plugin": "8.61.0",
|
|
72
|
+
"@typescript-eslint/parser": "8.61.0",
|
|
73
73
|
"eslint": "10.4.1",
|
|
74
74
|
"eslint-config-prettier": "10.1.8",
|
|
75
75
|
"globals": "17.6.0",
|
|
76
|
-
"prettier": "3.8.
|
|
76
|
+
"prettier": "3.8.4",
|
|
77
77
|
"ts-node": "10.9.2",
|
|
78
78
|
"typescript": "6.0.3"
|
|
79
79
|
}
|