@koda-sl/baker-bridge 0.154.0-dev.db7cf2f5d → 0.154.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 +22 -2
- package/dist/hono/agent-session.d.ts +0 -1
- package/dist/hono/agent-session.d.ts.map +1 -1
- package/dist/hono/agent-session.js +0 -7
- package/dist/hono/agent-session.js.map +1 -1
- package/dist/hono/agent-session.test.js +1 -7
- package/dist/hono/agent-session.test.js.map +1 -1
- package/dist/hono/agent.d.ts.map +1 -1
- package/dist/hono/agent.js +7 -9
- package/dist/hono/agent.js.map +1 -1
- package/dist/hono/bridge-job.d.ts +15 -0
- package/dist/hono/bridge-job.d.ts.map +1 -1
- package/dist/hono/bridge-job.js +69 -5
- package/dist/hono/bridge-job.js.map +1 -1
- package/dist/hono/bridge-job.test.js +102 -0
- package/dist/hono/bridge-job.test.js.map +1 -1
- package/dist/hono/convex.d.ts +2 -1
- package/dist/hono/convex.d.ts.map +1 -1
- package/dist/hono/convex.js +4 -0
- package/dist/hono/convex.js.map +1 -1
- package/dist/hono/main-sync.d.ts +7 -0
- package/dist/hono/main-sync.d.ts.map +1 -1
- package/dist/hono/main-sync.js +39 -0
- package/dist/hono/main-sync.js.map +1 -1
- package/dist/hono/main-sync.test.js +29 -1
- package/dist/hono/main-sync.test.js.map +1 -1
- package/dist/hono/relay.d.ts +10 -1
- package/dist/hono/relay.d.ts.map +1 -1
- package/dist/hono/relay.js +4 -0
- package/dist/hono/relay.js.map +1 -1
- package/dist/hono/relay.test.js +11 -0
- package/dist/hono/relay.test.js.map +1 -1
- package/dist/hono/served-model.d.ts +23 -0
- package/dist/hono/served-model.d.ts.map +1 -0
- package/dist/hono/served-model.js +82 -0
- package/dist/hono/served-model.js.map +1 -0
- package/dist/hono/served-model.test.d.ts +2 -0
- package/dist/hono/served-model.test.d.ts.map +1 -0
- package/dist/hono/served-model.test.js +29 -0
- package/dist/hono/served-model.test.js.map +1 -0
- package/dist/hono/session-continuity.d.ts +24 -0
- package/dist/hono/session-continuity.d.ts.map +1 -1
- package/dist/hono/session-continuity.integration.test.js +201 -5
- package/dist/hono/session-continuity.integration.test.js.map +1 -1
- package/dist/hono/session-continuity.js +94 -7
- package/dist/hono/session-continuity.js.map +1 -1
- package/dist/hono/session-store.d.ts +2 -0
- package/dist/hono/session-store.d.ts.map +1 -1
- package/dist/hono/session-store.js +6 -0
- package/dist/hono/session-store.js.map +1 -1
- package/dist/hono/session-usage-baseline.d.ts +1 -0
- package/dist/hono/session-usage-baseline.d.ts.map +1 -1
- package/dist/hono/session-usage-baseline.js +17 -4
- package/dist/hono/session-usage-baseline.js.map +1 -1
- package/dist/hono/turn-events.d.ts +1 -1
- package/dist/hono/turn-events.d.ts.map +1 -1
- package/dist/hono/turn-events.js.map +1 -1
- package/dist/review/digest.d.ts +45 -1
- package/dist/review/digest.d.ts.map +1 -1
- package/dist/review/digest.js +394 -63
- package/dist/review/digest.js.map +1 -1
- package/dist/review/digest.test.js +239 -1
- package/dist/review/digest.test.js.map +1 -1
- package/dist/review/evidence.d.ts.map +1 -1
- package/dist/review/evidence.js +1 -1
- package/dist/review/evidence.js.map +1 -1
- package/package.json +1 -1
package/dist/review/digest.js
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
* it could NOT find, so missing evidence is a fact before the model starts
|
|
16
16
|
* rather than a silence it fills in.
|
|
17
17
|
*/
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
18
19
|
import { imageSize } from "image-size";
|
|
20
|
+
import { parse } from "unbash";
|
|
19
21
|
import { languageMismatch, marketerWords, messageLanguage } from "./reply-language.js";
|
|
20
22
|
/** Parse a `.jsonl` body, dropping lines that are not JSON rather than failing. */
|
|
21
23
|
export function parseTranscript(body) {
|
|
@@ -289,6 +291,49 @@ function clip(text, max = TRUNCATE) {
|
|
|
289
291
|
function currentTurn(acc) {
|
|
290
292
|
return acc.turns[acc.turns.length - 1];
|
|
291
293
|
}
|
|
294
|
+
/** One conversation inside a thread: the main agent, or one subagent. */
|
|
295
|
+
function conversationKey(threadId, agentId) {
|
|
296
|
+
return `${threadId ?? ""}\u0000${agentId ?? ""}`;
|
|
297
|
+
}
|
|
298
|
+
function isSubagentLine(line) {
|
|
299
|
+
return line.isSidechain === true || line.isSubagent === true;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* The turn a line belongs to.
|
|
303
|
+
*
|
|
304
|
+
* A subagent's own file is read after the whole session file it was spawned
|
|
305
|
+
* in, so the turn open by then is that session's last one. Charged to it, a
|
|
306
|
+
* fan-out in turn 1 was listed under the re-render turn 2 — its refusals, its
|
|
307
|
+
* denials and its parent's closing recap as their intent. A subagent belongs to
|
|
308
|
+
* the last turn of its thread that began before the prompt that started it, or
|
|
309
|
+
* before the later prompt that continued it.
|
|
310
|
+
*/
|
|
311
|
+
function turnIndexOf(acc, line) {
|
|
312
|
+
const current = currentTurn(acc)?.index;
|
|
313
|
+
if (!isSubagentLine(line)) {
|
|
314
|
+
return current;
|
|
315
|
+
}
|
|
316
|
+
const key = line.agentId === undefined ? undefined : conversationKey(line.threadId, line.agentId);
|
|
317
|
+
const known = key === undefined ? undefined : acc.subagentTurns.get(key);
|
|
318
|
+
if (known !== undefined) {
|
|
319
|
+
return known;
|
|
320
|
+
}
|
|
321
|
+
const at = stampOf(line.timestamp);
|
|
322
|
+
if (at === undefined) {
|
|
323
|
+
return current;
|
|
324
|
+
}
|
|
325
|
+
let spawned;
|
|
326
|
+
for (const turn of acc.turns) {
|
|
327
|
+
const began = stampOf(turn.timestamp);
|
|
328
|
+
if (turn.threadId === line.threadId && began !== undefined && began <= at) {
|
|
329
|
+
spawned = turn.index;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (key !== undefined && spawned !== undefined) {
|
|
333
|
+
acc.subagentTurns.set(key, spawned);
|
|
334
|
+
}
|
|
335
|
+
return spawned ?? current;
|
|
336
|
+
}
|
|
292
337
|
/**
|
|
293
338
|
* Close the open turn at the latest line that still belongs to it.
|
|
294
339
|
*
|
|
@@ -302,7 +347,8 @@ function currentTurn(acc) {
|
|
|
302
347
|
*/
|
|
303
348
|
function noteTurnEnd(acc, line) {
|
|
304
349
|
const at = stampOf(line.timestamp);
|
|
305
|
-
const
|
|
350
|
+
const index = turnIndexOf(acc, line);
|
|
351
|
+
const turn = index === undefined ? undefined : acc.turns[index];
|
|
306
352
|
if (at === undefined || turn === undefined) {
|
|
307
353
|
return;
|
|
308
354
|
}
|
|
@@ -420,26 +466,38 @@ export function namedGuard(message) {
|
|
|
420
466
|
}
|
|
421
467
|
return undefined;
|
|
422
468
|
}
|
|
423
|
-
/**
|
|
424
|
-
|
|
469
|
+
/**
|
|
470
|
+
* Ledger a failure: its class, the guard row, and the turn's denial count when a
|
|
471
|
+
* guard refused it. A call can fail more than once — each `baker` call one
|
|
472
|
+
* command line ran — and its own class and message are its first failure's.
|
|
473
|
+
*/
|
|
474
|
+
function absorbFailure(acc, call, message, failureClass = classifyFailure(message)) {
|
|
425
475
|
const turn = acc.turns[call.turn];
|
|
426
|
-
|
|
427
|
-
call.failureClass
|
|
428
|
-
if (turn &&
|
|
476
|
+
call.isError = true;
|
|
477
|
+
call.failureClass ??= failureClass;
|
|
478
|
+
if (turn && failureClass === "refused") {
|
|
429
479
|
turn.denials += 1;
|
|
430
480
|
}
|
|
431
|
-
acc.failureMessages.
|
|
481
|
+
if (!acc.failureMessages.has(call.toolUseId)) {
|
|
482
|
+
acc.failureMessages.set(call.toolUseId, clip(message, 400));
|
|
483
|
+
}
|
|
432
484
|
const guard = namedGuard(message);
|
|
485
|
+
// A subagent's words, never the turn's: the turn's are its parent's.
|
|
486
|
+
const intent = call.isSidechain
|
|
487
|
+
? acc.subagentWords.get(conversationKey(call.threadId, call.agentId))
|
|
488
|
+
: turn?.assistantText || undefined;
|
|
433
489
|
acc.guards.push({
|
|
434
490
|
turn: call.turn,
|
|
435
491
|
...(call.threadId === undefined ? {} : { threadId: call.threadId }),
|
|
436
492
|
toolUseId: call.toolUseId,
|
|
437
493
|
tool: call.tool,
|
|
438
494
|
attempted: call.summary,
|
|
495
|
+
inputKey: call.inputKey,
|
|
439
496
|
message: clip(message, 400),
|
|
440
|
-
refusal:
|
|
497
|
+
refusal: failureClass === "refused",
|
|
441
498
|
...(guard === undefined ? {} : { guard }),
|
|
442
|
-
...(
|
|
499
|
+
...(intent ? { intent: clip(intent, 300) } : {}),
|
|
500
|
+
...(call.isSidechain && call.agentId !== undefined ? { agentId: call.agentId } : {}),
|
|
443
501
|
repeats: 1,
|
|
444
502
|
});
|
|
445
503
|
}
|
|
@@ -457,12 +515,11 @@ function absorbToolResults(acc, line) {
|
|
|
457
515
|
const seen = measureToolResult(block.content);
|
|
458
516
|
call.resultBytes = seen.bytes;
|
|
459
517
|
call.resultTokens = seen.tokens;
|
|
460
|
-
call.isError = block.is_error === true;
|
|
461
518
|
if (line.timestamp !== undefined) {
|
|
462
519
|
acc.answeredAt.set(call.toolUseId, line.timestamp);
|
|
463
520
|
}
|
|
464
|
-
if (
|
|
465
|
-
absorbFailure(acc, call, block);
|
|
521
|
+
if (block.is_error === true) {
|
|
522
|
+
absorbFailure(acc, call, typeof block.content === "string" ? block.content : String(block.content ?? ""));
|
|
466
523
|
}
|
|
467
524
|
}
|
|
468
525
|
}
|
|
@@ -490,6 +547,11 @@ function absorbUserLine(acc, line) {
|
|
|
490
547
|
absorbToolResults(acc, line);
|
|
491
548
|
const spoken = raw.replace(SYSTEM_REMINDER, "").trim();
|
|
492
549
|
const isToolResult = blocks(line).some((block) => block.type === "tool_result");
|
|
550
|
+
if (isSubagentLine(line) && !isToolResult && spoken.length > 0) {
|
|
551
|
+
// A prompt to a subagent: its spawn, or a later turn continuing it (`SendMessage`
|
|
552
|
+
// keeps the id), so the turn it belongs to is read again from this line.
|
|
553
|
+
acc.subagentTurns.delete(conversationKey(line.threadId, line.agentId));
|
|
554
|
+
}
|
|
493
555
|
if (line.isSidechain || line.isSubagent || isToolResult || spoken.length === 0) {
|
|
494
556
|
return;
|
|
495
557
|
}
|
|
@@ -509,6 +571,7 @@ function absorbUserLine(acc, line) {
|
|
|
509
571
|
outputTokens: 0,
|
|
510
572
|
cacheReadTokens: 0,
|
|
511
573
|
cacheWriteTokens: 0,
|
|
574
|
+
subagent: { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 },
|
|
512
575
|
denials: 0,
|
|
513
576
|
compacted: false,
|
|
514
577
|
});
|
|
@@ -535,21 +598,25 @@ function absorbRequestUsage(acc, line, turnAt) {
|
|
|
535
598
|
acc.countedRequests.add(requestId);
|
|
536
599
|
}
|
|
537
600
|
const usage = line.message?.usage ?? {};
|
|
601
|
+
const subagent = isSubagentLine(line);
|
|
538
602
|
const request = {
|
|
539
603
|
turn: turnAt,
|
|
540
604
|
...(line.message?.model === undefined ? {} : { model: line.message.model }),
|
|
605
|
+
subagent,
|
|
606
|
+
...(subagent && line.agentId !== undefined ? { agentId: line.agentId } : {}),
|
|
541
607
|
inputTokens: usage.input_tokens ?? 0,
|
|
542
608
|
outputTokens: usage.output_tokens ?? 0,
|
|
543
609
|
cacheReadTokens: usage.cache_read_input_tokens ?? 0,
|
|
544
610
|
cacheWriteTokens: usage.cache_creation_input_tokens ?? 0,
|
|
545
611
|
};
|
|
546
612
|
acc.requests.push(request);
|
|
547
|
-
const turn =
|
|
613
|
+
const turn = acc.turns[turnAt];
|
|
548
614
|
if (turn) {
|
|
549
|
-
turn.
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
615
|
+
const usage = subagent ? turn.subagent : turn;
|
|
616
|
+
usage.inputTokens += request.inputTokens;
|
|
617
|
+
usage.outputTokens += request.outputTokens;
|
|
618
|
+
usage.cacheReadTokens += request.cacheReadTokens;
|
|
619
|
+
usage.cacheWriteTokens += request.cacheWriteTokens;
|
|
553
620
|
}
|
|
554
621
|
}
|
|
555
622
|
function buildToolCall(input) {
|
|
@@ -561,6 +628,10 @@ function buildToolCall(input) {
|
|
|
561
628
|
toolUseId: input.block.id ?? `${input.turnAt}:${input.index}`,
|
|
562
629
|
tool,
|
|
563
630
|
summary: clip(summarizeInput(tool, input.block.input), 400),
|
|
631
|
+
inputKey: createHash("sha256")
|
|
632
|
+
.update(JSON.stringify(input.block.input ?? {}))
|
|
633
|
+
.digest("hex")
|
|
634
|
+
.slice(0, 16),
|
|
564
635
|
...(bakerVerb ? { bakerVerb } : {}),
|
|
565
636
|
resultBytes: 0,
|
|
566
637
|
resultTokens: 0,
|
|
@@ -581,6 +652,12 @@ function userFacingMessage(turn, text, marketerLanguage) {
|
|
|
581
652
|
...(mismatch === undefined ? {} : { languageMismatch: mismatch }),
|
|
582
653
|
};
|
|
583
654
|
}
|
|
655
|
+
/** A Bash call's whole command, which placing a failed `baker` call on it reads. */
|
|
656
|
+
function rememberCommand(acc, call, use) {
|
|
657
|
+
if (call.tool === "Bash" && typeof use.input?.command === "string") {
|
|
658
|
+
acc.commands.set(call.toolUseId, use.input.command);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
584
661
|
/** Ledger each tool call a response asked for, under the response it came in. */
|
|
585
662
|
function absorbToolUses(acc, line, turnAt, isSidechain) {
|
|
586
663
|
const uses = blocks(line).filter((block) => block.type === "tool_use");
|
|
@@ -597,6 +674,7 @@ function absorbToolUses(acc, line, turnAt, isSidechain) {
|
|
|
597
674
|
});
|
|
598
675
|
acc.toolCalls.push(call);
|
|
599
676
|
acc.pending.set(call.toolUseId, call);
|
|
677
|
+
rememberCommand(acc, call, use);
|
|
600
678
|
if (requestId !== undefined) {
|
|
601
679
|
acc.batches.set(requestId, [...(acc.batches.get(requestId) ?? []), call]);
|
|
602
680
|
}
|
|
@@ -608,12 +686,16 @@ function absorbToolUses(acc, line, turnAt, isSidechain) {
|
|
|
608
686
|
return uses.length;
|
|
609
687
|
}
|
|
610
688
|
function absorbAssistantLine(acc, line) {
|
|
611
|
-
const turnAt =
|
|
689
|
+
const turnAt = turnIndexOf(acc, line) ?? 0;
|
|
612
690
|
absorbRequestUsage(acc, line, turnAt);
|
|
613
|
-
const isSidechain = line
|
|
691
|
+
const isSidechain = isSubagentLine(line);
|
|
614
692
|
const uses = absorbToolUses(acc, line, turnAt, isSidechain);
|
|
615
693
|
const text = textOf(line);
|
|
616
|
-
if (text.trim().length === 0
|
|
694
|
+
if (text.trim().length === 0) {
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (isSidechain) {
|
|
698
|
+
acc.subagentWords.set(conversationKey(line.threadId, line.agentId), text);
|
|
617
699
|
return;
|
|
618
700
|
}
|
|
619
701
|
const turn = currentTurn(acc);
|
|
@@ -667,6 +749,131 @@ function timeCalls(acc) {
|
|
|
667
749
|
call.durationMs = took;
|
|
668
750
|
}
|
|
669
751
|
}
|
|
752
|
+
return startOf;
|
|
753
|
+
}
|
|
754
|
+
/** Whether the CLI says an invocation failed: a non-zero exit, or an `ok: false` envelope it printed. */
|
|
755
|
+
function cliFailed(invocation) {
|
|
756
|
+
return (invocation.exitCode !== undefined && invocation.exitCode !== 0) || invocation.error !== undefined;
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* What the CLI said about a failed invocation, and nothing the agent typed: the
|
|
760
|
+
* class is read from this, so `--title "Fix checkout timeout"` is not a timeout.
|
|
761
|
+
*/
|
|
762
|
+
function cliErrorOf(invocation) {
|
|
763
|
+
const exit = invocation.exitCode === undefined ? "failed" : `exited ${invocation.exitCode}`;
|
|
764
|
+
return invocation.error === undefined ? exit : `${invocation.error.code}: ${invocation.error.message} — ${exit}`;
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Its class, from the error code when the code names one: the CLI's message
|
|
768
|
+
* often echoes what the agent passed (`Invalid ID format: "zz-timeout-check"`),
|
|
769
|
+
* and read whole that `VALIDATION_ERROR` is a timeout.
|
|
770
|
+
*/
|
|
771
|
+
function classifyCliFailure(invocation) {
|
|
772
|
+
const byCode = invocation.error === undefined ? "other" : classifyFailure(invocation.error.code);
|
|
773
|
+
return byCode === "other" ? classifyFailure(cliErrorOf(invocation)) : byCode;
|
|
774
|
+
}
|
|
775
|
+
/** The error first, so clipping a long command never takes the code with it. */
|
|
776
|
+
function describeCliFailure(invocation) {
|
|
777
|
+
return invocation.error === undefined
|
|
778
|
+
? `\`baker ${invocation.argv.join(" ")}\` ${cliErrorOf(invocation)}`
|
|
779
|
+
: `${cliErrorOf(invocation)} — \`baker ${invocation.argv.join(" ")}\``;
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* The arguments of every `baker` command a shell command line runs, read the way
|
|
783
|
+
* the shell reads it: dequoted, inside pipes, lists, loops and substitutions,
|
|
784
|
+
* never inside a heredoc body or a quoted string.
|
|
785
|
+
*/
|
|
786
|
+
function bakerArgvsOf(command) {
|
|
787
|
+
const script = parse(command);
|
|
788
|
+
if (script.errors && script.errors.length > 0) {
|
|
789
|
+
return [];
|
|
790
|
+
}
|
|
791
|
+
const argvs = [];
|
|
792
|
+
// Serialised because unbash materialises a word's parts only in `toJSON`.
|
|
793
|
+
JSON.stringify(script, (_key, value) => {
|
|
794
|
+
const node = value;
|
|
795
|
+
if (node?.type === "Command" && typeof node.name?.value === "string" && /(?:^|\/)baker$/.test(node.name.value)) {
|
|
796
|
+
argvs.push((node.suffix ?? []).map((word) => String(word.value ?? "")));
|
|
797
|
+
}
|
|
798
|
+
return value;
|
|
799
|
+
});
|
|
800
|
+
return argvs;
|
|
801
|
+
}
|
|
802
|
+
/** A word the shell expands before `baker` sees it, and so matches whatever argument it became. */
|
|
803
|
+
const EXPANDED_WORD = /[$*?[`]/;
|
|
804
|
+
/** Whether a command's own `baker` words are the ones an invocation received. */
|
|
805
|
+
function ranInvocation(words, argv) {
|
|
806
|
+
return words.length === argv.length && words.every((word, index) => word === argv[index] || EXPANDED_WORD.test(word));
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* The Bash call a `baker` invocation ran inside: the one running when it
|
|
810
|
+
* started whose command runs exactly that `baker` call.
|
|
811
|
+
*
|
|
812
|
+
* Nothing else places it. Subagents run side by side, so two running calls can
|
|
813
|
+
* share a platform (`baker ads meta campaigns`, `baker ads meta ads pause`), and
|
|
814
|
+
* a watcher or a hook calls `baker` while an unrelated command runs; a failure
|
|
815
|
+
* pinned on whichever call was running reports a success as failed and the
|
|
816
|
+
* failed call as a success. When no running call ran it, or more than one did,
|
|
817
|
+
* it is left unplaced.
|
|
818
|
+
*/
|
|
819
|
+
function callRunning(invocation, acc, startOf, argvsOf) {
|
|
820
|
+
const at = stampOf(invocation.startedAt);
|
|
821
|
+
if (at === undefined) {
|
|
822
|
+
return undefined;
|
|
823
|
+
}
|
|
824
|
+
const ran = acc.toolCalls.filter((call) => {
|
|
825
|
+
const from = stampOf(startOf.get(call.toolUseId));
|
|
826
|
+
const to = stampOf(acc.answeredAt.get(call.toolUseId));
|
|
827
|
+
return (call.tool === "Bash" &&
|
|
828
|
+
from !== undefined &&
|
|
829
|
+
to !== undefined &&
|
|
830
|
+
from <= at &&
|
|
831
|
+
at <= to &&
|
|
832
|
+
argvsOf(call).some((words) => ranInvocation(words, invocation.argv)));
|
|
833
|
+
});
|
|
834
|
+
return ran.length === 1 ? ran[0] : undefined;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Count a `baker` call that failed as a failure, whatever its shell returned.
|
|
838
|
+
*
|
|
839
|
+
* A Bash result is an error only when the whole command line exited non-zero,
|
|
840
|
+
* and `| head`, `> file` or a later `; echo` make that zero. Read from the tool
|
|
841
|
+
* result alone, a Session whose every refused or timed-out `baker` call was
|
|
842
|
+
* piped reported "none of them failed" — while the CLI's own record said why
|
|
843
|
+
* each one did. A failure no call can be placed on is returned, never dropped.
|
|
844
|
+
*/
|
|
845
|
+
function absorbCliFailures(acc, cli, startOf) {
|
|
846
|
+
const unplaced = [];
|
|
847
|
+
const failedIn = new Map();
|
|
848
|
+
const argvs = new Map();
|
|
849
|
+
const argvsOf = (call) => {
|
|
850
|
+
const known = argvs.get(call.toolUseId) ?? bakerArgvsOf(acc.commands.get(call.toolUseId) ?? "");
|
|
851
|
+
argvs.set(call.toolUseId, known);
|
|
852
|
+
return known;
|
|
853
|
+
};
|
|
854
|
+
for (const invocation of cli.filter(cliFailed)) {
|
|
855
|
+
const call = callRunning(invocation, acc, startOf, argvsOf);
|
|
856
|
+
if (call === undefined) {
|
|
857
|
+
unplaced.push(invocation);
|
|
858
|
+
}
|
|
859
|
+
else if (!call.isError) {
|
|
860
|
+
failedIn.set(call, [...(failedIn.get(call) ?? []), invocation]);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
for (const [call, invocations] of failedIn) {
|
|
864
|
+
const failures = invocations.map((invocation) => ({
|
|
865
|
+
failureClass: classifyCliFailure(invocation),
|
|
866
|
+
message: clip(describeCliFailure(invocation), 400),
|
|
867
|
+
durationMs: invocation.durationMs ?? 0,
|
|
868
|
+
}));
|
|
869
|
+
for (const failure of failures) {
|
|
870
|
+
absorbFailure(acc, call, failure.message, failure.failureClass);
|
|
871
|
+
}
|
|
872
|
+
acc.cliFailures.set(call.toolUseId, failures);
|
|
873
|
+
}
|
|
874
|
+
const order = new Map(acc.toolCalls.map((call, index) => [call.toolUseId, index]));
|
|
875
|
+
acc.guards.sort((a, b) => (order.get(a.toolUseId) ?? 0) - (order.get(b.toolUseId) ?? 0));
|
|
876
|
+
return unplaced;
|
|
670
877
|
}
|
|
671
878
|
/** The latest of some ISO timestamps, or nothing when none is given. */
|
|
672
879
|
function latest(stamps) {
|
|
@@ -698,7 +905,7 @@ function attributeGuardFollowUps(guards, calls) {
|
|
|
698
905
|
.find((call) => call.threadId === refused.threadId &&
|
|
699
906
|
call.isSidechain === refused.isSidechain &&
|
|
700
907
|
call.agentId === refused.agentId &&
|
|
701
|
-
call.
|
|
908
|
+
call.inputKey !== refused.inputKey);
|
|
702
909
|
if (after) {
|
|
703
910
|
guard.nextAction = `${after.tool}: ${clip(after.summary, 160)}`;
|
|
704
911
|
}
|
|
@@ -715,15 +922,15 @@ function attributeGuardFollowUps(guards, calls) {
|
|
|
715
922
|
function countGuardRepeats(guards) {
|
|
716
923
|
const counts = new Map();
|
|
717
924
|
for (const guard of guards) {
|
|
718
|
-
const key = `${guard.tool}\u0000${guard.
|
|
925
|
+
const key = `${guard.tool}\u0000${guard.inputKey}\u0000${guard.guard ?? guard.message}`;
|
|
719
926
|
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
720
927
|
}
|
|
721
928
|
for (const guard of guards) {
|
|
722
|
-
const key = `${guard.tool}\u0000${guard.
|
|
929
|
+
const key = `${guard.tool}\u0000${guard.inputKey}\u0000${guard.guard ?? guard.message}`;
|
|
723
930
|
guard.repeats = counts.get(key) ?? 1;
|
|
724
931
|
}
|
|
725
932
|
}
|
|
726
|
-
export function buildTranscriptDigest(lines) {
|
|
933
|
+
export function buildTranscriptDigest(lines, cli = []) {
|
|
727
934
|
const acc = {
|
|
728
935
|
turns: [],
|
|
729
936
|
toolCalls: [],
|
|
@@ -739,6 +946,10 @@ export function buildTranscriptDigest(lines) {
|
|
|
739
946
|
marketerLanguage: new Map(),
|
|
740
947
|
batches: new Map(),
|
|
741
948
|
answeredAt: new Map(),
|
|
949
|
+
subagentTurns: new Map(),
|
|
950
|
+
subagentWords: new Map(),
|
|
951
|
+
commands: new Map(),
|
|
952
|
+
cliFailures: new Map(),
|
|
742
953
|
};
|
|
743
954
|
for (const line of lines) {
|
|
744
955
|
const openTurns = acc.turns.length;
|
|
@@ -770,7 +981,8 @@ export function buildTranscriptDigest(lines) {
|
|
|
770
981
|
call.failureClass = "timeout";
|
|
771
982
|
acc.failureMessages.set(call.toolUseId, "No result was ever recorded for this call.");
|
|
772
983
|
}
|
|
773
|
-
timeCalls(acc);
|
|
984
|
+
const startOf = timeCalls(acc);
|
|
985
|
+
const unplacedCliFailures = absorbCliFailures(acc, cli, startOf);
|
|
774
986
|
attributeGuardFollowUps(acc.guards, acc.toolCalls);
|
|
775
987
|
countGuardRepeats(acc.guards);
|
|
776
988
|
const threadStamps = stampsByThread(lines);
|
|
@@ -789,7 +1001,8 @@ export function buildTranscriptDigest(lines) {
|
|
|
789
1001
|
.filter((call) => call.durationMs !== undefined)
|
|
790
1002
|
.sort((a, b) => (b.durationMs ?? 0) - (a.durationMs ?? 0))
|
|
791
1003
|
.slice(0, 15),
|
|
792
|
-
failures: aggregateFailures(acc.toolCalls, acc.failureMessages),
|
|
1004
|
+
failures: aggregateFailures(acc.toolCalls, acc.failureMessages, acc.cliFailures),
|
|
1005
|
+
unplacedCliFailures: unplacedCliFailures.map(describeCliFailure),
|
|
793
1006
|
flaky: findFlakyCalls(acc.toolCalls),
|
|
794
1007
|
introspectionCalls: acc.toolCalls.filter((call) => call.bakerVerb?.startsWith("baker schema") || /--help\b/.test(call.summary)),
|
|
795
1008
|
hooks: [...acc.hooks.values()].sort((a, b) => b.fired - a.fired),
|
|
@@ -809,30 +1022,37 @@ export function buildTranscriptDigest(lines) {
|
|
|
809
1022
|
* a bad moment, eleven of them at the same bound is a default set for a network
|
|
810
1023
|
* this sandbox does not have.
|
|
811
1024
|
*/
|
|
812
|
-
function
|
|
1025
|
+
function ownFailure(call, failureClass, messages) {
|
|
1026
|
+
return { failureClass, message: messages.get(call.toolUseId) ?? "", durationMs: call.durationMs ?? 0 };
|
|
1027
|
+
}
|
|
1028
|
+
function aggregateFailures(calls, messages, cliFailures) {
|
|
813
1029
|
const map = new Map();
|
|
814
1030
|
for (const call of calls) {
|
|
815
1031
|
if (!call.isError || call.failureClass === undefined) {
|
|
816
1032
|
continue;
|
|
817
1033
|
}
|
|
818
|
-
|
|
819
|
-
const
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
entry.
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
1034
|
+
// A command line that ran several failed `baker` calls counts each of them.
|
|
1035
|
+
const failures = cliFailures.get(call.toolUseId) ?? [ownFailure(call, call.failureClass, messages)];
|
|
1036
|
+
for (const failure of failures) {
|
|
1037
|
+
const key = `${failure.failureClass}::${call.tool}`;
|
|
1038
|
+
const entry = map.get(key) ?? {
|
|
1039
|
+
failureClass: failure.failureClass,
|
|
1040
|
+
tool: call.tool,
|
|
1041
|
+
calls: 0,
|
|
1042
|
+
totalMs: 0,
|
|
1043
|
+
example: failure.message,
|
|
1044
|
+
turns: [],
|
|
1045
|
+
};
|
|
1046
|
+
entry.calls += 1;
|
|
1047
|
+
entry.totalMs += failure.durationMs;
|
|
1048
|
+
if (entry.example.length === 0) {
|
|
1049
|
+
entry.example = failure.message;
|
|
1050
|
+
}
|
|
1051
|
+
if (!entry.turns.includes(call.turn)) {
|
|
1052
|
+
entry.turns.push(call.turn);
|
|
1053
|
+
}
|
|
1054
|
+
map.set(key, entry);
|
|
834
1055
|
}
|
|
835
|
-
map.set(key, entry);
|
|
836
1056
|
}
|
|
837
1057
|
return [...map.values()].sort((a, b) => b.calls - a.calls);
|
|
838
1058
|
}
|
|
@@ -847,7 +1067,7 @@ function aggregateFailures(calls, messages) {
|
|
|
847
1067
|
function findFlakyCalls(calls) {
|
|
848
1068
|
const groups = new Map();
|
|
849
1069
|
for (const call of calls) {
|
|
850
|
-
const key = `${call.tool}\u0000${call.
|
|
1070
|
+
const key = `${call.tool}\u0000${call.inputKey}`;
|
|
851
1071
|
groups.set(key, [...(groups.get(key) ?? []), call]);
|
|
852
1072
|
}
|
|
853
1073
|
const out = [];
|
|
@@ -877,7 +1097,18 @@ function findFlakyCalls(calls) {
|
|
|
877
1097
|
* and this is what says where in the Session to look.
|
|
878
1098
|
*/
|
|
879
1099
|
function emptyThread(threadId) {
|
|
880
|
-
return {
|
|
1100
|
+
return {
|
|
1101
|
+
threadId,
|
|
1102
|
+
turns: 0,
|
|
1103
|
+
toolCalls: 0,
|
|
1104
|
+
resultBytes: 0,
|
|
1105
|
+
resultTokens: 0,
|
|
1106
|
+
subagentToolCalls: 0,
|
|
1107
|
+
subagentResultTokens: 0,
|
|
1108
|
+
errors: 0,
|
|
1109
|
+
denials: 0,
|
|
1110
|
+
workingMs: 0,
|
|
1111
|
+
};
|
|
881
1112
|
}
|
|
882
1113
|
/** Milliseconds parsed off an ISO timestamp, or nothing. */
|
|
883
1114
|
function stampOf(value) {
|
|
@@ -914,6 +1145,8 @@ function aggregateByThread(turns, calls, stamps) {
|
|
|
914
1145
|
entry.toolCalls += 1;
|
|
915
1146
|
entry.resultBytes += call.resultBytes;
|
|
916
1147
|
entry.resultTokens += call.resultTokens;
|
|
1148
|
+
entry.subagentToolCalls += call.isSidechain ? 1 : 0;
|
|
1149
|
+
entry.subagentResultTokens += call.isSidechain ? call.resultTokens : 0;
|
|
917
1150
|
entry.errors += call.isError ? 1 : 0;
|
|
918
1151
|
}
|
|
919
1152
|
for (const [threadId, values] of stamps) {
|
|
@@ -930,14 +1163,18 @@ function aggregateByTool(calls) {
|
|
|
930
1163
|
const entry = map.get(call.tool) ?? {
|
|
931
1164
|
tool: call.tool,
|
|
932
1165
|
calls: 0,
|
|
1166
|
+
subagentCalls: 0,
|
|
933
1167
|
totalBytes: 0,
|
|
934
1168
|
totalTokens: 0,
|
|
1169
|
+
subagentTokens: 0,
|
|
935
1170
|
maxBytes: 0,
|
|
936
1171
|
errors: 0,
|
|
937
1172
|
};
|
|
938
1173
|
entry.calls += 1;
|
|
1174
|
+
entry.subagentCalls += call.isSidechain ? 1 : 0;
|
|
939
1175
|
entry.totalBytes += call.resultBytes;
|
|
940
1176
|
entry.totalTokens += call.resultTokens;
|
|
1177
|
+
entry.subagentTokens += call.isSidechain ? call.resultTokens : 0;
|
|
941
1178
|
entry.maxBytes = Math.max(entry.maxBytes, call.resultBytes);
|
|
942
1179
|
entry.errors += call.isError ? 1 : 0;
|
|
943
1180
|
map.set(call.tool, entry);
|
|
@@ -974,7 +1211,7 @@ function aggregateByVerb(calls) {
|
|
|
974
1211
|
function findRepeats(calls) {
|
|
975
1212
|
const seen = new Map();
|
|
976
1213
|
for (const call of calls) {
|
|
977
|
-
const key = `${call.tool}\u0000${call.
|
|
1214
|
+
const key = `${call.tool}\u0000${call.inputKey}`;
|
|
978
1215
|
const entry = seen.get(key);
|
|
979
1216
|
if (entry) {
|
|
980
1217
|
entry.extra += 1;
|
|
@@ -1029,6 +1266,9 @@ function absorbInvocation(entry, line) {
|
|
|
1029
1266
|
return;
|
|
1030
1267
|
}
|
|
1031
1268
|
entry.argv = line.argv;
|
|
1269
|
+
if (line.ts !== undefined) {
|
|
1270
|
+
entry.startedAt = line.ts;
|
|
1271
|
+
}
|
|
1032
1272
|
const verb = bakerVerbOf(`baker ${line.argv.join(" ")}`);
|
|
1033
1273
|
if (verb) {
|
|
1034
1274
|
entry.verb = verb;
|
|
@@ -1053,14 +1293,38 @@ function absorbExit(entry, line) {
|
|
|
1053
1293
|
entry.durationMs = line.durationMs;
|
|
1054
1294
|
}
|
|
1055
1295
|
}
|
|
1296
|
+
/** What it printed, and the error it reported when the text is an `ok: false` envelope. */
|
|
1297
|
+
function absorbOutput(entry, line) {
|
|
1298
|
+
if (typeof line.text !== "string") {
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
entry.printedBytes += Buffer.byteLength(line.text);
|
|
1302
|
+
const error = printedError(line.text);
|
|
1303
|
+
if (error !== undefined && entry.error === undefined) {
|
|
1304
|
+
entry.error = error;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
function printedError(text) {
|
|
1308
|
+
let parsed;
|
|
1309
|
+
try {
|
|
1310
|
+
parsed = JSON.parse(text);
|
|
1311
|
+
}
|
|
1312
|
+
catch {
|
|
1313
|
+
return undefined;
|
|
1314
|
+
}
|
|
1315
|
+
if (typeof parsed !== "object" || parsed === null || parsed.ok !== false) {
|
|
1316
|
+
return undefined;
|
|
1317
|
+
}
|
|
1318
|
+
const error = parsed.error;
|
|
1319
|
+
return {
|
|
1320
|
+
code: typeof error?.code === "string" ? error.code : "ERROR",
|
|
1321
|
+
message: typeof error?.message === "string" ? error.message : "",
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1056
1324
|
const DEBUG_LINE_HANDLERS = {
|
|
1057
1325
|
invocation: absorbInvocation,
|
|
1058
1326
|
http: absorbHttpCall,
|
|
1059
|
-
output:
|
|
1060
|
-
if (typeof line.text === "string") {
|
|
1061
|
-
entry.printedBytes += Buffer.byteLength(line.text);
|
|
1062
|
-
}
|
|
1063
|
-
},
|
|
1327
|
+
output: absorbOutput,
|
|
1064
1328
|
exit: absorbExit,
|
|
1065
1329
|
};
|
|
1066
1330
|
/**
|
|
@@ -1141,12 +1405,25 @@ export function renderTurns(digest) {
|
|
|
1141
1405
|
"",
|
|
1142
1406
|
digest.threads.length === 0
|
|
1143
1407
|
? "No thread could be attributed — the transcripts carried no thread id."
|
|
1144
|
-
: table([
|
|
1408
|
+
: table([
|
|
1409
|
+
"thread",
|
|
1410
|
+
"turns",
|
|
1411
|
+
"tool calls",
|
|
1412
|
+
"subagent calls",
|
|
1413
|
+
"result bytes",
|
|
1414
|
+
"~main result tokens",
|
|
1415
|
+
"~subagent result tokens",
|
|
1416
|
+
"errors",
|
|
1417
|
+
"denials",
|
|
1418
|
+
"running",
|
|
1419
|
+
], digest.threads.map((thread) => [
|
|
1145
1420
|
thread.threadId,
|
|
1146
1421
|
thread.turns,
|
|
1147
1422
|
thread.toolCalls,
|
|
1423
|
+
thread.subagentToolCalls,
|
|
1148
1424
|
thread.resultBytes,
|
|
1149
|
-
thread.resultTokens,
|
|
1425
|
+
thread.resultTokens - thread.subagentResultTokens,
|
|
1426
|
+
thread.subagentResultTokens,
|
|
1150
1427
|
thread.errors,
|
|
1151
1428
|
thread.denials,
|
|
1152
1429
|
`${Math.round(thread.workingMs / 1000)}s`,
|
|
@@ -1154,7 +1431,25 @@ export function renderTurns(digest) {
|
|
|
1154
1431
|
"",
|
|
1155
1432
|
"## Every turn",
|
|
1156
1433
|
"",
|
|
1157
|
-
|
|
1434
|
+
"`in`, `out` and `cache read` are the main agent's context; `subagent` columns",
|
|
1435
|
+
"are what that turn's subagents spent, which never reached it.",
|
|
1436
|
+
"",
|
|
1437
|
+
table([
|
|
1438
|
+
"#",
|
|
1439
|
+
"thread",
|
|
1440
|
+
"at",
|
|
1441
|
+
"user",
|
|
1442
|
+
"assistant",
|
|
1443
|
+
"tools",
|
|
1444
|
+
"in",
|
|
1445
|
+
"out",
|
|
1446
|
+
"cache read",
|
|
1447
|
+
"subagent in",
|
|
1448
|
+
"subagent out",
|
|
1449
|
+
"subagent cache read",
|
|
1450
|
+
"denials",
|
|
1451
|
+
"compacted",
|
|
1452
|
+
], digest.turns.map((turn) => [
|
|
1158
1453
|
turn.index,
|
|
1159
1454
|
turn.threadId ?? "",
|
|
1160
1455
|
turn.timestamp ?? "",
|
|
@@ -1164,23 +1459,36 @@ export function renderTurns(digest) {
|
|
|
1164
1459
|
turn.inputTokens,
|
|
1165
1460
|
turn.outputTokens,
|
|
1166
1461
|
turn.cacheReadTokens,
|
|
1462
|
+
turn.subagent.inputTokens,
|
|
1463
|
+
turn.subagent.outputTokens,
|
|
1464
|
+
turn.subagent.cacheReadTokens,
|
|
1167
1465
|
turn.denials,
|
|
1168
1466
|
turn.compacted ? "yes" : "",
|
|
1169
1467
|
])),
|
|
1170
1468
|
"",
|
|
1171
1469
|
].join("\n");
|
|
1172
1470
|
}
|
|
1471
|
+
/** Who made a call: the main agent, or the subagent by its id. */
|
|
1472
|
+
function agentOf(call) {
|
|
1473
|
+
return call.isSidechain ? `subagent ${call.agentId ?? "(unnamed)"}` : "main";
|
|
1474
|
+
}
|
|
1173
1475
|
export function renderTools(digest) {
|
|
1174
1476
|
const sections = [
|
|
1175
1477
|
"# Tool calls",
|
|
1176
1478
|
"",
|
|
1177
1479
|
"## Per tool",
|
|
1178
1480
|
"",
|
|
1179
|
-
|
|
1481
|
+
"`main` is what the agent the marketer talks to called; `subagent` ran in a",
|
|
1482
|
+
"subagent, and its results never reached the main agent's context.",
|
|
1483
|
+
"",
|
|
1484
|
+
table(["tool", "calls", "main", "subagent", "total bytes", "~main tokens", "~subagent tokens", "max bytes", "errors"], digest.byTool.map((entry) => [
|
|
1180
1485
|
entry.tool,
|
|
1181
1486
|
entry.calls,
|
|
1487
|
+
entry.calls - entry.subagentCalls,
|
|
1488
|
+
entry.subagentCalls,
|
|
1182
1489
|
entry.totalBytes,
|
|
1183
|
-
entry.totalTokens,
|
|
1490
|
+
entry.totalTokens - entry.subagentTokens,
|
|
1491
|
+
entry.subagentTokens,
|
|
1184
1492
|
entry.maxBytes,
|
|
1185
1493
|
entry.errors,
|
|
1186
1494
|
])),
|
|
@@ -1208,7 +1516,14 @@ export function renderTools(digest) {
|
|
|
1208
1516
|
"",
|
|
1209
1517
|
"## Ten largest single results (by what the model received)",
|
|
1210
1518
|
"",
|
|
1211
|
-
table(["turn", "tool", "input", "bytes", "~tokens"], digest.largestResults.map((call) => [
|
|
1519
|
+
table(["turn", "agent", "tool", "input", "bytes", "~tokens"], digest.largestResults.map((call) => [
|
|
1520
|
+
call.turn,
|
|
1521
|
+
agentOf(call),
|
|
1522
|
+
call.tool,
|
|
1523
|
+
call.summary,
|
|
1524
|
+
call.resultBytes,
|
|
1525
|
+
call.resultTokens,
|
|
1526
|
+
])),
|
|
1212
1527
|
"",
|
|
1213
1528
|
"## Introspection calls (`baker schema`, `--help`)",
|
|
1214
1529
|
"",
|
|
@@ -1258,6 +1573,17 @@ export function renderFailures(digest) {
|
|
|
1258
1573
|
clip(entry.example, 160),
|
|
1259
1574
|
])),
|
|
1260
1575
|
"",
|
|
1576
|
+
...(digest.unplacedCliFailures.length === 0
|
|
1577
|
+
? []
|
|
1578
|
+
: [
|
|
1579
|
+
"## Failed `baker` calls no tool call can be placed on",
|
|
1580
|
+
"",
|
|
1581
|
+
"The CLI recorded these as failed, and when each started no loaded transcript",
|
|
1582
|
+
"had exactly one running Bash call whose command ran that `baker` call.",
|
|
1583
|
+
"",
|
|
1584
|
+
...digest.unplacedCliFailures.map((failure) => `- ${clip(failure, 300)}`),
|
|
1585
|
+
"",
|
|
1586
|
+
]),
|
|
1261
1587
|
"## The same call, two answers",
|
|
1262
1588
|
"",
|
|
1263
1589
|
"Identical tool and input, one attempt failing and another succeeding, with",
|
|
@@ -1279,8 +1605,9 @@ export function renderFailures(digest) {
|
|
|
1279
1605
|
"",
|
|
1280
1606
|
digest.slowest.length === 0
|
|
1281
1607
|
? "No call carried both a request and a result timestamp, so nothing here can be timed."
|
|
1282
|
-
: table(["turn", "tool", "took", "failed", "input"], digest.slowest.map((call) => [
|
|
1608
|
+
: table(["turn", "agent", "tool", "took", "failed", "input"], digest.slowest.map((call) => [
|
|
1283
1609
|
call.turn,
|
|
1610
|
+
agentOf(call),
|
|
1284
1611
|
call.tool,
|
|
1285
1612
|
secs(call.durationMs ?? 0),
|
|
1286
1613
|
call.isError ? (call.failureClass ?? "yes") : "",
|
|
@@ -1304,6 +1631,9 @@ export function renderHooks(digest) {
|
|
|
1304
1631
|
"",
|
|
1305
1632
|
].join("\n");
|
|
1306
1633
|
}
|
|
1634
|
+
function guardAgent(guard) {
|
|
1635
|
+
return guard.agentId === undefined ? "" : ` · subagent ${guard.agentId}`;
|
|
1636
|
+
}
|
|
1307
1637
|
export function renderGuards(digest) {
|
|
1308
1638
|
const refusals = digest.guards.filter((guard) => guard.refusal);
|
|
1309
1639
|
const errors = digest.guards.filter((guard) => !guard.refusal);
|
|
@@ -1323,13 +1653,14 @@ export function renderGuards(digest) {
|
|
|
1323
1653
|
: [
|
|
1324
1654
|
`${refusals.length} refusals.`,
|
|
1325
1655
|
"",
|
|
1326
|
-
"`Intent` is the agent's own words
|
|
1327
|
-
"
|
|
1656
|
+
"`Intent` is the refused agent's own words — the main agent's in that",
|
|
1657
|
+
"turn, or the subagent's own when a subagent was refused: what it thought",
|
|
1658
|
+
"it was doing. It is the transcript's only answer to why it went there;",
|
|
1328
1659
|
"the rest of the answer is in whatever doc, skill, hint or example",
|
|
1329
1660
|
"pointed it that way, which is yours to go and open.",
|
|
1330
1661
|
"",
|
|
1331
1662
|
...refusals.map((guard) => [
|
|
1332
|
-
`### Turn ${guard.turn} · ${guard.tool}${guard.guard ? ` · ${guard.guard}` : ""}${guard.repeats > 1 ? ` · refused ${guard.repeats}× this Session` : ""}`,
|
|
1663
|
+
`### Turn ${guard.turn}${guardAgent(guard)} · ${guard.tool}${guard.guard ? ` · ${guard.guard}` : ""}${guard.repeats > 1 ? ` · refused ${guard.repeats}× this Session` : ""}`,
|
|
1333
1664
|
"",
|
|
1334
1665
|
`Attempted: \`${guard.attempted}\``,
|
|
1335
1666
|
"",
|
|
@@ -1348,7 +1679,7 @@ export function renderGuards(digest) {
|
|
|
1348
1679
|
? "No tool returned an error."
|
|
1349
1680
|
: errors
|
|
1350
1681
|
.map((guard) => [
|
|
1351
|
-
`### Turn ${guard.turn} · ${guard.tool}${guard.repeats > 1 ? ` · ${guard.repeats}×` : ""}`,
|
|
1682
|
+
`### Turn ${guard.turn}${guardAgent(guard)} · ${guard.tool}${guard.repeats > 1 ? ` · ${guard.repeats}×` : ""}`,
|
|
1352
1683
|
"",
|
|
1353
1684
|
`Attempted: \`${guard.attempted}\``,
|
|
1354
1685
|
"",
|