@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
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
receiptText,
|
|
4
|
+
screenReaderMode
|
|
5
|
+
} from "./chunk-2OYTNNUJ.js";
|
|
2
6
|
import {
|
|
3
7
|
serverCallText,
|
|
4
8
|
serverResultSummary
|
|
@@ -85,6 +89,19 @@ var PlainRenderer = class {
|
|
|
85
89
|
info(text) {
|
|
86
90
|
this.line(this.paint("dim", text));
|
|
87
91
|
}
|
|
92
|
+
/** UX U1: a box on a terminal of 60 columns or more, else plain lines (stderr, like notes). */
|
|
93
|
+
receipt(receipt) {
|
|
94
|
+
const tty = this.err.isTTY === true;
|
|
95
|
+
const columns = tty ? this.err.columns : void 0;
|
|
96
|
+
this.line(
|
|
97
|
+
`
|
|
98
|
+
${receiptText(receipt, {
|
|
99
|
+
...columns === void 0 ? {} : { columns },
|
|
100
|
+
...this.color ? { paint: (style, text) => this.paint(style, text) } : {},
|
|
101
|
+
screenReader: screenReaderMode()
|
|
102
|
+
})}`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
88
105
|
warn(text) {
|
|
89
106
|
this.line(this.paint("yellow", text));
|
|
90
107
|
}
|
|
@@ -203,4 +220,4 @@ export {
|
|
|
203
220
|
summariseCall,
|
|
204
221
|
summariseResult
|
|
205
222
|
};
|
|
206
|
-
//# sourceMappingURL=chunk-
|
|
223
|
+
//# sourceMappingURL=chunk-AX3A5KPH.js.map
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
colorDiff
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-VISKX36R.js";
|
|
5
|
+
import {
|
|
6
|
+
budgetNote,
|
|
7
|
+
chatStopMessage,
|
|
8
|
+
formatTokens,
|
|
9
|
+
formatUsd,
|
|
10
|
+
stopMessage,
|
|
11
|
+
usageLine
|
|
12
|
+
} from "./chunk-2OYTNNUJ.js";
|
|
13
|
+
import {
|
|
14
|
+
tourChoice
|
|
15
|
+
} from "./chunk-Y62J7PTD.js";
|
|
5
16
|
import {
|
|
6
17
|
AuditDirError,
|
|
7
18
|
SPECIALIST_SPECS,
|
|
@@ -18,19 +29,21 @@ import {
|
|
|
18
29
|
repoMapText,
|
|
19
30
|
repoMapTool,
|
|
20
31
|
verifyAuditDir
|
|
21
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-STVFWSIL.js";
|
|
33
|
+
import {
|
|
34
|
+
hasReceipt
|
|
35
|
+
} from "./chunk-4QRTTRVK.js";
|
|
22
36
|
import {
|
|
23
|
-
budgetSpent,
|
|
24
37
|
isCostIncomplete,
|
|
25
38
|
pendingChildTokens
|
|
26
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-NLS7GMF7.js";
|
|
27
40
|
import {
|
|
28
41
|
WEB_SEARCH_USD,
|
|
29
42
|
totalTokens
|
|
30
43
|
} from "./chunk-7KBOIPUT.js";
|
|
31
44
|
import {
|
|
32
45
|
BUILTIN_COMMANDS
|
|
33
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-MNE7HHX2.js";
|
|
34
47
|
import {
|
|
35
48
|
LSP_LANGUAGES
|
|
36
49
|
} from "./chunk-EU7PA45O.js";
|
|
@@ -76,70 +89,19 @@ function focusEvent(input) {
|
|
|
76
89
|
return void 0;
|
|
77
90
|
}
|
|
78
91
|
|
|
79
|
-
// src/cli/report.ts
|
|
80
|
-
function formatTokens(n) {
|
|
81
|
-
if (n < 1e3) return String(n);
|
|
82
|
-
if (n < 1e6) return `${(n / 1e3).toFixed(1)}k`;
|
|
83
|
-
return `${(n / 1e6).toFixed(1)}M`;
|
|
84
|
-
}
|
|
85
|
-
function formatUsd(usd, incomplete = false) {
|
|
86
|
-
if (usd === void 0) return "cost unknown";
|
|
87
|
-
const amount = usd < 0.01 ? `$${usd.toFixed(4)}` : `$${usd.toFixed(2)}`;
|
|
88
|
-
return incomplete ? `\u2265 ${amount} (incomplete)` : amount;
|
|
89
|
-
}
|
|
90
|
-
function budgetNote(session) {
|
|
91
|
-
const unreported = session.unreportedTokens;
|
|
92
|
-
const children = pendingChildTokens(session);
|
|
93
|
-
if (unreported <= 0 && children <= 0) return void 0;
|
|
94
|
-
const parts = [
|
|
95
|
-
...unreported > 0 ? [`${formatTokens(unreported)} reserved without reported usage`] : [],
|
|
96
|
-
...children > 0 ? [`${formatTokens(children)} from subagents with no report`] : []
|
|
97
|
-
];
|
|
98
|
-
return `budget ${formatTokens(budgetSpent(session))} tokens (${parts.join(", ")})`;
|
|
99
|
-
}
|
|
100
|
-
function usageLine(result, session, runCostUsd, contextWindow, runCostIncomplete = false) {
|
|
101
|
-
const u = result.usage;
|
|
102
|
-
const input = u.inputTokens + u.cacheReadTokens + u.cacheWriteTokens;
|
|
103
|
-
const percent = Math.round(session.contextTokens / contextWindow * 100);
|
|
104
|
-
return [
|
|
105
|
-
`${result.stopReason} \xB7 ${result.steps} step(s)`,
|
|
106
|
-
`${budgetNote(session) !== void 0 ? "reported " : ""}${formatTokens(input)} in (${formatTokens(u.cacheReadTokens)} cached) / ${formatTokens(u.outputTokens)} out`,
|
|
107
|
-
formatUsd(runCostUsd, runCostIncomplete),
|
|
108
|
-
`context ${percent}% of ${formatTokens(contextWindow)}`,
|
|
109
|
-
`session ${formatTokens(totalTokens(session.usage))} tokens, ${formatUsd(session.costUsd, isCostIncomplete(session))}`,
|
|
110
|
-
...budgetNote(session) === void 0 ? [] : [budgetNote(session)]
|
|
111
|
-
].join(" \xB7 ");
|
|
112
|
-
}
|
|
113
|
-
function stopMessage(reason, limits) {
|
|
114
|
-
switch (reason) {
|
|
115
|
-
case "done":
|
|
116
|
-
return void 0;
|
|
117
|
-
case "max_steps":
|
|
118
|
-
return `Stopped: the run reached the step limit (${limits.maxSteps}). Use --resume to continue, or raise limits.maxSteps in .garuda/settings.json.`;
|
|
119
|
-
case "token_budget":
|
|
120
|
-
return `Stopped: the session used its token budget (${formatTokens(limits.tokenBudget)} tokens). Raise limits.tokenBudget in .garuda/settings.json to continue.`;
|
|
121
|
-
case "repeated_calls":
|
|
122
|
-
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.";
|
|
123
|
-
case "max_tokens":
|
|
124
|
-
return "Stopped: the response reached the output token limit.";
|
|
125
|
-
case "refusal":
|
|
126
|
-
return "Stopped: the model refused the request.";
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
92
|
// src/cli/turn.ts
|
|
131
|
-
async function runTurnInTerminal(runtime, approver, renderer, prompt, exitNow, watcher) {
|
|
93
|
+
async function runTurnInTerminal(runtime, approver, renderer, prompt, exitNow, watcher, options = {}) {
|
|
132
94
|
const started = performance.now();
|
|
133
95
|
watcher?.turnStarted();
|
|
134
96
|
let outcome;
|
|
135
97
|
try {
|
|
136
|
-
outcome = await runTurn(runtime, approver, renderer, prompt, exitNow);
|
|
98
|
+
outcome = await runTurn(runtime, approver, renderer, prompt, exitNow, options);
|
|
137
99
|
return outcome;
|
|
138
100
|
} finally {
|
|
139
101
|
watcher?.turnEnded(outcome, performance.now() - started);
|
|
140
102
|
}
|
|
141
103
|
}
|
|
142
|
-
async function runTurn(runtime, approver, renderer, prompt, exitNow) {
|
|
104
|
+
async function runTurn(runtime, approver, renderer, prompt, exitNow, options) {
|
|
143
105
|
const controller = new AbortController();
|
|
144
106
|
const stop = () => {
|
|
145
107
|
if (controller.signal.aborted) exitNow();
|
|
@@ -158,12 +120,20 @@ async function runTurn(runtime, approver, renderer, prompt, exitNow) {
|
|
|
158
120
|
if (session !== void 0) {
|
|
159
121
|
const runCost = session.costUsd === void 0 ? void 0 : session.costUsd - costBefore;
|
|
160
122
|
const runIncomplete = session.unreportedTokens > unreportedBefore || pendingChildTokens(session) > pendingBefore || session.costIncomplete && !incompleteBefore;
|
|
161
|
-
|
|
162
|
-
|
|
123
|
+
const receipt = runtime.lastReceipt;
|
|
124
|
+
if (receipt !== void 0 && hasReceipt(receipt) && renderer.receipt !== void 0) {
|
|
125
|
+
renderer.receipt(receipt);
|
|
126
|
+
} else {
|
|
127
|
+
renderer.info(
|
|
128
|
+
`
|
|
163
129
|
[${usageLine(result, session, runCost, runtime.limits.contextWindow, runIncomplete)}]`
|
|
164
|
-
|
|
130
|
+
);
|
|
131
|
+
}
|
|
165
132
|
}
|
|
166
|
-
const message =
|
|
133
|
+
const message = (options.chat === true ? chatStopMessage : stopMessage)(
|
|
134
|
+
result.stopReason,
|
|
135
|
+
runtime.limits
|
|
136
|
+
);
|
|
167
137
|
if (message !== void 0) renderer.warn(message);
|
|
168
138
|
return { kind: "done", result };
|
|
169
139
|
} catch (error) {
|
|
@@ -208,6 +178,34 @@ function modeText(runtime) {
|
|
|
208
178
|
return runtime.mode === "plan" ? "Plan mode: the agent reads and plans. It cannot change files; commands run read-only in the sandbox. /build (or Shift+Tab) switches back." : "Build mode: the agent can change files and run commands, with the usual approvals.";
|
|
209
179
|
}
|
|
210
180
|
|
|
181
|
+
// src/cli/chat/rewind.ts
|
|
182
|
+
function ago(at, now) {
|
|
183
|
+
if (at === void 0) return "";
|
|
184
|
+
const minutes = Math.floor(Math.max(0, now - at) / 6e4);
|
|
185
|
+
if (minutes < 1) return "<1 min";
|
|
186
|
+
if (minutes < 60) return `${minutes} min`;
|
|
187
|
+
const hours = Math.floor(minutes / 60);
|
|
188
|
+
return hours < 48 ? `${hours} h` : `${Math.floor(hours / 24)} d`;
|
|
189
|
+
}
|
|
190
|
+
function rewindList(points, now = Date.now()) {
|
|
191
|
+
if (points.length === 0) return "There is no turn to rewind to yet.";
|
|
192
|
+
const width = Math.min(40, Math.max(...points.map((p) => p.prompt.length + 2)));
|
|
193
|
+
const rows = points.map((p, i) => {
|
|
194
|
+
const prompt = `"${p.prompt}"`;
|
|
195
|
+
const shown = prompt.length > width ? `${prompt.slice(0, width - 2)}\u2026"` : prompt;
|
|
196
|
+
const files = p.changedFiles === void 0 ? "" : p.changedFiles === 0 ? "no files" : `${p.changedFiles} file${p.changedFiles === 1 ? "" : "s"}`;
|
|
197
|
+
const cost = p.costUsd === void 0 ? "" : formatUsd(p.costUsd);
|
|
198
|
+
const note = p.conversation ? "" : " (before a compaction: files only)";
|
|
199
|
+
return ` ${String(i + 1).padStart(3)} ${shown.padEnd(width)} ${files.padEnd(8)} ${cost.padEnd(8)} ${ago(p.at, now)}${note}`.trimEnd();
|
|
200
|
+
}).reverse();
|
|
201
|
+
return [
|
|
202
|
+
"Rewind to the start of a turn (newest first):",
|
|
203
|
+
...rows,
|
|
204
|
+
"/rewind <n> goes back to the start of turn n (/redo brings the turns back).",
|
|
205
|
+
"/rewind fork <n> starts a new session from there; this one stays as it is."
|
|
206
|
+
].join("\n");
|
|
207
|
+
}
|
|
208
|
+
|
|
211
209
|
// src/cli/chat/commands.ts
|
|
212
210
|
var HELP = [
|
|
213
211
|
"Type a task and press Enter. @path attaches a file or folder, !command runs a command, Tab completes.",
|
|
@@ -217,7 +215,7 @@ var HELP = [
|
|
|
217
215
|
" /session the session id and file",
|
|
218
216
|
" /sessions this project's sessions; /sessions <n|id> continues one; rename, delete",
|
|
219
217
|
" /models the models; /models <n|id|opus|sonnet|haiku> switches for this chat",
|
|
220
|
-
" /export write this conversation as Markdown; /export <file
|
|
218
|
+
" /export write this conversation as Markdown; /export <file>; /export --html [file] as one page",
|
|
221
219
|
" /compact summarise the older turns now; /compact <what to keep>",
|
|
222
220
|
" /diff file changes in this session; /diff last (last turn); /diff [last] <path>",
|
|
223
221
|
" /schedule make the last plan a job that runs later: /schedule [HH:MM], then garuda run <id>",
|
|
@@ -240,6 +238,7 @@ var HELP = [
|
|
|
240
238
|
" /build build mode: change files and run commands (the default); /build <task> runs it",
|
|
241
239
|
" /undo take back the last turn: its file changes and its messages",
|
|
242
240
|
" /redo bring back the last undone turn",
|
|
241
|
+
" /rewind the turns of this session; /rewind <n> goes back to turn n, /rewind fork <n> forks",
|
|
243
242
|
" /init set up this folder: AGENTS.md, and files from other agents (Claude Code, OpenCode \u2026)",
|
|
244
243
|
" /editor write the next prompt in $VISUAL or $EDITOR (also Ctrl-G)",
|
|
245
244
|
" /details show or hide the result line under each tool call; /details on|off",
|
|
@@ -362,6 +361,18 @@ async function runCommand(text, { runtime, renderer, sessionPath, output, signal
|
|
|
362
361
|
await expertsCommand(runtime, renderer);
|
|
363
362
|
} else if (command === "/audit") {
|
|
364
363
|
await auditCommand(runtime, renderer, text.slice(command.length).trim());
|
|
364
|
+
} else if (command === "/rewind") {
|
|
365
|
+
const args = text.slice(command.length).trim().split(/\s+/).filter((a) => a !== "");
|
|
366
|
+
const signal2 = new AbortController().signal;
|
|
367
|
+
const fork = args[0] === "fork";
|
|
368
|
+
const number = fork ? args[1] : args[0];
|
|
369
|
+
if (number === void 0 && !fork) renderer.info(rewindList(runtime.rewindPoints));
|
|
370
|
+
else if (number === void 0 || !/^\d+$/.test(number) || args.length > (fork ? 2 : 1)) {
|
|
371
|
+
renderer.info("Use: /rewind, /rewind <n> or /rewind fork <n>.");
|
|
372
|
+
} else {
|
|
373
|
+
const turn = Number(number);
|
|
374
|
+
renderer.info(await (fork ? runtime.fork(turn, signal2) : runtime.rewind(turn, signal2)));
|
|
375
|
+
}
|
|
365
376
|
} else if (command === "/undo" || command === "/redo") {
|
|
366
377
|
const signal2 = new AbortController().signal;
|
|
367
378
|
renderer.info(await (command === "/undo" ? runtime.undo(signal2) : runtime.redo(signal2)));
|
|
@@ -484,10 +495,10 @@ async function diffCommand(runtime, renderer, arg, output) {
|
|
|
484
495
|
`Changes${where} ${since} (${result.files.length} file${result.files.length === 1 ? "" : "s"}, +${plus} \u2212${minus}). Changes you made yourself count too.`,
|
|
485
496
|
...rows,
|
|
486
497
|
"",
|
|
487
|
-
colorDiff(lines.slice(0, DIFF_LINES).join("\n")),
|
|
498
|
+
colorDiff(lines.slice(0, DIFF_LINES).join("\n"), process.stdout.columns),
|
|
488
499
|
...cut ? [`\u2026 ${lines.length - DIFF_LINES} more lines. ${more}`] : []
|
|
489
500
|
].join("\n"),
|
|
490
|
-
cut ? { title: `diff ${since}${where}`, text: colorDiff(result.patch) } : void 0
|
|
501
|
+
cut ? { title: `diff ${since}${where}`, text: colorDiff(result.patch, process.stdout.columns) } : void 0
|
|
491
502
|
);
|
|
492
503
|
}
|
|
493
504
|
async function jobsCommand(runtime, renderer, id) {
|
|
@@ -513,7 +524,7 @@ async function jobsCommand(runtime, renderer, id) {
|
|
|
513
524
|
return;
|
|
514
525
|
}
|
|
515
526
|
const jobs = await listJobs2(runtime.root);
|
|
516
|
-
const { defaultAgentEnv, nightAgentTime } = await import("./launchd-
|
|
527
|
+
const { defaultAgentEnv, nightAgentTime } = await import("./launchd-QG6R5RAK.js");
|
|
517
528
|
const night = nightAgentTime(runtime.root, defaultAgentEnv());
|
|
518
529
|
const nightLine = night === void 0 ? [] : [
|
|
519
530
|
`Night shift: launchd starts the queue at ${night.toTimeString().slice(0, 5)} on ${night.toDateString()} (/jobs cancel night removes it).`
|
|
@@ -551,8 +562,11 @@ async function exportCommand(runtime, renderer, file) {
|
|
|
551
562
|
renderer.info("There is no conversation to export yet.");
|
|
552
563
|
return;
|
|
553
564
|
}
|
|
554
|
-
const { sessionMarkdown, writeExport } = await import("./export-
|
|
555
|
-
const
|
|
565
|
+
const { sessionMarkdown, writeExport } = await import("./export-ZPF3AMO6.js");
|
|
566
|
+
const html = /^--html(\s|$)/.test(file);
|
|
567
|
+
const name = html ? file.slice("--html".length).trim() : file;
|
|
568
|
+
const text = html ? (await import("./exportHtml-ISISG5HG.js")).sessionHtml(records, id) : sessionMarkdown(records, id);
|
|
569
|
+
const result = await writeExport(runtime.root, id, text, name, html ? ".html" : ".md");
|
|
556
570
|
if ("problem" in result) renderer.warn(result.problem);
|
|
557
571
|
else
|
|
558
572
|
renderer.info(
|
|
@@ -896,6 +910,7 @@ var RUN_AT_ONCE = /* @__PURE__ */ new Set([
|
|
|
896
910
|
"build",
|
|
897
911
|
"undo",
|
|
898
912
|
"redo",
|
|
913
|
+
"rewind",
|
|
899
914
|
"init",
|
|
900
915
|
"editor",
|
|
901
916
|
"details",
|
|
@@ -1097,8 +1112,30 @@ function rootFiles(root, now = Date.now) {
|
|
|
1097
1112
|
};
|
|
1098
1113
|
}
|
|
1099
1114
|
|
|
1115
|
+
// src/cli/chat/stop.ts
|
|
1116
|
+
var CONTINUE_PROMPT = "Go on with the task from where you stopped. You reached the step limit; the conversation above has your work so far.";
|
|
1117
|
+
var SUMMARY_PROMPT = "Stop the work here and do not call tools. Say what is done, what is left, and the next step.";
|
|
1118
|
+
async function stopHandoff(approver, maxSteps, isolation, signal = new AbortController().signal) {
|
|
1119
|
+
const request = {
|
|
1120
|
+
tool: "step_limit",
|
|
1121
|
+
target: { kind: "input", json: "{}" },
|
|
1122
|
+
preview: `The turn used its ${maxSteps} steps. The next turn gets ${maxSteps} more.`,
|
|
1123
|
+
isolation,
|
|
1124
|
+
title: "The task is not finished.",
|
|
1125
|
+
question: "Go on?",
|
|
1126
|
+
labels: {
|
|
1127
|
+
once: `Go on for ${maxSteps} more steps`,
|
|
1128
|
+
session: "Summarise what is done and what is left",
|
|
1129
|
+
deny: "Stop here"
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
const choice = await approver.ask(request, signal);
|
|
1133
|
+
return choice === "once" ? "more" : choice === "session" ? "summary" : "stop";
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1100
1136
|
// src/cli/chat/controller.ts
|
|
1101
|
-
async function runChat(runtime, store, sessionPath, exitNow, notifier) {
|
|
1137
|
+
async function runChat(runtime, store, sessionPath, exitNow, notifier, tour) {
|
|
1138
|
+
let tourTasks = tour;
|
|
1102
1139
|
store.onToggleMode = () => {
|
|
1103
1140
|
runtime.setMode(runtime.mode === "plan" ? "build" : "plan");
|
|
1104
1141
|
return statusOf(runtime);
|
|
@@ -1117,7 +1154,8 @@ async function runChat(runtime, store, sessionPath, exitNow, notifier) {
|
|
|
1117
1154
|
void args.refresh();
|
|
1118
1155
|
const input = await store.nextInput();
|
|
1119
1156
|
if (input === void 0) return;
|
|
1120
|
-
const text = input.trim();
|
|
1157
|
+
const text = tourChoice(input.trim(), tourTasks);
|
|
1158
|
+
tourTasks = void 0;
|
|
1121
1159
|
let prompt = text;
|
|
1122
1160
|
if (text.startsWith("!") && text.length > 1) {
|
|
1123
1161
|
await runShell(runtime, store, text.slice(1).trim());
|
|
@@ -1167,10 +1205,20 @@ async function runChat(runtime, store, sessionPath, exitNow, notifier) {
|
|
|
1167
1205
|
store.begin(display, prompt === display);
|
|
1168
1206
|
let outcome;
|
|
1169
1207
|
try {
|
|
1170
|
-
outcome = await runTurnInTerminal(runtime, store, store, prompt, exitNow, notifier
|
|
1208
|
+
outcome = await runTurnInTerminal(runtime, store, store, prompt, exitNow, notifier, {
|
|
1209
|
+
chat: true
|
|
1210
|
+
});
|
|
1171
1211
|
} finally {
|
|
1172
1212
|
store.end(statusOf(runtime));
|
|
1173
1213
|
}
|
|
1214
|
+
if (outcome.kind === "done" && outcome.result.stopReason === "max_steps") {
|
|
1215
|
+
const next2 = await stopHandoff(store, runtime.limits.maxSteps, runtime.executor.isolation);
|
|
1216
|
+
store.refreshStatus(statusOf(runtime));
|
|
1217
|
+
if (next2 === "stop") break;
|
|
1218
|
+
prompt = next2 === "more" ? CONTINUE_PROMPT : SUMMARY_PROMPT;
|
|
1219
|
+
display = prompt;
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1174
1222
|
if (!planning || outcome.kind !== "done" || outcome.result.stopReason !== "done") break;
|
|
1175
1223
|
const next = await planHandoff(runtime, store);
|
|
1176
1224
|
store.refreshStatus(statusOf(runtime));
|
|
@@ -1252,6 +1300,14 @@ var CommandArgs = class {
|
|
|
1252
1300
|
return (first === "cancel" || first === "delete") && second === void 0 ? this.jobs : [];
|
|
1253
1301
|
case "diff":
|
|
1254
1302
|
return first === void 0 ? words("last") : [];
|
|
1303
|
+
case "rewind": {
|
|
1304
|
+
const turns = this.runtime.rewindPoints.map((p, i) => ({
|
|
1305
|
+
value: String(i + 1),
|
|
1306
|
+
hint: p.prompt
|
|
1307
|
+
}));
|
|
1308
|
+
if (first === void 0) return [...words("fork"), ...turns];
|
|
1309
|
+
return first === "fork" && second === void 0 ? turns : [];
|
|
1310
|
+
}
|
|
1255
1311
|
case "mcp":
|
|
1256
1312
|
if (first === void 0) return words("logout");
|
|
1257
1313
|
return first === "logout" && second === void 0 ? this.runtime.mcpStatus().map((s) => ({ value: s.name })) : [];
|
|
@@ -1278,8 +1334,6 @@ export {
|
|
|
1278
1334
|
reportsFocus,
|
|
1279
1335
|
FocusTracker,
|
|
1280
1336
|
focusEvent,
|
|
1281
|
-
formatTokens,
|
|
1282
|
-
stopMessage,
|
|
1283
1337
|
BUILD_PROMPT,
|
|
1284
1338
|
planHandoff,
|
|
1285
1339
|
runCommand,
|
|
@@ -1288,9 +1342,12 @@ export {
|
|
|
1288
1342
|
fuzzyFiles,
|
|
1289
1343
|
rootFiles,
|
|
1290
1344
|
runTurnInTerminal,
|
|
1345
|
+
CONTINUE_PROMPT,
|
|
1346
|
+
SUMMARY_PROMPT,
|
|
1347
|
+
stopHandoff,
|
|
1291
1348
|
runChat,
|
|
1292
1349
|
commandNames,
|
|
1293
1350
|
statusOf,
|
|
1294
1351
|
CommandArgs
|
|
1295
1352
|
};
|
|
1296
|
-
//# sourceMappingURL=chunk-
|
|
1353
|
+
//# sourceMappingURL=chunk-HP6EGFHV.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
|