@garuda-agent/garuda 0.17.0 → 0.18.1
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 +243 -0
- package/README.md +3 -2
- package/SECURITY.md +1 -1
- package/dist/{acpCommand-7EY3NHPG.js → acpCommand-JAYNMIMP.js} +19 -16
- package/dist/advise-HDKJGNBL.js +195 -0
- package/dist/brief-RWKESOPT.js +91 -0
- package/dist/buildCheck-HB46NWLX.js +19 -0
- package/dist/buildInfo-K73FFI5S.js +8 -0
- package/dist/chunk-2OYTNNUJ.js +216 -0
- package/dist/{chunk-Z66BWTMD.js → chunk-34WBDQBL.js} +135 -78
- package/dist/chunk-4QRTTRVK.js +88 -0
- package/dist/{chunk-2DYMJMRQ.js → chunk-5UDYQ5MK.js} +405 -70
- package/dist/{chunk-J4346IDH.js → chunk-7KBOIPUT.js} +2 -1
- package/dist/chunk-CWVXOSAA.js +894 -0
- package/dist/{chunk-FARS4R67.js → chunk-DQSCCE77.js} +18 -1
- package/dist/chunk-DUYLVGPY.js +821 -0
- package/dist/chunk-GKU62MHQ.js +5258 -0
- package/dist/chunk-H7LIBGZT.js +9 -0
- package/dist/chunk-IKPGHM54.js +334 -0
- package/dist/chunk-IYGJWX65.js +9 -0
- package/dist/{chunk-U65DQZDB.js → chunk-JMH5W5DG.js} +2 -45
- package/dist/chunk-L55VUSX3.js +54 -0
- package/dist/{chunk-B4SJY7KT.js → chunk-LMKGSYJC.js} +77 -8
- package/dist/{chunk-23FXBNBB.js → chunk-MNE7HHX2.js} +2 -1
- package/dist/chunk-NH72AELP.js +48 -0
- package/dist/{chunk-735BKABA.js → chunk-NLS7GMF7.js} +12 -106
- package/dist/{chunk-ZAE25JM2.js → chunk-QANRZW7W.js} +8 -1
- package/dist/{chunk-JRVWH27M.js → chunk-QLBCM2YQ.js} +7 -3
- package/dist/{chunk-6XHON3Q6.js → chunk-QXTHEFKP.js} +3 -3
- package/dist/{chunk-TJIWD7R7.js → chunk-R7RCW4BQ.js} +10 -8
- package/dist/chunk-TLA2PDGM.js +156 -0
- 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-7TH3NHCQ.js → chunk-ZKT4P2PC.js} +2 -2
- package/dist/cli/index.js +289 -65
- package/dist/config-WV2H64TF.js +23 -0
- package/dist/{create-W6BV75QX.js → create-OACSGJ37.js} +5 -5
- package/dist/evalCommand-BLJJ4G76.js +696 -0
- package/dist/evalTune-W53KAD4V.js +302 -0
- package/dist/{export-SJ3DUP6W.js → export-E677HIQS.js} +27 -4
- package/dist/exportHtml-BMV73YRO.js +21 -0
- package/dist/{inkChat-FNUBK43U.js → inkChat-EVLYDC3K.js} +263 -47
- package/dist/{install-TKWN5AYH.js → install-4ST5BNRG.js} +3 -3
- package/dist/{jobCommand-FE6XN3YD.js → jobCommand-ILOTYOX3.js} +9 -8
- package/dist/{launchd-3A3ONEW5.js → launchd-QG6R5RAK.js} +4 -4
- package/dist/{lspCommand-ZZ6R4PA2.js → lspCommand-X4FTTONR.js} +3 -3
- package/dist/{manager-73MPYFTP.js → manager-L25IUXHW.js} +11 -11
- package/dist/{nightCommand-OODJNWDT.js → nightCommand-XPWT6CQZ.js} +9 -8
- package/dist/receiptView-YFDHV253.js +19 -0
- package/dist/{remove-42WQNVPX.js → remove-STX5UO7X.js} +3 -3
- package/dist/{run-ML6GSPN4.js → run-W4TK52WT.js} +6 -6
- package/dist/{server-VEYIJHEU.js → server-ASM73QVY.js} +3 -3
- package/dist/{setupCommand-GBQIVYJC.js → setupCommand-OSOMFHDQ.js} +10 -9
- package/dist/tour-CEVMAC5S.js +18 -0
- package/dist/tune-VX2WQTHS.js +52 -0
- package/dist/uiState-YFJQ43D7.js +16 -0
- package/package.json +1 -1
- package/dist/chunk-4JSUUDFQ.js +0 -118
- package/dist/chunk-BSK5T2ZG.js +0 -9
- package/dist/evalCommand-YVQAWUZE.js +0 -2833
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
taskHash
|
|
4
|
+
} from "./chunk-DUYLVGPY.js";
|
|
5
|
+
import {
|
|
6
|
+
compareReports,
|
|
7
|
+
costPerPass,
|
|
8
|
+
formatComparison
|
|
9
|
+
} from "./chunk-CWVXOSAA.js";
|
|
10
|
+
|
|
11
|
+
// src/evals/tune.ts
|
|
12
|
+
import { readFile } from "fs/promises";
|
|
13
|
+
var ADDENDUM_MAX_CHARS = 4e3;
|
|
14
|
+
var CHECK_TAIL_LINES = 40;
|
|
15
|
+
var REJECTED = [
|
|
16
|
+
/\b(skip|disable|ignore|bypass|weaken)\b[^.\n]{0,40}\b(tests?|checks?|assert\w*|sandbox|protect\w*)/i,
|
|
17
|
+
/\b(edit|change|modify|delete|remove|rewrite)\b[^.\n]{0,40}\b(the )?(tests?|test files?|checks?|protected files?)\b/i,
|
|
18
|
+
/\boutside_sandbox\b/i,
|
|
19
|
+
/\b(hard-?code|special-?case)\b[^.\n]{0,40}\b(expected|test|check)/i,
|
|
20
|
+
/\.check\//i
|
|
21
|
+
];
|
|
22
|
+
async function readAddendum(path, missingIsEmpty = false) {
|
|
23
|
+
let text;
|
|
24
|
+
try {
|
|
25
|
+
text = await readFile(path, "utf8");
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (missingIsEmpty && error.code === "ENOENT") return { text: "" };
|
|
28
|
+
return { problem: `--addendum ${path}: ${error.message}` };
|
|
29
|
+
}
|
|
30
|
+
if (text.length > ADDENDUM_MAX_CHARS) {
|
|
31
|
+
return {
|
|
32
|
+
problem: `--addendum ${path}: ${text.length} characters; the limit is ${ADDENDUM_MAX_CHARS}.`
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return { text };
|
|
36
|
+
}
|
|
37
|
+
function rejectReason(text) {
|
|
38
|
+
if (text.trim() === "") return "the addendum is empty";
|
|
39
|
+
if (text.length > ADDENDUM_MAX_CHARS) {
|
|
40
|
+
return `the addendum has ${text.length} characters; the limit is ${ADDENDUM_MAX_CHARS}`;
|
|
41
|
+
}
|
|
42
|
+
for (const clause of text.split(/[.;!?](?=\s|$)|\n/)) {
|
|
43
|
+
for (const pattern of REJECTED) {
|
|
44
|
+
const hit = pattern.exec(clause);
|
|
45
|
+
if (hit === null || NEGATION.test(clause.slice(0, hit.index + hit[0].length))) continue;
|
|
46
|
+
return `the addendum asks to get around the grading ("${hit[0]}")`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return void 0;
|
|
50
|
+
}
|
|
51
|
+
var NEGATION = /\b(never|not|don't|do not|no|without)\b/i;
|
|
52
|
+
function groupRuns(results) {
|
|
53
|
+
const out = /* @__PURE__ */ new Map();
|
|
54
|
+
for (const r of results) out.set(r.id, [...out.get(r.id) ?? [], r]);
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
var asReport = (path, results, tasks) => ({
|
|
58
|
+
path,
|
|
59
|
+
schema: 2,
|
|
60
|
+
model: void 0,
|
|
61
|
+
seed: void 0,
|
|
62
|
+
tasks: tasks === void 0 ? void 0 : Object.fromEntries(tasks.map((t) => [t.id, taskHash(t)])),
|
|
63
|
+
results: [...results]
|
|
64
|
+
});
|
|
65
|
+
function decide(current, candidate, options) {
|
|
66
|
+
const passesCurrent = current.filter((r) => r.passed).length;
|
|
67
|
+
const passesCandidate = candidate.filter((r) => r.passed).length;
|
|
68
|
+
const runs = candidate.length;
|
|
69
|
+
const base = { passesCurrent, passesCandidate, runs };
|
|
70
|
+
const before = groupRuns(current);
|
|
71
|
+
const after = groupRuns(candidate);
|
|
72
|
+
const lost = [...before.entries()].filter(([, rs]) => rs.length > 0 && rs.every((r) => r.passed)).filter(([id]) => {
|
|
73
|
+
const now = after.get(id) ?? [];
|
|
74
|
+
return now.length > 0 && now.every((r) => !r.passed);
|
|
75
|
+
}).map(([id]) => id);
|
|
76
|
+
if (lost.length > 0) {
|
|
77
|
+
return {
|
|
78
|
+
...base,
|
|
79
|
+
kept: false,
|
|
80
|
+
reason: `dropped: ${lost.join(", ")} went from all runs passed to none`
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const compared = compareReports(asReport("current", current), asReport("candidate", candidate), {
|
|
84
|
+
seed: options.seed
|
|
85
|
+
});
|
|
86
|
+
const ratio = "problems" in compared || compared.comparison.costRatio === void 0 ? void 0 : compared.comparison.costRatio;
|
|
87
|
+
const costRatio = ratio === void 0 ? void 0 : { value: ratio.value, low: ratio.low, high: ratio.high };
|
|
88
|
+
const withCost = costRatio === void 0 ? base : { ...base, costRatio };
|
|
89
|
+
const gain = passesCandidate - passesCurrent;
|
|
90
|
+
if (gain >= options.margin) {
|
|
91
|
+
return {
|
|
92
|
+
...withCost,
|
|
93
|
+
kept: true,
|
|
94
|
+
reason: `kept: ${gain} more passing run(s) (margin ${options.margin})`
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (gain >= 0 && costRatio !== void 0 && costRatio.high < 1) {
|
|
98
|
+
return {
|
|
99
|
+
...withCost,
|
|
100
|
+
kept: true,
|
|
101
|
+
reason: `kept: as many passes or more, and cost per accepted change \xD7${costRatio.value.toFixed(2)} (95% interval \xD7${costRatio.low.toFixed(2)} to \xD7${costRatio.high.toFixed(2)})`
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const cost = costRatio === void 0 ? "cost ratio unknown" : `cost \xD7${costRatio.value.toFixed(2)} (\xD7${costRatio.low.toFixed(2)} to \xD7${costRatio.high.toFixed(2)})`;
|
|
105
|
+
return {
|
|
106
|
+
...withCost,
|
|
107
|
+
kept: false,
|
|
108
|
+
reason: `dropped: ${gain >= 0 ? "+" : ""}${gain} passing run(s) (margin ${options.margin}), ${cost}`
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function proposerInput(addendum, tasks, results, history) {
|
|
112
|
+
const prompts = new Map(tasks.map((t) => [t.id, t.prompt]));
|
|
113
|
+
const lines = [
|
|
114
|
+
"# The current addendum",
|
|
115
|
+
"",
|
|
116
|
+
addendum.trim() === "" ? "(empty)" : addendum.trim(),
|
|
117
|
+
""
|
|
118
|
+
];
|
|
119
|
+
const failed = results.filter((r) => !r.passed);
|
|
120
|
+
if (failed.length > 0) {
|
|
121
|
+
lines.push(`# Failed runs (${failed.length} of ${results.length})`, "");
|
|
122
|
+
for (const r of failed) {
|
|
123
|
+
const tail = (r.reason ?? "").split("\n").slice(-CHECK_TAIL_LINES).join("\n");
|
|
124
|
+
lines.push(
|
|
125
|
+
`## ${r.id} (run ${r.attempt ?? 1}): ${r.stopReason}, ${r.steps} steps`,
|
|
126
|
+
"",
|
|
127
|
+
"Task prompt:",
|
|
128
|
+
prompts.get(r.id) ?? "(unknown)",
|
|
129
|
+
"",
|
|
130
|
+
"Why it failed (last lines):",
|
|
131
|
+
"```",
|
|
132
|
+
tail,
|
|
133
|
+
"```",
|
|
134
|
+
...r.unexpected !== void 0 && r.unexpected.length > 0 ? ["", `Unexpected changes: ${r.unexpected.join(", ")}`] : [],
|
|
135
|
+
""
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
lines.push(`# Every run passed (${results.length}). Lower the cost: what each task took`, "");
|
|
140
|
+
for (const [id, rs] of groupRuns(results)) {
|
|
141
|
+
const mean = (f) => rs.reduce((s, r) => s + f(r), 0) / rs.length;
|
|
142
|
+
const costliest = [...rs].sort((a, b) => (b.costUsd ?? 0) - (a.costUsd ?? 0))[0];
|
|
143
|
+
lines.push(
|
|
144
|
+
`- ${id}: ${mean((r) => r.steps).toFixed(1)} steps, ${Math.round(mean((r) => r.tokens))} tokens, $${mean((r) => r.costUsd ?? 0).toFixed(4)} a run; tools of the costliest run: ${(costliest?.tools ?? []).join(", ") || "(none recorded)"}`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
lines.push("");
|
|
148
|
+
}
|
|
149
|
+
if (history.length > 0) {
|
|
150
|
+
lines.push("# Earlier proposals", "");
|
|
151
|
+
for (const h of history) {
|
|
152
|
+
lines.push(
|
|
153
|
+
`- round ${h.round} (${h.version}): ${h.rejected === void 0 ? h.decision?.reason ?? "" : `rejected before a run: ${h.rejected}`}. The proposer's reason: ${h.reason}`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
lines.push("");
|
|
157
|
+
}
|
|
158
|
+
return lines.join("\n");
|
|
159
|
+
}
|
|
160
|
+
var PROPOSER_SYSTEM = [
|
|
161
|
+
"You improve a coding agent. The agent gets a short text, the addendum, at the end of its system",
|
|
162
|
+
"prompt. You see the current addendum and the agent's results on development tasks. Write a",
|
|
163
|
+
"complete new addendum that should make the agent pass more tasks, or pass them at a lower cost.",
|
|
164
|
+
"",
|
|
165
|
+
"Rules:",
|
|
166
|
+
`- At most ${ADDENDUM_MAX_CHARS} characters. General advice about how to work, not about one task:`,
|
|
167
|
+
" the addendum is measured later on tasks you never see.",
|
|
168
|
+
"- Never tell the agent to skip, weaken or edit tests or checks, to touch protected files, or to",
|
|
169
|
+
" leave the sandbox. Such a text is refused without a run.",
|
|
170
|
+
"",
|
|
171
|
+
"Answer in exactly this form:",
|
|
172
|
+
"<addendum>",
|
|
173
|
+
"the complete new addendum",
|
|
174
|
+
"</addendum>",
|
|
175
|
+
"<reason>one paragraph: what you changed and why</reason>"
|
|
176
|
+
].join("\n");
|
|
177
|
+
function parseProposal(text) {
|
|
178
|
+
const addendum = /<addendum>\n?([\s\S]*?)\n?<\/addendum>/.exec(text)?.[1];
|
|
179
|
+
const reason = /<reason>([\s\S]*?)<\/reason>/.exec(text)?.[1];
|
|
180
|
+
if (addendum === void 0) return void 0;
|
|
181
|
+
return { addendum: addendum.trim(), reason: (reason ?? "").trim() };
|
|
182
|
+
}
|
|
183
|
+
var costOf = (results) => {
|
|
184
|
+
const c = costPerPass(results);
|
|
185
|
+
if (c.kind === "unknown") return void 0;
|
|
186
|
+
return results.reduce((s, r) => s + (r.costUsd ?? 0), 0);
|
|
187
|
+
};
|
|
188
|
+
async function tune(options) {
|
|
189
|
+
const history = [];
|
|
190
|
+
let spent = 0;
|
|
191
|
+
let current = options.start;
|
|
192
|
+
let version = 0;
|
|
193
|
+
await options.save("v0.md", current);
|
|
194
|
+
options.log(`Scoring v0 on ${options.devTasks.length} dev task(s) \xD7 ${options.repeat}.`);
|
|
195
|
+
let currentResults = await options.score(current, options.devTasks, options.repeat, "v0");
|
|
196
|
+
let lastScoreCost = costOf(currentResults);
|
|
197
|
+
if (lastScoreCost === void 0) {
|
|
198
|
+
return { final: current, history, spentUsd: spent, stopped: "unknown cost" };
|
|
199
|
+
}
|
|
200
|
+
spent += lastScoreCost;
|
|
201
|
+
let stopped = "rounds";
|
|
202
|
+
let lastProposalCost = 0;
|
|
203
|
+
for (let round = 1; round <= options.rounds; round++) {
|
|
204
|
+
if (spent + lastScoreCost + lastProposalCost > options.maxCostUsd) {
|
|
205
|
+
stopped = "cost";
|
|
206
|
+
options.log(
|
|
207
|
+
`Stopping before round ${round}: $${spent.toFixed(2)} spent, and the next round would pass --max-cost $${options.maxCostUsd}.`
|
|
208
|
+
);
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
const input = proposerInput(current, options.devTasks, currentResults, history);
|
|
212
|
+
const name = `v${round}`;
|
|
213
|
+
await options.save(`${name}.input.md`, input);
|
|
214
|
+
let proposal = await options.propose(input);
|
|
215
|
+
if (proposal.costUsd === void 0) {
|
|
216
|
+
stopped = "unknown cost";
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
let proposalCost = proposal.costUsd;
|
|
220
|
+
let rejected = rejectReason(proposal.addendum);
|
|
221
|
+
let refusedFirst;
|
|
222
|
+
if (rejected !== void 0) {
|
|
223
|
+
refusedFirst = rejected;
|
|
224
|
+
await options.save(`${name}.refused.md`, proposal.addendum);
|
|
225
|
+
options.log(`Round ${round}: ${name} refused (${rejected}); asking once more.`);
|
|
226
|
+
const again = await options.propose(
|
|
227
|
+
`${input}
|
|
228
|
+
# Your last answer was refused
|
|
229
|
+
|
|
230
|
+
${rejected}. Write the complete addendum again without that phrase. Do not tell the agent to skip, change or delete tests or checks, even as an example of what to avoid.
|
|
231
|
+
`
|
|
232
|
+
);
|
|
233
|
+
if (again.costUsd === void 0) {
|
|
234
|
+
stopped = "unknown cost";
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
proposalCost += again.costUsd;
|
|
238
|
+
proposal = again;
|
|
239
|
+
rejected = rejectReason(proposal.addendum);
|
|
240
|
+
}
|
|
241
|
+
spent += proposalCost;
|
|
242
|
+
lastProposalCost = proposalCost;
|
|
243
|
+
await options.save(`${name}.md`, proposal.addendum);
|
|
244
|
+
if (rejected !== void 0) {
|
|
245
|
+
history.push({
|
|
246
|
+
round,
|
|
247
|
+
version: name,
|
|
248
|
+
reason: proposal.reason,
|
|
249
|
+
rejected,
|
|
250
|
+
costUsd: proposalCost
|
|
251
|
+
});
|
|
252
|
+
options.log(`Round ${round}: ${name} rejected before a run: ${rejected}.`);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
options.log(`Round ${round}: scoring ${name}.`);
|
|
256
|
+
const candidate = await options.score(
|
|
257
|
+
proposal.addendum,
|
|
258
|
+
options.devTasks,
|
|
259
|
+
options.repeat,
|
|
260
|
+
name
|
|
261
|
+
);
|
|
262
|
+
const scoreCost = costOf(candidate);
|
|
263
|
+
if (scoreCost === void 0) {
|
|
264
|
+
stopped = "unknown cost";
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
spent += scoreCost;
|
|
268
|
+
lastScoreCost = scoreCost;
|
|
269
|
+
const decision = decide(currentResults, candidate, {
|
|
270
|
+
margin: options.margin,
|
|
271
|
+
seed: options.seed
|
|
272
|
+
});
|
|
273
|
+
history.push({
|
|
274
|
+
round,
|
|
275
|
+
version: name,
|
|
276
|
+
reason: proposal.reason,
|
|
277
|
+
decision,
|
|
278
|
+
costUsd: proposalCost + scoreCost,
|
|
279
|
+
...refusedFirst === void 0 ? {} : { refusedFirst }
|
|
280
|
+
});
|
|
281
|
+
options.log(`Round ${round}: ${name} ${decision.reason}.`);
|
|
282
|
+
if (decision.kept) {
|
|
283
|
+
current = proposal.addendum;
|
|
284
|
+
currentResults = candidate;
|
|
285
|
+
version = round;
|
|
286
|
+
}
|
|
287
|
+
await options.save("history.json", `${JSON.stringify(history, null, 2)}
|
|
288
|
+
`);
|
|
289
|
+
}
|
|
290
|
+
await options.save("history.json", `${JSON.stringify(history, null, 2)}
|
|
291
|
+
`);
|
|
292
|
+
await options.save("final.md", current);
|
|
293
|
+
let heldOut;
|
|
294
|
+
if (options.heldOutTasks.length > 0 && version > 0) {
|
|
295
|
+
options.log(
|
|
296
|
+
`Held-out: the empty addendum (A) against v${version} (B), \xD7 ${options.heldoutRepeat}.`
|
|
297
|
+
);
|
|
298
|
+
const a = await options.score("", options.heldOutTasks, options.heldoutRepeat, "heldout-empty");
|
|
299
|
+
const b = await options.score(
|
|
300
|
+
current,
|
|
301
|
+
options.heldOutTasks,
|
|
302
|
+
options.heldoutRepeat,
|
|
303
|
+
"heldout-final"
|
|
304
|
+
);
|
|
305
|
+
spent += (costOf(a) ?? 0) + (costOf(b) ?? 0);
|
|
306
|
+
const ra = asReport("held-out, empty addendum", a, options.heldOutTasks);
|
|
307
|
+
const rb = asReport(`held-out, v${version}`, b, options.heldOutTasks);
|
|
308
|
+
const compared = compareReports(ra, rb, { seed: options.seed });
|
|
309
|
+
heldOut = "problems" in compared ? compared.problems.join("\n") : formatComparison(ra, rb, compared.comparison);
|
|
310
|
+
await options.save("heldout.txt", `${heldOut}
|
|
311
|
+
`);
|
|
312
|
+
} else if (version === 0) {
|
|
313
|
+
options.log("No version was kept: no held-out measurement.");
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
final: current,
|
|
317
|
+
history,
|
|
318
|
+
spentUsd: spent,
|
|
319
|
+
stopped,
|
|
320
|
+
...heldOut === void 0 ? {} : { heldOut }
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export {
|
|
325
|
+
ADDENDUM_MAX_CHARS,
|
|
326
|
+
readAddendum,
|
|
327
|
+
rejectReason,
|
|
328
|
+
decide,
|
|
329
|
+
proposerInput,
|
|
330
|
+
PROPOSER_SYSTEM,
|
|
331
|
+
parseProposal,
|
|
332
|
+
tune
|
|
333
|
+
};
|
|
334
|
+
//# sourceMappingURL=chunk-IKPGHM54.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
knownModels
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-7KBOIPUT.js";
|
|
5
5
|
|
|
6
6
|
// src/model/catalog.ts
|
|
7
7
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
@@ -91,53 +91,10 @@ function prefixInfo(id) {
|
|
|
91
91
|
return best?.info;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
// src/tools/atomicWrite.ts
|
|
95
|
-
import { randomBytes } from "crypto";
|
|
96
|
-
import { chmod, link, realpath, rename, stat, unlink, writeFile as writeFile2 } from "fs/promises";
|
|
97
|
-
import { basename, dirname as dirname2, join as join2 } from "path";
|
|
98
|
-
async function writeFileAtomic(path, content, { createOnly }) {
|
|
99
|
-
const target = createOnly ? path : await realpath(path);
|
|
100
|
-
const temp = join2(
|
|
101
|
-
dirname2(target),
|
|
102
|
-
`.${basename(target)}.garuda-${randomBytes(4).toString("hex")}.tmp`
|
|
103
|
-
);
|
|
104
|
-
const mode = createOnly ? void 0 : (await stat(target)).mode & 4095;
|
|
105
|
-
await writeFile2(temp, content, { flag: "wx", ...mode === void 0 ? {} : { mode } });
|
|
106
|
-
try {
|
|
107
|
-
if (createOnly) {
|
|
108
|
-
try {
|
|
109
|
-
await link(temp, target);
|
|
110
|
-
} catch (error) {
|
|
111
|
-
const code = error.code;
|
|
112
|
-
if (code !== "EPERM" && code !== "ENOTSUP" && code !== "EXDEV") throw error;
|
|
113
|
-
if (await exists(target)) throw Object.assign(new Error("EEXIST"), { code: "EEXIST" });
|
|
114
|
-
await rename(temp, target);
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
} else {
|
|
118
|
-
await chmod(temp, mode ?? 420);
|
|
119
|
-
await rename(temp, target);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
} finally {
|
|
123
|
-
await unlink(temp).catch(() => {
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
async function exists(path) {
|
|
128
|
-
try {
|
|
129
|
-
await stat(path);
|
|
130
|
-
return true;
|
|
131
|
-
} catch {
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
94
|
export {
|
|
137
|
-
writeFileAtomic,
|
|
138
95
|
fetchCatalog,
|
|
139
96
|
saveCatalog,
|
|
140
97
|
loadCatalog,
|
|
141
98
|
mergedModels
|
|
142
99
|
};
|
|
143
|
-
//# sourceMappingURL=chunk-
|
|
100
|
+
//# sourceMappingURL=chunk-JMH5W5DG.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/uiState.ts
|
|
4
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
var KEY_HINT_CHATS = 5;
|
|
7
|
+
async function countChat(home) {
|
|
8
|
+
const dir = join(home, ".garuda");
|
|
9
|
+
const file = join(dir, "ui.json");
|
|
10
|
+
let chats = 0;
|
|
11
|
+
let data = {};
|
|
12
|
+
try {
|
|
13
|
+
const read = JSON.parse(await readFile(file, "utf8"));
|
|
14
|
+
if (typeof read === "object" && read !== null && !Array.isArray(read)) {
|
|
15
|
+
data = read;
|
|
16
|
+
}
|
|
17
|
+
if (typeof data.chats === "number" && Number.isInteger(data.chats) && data.chats >= 0) {
|
|
18
|
+
chats = data.chats;
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
chats++;
|
|
23
|
+
try {
|
|
24
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
25
|
+
await writeFile(file, `${JSON.stringify({ ...data, chats })}
|
|
26
|
+
`, { mode: 384 });
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
return chats;
|
|
30
|
+
}
|
|
31
|
+
var KEYS_HELP = [
|
|
32
|
+
"Keys:",
|
|
33
|
+
" Enter sends \xB7 \\ then Enter, or Alt+Enter: a new line \xB7 \u2191\u2193 earlier inputs",
|
|
34
|
+
" Tab completes /commands and @paths \xB7 Shift+Tab plan mode \xB7 Ctrl-P commands",
|
|
35
|
+
" Ctrl-G edits the prompt in your editor \xB7 Ctrl-O shows the last output in full",
|
|
36
|
+
" Esc or Ctrl-C stops a turn \xB7 Ctrl-C clears the line \xB7 Ctrl-D exits",
|
|
37
|
+
" !command runs a command yourself \xB7 @path attaches a file"
|
|
38
|
+
].join("\n");
|
|
39
|
+
function contextBar(percent) {
|
|
40
|
+
const full = percent <= 0 ? 0 : Math.max(1, Math.min(10, Math.round(percent / 10)));
|
|
41
|
+
return `${"\u2593".repeat(full)}${"\u2591".repeat(10 - full)}`;
|
|
42
|
+
}
|
|
43
|
+
function contextLevel(percent) {
|
|
44
|
+
return percent >= 80 ? "full" : percent >= 70 ? "high" : "ok";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
KEY_HINT_CHATS,
|
|
49
|
+
countChat,
|
|
50
|
+
KEYS_HELP,
|
|
51
|
+
contextBar,
|
|
52
|
+
contextLevel
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=chunk-L55VUSX3.js.map
|
|
@@ -42,6 +42,10 @@ var NETWORK_PRESETS = {
|
|
|
42
42
|
]
|
|
43
43
|
};
|
|
44
44
|
var PRESET_NAMES = Object.keys(NETWORK_PRESETS);
|
|
45
|
+
function presetFor(host) {
|
|
46
|
+
const bare = host.toLowerCase();
|
|
47
|
+
return PRESET_NAMES.find((name) => NETWORK_PRESETS[name]?.includes(bare));
|
|
48
|
+
}
|
|
45
49
|
var NETWORK_PORTS = [80, 443];
|
|
46
50
|
var HOST = /^(\*\.)?([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,63}$/;
|
|
47
51
|
function expandAllowlist(entries) {
|
|
@@ -74,9 +78,9 @@ function describeAllowlist(entries) {
|
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
// src/permissions/sandboxPaths.ts
|
|
77
|
-
import { realpathSync } from "fs";
|
|
81
|
+
import { readdirSync, realpathSync } from "fs";
|
|
78
82
|
import { homedir, tmpdir } from "os";
|
|
79
|
-
import { isAbsolute, join, resolve } from "path";
|
|
83
|
+
import { isAbsolute, join, resolve, sep } from "path";
|
|
80
84
|
import { globbySync } from "globby";
|
|
81
85
|
var DENY_READ_IN_HOME = [
|
|
82
86
|
".ssh",
|
|
@@ -101,6 +105,49 @@ var DENY_READ_IN_HOME = [
|
|
|
101
105
|
".claude.json",
|
|
102
106
|
".claude/.credentials.json"
|
|
103
107
|
];
|
|
108
|
+
var PERSONAL_IN_HOME = [
|
|
109
|
+
"Desktop",
|
|
110
|
+
"Documents",
|
|
111
|
+
"Downloads",
|
|
112
|
+
"Movies",
|
|
113
|
+
"Music",
|
|
114
|
+
"Pictures",
|
|
115
|
+
"Public",
|
|
116
|
+
"Library/Mail",
|
|
117
|
+
"Library/Messages",
|
|
118
|
+
"Library/Safari",
|
|
119
|
+
"Library/Calendars",
|
|
120
|
+
"Library/Application Support/AddressBook",
|
|
121
|
+
"Library/Mobile Documents",
|
|
122
|
+
"Library/CloudStorage"
|
|
123
|
+
];
|
|
124
|
+
function denyAround(base, keep, keepHidden = false) {
|
|
125
|
+
let names;
|
|
126
|
+
try {
|
|
127
|
+
names = readdirSync(base);
|
|
128
|
+
} catch {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
const out = [];
|
|
132
|
+
for (const name of names) {
|
|
133
|
+
const path = join(base, name);
|
|
134
|
+
if (keep.includes(path)) continue;
|
|
135
|
+
if (keep.some((k) => k.startsWith(`${path}${sep}`))) {
|
|
136
|
+
out.push(...denyAround(path, keep));
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (keepHidden && name.startsWith(".")) continue;
|
|
140
|
+
out.push(path);
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
function personalDenials(home, root) {
|
|
145
|
+
return PERSONAL_IN_HOME.flatMap((name) => {
|
|
146
|
+
const folder = join(home, name);
|
|
147
|
+
if (root === folder) return [];
|
|
148
|
+
return root.startsWith(`${folder}${sep}`) ? denyAround(folder, [root]) : [folder];
|
|
149
|
+
});
|
|
150
|
+
}
|
|
104
151
|
var PROTECTED_IN_ROOT = [".git/hooks", ".git/config", ".garuda"];
|
|
105
152
|
var WRITE_IN_HOME = [
|
|
106
153
|
".cache",
|
|
@@ -122,6 +169,7 @@ function sandboxPaths(root, settings = {}, home = homedir()) {
|
|
|
122
169
|
denyWritePaths: PROTECTED_IN_ROOT.map((p) => join(root, p)),
|
|
123
170
|
denyReadPaths: unique([
|
|
124
171
|
...DENY_READ_IN_HOME.map((p) => join(home, p)),
|
|
172
|
+
...personalDenials(home, root),
|
|
125
173
|
...(settings.denyRead ?? []).map(expand)
|
|
126
174
|
])
|
|
127
175
|
};
|
|
@@ -817,10 +865,10 @@ function seatbeltProfile(policy, extra = []) {
|
|
|
817
865
|
}
|
|
818
866
|
}
|
|
819
867
|
if (policy.denyWritePaths.length > 0) {
|
|
820
|
-
lines.push(`(deny file-write* ${paths(policy.denyWritePaths)})`);
|
|
868
|
+
lines.push(`(deny file-write* ${paths(withDataVolume(policy.denyWritePaths))})`);
|
|
821
869
|
}
|
|
822
870
|
if (policy.denyReadPaths.length > 0) {
|
|
823
|
-
lines.push(`(deny file-read* ${paths(policy.denyReadPaths)})`);
|
|
871
|
+
lines.push(`(deny file-read* ${paths(withDataVolume(policy.denyReadPaths))})`);
|
|
824
872
|
}
|
|
825
873
|
lines.push("(deny lsopen)", "(deny appleevent-send)");
|
|
826
874
|
if (!policy.network) {
|
|
@@ -836,6 +884,14 @@ function seatbeltProfile(policy, extra = []) {
|
|
|
836
884
|
lines.push(...extra);
|
|
837
885
|
return lines.join("\n");
|
|
838
886
|
}
|
|
887
|
+
function withDataVolume(list) {
|
|
888
|
+
const out = [];
|
|
889
|
+
for (const path of list) {
|
|
890
|
+
out.push(path);
|
|
891
|
+
if (path.startsWith("/Users/")) out.push(`/System/Volumes/Data${path}`);
|
|
892
|
+
}
|
|
893
|
+
return out;
|
|
894
|
+
}
|
|
839
895
|
function quote2(path) {
|
|
840
896
|
return `"${path.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
841
897
|
}
|
|
@@ -976,6 +1032,8 @@ var schema = z.strictObject({
|
|
|
976
1032
|
}).optional(),
|
|
977
1033
|
/** Language server diagnostics after edits (0.4). enabled: default false. */
|
|
978
1034
|
lsp: z.strictObject({ enabled: z.boolean().optional() }).optional(),
|
|
1035
|
+
/** The chat's look (0.18.1). clearOnStart: default true (the Ink chat starts on a clear screen). */
|
|
1036
|
+
ui: z.strictObject({ clearOnStart: z.boolean().optional() }).optional(),
|
|
979
1037
|
/**
|
|
980
1038
|
* The project's formatter after edits (0.10). enabled: default false (A/B first). commands: by
|
|
981
1039
|
* name, false turns a detected formatter off; a command adds one or replaces a detected one.
|
|
@@ -1065,6 +1123,7 @@ function parseSettings(json, source = SETTINGS_FILE) {
|
|
|
1065
1123
|
skills,
|
|
1066
1124
|
agents,
|
|
1067
1125
|
notifications,
|
|
1126
|
+
ui,
|
|
1068
1127
|
thinking,
|
|
1069
1128
|
formatters,
|
|
1070
1129
|
network
|
|
@@ -1102,6 +1161,7 @@ function parseSettings(json, source = SETTINGS_FILE) {
|
|
|
1102
1161
|
...commands?.mode === void 0 ? {} : { commands: { mode: commands.mode } },
|
|
1103
1162
|
...todo?.enabled === void 0 ? {} : { todo: { enabled: todo.enabled } },
|
|
1104
1163
|
...lsp?.enabled === void 0 ? {} : { lsp: { enabled: lsp.enabled } },
|
|
1164
|
+
...ui?.clearOnStart === void 0 ? {} : { ui: { clearOnStart: ui.clearOnStart } },
|
|
1105
1165
|
...undo?.enabled === void 0 ? {} : { undo: { enabled: undo.enabled } },
|
|
1106
1166
|
...skills?.enabled === void 0 ? {} : { skills: { enabled: skills.enabled } },
|
|
1107
1167
|
...agents?.enabled === void 0 ? {} : { agents: { enabled: agents.enabled } },
|
|
@@ -1160,7 +1220,7 @@ async function loadSettings(root) {
|
|
|
1160
1220
|
}
|
|
1161
1221
|
|
|
1162
1222
|
// src/permissions/engine.ts
|
|
1163
|
-
import { sep } from "path";
|
|
1223
|
+
import { sep as sep2 } from "path";
|
|
1164
1224
|
|
|
1165
1225
|
// src/permissions/sensitive.ts
|
|
1166
1226
|
var SENSITIVE_PATTERNS = [
|
|
@@ -1430,7 +1490,14 @@ var PermissionEngine = class {
|
|
|
1430
1490
|
if (choice === "once" && accepted !== void 0) {
|
|
1431
1491
|
return { decision: { allowed: true, by: "user", hunks: accepted }, userChoice: choice };
|
|
1432
1492
|
}
|
|
1433
|
-
return {
|
|
1493
|
+
return {
|
|
1494
|
+
decision: {
|
|
1495
|
+
allowed: true,
|
|
1496
|
+
by: "user",
|
|
1497
|
+
...choice === "session" ? { remembered: true } : {}
|
|
1498
|
+
},
|
|
1499
|
+
userChoice: choice
|
|
1500
|
+
};
|
|
1434
1501
|
}
|
|
1435
1502
|
/**
|
|
1436
1503
|
* Plan mode: file changes and memory are always denied; a command runs only in the OS sandbox
|
|
@@ -1518,7 +1585,7 @@ var PermissionEngine = class {
|
|
|
1518
1585
|
const denied = policyDeniedPaths(this.root, this.policy?.denyPaths ?? []);
|
|
1519
1586
|
let writePaths = [.../* @__PURE__ */ new Set([...paths.writePaths, ...this.access.writePaths])];
|
|
1520
1587
|
let denyWritePaths = [...paths.denyWritePaths, ...denied];
|
|
1521
|
-
const inRoot = (p) => p === this.root || p.startsWith(`${this.root}${
|
|
1588
|
+
const inRoot = (p) => p === this.root || p.startsWith(`${this.root}${sep2}`);
|
|
1522
1589
|
if (readOnlyRoot) {
|
|
1523
1590
|
writePaths = writePaths.filter((p) => !inRoot(p));
|
|
1524
1591
|
denyWritePaths = [this.root, ...denyWritePaths];
|
|
@@ -1588,10 +1655,12 @@ function describeTarget(target) {
|
|
|
1588
1655
|
|
|
1589
1656
|
export {
|
|
1590
1657
|
isSensitive,
|
|
1658
|
+
presetFor,
|
|
1591
1659
|
NETWORK_PORTS,
|
|
1592
1660
|
expandAllowlist,
|
|
1593
1661
|
hostAllowed,
|
|
1594
1662
|
describeAllowlist,
|
|
1663
|
+
denyAround,
|
|
1595
1664
|
sandboxPaths,
|
|
1596
1665
|
HostExecutor,
|
|
1597
1666
|
EXECUTOR_NAMES,
|
|
@@ -1602,4 +1671,4 @@ export {
|
|
|
1602
1671
|
JOB_DENIAL,
|
|
1603
1672
|
PermissionEngine
|
|
1604
1673
|
};
|
|
1605
|
-
//# sourceMappingURL=chunk-
|
|
1674
|
+
//# sourceMappingURL=chunk-LMKGSYJC.js.map
|
|
@@ -36,6 +36,7 @@ var BUILTIN_COMMANDS = [
|
|
|
36
36
|
"search",
|
|
37
37
|
"undo",
|
|
38
38
|
"redo",
|
|
39
|
+
"rewind",
|
|
39
40
|
"exit",
|
|
40
41
|
"quit"
|
|
41
42
|
];
|
|
@@ -43,4 +44,4 @@ var BUILTIN_COMMANDS = [
|
|
|
43
44
|
export {
|
|
44
45
|
BUILTIN_COMMANDS
|
|
45
46
|
};
|
|
46
|
-
//# sourceMappingURL=chunk-
|
|
47
|
+
//# sourceMappingURL=chunk-MNE7HHX2.js.map
|