@lucascouts/claude-agent-tui 0.5.2 → 0.6.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/dist/acp-agent.d.ts +187 -13
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +444 -59
- package/dist/agent-catalog.d.ts +96 -0
- package/dist/agent-catalog.d.ts.map +1 -0
- package/dist/agent-catalog.js +287 -0
- package/dist/claude-path.d.ts.map +1 -1
- package/dist/claude-path.js +6 -0
- package/dist/end-of-turn.d.ts +6 -0
- package/dist/end-of-turn.d.ts.map +1 -1
- package/dist/end-of-turn.js +8 -1
- package/dist/engine-lifecycle.d.ts +66 -1
- package/dist/engine-lifecycle.d.ts.map +1 -1
- package/dist/engine-lifecycle.js +43 -4
- package/dist/engine-pty.d.ts +70 -2
- package/dist/engine-pty.d.ts.map +1 -1
- package/dist/engine-pty.js +80 -6
- package/dist/gate/settings-writer.d.ts +14 -0
- package/dist/gate/settings-writer.d.ts.map +1 -1
- package/dist/gate/settings-writer.js +49 -0
- package/dist/image-input.d.ts +31 -0
- package/dist/image-input.d.ts.map +1 -0
- package/dist/image-input.js +79 -0
- package/dist/image-vision-smoke.d.ts +52 -0
- package/dist/image-vision-smoke.d.ts.map +1 -0
- package/dist/image-vision-smoke.js +111 -0
- package/dist/index.js +6 -0
- package/dist/mcp-config-writer.d.ts +61 -0
- package/dist/mcp-config-writer.d.ts.map +1 -0
- package/dist/mcp-config-writer.js +172 -0
- package/dist/model-catalog.d.ts +29 -2
- package/dist/model-catalog.d.ts.map +1 -1
- package/dist/model-catalog.js +50 -10
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +9 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +5 -1
- package/dist/usage.d.ts +3 -0
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +3 -0
- package/package.json +8 -8
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// === Story 058 / Task 3.1 (R3.1, R3.2) — version-aware image-vision smoke (pure verdict + fail-loud) ==
|
|
2
|
+
//
|
|
3
|
+
// The whole "image input via @path" feature rests on a VERSION-FRAGILE claude behavior: a prompt
|
|
4
|
+
// containing `@<path>` makes the native TUI invoke the Read tool, and Read vision-encodes the image so
|
|
5
|
+
// the model can see it. This was PROVEN on claude 2.1.195 (story 058 research — two-arm proof: a blue
|
|
6
|
+
// image vs a green image under the same filename, each correctly described). It did NOT work on claude
|
|
7
|
+
// 2.1.170 (story 030): there, `@path` did not vision-encode, so an image prompt silently dropped its
|
|
8
|
+
// picture.
|
|
9
|
+
//
|
|
10
|
+
// Because the behavior is version-fragile, this module makes a too-old / unknown `claude` LOUD instead
|
|
11
|
+
// of silently dropping the image. It mirrors story 049's drift discipline:
|
|
12
|
+
// - the VERDICT is PURE (no I/O — like src/drift-checks.ts's `{surface, ok, detail}` checks), and
|
|
13
|
+
// - the REPORTER is best-effort + fail-loud one-liner (like src/claude-path.ts's reportVersionDrift,
|
|
14
|
+
// `[claude-path] …`); here the prefix is `[image-vision]`.
|
|
15
|
+
//
|
|
16
|
+
// CRITICAL — OBSERVABILITY ONLY, NEVER A GATE (R3.1): this smoke NEVER blocks the prompt and NEVER
|
|
17
|
+
// touches `promptCapabilities.image`, which stays literally `image: true` (acp-agent.ts:1413),
|
|
18
|
+
// UNCONDITIONALLY. The verdict's only output is `{confirmed, detail}` — there is deliberately NO field
|
|
19
|
+
// or return shape here that could disable a capability. A too-old `claude` produces a WARNING, not a
|
|
20
|
+
// degraded capability: the image still rides the @path delivery; we just warn that this `claude` is not
|
|
21
|
+
// a version we have confirmed vision-encodes it.
|
|
22
|
+
//
|
|
23
|
+
// LIMITATION (documented honestly): this is an OFFLINE `>= anchor` version-gate. It catches too-old and
|
|
24
|
+
// undetected/unparseable versions, but it CANNOT catch a *future regression* — a hypothetical 2.1.300
|
|
25
|
+
// that re-breaks @path-vision would still parse as `>= 2.1.195` and report confirmed. Catching a future
|
|
26
|
+
// regression needs the LIVE smoke (opt-in — story 058 R4 probe: actually send @image and check the
|
|
27
|
+
// model saw it), which this offline gate intentionally does NOT implement.
|
|
28
|
+
//
|
|
29
|
+
// node:test runner: `node --experimental-strip-types --test test/image-vision-smoke.test.ts`
|
|
30
|
+
/**
|
|
31
|
+
* The claude version where `@<path>` → Read-vision was PROVEN to work (story 058 research).
|
|
32
|
+
*
|
|
33
|
+
* WHY this exact anchor: on claude 2.1.170 a `@path` image prompt did NOT vision-encode the picture —
|
|
34
|
+
* the TUI did not turn it into a Read the model could see (story 030). On claude 2.1.195 it DOES: the
|
|
35
|
+
* two-arm proof (a blue vs a green image under the same uuid filename, each described correctly) showed
|
|
36
|
+
* `@path` → Read vision-encodes. So 2.1.195 is the earliest version we are willing to call "confirmed";
|
|
37
|
+
* anything below is treated as unconfirmed and warned about.
|
|
38
|
+
*/
|
|
39
|
+
export const VISION_CONFIRMED_CLAUDE_VERSION = "2.1.195";
|
|
40
|
+
/**
|
|
41
|
+
* Compare two `x.y.z` semver strings: `true` iff `a >= b` componentwise (major, then minor, then patch).
|
|
42
|
+
*
|
|
43
|
+
* Splits on `.`, parses the three leading numeric components; a missing/non-numeric component counts as
|
|
44
|
+
* `0` (so `"2.1"` reads as `2.1.0`). Private to this module — exported only so the unit test can pin the
|
|
45
|
+
* ordering directly. Pure.
|
|
46
|
+
*/
|
|
47
|
+
export function versionGte(a, b) {
|
|
48
|
+
const partsA = a.split(".");
|
|
49
|
+
const partsB = b.split(".");
|
|
50
|
+
for (let i = 0; i < 3; i++) {
|
|
51
|
+
const na = toComponent(partsA[i]);
|
|
52
|
+
const nb = toComponent(partsB[i]);
|
|
53
|
+
if (na !== nb) {
|
|
54
|
+
return na > nb;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return true; // all three components equal → a >= b holds
|
|
58
|
+
}
|
|
59
|
+
/** Parse one semver component to a non-negative integer; missing / non-numeric → 0. */
|
|
60
|
+
function toComponent(part) {
|
|
61
|
+
if (part === undefined) {
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
const n = Number.parseInt(part, 10);
|
|
65
|
+
return Number.isNaN(n) ? 0 : n;
|
|
66
|
+
}
|
|
67
|
+
/** Matches a leading `x.y.z` triple — same shape parseClaudeVersion extracts (claude-path.ts:58). */
|
|
68
|
+
const SEMVER_RE = /^(\d+\.\d+\.\d+)/;
|
|
69
|
+
/**
|
|
70
|
+
* PURE verdict: is the running `claude` `version` one we have CONFIRMED vision-encodes `@image` via Read?
|
|
71
|
+
*
|
|
72
|
+
* `confirmed: true` IFF `version` is a parseable leading `x.y.z` that is `>= {@link
|
|
73
|
+
* VISION_CONFIRMED_CLAUDE_VERSION}`. In that case `detail` is a positive one-liner.
|
|
74
|
+
*
|
|
75
|
+
* Otherwise (`null` / `undefined` / unparseable, OR a parseable version BELOW the anchor) `confirmed:
|
|
76
|
+
* false` and `detail` is the fail-loud WARNING text — it NAMES the detected version (or "undetected")
|
|
77
|
+
* and the confirmed anchor, and states that the image is NOT blocked (R3.1). No I/O; depends only on
|
|
78
|
+
* its argument.
|
|
79
|
+
*/
|
|
80
|
+
export function imageVisionSmoke(version) {
|
|
81
|
+
const parsed = typeof version === "string" ? SEMVER_RE.exec(version) : null;
|
|
82
|
+
if (parsed && versionGte(parsed[1], VISION_CONFIRMED_CLAUDE_VERSION)) {
|
|
83
|
+
return {
|
|
84
|
+
confirmed: true,
|
|
85
|
+
detail: `claude ${parsed[1]} >= ${VISION_CONFIRMED_CLAUDE_VERSION} — @image is confirmed to vision-encode via Read`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const named = parsed ? parsed[1] : "undetected";
|
|
89
|
+
return {
|
|
90
|
+
confirmed: false,
|
|
91
|
+
detail: `@image vision-encoding is UNCONFIRMED on this claude (detected ${named}; confirmed at ` +
|
|
92
|
+
`${VISION_CONFIRMED_CLAUDE_VERSION}+). Sending images anyway — image input is NOT blocked — but ` +
|
|
93
|
+
`if the picture is ignored, upgrade claude to ${VISION_CONFIRMED_CLAUDE_VERSION} or newer`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Best-effort fail-loud reporter: compute {@link imageVisionSmoke} and, WHEN unconfirmed, log the
|
|
98
|
+
* verdict's `detail` EXACTLY ONCE via `log` with an `[image-vision]` prefix (mirrors claude-path.ts's
|
|
99
|
+
* reportVersionDrift `[claude-path] …` style). Returns the verdict.
|
|
100
|
+
*
|
|
101
|
+
* Logs NOTHING on a confirmed version (a confirmed `claude` is the expected case — no news is good news,
|
|
102
|
+
* matching reportVersionDrift which is silent when there is no drift). NEVER blocks and NEVER throws out
|
|
103
|
+
* of the caller's intent — it is pure aside from the single conditional `log` call (R3.1, R3.2).
|
|
104
|
+
*/
|
|
105
|
+
export function reportImageVisionSmoke(version, log = console.error) {
|
|
106
|
+
const result = imageVisionSmoke(version);
|
|
107
|
+
if (!result.confirmed) {
|
|
108
|
+
log(`[image-vision] ${result.detail}`);
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -88,6 +88,12 @@ else {
|
|
|
88
88
|
// (src/live-subagent-env.ts) so the truth table is unit-checkable; threaded through
|
|
89
89
|
// runAcp → AgentDeps → the agent.
|
|
90
90
|
const liveSubagentWatch = liveSubagentWatchEnabled();
|
|
91
|
+
// Story 056 / Task 7 (R3.5): enable the live agent-discovery probe in PRODUCTION only. The probe
|
|
92
|
+
// (agent-catalog.ts) spawns `claude --agent <sentinel>` once per createSession to read `claude`'s
|
|
93
|
+
// canonical persona list (enabled plugins + built-ins) for the 4th `agent` selector. Gated opt-in so
|
|
94
|
+
// the unit suite — which imports modules directly without this flag — stays on the hermetic glob
|
|
95
|
+
// fallback and never spawns the real binary. `??=` keeps a user opt-out (`FORK_AGENT_PROBE=0`).
|
|
96
|
+
process.env.FORK_AGENT_PROBE ??= "1";
|
|
91
97
|
const { connection, agent } = runAcp({ usageUpdate, gate, liveDiff, liveSubagentWatch });
|
|
92
98
|
async function shutdown() {
|
|
93
99
|
await agent.dispose().catch((err) => {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { McpServer } from "@agentclientprotocol/sdk";
|
|
2
|
+
/**
|
|
3
|
+
* A single entry under claude's `--mcp-config` `mcpServers` map. A discriminated union over the
|
|
4
|
+
* transport claude understands:
|
|
5
|
+
* - http/sse → `{ type, url, headers? }` (headers as a plain `{ [name]: value }` object)
|
|
6
|
+
* - stdio → `{ command, args?, env? }` (NO `type` — claude infers stdio from `command`)
|
|
7
|
+
* ACP-transport servers have no `--mcp-config` equivalent and are dropped before this shape.
|
|
8
|
+
*/
|
|
9
|
+
export type ClaudeMcpEntry = {
|
|
10
|
+
type: "http" | "sse";
|
|
11
|
+
url: string;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
} | {
|
|
14
|
+
command: string;
|
|
15
|
+
args?: string[];
|
|
16
|
+
env?: Record<string, string>;
|
|
17
|
+
};
|
|
18
|
+
/** The full claude `--mcp-config` document: a name-keyed map of {@link ClaudeMcpEntry}. */
|
|
19
|
+
export interface ClaudeMcpConfig {
|
|
20
|
+
mcpServers: Record<string, ClaudeMcpEntry>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Translate the ACP `McpServer[]` union into claude's `--mcp-config` `{ mcpServers: { … } }` shape
|
|
24
|
+
* (R2.1). PURE and total — never throws; empty/undefined input yields `{ mcpServers: {} }`.
|
|
25
|
+
*
|
|
26
|
+
* Discriminator (per the ACP schema): `type === "http"|"sse"|"acp"` selects that transport; a server
|
|
27
|
+
* with NO `type` field is the bare stdio variant. Per-transport mapping:
|
|
28
|
+
* - http / sse → `{ type, url, headers? }`; `headers` is OMITTED when the ACP array is empty.
|
|
29
|
+
* - stdio → `{ command, args?, env? }`; `args` and `env` are each OMITTED when empty; NO `type`.
|
|
30
|
+
* - acp → DROPPED (ACP-channel transport has no `--mcp-config` equivalent); a one-line note
|
|
31
|
+
* is emitted to stderr (no url/headers/env — R2.3).
|
|
32
|
+
*
|
|
33
|
+
* Duplicate names collapse onto the same key (last wins) — claude's map is name-keyed, so this matches
|
|
34
|
+
* how claude itself would resolve a repeated server name.
|
|
35
|
+
*
|
|
36
|
+
* @param servers the ACP-declared MCP servers, or `undefined`/`[]` when the client declared none.
|
|
37
|
+
* @returns the claude `--mcp-config` document (possibly with an empty `mcpServers`).
|
|
38
|
+
*/
|
|
39
|
+
export declare function translateMcpServers(servers: readonly McpServer[] | undefined): ClaudeMcpConfig;
|
|
40
|
+
/**
|
|
41
|
+
* DURABLY write `config` to a fresh uuid-namespaced scratch file under the OS temp dir with mode
|
|
42
|
+
* `0600`, and RETURN its absolute path (R2.1, R2.3). The file is `JSON.stringify(config, null, 2)`
|
|
43
|
+
* plus a trailing newline. The caller passes the returned path to `claude --mcp-config <path>` and
|
|
44
|
+
* is responsible for removing it on teardown via {@link removeMcpScratch}.
|
|
45
|
+
*
|
|
46
|
+
* The file may carry MCP auth headers/env (secrets), so it is created owner-only (0600) and is NEVER
|
|
47
|
+
* logged here (R2.3).
|
|
48
|
+
*
|
|
49
|
+
* @param config the document from {@link translateMcpServers}.
|
|
50
|
+
* @returns the absolute path of the written scratch file.
|
|
51
|
+
* @throws {Error} if the durable write fails — the partial temp is cleaned up first (no residue).
|
|
52
|
+
*/
|
|
53
|
+
export declare function writeMcpScratch(config: ClaudeMcpConfig): Promise<string>;
|
|
54
|
+
/**
|
|
55
|
+
* Idempotently remove a scratch file written by {@link writeMcpScratch} (R2.3 teardown). Uses
|
|
56
|
+
* `fs.rm(filePath, { force: true })`, so a missing/already-removed path is a no-op and NEVER throws.
|
|
57
|
+
*
|
|
58
|
+
* @param filePath the path returned by {@link writeMcpScratch}.
|
|
59
|
+
*/
|
|
60
|
+
export declare function removeMcpScratch(filePath: string): Promise<void>;
|
|
61
|
+
//# sourceMappingURL=mcp-config-writer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-config-writer.d.ts","sourceRoot":"","sources":["../src/mcp-config-writer.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,SAAS,EAA2B,MAAM,0BAA0B,CAAC;AAEnF;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GACvE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC;AAEvE,2FAA2F;AAC3F,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CAC5C;AAWD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,GAAG,eAAe,CA2C9F;AAqDD;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAK9E;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtE"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// Story 057 / Task 2.1 — translate ACP MCP servers → claude `--mcp-config` JSON + scratch-file seam.
|
|
2
|
+
//
|
|
3
|
+
// The fork spawns the real `claude` TUI; claude consumes externally-declared MCP servers via a
|
|
4
|
+
// `--mcp-config <file.json>` flag (a `{ mcpServers: { <name>: { … } } }` document). The Zed client,
|
|
5
|
+
// however, declares its MCP servers over ACP as a `McpServer[]` array (a discriminated union). This
|
|
6
|
+
// module is the standalone, OFFLINE-testable translation + scratch-file seam between the two:
|
|
7
|
+
//
|
|
8
|
+
// translateMcpServers(servers) → ClaudeMcpConfig (PURE — array → claude `{mcpServers:{…}}` shape)
|
|
9
|
+
// writeMcpScratch(config) → Promise<string> (DURABLE 0600 write → returns the scratch path)
|
|
10
|
+
// removeMcpScratch(path) → Promise<void> (idempotent unlink on teardown)
|
|
11
|
+
//
|
|
12
|
+
// It owns NO engine wiring — the before-spawn `--mcp-config` plumbing into acp-agent.ts/the engine is
|
|
13
|
+
// a separate Task (2.2/2.3). The scratch-JSON discipline (temp-file → fsync → atomic rename, with a
|
|
14
|
+
// guaranteed 0600 mode) mirrors `gate/settings-writer.ts`'s private `durableWrite`; that pattern is
|
|
15
|
+
// copied here rather than imported (it is not exported there).
|
|
16
|
+
//
|
|
17
|
+
// SECRETS (R2.3): the scratch file may carry MCP auth headers / env / URLs. Its mode is forced to
|
|
18
|
+
// `0600` (owner-only) and it is removable on teardown. The config object, headers, env, and url are
|
|
19
|
+
// NEVER logged — the ONLY log this module emits is a one-line note when an `acp`-transport server is
|
|
20
|
+
// dropped, and that note carries no secret-bearing field.
|
|
21
|
+
import * as fs from "node:fs/promises";
|
|
22
|
+
import * as path from "node:path";
|
|
23
|
+
import * as os from "node:os";
|
|
24
|
+
import { randomUUID } from "node:crypto";
|
|
25
|
+
/** Convert an ACP `{name,value}[]` list into a plain `{ [name]: value }` object. */
|
|
26
|
+
function pairsToObject(pairs) {
|
|
27
|
+
const out = {};
|
|
28
|
+
for (const { name, value } of pairs) {
|
|
29
|
+
out[name] = value;
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Translate the ACP `McpServer[]` union into claude's `--mcp-config` `{ mcpServers: { … } }` shape
|
|
35
|
+
* (R2.1). PURE and total — never throws; empty/undefined input yields `{ mcpServers: {} }`.
|
|
36
|
+
*
|
|
37
|
+
* Discriminator (per the ACP schema): `type === "http"|"sse"|"acp"` selects that transport; a server
|
|
38
|
+
* with NO `type` field is the bare stdio variant. Per-transport mapping:
|
|
39
|
+
* - http / sse → `{ type, url, headers? }`; `headers` is OMITTED when the ACP array is empty.
|
|
40
|
+
* - stdio → `{ command, args?, env? }`; `args` and `env` are each OMITTED when empty; NO `type`.
|
|
41
|
+
* - acp → DROPPED (ACP-channel transport has no `--mcp-config` equivalent); a one-line note
|
|
42
|
+
* is emitted to stderr (no url/headers/env — R2.3).
|
|
43
|
+
*
|
|
44
|
+
* Duplicate names collapse onto the same key (last wins) — claude's map is name-keyed, so this matches
|
|
45
|
+
* how claude itself would resolve a repeated server name.
|
|
46
|
+
*
|
|
47
|
+
* @param servers the ACP-declared MCP servers, or `undefined`/`[]` when the client declared none.
|
|
48
|
+
* @returns the claude `--mcp-config` document (possibly with an empty `mcpServers`).
|
|
49
|
+
*/
|
|
50
|
+
export function translateMcpServers(servers) {
|
|
51
|
+
const mcpServers = {};
|
|
52
|
+
if (!servers) {
|
|
53
|
+
return { mcpServers };
|
|
54
|
+
}
|
|
55
|
+
for (const server of servers) {
|
|
56
|
+
// The ACP union is NOT a uniform discriminated union: http/sse/acp carry a `type` tag, but the
|
|
57
|
+
// stdio variant has NO `type` field at all. So a `"type" in server` guard (not `server.type`)
|
|
58
|
+
// first splits the tagged members from the bare stdio variant; within the tagged set, `type`
|
|
59
|
+
// then selects http/sse vs acp.
|
|
60
|
+
if ("type" in server) {
|
|
61
|
+
if (server.type === "http" || server.type === "sse") {
|
|
62
|
+
const entry = { type: server.type, url: server.url };
|
|
63
|
+
if (server.headers.length > 0) {
|
|
64
|
+
entry.headers = pairsToObject(server.headers);
|
|
65
|
+
}
|
|
66
|
+
mcpServers[server.name] = entry;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// type === "acp": ACP-transport servers ride the ACP channel (mcp/connect…); claude's
|
|
70
|
+
// --mcp-config cannot express them, so drop. Log ONLY the (non-secret) name — never
|
|
71
|
+
// id/url/headers/env (R2.3).
|
|
72
|
+
console.error(`[mcp-config-writer] dropping ACP-transport MCP server ${JSON.stringify(server.name)}: ` +
|
|
73
|
+
`no --mcp-config equivalent (ACP-channel transport is not supported by the claude CLI).`);
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
// No `type` field → the bare stdio variant: `{ command, args, env }`. NO `type` is emitted
|
|
78
|
+
// (claude infers stdio from `command`); empty `args`/`env` are omitted.
|
|
79
|
+
const entry = { command: server.command };
|
|
80
|
+
if (server.args.length > 0) {
|
|
81
|
+
entry.args = [...server.args];
|
|
82
|
+
}
|
|
83
|
+
if (server.env.length > 0) {
|
|
84
|
+
entry.env = pairsToObject(server.env);
|
|
85
|
+
}
|
|
86
|
+
mcpServers[server.name] = entry;
|
|
87
|
+
}
|
|
88
|
+
return { mcpServers };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Durably write `text` to `filePath` with mode `0600` via a temp-file → fsync → atomic `rename` so a
|
|
92
|
+
* reader (claude on startup) never observes a partial file. The 0600 mode is GUARANTEED twice: the
|
|
93
|
+
* temp is created with `mode: 0o600`, and `chmod(0o600)` is re-applied after the rename so a permissive
|
|
94
|
+
* umask cannot loosen it. On any failure the partial temp is removed before the error propagates.
|
|
95
|
+
*
|
|
96
|
+
* Mirrors `gate/settings-writer.ts`'s private `durableWrite` (copied — that helper is not exported),
|
|
97
|
+
* with the added 0600 guarantee this scratch file requires (R2.3).
|
|
98
|
+
*/
|
|
99
|
+
async function durableWrite0600(filePath, text) {
|
|
100
|
+
const dir = path.dirname(filePath);
|
|
101
|
+
const tmp = path.join(dir, `.${path.basename(filePath)}.fork-tmp-${process.pid}-${Date.now()}`);
|
|
102
|
+
let handle = null;
|
|
103
|
+
try {
|
|
104
|
+
// Create the temp owner-only from the outset so its bytes are never world-readable, even briefly.
|
|
105
|
+
handle = await fs.open(tmp, "w", 0o600);
|
|
106
|
+
await handle.writeFile(text, "utf8");
|
|
107
|
+
await handle.sync(); // flush the file's own bytes to disk before the rename
|
|
108
|
+
await handle.close();
|
|
109
|
+
handle = null;
|
|
110
|
+
await fs.rename(tmp, filePath); // atomic swap into place
|
|
111
|
+
// Re-assert 0600 AFTER the rename so the active umask cannot have loosened the open() mode.
|
|
112
|
+
await fs.chmod(filePath, 0o600);
|
|
113
|
+
// Best-effort fsync of the directory entry so the rename itself is durable.
|
|
114
|
+
try {
|
|
115
|
+
const dirHandle = await fs.open(dir, "r");
|
|
116
|
+
try {
|
|
117
|
+
await dirHandle.sync();
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
await dirHandle.close();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// Some platforms reject O_RDONLY dir fsync; the file fsync above is the load-bearing one.
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
if (handle !== null) {
|
|
129
|
+
try {
|
|
130
|
+
await handle.close();
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// ignore
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
await fs.rm(tmp, { force: true });
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// ignore — nothing durable to clean
|
|
141
|
+
}
|
|
142
|
+
throw err;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* DURABLY write `config` to a fresh uuid-namespaced scratch file under the OS temp dir with mode
|
|
147
|
+
* `0600`, and RETURN its absolute path (R2.1, R2.3). The file is `JSON.stringify(config, null, 2)`
|
|
148
|
+
* plus a trailing newline. The caller passes the returned path to `claude --mcp-config <path>` and
|
|
149
|
+
* is responsible for removing it on teardown via {@link removeMcpScratch}.
|
|
150
|
+
*
|
|
151
|
+
* The file may carry MCP auth headers/env (secrets), so it is created owner-only (0600) and is NEVER
|
|
152
|
+
* logged here (R2.3).
|
|
153
|
+
*
|
|
154
|
+
* @param config the document from {@link translateMcpServers}.
|
|
155
|
+
* @returns the absolute path of the written scratch file.
|
|
156
|
+
* @throws {Error} if the durable write fails — the partial temp is cleaned up first (no residue).
|
|
157
|
+
*/
|
|
158
|
+
export async function writeMcpScratch(config) {
|
|
159
|
+
const filePath = path.join(os.tmpdir(), `fork-acp-mcp-${randomUUID()}.json`);
|
|
160
|
+
const text = `${JSON.stringify(config, null, 2)}\n`;
|
|
161
|
+
await durableWrite0600(filePath, text);
|
|
162
|
+
return filePath;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Idempotently remove a scratch file written by {@link writeMcpScratch} (R2.3 teardown). Uses
|
|
166
|
+
* `fs.rm(filePath, { force: true })`, so a missing/already-removed path is a no-op and NEVER throws.
|
|
167
|
+
*
|
|
168
|
+
* @param filePath the path returned by {@link writeMcpScratch}.
|
|
169
|
+
*/
|
|
170
|
+
export async function removeMcpScratch(filePath) {
|
|
171
|
+
await fs.rm(filePath, { force: true });
|
|
172
|
+
}
|
package/dist/model-catalog.d.ts
CHANGED
|
@@ -1,9 +1,36 @@
|
|
|
1
1
|
import type { ModelInfo } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Effort levels offered for reasoning-capable models — the SDK's `supportedEffortLevels` vocabulary.
|
|
4
|
+
* Story 060: exported so the ultracode guard test can assert membership stays the five real levels
|
|
5
|
+
* (it must NEVER contain {@link ULTRACODE_EFFORT}; the real `--effort` enum is exactly this set).
|
|
6
|
+
*/
|
|
7
|
+
export declare const REASONING_EFFORT_LEVELS: NonNullable<ModelInfo["supportedEffortLevels"]>;
|
|
8
|
+
/**
|
|
9
|
+
* Story 060 — the "ultracode" pseudo-level sentinel for the effort selector. NOT a real `--effort`
|
|
10
|
+
* value (claude 2.1.195 rejects `--effort ultracode`; the enum stays {@link REASONING_EFFORT_LEVELS}).
|
|
11
|
+
* Selecting it maps to `--effort xhigh` PLUS activating the ultracode keyword-trigger for the session
|
|
12
|
+
* (story 060 Task 3). Kept OUT of REASONING_EFFORT_LEVELS so no real-effort code path ever emits it.
|
|
13
|
+
*/
|
|
14
|
+
export declare const ULTRACODE_EFFORT = "ultracode";
|
|
15
|
+
/** The real effort level the ultracode pseudo-level maps to (its documented effort component). */
|
|
16
|
+
export declare const ULTRACODE_EFFORT_LEVEL = "xhigh";
|
|
17
|
+
/** Selector label — makes clear it is xhigh + orchestration, NOT a duplicate of the plain `xhigh` entry. */
|
|
18
|
+
export declare const ULTRACODE_EFFORT_LABEL = "ultracode (xhigh + orchestration)";
|
|
2
19
|
/**
|
|
3
20
|
* The static curated catalog advertised to the Zed Agent Panel's `model` selector. Each `value` is a
|
|
4
21
|
* `claude` TUI alias accepted by `/model <alias>` (live) and `--model <alias>` (spawn). `default` is
|
|
5
|
-
* first and is the safe fallback.
|
|
6
|
-
*
|
|
22
|
+
* first and is the safe fallback.
|
|
23
|
+
*
|
|
24
|
+
* ORDER + membership mirror the ORIGINAL's `/model` picker, LIVE-VERIFIED against 2.1.195 (PTY probe):
|
|
25
|
+
* `Default (recommended)`, `Opus`, `Sonnet`, `Sonnet (1M context)`, `Haiku`. The original gets this
|
|
26
|
+
* from the SDK `supportedModels()` the fork cut, so we curate it statically. The 1M alias is literally
|
|
27
|
+
* `sonnet[1m]` (`/model sonnet[1m]` accepted; `sonnet-1m` → "not found"). `opusplan` is a fork-only
|
|
28
|
+
* EXTRA (a real `claude` plan-mode preset the original does not list) kept as the trailing entry.
|
|
29
|
+
*
|
|
30
|
+
* Effort-capable models (`default`/`opus`/`sonnet`/`sonnet[1m]`) carry `supportsEffort` +
|
|
31
|
+
* `supportedEffortLevels`; `haiku`/`opusplan` advertise none. `supportsAutoMode: true` on the same four
|
|
32
|
+
* surfaces the `auto` permission mode (a model classifier) — the original drops `auto` on `haiku`
|
|
33
|
+
* (the SDK signal `reconcileModeFromTranscript` clamps), so haiku/opusplan omit it.
|
|
7
34
|
*/
|
|
8
35
|
export declare const MODEL_CATALOG: ModelInfo[];
|
|
9
36
|
/** The safe fallback entry, kept as a named export so callers can seed/anchor on it without a lookup. */
|
|
@@ -1 +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;
|
|
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;AAEhE;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,EAAE,WAAW,CAAC,SAAS,CAAC,uBAAuB,CAAC,CAMnF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAE5C,kGAAkG;AAClG,eAAO,MAAM,sBAAsB,UAAU,CAAC;AAE9C,4GAA4G;AAC5G,eAAO,MAAM,sBAAsB,sCAAsC,CAAC;AAE1E;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,EA6CpC,CAAC;AAEF,yGAAyG;AACzG,eAAO,MAAM,kBAAkB,EAAE,SAA4B,CAAC"}
|
package/dist/model-catalog.js
CHANGED
|
@@ -1,20 +1,58 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Effort levels offered for reasoning-capable models — the SDK's `supportedEffortLevels` vocabulary.
|
|
3
|
+
* Story 060: exported so the ultracode guard test can assert membership stays the five real levels
|
|
4
|
+
* (it must NEVER contain {@link ULTRACODE_EFFORT}; the real `--effort` enum is exactly this set).
|
|
5
|
+
*/
|
|
6
|
+
export const REASONING_EFFORT_LEVELS = [
|
|
3
7
|
"low",
|
|
4
8
|
"medium",
|
|
5
9
|
"high",
|
|
10
|
+
"xhigh",
|
|
11
|
+
"max",
|
|
6
12
|
];
|
|
13
|
+
/**
|
|
14
|
+
* Story 060 — the "ultracode" pseudo-level sentinel for the effort selector. NOT a real `--effort`
|
|
15
|
+
* value (claude 2.1.195 rejects `--effort ultracode`; the enum stays {@link REASONING_EFFORT_LEVELS}).
|
|
16
|
+
* Selecting it maps to `--effort xhigh` PLUS activating the ultracode keyword-trigger for the session
|
|
17
|
+
* (story 060 Task 3). Kept OUT of REASONING_EFFORT_LEVELS so no real-effort code path ever emits it.
|
|
18
|
+
*/
|
|
19
|
+
export const ULTRACODE_EFFORT = "ultracode";
|
|
20
|
+
/** The real effort level the ultracode pseudo-level maps to (its documented effort component). */
|
|
21
|
+
export const ULTRACODE_EFFORT_LEVEL = "xhigh";
|
|
22
|
+
/** Selector label — makes clear it is xhigh + orchestration, NOT a duplicate of the plain `xhigh` entry. */
|
|
23
|
+
export const ULTRACODE_EFFORT_LABEL = "ultracode (xhigh + orchestration)";
|
|
7
24
|
/**
|
|
8
25
|
* The static curated catalog advertised to the Zed Agent Panel's `model` selector. Each `value` is a
|
|
9
26
|
* `claude` TUI alias accepted by `/model <alias>` (live) and `--model <alias>` (spawn). `default` is
|
|
10
|
-
* first and is the safe fallback.
|
|
11
|
-
*
|
|
27
|
+
* first and is the safe fallback.
|
|
28
|
+
*
|
|
29
|
+
* ORDER + membership mirror the ORIGINAL's `/model` picker, LIVE-VERIFIED against 2.1.195 (PTY probe):
|
|
30
|
+
* `Default (recommended)`, `Opus`, `Sonnet`, `Sonnet (1M context)`, `Haiku`. The original gets this
|
|
31
|
+
* from the SDK `supportedModels()` the fork cut, so we curate it statically. The 1M alias is literally
|
|
32
|
+
* `sonnet[1m]` (`/model sonnet[1m]` accepted; `sonnet-1m` → "not found"). `opusplan` is a fork-only
|
|
33
|
+
* EXTRA (a real `claude` plan-mode preset the original does not list) kept as the trailing entry.
|
|
34
|
+
*
|
|
35
|
+
* Effort-capable models (`default`/`opus`/`sonnet`/`sonnet[1m]`) carry `supportsEffort` +
|
|
36
|
+
* `supportedEffortLevels`; `haiku`/`opusplan` advertise none. `supportsAutoMode: true` on the same four
|
|
37
|
+
* surfaces the `auto` permission mode (a model classifier) — the original drops `auto` on `haiku`
|
|
38
|
+
* (the SDK signal `reconcileModeFromTranscript` clamps), so haiku/opusplan omit it.
|
|
12
39
|
*/
|
|
13
40
|
export const MODEL_CATALOG = [
|
|
14
41
|
{
|
|
15
42
|
value: "default",
|
|
16
|
-
displayName: "Default",
|
|
17
|
-
description: "Use the model the claude TUI is configured with (safe fallback).",
|
|
43
|
+
displayName: "Default (recommended)",
|
|
44
|
+
description: "Use the model the claude TUI is configured with (safe fallback); supports reasoning effort.",
|
|
45
|
+
supportsEffort: true,
|
|
46
|
+
supportedEffortLevels: REASONING_EFFORT_LEVELS,
|
|
47
|
+
supportsAutoMode: true,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
value: "opus",
|
|
51
|
+
displayName: "Opus",
|
|
52
|
+
description: "Claude Opus — highest capability; supports reasoning effort.",
|
|
53
|
+
supportsEffort: true,
|
|
54
|
+
supportedEffortLevels: REASONING_EFFORT_LEVELS,
|
|
55
|
+
supportsAutoMode: true,
|
|
18
56
|
},
|
|
19
57
|
{
|
|
20
58
|
value: "sonnet",
|
|
@@ -22,13 +60,15 @@ export const MODEL_CATALOG = [
|
|
|
22
60
|
description: "Claude Sonnet — balanced speed and capability; supports reasoning effort.",
|
|
23
61
|
supportsEffort: true,
|
|
24
62
|
supportedEffortLevels: REASONING_EFFORT_LEVELS,
|
|
63
|
+
supportsAutoMode: true,
|
|
25
64
|
},
|
|
26
65
|
{
|
|
27
|
-
value: "
|
|
28
|
-
displayName: "
|
|
29
|
-
description: "Claude
|
|
66
|
+
value: "sonnet[1m]",
|
|
67
|
+
displayName: "Sonnet (1M context)",
|
|
68
|
+
description: "Claude Sonnet with a 1M-token context window; draws from usage credits; supports reasoning effort.",
|
|
30
69
|
supportsEffort: true,
|
|
31
70
|
supportedEffortLevels: REASONING_EFFORT_LEVELS,
|
|
71
|
+
supportsAutoMode: true,
|
|
32
72
|
},
|
|
33
73
|
{
|
|
34
74
|
value: "haiku",
|
|
@@ -38,7 +78,7 @@ export const MODEL_CATALOG = [
|
|
|
38
78
|
{
|
|
39
79
|
value: "opusplan",
|
|
40
80
|
displayName: "Opus Plan",
|
|
41
|
-
description: "Opus while planning, Sonnet for execution (plan-mode workflow).",
|
|
81
|
+
description: "Opus while planning, Sonnet for execution (plan-mode workflow; fork extra).",
|
|
42
82
|
},
|
|
43
83
|
];
|
|
44
84
|
/** The safe fallback entry, kept as a named export so callers can seed/anchor on it without a lookup. */
|
package/dist/settings.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,QAAQ,EACd,MAAM,gCAAgC,CAAC;AA0BxC,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;QAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;KAAE,CAAC;CAC7E;AAED;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,QAAQ,CAAC,CAAa;IAC9B,OAAO,CAAC,MAAM,CAAqE;IACnF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAA8C;IACnE,OAAO,CAAC,WAAW,CAA8B;gBAErC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB;IAMzD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBjC;;;OAGG;IACH,OAAO,CAAC,eAAe;IASvB;;;OAGG;YACW,eAAe;IAU7B;;OAEG;IACH,OAAO,CAAC,aAAa;
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,QAAQ,EACd,MAAM,gCAAgC,CAAC;AA0BxC,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE;QAAE,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;QAAC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;KAAE,CAAC;CAC7E;AAED;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,QAAQ,CAAC,CAAa;IAC9B,OAAO,CAAC,MAAM,CAAqE;IACnF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAA8C;IACnE,OAAO,CAAC,WAAW,CAA8B;gBAErC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB;IAMzD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBjC;;;OAGG;IACH,OAAO,CAAC,eAAe;IASvB;;;OAGG;YACW,eAAe;IAU7B;;OAEG;IACH,OAAO,CAAC,aAAa;IAgCrB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAuB5B;;OAEG;IACH,WAAW,IAAI,QAAQ;IAIvB;;OAEG;IACH,MAAM,IAAI,MAAM;IAIhB;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUxC;;OAEG;IACH,OAAO,IAAI,IAAI;CAehB"}
|
package/dist/settings.js
CHANGED
|
@@ -106,6 +106,13 @@ export class SettingsManager {
|
|
|
106
106
|
this.handleSettingsChange();
|
|
107
107
|
}
|
|
108
108
|
});
|
|
109
|
+
// Story 059: a best-effort settings watcher must NOT keep the Node event loop alive on its
|
|
110
|
+
// own. A settingsManager that escapes dispose() (an initialize/re-spawn race — see the
|
|
111
|
+
// pre-existing-leak note in acp-agent.ts createSession) otherwise leaves a live fs.watch
|
|
112
|
+
// handle that hangs `node:test` on process exit (the live ACP process is held open by its
|
|
113
|
+
// stdio, never by this watcher). unref() detaches it from the loop's keep-alive set while
|
|
114
|
+
// leaving the subscription fully functional.
|
|
115
|
+
watcher.unref?.();
|
|
109
116
|
watcher.on("error", (error) => {
|
|
110
117
|
this.logger.error(`Settings watcher error for ${filePath}:`, error);
|
|
111
118
|
});
|
|
@@ -139,6 +146,8 @@ export class SettingsManager {
|
|
|
139
146
|
this.logger.error("Failed to reload settings:", error);
|
|
140
147
|
}
|
|
141
148
|
}, 100);
|
|
149
|
+
// Story 059: same rationale as the watcher unref — a pending debounce timer must not block exit.
|
|
150
|
+
this.debounceTimer?.unref?.();
|
|
142
151
|
}
|
|
143
152
|
/**
|
|
144
153
|
* Returns the current merged settings
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,SAAS,EACT,eAAe,EACf,gBAAgB,EAChB,QAAQ,EACT,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAQL,eAAe,EACf,gBAAgB,EAChB,eAAe,EAIhB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EAGL,oBAAoB,EAEpB,6BAA6B,EAE9B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAEL,yCAAyC,EAGzC,qCAAqC,EAGrC,kCAAkC,EAGlC,+CAA+C,EAI/C,wBAAwB,EACxB,kCAAkC,EAIlC,gCAAgC,EAEhC,iCAAiC,EAClC,MAAM,sCAAsC,CAAC;AAE9C,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AA0BxC,UAAU,QAAQ;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAChC;AAED,UAAU,UAAU;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC/B,KAAK,CAAC,EAAE;QACN,aAAa,CAAC,EAAE;YACd,WAAW,EAAE,MAAM,CAAC;SACrB,CAAC;QACF,eAAe,CAAC,EAAE;YAChB,WAAW,EAAE,MAAM,CAAC;YACpB,IAAI,EAAE,MAAM,CAAC;SACd,CAAC;QACF,aAAa,CAAC,EAAE;YACd,WAAW,EAAE,MAAM,CAAC;YACpB,SAAS,EAAE,MAAM,CAAC;YAClB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;SACvB,CAAC;KACH,CAAC;CACH;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAQpE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GACrC,MAAM,CAER;AAED,kFAAkF;AAClF,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,UAAU,EACnB,sBAAsB,GAAE,OAAe,EACvC,GAAG,CAAC,EAAE,MAAM,GACX,QAAQ,CAgUV;AAED,wBAAgB,wBAAwB,CACtC,UAAU,EACN,oBAAoB,GACpB,wBAAwB,GACxB,iCAAiC,GACjC,gCAAgC,GAChC,6BAA6B,GAC7B,qCAAqC,GACrC,yCAAyC,GACzC,+CAA+C,GAC/C,kCAAkC,GAClC,kCAAkC,EACtC,OAAO,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EACtC,sBAAsB,GAAE,OAAe,GACtC,UAAU,
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|
package/dist/tools.js
CHANGED
|
@@ -325,7 +325,11 @@ export function toolUpdateFromToolResult(toolResult, toolUse, supportsTerminalOu
|
|
|
325
325
|
if ("is_error" in toolResult &&
|
|
326
326
|
toolResult.is_error &&
|
|
327
327
|
toolResult.content &&
|
|
328
|
-
toolResult.content.length > 0
|
|
328
|
+
toolResult.content.length > 0 &&
|
|
329
|
+
// Story 056 (#776): a FAILED Bash with a negotiated terminal must still render as terminal
|
|
330
|
+
// output (terminal_output + terminal_exit exit_code 1 via `case "Bash"`), not markdown — so it
|
|
331
|
+
// is excluded from this error-only early-return. Every other errored result is unchanged.
|
|
332
|
+
!(toolUse?.name === "Bash" && supportsTerminalOutput)) {
|
|
329
333
|
// Only return errors
|
|
330
334
|
return toAcpContentUpdate(toolResult.content, true);
|
|
331
335
|
}
|
package/dist/usage.d.ts
CHANGED
|
@@ -32,6 +32,9 @@ export interface UsageOptions {
|
|
|
32
32
|
* • R3.1 — the optional `cost` field is INTENTIONALLY OMITTED and must NEVER be fabricated:
|
|
33
33
|
* the JSONL usage block carries only token counts, and `cost` is optional in the Zed v1 struct,
|
|
34
34
|
* so omitting it is contract-correct (an invented cost would violate §1 "never fabricate").
|
|
35
|
+
* Story 059 EVIDENCE (2026-06-28): an audit of 5169 local transcripts found 0 carrying
|
|
36
|
+
* `total_cost_usd` as a JSON key — it lives only in the SDK `result` envelope (`claude -p`), never
|
|
37
|
+
* in the interactive PTY+tail JSONL mapped here (the 68 substring hits were all conversation text).
|
|
35
38
|
*/
|
|
36
39
|
export declare function toUsageUpdate(message: UsageCarrier, options?: UsageOptions): UsageUpdateNotification | undefined;
|
|
37
40
|
export interface UsageFlagOptions extends UsageOptions {
|
package/dist/usage.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,iFAAiF;AACjF,MAAM,MAAM,uBAAuB,GAAG,OAAO,CAAC,aAAa,EAAE;IAAE,aAAa,EAAE,cAAc,CAAA;CAAE,CAAC,CAAC;AAEhG,yFAAyF;AACzF,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;CAChF;AAED,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED
|
|
1
|
+
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,iFAAiF;AACjF,MAAM,MAAM,uBAAuB,GAAG,OAAO,CAAC,aAAa,EAAE;IAAE,aAAa,EAAE,cAAc,CAAA;CAAE,CAAC,CAAC;AAEhG,yFAAyF;AACzF,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;CAChF;AAED,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,YAAY,EACrB,OAAO,GAAE,YAAiB,GACzB,uBAAuB,GAAG,SAAS,CAUrC;AAED,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,wFAAwF;IACxF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,YAAY,EACrB,OAAO,GAAE,gBAAqB,GAC7B,uBAAuB,EAAE,CAI3B"}
|
package/dist/usage.js
CHANGED
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
* • R3.1 — the optional `cost` field is INTENTIONALLY OMITTED and must NEVER be fabricated:
|
|
23
23
|
* the JSONL usage block carries only token counts, and `cost` is optional in the Zed v1 struct,
|
|
24
24
|
* so omitting it is contract-correct (an invented cost would violate §1 "never fabricate").
|
|
25
|
+
* Story 059 EVIDENCE (2026-06-28): an audit of 5169 local transcripts found 0 carrying
|
|
26
|
+
* `total_cost_usd` as a JSON key — it lives only in the SDK `result` envelope (`claude -p`), never
|
|
27
|
+
* in the interactive PTY+tail JSONL mapped here (the 68 substring hits were all conversation text).
|
|
25
28
|
*/
|
|
26
29
|
export function toUsageUpdate(message, options = {}) {
|
|
27
30
|
const input = message.usage?.input_tokens;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.6.0",
|
|
7
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 billing split (paused June 15; see README).",
|
|
8
8
|
"main": "dist/lib.js",
|
|
9
9
|
"types": "dist/lib.d.ts",
|
|
@@ -60,20 +60,20 @@
|
|
|
60
60
|
"author": "lucascouts",
|
|
61
61
|
"license": "Apache-2.0",
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@agentclientprotocol/sdk": "0.
|
|
64
|
-
"@anthropic-ai/claude-agent-sdk": "0.3.
|
|
63
|
+
"@agentclientprotocol/sdk": "1.0.0",
|
|
64
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.191",
|
|
65
65
|
"node-pty": "1.1.0",
|
|
66
66
|
"zod": "^3.25.0 || ^4.0.0"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@anthropic-ai/sdk": "0.
|
|
69
|
+
"@anthropic-ai/sdk": "0.106.0",
|
|
70
70
|
"@eslint/js": "10.0.1",
|
|
71
|
-
"@types/node": "26.0.
|
|
72
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
73
|
-
"@typescript-eslint/parser": "8.
|
|
71
|
+
"@types/node": "26.0.1",
|
|
72
|
+
"@typescript-eslint/eslint-plugin": "8.62.0",
|
|
73
|
+
"@typescript-eslint/parser": "8.62.0",
|
|
74
74
|
"eslint": "10.5.0",
|
|
75
75
|
"eslint-config-prettier": "10.1.8",
|
|
76
|
-
"globals": "17.
|
|
76
|
+
"globals": "17.7.0",
|
|
77
77
|
"prettier": "3.8.4",
|
|
78
78
|
"ts-node": "10.9.2",
|
|
79
79
|
"typescript": "6.0.3"
|