@garuda-agent/garuda 0.18.0 → 0.18.2
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/CHANGELOG.md +156 -0
- package/README.md +3 -2
- package/dist/{acpCommand-IUZPLPLI.js → acpCommand-PUJHFWYU.js} +10 -8
- package/dist/advise-C777SZ5M.js +433 -0
- package/dist/brief-RWKESOPT.js +91 -0
- package/dist/buildCheck-HB46NWLX.js +19 -0
- package/dist/buildInfo-Z4P7QAQL.js +8 -0
- package/dist/chunk-2OYTNNUJ.js +216 -0
- package/dist/chunk-4BELTWP4.js +156 -0
- package/dist/chunk-4QRTTRVK.js +88 -0
- package/dist/chunk-6JQTPW3P.js +88 -0
- package/dist/chunk-A5DLAXB4.js +1780 -0
- package/dist/{chunk-FARS4R67.js → chunk-AX3A5KPH.js} +18 -1
- package/dist/chunk-CFDRP6XW.js +9 -0
- package/dist/chunk-E6YRCDSF.js +9 -0
- package/dist/{chunk-OQ26W5L4.js → chunk-HP6EGFHV.js} +132 -75
- package/dist/chunk-L55VUSX3.js +54 -0
- package/dist/{chunk-B4SJY7KT.js → chunk-LMKGSYJC.js} +77 -8
- package/dist/{chunk-Q4P3W657.js → chunk-LWNMEGDF.js} +10 -1124
- package/dist/{chunk-23FXBNBB.js → chunk-MNE7HHX2.js} +2 -1
- package/dist/{chunk-UKIE4AAM.js → chunk-NLS7GMF7.js} +11 -105
- package/dist/{chunk-6XHON3Q6.js → chunk-QXTHEFKP.js} +3 -3
- package/dist/{chunk-4BVCYCQT.js → chunk-R7RCW4BQ.js} +9 -7
- package/dist/{chunk-NHWHG3SW.js → chunk-STVFWSIL.js} +350 -57
- package/dist/{chunk-5B5HDTUR.js → chunk-TWQ7V2L6.js} +2 -2
- package/dist/chunk-ULAJPMY7.js +107 -0
- package/dist/chunk-VISKX36R.js +306 -0
- package/dist/{chunk-55CFEEIN.js → chunk-WK5NE6RY.js} +2 -2
- package/dist/chunk-Y62J7PTD.js +90 -0
- package/dist/chunk-YAQ6QLKB.js +40 -0
- package/dist/{chunk-FOPBOB7W.js → chunk-YEH2OONM.js} +6 -4
- package/dist/{chunk-7TH3NHCQ.js → chunk-ZKT4P2PC.js} +2 -2
- package/dist/cli/index.js +242 -52
- package/dist/{create-W6BV75QX.js → create-OACSGJ37.js} +5 -5
- package/dist/{evalCommand-SKJQ6CW2.js → evalCommand-MRO6ENCA.js} +77 -39
- package/dist/{evalTune-CZRCPSZ3.js → evalTune-VL5RHYOG.js} +24 -18
- package/dist/{export-SJ3DUP6W.js → export-ZPF3AMO6.js} +27 -4
- package/dist/exportHtml-ISISG5HG.js +21 -0
- package/dist/{inkChat-WYYAFZXD.js → inkChat-UDFX6GH6.js} +256 -41
- package/dist/{install-TKWN5AYH.js → install-4ST5BNRG.js} +3 -3
- package/dist/{jobCommand-2ODQLHB3.js → jobCommand-ILOTYOX3.js} +8 -7
- package/dist/{launchd-3A3ONEW5.js → launchd-QG6R5RAK.js} +4 -4
- package/dist/{lspCommand-ZZ6R4PA2.js → lspCommand-X4FTTONR.js} +3 -3
- package/dist/{manager-TCYTA5XU.js → manager-YH7722C2.js} +3 -3
- package/dist/{nightCommand-WJU6VZA6.js → nightCommand-XPWT6CQZ.js} +8 -7
- package/dist/receiptView-YFDHV253.js +19 -0
- package/dist/{remove-42WQNVPX.js → remove-STX5UO7X.js} +3 -3
- package/dist/{run-PTMRQCIU.js → run-W4TK52WT.js} +5 -5
- package/dist/runner-EBF6EOYM.js +97 -0
- package/dist/{server-VEYIJHEU.js → server-ASM73QVY.js} +3 -3
- package/dist/tour-CEVMAC5S.js +18 -0
- package/dist/tune-KSNKBPNJ.js +51 -0
- package/dist/uiState-YFJQ43D7.js +16 -0
- package/package.json +1 -1
- package/dist/chunk-4JSUUDFQ.js +0 -118
- package/dist/chunk-C57GHLKP.js +0 -9
- package/dist/chunk-DOCDCQVM.js +0 -484
- package/dist/tune-YVI2KWQF.js +0 -31
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
parseTestCount
|
|
4
|
+
} from "./chunk-4QRTTRVK.js";
|
|
5
|
+
import {
|
|
6
|
+
formatCost,
|
|
7
|
+
jobCost,
|
|
8
|
+
sumCosts
|
|
9
|
+
} from "./chunk-3MRQJKUO.js";
|
|
10
|
+
import "./chunk-AOGFYUKG.js";
|
|
11
|
+
import {
|
|
12
|
+
JOBS_DIR,
|
|
13
|
+
listJobs
|
|
14
|
+
} from "./chunk-VTMD5IHD.js";
|
|
15
|
+
import "./chunk-UIRJOQQS.js";
|
|
16
|
+
import "./chunk-4LFJFDCF.js";
|
|
17
|
+
import "./chunk-3NJNVZTK.js";
|
|
18
|
+
|
|
19
|
+
// src/jobs/brief.ts
|
|
20
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
21
|
+
import { join } from "path";
|
|
22
|
+
var BRIEF_FILE = "brief.seen";
|
|
23
|
+
var FIRST_BRIEF_MS = 24 * 36e5;
|
|
24
|
+
function testsText(job) {
|
|
25
|
+
const proof = job.result?.proof;
|
|
26
|
+
const after = proof?.after;
|
|
27
|
+
if (after === void 0) return "no tests";
|
|
28
|
+
const count = (tail) => tail === void 0 ? void 0 : parseTestCount(tail)?.total;
|
|
29
|
+
const before = count(proof?.before?.tail);
|
|
30
|
+
const now = count(after.tail);
|
|
31
|
+
const passed = after.exitCode === 0 && !after.timedOut;
|
|
32
|
+
if (before !== void 0 && now !== void 0 && passed) return `tests ${before} \u2192 ${now}`;
|
|
33
|
+
return passed ? "tests pass" : "tests fail after the change";
|
|
34
|
+
}
|
|
35
|
+
function jobLine(job, paint) {
|
|
36
|
+
const verdict = job.result?.proof?.verdict;
|
|
37
|
+
const ok = job.status === "done" && verdict === "ready";
|
|
38
|
+
const mark = ok ? paint("green", "\u2713") : paint("red", "\u2717");
|
|
39
|
+
const what = verdict === "ready" ? "ready to merge" : verdict === "needs-look" ? "needs a look" : job.status === "done" ? "done, no verdict" : job.status;
|
|
40
|
+
const title = job.title.length > 32 ? `${job.title.slice(0, 31)}\u2026` : job.title;
|
|
41
|
+
return ` ${mark} ${title.padEnd(32)} ${what.padEnd(16)} ${testsText(job).padEnd(26)} ${paint("dim", job.branch)}`;
|
|
42
|
+
}
|
|
43
|
+
function briefText(jobs, paint) {
|
|
44
|
+
if (jobs.length === 0) return void 0;
|
|
45
|
+
const cost = sumCosts(jobs.flatMap((j) => j.result === void 0 ? [] : [jobCost(j.result)]));
|
|
46
|
+
return [
|
|
47
|
+
paint(
|
|
48
|
+
"bold",
|
|
49
|
+
`While you were away: ${jobs.length} job${jobs.length === 1 ? "" : "s"} ran (${formatCost(cost)})`
|
|
50
|
+
),
|
|
51
|
+
...jobs.map((j) => jobLine(j, paint)),
|
|
52
|
+
paint(
|
|
53
|
+
"dim",
|
|
54
|
+
" /jobs <id> shows a job's report and review \xB7 git diff <base>...<branch> shows its change"
|
|
55
|
+
)
|
|
56
|
+
].join("\n");
|
|
57
|
+
}
|
|
58
|
+
function endedSince(jobs, since) {
|
|
59
|
+
return jobs.filter((j) => j.endedAt !== void 0 && Date.parse(j.endedAt) > since).filter((j) => j.status === "done" || j.status === "stopped" || j.status === "failed").sort((a, b) => Date.parse(a.endedAt ?? "") - Date.parse(b.endedAt ?? ""));
|
|
60
|
+
}
|
|
61
|
+
async function morningBrief(root, paint, now = Date.now()) {
|
|
62
|
+
const dir = join(root, JOBS_DIR);
|
|
63
|
+
const file = join(dir, BRIEF_FILE);
|
|
64
|
+
let since = now - FIRST_BRIEF_MS;
|
|
65
|
+
try {
|
|
66
|
+
const data = JSON.parse(await readFile(file, "utf8"));
|
|
67
|
+
if (typeof data.seenAt === "string" && !Number.isNaN(Date.parse(data.seenAt))) {
|
|
68
|
+
since = Date.parse(data.seenAt);
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
}
|
|
72
|
+
const jobs = endedSince(await listJobs(root), since);
|
|
73
|
+
if (jobs.length === 0) return void 0;
|
|
74
|
+
try {
|
|
75
|
+
await mkdir(dir, { recursive: true });
|
|
76
|
+
await writeFile(file, `${JSON.stringify({ seenAt: new Date(now).toISOString() })}
|
|
77
|
+
`);
|
|
78
|
+
} catch {
|
|
79
|
+
}
|
|
80
|
+
return briefText(jobs, paint);
|
|
81
|
+
}
|
|
82
|
+
export {
|
|
83
|
+
BRIEF_FILE,
|
|
84
|
+
FIRST_BRIEF_MS,
|
|
85
|
+
briefText,
|
|
86
|
+
endedSince,
|
|
87
|
+
jobLine,
|
|
88
|
+
morningBrief,
|
|
89
|
+
testsText
|
|
90
|
+
};
|
|
91
|
+
//# sourceMappingURL=brief-RWKESOPT.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CHAT_STALE,
|
|
4
|
+
garudaCommit,
|
|
5
|
+
staleBuildNote
|
|
6
|
+
} from "./chunk-YAQ6QLKB.js";
|
|
7
|
+
import "./chunk-QXTHEFKP.js";
|
|
8
|
+
import "./chunk-LMKGSYJC.js";
|
|
9
|
+
import "./chunk-BHZ6G65V.js";
|
|
10
|
+
import "./chunk-P6XWWIEK.js";
|
|
11
|
+
import "./chunk-UIRJOQQS.js";
|
|
12
|
+
import "./chunk-4LFJFDCF.js";
|
|
13
|
+
import "./chunk-3NJNVZTK.js";
|
|
14
|
+
export {
|
|
15
|
+
CHAT_STALE,
|
|
16
|
+
garudaCommit,
|
|
17
|
+
staleBuildNote
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=buildCheck-HB46NWLX.js.map
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
budgetSpent,
|
|
4
|
+
isCostIncomplete,
|
|
5
|
+
pendingChildTokens
|
|
6
|
+
} from "./chunk-NLS7GMF7.js";
|
|
7
|
+
import {
|
|
8
|
+
totalTokens
|
|
9
|
+
} from "./chunk-7KBOIPUT.js";
|
|
10
|
+
|
|
11
|
+
// src/cli/report.ts
|
|
12
|
+
function formatTokens(n) {
|
|
13
|
+
if (n < 1e3) return String(n);
|
|
14
|
+
if (n < 1e6) return `${(n / 1e3).toFixed(1)}k`;
|
|
15
|
+
return `${(n / 1e6).toFixed(1)}M`;
|
|
16
|
+
}
|
|
17
|
+
function formatUsd(usd, incomplete = false) {
|
|
18
|
+
if (usd === void 0) return "cost unknown";
|
|
19
|
+
const amount = usd < 0.01 ? `$${usd.toFixed(4)}` : `$${usd.toFixed(2)}`;
|
|
20
|
+
return incomplete ? `\u2265 ${amount} (incomplete)` : amount;
|
|
21
|
+
}
|
|
22
|
+
function budgetNote(session) {
|
|
23
|
+
const unreported = session.unreportedTokens;
|
|
24
|
+
const children = pendingChildTokens(session);
|
|
25
|
+
if (unreported <= 0 && children <= 0) return void 0;
|
|
26
|
+
const parts = [
|
|
27
|
+
...unreported > 0 ? [`${formatTokens(unreported)} reserved without reported usage`] : [],
|
|
28
|
+
...children > 0 ? [`${formatTokens(children)} from subagents with no report`] : []
|
|
29
|
+
];
|
|
30
|
+
return `budget ${formatTokens(budgetSpent(session))} tokens (${parts.join(", ")})`;
|
|
31
|
+
}
|
|
32
|
+
function usageLine(result, session, runCostUsd, contextWindow, runCostIncomplete = false) {
|
|
33
|
+
const u = result.usage;
|
|
34
|
+
const input = u.inputTokens + u.cacheReadTokens + u.cacheWriteTokens;
|
|
35
|
+
const percent = Math.round(session.contextTokens / contextWindow * 100);
|
|
36
|
+
return [
|
|
37
|
+
`${result.stopReason} \xB7 ${result.steps} step(s)`,
|
|
38
|
+
`${budgetNote(session) !== void 0 ? "reported " : ""}${formatTokens(input)} in (${formatTokens(u.cacheReadTokens)} cached) / ${formatTokens(u.outputTokens)} out`,
|
|
39
|
+
formatUsd(runCostUsd, runCostIncomplete),
|
|
40
|
+
`context ${percent}% of ${formatTokens(contextWindow)}`,
|
|
41
|
+
`session ${formatTokens(totalTokens(session.usage))} tokens, ${formatUsd(session.costUsd, isCostIncomplete(session))}`,
|
|
42
|
+
...budgetNote(session) === void 0 ? [] : [budgetNote(session)]
|
|
43
|
+
].join(" \xB7 ");
|
|
44
|
+
}
|
|
45
|
+
function stopMessage(reason, limits) {
|
|
46
|
+
switch (reason) {
|
|
47
|
+
case "done":
|
|
48
|
+
return void 0;
|
|
49
|
+
case "max_steps":
|
|
50
|
+
return `Stopped: the run reached the step limit (${limits.maxSteps}). Use --resume to continue, or raise limits.maxSteps in .garuda/settings.json.`;
|
|
51
|
+
case "token_budget":
|
|
52
|
+
return `Stopped: the session used its token budget (${formatTokens(limits.tokenBudget)} tokens). Raise limits.tokenBudget in .garuda/settings.json to continue.`;
|
|
53
|
+
case "repeated_calls":
|
|
54
|
+
return "Stopped: the agent made the same tool call 3 times in a row, so it may be stuck. Use --resume with a hint to continue.";
|
|
55
|
+
case "max_tokens":
|
|
56
|
+
return "Stopped: the response reached the output token limit.";
|
|
57
|
+
case "refusal":
|
|
58
|
+
return "Stopped: the model refused the request.";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function chatStopMessage(reason, limits) {
|
|
62
|
+
switch (reason) {
|
|
63
|
+
case "max_steps":
|
|
64
|
+
return `\u25A0 Stopped at the step limit (${limits.maxSteps}). The task is not finished.`;
|
|
65
|
+
case "repeated_calls":
|
|
66
|
+
return "\u25A0 Stopped: the agent made the same tool call 3 times in a row, so it may be stuck. Type a hint as your next message, and it goes on from there.";
|
|
67
|
+
default:
|
|
68
|
+
return stopMessage(reason, limits);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/cli/receiptView.ts
|
|
73
|
+
function screenReaderMode(env = process.env) {
|
|
74
|
+
return env.GARUDA_SCREEN_READER === "1";
|
|
75
|
+
}
|
|
76
|
+
var RECEIPT_BOX_MIN_COLUMNS = 60;
|
|
77
|
+
var RECEIPT_MAX_WIDTH = 76;
|
|
78
|
+
var seconds = (ms) => `${Math.max(0, Math.round(ms / 1e3))} s`;
|
|
79
|
+
var plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
80
|
+
function cells(text) {
|
|
81
|
+
let n = 0;
|
|
82
|
+
for (const ch of text) {
|
|
83
|
+
const c = ch.codePointAt(0) ?? 0;
|
|
84
|
+
const wide = c >= 4352 && c <= 4447 || c >= 11904 && c <= 42191 || c >= 44032 && c <= 55203 || c >= 63744 && c <= 64255 || c >= 65072 && c <= 65103 || c >= 65280 && c <= 65376 || c >= 65504 && c <= 65510 || c >= 127744 && c <= 129791 || c >= 131072 && c <= 262141;
|
|
85
|
+
n += wide ? 2 : 1;
|
|
86
|
+
}
|
|
87
|
+
return n;
|
|
88
|
+
}
|
|
89
|
+
function cut(text, width) {
|
|
90
|
+
if (cells(text) <= width) return text;
|
|
91
|
+
let out = "";
|
|
92
|
+
for (const ch of text) {
|
|
93
|
+
if (cells(out + ch) > width - 1) break;
|
|
94
|
+
out += ch;
|
|
95
|
+
}
|
|
96
|
+
return `${out}\u2026`;
|
|
97
|
+
}
|
|
98
|
+
function filesParts(receipt, room) {
|
|
99
|
+
const files = receipt.files;
|
|
100
|
+
if (files === void 0) return [["undo is off: no file list", "dim"]];
|
|
101
|
+
if (files.length === 0) return [["no file changed", "dim"]];
|
|
102
|
+
const shown = [];
|
|
103
|
+
for (const [i, f] of files.entries()) {
|
|
104
|
+
const lines = f.added === void 0 ? " (binary)" : ` +${f.added} \u2212${f.removed ?? 0}`;
|
|
105
|
+
const item = `${f.path}${lines}`;
|
|
106
|
+
const rest = files.length - i - 1;
|
|
107
|
+
const tail = rest > 0 ? ` \xB7 ${rest} more` : "";
|
|
108
|
+
const next = [...shown, item].join(" \xB7 ");
|
|
109
|
+
if (shown.length > 0 && cells(next + tail) > room) {
|
|
110
|
+
shown.push(`${files.length - i} more`);
|
|
111
|
+
return [[shown.join(" \xB7 ")]];
|
|
112
|
+
}
|
|
113
|
+
shown.push(item);
|
|
114
|
+
}
|
|
115
|
+
return [[shown.join(" \xB7 ")]];
|
|
116
|
+
}
|
|
117
|
+
function testsParts(receipt) {
|
|
118
|
+
const t = receipt.tests;
|
|
119
|
+
if (t === void 0) return void 0;
|
|
120
|
+
const count = t.total === void 0 ? "" : t.passed ? ` (${plural(t.total, "test")}${t.previousTotal !== void 0 && t.previousTotal !== t.total ? `, was ${t.previousTotal}` : ""})` : (t.failed ?? 0) > 0 ? ` (${t.failed} of ${t.total})` : ` (${plural(t.total, "test")})`;
|
|
121
|
+
return t.passed ? [[`${t.command} `], ["\u2713 passed", "green"], [count]] : [[`${t.command} `], [t.timedOut === true ? "\u2717 timed out" : "\u2717 failed", "red"], [count]];
|
|
122
|
+
}
|
|
123
|
+
function sandboxParts(receipt) {
|
|
124
|
+
const c = receipt.commands;
|
|
125
|
+
if (c.sandboxed + c.outside === 0) return void 0;
|
|
126
|
+
const parts = [
|
|
127
|
+
[`\u26E8 ${plural(c.sandboxed, "command")} in the sandbox \xB7 `],
|
|
128
|
+
c.outside > 0 ? [`${c.outside} outside`, "yellow"] : [`${c.outside} outside`]
|
|
129
|
+
];
|
|
130
|
+
return parts;
|
|
131
|
+
}
|
|
132
|
+
function rows(receipt, room) {
|
|
133
|
+
const out = [{ label: "Files", parts: filesParts(receipt, room) }];
|
|
134
|
+
const tests = testsParts(receipt);
|
|
135
|
+
if (tests !== void 0) out.push({ label: "Tests", parts: tests });
|
|
136
|
+
const sandbox = sandboxParts(receipt);
|
|
137
|
+
if (sandbox !== void 0) out.push({ label: "Sandbox", parts: sandbox });
|
|
138
|
+
const hosts = receipt.commands.blockedHosts;
|
|
139
|
+
if (hosts.length > 0) {
|
|
140
|
+
out.push({ label: "Network", parts: [[`blocked ${hosts.join(", ")}`, "yellow"]] });
|
|
141
|
+
}
|
|
142
|
+
const changed = receipt.files?.length ?? 0;
|
|
143
|
+
if (changed > 0)
|
|
144
|
+
out.push({ label: "Undo", parts: [[`/undo puts back ${plural(changed, "file")}`]] });
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
function headText(receipt) {
|
|
148
|
+
return `Turn ${receipt.turn} \xB7 ${receipt.stopReason} \xB7 ${plural(receipt.steps, "step")} \xB7 ${seconds(receipt.durationMs)}`;
|
|
149
|
+
}
|
|
150
|
+
function footText(receipt) {
|
|
151
|
+
const parts = [`context ${receipt.contextPercent}%`];
|
|
152
|
+
if (receipt.sessionCostUsd !== void 0) {
|
|
153
|
+
parts.push(
|
|
154
|
+
`session ${formatUsd(receipt.sessionCostUsd, receipt.sessionCostIncomplete === true)}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
const kept = receipt.keptChanges ?? 0;
|
|
158
|
+
if (kept > 0) {
|
|
159
|
+
parts.push(plural(kept, "kept change"));
|
|
160
|
+
if (receipt.sessionCostUsd !== void 0) {
|
|
161
|
+
const each = formatUsd(receipt.sessionCostUsd / kept, receipt.sessionCostIncomplete === true);
|
|
162
|
+
parts.push(`${each} each`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return parts.join(" \xB7 ");
|
|
166
|
+
}
|
|
167
|
+
function receiptText(receipt, options = {}) {
|
|
168
|
+
const paint = options.paint ?? ((_style, text) => text);
|
|
169
|
+
const draw = (parts) => parts.map(([text, style]) => style === void 0 ? text : paint(style, text)).join("");
|
|
170
|
+
const cost = formatUsd(receipt.costUsd, receipt.costIncomplete === true);
|
|
171
|
+
const columns = options.columns;
|
|
172
|
+
if (options.screenReader === true || columns === void 0 || columns < RECEIPT_BOX_MIN_COLUMNS) {
|
|
173
|
+
const lines = [`${headText(receipt)} \xB7 ${cost}`];
|
|
174
|
+
for (const row of rows(receipt, 200)) lines.push(`${row.label}: ${draw(row.parts)}`);
|
|
175
|
+
lines.push(footText(receipt));
|
|
176
|
+
return lines.join("\n");
|
|
177
|
+
}
|
|
178
|
+
const width = Math.min(columns, RECEIPT_MAX_WIDTH);
|
|
179
|
+
const label = 9;
|
|
180
|
+
const room = width - 4 - label;
|
|
181
|
+
const border = (text) => paint("yellow", text);
|
|
182
|
+
const edge = (left, text, right, end) => {
|
|
183
|
+
const tail = right === "" ? "" : ` ${right} \u2500`;
|
|
184
|
+
const head = cut(text, width - 4 - cells(tail) - cells(end) - 1);
|
|
185
|
+
const fill = width - 3 - cells(head) - 1 - cells(tail) - cells(end);
|
|
186
|
+
return `${border(`${left}\u2500 `)}${head} ${border("\u2500".repeat(Math.max(1, fill)))}${right === "" ? "" : ` ${right} ${border("\u2500")}`}${border(end)}`;
|
|
187
|
+
};
|
|
188
|
+
const out = [edge("\u256D", headText(receipt), cost, "\u256E")];
|
|
189
|
+
for (const row of rows(receipt, room)) {
|
|
190
|
+
const plain = row.parts.map(([text]) => text).join("");
|
|
191
|
+
let parts = row.parts;
|
|
192
|
+
if (cells(plain) > room) parts = [[cut(plain, room)]];
|
|
193
|
+
const shown = parts.map(([text]) => text).join("");
|
|
194
|
+
const pad = " ".repeat(Math.max(0, room - cells(shown)));
|
|
195
|
+
out.push(
|
|
196
|
+
`${border("\u2502")} ${paint("dim", row.label.padEnd(label))}${draw(parts)}${pad} ${border("\u2502")}`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
out.push(edge("\u2570", footText(receipt), "", "\u256F"));
|
|
200
|
+
return out.join("\n");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export {
|
|
204
|
+
formatTokens,
|
|
205
|
+
formatUsd,
|
|
206
|
+
budgetNote,
|
|
207
|
+
usageLine,
|
|
208
|
+
stopMessage,
|
|
209
|
+
chatStopMessage,
|
|
210
|
+
screenReaderMode,
|
|
211
|
+
RECEIPT_BOX_MIN_COLUMNS,
|
|
212
|
+
RECEIPT_MAX_WIDTH,
|
|
213
|
+
cells,
|
|
214
|
+
receiptText
|
|
215
|
+
};
|
|
216
|
+
//# sourceMappingURL=chunk-2OYTNNUJ.js.map
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
summariseCall,
|
|
4
|
+
summariseResult
|
|
5
|
+
} from "./chunk-AX3A5KPH.js";
|
|
6
|
+
import {
|
|
7
|
+
receiptText
|
|
8
|
+
} from "./chunk-2OYTNNUJ.js";
|
|
9
|
+
import {
|
|
10
|
+
hasReceipt
|
|
11
|
+
} from "./chunk-4QRTTRVK.js";
|
|
12
|
+
import {
|
|
13
|
+
serverCallText,
|
|
14
|
+
serverResultSummary
|
|
15
|
+
} from "./chunk-A2QAIRQL.js";
|
|
16
|
+
|
|
17
|
+
// src/cli/exportHtml.ts
|
|
18
|
+
function escapeHtml(text) {
|
|
19
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
20
|
+
}
|
|
21
|
+
var OUTPUT_CHARS = 4e3;
|
|
22
|
+
var cutOutput = (text) => text.length <= OUTPUT_CHARS ? text : `${text.slice(0, OUTPUT_CHARS)}
|
|
23
|
+
\u2026 ${text.length - OUTPUT_CHARS} more characters in the session file.`;
|
|
24
|
+
function callChange(call) {
|
|
25
|
+
const input = call.input ?? {};
|
|
26
|
+
const lines = (text, sign) => typeof text === "string" ? text.split("\n").map((l) => `${sign}${l}`) : [];
|
|
27
|
+
if (call.name === "edit_file") {
|
|
28
|
+
return [...lines(input.old_string, "-"), ...lines(input.new_string, "+")].join("\n");
|
|
29
|
+
}
|
|
30
|
+
if (call.name === "write_file") return lines(input.content, "+").join("\n");
|
|
31
|
+
return void 0;
|
|
32
|
+
}
|
|
33
|
+
function diffHtml(text) {
|
|
34
|
+
return text.split("\n").map((line) => {
|
|
35
|
+
const cls = line.startsWith("+") ? "add" : line.startsWith("-") ? "del" : "ctx";
|
|
36
|
+
return `<span class="${cls}">${escapeHtml(line)}</span>`;
|
|
37
|
+
}).join("\n");
|
|
38
|
+
}
|
|
39
|
+
var STYLE = `
|
|
40
|
+
:root { --bg: #fbf7ef; --fg: #222; --dim: #6b6b6b; --line: #e4dccb; --accent: #d97706;
|
|
41
|
+
--add: #e6f4e6; --addfg: #17602a; --del: #fbe7e7; --delfg: #8f1d1d; --you: #0e7490; }
|
|
42
|
+
@media (prefers-color-scheme: dark) { :root { --bg: #1d1d1b; --fg: #e8e4dc; --dim: #a19c92;
|
|
43
|
+
--line: #3a3833; --add: #183522; --addfg: #9ee2ad; --del: #3b1d1d; --delfg: #f4a9a9; } }
|
|
44
|
+
body { background: var(--bg); color: var(--fg); font: 15px/1.55 system-ui, sans-serif;
|
|
45
|
+
max-width: 920px; margin: 32px auto; padding: 0 16px; }
|
|
46
|
+
h1 { font-size: 20px; } .meta { color: var(--dim); font-size: 13px; }
|
|
47
|
+
.you { border-left: 4px solid var(--you); padding: 4px 12px; margin: 24px 0 8px; font-weight: 600;
|
|
48
|
+
white-space: pre-wrap; }
|
|
49
|
+
.answer { white-space: pre-wrap; margin: 8px 0; }
|
|
50
|
+
.answer::before { content: "\u2726 "; color: var(--accent); }
|
|
51
|
+
details { border: 1px solid var(--line); border-radius: 6px; margin: 6px 0; padding: 4px 10px; }
|
|
52
|
+
summary { cursor: pointer; font-family: ui-monospace, monospace; font-size: 13px; }
|
|
53
|
+
pre { font: 12.5px/1.45 ui-monospace, monospace; white-space: pre-wrap; overflow-wrap: anywhere;
|
|
54
|
+
margin: 6px 0; }
|
|
55
|
+
.add { background: var(--add); color: var(--addfg); display: inline-block; width: 100%; }
|
|
56
|
+
.del { background: var(--del); color: var(--delfg); display: inline-block; width: 100%; }
|
|
57
|
+
.receipt { border: 1px solid var(--accent); border-radius: 6px; padding: 6px 10px; }
|
|
58
|
+
.note { color: var(--dim); font-style: italic; }
|
|
59
|
+
.failed summary { color: var(--delfg); }
|
|
60
|
+
`;
|
|
61
|
+
function sessionHtml(records, sessionId) {
|
|
62
|
+
const body = [];
|
|
63
|
+
const start = records.find((r) => r.type === "start");
|
|
64
|
+
body.push(`<h1>Garuda session ${escapeHtml(sessionId)}</h1>`);
|
|
65
|
+
if (start?.type === "start") {
|
|
66
|
+
body.push(
|
|
67
|
+
`<p class="meta">${escapeHtml(start.root)} \xB7 ${escapeHtml(start.model)} \xB7 ${escapeHtml(start.t)}</p>`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
const calls = /* @__PURE__ */ new Map();
|
|
71
|
+
for (const record of records) {
|
|
72
|
+
switch (record.type) {
|
|
73
|
+
case "user": {
|
|
74
|
+
const prompt = record.message.content[0];
|
|
75
|
+
body.push(
|
|
76
|
+
`<div class="you">${escapeHtml(prompt?.type === "text" ? prompt.text : "")}</div>`
|
|
77
|
+
);
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case "assistant":
|
|
81
|
+
for (const block of record.response.content) {
|
|
82
|
+
if (block.type === "text" && block.text.trim() !== "") {
|
|
83
|
+
body.push(`<div class="answer">${escapeHtml(block.text.trim())}</div>`);
|
|
84
|
+
}
|
|
85
|
+
if (block.type === "tool_use") calls.set(block.id, block);
|
|
86
|
+
if (block.type === "server_tool_result") {
|
|
87
|
+
const call = record.response.content.find(
|
|
88
|
+
(b) => b.type === "server_tool_use" && b.id === block.toolUseId
|
|
89
|
+
);
|
|
90
|
+
const query = call?.type === "server_tool_use" ? ` ${serverCallText(call)}` : "";
|
|
91
|
+
const pages = block.results.map((r) => `${r.title}
|
|
92
|
+
${r.url}`).join("\n");
|
|
93
|
+
body.push(
|
|
94
|
+
`<details><summary>${escapeHtml(`\u25CF ${block.name} (Claude)${query} \u2192 ${serverResultSummary(block)}`)}</summary><pre>${escapeHtml(pages)}</pre></details>`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
case "tool_results":
|
|
100
|
+
for (const block of record.message.content) {
|
|
101
|
+
if (block.type !== "tool_result") continue;
|
|
102
|
+
const call = calls.get(block.toolUseId);
|
|
103
|
+
if (call === void 0) continue;
|
|
104
|
+
const summary = summariseResult(call, { content: block.content, isError: block.isError });
|
|
105
|
+
const head = `\u25CF ${[call.name, summariseCall(call)].filter((p) => p !== "").join(" ")} \u2192 ${block.isError ? `failed: ${summary}` : summary}`;
|
|
106
|
+
const change = callChange(call);
|
|
107
|
+
const inner = change === void 0 ? `<pre>${escapeHtml(cutOutput(block.content))}</pre>` : `<pre>${diffHtml(cutOutput(change))}</pre>`;
|
|
108
|
+
body.push(
|
|
109
|
+
`<details${block.isError ? ' class="failed"' : ""}><summary>${escapeHtml(head)}</summary>${inner}</details>`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
case "receipt":
|
|
114
|
+
if (hasReceipt(record.receipt)) {
|
|
115
|
+
body.push(`<pre class="receipt">${escapeHtml(receiptText(record.receipt))}</pre>`);
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
case "undo":
|
|
119
|
+
body.push('<p class="note">/undo: the last turn was taken back.</p>');
|
|
120
|
+
break;
|
|
121
|
+
case "redo":
|
|
122
|
+
body.push('<p class="note">/redo: the turn came back.</p>');
|
|
123
|
+
break;
|
|
124
|
+
case "compaction":
|
|
125
|
+
body.push('<p class="note">The conversation was compacted here.</p>');
|
|
126
|
+
break;
|
|
127
|
+
case "fork":
|
|
128
|
+
body.push(
|
|
129
|
+
`<p class="note">This session starts as a fork of session ${escapeHtml(record.from.sessionId)}, at turn ${record.from.turn}.</p>`
|
|
130
|
+
);
|
|
131
|
+
break;
|
|
132
|
+
default:
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return [
|
|
137
|
+
"<!doctype html>",
|
|
138
|
+
'<html lang="en"><head><meta charset="utf-8">',
|
|
139
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
140
|
+
// No script may run, and nothing loads from outside: the page's own policy says so too.
|
|
141
|
+
`<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'">`,
|
|
142
|
+
`<title>Garuda session ${escapeHtml(sessionId)}</title>`,
|
|
143
|
+
`<style>${STYLE}</style></head><body>`,
|
|
144
|
+
...body,
|
|
145
|
+
"</body></html>",
|
|
146
|
+
""
|
|
147
|
+
].join("\n");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export {
|
|
151
|
+
escapeHtml,
|
|
152
|
+
OUTPUT_CHARS,
|
|
153
|
+
callChange,
|
|
154
|
+
sessionHtml
|
|
155
|
+
};
|
|
156
|
+
//# sourceMappingURL=chunk-4BELTWP4.js.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/app/receipt.ts
|
|
4
|
+
function hasReceipt(receipt) {
|
|
5
|
+
return (receipt.files?.length ?? 0) > 0 || receipt.tests !== void 0 || receipt.commands.outside > 0 || receipt.commands.blockedHosts.length > 0;
|
|
6
|
+
}
|
|
7
|
+
var RUNNERS = /(^|[\s;&|(/])(vitest|jest|pytest|py\.test|mvn|mvnw|gradle|gradlew|go\s+test|node\s+--test|(npm|pnpm|yarn|bun)\s+(run\s+)?test|cargo\s+test|tox|nox)\b/;
|
|
8
|
+
function isTestCommand(command, projectTest) {
|
|
9
|
+
const text = command.trim();
|
|
10
|
+
if (projectTest !== void 0 && projectTest !== "" && text.includes(projectTest)) return true;
|
|
11
|
+
if (/(^|[\s;&|])(mvn|mvnw|gradle|gradlew)\b/.test(text) && !/\b(test|verify|check)\b/.test(text)) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
return RUNNERS.test(text);
|
|
15
|
+
}
|
|
16
|
+
function parseTestCount(output) {
|
|
17
|
+
const num = (s) => s === void 0 ? 0 : Number(s);
|
|
18
|
+
const vitest = /^\s*Tests\s+(.*\b(?:passed|failed|skipped)\b.*?)\s+\((\d+)\)\s*$/m.exec(output);
|
|
19
|
+
if (vitest !== null) {
|
|
20
|
+
return { total: num(vitest[2]), failed: num(/(\d+) failed/.exec(vitest[1] ?? "")?.[1]) };
|
|
21
|
+
}
|
|
22
|
+
const jest = /^Tests:\s+(?:(\d+) failed, )?.*?(\d+) total\s*$/m.exec(output);
|
|
23
|
+
if (jest !== null) return { total: num(jest[2]), failed: num(jest[1]) };
|
|
24
|
+
const nodeTotal = /^(?:#|ℹ) tests (\d+)\s*$/m.exec(output);
|
|
25
|
+
if (nodeTotal !== null) {
|
|
26
|
+
return { total: num(nodeTotal[1]), failed: num(/^(?:#|ℹ) fail (\d+)\s*$/m.exec(output)?.[1]) };
|
|
27
|
+
}
|
|
28
|
+
const pytest = /^=+ (.*\b(?:passed|failed)\b.*) in [\d.]+s(?: \([^)]*\))? =+\s*$/m.exec(output);
|
|
29
|
+
if (pytest !== null) {
|
|
30
|
+
const part = (word) => num(new RegExp(`(\\d+) ${word}\\b`).exec(pytest[1] ?? "")?.[1]);
|
|
31
|
+
const failed = part("failed") + part("errors?");
|
|
32
|
+
return { total: part("passed") + failed, failed };
|
|
33
|
+
}
|
|
34
|
+
const maven = [...output.matchAll(/Tests run: (\d+), Failures: (\d+), Errors: (\d+)/g)].at(-1);
|
|
35
|
+
if (maven !== void 0) {
|
|
36
|
+
return { total: num(maven[1]), failed: num(maven[2]) + num(maven[3]) };
|
|
37
|
+
}
|
|
38
|
+
const passes = output.match(/^\s*--- PASS: /gm)?.length ?? 0;
|
|
39
|
+
const fails = output.match(/^\s*--- FAIL: /gm)?.length ?? 0;
|
|
40
|
+
if (passes + fails > 0) return { total: passes + fails, failed: fails };
|
|
41
|
+
return void 0;
|
|
42
|
+
}
|
|
43
|
+
var ReceiptCollector = class {
|
|
44
|
+
constructor(projectTest) {
|
|
45
|
+
this.projectTest = projectTest;
|
|
46
|
+
}
|
|
47
|
+
projectTest;
|
|
48
|
+
calls = /* @__PURE__ */ new Map();
|
|
49
|
+
sandboxed = 0;
|
|
50
|
+
outside = 0;
|
|
51
|
+
blockedHosts = /* @__PURE__ */ new Set();
|
|
52
|
+
tests;
|
|
53
|
+
onEvent(event) {
|
|
54
|
+
if (event.type === "tool_call") {
|
|
55
|
+
const command = event.call.input.command;
|
|
56
|
+
if (typeof command === "string") this.calls.set(event.call.id, command);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (event.type !== "tool_result" || event.outcome.command === void 0) return;
|
|
60
|
+
this.add(this.calls.get(event.call.id) ?? "", event.outcome.command, event.outcome.content);
|
|
61
|
+
}
|
|
62
|
+
/** One command's facts; `output` is the text that the model got (for the test count). */
|
|
63
|
+
add(command, facts, output) {
|
|
64
|
+
if (facts.sandboxed) this.sandboxed++;
|
|
65
|
+
else this.outside++;
|
|
66
|
+
for (const host of facts.blockedHosts) this.blockedHosts.add(host);
|
|
67
|
+
if (!isTestCommand(command, this.projectTest)) return;
|
|
68
|
+
const count = parseTestCount(output);
|
|
69
|
+
const passed = facts.exitCode === 0 && !facts.timedOut;
|
|
70
|
+
this.tests = {
|
|
71
|
+
command: shortCommand(command),
|
|
72
|
+
passed,
|
|
73
|
+
...facts.timedOut ? { timedOut: true } : {},
|
|
74
|
+
...count === void 0 ? {} : { total: count.total, failed: count.failed }
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
function shortCommand(command) {
|
|
79
|
+
const line = command.trim().split("\n")[0] ?? "";
|
|
80
|
+
return line.length > 40 ? `${line.slice(0, 39)}\u2026` : line;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
hasReceipt,
|
|
85
|
+
parseTestCount,
|
|
86
|
+
ReceiptCollector
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=chunk-4QRTTRVK.js.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
garudaCommit
|
|
4
|
+
} from "./chunk-YAQ6QLKB.js";
|
|
5
|
+
import {
|
|
6
|
+
BUILD_COMMIT
|
|
7
|
+
} from "./chunk-E6YRCDSF.js";
|
|
8
|
+
import {
|
|
9
|
+
byTask,
|
|
10
|
+
consistency,
|
|
11
|
+
costPerPass,
|
|
12
|
+
failureSummary,
|
|
13
|
+
passInterval,
|
|
14
|
+
spread,
|
|
15
|
+
stepsSd,
|
|
16
|
+
unexpectedChanges
|
|
17
|
+
} from "./chunk-A5DLAXB4.js";
|
|
18
|
+
import {
|
|
19
|
+
VERSION
|
|
20
|
+
} from "./chunk-CFDRP6XW.js";
|
|
21
|
+
|
|
22
|
+
// src/evals/report.ts
|
|
23
|
+
import { createHash } from "crypto";
|
|
24
|
+
import { arch, platform, release } from "os";
|
|
25
|
+
var REPORT_SCHEMA = 2;
|
|
26
|
+
function taskHash(task) {
|
|
27
|
+
const sorted = (record) => Object.fromEntries(Object.entries(record).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
|
|
28
|
+
const data = {
|
|
29
|
+
prompt: task.prompt,
|
|
30
|
+
files: sorted(task.files),
|
|
31
|
+
check: task.check,
|
|
32
|
+
protect: task.protect ?? null,
|
|
33
|
+
solution: sorted(task.solution),
|
|
34
|
+
allowChanges: task.allowChanges ?? null,
|
|
35
|
+
repo: task.repo === void 0 ? null : { base: task.repo.base, sha: task.repo.sha ?? null, tests: task.repo.tests }
|
|
36
|
+
};
|
|
37
|
+
return createHash("sha256").update(JSON.stringify(data)).digest("hex");
|
|
38
|
+
}
|
|
39
|
+
function buildFields(build, checkout) {
|
|
40
|
+
const commit = build ?? checkout;
|
|
41
|
+
return {
|
|
42
|
+
version: VERSION,
|
|
43
|
+
...commit === void 0 ? {} : { commit },
|
|
44
|
+
...build !== void 0 && checkout !== void 0 && checkout !== build ? { checkout } : {}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
async function reportV2(meta, tasks, results) {
|
|
48
|
+
return {
|
|
49
|
+
schema: REPORT_SCHEMA,
|
|
50
|
+
garuda: buildFields(BUILD_COMMIT, await garudaCommit()),
|
|
51
|
+
spec: meta.model,
|
|
52
|
+
agent: meta.agent ?? { name: "garuda" },
|
|
53
|
+
provider: meta.provider,
|
|
54
|
+
// Evals set no effort: each model's default applies.
|
|
55
|
+
effort: "model default",
|
|
56
|
+
isolation: meta.isolation,
|
|
57
|
+
os: { platform: platform(), release: release(), arch: arch(), node: process.versions.node },
|
|
58
|
+
seed: meta.seed,
|
|
59
|
+
shuffle: meta.shuffle,
|
|
60
|
+
split: meta.split ?? "dev",
|
|
61
|
+
addendum: meta.addendum === void 0 ? null : {
|
|
62
|
+
file: meta.addendum.file,
|
|
63
|
+
chars: meta.addendum.text.length,
|
|
64
|
+
sha256: createHash("sha256").update(meta.addendum.text).digest("hex")
|
|
65
|
+
},
|
|
66
|
+
mcp: meta.mcp === void 0 ? null : {
|
|
67
|
+
server: meta.mcp.server,
|
|
68
|
+
...meta.mcp.prepare === void 0 ? {} : { prepare: meta.mcp.prepare }
|
|
69
|
+
},
|
|
70
|
+
order: results.map((r) => ({ id: r.id, attempt: r.attempt ?? 1 })),
|
|
71
|
+
tasks: Object.fromEntries(tasks.map((t) => [t.id, taskHash(t)])),
|
|
72
|
+
evidence: {
|
|
73
|
+
interval: passInterval(results, { seed: meta.seed }) ?? null,
|
|
74
|
+
consistency: consistency(results),
|
|
75
|
+
spread: spread(results) ?? null,
|
|
76
|
+
stepsSd: Object.fromEntries(byTask(results).map((t) => [t.id, stepsSd(t.results) ?? null])),
|
|
77
|
+
costPerAcceptedChange: costPerPass(results),
|
|
78
|
+
unexpected: unexpectedChanges(results),
|
|
79
|
+
failures: failureSummary(results)
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export {
|
|
85
|
+
taskHash,
|
|
86
|
+
reportV2
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=chunk-6JQTPW3P.js.map
|