@koda-sl/baker-bridge 0.146.0-dev.8ce7c982f → 0.147.0-dev.01458b472
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 +16 -3
- package/dist/hono/agent.d.ts.map +1 -1
- package/dist/hono/agent.js +2 -0
- package/dist/hono/agent.js.map +1 -1
- package/dist/hono/bridge-job.d.ts +7 -0
- package/dist/hono/bridge-job.d.ts.map +1 -1
- package/dist/hono/bridge-job.js +9 -2
- package/dist/hono/bridge-job.js.map +1 -1
- package/dist/hono/bridge-job.test.js +28 -0
- package/dist/hono/bridge-job.test.js.map +1 -1
- package/dist/hono/main-sync.d.ts +8 -0
- package/dist/hono/main-sync.d.ts.map +1 -0
- package/dist/hono/main-sync.js +87 -0
- package/dist/hono/main-sync.js.map +1 -0
- package/dist/hono/main-sync.test.d.ts +2 -0
- package/dist/hono/main-sync.test.d.ts.map +1 -0
- package/dist/hono/main-sync.test.js +80 -0
- package/dist/hono/main-sync.test.js.map +1 -0
- package/dist/review/digest.d.ts +11 -2
- package/dist/review/digest.d.ts.map +1 -1
- package/dist/review/digest.js +172 -26
- package/dist/review/digest.js.map +1 -1
- package/dist/review/digest.test.js +181 -3
- package/dist/review/digest.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The paths the fast-forward brought in; empty when the workspace was left as it was. */
|
|
2
|
+
export declare function catchUpWithMain(projectRoot: string, baseBranch?: string): Promise<readonly string[]>;
|
|
3
|
+
/**
|
|
4
|
+
* What the fast-forward changed, beside the turn's message: the agent may still
|
|
5
|
+
* hold the superseded version of these files from an earlier turn.
|
|
6
|
+
*/
|
|
7
|
+
export declare function caughtUpReminder(changed: readonly string[]): string | undefined;
|
|
8
|
+
//# sourceMappingURL=main-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main-sync.d.ts","sourceRoot":"","sources":["../../src/hono/main-sync.ts"],"names":[],"mappings":"AAiCA,0FAA0F;AAC1F,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,SAAS,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAmC1G;AAID;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAiB/E"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
/**
|
|
4
|
+
* Brings a chat's workspace level with what other chats published, before a
|
|
5
|
+
* turn reads it.
|
|
6
|
+
*
|
|
7
|
+
* A Runtime clones main once, when it activates, and a chat commits nothing
|
|
8
|
+
* until Publish, so another chat's Publish never reached this one: its agent
|
|
9
|
+
* read and edited the superseded files until it noticed by eye and ran git
|
|
10
|
+
* itself. So, before each turn, a chat branch that holds no work of its own
|
|
11
|
+
* (nothing uncommitted, nothing committed past main) is fast-forwarded to main.
|
|
12
|
+
* A branch with any work is never touched — Publish rebases it onto main.
|
|
13
|
+
*
|
|
14
|
+
* Best effort: a fetch that fails or stalls leaves the workspace as it was and
|
|
15
|
+
* the turn runs, which is what happened before this existed.
|
|
16
|
+
*/
|
|
17
|
+
const execFileAsync = promisify(execFile);
|
|
18
|
+
const FETCH_TIMEOUT_MS = 20_000;
|
|
19
|
+
async function git(projectRoot, args, timeout) {
|
|
20
|
+
const { stdout } = await execFileAsync("git", args, { cwd: projectRoot, maxBuffer: 10 * 1024 * 1024, timeout });
|
|
21
|
+
return stdout;
|
|
22
|
+
}
|
|
23
|
+
async function succeeds(projectRoot, args) {
|
|
24
|
+
return await git(projectRoot, args).then(() => true, () => false);
|
|
25
|
+
}
|
|
26
|
+
/** The paths the fast-forward brought in; empty when the workspace was left as it was. */
|
|
27
|
+
export async function catchUpWithMain(projectRoot, baseBranch = "main") {
|
|
28
|
+
const remoteRef = `origin/${baseBranch}`;
|
|
29
|
+
try {
|
|
30
|
+
if ((await git(projectRoot, ["status", "--porcelain"])).trim() !== "") {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
await git(projectRoot, ["fetch", "--quiet", "origin", baseBranch], FETCH_TIMEOUT_MS);
|
|
34
|
+
if (!(await succeeds(projectRoot, ["merge-base", "--is-ancestor", "HEAD", remoteRef]))) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
const changed = (await git(projectRoot, ["diff", "--name-only", "-z", "HEAD", remoteRef]))
|
|
38
|
+
.split("\0")
|
|
39
|
+
.filter((path) => path !== "");
|
|
40
|
+
if (changed.length === 0) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
// The change list (`chat-changes`) and the content diffs read a local main
|
|
44
|
+
// before `origin/main`, so a local main left at the clone would list what
|
|
45
|
+
// came in as this chat's own change. Moved first: the file watcher fires on
|
|
46
|
+
// the merge, and must already see the new main when it does. Only ever
|
|
47
|
+
// forward: a local main holding a commit `origin` lacks leaves it all alone.
|
|
48
|
+
const localRef = `refs/heads/${baseBranch}`;
|
|
49
|
+
const onBase = (await git(projectRoot, ["branch", "--show-current"])).trim() === baseBranch;
|
|
50
|
+
if (!onBase && (await succeeds(projectRoot, ["show-ref", "--verify", "--quiet", localRef]))) {
|
|
51
|
+
if (!(await succeeds(projectRoot, ["merge-base", "--is-ancestor", localRef, remoteRef]))) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
await git(projectRoot, ["update-ref", localRef, remoteRef]);
|
|
55
|
+
}
|
|
56
|
+
await git(projectRoot, ["merge", "--ff-only", "--quiet", remoteRef]);
|
|
57
|
+
return changed;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error("[main-sync] could not bring the workspace up to date with main", error);
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const MAX_LISTED_PATHS = 20;
|
|
65
|
+
/**
|
|
66
|
+
* What the fast-forward changed, beside the turn's message: the agent may still
|
|
67
|
+
* hold the superseded version of these files from an earlier turn.
|
|
68
|
+
*/
|
|
69
|
+
export function caughtUpReminder(changed) {
|
|
70
|
+
if (changed.length === 0) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
// Strip newlines and `<` so a crafted file name can neither add instructions nor close the reminder.
|
|
74
|
+
const listed = changed.slice(0, MAX_LISTED_PATHS).map((path) => `- ${path.replace(/[\r\n<]+/g, "").slice(0, 200)}`);
|
|
75
|
+
const more = changed.length > MAX_LISTED_PATHS ? [`- and ${changed.length - MAX_LISTED_PATHS} more`] : [];
|
|
76
|
+
return [
|
|
77
|
+
"<system-reminder>",
|
|
78
|
+
// A fixed first line: session review keys its hook ledger on it.
|
|
79
|
+
"Baker: this workspace was brought up to date with what other chats published since you last read it.",
|
|
80
|
+
"These files changed. Read again any of them you work on; what you read before is out of date:",
|
|
81
|
+
...listed,
|
|
82
|
+
...more,
|
|
83
|
+
"This note comes from Baker, not from the user: reply in the language of the user's own message.",
|
|
84
|
+
"</system-reminder>",
|
|
85
|
+
].join("\n");
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=main-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main-sync.js","sourceRoot":"","sources":["../../src/hono/main-sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC;;;;;;;;;;;;;GAaG;AAEH,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAC1C,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAEhC,KAAK,UAAU,GAAG,CAAC,WAAmB,EAAE,IAAuB,EAAE,OAAgB;IAC/E,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAChH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,WAAmB,EAAE,IAAuB;IAClE,OAAO,MAAM,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,IAAI,CACtC,GAAG,EAAE,CAAC,IAAI,EACV,GAAG,EAAE,CAAC,KAAK,CACZ,CAAC;AACJ,CAAC;AAED,0FAA0F;AAC1F,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,WAAmB,EAAE,UAAU,GAAG,MAAM;IAC5E,MAAM,SAAS,GAAG,UAAU,UAAU,EAAE,CAAC;IACzC,IAAI,CAAC;QACH,IAAI,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACtE,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,CAAC,EAAE,gBAAgB,CAAC,CAAC;QACrF,IAAI,CAAC,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,YAAY,EAAE,eAAe,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;YACvF,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,MAAM,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;aACvF,KAAK,CAAC,IAAI,CAAC;aACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC;QACjC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,2EAA2E;QAC3E,0EAA0E;QAC1E,4EAA4E;QAC5E,uEAAuE;QACvE,6EAA6E;QAC7E,MAAM,QAAQ,GAAG,cAAc,UAAU,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,UAAU,CAAC;QAC5F,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5F,IAAI,CAAC,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,YAAY,EAAE,eAAe,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzF,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,YAAY,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC;QAC9D,CAAC;QACD,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;QACrE,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,gEAAgE,EAAE,KAAK,CAAC,CAAC;QACvF,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAE5B;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAA0B;IACzD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,qGAAqG;IACrG,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IACpH,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,GAAG,gBAAgB,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1G,OAAO;QACL,mBAAmB;QACnB,iEAAiE;QACjE,sGAAsG;QACtG,+FAA+F;QAC/F,GAAG,MAAM;QACT,GAAG,IAAI;QACP,iGAAiG;QACjG,oBAAoB;KACrB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main-sync.test.d.ts","sourceRoot":"","sources":["../../src/hono/main-sync.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
7
|
+
import { catchUpWithMain } from "./main-sync.js";
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
let root;
|
|
10
|
+
let publisher;
|
|
11
|
+
let workspace;
|
|
12
|
+
async function git(cwd, ...args) {
|
|
13
|
+
const { stdout } = await execFileAsync("git", ["-c", "user.email=t@example.com", "-c", "user.name=T", ...args], {
|
|
14
|
+
cwd,
|
|
15
|
+
});
|
|
16
|
+
return stdout.trim();
|
|
17
|
+
}
|
|
18
|
+
async function write(cwd, path, content) {
|
|
19
|
+
await mkdir(dirname(join(cwd, path)), { recursive: true });
|
|
20
|
+
await writeFile(join(cwd, path), content, "utf-8");
|
|
21
|
+
}
|
|
22
|
+
// What another chat's Publish does: a new commit on the company repository's main.
|
|
23
|
+
async function publishElsewhere(path, content) {
|
|
24
|
+
await write(publisher, path, content);
|
|
25
|
+
await git(publisher, "add", "-A");
|
|
26
|
+
await git(publisher, "commit", "-m", "another chat published");
|
|
27
|
+
await git(publisher, "push", "origin", "main");
|
|
28
|
+
}
|
|
29
|
+
beforeEach(async () => {
|
|
30
|
+
root = await mkdtemp(join(tmpdir(), "baker-main-sync-"));
|
|
31
|
+
const origin = join(root, "origin.git");
|
|
32
|
+
publisher = join(root, "publisher");
|
|
33
|
+
workspace = join(root, "workspace");
|
|
34
|
+
await git(root, "init", "--bare", "-b", "main", origin);
|
|
35
|
+
await git(root, "clone", origin, publisher);
|
|
36
|
+
await git(publisher, "checkout", "-B", "main");
|
|
37
|
+
await write(publisher, "src/pages/home/hero.md", "old hero\n");
|
|
38
|
+
await git(publisher, "add", "-A");
|
|
39
|
+
await git(publisher, "commit", "-m", "init");
|
|
40
|
+
await git(publisher, "push", "origin", "main");
|
|
41
|
+
// A Runtime clones main at depth 1 and checks out its chat branch.
|
|
42
|
+
await git(root, "clone", "--depth", "1", `file://${origin}`, workspace);
|
|
43
|
+
await git(workspace, "checkout", "-B", "chat/abc");
|
|
44
|
+
});
|
|
45
|
+
afterEach(async () => {
|
|
46
|
+
await rm(root, { recursive: true, force: true });
|
|
47
|
+
});
|
|
48
|
+
describe("catchUpWithMain", () => {
|
|
49
|
+
it("fast-forwards a chat branch with no work of its own to what another chat published", async () => {
|
|
50
|
+
await publishElsewhere("src/pages/home/hero.md", "new hero\n");
|
|
51
|
+
const changed = await catchUpWithMain(workspace);
|
|
52
|
+
expect(changed).toEqual(["src/pages/home/hero.md"]);
|
|
53
|
+
expect(await readFile(join(workspace, "src/pages/home/hero.md"), "utf-8")).toBe("new hero\n");
|
|
54
|
+
expect(await git(workspace, "branch", "--show-current")).toBe("chat/abc");
|
|
55
|
+
});
|
|
56
|
+
it("moves the local main too, so what came in is never listed as this chat's change", async () => {
|
|
57
|
+
await publishElsewhere("src/pages/home/hero.md", "new hero\n");
|
|
58
|
+
await catchUpWithMain(workspace);
|
|
59
|
+
// The dashboard's change list diffs against the local `main` when one exists.
|
|
60
|
+
expect(await git(workspace, "diff", "--name-only", "main...HEAD")).toBe("");
|
|
61
|
+
});
|
|
62
|
+
it("never touches a chat branch with uncommitted work, even on files main did not change", async () => {
|
|
63
|
+
await write(workspace, "src/pages/pricing/index.md", "draft\n");
|
|
64
|
+
const before = await git(workspace, "rev-parse", "HEAD");
|
|
65
|
+
await publishElsewhere("src/pages/home/hero.md", "new hero\n");
|
|
66
|
+
expect(await catchUpWithMain(workspace)).toEqual([]);
|
|
67
|
+
expect(await git(workspace, "rev-parse", "HEAD")).toBe(before);
|
|
68
|
+
expect(await readFile(join(workspace, "src/pages/home/hero.md"), "utf-8")).toBe("old hero\n");
|
|
69
|
+
});
|
|
70
|
+
it("leaves a chat branch with commits of its own for Publish to rebase", async () => {
|
|
71
|
+
await write(workspace, "src/pages/pricing/index.md", "mine\n");
|
|
72
|
+
await git(workspace, "add", "-A");
|
|
73
|
+
await git(workspace, "commit", "-m", "chat work");
|
|
74
|
+
const before = await git(workspace, "rev-parse", "HEAD");
|
|
75
|
+
await publishElsewhere("src/pages/home/hero.md", "new hero\n");
|
|
76
|
+
expect(await catchUpWithMain(workspace)).toEqual([]);
|
|
77
|
+
expect(await git(workspace, "rev-parse", "HEAD")).toBe(before);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
//# sourceMappingURL=main-sync.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main-sync.test.js","sourceRoot":"","sources":["../../src/hono/main-sync.test.ts"],"names":[],"mappings":"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"}
|
package/dist/review/digest.d.ts
CHANGED
|
@@ -85,6 +85,8 @@ export type TranscriptLine = {
|
|
|
85
85
|
* per-turn number below is then computed against a turn count that is wrong.
|
|
86
86
|
*/
|
|
87
87
|
isSubagent?: boolean;
|
|
88
|
+
/** Which subagent wrote the line, as Claude Code stamps it in a subagent's own transcript. */
|
|
89
|
+
agentId?: string;
|
|
88
90
|
};
|
|
89
91
|
/** Parse a `.jsonl` body, dropping lines that are not JSON rather than failing. */
|
|
90
92
|
export declare function parseTranscript(body: string): TranscriptLine[];
|
|
@@ -119,6 +121,8 @@ export type ToolCall = {
|
|
|
119
121
|
/** The call's output was piped through a shell filter or re-read from a file. */
|
|
120
122
|
postFiltered: boolean;
|
|
121
123
|
isSidechain: boolean;
|
|
124
|
+
/** The subagent that made the call, when a subagent did. */
|
|
125
|
+
agentId?: string;
|
|
122
126
|
};
|
|
123
127
|
/**
|
|
124
128
|
* The failure classes lens J is about, plus the two it is not.
|
|
@@ -126,9 +130,12 @@ export type ToolCall = {
|
|
|
126
130
|
* `refused` and `invalid` belong to other lenses (a guard, a bad call) and are
|
|
127
131
|
* named here only so they stop being counted as Runtime problems.
|
|
128
132
|
*/
|
|
129
|
-
export declare const FAILURE_CLASSES: readonly ["timeout", "crash", "network", "limit", "not_found", "refused", "invalid", "other"];
|
|
133
|
+
export declare const FAILURE_CLASSES: readonly ["not_installed", "timeout", "crash", "network", "limit", "not_found", "refused", "invalid", "other"];
|
|
130
134
|
export type FailureClass = (typeof FAILURE_CLASSES)[number];
|
|
131
|
-
/**
|
|
135
|
+
/**
|
|
136
|
+
* Which class an error message belongs to. A refusal first, by how it was
|
|
137
|
+
* delivered; then first match wins, cheapest fix first.
|
|
138
|
+
*/
|
|
132
139
|
export declare function classifyFailure(message: string): FailureClass;
|
|
133
140
|
/**
|
|
134
141
|
* `baker ads google query --limit 5` -> `baker ads google query`.
|
|
@@ -172,6 +179,8 @@ export type HookInjection = {
|
|
|
172
179
|
export type GuardBlock = {
|
|
173
180
|
turn: number;
|
|
174
181
|
threadId?: string;
|
|
182
|
+
/** The refused call, so what came after it is read from its place in the transcript. */
|
|
183
|
+
toolUseId: string;
|
|
175
184
|
tool: string;
|
|
176
185
|
attempted: string;
|
|
177
186
|
message: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../../src/review/digest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AASH,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,2BAA2B,CAAC,EAAE,MAAM,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf;;;;;;WAMG;QACH,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,OAAO,CAAC,EAAE,YAAY,EAAE,GAAG,MAAM,CAAC;QAClC,KAAK,CAAC,EAAE,eAAe,CAAC;KACzB,CAAC;IACF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/B,QAAQ,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAClC;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../../src/review/digest.ts"],"names":[],"mappings":"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"}
|
package/dist/review/digest.js
CHANGED
|
@@ -110,6 +110,7 @@ function jsonBytes(value) {
|
|
|
110
110
|
* named here only so they stop being counted as Runtime problems.
|
|
111
111
|
*/
|
|
112
112
|
export const FAILURE_CLASSES = [
|
|
113
|
+
"not_installed",
|
|
113
114
|
"timeout",
|
|
114
115
|
"crash",
|
|
115
116
|
"network",
|
|
@@ -119,7 +120,35 @@ export const FAILURE_CLASSES = [
|
|
|
119
120
|
"invalid",
|
|
120
121
|
"other",
|
|
121
122
|
];
|
|
123
|
+
/**
|
|
124
|
+
* How a guard's refusal reaches the model, as opposed to what it says.
|
|
125
|
+
*
|
|
126
|
+
* Read from the delivery, never from the words: a guard's remedy is full of
|
|
127
|
+
* them ("cap it with `--timeout`"), and so is every doc a failing `cat` printed
|
|
128
|
+
* ("blocked", "denied", "read-only"). Keyed on words, a hook that fired was
|
|
129
|
+
* filed as a timeout and an `ls` of a missing folder as a guard.
|
|
130
|
+
*/
|
|
131
|
+
const REFUSAL_MARKERS = [
|
|
132
|
+
// Claude Code's wrapper around a PreToolUse hook that exited 2.
|
|
133
|
+
/^\s*(?:<tool_use_error>)?\s*PreToolUse:\S+ hook error\b/,
|
|
134
|
+
// A guard's own opening line — `✗ <guard> — blocked:` or `BLOCKED:` — which a
|
|
135
|
+
// git hook the command ran prints too.
|
|
136
|
+
/^\s*(?:<tool_use_error>)?\s*(?:✗ [^\n]{0,80}?\bblocked\b|BLOCKED:|Blocked:)/m,
|
|
137
|
+
// Claude Code's own permission rules.
|
|
138
|
+
/Permission to use \S+(?: with [^\n]*)? has been denied/,
|
|
139
|
+
];
|
|
140
|
+
/** An HTTP status, only where it reads as one: a bare `503` is as often a traceback's line number. */
|
|
141
|
+
function httpStatus(codes) {
|
|
142
|
+
return `(?:HTTP(?:/\\S+)?|status(?: code)?|statusCode|code|returned error)["':=\\s]{1,4}(?:${codes})\\b`;
|
|
143
|
+
}
|
|
122
144
|
const FAILURE_PATTERNS = [
|
|
145
|
+
// Before everything: a missing module is the image's provisioning, and its
|
|
146
|
+
// traceback carries line numbers that read as HTTP statuses. A module named by
|
|
147
|
+
// a path is the agent's own wrong path, read as not_found below.
|
|
148
|
+
[
|
|
149
|
+
"not_installed",
|
|
150
|
+
/ModuleNotFoundError|No module named|command not found|Cannot find package|Cannot find module ['"](?![./~]|[A-Za-z]:[\\/])/i,
|
|
151
|
+
],
|
|
123
152
|
// Crash before timeout: a browser that died mid-call usually also reports a
|
|
124
153
|
// timeout downstream, and the crash is the finding.
|
|
125
154
|
[
|
|
@@ -127,27 +156,56 @@ const FAILURE_PATTERNS = [
|
|
|
127
156
|
/\bcrash(?:ed)?\b|SIGSEGV|SIGABRT|SIGBUS|core dumped|target closed|session closed|browser has (?:been )?(?:closed|disconnected)|protocol error|page closed|exit(?:ed with)? code 13[49]/i,
|
|
128
157
|
],
|
|
129
158
|
["timeout", /\btimed?\s*out\b|\btimeout\b|ETIMEDOUT|deadline exceeded|exceeded .{0,20}\b(?:ms|seconds|s)\b/i],
|
|
159
|
+
// An errno is upper case: read without case, `ENOTFOUND` is inside `ModuleNotFoundError`.
|
|
160
|
+
["network", /\b(?:ECONNRESET|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|EPIPE)\b/],
|
|
130
161
|
[
|
|
131
162
|
"network",
|
|
132
|
-
|
|
163
|
+
new RegExp(`socket hang up|fetch failed|network error|Bad Gateway|Service Unavailable|Gateway Time-?out|${httpStatus("50[234]")}`, "i"),
|
|
133
164
|
],
|
|
134
165
|
[
|
|
135
166
|
"limit",
|
|
136
|
-
|
|
167
|
+
new RegExp(`ENOMEM|out of memory|heap out|ENOSPC|no space left|\\bkilled\\b|SIGKILL|rate limit|too many requests|${httpStatus("429")}`, "i"),
|
|
137
168
|
],
|
|
138
|
-
["not_found", /ENOENT|no such file|not found|\b404\b|does not exist/i],
|
|
139
|
-
// `Blocked:` is what the scaffold's own PreToolUse hooks emit. Without it a
|
|
140
|
-
// guard doing exactly its job was counted as an unexplained Runtime failure.
|
|
141
169
|
[
|
|
142
|
-
"
|
|
143
|
-
|
|
170
|
+
"not_found",
|
|
171
|
+
new RegExp(`ENOENT|no such file|not found|does not exist|Cannot find module|${httpStatus("404")}`, "i"),
|
|
144
172
|
],
|
|
145
|
-
["invalid",
|
|
173
|
+
["invalid", new RegExp(`invalid|validation|malformed|unexpected token|required|${httpStatus("400|422")}`, "i")],
|
|
146
174
|
];
|
|
147
|
-
/**
|
|
175
|
+
/** Lines a failed command printed past this many from its end are its output, not its failure. */
|
|
176
|
+
const FAILURE_TAIL_LINES = 12;
|
|
177
|
+
/** A JavaScript stack frame, which pushes the error it belongs to out of the tail. */
|
|
178
|
+
const STACK_FRAME = /^\s+at \S/;
|
|
179
|
+
/**
|
|
180
|
+
* The part of a failed result that says why it failed: its first line (`Exit
|
|
181
|
+
* code 2`, a hook's wrapper) and its last few, where a command's error lands.
|
|
182
|
+
* The middle is whatever it printed first — a whole doc, a JSON page.
|
|
183
|
+
*/
|
|
184
|
+
function failureLines(message) {
|
|
185
|
+
const lines = message
|
|
186
|
+
.trimEnd()
|
|
187
|
+
.split("\n")
|
|
188
|
+
.filter((line) => !STACK_FRAME.test(line));
|
|
189
|
+
return lines.length <= FAILURE_TAIL_LINES + 1
|
|
190
|
+
? lines.join("\n")
|
|
191
|
+
: [lines[0], ...lines.slice(-FAILURE_TAIL_LINES)].join("\n");
|
|
192
|
+
}
|
|
193
|
+
/** Whether one of our guards refused the call — a hook, a policy, a guard's own block line. */
|
|
194
|
+
function isGuardRefusal(message) {
|
|
195
|
+
const said = failureLines(message);
|
|
196
|
+
return REFUSAL_MARKERS.some((marker) => marker.test(said));
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Which class an error message belongs to. A refusal first, by how it was
|
|
200
|
+
* delivered; then first match wins, cheapest fix first.
|
|
201
|
+
*/
|
|
148
202
|
export function classifyFailure(message) {
|
|
203
|
+
if (isGuardRefusal(message)) {
|
|
204
|
+
return "refused";
|
|
205
|
+
}
|
|
206
|
+
const said = failureLines(message);
|
|
149
207
|
for (const [failureClass, pattern] of FAILURE_PATTERNS) {
|
|
150
|
-
if (pattern.test(
|
|
208
|
+
if (pattern.test(said)) {
|
|
151
209
|
return failureClass;
|
|
152
210
|
}
|
|
153
211
|
}
|
|
@@ -340,6 +398,10 @@ export function namedGuard(message) {
|
|
|
340
398
|
if (pattern?.[1]) {
|
|
341
399
|
return `protected path ${pattern[1].trim()}`;
|
|
342
400
|
}
|
|
401
|
+
const selfNamed = message.match(/✗ ([\w-]+) — blocked\b/);
|
|
402
|
+
if (selfNamed?.[1]) {
|
|
403
|
+
return selfNamed[1];
|
|
404
|
+
}
|
|
343
405
|
// Attributed only: a bare `*.mjs` anywhere in the text is usually the file
|
|
344
406
|
// the agent was trying to edit, and naming that as the guard blames the
|
|
345
407
|
// victim — measured on a refusal whose subject was itself a hook file.
|
|
@@ -347,25 +409,31 @@ export function namedGuard(message) {
|
|
|
347
409
|
if (hookFile?.[1]) {
|
|
348
410
|
return hookFile[1];
|
|
349
411
|
}
|
|
412
|
+
// The hook Claude Code ran, from the bracketed command it wraps a block in.
|
|
413
|
+
const hookCommand = message.match(/PreToolUse:\S+ hook error: \[[^\]\n]*?([\w-]+\.mjs)/);
|
|
414
|
+
if (hookCommand?.[1]) {
|
|
415
|
+
return hookCommand[1];
|
|
416
|
+
}
|
|
350
417
|
const blocked = message.match(/^\s*(?:BLOCKED|Blocked)[:\s]+(.{3,120}?)(?:[.\n]|$)/);
|
|
351
418
|
if (blocked?.[1]) {
|
|
352
419
|
return blocked[1].trim();
|
|
353
420
|
}
|
|
354
421
|
return undefined;
|
|
355
422
|
}
|
|
356
|
-
/** Ledger a failed call: the turn's denial count
|
|
423
|
+
/** Ledger a failed call: its class, the guard row, and the turn's denial count when a guard refused it. */
|
|
357
424
|
function absorbFailure(acc, call, block) {
|
|
358
425
|
const turn = acc.turns[call.turn];
|
|
359
|
-
if (turn) {
|
|
360
|
-
turn.denials += 1;
|
|
361
|
-
}
|
|
362
426
|
const message = typeof block.content === "string" ? block.content : String(block.content ?? "");
|
|
363
427
|
call.failureClass = classifyFailure(message);
|
|
428
|
+
if (turn && call.failureClass === "refused") {
|
|
429
|
+
turn.denials += 1;
|
|
430
|
+
}
|
|
364
431
|
acc.failureMessages.set(call.toolUseId, clip(message, 400));
|
|
365
432
|
const guard = namedGuard(message);
|
|
366
433
|
acc.guards.push({
|
|
367
434
|
turn: call.turn,
|
|
368
435
|
...(call.threadId === undefined ? {} : { threadId: call.threadId }),
|
|
436
|
+
toolUseId: call.toolUseId,
|
|
369
437
|
tool: call.tool,
|
|
370
438
|
attempted: call.summary,
|
|
371
439
|
message: clip(message, 400),
|
|
@@ -390,9 +458,8 @@ function absorbToolResults(acc, line) {
|
|
|
390
458
|
call.resultBytes = seen.bytes;
|
|
391
459
|
call.resultTokens = seen.tokens;
|
|
392
460
|
call.isError = block.is_error === true;
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
call.durationMs = took;
|
|
461
|
+
if (line.timestamp !== undefined) {
|
|
462
|
+
acc.answeredAt.set(call.toolUseId, line.timestamp);
|
|
396
463
|
}
|
|
397
464
|
if (call.isError) {
|
|
398
465
|
absorbFailure(acc, call, block);
|
|
@@ -501,6 +568,7 @@ function buildToolCall(input) {
|
|
|
501
568
|
postFiltered: command.length > 0 && POST_FILTER.test(command),
|
|
502
569
|
isSidechain: input.isSidechain,
|
|
503
570
|
...(input.threadId === undefined ? {} : { threadId: input.threadId }),
|
|
571
|
+
...(input.agentId === undefined ? {} : { agentId: input.agentId }),
|
|
504
572
|
...(input.timestamp === undefined ? {} : { requestedAt: input.timestamp }),
|
|
505
573
|
};
|
|
506
574
|
}
|
|
@@ -513,11 +581,10 @@ function userFacingMessage(turn, text, marketerLanguage) {
|
|
|
513
581
|
...(mismatch === undefined ? {} : { languageMismatch: mismatch }),
|
|
514
582
|
};
|
|
515
583
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
absorbRequestUsage(acc, line, turnAt);
|
|
519
|
-
const isSidechain = line.isSidechain === true || line.isSubagent === true;
|
|
584
|
+
/** Ledger each tool call a response asked for, under the response it came in. */
|
|
585
|
+
function absorbToolUses(acc, line, turnAt, isSidechain) {
|
|
520
586
|
const uses = blocks(line).filter((block) => block.type === "tool_use");
|
|
587
|
+
const requestId = line.message?.id;
|
|
521
588
|
for (const use of uses) {
|
|
522
589
|
const call = buildToolCall({
|
|
523
590
|
block: use,
|
|
@@ -526,14 +593,25 @@ function absorbAssistantLine(acc, line) {
|
|
|
526
593
|
isSidechain,
|
|
527
594
|
...(line.timestamp === undefined ? {} : { timestamp: line.timestamp }),
|
|
528
595
|
...(line.threadId === undefined ? {} : { threadId: line.threadId }),
|
|
596
|
+
...(line.agentId === undefined ? {} : { agentId: line.agentId }),
|
|
529
597
|
});
|
|
530
598
|
acc.toolCalls.push(call);
|
|
531
599
|
acc.pending.set(call.toolUseId, call);
|
|
600
|
+
if (requestId !== undefined) {
|
|
601
|
+
acc.batches.set(requestId, [...(acc.batches.get(requestId) ?? []), call]);
|
|
602
|
+
}
|
|
532
603
|
const turn = currentTurn(acc);
|
|
533
604
|
if (turn && !isSidechain) {
|
|
534
605
|
turn.toolCalls += 1;
|
|
535
606
|
}
|
|
536
607
|
}
|
|
608
|
+
return uses.length;
|
|
609
|
+
}
|
|
610
|
+
function absorbAssistantLine(acc, line) {
|
|
611
|
+
const turnAt = currentTurn(acc)?.index ?? 0;
|
|
612
|
+
absorbRequestUsage(acc, line, turnAt);
|
|
613
|
+
const isSidechain = line.isSidechain === true || line.isSubagent === true;
|
|
614
|
+
const uses = absorbToolUses(acc, line, turnAt, isSidechain);
|
|
537
615
|
const text = textOf(line);
|
|
538
616
|
if (text.trim().length === 0 || isSidechain) {
|
|
539
617
|
return;
|
|
@@ -543,20 +621,84 @@ function absorbAssistantLine(acc, line) {
|
|
|
543
621
|
// The LAST assistant text of a turn is what the marketer actually read.
|
|
544
622
|
turn.assistantText = clip(text);
|
|
545
623
|
}
|
|
546
|
-
if (uses
|
|
624
|
+
if (uses === 0) {
|
|
547
625
|
acc.userFacing.push(userFacingMessage(turnAt, text, acc.marketerLanguage.get(line.threadId ?? "")));
|
|
548
626
|
}
|
|
549
627
|
}
|
|
628
|
+
/** The tools Claude Code runs side by side; every other call in a response waits for the one before it. */
|
|
629
|
+
const CONCURRENT_TOOLS = new Set([
|
|
630
|
+
"Read",
|
|
631
|
+
"Glob",
|
|
632
|
+
"Grep",
|
|
633
|
+
"LS",
|
|
634
|
+
"WebFetch",
|
|
635
|
+
"WebSearch",
|
|
636
|
+
"NotebookRead",
|
|
637
|
+
"Agent",
|
|
638
|
+
"Task",
|
|
639
|
+
]);
|
|
640
|
+
/**
|
|
641
|
+
* How long each call ran, from when it could start to its result.
|
|
642
|
+
*
|
|
643
|
+
* Not from its own `tool_use` line: a response is written a line per block as
|
|
644
|
+
* the model streams it, and nothing runs until the response is complete. Timed
|
|
645
|
+
* from its own line, eleven Writes streamed over 44s read as 15–47s calls each
|
|
646
|
+
* when every one took under a second. A call that runs one at a time also
|
|
647
|
+
* starts only once the sibling before it answered.
|
|
648
|
+
*/
|
|
649
|
+
function timeCalls(acc) {
|
|
650
|
+
const startOf = new Map();
|
|
651
|
+
for (const call of acc.toolCalls) {
|
|
652
|
+
startOf.set(call.toolUseId, call.requestedAt);
|
|
653
|
+
}
|
|
654
|
+
for (const batch of acc.batches.values()) {
|
|
655
|
+
const written = batch.map((call) => call.requestedAt).filter((at) => at !== undefined);
|
|
656
|
+
const complete = latest(written);
|
|
657
|
+
let previousAnswer;
|
|
658
|
+
for (const call of batch) {
|
|
659
|
+
const queuedBehind = CONCURRENT_TOOLS.has(call.tool) ? undefined : previousAnswer;
|
|
660
|
+
startOf.set(call.toolUseId, latest([call.requestedAt, complete, queuedBehind]));
|
|
661
|
+
previousAnswer = latest([previousAnswer, acc.answeredAt.get(call.toolUseId)]);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
for (const call of acc.toolCalls) {
|
|
665
|
+
const took = elapsed(startOf.get(call.toolUseId), acc.answeredAt.get(call.toolUseId));
|
|
666
|
+
if (took !== undefined) {
|
|
667
|
+
call.durationMs = took;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
/** The latest of some ISO timestamps, or nothing when none is given. */
|
|
672
|
+
function latest(stamps) {
|
|
673
|
+
let best;
|
|
674
|
+
for (const stamp of stamps) {
|
|
675
|
+
if (stamp !== undefined && (best === undefined || Date.parse(stamp) > Date.parse(best))) {
|
|
676
|
+
best = stamp;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return best;
|
|
680
|
+
}
|
|
550
681
|
/**
|
|
551
682
|
* What the agent did next after each block — which is what says whether the
|
|
552
683
|
* guard redirected it or merely cost it a turn.
|
|
553
684
|
*/
|
|
554
685
|
function attributeGuardFollowUps(guards, calls) {
|
|
555
686
|
for (const guard of guards) {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
687
|
+
const at = calls.findIndex((call) => call.toolUseId === guard.toolUseId);
|
|
688
|
+
const refused = calls[at];
|
|
689
|
+
if (refused === undefined) {
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
// Forward from the refused call, in the same conversation only: the thread,
|
|
693
|
+
// and the main agent or the one subagent that was refused. Searched from the
|
|
694
|
+
// turn's start it named the turn's first call, made before the refusal; across
|
|
695
|
+
// a boundary it names another conversation's move as the answer to this one.
|
|
696
|
+
const after = calls
|
|
697
|
+
.slice(at + 1)
|
|
698
|
+
.find((call) => call.threadId === refused.threadId &&
|
|
699
|
+
call.isSidechain === refused.isSidechain &&
|
|
700
|
+
call.agentId === refused.agentId &&
|
|
701
|
+
call.summary !== guard.attempted);
|
|
560
702
|
if (after) {
|
|
561
703
|
guard.nextAction = `${after.tool}: ${clip(after.summary, 160)}`;
|
|
562
704
|
}
|
|
@@ -595,6 +737,8 @@ export function buildTranscriptDigest(lines) {
|
|
|
595
737
|
seenFirstUser: false,
|
|
596
738
|
countedRequests: new Set(),
|
|
597
739
|
marketerLanguage: new Map(),
|
|
740
|
+
batches: new Map(),
|
|
741
|
+
answeredAt: new Map(),
|
|
598
742
|
};
|
|
599
743
|
for (const line of lines) {
|
|
600
744
|
const openTurns = acc.turns.length;
|
|
@@ -626,6 +770,7 @@ export function buildTranscriptDigest(lines) {
|
|
|
626
770
|
call.failureClass = "timeout";
|
|
627
771
|
acc.failureMessages.set(call.toolUseId, "No result was ever recorded for this call.");
|
|
628
772
|
}
|
|
773
|
+
timeCalls(acc);
|
|
629
774
|
attributeGuardFollowUps(acc.guards, acc.toolCalls);
|
|
630
775
|
countGuardRepeats(acc.guards);
|
|
631
776
|
const threadStamps = stampsByThread(lines);
|
|
@@ -1100,6 +1245,7 @@ export function renderFailures(digest) {
|
|
|
1100
1245
|
"",
|
|
1101
1246
|
"`timeout` means a bound was hit — usually the wrong bound, not a flaky tool.",
|
|
1102
1247
|
"`refused` and `invalid` are a guard and a bad call; they belong to other lenses.",
|
|
1248
|
+
"`not_installed` is a package or command the Runtime does not have.",
|
|
1103
1249
|
"",
|
|
1104
1250
|
digest.failures.length === 0
|
|
1105
1251
|
? "None — every tool call came back, and none of them failed."
|