@livx.cc/agentx 0.99.27 → 0.99.29
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/dist/{Agent-ChSs1QSx.d.ts → Agent-qoEmC4ic.d.ts} +54 -10
- package/dist/cli.d.ts +10 -7
- package/dist/cli.js +766 -294
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +10 -5
- package/dist/index.js +610 -139
- package/dist/index.js.map +1 -1
- package/dist/{mcp-DiAiMiG5.d.ts → mcp-Cnq0pS95.d.ts} +1 -1
- package/dist/mcp.client.d.ts +2 -2
- package/dist/{tools-CJAPSBhS.d.ts → tools-BZVppx-6.d.ts} +98 -1
- package/dist/tools.shell.d.ts +1 -1
- package/dist/tools.shell.js +20 -11
- package/dist/tools.shell.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,271 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/logging.ts
|
|
13
|
+
import { log } from "libx.js/src/modules/log";
|
|
14
|
+
var forComponent;
|
|
15
|
+
var init_logging = __esm({
|
|
16
|
+
"src/logging.ts"() {
|
|
17
|
+
"use strict";
|
|
18
|
+
forComponent = (name) => log.forComponent(name);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// src/llm.ts
|
|
23
|
+
import { readFileSync, statSync as statSync2, realpathSync } from "fs";
|
|
24
|
+
import { resolve, sep } from "path";
|
|
25
|
+
import { randomBytes } from "crypto";
|
|
26
|
+
function contentText(content) {
|
|
27
|
+
if (content == null) return "";
|
|
28
|
+
if (typeof content === "string") return content;
|
|
29
|
+
return content.map((p) => p.type === "text" ? p.text ?? "" : "[image]").join(p_sep(content));
|
|
30
|
+
}
|
|
31
|
+
function imagePart(url) {
|
|
32
|
+
return { type: "image_url", image_url: { url } };
|
|
33
|
+
}
|
|
34
|
+
function messageHasInlineImage(content) {
|
|
35
|
+
if (content == null || typeof content === "string") return false;
|
|
36
|
+
return content.some((p) => p.type === "image_url" && (p.image_url?.url ?? "").startsWith("data:"));
|
|
37
|
+
}
|
|
38
|
+
function contentBytes(content) {
|
|
39
|
+
if (content == null) return 0;
|
|
40
|
+
if (typeof content === "string") return content.length;
|
|
41
|
+
let n = 0;
|
|
42
|
+
for (const p of content) {
|
|
43
|
+
if (p.type === "text") {
|
|
44
|
+
n += (p.text ?? "").length;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const url = p.image_url?.url ?? "";
|
|
48
|
+
const ref = parseImageRef(url);
|
|
49
|
+
n += (ref ? ref.bytes : url.length) || 7;
|
|
50
|
+
}
|
|
51
|
+
return n;
|
|
52
|
+
}
|
|
53
|
+
function elideInlineImages(content, stub = IMAGE_ELIDE_STUB) {
|
|
54
|
+
return elideImageParts(content, (url) => url.startsWith("data:"), stub);
|
|
55
|
+
}
|
|
56
|
+
function elideImages(content, stub = IMAGE_ELIDE_STUB) {
|
|
57
|
+
return elideImageParts(content, (url) => url.startsWith("data:") || isImageRefUrl(url), stub);
|
|
58
|
+
}
|
|
59
|
+
function elideImageParts(content, hit, stub) {
|
|
60
|
+
if (typeof content === "string") return content;
|
|
61
|
+
let changed = false;
|
|
62
|
+
const out = [];
|
|
63
|
+
for (const p of content) {
|
|
64
|
+
if (p.type !== "image_url" || !hit(p.image_url?.url ?? "")) {
|
|
65
|
+
out.push(p);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
changed = true;
|
|
69
|
+
out.push({ type: "text", text: stub });
|
|
70
|
+
}
|
|
71
|
+
return changed ? out : content;
|
|
72
|
+
}
|
|
73
|
+
function mintImageRef(path, mime) {
|
|
74
|
+
const token = randomBytes(16).toString("hex");
|
|
75
|
+
let bytes = 0;
|
|
76
|
+
try {
|
|
77
|
+
bytes = Math.ceil(statSync2(path).size * 4 / 3);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
log2.warn(`image ref stat failed, weighing as 0: ${path}`, e);
|
|
80
|
+
}
|
|
81
|
+
refRegistry.set(token, { mime, path, bytes });
|
|
82
|
+
while (refRegistry.size > REF_REGISTRY_MAX) refRegistry.delete(refRegistry.keys().next().value);
|
|
83
|
+
return `${IMAGE_REF_SCHEME}${mime};${token};${path}`;
|
|
84
|
+
}
|
|
85
|
+
function imageRefPart(path, mime) {
|
|
86
|
+
return { type: "image_url", image_url: { url: mintImageRef(path, mime) } };
|
|
87
|
+
}
|
|
88
|
+
function parseImageRef(url) {
|
|
89
|
+
const parts = refUrlParts(url);
|
|
90
|
+
const ref = parts && refRegistry.get(parts.token);
|
|
91
|
+
if (!ref) {
|
|
92
|
+
log2.debug("image ref not in registry \u2014 treating as non-image text");
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return ref;
|
|
96
|
+
}
|
|
97
|
+
function refUrlParts(url) {
|
|
98
|
+
if (!isImageRefUrl(url)) return null;
|
|
99
|
+
const rest = url.slice(IMAGE_REF_SCHEME.length);
|
|
100
|
+
const a = rest.indexOf(";");
|
|
101
|
+
if (a < 0) return null;
|
|
102
|
+
const b = rest.indexOf(";", a + 1);
|
|
103
|
+
return { mime: rest.slice(0, a), token: b < 0 ? rest.slice(a + 1) : rest.slice(a + 1, b), path: b < 0 ? "" : rest.slice(b + 1) };
|
|
104
|
+
}
|
|
105
|
+
function remintImageRefs(messages, roots) {
|
|
106
|
+
const allowed = [];
|
|
107
|
+
for (const r of roots) {
|
|
108
|
+
try {
|
|
109
|
+
allowed.push(realpathSync(resolve(r)));
|
|
110
|
+
} catch (e) {
|
|
111
|
+
log2.debug(`image-ref root unavailable: ${r}`, e);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (allowed.length === 0) return messages;
|
|
115
|
+
const inside = (p) => allowed.some((root) => p === root || p.startsWith(root + sep));
|
|
116
|
+
let changed = false;
|
|
117
|
+
const out = messages.map((m) => {
|
|
118
|
+
if (m.role !== "user" && m.role !== "assistant" || typeof m.content === "string" || m.content == null) return m;
|
|
119
|
+
let touched = false;
|
|
120
|
+
const parts = m.content.map((p) => {
|
|
121
|
+
if (p.type !== "image_url") return p;
|
|
122
|
+
const f = refUrlParts(p.image_url?.url);
|
|
123
|
+
if (!f || refRegistry.has(f.token)) return p;
|
|
124
|
+
let real;
|
|
125
|
+
try {
|
|
126
|
+
real = realpathSync(f.path);
|
|
127
|
+
} catch (e) {
|
|
128
|
+
log2.warn(`resumed image ref unreadable, will stub: ${f.path}`, e);
|
|
129
|
+
return p;
|
|
130
|
+
}
|
|
131
|
+
if (!inside(real)) {
|
|
132
|
+
log2.warn(`resumed image ref outside the allowed roots, will stub: ${f.path} -> ${real}`);
|
|
133
|
+
return p;
|
|
134
|
+
}
|
|
135
|
+
touched = true;
|
|
136
|
+
return { type: "image_url", image_url: { url: mintImageRef(real, f.mime) } };
|
|
137
|
+
});
|
|
138
|
+
if (!touched) return m;
|
|
139
|
+
changed = true;
|
|
140
|
+
return { ...m, content: parts };
|
|
141
|
+
});
|
|
142
|
+
return changed ? out : messages;
|
|
143
|
+
}
|
|
144
|
+
function messageHasImageRef(content) {
|
|
145
|
+
if (content == null || typeof content === "string") return false;
|
|
146
|
+
return content.some((p) => p.type === "image_url" && isImageRefUrl(p.image_url?.url));
|
|
147
|
+
}
|
|
148
|
+
function messageHasImage(content) {
|
|
149
|
+
return messageHasInlineImage(content) || messageHasImageRef(content);
|
|
150
|
+
}
|
|
151
|
+
function imageRefResult(path, mime) {
|
|
152
|
+
return JSON.stringify({ imageRef: mintImageRef(path, mime), image: path });
|
|
153
|
+
}
|
|
154
|
+
function toolImageRef(content) {
|
|
155
|
+
if (typeof content !== "string" || !content.startsWith('{"imageRef":')) return null;
|
|
156
|
+
try {
|
|
157
|
+
return parseImageRef(JSON.parse(content).imageRef);
|
|
158
|
+
} catch (e) {
|
|
159
|
+
log2.debug("bad imageRef tool result", e);
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function elideStaleImages(messages, keepRecent = 1) {
|
|
164
|
+
const idx = messages.map((m, i) => messageHasImage(m.content) ? i : -1).filter((i) => i >= 0);
|
|
165
|
+
const drop = new Set(idx.slice(0, Math.max(0, idx.length - keepRecent)));
|
|
166
|
+
if (drop.size === 0) return messages;
|
|
167
|
+
let changed = false;
|
|
168
|
+
const out = messages.map((m, i) => {
|
|
169
|
+
if (!drop.has(i)) return m;
|
|
170
|
+
const c = elideInlineImages(m.content);
|
|
171
|
+
if (c === m.content) return m;
|
|
172
|
+
changed = true;
|
|
173
|
+
return { ...m, content: c };
|
|
174
|
+
});
|
|
175
|
+
return changed ? out : messages;
|
|
176
|
+
}
|
|
177
|
+
function imageWindows(messages, keepRecent) {
|
|
178
|
+
const attach = [];
|
|
179
|
+
const tool = [];
|
|
180
|
+
messages.forEach((m, i) => {
|
|
181
|
+
if (toolImageRef(m.content)) tool.push(i);
|
|
182
|
+
else if (messageHasImage(m.content)) attach.push(i);
|
|
183
|
+
});
|
|
184
|
+
const k = Math.max(0, keepRecent);
|
|
185
|
+
return /* @__PURE__ */ new Set([...attach.slice(Math.max(0, attach.length - k)), ...tool.slice(Math.max(0, tool.length - k))]);
|
|
186
|
+
}
|
|
187
|
+
function expandImagesForSend(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB) {
|
|
188
|
+
if (!messages.some(bearsImage)) return messages;
|
|
189
|
+
const keep = imageWindows(messages, keepRecent);
|
|
190
|
+
let changed = false;
|
|
191
|
+
const out = messages.map((m, i) => {
|
|
192
|
+
const content = keep.has(i) ? expandContent(m.content, stub) : stripContent(m.content, stub);
|
|
193
|
+
if (content === m.content) return m;
|
|
194
|
+
changed = true;
|
|
195
|
+
return { ...m, content };
|
|
196
|
+
});
|
|
197
|
+
return changed ? out : messages;
|
|
198
|
+
}
|
|
199
|
+
function sendBytes(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB) {
|
|
200
|
+
let n = 0;
|
|
201
|
+
for (const b of sendBytesPer(messages, keepRecent, stub)) n += b;
|
|
202
|
+
return n;
|
|
203
|
+
}
|
|
204
|
+
function sendBytesPer(messages, keepRecent = 1, stub = IMAGE_ELIDE_STUB) {
|
|
205
|
+
const keep = messages.some(bearsImage) ? imageWindows(messages, keepRecent) : null;
|
|
206
|
+
return messages.map((m, i) => wireBytes(m.content, keep == null || keep.has(i), stub) + (m.tool_calls ? JSON.stringify(m.tool_calls).length : 0));
|
|
207
|
+
}
|
|
208
|
+
function wireBytes(content, kept, stub) {
|
|
209
|
+
const tool = toolImageRef(content);
|
|
210
|
+
if (tool) return kept ? tool.bytes : stub.length;
|
|
211
|
+
if (content == null || typeof content === "string") return contentBytes(content);
|
|
212
|
+
if (kept) return contentBytes(content);
|
|
213
|
+
let n = 0;
|
|
214
|
+
for (const p of content) {
|
|
215
|
+
if (p.type === "text") {
|
|
216
|
+
n += (p.text ?? "").length;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const url = p.image_url?.url ?? "";
|
|
220
|
+
n += url.startsWith("data:") || isImageRefUrl(url) ? stub.length : url.length || 7;
|
|
221
|
+
}
|
|
222
|
+
return n;
|
|
223
|
+
}
|
|
224
|
+
function readRefDataUrl(ref) {
|
|
225
|
+
try {
|
|
226
|
+
return `data:${ref.mime};base64,${readFileSync(ref.path).toString("base64")}`;
|
|
227
|
+
} catch (e) {
|
|
228
|
+
log2.warn(`image ref unreadable, sending stub instead: ${ref.path}`, e);
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function expandContent(content, stub) {
|
|
233
|
+
const tool = toolImageRef(content);
|
|
234
|
+
if (tool) {
|
|
235
|
+
const url = readRefDataUrl(tool);
|
|
236
|
+
return url ? JSON.stringify({ dataUrl: url, image: tool.path }) : stub;
|
|
237
|
+
}
|
|
238
|
+
if (typeof content === "string") return content;
|
|
239
|
+
let changed = false;
|
|
240
|
+
const out = content.map((p) => {
|
|
241
|
+
if (p.type !== "image_url" || !isImageRefUrl(p.image_url?.url)) return p;
|
|
242
|
+
changed = true;
|
|
243
|
+
const ref = parseImageRef(p.image_url?.url);
|
|
244
|
+
const url = ref && readRefDataUrl(ref);
|
|
245
|
+
return url ? imagePart(url) : { type: "text", text: stub };
|
|
246
|
+
});
|
|
247
|
+
return changed ? out : content;
|
|
248
|
+
}
|
|
249
|
+
function stripContent(content, stub) {
|
|
250
|
+
if (toolImageRef(content)) return stub;
|
|
251
|
+
if (typeof content === "string") return content;
|
|
252
|
+
let changed = false;
|
|
253
|
+
const out = content.map((p) => {
|
|
254
|
+
const url = p.type === "image_url" ? p.image_url?.url ?? "" : "";
|
|
255
|
+
if (!url.startsWith("data:") && !isImageRefUrl(url)) return p;
|
|
256
|
+
changed = true;
|
|
257
|
+
return { type: "text", text: stub };
|
|
258
|
+
});
|
|
259
|
+
return changed ? out : content;
|
|
260
|
+
}
|
|
261
|
+
var log2, p_sep, IMAGE_ELIDE_STUB, IMAGE_REF_SCHEME, REF_REGISTRY_MAX, refRegistry, isImageRefUrl, bearsImage;
|
|
262
|
+
var init_llm = __esm({
|
|
263
|
+
"src/llm.ts"() {
|
|
264
|
+
"use strict";
|
|
265
|
+
init_logging();
|
|
266
|
+
log2 = forComponent("llm");
|
|
267
|
+
p_sep = (parts) => parts.length > 1 ? "\n" : "";
|
|
268
|
+
IMAGE_ELIDE_STUB = "[image elided \u2014 re-attach @path or /paste to view again]";
|
|
269
|
+
IMAGE_REF_SCHEME = "agentx-image:";
|
|
270
|
+
REF_REGISTRY_MAX = 256;
|
|
271
|
+
refRegistry = /* @__PURE__ */ new Map();
|
|
272
|
+
isImageRefUrl = (url) => !!url && url.startsWith(IMAGE_REF_SCHEME);
|
|
273
|
+
bearsImage = (m) => messageHasImage(m.content) || toolImageRef(m.content) != null;
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
12
277
|
// src/redact.ts
|
|
13
278
|
function redactSecrets(text) {
|
|
14
279
|
if (!text) return text;
|
|
@@ -475,16 +740,6 @@ var init_todo = __esm({
|
|
|
475
740
|
}
|
|
476
741
|
});
|
|
477
742
|
|
|
478
|
-
// src/logging.ts
|
|
479
|
-
import { log } from "libx.js/src/modules/log";
|
|
480
|
-
var forComponent;
|
|
481
|
-
var init_logging = __esm({
|
|
482
|
-
"src/logging.ts"() {
|
|
483
|
-
"use strict";
|
|
484
|
-
forComponent = (name) => log.forComponent(name);
|
|
485
|
-
}
|
|
486
|
-
});
|
|
487
|
-
|
|
488
743
|
// src/tools.web.ts
|
|
489
744
|
function htmlToText(html) {
|
|
490
745
|
let s = html.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<title[\s\S]*?<\/title>/gi, " ").replace(/<noscript[\s\S]*?<\/noscript>/gi, " ").replace(/<textarea[\s\S]*?<\/textarea>/gi, " ").replace(/<!--[\s\S]*?-->/g, " ").replace(/<\/(p|div|li|h[1-6]|tr|section|article|header|footer|nav)>/gi, "\n").replace(/<br\s*\/?>/gi, "\n").replace(/<[^>]+>/g, " ");
|
|
@@ -606,7 +861,7 @@ function makeWebFetchTool(options = {}) {
|
|
|
606
861
|
|
|
607
862
|
${capped}`;
|
|
608
863
|
} catch (e) {
|
|
609
|
-
|
|
864
|
+
log3.debug(`WebFetch ${u} failed`, e);
|
|
610
865
|
return `Error fetching ${u}: ${e?.name === "AbortError" ? `timed out after ${timeoutMs}ms` : e?.message ?? e}`;
|
|
611
866
|
} finally {
|
|
612
867
|
clearTimeout(timer);
|
|
@@ -730,7 +985,7 @@ function makeWebSearchTool(options = {}) {
|
|
|
730
985
|
if (!res.ok) return `Error: search returned ${res.status} ${res.statusText}`;
|
|
731
986
|
return formatHits(parseDdgHtml(await res.text(), maxResults));
|
|
732
987
|
} catch (e) {
|
|
733
|
-
|
|
988
|
+
log3.debug("WebSearch failed", e);
|
|
734
989
|
return `Error searching: ${e?.name === "AbortError" ? `timed out after ${timeoutMs}ms` : e?.message ?? e}`;
|
|
735
990
|
} finally {
|
|
736
991
|
clearTimeout(timer);
|
|
@@ -741,12 +996,12 @@ function makeWebSearchTool(options = {}) {
|
|
|
741
996
|
function makeWebSearchAnthropicTool(opts = {}) {
|
|
742
997
|
return makeWebSearchTool({ provider: "anthropic", name: "WebSearchAnthropic", description: ANTHROPIC_SEARCH_DESC, anthropicApiKey: opts.anthropicApiKey, model: opts.model });
|
|
743
998
|
}
|
|
744
|
-
var
|
|
999
|
+
var log3, _dnsLookup, webFetchTool, webSearchTool, ANTHROPIC_SEARCH_DESC, webSearchAnthropicTool;
|
|
745
1000
|
var init_tools_web = __esm({
|
|
746
1001
|
"src/tools.web.ts"() {
|
|
747
1002
|
"use strict";
|
|
748
1003
|
init_logging();
|
|
749
|
-
|
|
1004
|
+
log3 = forComponent("web");
|
|
750
1005
|
webFetchTool = makeWebFetchTool();
|
|
751
1006
|
webSearchTool = makeWebSearchTool();
|
|
752
1007
|
ANTHROPIC_SEARCH_DESC = "High-quality web search via Anthropic's native search index. Returns concise, sourced findings \u2014 one claim per line, each with its source URL. Prefer this over WebSearch when accuracy and citations matter; it is slower (~3\u20138s) and bills the Anthropic API account per call (one cheap-model turn + search fee).";
|
|
@@ -961,6 +1216,36 @@ ${out}`.trim() : out || "(command succeeded, no output)";
|
|
|
961
1216
|
);
|
|
962
1217
|
return `Started background job ${id} \u2014 poll with JobOutput({id:"${id}"}) / JobStatus, stop with JobKill.`;
|
|
963
1218
|
}
|
|
1219
|
+
function realPathOf(fs, path) {
|
|
1220
|
+
if (typeof fs.realPath !== "function") return null;
|
|
1221
|
+
try {
|
|
1222
|
+
return fs.realPath(path);
|
|
1223
|
+
} catch (e) {
|
|
1224
|
+
log4.debug(`realPath unavailable for ${path} \u2014 inlining image bytes instead`, e);
|
|
1225
|
+
return null;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
function isBinary(raw) {
|
|
1229
|
+
if (raw === "") return false;
|
|
1230
|
+
const head = raw.length > 8192 ? raw.slice(0, 8192) : raw;
|
|
1231
|
+
let repl2 = 0;
|
|
1232
|
+
for (let i = 0; i < head.length; i++) {
|
|
1233
|
+
const c = head.charCodeAt(i);
|
|
1234
|
+
if (c === 0) return true;
|
|
1235
|
+
if (c === 65533) repl2++;
|
|
1236
|
+
}
|
|
1237
|
+
return repl2 / head.length > 0.1;
|
|
1238
|
+
}
|
|
1239
|
+
async function byteSize(fs, path, raw) {
|
|
1240
|
+
const bfs = fs;
|
|
1241
|
+
if (typeof bfs.readFileBytes === "function") {
|
|
1242
|
+
try {
|
|
1243
|
+
return (await bfs.readFileBytes(path)).length;
|
|
1244
|
+
} catch {
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
return Buffer.byteLength(raw, "utf8");
|
|
1248
|
+
}
|
|
964
1249
|
function exitSessionTool(onExit) {
|
|
965
1250
|
return {
|
|
966
1251
|
name: "ExitSession",
|
|
@@ -987,15 +1272,18 @@ function toolsByName(names) {
|
|
|
987
1272
|
return t;
|
|
988
1273
|
});
|
|
989
1274
|
}
|
|
990
|
-
var numberLines, bashTool, IMG_MIME, readTool, editTool;
|
|
1275
|
+
var log4, numberLines, bashTool, IMG_MIME, readTool, editTool;
|
|
991
1276
|
var init_tools = __esm({
|
|
992
1277
|
"src/tools.ts"() {
|
|
993
1278
|
"use strict";
|
|
1279
|
+
init_llm();
|
|
1280
|
+
init_logging();
|
|
994
1281
|
init_tools_structured();
|
|
995
1282
|
init_todo();
|
|
996
1283
|
init_tools_web();
|
|
997
1284
|
init_OverlayFilesystem();
|
|
998
1285
|
init_redact();
|
|
1286
|
+
log4 = forComponent("tools");
|
|
999
1287
|
numberLines = (content, offset = 0, limit) => {
|
|
1000
1288
|
const lines = content.split("\n");
|
|
1001
1289
|
const start = Math.max(0, offset);
|
|
@@ -1050,12 +1338,22 @@ var init_tools = __esm({
|
|
|
1050
1338
|
if (typeof fs.readFileBytes !== "function") {
|
|
1051
1339
|
return `[${path} is an image, but this filesystem can't read binary \u2014 attach it as @${path} instead, or run on disk.]`;
|
|
1052
1340
|
}
|
|
1341
|
+
const real = realPathOf(fs, path);
|
|
1342
|
+
if (real) {
|
|
1343
|
+
if (!await ctx.fs.exists(path)) return `Error: File not found: ${path}`;
|
|
1344
|
+
return imageRefResult(real, IMG_MIME[ext]);
|
|
1345
|
+
}
|
|
1053
1346
|
const bytes = await fs.readFileBytes(path);
|
|
1054
1347
|
const b64 = Buffer.from(bytes).toString("base64");
|
|
1055
1348
|
return JSON.stringify({ dataUrl: `data:${IMG_MIME[ext]};base64,${b64}`, image: path });
|
|
1056
1349
|
}
|
|
1057
1350
|
const raw = await ctx.fs.readFile(path);
|
|
1351
|
+
if (isBinary(raw)) {
|
|
1352
|
+
const bytes = await byteSize(ctx.fs, path, raw);
|
|
1353
|
+
return `[binary file: ${path} \u2014 ${bytes} bytes, not shown as text. Read renders png/jpg/jpeg/gif/webp images directly; otherwise attach it as @${path}.]`;
|
|
1354
|
+
}
|
|
1058
1355
|
ctx.readState.set(ctx.fs.resolvePath(path), raw);
|
|
1356
|
+
if (raw === "") return `[empty file: ${path} \u2014 0 bytes]`;
|
|
1059
1357
|
const content = CONFIG_FILE_RE.test(ctx.fs.resolvePath(path)) ? redactSecrets(raw) : raw;
|
|
1060
1358
|
const total = content === "" ? 0 : content.split("\n").length;
|
|
1061
1359
|
const start = Math.max(0, offset ?? 0);
|
|
@@ -1123,7 +1421,7 @@ __export(worktree_exports, {
|
|
|
1123
1421
|
worktreeBranchName: () => worktreeBranchName
|
|
1124
1422
|
});
|
|
1125
1423
|
import { spawnSync } from "child_process";
|
|
1126
|
-
import { symlinkSync, mkdirSync, statSync as
|
|
1424
|
+
import { symlinkSync, mkdirSync, statSync as statSync3, readFileSync as readFileSync2, rmSync } from "fs";
|
|
1127
1425
|
import { join as join2 } from "path";
|
|
1128
1426
|
function validateWorktreeSlug(slug2) {
|
|
1129
1427
|
if (!slug2) throw new Error("worktree slug must not be empty");
|
|
@@ -1159,16 +1457,16 @@ function findGitRoot(from) {
|
|
|
1159
1457
|
}
|
|
1160
1458
|
function readWorktreeHead(worktreePath) {
|
|
1161
1459
|
try {
|
|
1162
|
-
const gitFile =
|
|
1460
|
+
const gitFile = readFileSync2(join2(worktreePath, ".git"), "utf-8").trim();
|
|
1163
1461
|
const m = gitFile.match(/^gitdir:\s*(.+)$/);
|
|
1164
1462
|
if (!m) return null;
|
|
1165
1463
|
const headPath = join2(m[1], "HEAD");
|
|
1166
|
-
const head =
|
|
1464
|
+
const head = readFileSync2(headPath, "utf-8").trim();
|
|
1167
1465
|
const refMatch = head.match(/^ref:\s*(.+)$/);
|
|
1168
1466
|
if (!refMatch) return head.length === 40 ? head : null;
|
|
1169
1467
|
const refPath = join2(m[1], "..", refMatch[1]);
|
|
1170
1468
|
try {
|
|
1171
|
-
return
|
|
1469
|
+
return readFileSync2(refPath, "utf-8").trim();
|
|
1172
1470
|
} catch {
|
|
1173
1471
|
}
|
|
1174
1472
|
const r = git(["rev-parse", "HEAD"], worktreePath);
|
|
@@ -1204,7 +1502,7 @@ function symlinkDirs(repoRoot, worktreePath, dirs) {
|
|
|
1204
1502
|
const src = join2(repoRoot, dir);
|
|
1205
1503
|
const dst = join2(worktreePath, dir);
|
|
1206
1504
|
try {
|
|
1207
|
-
|
|
1505
|
+
statSync3(src);
|
|
1208
1506
|
} catch {
|
|
1209
1507
|
continue;
|
|
1210
1508
|
}
|
|
@@ -1220,7 +1518,7 @@ function cleanupWorktree(repoRoot, slug2, opts) {
|
|
|
1220
1518
|
const worktreePath = worktreePathFor(repoRoot, slug2);
|
|
1221
1519
|
const branch = worktreeBranchName(slug2);
|
|
1222
1520
|
try {
|
|
1223
|
-
|
|
1521
|
+
statSync3(worktreePath);
|
|
1224
1522
|
} catch {
|
|
1225
1523
|
return { removed: false, reason: "not found" };
|
|
1226
1524
|
}
|
|
@@ -1355,6 +1653,11 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
1355
1653
|
throw new Error(`File not found: ${path}`);
|
|
1356
1654
|
}
|
|
1357
1655
|
}
|
|
1656
|
+
/** The REAL on-disk path behind a VFS path — an optional capability (not on IFilesystem) the Read
|
|
1657
|
+
* tool duck-types so it can persist an image REFERENCE instead of megabytes of base64. */
|
|
1658
|
+
realPath(path) {
|
|
1659
|
+
return this.real(this.resolvePath(path));
|
|
1660
|
+
}
|
|
1358
1661
|
/** Read raw bytes (for binary files like images). Not on the base IFilesystem (which is utf8-only) — an
|
|
1359
1662
|
* optional capability the Read tool duck-types to return image blocks. Same symlink/jail guards as readFile. */
|
|
1360
1663
|
async readFileBytes(path) {
|
|
@@ -1537,6 +1840,12 @@ var init_JailedFilesystem = __esm({
|
|
|
1537
1840
|
if (typeof inner.readFileBytes !== "function") throw new Error("binary read not supported by this filesystem");
|
|
1538
1841
|
return inner.readFileBytes(this.guardRead("readFileBytes", path));
|
|
1539
1842
|
}
|
|
1843
|
+
/** Forward the optional real-path capability (image REFS), jail-guarded like readFile. */
|
|
1844
|
+
realPath(path) {
|
|
1845
|
+
const inner = this.inner;
|
|
1846
|
+
if (typeof inner.realPath !== "function") throw new Error("realPath not supported by this filesystem");
|
|
1847
|
+
return inner.realPath(this.guardRead("realPath", path));
|
|
1848
|
+
}
|
|
1540
1849
|
async writeFile(path, content) {
|
|
1541
1850
|
return this.inner.writeFile(this.guardWrite("writeFile", path), content);
|
|
1542
1851
|
}
|
|
@@ -1777,13 +2086,13 @@ function makeRealShellTool(options) {
|
|
|
1777
2086
|
}
|
|
1778
2087
|
};
|
|
1779
2088
|
try {
|
|
1780
|
-
return await new Promise((
|
|
2089
|
+
return await new Promise((resolve5) => {
|
|
1781
2090
|
let out = "";
|
|
1782
2091
|
let settled = false;
|
|
1783
2092
|
const finish = (s) => {
|
|
1784
2093
|
if (settled) return;
|
|
1785
2094
|
settled = true;
|
|
1786
|
-
|
|
2095
|
+
resolve5(s);
|
|
1787
2096
|
};
|
|
1788
2097
|
let proc;
|
|
1789
2098
|
try {
|
|
@@ -1806,7 +2115,7 @@ function makeRealShellTool(options) {
|
|
|
1806
2115
|
proc.stderr?.on("data", collect);
|
|
1807
2116
|
proc.on("error", (err2) => {
|
|
1808
2117
|
if (err2?.name === "AbortError" || ctl.signal.aborted) return finish(reasonFor(timedOut, timeoutMs, clean(out)));
|
|
1809
|
-
|
|
2118
|
+
log16.debug("shell spawn error", err2);
|
|
1810
2119
|
finish(`[exit 1] ${err2?.message ?? err2}${out ? "\n" + clean(out) : ""}`);
|
|
1811
2120
|
});
|
|
1812
2121
|
proc.on("close", (code) => {
|
|
@@ -1868,7 +2177,7 @@ ${clean(out) || "(no output yet)"}`;
|
|
|
1868
2177
|
}
|
|
1869
2178
|
];
|
|
1870
2179
|
}
|
|
1871
|
-
var
|
|
2180
|
+
var log16, clean, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, NO_JOB2;
|
|
1872
2181
|
var init_tools_shell = __esm({
|
|
1873
2182
|
"src/tools.shell.ts"() {
|
|
1874
2183
|
"use strict";
|
|
@@ -1876,7 +2185,7 @@ var init_tools_shell = __esm({
|
|
|
1876
2185
|
init_redact();
|
|
1877
2186
|
init_logging();
|
|
1878
2187
|
init_shell_sandbox();
|
|
1879
|
-
|
|
2188
|
+
log16 = forComponent("shell");
|
|
1880
2189
|
clean = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
|
|
1881
2190
|
DETACHED = { stdio: ["ignore", "pipe", "pipe"], detached: true };
|
|
1882
2191
|
SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
|
|
@@ -1958,8 +2267,8 @@ var init_tools_shell = __esm({
|
|
|
1958
2267
|
|
|
1959
2268
|
// cli/cli.ts
|
|
1960
2269
|
import { createInterface } from "readline/promises";
|
|
1961
|
-
import { existsSync as existsSync9, readFileSync as
|
|
1962
|
-
import { homedir as homedir10
|
|
2270
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9, appendFileSync, mkdirSync as mkdirSync12, writeFileSync as writeFileSync9, readdirSync as readdirSync4, statSync as statSync5, unlinkSync as unlinkSync5 } from "fs";
|
|
2271
|
+
import { homedir as homedir10 } from "os";
|
|
1963
2272
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
1964
2273
|
|
|
1965
2274
|
// cli/clipboard.ts
|
|
@@ -2027,21 +2336,11 @@ function copyTextToClipboard(text, platform2 = process.platform) {
|
|
|
2027
2336
|
}
|
|
2028
2337
|
|
|
2029
2338
|
// cli/cli.ts
|
|
2030
|
-
import { join as join14, resolve as
|
|
2339
|
+
import { join as join14, resolve as resolve4, basename as basename2, extname, dirname as dirname5 } from "path";
|
|
2031
2340
|
import { AIClient, listModels, listProviders, getProviderFromModel, getModelInfo, resolveModel, isModelSupported, disposeCursorSessions, disposeClaudeCodeSessions } from "ai.libx.js";
|
|
2032
2341
|
|
|
2033
|
-
// src/llm.ts
|
|
2034
|
-
function contentText(content) {
|
|
2035
|
-
if (content == null) return "";
|
|
2036
|
-
if (typeof content === "string") return content;
|
|
2037
|
-
return content.map((p) => p.type === "text" ? p.text ?? "" : "[image]").join(p_sep(content));
|
|
2038
|
-
}
|
|
2039
|
-
var p_sep = (parts) => parts.length > 1 ? "\n" : "";
|
|
2040
|
-
function imagePart(url) {
|
|
2041
|
-
return { type: "image_url", image_url: { url } };
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
2342
|
// src/Agent.ts
|
|
2343
|
+
init_llm();
|
|
2045
2344
|
init_tools();
|
|
2046
2345
|
|
|
2047
2346
|
// src/tools.jobs.ts
|
|
@@ -2680,7 +2979,7 @@ init_OverlayFilesystem();
|
|
|
2680
2979
|
init_worktree();
|
|
2681
2980
|
init_NodeDiskFilesystem();
|
|
2682
2981
|
init_logging();
|
|
2683
|
-
var
|
|
2982
|
+
var log5 = forComponent("subagent");
|
|
2684
2983
|
async function boundedPool(items, limit, fn) {
|
|
2685
2984
|
const out = new Array(items.length);
|
|
2686
2985
|
let next = 0;
|
|
@@ -2722,7 +3021,7 @@ function traceFileFor(dir, label, depth) {
|
|
|
2722
3021
|
const id = `${Date.now().toString(36)}-${(traceSeq++).toString(36)}`;
|
|
2723
3022
|
return join4(dir, `${id}-d${depth}-${sanitizeSlug(label) || "sub"}.json`);
|
|
2724
3023
|
} catch (e) {
|
|
2725
|
-
|
|
3024
|
+
log5.warn(`subagent trace dir unavailable (${dir}) \u2014 running without a trace: ${e instanceof Error ? e.message : String(e)}`);
|
|
2726
3025
|
return void 0;
|
|
2727
3026
|
}
|
|
2728
3027
|
}
|
|
@@ -2730,7 +3029,7 @@ function flushTrace(file, meta, messages, status) {
|
|
|
2730
3029
|
try {
|
|
2731
3030
|
writeFileSync2(file, JSON.stringify({ ...meta, status, updated: Date.now(), messages }, null, 2));
|
|
2732
3031
|
} catch (e) {
|
|
2733
|
-
|
|
3032
|
+
log5.warn(`subagent trace flush failed (${file}): ${e instanceof Error ? e.message : String(e)}`);
|
|
2734
3033
|
}
|
|
2735
3034
|
}
|
|
2736
3035
|
function installTrace(child, file, meta) {
|
|
@@ -3191,7 +3490,11 @@ function reasoningToChatFragment(model, effort) {
|
|
|
3191
3490
|
}
|
|
3192
3491
|
|
|
3193
3492
|
// src/Agent.ts
|
|
3194
|
-
var
|
|
3493
|
+
var log6 = forComponent("Agent");
|
|
3494
|
+
function envInt(key, fallback) {
|
|
3495
|
+
const n = typeof process !== "undefined" ? Number(process.env?.[key]) : NaN;
|
|
3496
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
3497
|
+
}
|
|
3195
3498
|
function isBrowserTool(name) {
|
|
3196
3499
|
return name.startsWith("mcp__") && /browser/i.test(name.slice(5).split("__")[0]);
|
|
3197
3500
|
}
|
|
@@ -3217,8 +3520,9 @@ var AgentOptions = class {
|
|
|
3217
3520
|
/** Hard ceiling on accumulated tokens (prompt+completion) across the run; cache READS count at 0.1×
|
|
3218
3521
|
* (their real price) so a fat cached context doesn't trip the guard on healthy turns. 0 = unbounded. */
|
|
3219
3522
|
maxTokens = 2e5;
|
|
3220
|
-
/**
|
|
3221
|
-
|
|
3523
|
+
/** Idle ceiling in ms: max time since the last productive step before the run is killed (a long/active
|
|
3524
|
+
* step resets it — see runLoop's `lastProgressAt`). Not a whole-run wall clock. 0 = unbounded. */
|
|
3525
|
+
timeoutMs = 3e5;
|
|
3222
3526
|
/** Idle-stall watchdog for a SINGLE streamed model request: abort it if no chunk (text OR reasoning)
|
|
3223
3527
|
* arrives for this long. The between-steps `timeoutMs` can't preempt an in-flight `chat()`, so a
|
|
3224
3528
|
* provider that goes silent mid-stream would otherwise park the loop forever. 0 = off. */
|
|
@@ -3229,15 +3533,33 @@ var AgentOptions = class {
|
|
|
3229
3533
|
maxToolCalls = 100;
|
|
3230
3534
|
/** External cancellation — abort the loop between steps (e.g. a UI "Cancel" button). */
|
|
3231
3535
|
signal;
|
|
3536
|
+
/** Retry budget for an EMPTY/silent-turn (a delegated-runtime wedge: cursor's warm helper spins then
|
|
3537
|
+
* returns a content-less end, surfaced as `code:'empty'`/`code:'cursor_silent'`). Tuned WIDER than the
|
|
3538
|
+
* network-drop budget (fixed 2) because clearing a wedge needs several reap+respawn cycles — each retry
|
|
3539
|
+
* hits a FRESH cursor session (the adapter evicts the wedged warm helper on the silent-end). Still bounded:
|
|
3540
|
+
* after exhaustion the turn surfaces `finishReason:'empty'` (non-zero exit), never infinite, never silent.
|
|
3541
|
+
* Env: AGENTX_EMPTY_RETRIES. */
|
|
3542
|
+
emptyRetries = envInt("AGENTX_EMPTY_RETRIES", 4);
|
|
3543
|
+
/** Base backoff (ms) between empty-turn retries; grows linearly per attempt (longer than the network
|
|
3544
|
+
* class's fixed 1s so a wedged helper has time to be reaped/respawned). Env: AGENTX_EMPTY_RETRY_BASE_MS. */
|
|
3545
|
+
emptyRetryBaseMs = envInt("AGENTX_EMPTY_RETRY_BASE_MS", 1500);
|
|
3232
3546
|
/** 0 = never trim. Otherwise cap the messages sent per turn (system + most recent). */
|
|
3233
3547
|
maxContextMessages = 0;
|
|
3234
3548
|
/** Note-taking: keep the most-recent N tool-result outputs verbatim; collapse OLDER ones to a one-line
|
|
3235
3549
|
* stub in the sent context (the model already consumed them — it can re-Read/re-run). The stored
|
|
3236
3550
|
* transcript is never mutated. 0 = keep all verbatim. */
|
|
3237
3551
|
keepToolOutputs = 0;
|
|
3552
|
+
/** How many of the most-recent image-bearing messages get their image REF expanded to real base64 in
|
|
3553
|
+
* the outgoing request. Older ones (and legacy inline base64) go on the wire as the elision stub. */
|
|
3554
|
+
keepRecentImages = 1;
|
|
3238
3555
|
/** Token-aware backstop (~4 chars/token estimate). After note-taking, drop oldest messages from the
|
|
3239
3556
|
* sent context until the estimate is under this ceiling (pairing-safe). 0 = off. */
|
|
3240
3557
|
maxContextTokens = 0;
|
|
3558
|
+
/** The MODEL's context window (tokens) — what the context % is measured against and what `/compact`
|
|
3559
|
+
* targets 50% of. Filled by the CLI from model metadata; 0 = fall back to 200k. Deliberately NOT
|
|
3560
|
+
* `maxTokens`: that is the cumulative SPEND kill-switch, and a spend budget must never be able to
|
|
3561
|
+
* drive destructive compaction (`--max-tokens 50000` would otherwise shred the transcript). */
|
|
3562
|
+
contextWindow = 0;
|
|
3241
3563
|
/** Pagination ceiling for a SINGLE tool result (bytes). A result over this is cropped to page 1 with
|
|
3242
3564
|
* a marker telling the model it was cropped (refine the query, or page further). Guards against one
|
|
3243
3565
|
* Grep/Read/MCP call blowing the whole context window. 0 = off. Default 60k (~15k tokens). */
|
|
@@ -3481,7 +3803,7 @@ var Agent = class _Agent {
|
|
|
3481
3803
|
const disk = new NodeDiskFilesystem2(process.cwd());
|
|
3482
3804
|
await disk.init();
|
|
3483
3805
|
this.options.fs = new JailedFilesystem2(disk);
|
|
3484
|
-
|
|
3806
|
+
log6.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
|
|
3485
3807
|
}
|
|
3486
3808
|
this.buildCtx();
|
|
3487
3809
|
}
|
|
@@ -3599,19 +3921,42 @@ var Agent = class _Agent {
|
|
|
3599
3921
|
return this.runLoop();
|
|
3600
3922
|
}
|
|
3601
3923
|
/**
|
|
3602
|
-
* Fold the conversation in place (manual `/compact`)
|
|
3603
|
-
*
|
|
3604
|
-
*
|
|
3605
|
-
*
|
|
3606
|
-
*
|
|
3924
|
+
* Fold the conversation in place (manual `/compact`) until it fits a TOKEN budget — not a message
|
|
3925
|
+
* count. Message-count folding alone frees almost nothing when a few recent tool results are huge,
|
|
3926
|
+
* so we escalate: fold the middle into a synthetic recap → elide oversized tool-result bodies in the
|
|
3927
|
+
* retained window → halve the window → drop-oldest backstop. Deterministic, no LLM call.
|
|
3928
|
+
* `focus` (e.g. `/compact keep the API details`) preserves matching lines from the dropped span verbatim.
|
|
3929
|
+
* Returns before/after token estimates so the caller can report honestly (0 freed => say so).
|
|
3607
3930
|
*/
|
|
3608
3931
|
compactNow(maxMessages = 12, focus) {
|
|
3609
|
-
const
|
|
3610
|
-
|
|
3932
|
+
const cap = _Agent.contextCap(this.options);
|
|
3933
|
+
const target = Math.round(cap * _Agent.COMPACT_TARGET);
|
|
3934
|
+
const kr = this.options.keepRecentImages ?? 1;
|
|
3935
|
+
const tokensBefore = estimateTokens(this.transcript, kr);
|
|
3936
|
+
const msgsBefore = this.transcript.length;
|
|
3937
|
+
let max = Math.max(2, maxMessages);
|
|
3938
|
+
if (this.transcript.length <= max && tokensBefore <= target)
|
|
3939
|
+
return { tokensBefore, tokensAfter: tokensBefore, target, cap, removed: 0 };
|
|
3611
3940
|
void this.activeHooks?.onPreCompact?.(this.transcript);
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3941
|
+
let out = this.transcript;
|
|
3942
|
+
if (out.length > max) out = compact(out, max, focus);
|
|
3943
|
+
if (estimateTokens(out, kr) > target) out = elideOversizedImages(out, target, true, kr);
|
|
3944
|
+
if (estimateTokens(out, kr) > target) out = capOversizedBodies(out, target, kr);
|
|
3945
|
+
if (estimateTokens(out, kr) > target) out = elideOversizedImages(out, target, false, kr);
|
|
3946
|
+
while (estimateTokens(out, kr) > target && max > 2 && out.length > 2) {
|
|
3947
|
+
max = Math.max(2, Math.floor(max / 2));
|
|
3948
|
+
out = capOversizedBodies(compact(out, max, focus), target, kr);
|
|
3949
|
+
}
|
|
3950
|
+
if (estimateTokens(out, kr) > target) out = fitTokenBudget(out, target, kr);
|
|
3951
|
+
out = dropOrphanToolResults(out);
|
|
3952
|
+
this.transcript = out;
|
|
3953
|
+
this.releaseStaleImages();
|
|
3954
|
+
return { tokensBefore, tokensAfter: estimateTokens(this.transcript, kr), target, cap, removed: msgsBefore - this.transcript.length };
|
|
3955
|
+
}
|
|
3956
|
+
/** Drop inline base64 images from older messages so Bun/JSC can reclaim the heap (see contentBytes). */
|
|
3957
|
+
releaseStaleImages(keepRecent = 1) {
|
|
3958
|
+
const next = elideStaleImages(this.transcript, keepRecent);
|
|
3959
|
+
if (next !== this.transcript) this.transcript = next;
|
|
3615
3960
|
}
|
|
3616
3961
|
async runLoop() {
|
|
3617
3962
|
const o = this.options;
|
|
@@ -3627,15 +3972,16 @@ var Agent = class _Agent {
|
|
|
3627
3972
|
let lastFp = "";
|
|
3628
3973
|
let repeats = 0;
|
|
3629
3974
|
let closureNudged = false;
|
|
3975
|
+
let lastProgressAt = start;
|
|
3630
3976
|
const kill = (finishReason) => {
|
|
3631
|
-
|
|
3977
|
+
log6.warn(`kill-switch: ${finishReason} (steps=${steps}, tokens=${usage.totalTokens}, budgetTokens=${Math.round(usage.totalTokens - 0.9 * usage.cacheReadTokens)}, ms=${Date.now() - start - this.parkedMs}${finishReason === "timeout" ? `, idle=${Date.now() - lastProgressAt - this.parkedMs}ms` : ""}${this.parkedMs ? ` +${this.parkedMs} parked` : ""})`);
|
|
3632
3978
|
this.ctx.jobs?.killAll();
|
|
3633
3979
|
return { text: lastAssistantText(this.transcript), steps, finishReason, messages: this.transcript, usage, usageEstimated };
|
|
3634
3980
|
};
|
|
3635
3981
|
while (true) {
|
|
3636
3982
|
if (o.signal?.aborted) return kill("aborted");
|
|
3637
3983
|
if (steps >= o.maxSteps) return kill("max_steps");
|
|
3638
|
-
if (o.timeoutMs && Date.now() -
|
|
3984
|
+
if (o.timeoutMs && Date.now() - lastProgressAt - this.parkedMs >= o.timeoutMs) return kill("timeout");
|
|
3639
3985
|
const budgetTokens = usage.totalTokens - 0.9 * usage.cacheReadTokens;
|
|
3640
3986
|
if (o.maxTokens && budgetTokens >= o.maxTokens) return kill("budget");
|
|
3641
3987
|
steps++;
|
|
@@ -3675,27 +4021,38 @@ var Agent = class _Agent {
|
|
|
3675
4021
|
const r = await o.ai.chat({ model: o.model, messages: sent, tools: sentTools, stream: false, signal: o.signal, ...o.toolChoice ? { toolChoice: o.toolChoice } : {}, ...reasonOpts });
|
|
3676
4022
|
res = r;
|
|
3677
4023
|
}
|
|
4024
|
+
if (steps === 1 && this.isEmptyStop(res) && !this.pendingInjections.length) {
|
|
4025
|
+
const e = new Error("model returned an empty turn (finishReason=stop, no text, no tools)");
|
|
4026
|
+
e.code = "empty";
|
|
4027
|
+
throw e;
|
|
4028
|
+
}
|
|
3678
4029
|
break;
|
|
3679
4030
|
} catch (err2) {
|
|
3680
4031
|
const sc = err2?.statusCode;
|
|
3681
4032
|
const serverSide = sc >= 500 || sc === 408 || /Service Unavailable|overloaded|Internal server error|Bad gateway|Gateway time/i.test(String(err2?.message ?? ""));
|
|
3682
4033
|
const network = !sc && /ECONNRESET|ECONNREFUSED|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|EPIPE|socket hang up|fetch failed|network|terminated|UND_ERR/i.test(String(err2?.message ?? err2?.code ?? err2));
|
|
3683
4034
|
const stalled = err2?.code === "stall";
|
|
3684
|
-
const
|
|
4035
|
+
const empty = err2?.code === "empty" || err2?.code === "cursor_silent";
|
|
4036
|
+
const maxAttempts = empty ? Math.max(o.emptyRetries, 0) : 2;
|
|
4037
|
+
const transient = !o.signal?.aborted && !isAbortError(err2) && attempt < maxAttempts && (network || serverSide || stalled || empty);
|
|
3685
4038
|
if (!transient) throw err2;
|
|
3686
|
-
const waitMs = 1e3 * (attempt + 1);
|
|
3687
|
-
|
|
3688
|
-
o.host?.notify?.({ kind: "retry", message: `${stalled ? "model stalled" : "connection dropped"} \u2014 retrying step (#${attempt + 1})` });
|
|
4039
|
+
const waitMs = (empty ? o.emptyRetryBaseMs : 1e3) * (attempt + 1);
|
|
4040
|
+
log6.warn(`${empty ? "empty model turn" : stalled ? "model stall" : "network drop"} mid-step (${err2?.message ?? err2}) \u2014 retrying in ${waitMs}ms`);
|
|
4041
|
+
o.host?.notify?.({ kind: "retry", message: `${empty ? "model returned nothing" : stalled ? "model stalled" : "connection dropped"} \u2014 retrying step (#${attempt + 1})` });
|
|
3689
4042
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
3690
4043
|
}
|
|
3691
4044
|
}
|
|
3692
4045
|
} catch (err2) {
|
|
3693
4046
|
if (err2?.code === "budget") return kill("budget");
|
|
3694
4047
|
if (err2?.code === "stall") {
|
|
3695
|
-
|
|
4048
|
+
log6.warn(`model stalled out after retries: ${err2?.message ?? err2}`);
|
|
3696
4049
|
return kill("timeout");
|
|
3697
4050
|
}
|
|
3698
4051
|
if (o.signal?.aborted || isAbortError(err2)) return kill("aborted");
|
|
4052
|
+
if (err2?.code === "empty" || err2?.code === "cursor_silent") {
|
|
4053
|
+
log6.warn(`model returned empty/silent turns after retries \u2014 surfacing as failed`);
|
|
4054
|
+
return { text: "", steps, finishReason: "empty", messages: this.transcript, usage, usageEstimated, error: err2 };
|
|
4055
|
+
}
|
|
3699
4056
|
const body = err2?.body ?? err2?.response?.data ?? err2?.error;
|
|
3700
4057
|
let bodyStr;
|
|
3701
4058
|
try {
|
|
@@ -3705,7 +4062,7 @@ var Agent = class _Agent {
|
|
|
3705
4062
|
}
|
|
3706
4063
|
if (bodyStr && err2 instanceof Error && !err2.message.includes(bodyStr)) err2.detail = bodyStr;
|
|
3707
4064
|
const errLog = err2 instanceof Error ? { message: err2.message, ...err2.detail ? { detail: err2.detail } : {}, stack: err2.stack } : err2;
|
|
3708
|
-
|
|
4065
|
+
log6.error(`chat() failed: ${err2?.message ?? err2}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, errLog);
|
|
3709
4066
|
return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err2 };
|
|
3710
4067
|
}
|
|
3711
4068
|
if (o.signal?.aborted) return kill("aborted");
|
|
@@ -3749,6 +4106,7 @@ var Agent = class _Agent {
|
|
|
3749
4106
|
}
|
|
3750
4107
|
const toolCalls = res.toolCalls ?? [];
|
|
3751
4108
|
const emptyTurn = toolCalls.length === 0 && delegatedTools.length === 0 && contentText(res.content ?? "").trim() === "";
|
|
4109
|
+
if (!emptyTurn) lastProgressAt = Date.now();
|
|
3752
4110
|
if (!emptyTurn) {
|
|
3753
4111
|
this.transcript.push({
|
|
3754
4112
|
role: "assistant",
|
|
@@ -3760,12 +4118,13 @@ var Agent = class _Agent {
|
|
|
3760
4118
|
if (this.drainInjections()) continue;
|
|
3761
4119
|
if (o.closeDelegatedTurns && res.endedOnToolNoText && !closureNudged && steps < o.maxSteps) {
|
|
3762
4120
|
closureNudged = true;
|
|
3763
|
-
|
|
4121
|
+
log6.verbose("delegated turn went silent after a tool \u2014 nudging a closing summary");
|
|
3764
4122
|
this.transcript.push({ role: "user", content: "Summarize what you did this turn and what (if anything) still needs doing. Be concise." });
|
|
3765
4123
|
continue;
|
|
3766
4124
|
}
|
|
3767
|
-
|
|
4125
|
+
log6.verbose(`completed in ${steps} step(s)`);
|
|
3768
4126
|
await this.ctx.jobs?.drain();
|
|
4127
|
+
this.releaseStaleImages();
|
|
3769
4128
|
await this.activeHooks?.onStop?.(res.content ?? "");
|
|
3770
4129
|
return { text: res.content ?? "", steps, finishReason: "stop", messages: this.transcript, usage, usageEstimated };
|
|
3771
4130
|
}
|
|
@@ -3791,7 +4150,7 @@ var Agent = class _Agent {
|
|
|
3791
4150
|
if (this.ctx.needsInput) {
|
|
3792
4151
|
const question = this.ctx.needsInput;
|
|
3793
4152
|
this.ctx.needsInput = void 0;
|
|
3794
|
-
|
|
4153
|
+
log6.warn(`needs_input: ${question.question} (steps=${steps})`);
|
|
3795
4154
|
await this.ctx.jobs?.drain();
|
|
3796
4155
|
const text = [
|
|
3797
4156
|
`OPEN QUESTION (run paused \u2014 no human was reachable): ${question.question}`,
|
|
@@ -3848,6 +4207,12 @@ var Agent = class _Agent {
|
|
|
3848
4207
|
}
|
|
3849
4208
|
};
|
|
3850
4209
|
}
|
|
4210
|
+
/** A clean-stop turn that carries no assistant text AND no tool calls (native or delegated) — a
|
|
4211
|
+
* non-result the model produced by going silent. Callers retry it rather than report "done". */
|
|
4212
|
+
isEmptyStop(res) {
|
|
4213
|
+
const stop = res.finishReason === "stop" || res.finishReason == null;
|
|
4214
|
+
return stop && (res.toolCalls?.length ?? 0) === 0 && (res.delegatedTools?.length ?? 0) === 0 && contentText(res.content ?? "").trim() === "";
|
|
4215
|
+
}
|
|
3851
4216
|
async consumeStream(stream, poke) {
|
|
3852
4217
|
let content = "";
|
|
3853
4218
|
let finishReason;
|
|
@@ -3906,13 +4271,13 @@ var Agent = class _Agent {
|
|
|
3906
4271
|
const decision = await this.park(Promise.resolve(hooks?.preToolUse?.(call, meta)));
|
|
3907
4272
|
if (decision?.block) {
|
|
3908
4273
|
const blocked = `Blocked by hook: ${decision.reason ?? "no reason given"}`;
|
|
3909
|
-
|
|
4274
|
+
log6.debug(`${tc.function.name} -> ${blocked}`);
|
|
3910
4275
|
await hooks?.postToolUse?.(call, blocked, meta);
|
|
3911
4276
|
return blocked;
|
|
3912
4277
|
}
|
|
3913
4278
|
this.options.host?.notify?.({ kind: "tool_use", id: tc.id ?? "", name: tc.function.name, input: args });
|
|
3914
4279
|
if (earlyError) {
|
|
3915
|
-
|
|
4280
|
+
log6.debug(`${tc.function.name} -> ${earlyError}`);
|
|
3916
4281
|
await hooks?.postToolUse?.(call, earlyError, meta);
|
|
3917
4282
|
this.options.host?.notify?.({ kind: "tool_result", id: tc.id ?? "", output: earlyError, isError: true });
|
|
3918
4283
|
return earlyError;
|
|
@@ -3921,12 +4286,12 @@ var Agent = class _Agent {
|
|
|
3921
4286
|
let images;
|
|
3922
4287
|
let threw = false;
|
|
3923
4288
|
try {
|
|
3924
|
-
|
|
4289
|
+
log6.debug(`${tc.function.name}(${tc.function.arguments})`);
|
|
3925
4290
|
this.ctx.emit = hooks?.onToolOutput ? (chunk) => {
|
|
3926
4291
|
try {
|
|
3927
4292
|
hooks.onToolOutput(call, chunk, meta);
|
|
3928
4293
|
} catch (e) {
|
|
3929
|
-
|
|
4294
|
+
log6.debug(`onToolOutput hook error: ${e}`);
|
|
3930
4295
|
}
|
|
3931
4296
|
} : void 0;
|
|
3932
4297
|
const raw = await tool.run(args, this.ctx);
|
|
@@ -3938,7 +4303,7 @@ var Agent = class _Agent {
|
|
|
3938
4303
|
}
|
|
3939
4304
|
} catch (e) {
|
|
3940
4305
|
const msg = e instanceof Error ? e.message : String(e);
|
|
3941
|
-
|
|
4306
|
+
log6.debug(`${tc.function.name} -> error: ${msg}`);
|
|
3942
4307
|
result = `Error: ${msg}`;
|
|
3943
4308
|
threw = true;
|
|
3944
4309
|
} finally {
|
|
@@ -3964,6 +4329,16 @@ var Agent = class _Agent {
|
|
|
3964
4329
|
}
|
|
3965
4330
|
return images?.length ? { text: result, images } : result;
|
|
3966
4331
|
}
|
|
4332
|
+
/** `/compact` aims to land the transcript at this fraction of the context window. */
|
|
4333
|
+
static COMPACT_TARGET = 0.5;
|
|
4334
|
+
/** THE context-window cap resolution — `/compact`'s budget and the CLI's context footer/warnings both
|
|
4335
|
+
* route here, so the percentage the user reads and the one compaction targets can never drift. */
|
|
4336
|
+
static contextCap(o) {
|
|
4337
|
+
if (o.maxContextTokens) return o.maxContextTokens;
|
|
4338
|
+
const w = o.contextWindow || 0;
|
|
4339
|
+
if (w > ANTHROPIC_WINDOW_WITHOUT_1M_BETA && isAnthropicModel(o.model)) return ANTHROPIC_WINDOW_WITHOUT_1M_BETA;
|
|
4340
|
+
return w || 2e5;
|
|
4341
|
+
}
|
|
3967
4342
|
static WRITE_CLASS = ["Write", "Edit", "MultiEdit", "ApplyEdits"];
|
|
3968
4343
|
/** Append an autoTest failure section to a write-class tool result, if configured. */
|
|
3969
4344
|
async maybeAutoTest(toolName, result) {
|
|
@@ -3997,26 +4372,27 @@ ${out}`;
|
|
|
3997
4372
|
const browserKeep = this.browserActive ? o.browserTrim?.keepOutputs : void 0;
|
|
3998
4373
|
const keep = browserKeep != null ? o.keepToolOutputs ? Math.min(o.keepToolOutputs, browserKeep) : browserKeep : o.keepToolOutputs;
|
|
3999
4374
|
if (keep) out = stubOldToolResults(out ?? m, keep);
|
|
4375
|
+
out = expandImagesForSend(dropOrphanToolResults(out ?? m), o.keepRecentImages ?? 1);
|
|
4000
4376
|
if (o.maxContextTokens) {
|
|
4001
|
-
const pre =
|
|
4002
|
-
out = fitTokenBudget(out
|
|
4377
|
+
const pre = out.length;
|
|
4378
|
+
out = fitTokenBudget(out, o.maxContextTokens, o.keepRecentImages ?? 1);
|
|
4003
4379
|
const dropped = pre - out.length;
|
|
4004
4380
|
if (dropped > 0 && dropped !== this.lastTrimNotified) {
|
|
4005
4381
|
this.lastTrimNotified = dropped;
|
|
4006
4382
|
o.host?.notify?.({ kind: "compaction", message: `context full \u2014 auto-trimmed ${dropped} oldest message(s) to fit ~${Math.round(o.maxContextTokens / 1e3)}k tokens` });
|
|
4007
4383
|
}
|
|
4008
4384
|
}
|
|
4009
|
-
return dropOrphanToolResults(out
|
|
4385
|
+
return dropOrphanToolResults(out);
|
|
4010
4386
|
}
|
|
4011
4387
|
};
|
|
4012
4388
|
function dropOldest(m, max) {
|
|
4013
4389
|
const head = m[0]?.role === "system" ? [m[0]] : [];
|
|
4014
4390
|
return [...head, ...m.slice(-(max - head.length))];
|
|
4015
4391
|
}
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
return Math.ceil(
|
|
4392
|
+
var ANTHROPIC_WINDOW_WITHOUT_1M_BETA = 2e5;
|
|
4393
|
+
var isAnthropicModel = (m) => !!m && (m.startsWith("anthropic/") || /(^|\/)claude-/.test(m));
|
|
4394
|
+
function estimateTokens(m, keepRecentImages = 1) {
|
|
4395
|
+
return Math.ceil(sendBytes(m, keepRecentImages) / 4);
|
|
4020
4396
|
}
|
|
4021
4397
|
function cropResult(result, cap) {
|
|
4022
4398
|
const head = result.slice(0, cap);
|
|
@@ -4050,6 +4426,60 @@ function stubOldToolResults(messages, keep) {
|
|
|
4050
4426
|
return { ...x, content: `[${x.name ?? "tool"}${where ? ` ${where}` : ""} output elided \u2014 ${lines} lines; re-run the tool to view]` };
|
|
4051
4427
|
});
|
|
4052
4428
|
}
|
|
4429
|
+
function elideOversizedImages(messages, target, protectNewest = false, keepRecentImages = 1) {
|
|
4430
|
+
let out = messages;
|
|
4431
|
+
const withImages = out.map((_, i) => i).filter((i) => messageHasImage(out[i].content));
|
|
4432
|
+
const newest = protectNewest ? withImages[withImages.length - 1] : -1;
|
|
4433
|
+
const weight = sendBytesPer(out, keepRecentImages);
|
|
4434
|
+
const heaviestFirst = withImages.filter((i) => i !== newest).sort((a, b) => weight[b] - weight[a]);
|
|
4435
|
+
for (const i of heaviestFirst) {
|
|
4436
|
+
if (estimateTokens(out, keepRecentImages) <= target) break;
|
|
4437
|
+
const c = elideImages(out[i].content);
|
|
4438
|
+
if (c === out[i].content) continue;
|
|
4439
|
+
out = out.map((m, j) => j === i ? { ...m, content: c } : m);
|
|
4440
|
+
}
|
|
4441
|
+
return out;
|
|
4442
|
+
}
|
|
4443
|
+
function capOversizedBodies(messages, target, keepRecentImages = 1) {
|
|
4444
|
+
let out = messages;
|
|
4445
|
+
for (const cap of [8e3, 4e3, 2e3, 800, 300, 120]) {
|
|
4446
|
+
if (estimateTokens(out, keepRecentImages) <= target) break;
|
|
4447
|
+
out = out.map((x) => x.role === "system" ? x : elideBody(x, cap));
|
|
4448
|
+
}
|
|
4449
|
+
return out;
|
|
4450
|
+
}
|
|
4451
|
+
function elideBody(x, cap) {
|
|
4452
|
+
const note = (bytes, imgs2) => `
|
|
4453
|
+
|
|
4454
|
+
[${bytes} bytes elided by /compact${x.role === "tool" ? " \u2014 re-run the tool to view" : ""}${imgs2 ? `; ${imgs2} image part(s) KEPT in full \u2014 they still cost context` : ""}]`;
|
|
4455
|
+
if (!Array.isArray(x.content)) {
|
|
4456
|
+
const text = contentText(x.content);
|
|
4457
|
+
if (text.length <= cap) return x;
|
|
4458
|
+
return { ...x, content: `${text.slice(0, cap)}${note(text.length - cap, 0)}` };
|
|
4459
|
+
}
|
|
4460
|
+
const imgs = x.content.filter((p) => p.type !== "text").length;
|
|
4461
|
+
let left = cap;
|
|
4462
|
+
let elided = 0;
|
|
4463
|
+
const parts = [];
|
|
4464
|
+
for (const p of x.content) {
|
|
4465
|
+
if (p.type !== "text") {
|
|
4466
|
+
parts.push(p);
|
|
4467
|
+
continue;
|
|
4468
|
+
}
|
|
4469
|
+
const t = p.text ?? "";
|
|
4470
|
+
if (t.length <= left) {
|
|
4471
|
+
left -= t.length;
|
|
4472
|
+
if (t) parts.push(p);
|
|
4473
|
+
continue;
|
|
4474
|
+
}
|
|
4475
|
+
const keep = left;
|
|
4476
|
+
left = 0;
|
|
4477
|
+
elided += t.length - keep;
|
|
4478
|
+
if (keep > 0) parts.push({ ...p, text: t.slice(0, keep) });
|
|
4479
|
+
}
|
|
4480
|
+
if (!elided) return parts.length === x.content.length || !parts.length ? x : { ...x, content: parts };
|
|
4481
|
+
return { ...x, content: [...parts, { type: "text", text: note(elided, imgs).trimStart() }] };
|
|
4482
|
+
}
|
|
4053
4483
|
var callIdSet = (msgs) => {
|
|
4054
4484
|
const ids = /* @__PURE__ */ new Set();
|
|
4055
4485
|
for (const m of msgs) if (m.role === "assistant") for (const tc of m.tool_calls ?? []) ids.add(tc.id);
|
|
@@ -4060,8 +4490,8 @@ function dropOrphanToolResults(messages) {
|
|
|
4060
4490
|
const ok = (m) => m.role !== "tool" || ids.has(m.tool_call_id ?? "");
|
|
4061
4491
|
return messages.every(ok) ? messages : messages.filter(ok);
|
|
4062
4492
|
}
|
|
4063
|
-
function fitTokenBudget(messages, maxTokens) {
|
|
4064
|
-
const per = messages.map((
|
|
4493
|
+
function fitTokenBudget(messages, maxTokens, keepRecentImages = 1) {
|
|
4494
|
+
const per = sendBytesPer(messages, keepRecentImages).map((b) => Math.ceil(b / 4));
|
|
4065
4495
|
let total = per.reduce((a, b) => a + b, 0);
|
|
4066
4496
|
if (total <= maxTokens) return messages;
|
|
4067
4497
|
const head = messages[0]?.role === "system" ? [messages[0]] : [];
|
|
@@ -4070,7 +4500,7 @@ function fitTokenBudget(messages, maxTokens) {
|
|
|
4070
4500
|
const ids = callIdSet(messages.slice(from));
|
|
4071
4501
|
while (from < messages.length && messages[from].role === "tool" && !ids.has(messages[from].tool_call_id ?? "")) total -= per[from++];
|
|
4072
4502
|
if (total > maxTokens)
|
|
4073
|
-
|
|
4503
|
+
log6.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
|
|
4074
4504
|
return [...head, ...messages.slice(from)];
|
|
4075
4505
|
}
|
|
4076
4506
|
function compact(m, max, focus) {
|
|
@@ -4082,11 +4512,24 @@ function compact(m, max, focus) {
|
|
|
4082
4512
|
if (dropped.length === 0) return [...head, ...tail];
|
|
4083
4513
|
return [...head, { role: "system", content: summarize(dropped, focus) }, ...tail];
|
|
4084
4514
|
}
|
|
4515
|
+
var SUMMARY_HEAD = "[summary of ";
|
|
4516
|
+
var MAX_CARRIED_COUNT = 1e4;
|
|
4517
|
+
var isSyntheticSummary = (m) => m.role === "system" && typeof m.content === "string" && m.content.startsWith(SUMMARY_HEAD);
|
|
4085
4518
|
function summarize(messages, focus) {
|
|
4086
4519
|
const tools = /* @__PURE__ */ new Set();
|
|
4087
4520
|
const files = /* @__PURE__ */ new Set();
|
|
4521
|
+
const carried = [];
|
|
4522
|
+
let carriedCount = 0;
|
|
4523
|
+
let priors = 0;
|
|
4088
4524
|
let lastAssistant = "";
|
|
4089
4525
|
for (const msg of messages) {
|
|
4526
|
+
if (isSyntheticSummary(msg)) {
|
|
4527
|
+
priors++;
|
|
4528
|
+
const [head, ...rest] = String(msg.content).split("\n");
|
|
4529
|
+
carriedCount += Math.min(Math.max(Number(/of (\d+)/.exec(head)?.[1]) || 0, 0), MAX_CARRIED_COUNT);
|
|
4530
|
+
for (const l of rest) if (l.trim() && !carried.includes(l) && carried.length < 20) carried.push(l);
|
|
4531
|
+
continue;
|
|
4532
|
+
}
|
|
4090
4533
|
for (const tc of msg.tool_calls ?? []) {
|
|
4091
4534
|
tools.add(tc.function.name);
|
|
4092
4535
|
try {
|
|
@@ -4097,14 +4540,16 @@ function summarize(messages, focus) {
|
|
|
4097
4540
|
}
|
|
4098
4541
|
if (msg.role === "assistant" && msg.content) lastAssistant = contentText(msg.content);
|
|
4099
4542
|
}
|
|
4100
|
-
const lines = [
|
|
4543
|
+
const lines = [`${SUMMARY_HEAD}${messages.length - priors + carriedCount} earlier message(s)]`];
|
|
4101
4544
|
if (tools.size) lines.push(`tools used: ${[...tools].join(", ")}`);
|
|
4102
4545
|
if (files.size) lines.push(`files touched: ${[...files].join(", ")}`);
|
|
4103
4546
|
if (lastAssistant) lines.push(`last assistant: ${lastAssistant}`);
|
|
4547
|
+
if (carried.length) lines.push("earlier recap:", ...carried.map((l) => ` ${l}`));
|
|
4104
4548
|
if (focus?.trim()) {
|
|
4105
4549
|
const words = focus.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
|
|
4106
4550
|
const kept = [];
|
|
4107
|
-
|
|
4551
|
+
const ordered = [...messages.filter((m) => !isSyntheticSummary(m)), ...messages.filter(isSyntheticSummary)];
|
|
4552
|
+
outer: for (const msg of ordered) {
|
|
4108
4553
|
if (msg.role === "tool") continue;
|
|
4109
4554
|
for (const line of contentText(msg.content).split("\n")) {
|
|
4110
4555
|
const l = line.toLowerCase();
|
|
@@ -4627,7 +5072,7 @@ init_tools_web();
|
|
|
4627
5072
|
init_tools();
|
|
4628
5073
|
init_tools_structured();
|
|
4629
5074
|
init_logging();
|
|
4630
|
-
var
|
|
5075
|
+
var log7 = forComponent("scratch");
|
|
4631
5076
|
var SCRATCH_DIR = "/scratch";
|
|
4632
5077
|
function shortArgs(args) {
|
|
4633
5078
|
try {
|
|
@@ -4667,7 +5112,7 @@ var Scratch = class {
|
|
|
4667
5112
|
await (this.dirReady ??= mkdirp(this.fs, dir));
|
|
4668
5113
|
await this.fs.writeFile(path, header + raw);
|
|
4669
5114
|
} catch (e) {
|
|
4670
|
-
|
|
5115
|
+
log7.debug("scratch write failed; returning raw", e);
|
|
4671
5116
|
return raw;
|
|
4672
5117
|
}
|
|
4673
5118
|
const preview = raw.slice(0, previewChars).replace(/\s+/g, " ").trim();
|
|
@@ -4697,7 +5142,7 @@ To pull a specific detail, Grep/Read ${path}, or call Ask({ question: "\u2026",
|
|
|
4697
5142
|
await (this.dirReady ??= mkdirp(this.fs, dir));
|
|
4698
5143
|
await this.fs.writeFile(path, header + full);
|
|
4699
5144
|
} catch (e) {
|
|
4700
|
-
|
|
5145
|
+
log7.debug("scratch spill failed; cropping lossy", e);
|
|
4701
5146
|
return full.slice(0, pageBytes) + `
|
|
4702
5147
|
|
|
4703
5148
|
[output cropped to ${pageBytes} of ${full.length} bytes; full output unavailable (scratch write failed) \u2014 refine your query]`;
|
|
@@ -4743,7 +5188,7 @@ Question: ${q2}`);
|
|
|
4743
5188
|
const answer = (res.text ?? "").trim();
|
|
4744
5189
|
return answer || "(no answer found in scratch)";
|
|
4745
5190
|
} catch (e) {
|
|
4746
|
-
|
|
5191
|
+
log7.debug("Ask peek failed", e);
|
|
4747
5192
|
return `Error querying scratch: ${e?.message ?? e}`;
|
|
4748
5193
|
}
|
|
4749
5194
|
}
|
|
@@ -4752,7 +5197,7 @@ Question: ${q2}`);
|
|
|
4752
5197
|
|
|
4753
5198
|
// src/lessons.ts
|
|
4754
5199
|
init_logging();
|
|
4755
|
-
var
|
|
5200
|
+
var log8 = forComponent("Lessons");
|
|
4756
5201
|
var LessonOptionsDefaults = class {
|
|
4757
5202
|
minRepeats = 2;
|
|
4758
5203
|
};
|
|
@@ -4777,15 +5222,16 @@ function lessonCapture(options) {
|
|
|
4777
5222
|
counts.set(lesson.slug, n);
|
|
4778
5223
|
if (n < o.minRepeats) return;
|
|
4779
5224
|
written.add(lesson.slug);
|
|
4780
|
-
await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) =>
|
|
4781
|
-
|
|
5225
|
+
await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) => log8.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
|
|
5226
|
+
log8.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
|
|
4782
5227
|
}
|
|
4783
5228
|
};
|
|
4784
5229
|
}
|
|
4785
5230
|
|
|
4786
5231
|
// src/reflect.ts
|
|
5232
|
+
init_llm();
|
|
4787
5233
|
init_logging();
|
|
4788
|
-
var
|
|
5234
|
+
var log9 = forComponent("Reflect");
|
|
4789
5235
|
async function reflectOnRun(o) {
|
|
4790
5236
|
const digest = digestRun(o.result.messages, o.maxDigestChars ?? 6e3);
|
|
4791
5237
|
if (!digest.trim()) return null;
|
|
@@ -4801,7 +5247,7 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
|
|
|
4801
5247
|
const r = await o.ai.chat({ model: o.model, messages: [{ role: "user", content: prompt }], stream: false });
|
|
4802
5248
|
text = r?.content ?? "";
|
|
4803
5249
|
} catch (e) {
|
|
4804
|
-
|
|
5250
|
+
log9.warn(`reflection call failed: ${e?.message ?? e}`);
|
|
4805
5251
|
return null;
|
|
4806
5252
|
}
|
|
4807
5253
|
const m = text.match(/LESSON:\s*(.+)/i);
|
|
@@ -4811,10 +5257,10 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
|
|
|
4811
5257
|
try {
|
|
4812
5258
|
await writeFact(o.fs, o.dir, slug2, lesson);
|
|
4813
5259
|
} catch (e) {
|
|
4814
|
-
|
|
5260
|
+
log9.warn(`could not persist lesson: ${e?.message ?? e}`);
|
|
4815
5261
|
return null;
|
|
4816
5262
|
}
|
|
4817
|
-
|
|
5263
|
+
log9.debug(`reflection persisted ${slug2}`);
|
|
4818
5264
|
return slug2;
|
|
4819
5265
|
}
|
|
4820
5266
|
function digestRun(messages, maxChars) {
|
|
@@ -4830,6 +5276,7 @@ function digestRun(messages, maxChars) {
|
|
|
4830
5276
|
|
|
4831
5277
|
// src/duplex.ts
|
|
4832
5278
|
import { MemFilesystem as MemFilesystem2 } from "@livx.cc/wcli/core";
|
|
5279
|
+
init_llm();
|
|
4833
5280
|
init_logging();
|
|
4834
5281
|
|
|
4835
5282
|
// node_modules/@bod.ee/voice/src/core/logging.ts
|
|
@@ -4862,7 +5309,7 @@ function forComponent2(name) {
|
|
|
4862
5309
|
}
|
|
4863
5310
|
|
|
4864
5311
|
// node_modules/@bod.ee/voice/src/core/emotion.ts
|
|
4865
|
-
var
|
|
5312
|
+
var log10 = forComponent2("Emotion");
|
|
4866
5313
|
var EMOTIONS = [
|
|
4867
5314
|
// primary (best results)
|
|
4868
5315
|
"neutral",
|
|
@@ -4994,7 +5441,7 @@ function renderEmotions(text, opts = { show: true }) {
|
|
|
4994
5441
|
speech += cartesiaTag(tag);
|
|
4995
5442
|
if (opts.show) display += m[0];
|
|
4996
5443
|
} else {
|
|
4997
|
-
|
|
5444
|
+
log10.debug(`dropping unknown emotion tag ${m[0]}`);
|
|
4998
5445
|
}
|
|
4999
5446
|
last = m.index + m[0].length;
|
|
5000
5447
|
}
|
|
@@ -5036,7 +5483,7 @@ var EmotionStream = class {
|
|
|
5036
5483
|
if (tag) {
|
|
5037
5484
|
this.pending = tag;
|
|
5038
5485
|
if (this.show) display += m[0];
|
|
5039
|
-
} else
|
|
5486
|
+
} else log10.debug(`dropping unknown emotion tag ${m[0]}`);
|
|
5040
5487
|
last = m.index + m[0].length;
|
|
5041
5488
|
}
|
|
5042
5489
|
this.emit(body.slice(last), (s, d, p) => {
|
|
@@ -5059,7 +5506,7 @@ var EmotionStream = class {
|
|
|
5059
5506
|
};
|
|
5060
5507
|
|
|
5061
5508
|
// node_modules/@bod.ee/voice/src/core/engine.ts
|
|
5062
|
-
var
|
|
5509
|
+
var log11 = forComponent2("VoiceEngine");
|
|
5063
5510
|
var realClock = {
|
|
5064
5511
|
now: () => performance.now(),
|
|
5065
5512
|
setTimeout: (fn, ms) => setTimeout(fn, ms),
|
|
@@ -5334,7 +5781,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5334
5781
|
await Promise.all([this.tts.connect(), this.stt.start()]);
|
|
5335
5782
|
this.tts.warmup?.();
|
|
5336
5783
|
this.setState("listening");
|
|
5337
|
-
|
|
5784
|
+
log11.debug(`voice I/O up (${this.stt.usingAec ? "AEC" : "heuristic echo"} capture)`);
|
|
5338
5785
|
}
|
|
5339
5786
|
get usingAec() {
|
|
5340
5787
|
return this.stt.usingAec;
|
|
@@ -5356,7 +5803,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5356
5803
|
this.options.onDiag({ t: this.clock.now(), kind, ...fields });
|
|
5357
5804
|
} catch (e) {
|
|
5358
5805
|
this.diagOn = false;
|
|
5359
|
-
|
|
5806
|
+
log11.debug(`onDiag threw \u2014 diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
5360
5807
|
}
|
|
5361
5808
|
}
|
|
5362
5809
|
idleWaiters = [];
|
|
@@ -5465,7 +5912,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5465
5912
|
if (prose.trim()) this.repliedSinceDispatch = true;
|
|
5466
5913
|
for (const w of this.words(this.reply)) this.echoWords.add(w);
|
|
5467
5914
|
this.tts.speak(forSpeech(speech), true);
|
|
5468
|
-
if (!this.spokeDeltas && this.turnStartAt)
|
|
5915
|
+
if (!this.spokeDeltas && this.turnStartAt) log11.debug(`ttft: ${Math.round(this.clock.now() - this.turnStartAt)}ms`);
|
|
5469
5916
|
this.spokeDeltas = true;
|
|
5470
5917
|
this.setState("speaking");
|
|
5471
5918
|
if (this.options.revealMode !== "off" && display) {
|
|
@@ -5542,7 +5989,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5542
5989
|
this.drainTimer = null;
|
|
5543
5990
|
this.speaking = false;
|
|
5544
5991
|
this.stopWordReveal(true);
|
|
5545
|
-
if (this.turnStartAt)
|
|
5992
|
+
if (this.turnStartAt) log11.debug(`turn: ${Math.round(this.clock.now() - this.turnStartAt)}ms (incl. playback)`);
|
|
5546
5993
|
this.echoUntil = this.clock.now() + 2500;
|
|
5547
5994
|
if (!this.usingAec) this.stt.reset();
|
|
5548
5995
|
this.setState("listening");
|
|
@@ -5630,7 +6077,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5630
6077
|
this.clearAckTimer("interrupt");
|
|
5631
6078
|
this.adaptiveAckFired = false;
|
|
5632
6079
|
this.ackProbe = null;
|
|
5633
|
-
if (this.uttQueue.length)
|
|
6080
|
+
if (this.uttQueue.length) log11.info(`barge-in dropped ${this.uttQueue.length} queued worker utterance(s)`);
|
|
5634
6081
|
const droppedQueued = this.uttQueue.length;
|
|
5635
6082
|
this.uttQueue = [];
|
|
5636
6083
|
if (!this.speaking && !this.drainTimer) return;
|
|
@@ -5776,7 +6223,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5776
6223
|
if (flat && this.lastDispatchFlat && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS && (this.lastDispatchFlat.startsWith(flat) || flat.startsWith(this.lastDispatchFlat))) return;
|
|
5777
6224
|
this.specSpent = true;
|
|
5778
6225
|
this.specText = this.specPartial;
|
|
5779
|
-
|
|
6226
|
+
log11.debug(`speculate: "${this.specText.slice(0, 60)}"`);
|
|
5780
6227
|
this.diag("speculate", { text: this.specText });
|
|
5781
6228
|
this.options.onSpeculate(this.specText);
|
|
5782
6229
|
}, this.options.speculativeMs);
|
|
@@ -5845,7 +6292,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5845
6292
|
};
|
|
5846
6293
|
this.tts.newContext();
|
|
5847
6294
|
this.tts.speak(phrase, false);
|
|
5848
|
-
|
|
6295
|
+
log11.debug(`backchannel: "${phrase}"`);
|
|
5849
6296
|
this.diag("backchannel", { phrase });
|
|
5850
6297
|
this.options.onBackchannel(phrase);
|
|
5851
6298
|
}
|
|
@@ -5878,7 +6325,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5878
6325
|
});
|
|
5879
6326
|
if (idx < 0) return text;
|
|
5880
6327
|
toks.splice(idx, 1);
|
|
5881
|
-
|
|
6328
|
+
log11.verbose(`stripped backchannel echo "${this.lastBcPhrase}" from dispatching final`);
|
|
5882
6329
|
return toks.join(" ");
|
|
5883
6330
|
}
|
|
5884
6331
|
/** Merge a resumed utterance into the pending one, deduping any word-overlap. Soniox re-finalizes
|
|
@@ -5969,7 +6416,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5969
6416
|
this.pendingUtt = this.mergeUtterance(this.pendingUtt, text);
|
|
5970
6417
|
if (this.pendingTimer) this.clock.clearTimeout(this.pendingTimer);
|
|
5971
6418
|
if (this.options.incompleteMergeMs && this.looksIncomplete(this.pendingUtt)) {
|
|
5972
|
-
|
|
6419
|
+
log11.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
|
|
5973
6420
|
this.diag("hold", { reason: "incomplete", tail: this.pendingUtt.slice(-40) });
|
|
5974
6421
|
this.options.onHold();
|
|
5975
6422
|
if (this.options.holdFiller && !this.speaking) this.speakFiller(this.options.holdFiller);
|
|
@@ -6004,13 +6451,13 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
6004
6451
|
if (text) {
|
|
6005
6452
|
const flat = text.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
6006
6453
|
if (flat && flat === this.lastDispatchFlat && !this.repliedSinceDispatch && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS) {
|
|
6007
|
-
|
|
6454
|
+
log11.verbose(`dropped duplicate final "${text.slice(0, 40)}" (identical to the just-dispatched utterance, no reply between)`);
|
|
6008
6455
|
this.diag("utterance_drop", { reason: "dup_final", text: text.slice(0, 60) });
|
|
6009
6456
|
return;
|
|
6010
6457
|
}
|
|
6011
6458
|
const refinal = this.refinalizes(text);
|
|
6012
6459
|
if (refinal && this.repliedSinceDispatch) {
|
|
6013
|
-
|
|
6460
|
+
log11.verbose(`dropped re-finalization "${text.slice(0, 60)}" (superset of the just-dispatched utterance, reply already flowing)`);
|
|
6014
6461
|
this.diag("utterance_drop", { reason: "refinalized", text: text.slice(0, 60) });
|
|
6015
6462
|
this.lastDispatchFlat = flat;
|
|
6016
6463
|
this.lastDispatchWords = text.trim().split(/\s+/);
|
|
@@ -6170,7 +6617,7 @@ var SpokenSplitter = class {
|
|
|
6170
6617
|
};
|
|
6171
6618
|
|
|
6172
6619
|
// node_modules/@bod.ee/voice/src/adapters/soniox.ts
|
|
6173
|
-
var
|
|
6620
|
+
var log12 = forComponent2("SonioxSTT");
|
|
6174
6621
|
var now = () => performance.now();
|
|
6175
6622
|
var SonioxSTTOptions = class {
|
|
6176
6623
|
auth = "";
|
|
@@ -6211,7 +6658,7 @@ var SonioxSTT = class {
|
|
|
6211
6658
|
this.onDiag({ t: now(), kind, ...fields });
|
|
6212
6659
|
} catch (e) {
|
|
6213
6660
|
this.diagOn = false;
|
|
6214
|
-
|
|
6661
|
+
log12.debug(`onDiag threw \u2014 STT diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
6215
6662
|
}
|
|
6216
6663
|
}
|
|
6217
6664
|
lastChunkAt = 0;
|
|
@@ -6253,11 +6700,11 @@ var SonioxSTT = class {
|
|
|
6253
6700
|
this.ws.onmessage = (ev) => this.handle(JSON.parse(String(ev.data)));
|
|
6254
6701
|
this.ws.onclose = (ev) => {
|
|
6255
6702
|
if (this.stopped) return;
|
|
6256
|
-
|
|
6703
|
+
log12.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
|
|
6257
6704
|
this.diag("stt_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: true });
|
|
6258
6705
|
this.reset();
|
|
6259
6706
|
this.connectWs().catch((e) => {
|
|
6260
|
-
|
|
6707
|
+
log12.error(`soniox reconnect failed: ${e.message}`);
|
|
6261
6708
|
this.diag("stt_reconnect_failed", { message: e.message });
|
|
6262
6709
|
});
|
|
6263
6710
|
};
|
|
@@ -6269,7 +6716,7 @@ var SonioxSTT = class {
|
|
|
6269
6716
|
this.endpointTimer = setInterval(() => {
|
|
6270
6717
|
const combined = (this.finalText + this.partialText).trim();
|
|
6271
6718
|
if (!combined || now() - this.lastChangeAt < this.options.silenceEndpointMs) return;
|
|
6272
|
-
if (this.firstTokenAt)
|
|
6719
|
+
if (this.firstTokenAt) log12.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
|
|
6273
6720
|
this.reset();
|
|
6274
6721
|
this.onUtterance(combined, now());
|
|
6275
6722
|
}, 120);
|
|
@@ -6281,7 +6728,7 @@ var SonioxSTT = class {
|
|
|
6281
6728
|
if (this.stopped) return;
|
|
6282
6729
|
const ref = this.lastChunkAt || this.startedChunksAt;
|
|
6283
6730
|
if (now() - ref > noAudioMs) {
|
|
6284
|
-
|
|
6731
|
+
log12.error(`stt: no mic audio for >${Math.round(noAudioMs / 1e3)}s \u2014 capture device stopped delivering`);
|
|
6285
6732
|
this.diag("stt_watchdog_fatal", { noAudioMs });
|
|
6286
6733
|
this.onFatal("microphone stopped delivering audio (try a different input device, e.g. AirPods, or check System Settings \u2192 Sound \u2192 Input)");
|
|
6287
6734
|
this.stop();
|
|
@@ -6304,7 +6751,7 @@ var SonioxSTT = class {
|
|
|
6304
6751
|
handle(m) {
|
|
6305
6752
|
if (m.error_message) {
|
|
6306
6753
|
this.diag("stt_error", { message: String(m.error_message), code: m.error_code });
|
|
6307
|
-
return
|
|
6754
|
+
return log12.error(`soniox: ${m.error_message}`);
|
|
6308
6755
|
}
|
|
6309
6756
|
let endpoint = false;
|
|
6310
6757
|
for (const t of m.tokens ?? []) {
|
|
@@ -6321,7 +6768,7 @@ var SonioxSTT = class {
|
|
|
6321
6768
|
this.onPartial(combined);
|
|
6322
6769
|
if (endpoint && this.finalText.trim()) {
|
|
6323
6770
|
const utterance = this.finalText.trim();
|
|
6324
|
-
if (this.firstTokenAt)
|
|
6771
|
+
if (this.firstTokenAt) log12.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
|
|
6325
6772
|
this.reset();
|
|
6326
6773
|
this.onUtterance(utterance, now());
|
|
6327
6774
|
}
|
|
@@ -6343,7 +6790,7 @@ var SonioxSTT = class {
|
|
|
6343
6790
|
};
|
|
6344
6791
|
|
|
6345
6792
|
// node_modules/@bod.ee/voice/src/adapters/cartesia.ts
|
|
6346
|
-
var
|
|
6793
|
+
var log13 = forComponent2("CartesiaTTS");
|
|
6347
6794
|
var now2 = () => performance.now();
|
|
6348
6795
|
var CartesiaTTSOptions = class {
|
|
6349
6796
|
auth = "";
|
|
@@ -6379,7 +6826,7 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
6379
6826
|
this.onDiag({ t: now2(), kind, ...fields });
|
|
6380
6827
|
} catch (e) {
|
|
6381
6828
|
this.diagOn = false;
|
|
6382
|
-
|
|
6829
|
+
log13.debug(`onDiag threw \u2014 TTS diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
6383
6830
|
}
|
|
6384
6831
|
}
|
|
6385
6832
|
firstAudioAt = 0;
|
|
@@ -6414,11 +6861,11 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
6414
6861
|
this.ws.onerror = (e) => rej(new Error(`cartesia ws: ${e.message || "connect failed"}`));
|
|
6415
6862
|
});
|
|
6416
6863
|
this.ws.onclose = (ev) => {
|
|
6417
|
-
|
|
6864
|
+
log13.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
|
|
6418
6865
|
this.diag("tts_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: !this.closed });
|
|
6419
6866
|
if (!this.closed) {
|
|
6420
6867
|
this.connecting = this.doConnect().catch((e) => {
|
|
6421
|
-
|
|
6868
|
+
log13.error(`cartesia reconnect failed: ${e.message}`);
|
|
6422
6869
|
this.diag("tts_reconnect_failed", { message: e.message });
|
|
6423
6870
|
});
|
|
6424
6871
|
}
|
|
@@ -6446,12 +6893,12 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
6446
6893
|
this.down = true;
|
|
6447
6894
|
this.downAt = now2();
|
|
6448
6895
|
this.consecutiveOk = 0;
|
|
6449
|
-
|
|
6896
|
+
log13.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
|
|
6450
6897
|
this.diag("tts_breaker_open", { errors: this.consecutiveErrors });
|
|
6451
6898
|
this.onDone();
|
|
6452
6899
|
this.startProbe();
|
|
6453
6900
|
} else if (!this.down) {
|
|
6454
|
-
(/No valid transcripts/i.test(m.message || "") ?
|
|
6901
|
+
(/No valid transcripts/i.test(m.message || "") ? log13.debug : log13.warn)(`cartesia: ${JSON.stringify(m)}`);
|
|
6455
6902
|
}
|
|
6456
6903
|
}
|
|
6457
6904
|
};
|
|
@@ -6466,7 +6913,7 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
6466
6913
|
this.stopProbe();
|
|
6467
6914
|
const downMs = this.downAt ? now2() - this.downAt : 0;
|
|
6468
6915
|
this.diag("tts_breaker_close", { downMs: Math.round(downMs) });
|
|
6469
|
-
(downMs < 2e3 ?
|
|
6916
|
+
(downMs < 2e3 ? log13.debug : log13.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
|
|
6470
6917
|
}
|
|
6471
6918
|
/** Ensure the WS is open before sending — reconnects if idle-closed. */
|
|
6472
6919
|
async ensureConnected() {
|
|
@@ -6557,7 +7004,7 @@ configureLogging((name) => {
|
|
|
6557
7004
|
});
|
|
6558
7005
|
|
|
6559
7006
|
// src/duplex.ts
|
|
6560
|
-
var
|
|
7007
|
+
var log14 = forComponent("DuplexAgent");
|
|
6561
7008
|
function describeCall(call) {
|
|
6562
7009
|
const v = call.args && Object.values(call.args).find((x) => typeof x === "string" && x.trim());
|
|
6563
7010
|
const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
|
|
@@ -6768,7 +7215,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
6768
7215
|
const m = this.reflexBuf.match(RESERVED_EVENT_MARKER) ?? this.reflexBuf.match(RESERVED_EVENT_OPENER);
|
|
6769
7216
|
if (m) {
|
|
6770
7217
|
this.fabricationCut = true;
|
|
6771
|
-
|
|
7218
|
+
log14.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
|
|
6772
7219
|
const safe = this.reflexBuf.slice(this.reflexForwarded, m.index);
|
|
6773
7220
|
if (!safe) return;
|
|
6774
7221
|
if (safe.trim()) this.spokeThisTurn = true;
|
|
@@ -6861,7 +7308,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
6861
7308
|
return {
|
|
6862
7309
|
preToolUse: (call) => {
|
|
6863
7310
|
if (this.spec?.state === "pending" && !_DuplexAgent.SPEC_SAFE_TOOLS.has(call.name)) {
|
|
6864
|
-
|
|
7311
|
+
log14.verbose(`speculation aborted: reflex called ${call.name} on unconfirmed input`);
|
|
6865
7312
|
this.abortSpeculation();
|
|
6866
7313
|
return { block: true, reason: "Speculative turn aborted." };
|
|
6867
7314
|
}
|
|
@@ -6899,7 +7346,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
6899
7346
|
try {
|
|
6900
7347
|
await this.voice.send(fallback ? "[reminder] You said nothing to the user this turn. Tell them, in ONE short spoken sentence, what just happened \u2014 no tools." : dispatched ? "[reminder] You dispatched a task but said nothing to the user. Say ONE short spoken acknowledgement now \u2014 no tools." : "[reminder] You said nothing to the user this turn. Give your ONE short spoken reply now \u2014 no tools.");
|
|
6901
7348
|
} catch (e) {
|
|
6902
|
-
|
|
7349
|
+
log14.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
|
|
6903
7350
|
} finally {
|
|
6904
7351
|
this.nudging = false;
|
|
6905
7352
|
}
|
|
@@ -6992,7 +7439,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
6992
7439
|
try {
|
|
6993
7440
|
res = await this.voice.send(spec.text);
|
|
6994
7441
|
} catch (e) {
|
|
6995
|
-
|
|
7442
|
+
log14.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
|
|
6996
7443
|
} finally {
|
|
6997
7444
|
this.voice.options.signal = prevSignal;
|
|
6998
7445
|
}
|
|
@@ -7006,7 +7453,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
7006
7453
|
if (d === "abort") {
|
|
7007
7454
|
if (this.voice.transcript.length > base) this.voice.transcript.length = base;
|
|
7008
7455
|
this.speculativeAbortedCalls++;
|
|
7009
|
-
|
|
7456
|
+
log14.verbose(`speculation aborted (${this.speculativeAbortedCalls} total): "${spec.text.slice(0, 50)}"`);
|
|
7010
7457
|
if (this.spec === spec) this.spec = void 0;
|
|
7011
7458
|
return res ?? empty;
|
|
7012
7459
|
}
|
|
@@ -7271,7 +7718,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
7271
7718
|
this.notify("task_verify", `task ${id}: verifying`, { id });
|
|
7272
7719
|
const cres = await new Agent(checkerOpts).run(checkBrief);
|
|
7273
7720
|
if (cres.finishReason !== "stop") {
|
|
7274
|
-
|
|
7721
|
+
log14.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
|
|
7275
7722
|
this.notify("task_verify", `task ${id}: verify inconclusive (${cres.finishReason})`, { id, finishReason: cres.finishReason });
|
|
7276
7723
|
}
|
|
7277
7724
|
const sum = (a = 0, b = 0) => a + b;
|
|
@@ -7337,14 +7784,14 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
7337
7784
|
* '[task <id> asks] …' and resolves with the user's answer via AnswerTask — or '' on timeout/
|
|
7338
7785
|
* task settle (callers map '' to deny / best-judgment). Workers never block forever. */
|
|
7339
7786
|
parkQuestion(askId, question) {
|
|
7340
|
-
return new Promise((
|
|
7787
|
+
return new Promise((resolve5) => {
|
|
7341
7788
|
let settled = false;
|
|
7342
7789
|
const finish = (answer) => {
|
|
7343
7790
|
if (settled) return;
|
|
7344
7791
|
settled = true;
|
|
7345
7792
|
clearTimeout(timer);
|
|
7346
7793
|
this.pendingAsks.delete(askId);
|
|
7347
|
-
|
|
7794
|
+
resolve5(answer);
|
|
7348
7795
|
};
|
|
7349
7796
|
const timer = setTimeout(() => {
|
|
7350
7797
|
this.notify("task_ask_timeout", `task ${askId}: question timed out \u2014 proceeding without an answer`);
|
|
@@ -7406,8 +7853,8 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
7406
7853
|
}
|
|
7407
7854
|
rec.status = "done";
|
|
7408
7855
|
rec.result = res.text;
|
|
7409
|
-
const incomplete = res.finishReason !== "stop";
|
|
7410
|
-
|
|
7856
|
+
const incomplete = res.finishReason !== "stop" && res.finishReason !== "empty";
|
|
7857
|
+
log14.verbose(`task ${id} done (${res.steps} steps${incomplete ? `, INCOMPLETE: ${res.finishReason}` : ""})`);
|
|
7411
7858
|
this.notify("task_done", `task ${id} (${rec.label}) completed`, {
|
|
7412
7859
|
id,
|
|
7413
7860
|
text: res.text,
|
|
@@ -7449,14 +7896,14 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
7449
7896
|
foldIfSuperseded(rec, note) {
|
|
7450
7897
|
if (!(rec.deliveryParked && this.suppressParkedResume)) return false;
|
|
7451
7898
|
if (note.trim()) this.voice.transcript.push({ role: "assistant", content: note });
|
|
7452
|
-
|
|
7899
|
+
log14.verbose(`task ${rec.id} settled but superseded (${rec.status}) \u2014 folded into transcript, not voiced`);
|
|
7453
7900
|
return true;
|
|
7454
7901
|
}
|
|
7455
7902
|
failTask(rec, msg) {
|
|
7456
7903
|
this.dropAsk(rec.id);
|
|
7457
7904
|
rec.status = "error";
|
|
7458
7905
|
rec.result = msg;
|
|
7459
|
-
|
|
7906
|
+
log14.warn(`task ${rec.id} failed: ${msg}`);
|
|
7460
7907
|
this.notify("task_error", `task ${rec.id} (${rec.label}) failed: ${msg}`);
|
|
7461
7908
|
if (this.foldIfSuperseded(rec, `The "${rec.label}" task failed: ${msg}`)) return;
|
|
7462
7909
|
this.queueRevoice(this.integrationPrompt(rec, "error", msg, "error"), true);
|
|
@@ -7761,11 +8208,11 @@ function buildMcpCatalog(servers) {
|
|
|
7761
8208
|
}
|
|
7762
8209
|
return { specs, routes };
|
|
7763
8210
|
}
|
|
7764
|
-
function searchOverCatalog(servers, specs, routes,
|
|
8211
|
+
function searchOverCatalog(servers, specs, routes, resolve5, options) {
|
|
7765
8212
|
const tools = specs.length ? makeMcpToolSearch(specs, (name, args) => {
|
|
7766
8213
|
const r = routes.get(name);
|
|
7767
8214
|
if (!r) throw new Error(`unknown MCP tool '${name}' \u2014 use ToolSearch to find valid names`);
|
|
7768
|
-
return
|
|
8215
|
+
return resolve5(r.server, r.rawName, args ?? {});
|
|
7769
8216
|
}, options) : [];
|
|
7770
8217
|
return { tools, serverNames: servers, toolCount: specs.length };
|
|
7771
8218
|
}
|
|
@@ -7777,6 +8224,7 @@ function makeMcpToolSearchFromMounted(mounted, options) {
|
|
|
7777
8224
|
|
|
7778
8225
|
// src/index.ts
|
|
7779
8226
|
init_logging();
|
|
8227
|
+
init_llm();
|
|
7780
8228
|
init_worktree();
|
|
7781
8229
|
import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor as CommandExecutor2, registerHeadlessCommands as registerHeadlessCommands2 } from "@livx.cc/wcli/core";
|
|
7782
8230
|
|
|
@@ -7784,7 +8232,7 @@ import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor a
|
|
|
7784
8232
|
init_logging();
|
|
7785
8233
|
import { spawn } from "child_process";
|
|
7786
8234
|
import { createHash } from "crypto";
|
|
7787
|
-
var
|
|
8235
|
+
var log15 = forComponent("mcp");
|
|
7788
8236
|
var PROTOCOL_VERSION = "2025-06-18";
|
|
7789
8237
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
7790
8238
|
var StdioTransport = class {
|
|
@@ -7803,7 +8251,7 @@ var StdioTransport = class {
|
|
|
7803
8251
|
proc.stdout.setEncoding("utf8");
|
|
7804
8252
|
proc.stdout.on("data", (chunk) => this.onData(chunk));
|
|
7805
8253
|
proc.stderr.setEncoding("utf8");
|
|
7806
|
-
proc.stderr.on("data", (chunk) =>
|
|
8254
|
+
proc.stderr.on("data", (chunk) => log15.debug(`[${command}] stderr:`, chunk.trimEnd()));
|
|
7807
8255
|
proc.on("exit", (code) => this.failAll(new Error(`MCP server "${command}" exited (code ${code})`)));
|
|
7808
8256
|
proc.on("error", (e) => this.failAll(e instanceof Error ? e : new Error(String(e))));
|
|
7809
8257
|
}
|
|
@@ -7817,7 +8265,7 @@ var StdioTransport = class {
|
|
|
7817
8265
|
try {
|
|
7818
8266
|
this.dispatch(JSON.parse(line));
|
|
7819
8267
|
} catch (e) {
|
|
7820
|
-
|
|
8268
|
+
log15.debug("dropping non-JSON line from MCP server:", line, e);
|
|
7821
8269
|
}
|
|
7822
8270
|
}
|
|
7823
8271
|
}
|
|
@@ -7843,12 +8291,12 @@ var StdioTransport = class {
|
|
|
7843
8291
|
request(method, params) {
|
|
7844
8292
|
const id = this.nextId++;
|
|
7845
8293
|
const timeoutMs = this.spec.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
7846
|
-
return new Promise((
|
|
8294
|
+
return new Promise((resolve5, reject) => {
|
|
7847
8295
|
const timer = setTimeout(() => {
|
|
7848
8296
|
this.pending.delete(id);
|
|
7849
8297
|
reject(new Error(`MCP request "${method}" timed out after ${timeoutMs}ms`));
|
|
7850
8298
|
}, timeoutMs);
|
|
7851
|
-
this.pending.set(id, { resolve:
|
|
8299
|
+
this.pending.set(id, { resolve: resolve5, reject, timer });
|
|
7852
8300
|
try {
|
|
7853
8301
|
this.write({ jsonrpc: "2.0", id, method, params });
|
|
7854
8302
|
} catch (e) {
|
|
@@ -7866,7 +8314,7 @@ var StdioTransport = class {
|
|
|
7866
8314
|
try {
|
|
7867
8315
|
this.proc?.stdin?.end();
|
|
7868
8316
|
} catch (e) {
|
|
7869
|
-
|
|
8317
|
+
log15.debug("stdin end failed", e);
|
|
7870
8318
|
}
|
|
7871
8319
|
this.proc?.kill();
|
|
7872
8320
|
}
|
|
@@ -7935,7 +8383,7 @@ function parseSseResponse(body) {
|
|
|
7935
8383
|
const obj = JSON.parse(trimmed.slice(5).trim());
|
|
7936
8384
|
if (obj && (obj.result !== void 0 || obj.error !== void 0)) return obj;
|
|
7937
8385
|
} catch (e) {
|
|
7938
|
-
|
|
8386
|
+
log15.debug("skipping unparseable SSE data line", e);
|
|
7939
8387
|
}
|
|
7940
8388
|
}
|
|
7941
8389
|
return {};
|
|
@@ -7981,12 +8429,12 @@ function buildTransport(cfg) {
|
|
|
7981
8429
|
}
|
|
7982
8430
|
function withTimeout(p, ms, label) {
|
|
7983
8431
|
if (!ms || ms <= 0) return p;
|
|
7984
|
-
return new Promise((
|
|
8432
|
+
return new Promise((resolve5, reject) => {
|
|
7985
8433
|
const timer = setTimeout(() => reject(new Error(`MCP "${label}" mount exceeded ${ms}ms`)), ms);
|
|
7986
8434
|
timer.unref?.();
|
|
7987
8435
|
p.then((v) => {
|
|
7988
8436
|
clearTimeout(timer);
|
|
7989
|
-
|
|
8437
|
+
resolve5(v);
|
|
7990
8438
|
}, (e) => {
|
|
7991
8439
|
clearTimeout(timer);
|
|
7992
8440
|
reject(e);
|
|
@@ -8003,7 +8451,7 @@ async function mountWithDeadline(name, cfg, mountTimeoutMs) {
|
|
|
8003
8451
|
return { name, client, tools, specs, serverInfo: init?.serverInfo, config: cfg };
|
|
8004
8452
|
})(), mountTimeoutMs, name);
|
|
8005
8453
|
} catch (e) {
|
|
8006
|
-
await client.close().catch((err2) =>
|
|
8454
|
+
await client.close().catch((err2) => log15.debug(`close after failed mount of "${name}": ${err2}`));
|
|
8007
8455
|
throw e;
|
|
8008
8456
|
}
|
|
8009
8457
|
}
|
|
@@ -8014,15 +8462,15 @@ function validEntries(servers) {
|
|
|
8014
8462
|
return Object.entries(servers).filter(([name, cfg]) => {
|
|
8015
8463
|
if (!cfg || cfg.disabled) return false;
|
|
8016
8464
|
if (!cfg.command && !cfg.url) {
|
|
8017
|
-
|
|
8465
|
+
log15.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
|
|
8018
8466
|
return false;
|
|
8019
8467
|
}
|
|
8020
8468
|
return true;
|
|
8021
8469
|
});
|
|
8022
8470
|
}
|
|
8023
8471
|
function logMountFailure(name, e) {
|
|
8024
|
-
if (e instanceof McpAuthError)
|
|
8025
|
-
else
|
|
8472
|
+
if (e instanceof McpAuthError) log15.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
|
|
8473
|
+
else log15.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
|
|
8026
8474
|
}
|
|
8027
8475
|
async function mountMcpServers(servers = {}, opts = {}) {
|
|
8028
8476
|
const entries = validEntries(servers);
|
|
@@ -8032,7 +8480,7 @@ async function mountMcpServers(servers = {}, opts = {}) {
|
|
|
8032
8480
|
const name = entries[i][0];
|
|
8033
8481
|
if (r.status === "fulfilled") {
|
|
8034
8482
|
out.push(r.value);
|
|
8035
|
-
|
|
8483
|
+
log15.debug(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
|
|
8036
8484
|
} else logMountFailure(name, r.reason);
|
|
8037
8485
|
});
|
|
8038
8486
|
return out;
|
|
@@ -8072,7 +8520,7 @@ var McpPool = class {
|
|
|
8072
8520
|
const prev = this.warm.get(key);
|
|
8073
8521
|
if (prev) {
|
|
8074
8522
|
clearTimeout(prev.timer);
|
|
8075
|
-
if (prev.client !== client) void prev.client.close().catch((err2) =>
|
|
8523
|
+
if (prev.client !== client) void prev.client.close().catch((err2) => log15.debug(`warm-pool replace close failed: ${err2}`));
|
|
8076
8524
|
}
|
|
8077
8525
|
const e = { client, timer: void 0 };
|
|
8078
8526
|
this.warm.set(key, e);
|
|
@@ -8089,7 +8537,7 @@ var McpPool = class {
|
|
|
8089
8537
|
const e = this.warm.get(key);
|
|
8090
8538
|
if (!e) return;
|
|
8091
8539
|
this.warm.delete(key);
|
|
8092
|
-
await e.client.close().catch((err2) =>
|
|
8540
|
+
await e.client.close().catch((err2) => log15.debug(`warm-pool evict close failed: ${err2}`));
|
|
8093
8541
|
}
|
|
8094
8542
|
async closeAll() {
|
|
8095
8543
|
for (const e of this.warm.values()) {
|
|
@@ -8105,8 +8553,8 @@ var defaultPool = new McpPool();
|
|
|
8105
8553
|
|
|
8106
8554
|
// cli/mcpOAuth.ts
|
|
8107
8555
|
import { createServer } from "http";
|
|
8108
|
-
import { randomBytes, createHash as createHash2 } from "crypto";
|
|
8109
|
-
import { readFileSync as
|
|
8556
|
+
import { randomBytes as randomBytes2, createHash as createHash2 } from "crypto";
|
|
8557
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync } from "fs";
|
|
8110
8558
|
import { dirname as dirname2 } from "path";
|
|
8111
8559
|
var McpOAuthOptions = class {
|
|
8112
8560
|
/** Where to persist tokens. Mode 0600. */
|
|
@@ -8134,7 +8582,7 @@ var McpOAuth = class {
|
|
|
8134
8582
|
// -- store I/O ----------------------------------------------------------------
|
|
8135
8583
|
load() {
|
|
8136
8584
|
try {
|
|
8137
|
-
return existsSync(this.options.storePath) ? JSON.parse(
|
|
8585
|
+
return existsSync(this.options.storePath) ? JSON.parse(readFileSync3(this.options.storePath, "utf8")) : {};
|
|
8138
8586
|
} catch {
|
|
8139
8587
|
return {};
|
|
8140
8588
|
}
|
|
@@ -8148,9 +8596,9 @@ var McpOAuth = class {
|
|
|
8148
8596
|
async register(serverUrl) {
|
|
8149
8597
|
const meta = await this.discover(serverUrl);
|
|
8150
8598
|
const clientId = this.options.clientId ?? await this.registerClient(meta, serverUrl);
|
|
8151
|
-
const verifier = b64url(
|
|
8599
|
+
const verifier = b64url(randomBytes2(32));
|
|
8152
8600
|
const challenge = b64url(createHash2("sha256").update(verifier).digest());
|
|
8153
|
-
const state = b64url(
|
|
8601
|
+
const state = b64url(randomBytes2(16));
|
|
8154
8602
|
const { code, redirectUri } = await this.captureCode(meta.authorization_endpoint, clientId, challenge, state);
|
|
8155
8603
|
const tok = await this.exchange(meta.token_endpoint, {
|
|
8156
8604
|
grant_type: "authorization_code",
|
|
@@ -8232,7 +8680,7 @@ var McpOAuth = class {
|
|
|
8232
8680
|
}
|
|
8233
8681
|
/** Start a one-route loopback server, open the browser, and resolve with the returned auth code. */
|
|
8234
8682
|
captureCode(authEndpoint, clientId, challenge, state) {
|
|
8235
|
-
return new Promise((
|
|
8683
|
+
return new Promise((resolve5, reject) => {
|
|
8236
8684
|
const server = createServer((req, res) => {
|
|
8237
8685
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
8238
8686
|
if (url.pathname !== "/callback") {
|
|
@@ -8246,7 +8694,7 @@ var McpOAuth = class {
|
|
|
8246
8694
|
clearTimeout(timer);
|
|
8247
8695
|
if (gotState !== state) return reject(new Error("OAuth state mismatch (possible CSRF) \u2014 aborting"));
|
|
8248
8696
|
if (!code) return reject(new Error(`authorization failed: ${url.searchParams.get("error") ?? "no code"}`));
|
|
8249
|
-
|
|
8697
|
+
resolve5({ code, redirectUri });
|
|
8250
8698
|
});
|
|
8251
8699
|
let redirectUri = "";
|
|
8252
8700
|
const timer = setTimeout(() => {
|
|
@@ -8299,7 +8747,7 @@ function defaultOpenBrowser(url) {
|
|
|
8299
8747
|
// cli/core.ts
|
|
8300
8748
|
import { randomUUID } from "crypto";
|
|
8301
8749
|
import { execFile as execFile2 } from "child_process";
|
|
8302
|
-
import { resolve, basename, join as join5 } from "path";
|
|
8750
|
+
import { resolve as resolve2, basename, join as join5 } from "path";
|
|
8303
8751
|
import { existsSync as existsSync3, mkdirSync as mkdirSync4 } from "fs";
|
|
8304
8752
|
import { platform, arch, release, userInfo, homedir as homedir2, tmpdir } from "os";
|
|
8305
8753
|
init_tools_shell();
|
|
@@ -8307,7 +8755,7 @@ init_tools_shell();
|
|
|
8307
8755
|
// src/tools.notify.ts
|
|
8308
8756
|
init_logging();
|
|
8309
8757
|
import { execFile } from "child_process";
|
|
8310
|
-
var
|
|
8758
|
+
var log17 = forComponent("notify");
|
|
8311
8759
|
function makeNotifyTool(opts = {}) {
|
|
8312
8760
|
const platform2 = opts.platform ?? process.platform;
|
|
8313
8761
|
const run = opts.exec ?? execFile;
|
|
@@ -8328,12 +8776,12 @@ function makeNotifyTool(opts = {}) {
|
|
|
8328
8776
|
if (!msg) return "Error: empty message";
|
|
8329
8777
|
const argv = platform2 === "darwin" ? ["osascript", ["-e", `display notification ${JSON.stringify(msg)} with title ${JSON.stringify(head)}`]] : platform2 === "linux" ? ["notify-send", [head, msg]] : null;
|
|
8330
8778
|
if (!argv) return `Notifications unavailable on ${platform2}.`;
|
|
8331
|
-
return new Promise((
|
|
8779
|
+
return new Promise((resolve5) => {
|
|
8332
8780
|
run(argv[0], argv[1], { timeout: 5e3 }, (e) => {
|
|
8333
8781
|
if (e) {
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
} else
|
|
8782
|
+
log17.debug("notification failed", e);
|
|
8783
|
+
resolve5(`Notification failed: ${e.message}`);
|
|
8784
|
+
} else resolve5("Notification shown.");
|
|
8337
8785
|
});
|
|
8338
8786
|
});
|
|
8339
8787
|
}
|
|
@@ -8345,9 +8793,9 @@ import { BodDB as BodDB2 } from "@bod.ee/db";
|
|
|
8345
8793
|
|
|
8346
8794
|
// cli/util.ts
|
|
8347
8795
|
init_logging();
|
|
8348
|
-
import { existsSync as existsSync2, readFileSync as
|
|
8796
|
+
import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
|
|
8349
8797
|
import { homedir } from "os";
|
|
8350
|
-
var
|
|
8798
|
+
var log18 = forComponent("cli-util");
|
|
8351
8799
|
function dotDirs(base, sub, opts = {}) {
|
|
8352
8800
|
const home = opts.home ?? homedir();
|
|
8353
8801
|
const dirs = [`${base}/.agent/${sub}`, `${base}/.claude/${sub}`, `${home}/.agent/${sub}`, `${home}/.claude/${sub}`];
|
|
@@ -8364,7 +8812,7 @@ function parseJson(text, fallback, what = "json") {
|
|
|
8364
8812
|
try {
|
|
8365
8813
|
return JSON.parse(text);
|
|
8366
8814
|
} catch (e) {
|
|
8367
|
-
|
|
8815
|
+
log18.debug(`parseJson(${what}) failed: ${e.message}`);
|
|
8368
8816
|
return fallback;
|
|
8369
8817
|
}
|
|
8370
8818
|
}
|
|
@@ -8372,9 +8820,9 @@ function readJsonFile(path, fallback) {
|
|
|
8372
8820
|
if (!existsSync2(path)) return fallback;
|
|
8373
8821
|
let text;
|
|
8374
8822
|
try {
|
|
8375
|
-
text =
|
|
8823
|
+
text = readFileSync4(path, "utf8");
|
|
8376
8824
|
} catch (e) {
|
|
8377
|
-
|
|
8825
|
+
log18.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
|
|
8378
8826
|
return fallback;
|
|
8379
8827
|
}
|
|
8380
8828
|
return parseJson(text, fallback, path);
|
|
@@ -8466,7 +8914,7 @@ async function buildAgent(o) {
|
|
|
8466
8914
|
throw new Error(
|
|
8467
8915
|
"buildAgent: explicit `sandbox` required \u2014 false = disk (the REAL filesystem, full reach like Claude Code; secrets still hidden) or true = sandbox (an in-memory copy of cwd; the real disk is never modified). This forces a conscious sandboxing decision."
|
|
8468
8916
|
);
|
|
8469
|
-
const cwd =
|
|
8917
|
+
const cwd = resolve2(o.cwd ?? process.cwd());
|
|
8470
8918
|
const disk = new NodeDiskFilesystem("/", { denySymlinks: false });
|
|
8471
8919
|
await disk.init();
|
|
8472
8920
|
const jailedDisk = new JailedFilesystem(disk);
|
|
@@ -8487,7 +8935,7 @@ async function buildAgent(o) {
|
|
|
8487
8935
|
mem.setCwd(cwd);
|
|
8488
8936
|
fs = mem;
|
|
8489
8937
|
} else if (o.boddb) {
|
|
8490
|
-
const root =
|
|
8938
|
+
const root = resolve2(o.boddb);
|
|
8491
8939
|
mkdirSync4(root, { recursive: true });
|
|
8492
8940
|
const db = new BodDB2({ path: join5(root, "meta.db"), sweepInterval: 0, vfs: { storageRoot: join5(root, "files") } });
|
|
8493
8941
|
const bod = new BodDbFilesystem(db);
|
|
@@ -8503,7 +8951,7 @@ async function buildAgent(o) {
|
|
|
8503
8951
|
const mounts = [];
|
|
8504
8952
|
const notes = [];
|
|
8505
8953
|
for (const d of o.addDirs) {
|
|
8506
|
-
const abs =
|
|
8954
|
+
const abs = resolve2(d);
|
|
8507
8955
|
let name = basename(abs) || "dir", prefix = `/${name}`, n = 2;
|
|
8508
8956
|
while (used.has(prefix)) prefix = `/${name}-${n++}`;
|
|
8509
8957
|
used.add(prefix);
|
|
@@ -8638,6 +9086,7 @@ The filesystem root '/' is the real machine root \u2014 you have full filesystem
|
|
|
8638
9086
|
// Context-v2: by default, note-take over a long session (stub tool outputs older than the last 8).
|
|
8639
9087
|
keepToolOutputs: o.keepToolOutputs ?? 8,
|
|
8640
9088
|
...o.maxContextTokens != null ? { maxContextTokens: o.maxContextTokens } : {},
|
|
9089
|
+
...o.contextWindow ? { contextWindow: o.contextWindow } : {},
|
|
8641
9090
|
...o.browserTrim ? { browserTrim: o.browserTrim } : {}
|
|
8642
9091
|
});
|
|
8643
9092
|
}
|
|
@@ -8658,11 +9107,11 @@ var trunc = (s, n) => (s == null ? "" : String(s).length > n ? String(s).slice(0
|
|
|
8658
9107
|
// cli/voice.ts
|
|
8659
9108
|
init_logging();
|
|
8660
9109
|
import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
|
|
8661
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as
|
|
9110
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
8662
9111
|
import { homedir as homedir3 } from "os";
|
|
8663
9112
|
import { dirname as dirname3, join as join6 } from "path";
|
|
8664
9113
|
import { fileURLToPath } from "url";
|
|
8665
|
-
var
|
|
9114
|
+
var log19 = forComponent("VoiceIO");
|
|
8666
9115
|
var now3 = () => performance.now();
|
|
8667
9116
|
var Player = class {
|
|
8668
9117
|
proc = null;
|
|
@@ -8676,7 +9125,7 @@ var Player = class {
|
|
|
8676
9125
|
["-loglevel", "quiet", "-nodisp", "-fflags", "nobuffer", "-flags", "low_delay", "-probesize", "32", "-f", "s16le", "-ar", String(TTS_SAMPLE_RATE), "-ch_layout", "mono", "-i", "-"],
|
|
8677
9126
|
{ stdio: ["pipe", "ignore", "ignore"] }
|
|
8678
9127
|
);
|
|
8679
|
-
this.proc.on("error", (e) =>
|
|
9128
|
+
this.proc.on("error", (e) => log19.warn(`ffplay error: ${e.message}`));
|
|
8680
9129
|
this.proc.stdin.on("error", () => {
|
|
8681
9130
|
});
|
|
8682
9131
|
this.bytesWritten = 0;
|
|
@@ -8714,7 +9163,7 @@ function detectFfmpegMic() {
|
|
|
8714
9163
|
const devices = [...audio.matchAll(/\[(\d+)\] (.+)/g)].map(([, idx, name]) => ({ idx, name: name.trim() }));
|
|
8715
9164
|
const mic = devices.find((d) => /microphone|built-in/i.test(d.name) && !/teams|blackhole|loopback/i.test(d.name)) ?? devices[0];
|
|
8716
9165
|
if (!mic) throw new Error("no audio input device found");
|
|
8717
|
-
|
|
9166
|
+
log19.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
|
|
8718
9167
|
return `:${mic.idx}`;
|
|
8719
9168
|
}
|
|
8720
9169
|
function detectedInputDevice() {
|
|
@@ -8747,18 +9196,18 @@ function resolveAecBinary() {
|
|
|
8747
9196
|
if (!existsSync4(src)) return null;
|
|
8748
9197
|
const cacheDir = join6(homedir3(), ".agent", "cache");
|
|
8749
9198
|
const bin = join6(cacheDir, "mic-aec");
|
|
8750
|
-
if (existsSync4(bin) &&
|
|
9199
|
+
if (existsSync4(bin) && statSync4(bin).mtimeMs >= statSync4(src).mtimeMs) return bin;
|
|
8751
9200
|
if (spawnSync2("which", ["swiftc"]).status !== 0) return null;
|
|
8752
9201
|
mkdirSync5(cacheDir, { recursive: true });
|
|
8753
|
-
|
|
9202
|
+
log19.info("compiling AEC mic helper (first run)\u2026");
|
|
8754
9203
|
const build = spawnSync2("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
|
|
8755
9204
|
if (build.status !== 0) {
|
|
8756
|
-
|
|
9205
|
+
log19.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
|
|
8757
9206
|
return null;
|
|
8758
9207
|
}
|
|
8759
9208
|
const sign = spawnSync2("codesign", ["-fs", "-", bin], { encoding: "utf8" });
|
|
8760
9209
|
if (sign.status !== 0) {
|
|
8761
|
-
|
|
9210
|
+
log19.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
|
|
8762
9211
|
return null;
|
|
8763
9212
|
}
|
|
8764
9213
|
return bin;
|
|
@@ -8800,16 +9249,16 @@ var NodeMicSource = class {
|
|
|
8800
9249
|
this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
|
|
8801
9250
|
} else {
|
|
8802
9251
|
if (spawnSync2("which", ["ffmpeg"]).status !== 0) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
|
|
8803
|
-
|
|
9252
|
+
log19.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
|
|
8804
9253
|
this.proc = spawn2(
|
|
8805
9254
|
"ffmpeg",
|
|
8806
9255
|
["-loglevel", "error", "-f", "avfoundation", "-i", detectFfmpegMic(), "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
|
|
8807
9256
|
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
8808
9257
|
);
|
|
8809
|
-
this.proc.stderr.on("data", (d) =>
|
|
9258
|
+
this.proc.stderr.on("data", (d) => log19.warn(`ffmpeg: ${String(d).trim()}`));
|
|
8810
9259
|
}
|
|
8811
9260
|
this.proc.on("exit", (c) => {
|
|
8812
|
-
if (c && !this.stopped)
|
|
9261
|
+
if (c && !this.stopped) log19.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
|
|
8813
9262
|
});
|
|
8814
9263
|
this.proc.stdout.on("data", (chunk) => onChunk(chunk));
|
|
8815
9264
|
}
|
|
@@ -8875,7 +9324,7 @@ var AecDuplexAudio = class {
|
|
|
8875
9324
|
this.proc.stdin.on("error", () => {
|
|
8876
9325
|
});
|
|
8877
9326
|
this.proc.on("exit", (c) => {
|
|
8878
|
-
if (c && !this.stopped)
|
|
9327
|
+
if (c && !this.stopped) log19.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
|
|
8879
9328
|
});
|
|
8880
9329
|
this.proc.stdout.on("data", (chunk) => {
|
|
8881
9330
|
this.gotChunk = true;
|
|
@@ -8891,7 +9340,7 @@ var AecDuplexAudio = class {
|
|
|
8891
9340
|
openMicSettings();
|
|
8892
9341
|
this.onFatal?.("microphone permission denied \u2014 enable it in System Settings \u2192 Privacy & Security \u2192 Microphone for your terminal, then restart it");
|
|
8893
9342
|
}
|
|
8894
|
-
} else
|
|
9343
|
+
} else log19.debug(`mic-aec: ${s}`);
|
|
8895
9344
|
}
|
|
8896
9345
|
});
|
|
8897
9346
|
if (!this.noVpio && !this.triedFallback) {
|
|
@@ -8900,7 +9349,7 @@ var AecDuplexAudio = class {
|
|
|
8900
9349
|
this.triedFallback = true;
|
|
8901
9350
|
this.noVpio = true;
|
|
8902
9351
|
this._aec = false;
|
|
8903
|
-
|
|
9352
|
+
log19.warn("mic-aec: VPIO delivered no audio in 2.5s \u2014 falling back to non-VPIO capture (no AEC \u2192 half-duplex, no barge-in)");
|
|
8904
9353
|
this.onDegrade?.();
|
|
8905
9354
|
this.killProc();
|
|
8906
9355
|
this.spawnHelper();
|
|
@@ -9077,7 +9526,7 @@ function fakeVoiceParts(uttFile) {
|
|
|
9077
9526
|
timer = setInterval(() => {
|
|
9078
9527
|
let buf;
|
|
9079
9528
|
try {
|
|
9080
|
-
buf =
|
|
9529
|
+
buf = readFileSync5(uttFile, "utf8");
|
|
9081
9530
|
} catch {
|
|
9082
9531
|
return;
|
|
9083
9532
|
}
|
|
@@ -9120,7 +9569,7 @@ function fakeVoiceParts(uttFile) {
|
|
|
9120
9569
|
init_logging();
|
|
9121
9570
|
import { appendFile, mkdirSync as mkdirSync6 } from "fs";
|
|
9122
9571
|
import { dirname as dirname4 } from "path";
|
|
9123
|
-
var
|
|
9572
|
+
var log20 = forComponent("VoiceDiag");
|
|
9124
9573
|
var JsonlDiagSink = class {
|
|
9125
9574
|
constructor(path) {
|
|
9126
9575
|
this.path = path;
|
|
@@ -9161,7 +9610,7 @@ var JsonlDiagSink = class {
|
|
|
9161
9610
|
disable(e) {
|
|
9162
9611
|
this.disabled = true;
|
|
9163
9612
|
this.queue.length = 0;
|
|
9164
|
-
|
|
9613
|
+
log20.debug(`voice diag sink disabled (${this.path}): ${e instanceof Error ? e.message : e}`);
|
|
9165
9614
|
}
|
|
9166
9615
|
/** True once a failure permanently disabled the sink (tests/inspection). */
|
|
9167
9616
|
get isDisabled() {
|
|
@@ -9175,7 +9624,7 @@ var JsonlDiagSink = class {
|
|
|
9175
9624
|
|
|
9176
9625
|
// cli/config.ts
|
|
9177
9626
|
import { homedir as homedir4 } from "os";
|
|
9178
|
-
import { existsSync as existsSync5, readFileSync as
|
|
9627
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
9179
9628
|
import { join as join7 } from "path";
|
|
9180
9629
|
import { pathToFileURL } from "url";
|
|
9181
9630
|
var FILES = ["config.ts", "config.js", "config.mjs", "config.json"];
|
|
@@ -9197,7 +9646,7 @@ function loadSettings(dir) {
|
|
|
9197
9646
|
const p = join7(dir, ".agent", "settings.json");
|
|
9198
9647
|
if (!existsSync5(p)) return {};
|
|
9199
9648
|
try {
|
|
9200
|
-
const raw = JSON.parse(
|
|
9649
|
+
const raw = JSON.parse(readFileSync6(p, "utf8"));
|
|
9201
9650
|
const cfg = {};
|
|
9202
9651
|
if (raw.mcpServers && typeof raw.mcpServers === "object") cfg.mcpServers = raw.mcpServers;
|
|
9203
9652
|
if (raw.permissions && typeof raw.permissions === "object") cfg.permissions = raw.permissions;
|
|
@@ -9231,7 +9680,7 @@ async function loadConfig(cwd) {
|
|
|
9231
9680
|
|
|
9232
9681
|
// cli/hooks-config.ts
|
|
9233
9682
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
9234
|
-
var
|
|
9683
|
+
var log21 = forComponent("hooks");
|
|
9235
9684
|
var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
9236
9685
|
function ruleMatches(rule, toolName) {
|
|
9237
9686
|
if (!rule.tool || rule.tool === "*") return true;
|
|
@@ -9248,7 +9697,7 @@ function runCmd(rule, env) {
|
|
|
9248
9697
|
});
|
|
9249
9698
|
return { code: r.status ?? 1, out: ((r.stdout ?? "") + (r.stderr ?? "")).trim() };
|
|
9250
9699
|
} catch (e) {
|
|
9251
|
-
|
|
9700
|
+
log21.debug(`hook command failed: ${rule.command}`, e);
|
|
9252
9701
|
return { code: 1, out: String(e?.message ?? e) };
|
|
9253
9702
|
}
|
|
9254
9703
|
}
|
|
@@ -9352,10 +9801,10 @@ function formatDiff(ops, opts = {}) {
|
|
|
9352
9801
|
}
|
|
9353
9802
|
|
|
9354
9803
|
// cli/session.ts
|
|
9355
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as
|
|
9804
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync4, readdirSync, renameSync, symlinkSync as symlinkSync2, unlinkSync, readlinkSync } from "fs";
|
|
9356
9805
|
import { homedir as homedir5 } from "os";
|
|
9357
9806
|
import { join as join8 } from "path";
|
|
9358
|
-
var
|
|
9807
|
+
var log22 = forComponent("session");
|
|
9359
9808
|
var globalDir = () => join8(homedir5(), ".agent", "sessions");
|
|
9360
9809
|
var SessionStore = class {
|
|
9361
9810
|
dir;
|
|
@@ -9400,15 +9849,15 @@ var SessionStore = class {
|
|
|
9400
9849
|
}
|
|
9401
9850
|
load(id) {
|
|
9402
9851
|
if (!this.safeId(id)) {
|
|
9403
|
-
|
|
9852
|
+
log22.debug(`rejecting unsafe session id: ${id}`);
|
|
9404
9853
|
return void 0;
|
|
9405
9854
|
}
|
|
9406
9855
|
const path = join8(this.dir, `${id}.json`);
|
|
9407
9856
|
if (!existsSync6(path)) return void 0;
|
|
9408
9857
|
try {
|
|
9409
|
-
return JSON.parse(
|
|
9858
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
9410
9859
|
} catch (e) {
|
|
9411
|
-
|
|
9860
|
+
log22.debug(`unreadable session ${id} \u2014 ignoring`, e);
|
|
9412
9861
|
return void 0;
|
|
9413
9862
|
}
|
|
9414
9863
|
}
|
|
@@ -9419,9 +9868,9 @@ var SessionStore = class {
|
|
|
9419
9868
|
for (const f of readdirSync(this.dir)) {
|
|
9420
9869
|
if (!f.endsWith(".json")) continue;
|
|
9421
9870
|
try {
|
|
9422
|
-
metas.push(JSON.parse(
|
|
9871
|
+
metas.push(JSON.parse(readFileSync7(join8(this.dir, f), "utf8")).meta);
|
|
9423
9872
|
} catch (e) {
|
|
9424
|
-
|
|
9873
|
+
log22.debug(`skipping unreadable session file ${f}`, e);
|
|
9425
9874
|
}
|
|
9426
9875
|
}
|
|
9427
9876
|
return metas.sort((a, b) => b.updated - a.updated);
|
|
@@ -9442,7 +9891,7 @@ function globalSessionLoad(idOrPrefix) {
|
|
|
9442
9891
|
if (existsSync6(exact)) {
|
|
9443
9892
|
try {
|
|
9444
9893
|
const target = readlinkSync(exact);
|
|
9445
|
-
return JSON.parse(
|
|
9894
|
+
return JSON.parse(readFileSync7(target, "utf8"));
|
|
9446
9895
|
} catch {
|
|
9447
9896
|
return void 0;
|
|
9448
9897
|
}
|
|
@@ -9453,7 +9902,7 @@ function globalSessionLoad(idOrPrefix) {
|
|
|
9453
9902
|
const base = f.slice(0, -5);
|
|
9454
9903
|
if (base.includes(idOrPrefix) || base.endsWith(idOrPrefix)) {
|
|
9455
9904
|
const target = readlinkSync(join8(gd, f));
|
|
9456
|
-
return JSON.parse(
|
|
9905
|
+
return JSON.parse(readFileSync7(target, "utf8"));
|
|
9457
9906
|
}
|
|
9458
9907
|
}
|
|
9459
9908
|
} catch {
|
|
@@ -9475,7 +9924,7 @@ function globalSessionList() {
|
|
|
9475
9924
|
}
|
|
9476
9925
|
continue;
|
|
9477
9926
|
}
|
|
9478
|
-
metas.push(JSON.parse(
|
|
9927
|
+
metas.push(JSON.parse(readFileSync7(target, "utf8")).meta);
|
|
9479
9928
|
} catch {
|
|
9480
9929
|
}
|
|
9481
9930
|
}
|
|
@@ -9589,13 +10038,13 @@ import { execFile as execFile3 } from "child_process";
|
|
|
9589
10038
|
import { promisify } from "util";
|
|
9590
10039
|
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync8 } from "fs";
|
|
9591
10040
|
import { homedir as homedir6 } from "os";
|
|
9592
|
-
import { join as join9, resolve as
|
|
9593
|
-
var
|
|
10041
|
+
import { join as join9, resolve as resolve3, sep as sep3, parse as parsePath } from "path";
|
|
10042
|
+
var log23 = forComponent("checkpoints");
|
|
9594
10043
|
var exec = promisify(execFile3);
|
|
9595
10044
|
var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
|
|
9596
10045
|
function isBroadRoot(p) {
|
|
9597
|
-
const abs =
|
|
9598
|
-
return abs ===
|
|
10046
|
+
const abs = resolve3(p);
|
|
10047
|
+
return abs === resolve3(homedir6()) || abs === parsePath(abs).root;
|
|
9599
10048
|
}
|
|
9600
10049
|
var ShadowRepo = class {
|
|
9601
10050
|
// undefined = unprobed; false = git/this root unusable
|
|
@@ -9636,7 +10085,7 @@ var ShadowRepo = class {
|
|
|
9636
10085
|
writeFileSync5(join9(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
|
|
9637
10086
|
this.ready = true;
|
|
9638
10087
|
} catch (e) {
|
|
9639
|
-
|
|
10088
|
+
log23.debug(`git checkpoints unavailable for ${this.workTree}`, e);
|
|
9640
10089
|
this.ready = false;
|
|
9641
10090
|
}
|
|
9642
10091
|
return this.ready;
|
|
@@ -9647,7 +10096,7 @@ var ShadowRepo = class {
|
|
|
9647
10096
|
}
|
|
9648
10097
|
async commit(label, forced = []) {
|
|
9649
10098
|
await this.run("add", "-A");
|
|
9650
|
-
for (const p of forced) await this.run("add", "-f", "--", p).catch((e) =>
|
|
10099
|
+
for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log23.debug(`force-add failed: ${p}`, e));
|
|
9651
10100
|
await this.run("commit", "--allow-empty", "-q", "-m", label);
|
|
9652
10101
|
}
|
|
9653
10102
|
/** Inject the CURRENT (pre-edit) content of `paths` into the turn-open restore point by amending it.
|
|
@@ -9655,8 +10104,8 @@ var ShadowRepo = class {
|
|
|
9655
10104
|
* turn-boundary `add -A` would never have captured it. Amend (vs a new commit) keeps one restore
|
|
9656
10105
|
* point per turn, so the REPL's turn↔frame mapping stays intact. */
|
|
9657
10106
|
async amendForced(paths) {
|
|
9658
|
-
for (const p of paths) await this.run("add", "-f", "--", p).catch((e) =>
|
|
9659
|
-
await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) =>
|
|
10107
|
+
for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log23.debug(`force-capture failed: ${p}`, e));
|
|
10108
|
+
await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log23.debug("amend failed", e));
|
|
9660
10109
|
}
|
|
9661
10110
|
/** Commits on `ref`, oldest-first (canonical index space). */
|
|
9662
10111
|
async log(ref) {
|
|
@@ -9716,7 +10165,7 @@ var ShadowRepo = class {
|
|
|
9716
10165
|
await this.run("gc", "--auto", "-q").catch(() => {
|
|
9717
10166
|
});
|
|
9718
10167
|
} catch (e) {
|
|
9719
|
-
|
|
10168
|
+
log23.debug("checkpoint prune failed", e);
|
|
9720
10169
|
}
|
|
9721
10170
|
}
|
|
9722
10171
|
};
|
|
@@ -9742,12 +10191,12 @@ var GitCheckpoints = class {
|
|
|
9742
10191
|
}
|
|
9743
10192
|
/** cwd + any add-dir mounts that live OUTSIDE cwd (in-cwd mounts are already covered by the cwd root). */
|
|
9744
10193
|
roots() {
|
|
9745
|
-
const cwd =
|
|
10194
|
+
const cwd = resolve3(this.options.workTree);
|
|
9746
10195
|
const out = [{ workTree: cwd, gitDir: this.options.gitDir }];
|
|
9747
10196
|
for (const d of this.options.addDirs ?? []) {
|
|
9748
|
-
const abs =
|
|
9749
|
-
if (abs === cwd || abs.startsWith(cwd +
|
|
9750
|
-
if (cwd.startsWith(abs +
|
|
10197
|
+
const abs = resolve3(d);
|
|
10198
|
+
if (abs === cwd || abs.startsWith(cwd + sep3)) continue;
|
|
10199
|
+
if (cwd.startsWith(abs + sep3)) continue;
|
|
9751
10200
|
out.push({ workTree: abs, gitDir: join9(abs, ".agent", "checkpoints.git") });
|
|
9752
10201
|
}
|
|
9753
10202
|
return out;
|
|
@@ -9784,7 +10233,7 @@ var GitCheckpoints = class {
|
|
|
9784
10233
|
use(sessionId) {
|
|
9785
10234
|
if (sessionId === this.session) return;
|
|
9786
10235
|
this.session = sessionId;
|
|
9787
|
-
if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) =>
|
|
10236
|
+
if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log23.debug("re-point failed", e));
|
|
9788
10237
|
}
|
|
9789
10238
|
async begin(label) {
|
|
9790
10239
|
if (!await this.start()) return;
|
|
@@ -9804,7 +10253,7 @@ var GitCheckpoints = class {
|
|
|
9804
10253
|
process.stderr.write(`\x1B[2m checkpoints: '${this.repos[i].root}' too large to snapshot in ${Math.round(this.options.opTimeoutMs / 1e3)}s \u2014 file rewind disabled here\x1B[0m
|
|
9805
10254
|
`);
|
|
9806
10255
|
} else {
|
|
9807
|
-
|
|
10256
|
+
log23.debug("checkpoint commit failed", e);
|
|
9808
10257
|
survivors.push(this.repos[i]);
|
|
9809
10258
|
survivorCaches.push(this.caches[i] ?? []);
|
|
9810
10259
|
}
|
|
@@ -9819,8 +10268,8 @@ var GitCheckpoints = class {
|
|
|
9819
10268
|
repoIndexFor(abs) {
|
|
9820
10269
|
let best = -1, bestLen = -1;
|
|
9821
10270
|
this.repos.forEach((r, i) => {
|
|
9822
|
-
const wt = r.root.endsWith(
|
|
9823
|
-
if ((abs === wt || abs.startsWith(wt +
|
|
10271
|
+
const wt = r.root.endsWith(sep3) ? r.root.slice(0, -1) : r.root;
|
|
10272
|
+
if ((abs === wt || abs.startsWith(wt + sep3)) && wt.length > bestLen) {
|
|
9824
10273
|
best = i;
|
|
9825
10274
|
bestLen = wt.length;
|
|
9826
10275
|
}
|
|
@@ -9835,11 +10284,11 @@ var GitCheckpoints = class {
|
|
|
9835
10284
|
preToolUse: async (call) => {
|
|
9836
10285
|
if (!this.snapshotted || !this.repos.length) return;
|
|
9837
10286
|
for (const rel of mutatedPaths(call)) {
|
|
9838
|
-
const abs =
|
|
10287
|
+
const abs = resolve3(this.options.workTree, rel);
|
|
9839
10288
|
if (this.forced.has(abs)) continue;
|
|
9840
10289
|
this.forced.add(abs);
|
|
9841
10290
|
const i = this.repoIndexFor(abs);
|
|
9842
|
-
if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) =>
|
|
10291
|
+
if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log23.debug("amendForced failed", e));
|
|
9843
10292
|
}
|
|
9844
10293
|
}
|
|
9845
10294
|
};
|
|
@@ -10039,7 +10488,7 @@ function completePath(listDir, ref) {
|
|
|
10039
10488
|
// cli/lineEditor.ts
|
|
10040
10489
|
import { emitKeypressEvents } from "readline";
|
|
10041
10490
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
10042
|
-
import { writeFileSync as writeFileSync7, readFileSync as
|
|
10491
|
+
import { writeFileSync as writeFileSync7, readFileSync as readFileSync8, unlinkSync as unlinkSync2 } from "fs";
|
|
10043
10492
|
import { tmpdir as tmpdir2 } from "os";
|
|
10044
10493
|
import { join as join11 } from "path";
|
|
10045
10494
|
|
|
@@ -10635,7 +11084,8 @@ var EditorState = class _EditorState {
|
|
|
10635
11084
|
const lines = text.split("\n").length;
|
|
10636
11085
|
const id = ++this.pasteSeq;
|
|
10637
11086
|
const measure = multiline ? `+${lines} line${lines === 1 ? "" : "s"}` : `+${text.length} chars`;
|
|
10638
|
-
const
|
|
11087
|
+
const preview = pastePreview(text);
|
|
11088
|
+
const placeholder = `[Pasted #${id}${preview ? ` "${preview}"` : ""} ${measure}]`;
|
|
10639
11089
|
this.pastes.set(placeholder, text);
|
|
10640
11090
|
this.insert(placeholder);
|
|
10641
11091
|
} else {
|
|
@@ -10914,6 +11364,12 @@ var EditorState = class _EditorState {
|
|
|
10914
11364
|
}
|
|
10915
11365
|
};
|
|
10916
11366
|
var REWIND = "\0REWIND";
|
|
11367
|
+
var PASTE_PREVIEW_MAX = 40;
|
|
11368
|
+
function pastePreview(text) {
|
|
11369
|
+
const line = text.split("\n").find((l) => l.trim()) ?? "";
|
|
11370
|
+
const clean2 = line.replace(/[[\]]/g, "").replace(/\s+/g, " ").trim();
|
|
11371
|
+
return clean2.length > PASTE_PREVIEW_MAX ? clean2.slice(0, PASTE_PREVIEW_MAX) + "\u2026" : clean2;
|
|
11372
|
+
}
|
|
10917
11373
|
function applyVimNormal(s, key, str) {
|
|
10918
11374
|
const k = key?.name;
|
|
10919
11375
|
if (key?.ctrl || key?.meta) return "pass";
|
|
@@ -11228,7 +11684,7 @@ function createLineEditor(out, input = keyInput) {
|
|
|
11228
11684
|
out.write("\x1B[?2004l");
|
|
11229
11685
|
const r = spawnSync4(cmd, [...cargs, file], { stdio: "inherit" });
|
|
11230
11686
|
if (r.status === 0) {
|
|
11231
|
-
const text =
|
|
11687
|
+
const text = readFileSync8(file, "utf8").replace(/\n$/, "");
|
|
11232
11688
|
s.reset();
|
|
11233
11689
|
if (text) s.insert(text);
|
|
11234
11690
|
}
|
|
@@ -11264,10 +11720,10 @@ function createLineEditor(out, input = keyInput) {
|
|
|
11264
11720
|
render(s, promptOf(), maxVisible, opts.status);
|
|
11265
11721
|
};
|
|
11266
11722
|
process.on("SIGWINCH", onResize);
|
|
11267
|
-
return new Promise((
|
|
11723
|
+
return new Promise((resolve5) => {
|
|
11268
11724
|
activeAbort = () => {
|
|
11269
11725
|
finish();
|
|
11270
|
-
|
|
11726
|
+
resolve5(null);
|
|
11271
11727
|
};
|
|
11272
11728
|
const redraw = () => render(s, promptOf(), maxVisible, opts.status);
|
|
11273
11729
|
let lastStatus = opts.status?.() ?? "";
|
|
@@ -11347,20 +11803,20 @@ function createLineEditor(out, input = keyInput) {
|
|
|
11347
11803
|
if (action === "submit") {
|
|
11348
11804
|
finish();
|
|
11349
11805
|
opts.onSubmit?.();
|
|
11350
|
-
return
|
|
11806
|
+
return resolve5(s.expand());
|
|
11351
11807
|
}
|
|
11352
11808
|
if (action === "eof") {
|
|
11353
11809
|
finish();
|
|
11354
|
-
return
|
|
11810
|
+
return resolve5(null);
|
|
11355
11811
|
}
|
|
11356
11812
|
if (action === "rewind") {
|
|
11357
11813
|
finish();
|
|
11358
|
-
return
|
|
11814
|
+
return resolve5(REWIND);
|
|
11359
11815
|
}
|
|
11360
11816
|
if (action === "cancel") {
|
|
11361
11817
|
if (s.buf.length === 0) {
|
|
11362
11818
|
finish();
|
|
11363
|
-
return
|
|
11819
|
+
return resolve5(null);
|
|
11364
11820
|
}
|
|
11365
11821
|
s.reset();
|
|
11366
11822
|
s.refresh();
|
|
@@ -11473,7 +11929,7 @@ function selectMenu(out, opts) {
|
|
|
11473
11929
|
out.write(dim2(` ${filterHint}\u2191/\u2193 select \xB7 ${back}\u23CE choose \xB7 esc cancel${more}`));
|
|
11474
11930
|
prevLines++;
|
|
11475
11931
|
};
|
|
11476
|
-
return new Promise((
|
|
11932
|
+
return new Promise((resolve5) => {
|
|
11477
11933
|
const wasRaw = !!keyInput.isRaw;
|
|
11478
11934
|
menusOpen++;
|
|
11479
11935
|
emitKeypressEvents(keyInput);
|
|
@@ -11491,7 +11947,7 @@ function selectMenu(out, opts) {
|
|
|
11491
11947
|
} catch {
|
|
11492
11948
|
}
|
|
11493
11949
|
}
|
|
11494
|
-
|
|
11950
|
+
resolve5(val);
|
|
11495
11951
|
};
|
|
11496
11952
|
const drillIn = () => {
|
|
11497
11953
|
const it = items[sel];
|
|
@@ -11612,19 +12068,19 @@ function windowAround(count, sel, max) {
|
|
|
11612
12068
|
var plainBuf = "";
|
|
11613
12069
|
var plainEnded = false;
|
|
11614
12070
|
function readPlainLine() {
|
|
11615
|
-
return new Promise((
|
|
12071
|
+
return new Promise((resolve5) => {
|
|
11616
12072
|
const emit = () => {
|
|
11617
12073
|
const nl = plainBuf.indexOf("\n");
|
|
11618
12074
|
if (nl >= 0) {
|
|
11619
12075
|
const line = plainBuf.slice(0, nl);
|
|
11620
12076
|
plainBuf = plainBuf.slice(nl + 1);
|
|
11621
|
-
|
|
12077
|
+
resolve5(line);
|
|
11622
12078
|
return true;
|
|
11623
12079
|
}
|
|
11624
12080
|
if (plainEnded) {
|
|
11625
12081
|
const rest = plainBuf;
|
|
11626
12082
|
plainBuf = "";
|
|
11627
|
-
|
|
12083
|
+
resolve5(rest.length ? rest : null);
|
|
11628
12084
|
return true;
|
|
11629
12085
|
}
|
|
11630
12086
|
return false;
|
|
@@ -11655,7 +12111,7 @@ import { spawnSync as spawnSync5 } from "child_process";
|
|
|
11655
12111
|
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync7 } from "fs";
|
|
11656
12112
|
import { homedir as homedir8 } from "os";
|
|
11657
12113
|
import { join as join12 } from "path";
|
|
11658
|
-
var
|
|
12114
|
+
var log24 = forComponent("os-sched");
|
|
11659
12115
|
var OsScheduler = class {
|
|
11660
12116
|
options;
|
|
11661
12117
|
constructor(options) {
|
|
@@ -11717,7 +12173,7 @@ var OsScheduler = class {
|
|
|
11717
12173
|
}
|
|
11718
12174
|
}
|
|
11719
12175
|
} catch (e) {
|
|
11720
|
-
|
|
12176
|
+
log24.debug(`cancel ${id}`, e);
|
|
11721
12177
|
}
|
|
11722
12178
|
for (const f of [`${id}.json`, `${id}.sh`]) {
|
|
11723
12179
|
try {
|
|
@@ -11834,7 +12290,7 @@ import { spawn as spawn3 } from "child_process";
|
|
|
11834
12290
|
import { existsSync as existsSync8, mkdirSync as mkdirSync11, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
|
|
11835
12291
|
import { homedir as homedir9 } from "os";
|
|
11836
12292
|
import { join as join13 } from "path";
|
|
11837
|
-
var
|
|
12293
|
+
var log25 = forComponent("remote-trigger");
|
|
11838
12294
|
var TRIGGER_DIR = () => join13(homedir9(), ".agent", "triggers");
|
|
11839
12295
|
var sockPath = (sessionId, dir = TRIGGER_DIR()) => join13(dir, `${sessionId}.sock`);
|
|
11840
12296
|
var TriggerServer = class {
|
|
@@ -11872,13 +12328,13 @@ var TriggerServer = class {
|
|
|
11872
12328
|
conn.end(JSON.stringify({ ok: false, error: String(e) }) + "\n");
|
|
11873
12329
|
}
|
|
11874
12330
|
});
|
|
11875
|
-
conn.on("error", (e) =>
|
|
12331
|
+
conn.on("error", (e) => log25.debug("trigger conn error", e));
|
|
11876
12332
|
});
|
|
11877
|
-
this.server.on("error", (e) =>
|
|
12333
|
+
this.server.on("error", (e) => log25.debug("trigger server error", e));
|
|
11878
12334
|
this.server.listen(p);
|
|
11879
12335
|
this.path = p;
|
|
11880
12336
|
} catch (e) {
|
|
11881
|
-
|
|
12337
|
+
log25.debug("trigger server unavailable", e);
|
|
11882
12338
|
}
|
|
11883
12339
|
}
|
|
11884
12340
|
/** Re-bind on /resume (the session id changed). */
|
|
@@ -12000,10 +12456,10 @@ var strike = C("9");
|
|
|
12000
12456
|
var link = (text, url) => useColor ? `\x1B]8;;${url}\x1B\\${cyan(text)}\x1B]8;;\x1B\\` : `${text} (${url})`;
|
|
12001
12457
|
var mdPalette = { bold, dim, cyan, italic, strike, green, link };
|
|
12002
12458
|
var err = (s) => process.stderr.write(s);
|
|
12003
|
-
var
|
|
12459
|
+
var log26 = forComponent("cli");
|
|
12004
12460
|
var VERSION = (() => {
|
|
12005
12461
|
try {
|
|
12006
|
-
return JSON.parse(
|
|
12462
|
+
return JSON.parse(readFileSync9(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
|
|
12007
12463
|
} catch {
|
|
12008
12464
|
return "?";
|
|
12009
12465
|
}
|
|
@@ -12255,7 +12711,7 @@ REPL multi-line: Option/Alt+Enter inserts a newline, or end a line with \\ to co
|
|
|
12255
12711
|
REPL shortcuts: Shift+Tab cycles permission posture (ask \u2192 accept-edits \u2192 plan) \xB7 Alt+T toggles reasoning \xB7 Alt+P switches model \xB7 Ctrl+O toggles verbose tool output \xB7 Ctrl+X Ctrl+E edits the buffer in $EDITOR \xB7 \u2192 or Tab accepts the dim history ghost-suggestion \xB7 Alt+S/Ctrl+S stash/unstash.
|
|
12256
12712
|
REPL steer/stash: type while a turn is running \u2192 Enter STEERS it (the text reaches the model at its next step, alongside the next tool result). Alt+S (or Ctrl+S) with text stashes it for AFTER the turn instead; on an empty prompt pops the next stashed entry for editing.
|
|
12257
12713
|
REPL editing (emacs/readline): Ctrl-A/E line start/end \xB7 Ctrl-B/F char \xB7 Alt-B/F or Alt/Ctrl-\u2190/\u2192 word \xB7 Ctrl-W kill word \xB7 Ctrl-U/K kill to start/end \xB7 Ctrl-Y yank \xB7 Ctrl-_ undo \xB7 Alt-D kill word fwd \xB7 Ctrl-L clear screen. Set editorMode:'vim' (or /config) for modal vim editing.
|
|
12258
|
-
REPL paste: large/multi-line pastes collapse to a [Pasted
|
|
12714
|
+
REPL paste: large/multi-line pastes collapse to a [Pasted #N "\u2026" +N lines] preview (expands on send); a pasted image/file path attaches as [Image]/[File]; Ctrl-V or /paste grabs a clipboard image (macOS).`;
|
|
12259
12715
|
function newestModel() {
|
|
12260
12716
|
return listModels().slice().sort((a, b) => (getModelInfo(b)?.releaseDate ?? "").localeCompare(getModelInfo(a)?.releaseDate ?? ""))[0] ?? "";
|
|
12261
12717
|
}
|
|
@@ -12290,7 +12746,7 @@ function resolveModelOrNewest(model) {
|
|
|
12290
12746
|
var ENV_KEY_ALIASES = { google: ["GEMINI_API_KEY"] };
|
|
12291
12747
|
function loadEnvFile(file) {
|
|
12292
12748
|
if (!existsSync9(file)) return;
|
|
12293
|
-
for (const line of
|
|
12749
|
+
for (const line of readFileSync9(file, "utf8").split("\n")) {
|
|
12294
12750
|
const m = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
12295
12751
|
if (!m || m[1] in process.env) continue;
|
|
12296
12752
|
let val = m[2].trim();
|
|
@@ -12745,7 +13201,7 @@ function costOf(pricing, promptTokens = 0, completionTokens = 0, cacheCreationTo
|
|
|
12745
13201
|
function turnCost(model, usage) {
|
|
12746
13202
|
return costOf(getModelInfo(model)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, model);
|
|
12747
13203
|
}
|
|
12748
|
-
async function evaluateGoal(ai, condition, transcript,
|
|
13204
|
+
async function evaluateGoal(ai, condition, transcript, log27) {
|
|
12749
13205
|
const recent = transcript.filter((m) => m.role === "assistant").slice(-8).map((m) => {
|
|
12750
13206
|
const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
|
|
12751
13207
|
return text.slice(0, 600);
|
|
@@ -12765,7 +13221,7 @@ ${recent}` }
|
|
|
12765
13221
|
const match = r.content.match(/\{[\s\S]*\}/);
|
|
12766
13222
|
if (match) return JSON.parse(match[0]);
|
|
12767
13223
|
} catch (e) {
|
|
12768
|
-
|
|
13224
|
+
log27(dim(` (goal evaluator error: ${e?.message ?? e})
|
|
12769
13225
|
`));
|
|
12770
13226
|
}
|
|
12771
13227
|
return { met: false, reason: "evaluation unclear" };
|
|
@@ -12776,10 +13232,8 @@ function errInfo(e) {
|
|
|
12776
13232
|
function fmtUsd(n) {
|
|
12777
13233
|
return n >= 1 ? `$${n.toFixed(2)}` : `$${n.toFixed(4)}`;
|
|
12778
13234
|
}
|
|
12779
|
-
function estimateTranscriptTokens(messages) {
|
|
12780
|
-
|
|
12781
|
-
for (const m of messages) chars += (m.content?.length ?? 0) + (m.tool_calls ? JSON.stringify(m.tool_calls).length : 0);
|
|
12782
|
-
return Math.ceil(chars / 4);
|
|
13235
|
+
function estimateTranscriptTokens(messages, keepRecentImages = 1) {
|
|
13236
|
+
return estimateTokens(messages, keepRecentImages);
|
|
12783
13237
|
}
|
|
12784
13238
|
function formatStatus(s) {
|
|
12785
13239
|
const L = (k, v) => ` ${k.padEnd(12)} ${v}
|
|
@@ -12891,9 +13345,12 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
|
|
|
12891
13345
|
const rules = [...flagRules, ...configRules, ...askRules];
|
|
12892
13346
|
if (configRules.length) err(dim(` \u2295 ${configRules.length} permission rule(s) from config
|
|
12893
13347
|
`));
|
|
13348
|
+
const model = resolveModelOrNewest(args.model ?? cfg.model);
|
|
12894
13349
|
return {
|
|
12895
13350
|
ai,
|
|
12896
|
-
model
|
|
13351
|
+
model,
|
|
13352
|
+
// The real context window drives the context % and `/compact`'s budget (never `maxTokens` — that's spend).
|
|
13353
|
+
contextWindow: getModelInfo(model)?.contextWindow ?? 0,
|
|
12897
13354
|
cwd: args.cwd,
|
|
12898
13355
|
tools: cfg.tools,
|
|
12899
13356
|
extraTools,
|
|
@@ -12909,7 +13366,7 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
|
|
|
12909
13366
|
stream: args.stream,
|
|
12910
13367
|
host: makeHost(args.outputFormat, { stream: true }),
|
|
12911
13368
|
planMode: args.plan || cfg.planMode || false,
|
|
12912
|
-
permissions: rules.length ? new PermissionPolicy({ rules, default: "allow", host: makeHost(), ask: makeAskResolver(
|
|
13369
|
+
permissions: rules.length ? new PermissionPolicy({ rules, default: "allow", host: makeHost(), ask: makeAskResolver(resolve4(args.cwd ?? process.cwd())) }) : void 0,
|
|
12913
13370
|
subagents: args.subagents || cfg.subagents || false,
|
|
12914
13371
|
maxSteps: args.maxSteps ?? cfg.maxSteps,
|
|
12915
13372
|
reasoning: args.reasoning ?? cfg.reasoning,
|
|
@@ -12985,7 +13442,7 @@ function mcpAgentTools(mounted, opts) {
|
|
|
12985
13442
|
return tools;
|
|
12986
13443
|
}
|
|
12987
13444
|
async function closeMcp(mounted) {
|
|
12988
|
-
await Promise.all(mounted.map((m) => m.client.close().catch((e) =>
|
|
13445
|
+
await Promise.all(mounted.map((m) => m.client.close().catch((e) => log26.debug("mcp close failed", e))));
|
|
12989
13446
|
}
|
|
12990
13447
|
var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
|
|
12991
13448
|
function mentionRefs(line) {
|
|
@@ -12998,11 +13455,13 @@ function readImageParts(cwd, line) {
|
|
|
12998
13455
|
for (const ref of refs) {
|
|
12999
13456
|
const mime = IMG_EXT[extname(ref).toLowerCase()];
|
|
13000
13457
|
if (!mime) continue;
|
|
13001
|
-
const abs = ref.startsWith("~/") ? untilde(ref) :
|
|
13458
|
+
const abs = ref.startsWith("~/") ? untilde(ref) : resolve4(cwd, ref);
|
|
13002
13459
|
try {
|
|
13003
|
-
|
|
13460
|
+
statSync5(abs);
|
|
13004
13461
|
} catch {
|
|
13462
|
+
continue;
|
|
13005
13463
|
}
|
|
13464
|
+
parts.push(imageRefPart(abs, mime));
|
|
13006
13465
|
}
|
|
13007
13466
|
return parts;
|
|
13008
13467
|
}
|
|
@@ -13012,9 +13471,9 @@ function pastePathClassifier(cwd) {
|
|
|
13012
13471
|
if (!t || t.includes("\n")) return null;
|
|
13013
13472
|
t = t.replace(/\\ /g, " ").replace(/^['"]|['"]$/g, "");
|
|
13014
13473
|
if (!/^(\/|~\/|\.\/|\.\.\/)/.test(t)) return null;
|
|
13015
|
-
const abs = t.startsWith("~/") ? untilde(t) :
|
|
13474
|
+
const abs = t.startsWith("~/") ? untilde(t) : resolve4(cwd, t);
|
|
13016
13475
|
try {
|
|
13017
|
-
if (!
|
|
13476
|
+
if (!statSync5(abs).isFile()) return null;
|
|
13018
13477
|
} catch {
|
|
13019
13478
|
return null;
|
|
13020
13479
|
}
|
|
@@ -13035,7 +13494,7 @@ async function expandMentions(fs, line) {
|
|
|
13035
13494
|
if (loaded.includes(ref) || missing.includes(ref)) continue;
|
|
13036
13495
|
if (ref.includes(":") && mcpMentionResolver) {
|
|
13037
13496
|
const body = await mcpMentionResolver(ref).catch((e) => {
|
|
13038
|
-
|
|
13497
|
+
log26.debug("mcp mention resolve failed", e);
|
|
13039
13498
|
return null;
|
|
13040
13499
|
});
|
|
13041
13500
|
if (body != null) {
|
|
@@ -13121,7 +13580,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
13121
13580
|
try {
|
|
13122
13581
|
store.save(session);
|
|
13123
13582
|
} catch (ex) {
|
|
13124
|
-
|
|
13583
|
+
log26.debug("mid-turn session flush failed", ex);
|
|
13125
13584
|
}
|
|
13126
13585
|
}
|
|
13127
13586
|
return origNotify(e);
|
|
@@ -13190,10 +13649,12 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
13190
13649
|
}
|
|
13191
13650
|
return { ok, res };
|
|
13192
13651
|
}
|
|
13652
|
+
var remintSessionImages = (data, store, cwd) => remintImageRefs(data.messages, [join14(store.dir, `${data.meta.id}.images`), cwd]);
|
|
13193
13653
|
function startSession(args, store, agent, cwd) {
|
|
13194
13654
|
if (args.resume || args.cont) {
|
|
13195
13655
|
const data = args.resume ? store.load(args.resume) ?? globalSessionLoad(args.resume) : store.latestData();
|
|
13196
13656
|
if (data) {
|
|
13657
|
+
data.messages = elideStaleImages(remintSessionImages(data, store, cwd), 1);
|
|
13197
13658
|
agent.transcript = data.messages;
|
|
13198
13659
|
if (args.fork) {
|
|
13199
13660
|
const now5 = Date.now();
|
|
@@ -13249,7 +13710,7 @@ function initInstructions(cwd) {
|
|
|
13249
13710
|
function persistSetting(cwd, key, value) {
|
|
13250
13711
|
const path = join14(cwd, ".agent", "settings.json");
|
|
13251
13712
|
try {
|
|
13252
|
-
const obj = existsSync9(path) ? JSON.parse(
|
|
13713
|
+
const obj = existsSync9(path) ? JSON.parse(readFileSync9(path, "utf8")) : {};
|
|
13253
13714
|
if (obj[key] === value) return;
|
|
13254
13715
|
obj[key] = value;
|
|
13255
13716
|
mkdirSync12(dirname5(path), { recursive: true });
|
|
@@ -13269,14 +13730,14 @@ var isCancelTeardown = (e) => {
|
|
|
13269
13730
|
function installCancelGuards(mounted) {
|
|
13270
13731
|
process.on("unhandledRejection", (e) => {
|
|
13271
13732
|
if (isCancelTeardown(e)) {
|
|
13272
|
-
|
|
13733
|
+
log26.debug("suppressed unhandledRejection (cursor stream cancel)", e);
|
|
13273
13734
|
return;
|
|
13274
13735
|
}
|
|
13275
|
-
|
|
13736
|
+
log26.error("unhandledRejection", e);
|
|
13276
13737
|
});
|
|
13277
13738
|
process.on("uncaughtException", (e) => {
|
|
13278
13739
|
if (isCancelTeardown(e)) {
|
|
13279
|
-
|
|
13740
|
+
log26.debug("suppressed uncaughtException (cursor stream cancel)", e);
|
|
13280
13741
|
return;
|
|
13281
13742
|
}
|
|
13282
13743
|
console.error(e);
|
|
@@ -13308,7 +13769,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
13308
13769
|
},
|
|
13309
13770
|
tickMs: 15e3
|
|
13310
13771
|
});
|
|
13311
|
-
const agentxInvocation = () => process.argv[1] ? `${process.execPath} ${
|
|
13772
|
+
const agentxInvocation = () => process.argv[1] ? `${process.execPath} ${resolve4(process.argv[1])}` : "agentx";
|
|
13312
13773
|
const osSched = new OsScheduler({ agentx: agentxInvocation() });
|
|
13313
13774
|
const osBackend = osSched.available() ? {
|
|
13314
13775
|
get sessionId() {
|
|
@@ -13547,7 +14008,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
13547
14008
|
quickLook: {
|
|
13548
14009
|
branch: () => {
|
|
13549
14010
|
try {
|
|
13550
|
-
const head =
|
|
14011
|
+
const head = readFileSync9(join14(cwd, ".git", "HEAD"), "utf8").trim();
|
|
13551
14012
|
return head.startsWith("ref: refs/heads/") ? `branch: ${head.slice("ref: refs/heads/".length)}` : `detached HEAD at ${head.slice(0, 12)}`;
|
|
13552
14013
|
} catch {
|
|
13553
14014
|
return "not a git repository";
|
|
@@ -13613,6 +14074,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
13613
14074
|
};
|
|
13614
14075
|
const setModel = (m) => {
|
|
13615
14076
|
work.model = m;
|
|
14077
|
+
work.contextWindow = getModelInfo(m)?.contextWindow ?? 0;
|
|
13616
14078
|
work.providerOptions = providerOptionsFor(m, cwd, cfg.mcpServers);
|
|
13617
14079
|
if (dx) dx.options.actModel = m;
|
|
13618
14080
|
persistModel(cwd, m);
|
|
@@ -13676,10 +14138,11 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
13676
14138
|
const pendingImages = [];
|
|
13677
14139
|
const bangContext = [];
|
|
13678
14140
|
const grabClipboardAttachment = () => {
|
|
13679
|
-
const dir = join14(
|
|
14141
|
+
const dir = join14(store.dir, `${session.meta.id}.images`);
|
|
13680
14142
|
try {
|
|
13681
14143
|
mkdirSync12(dir, { recursive: true });
|
|
13682
|
-
} catch {
|
|
14144
|
+
} catch (e) {
|
|
14145
|
+
log26.debug("paste image dir create failed", e);
|
|
13683
14146
|
}
|
|
13684
14147
|
const img = grabClipboardImage(dir, String(Date.now()));
|
|
13685
14148
|
return img ? { display: "Image", ref: "@" + img.path, path: img.path } : null;
|
|
@@ -13786,13 +14249,13 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
|
|
|
13786
14249
|
</system-reminder>`;
|
|
13787
14250
|
};
|
|
13788
14251
|
const histPath = join14(cwd, ".agent", "history");
|
|
13789
|
-
const history = existsSync9(histPath) ?
|
|
14252
|
+
const history = existsSync9(histPath) ? readFileSync9(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
|
|
13790
14253
|
const remember = (line) => {
|
|
13791
14254
|
try {
|
|
13792
14255
|
mkdirSync12(join14(cwd, ".agent"), { recursive: true });
|
|
13793
14256
|
appendFileSync(histPath, line + "\n");
|
|
13794
14257
|
} catch (e) {
|
|
13795
|
-
|
|
14258
|
+
log26.debug("history write failed", e);
|
|
13796
14259
|
}
|
|
13797
14260
|
};
|
|
13798
14261
|
const ago = (t) => {
|
|
@@ -13800,6 +14263,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
|
|
|
13800
14263
|
return s < 60 ? "just now" : s < 3600 ? `${Math.floor(s / 60)}m ago` : s < 86400 ? `${Math.floor(s / 3600)}h ago` : `${Math.floor(s / 86400)}d ago`;
|
|
13801
14264
|
};
|
|
13802
14265
|
const resumeInto = (data) => {
|
|
14266
|
+
data.messages = elideStaleImages(remintSessionImages(data, store, cwd), 1);
|
|
13803
14267
|
face.transcript = data.messages;
|
|
13804
14268
|
session = data;
|
|
13805
14269
|
checkpoints.use?.(data.meta.id);
|
|
@@ -13863,7 +14327,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
|
|
|
13863
14327
|
try {
|
|
13864
14328
|
store.save(session);
|
|
13865
14329
|
} catch (e) {
|
|
13866
|
-
|
|
14330
|
+
log26.debug("session save after rewind failed", e);
|
|
13867
14331
|
}
|
|
13868
14332
|
err(green(" \u27F2 jumped back") + dim(` \u2014 ${face.transcript.length} message(s) kept; edit + resend
|
|
13869
14333
|
`));
|
|
@@ -13897,7 +14361,7 @@ ${task}`;
|
|
|
13897
14361
|
bangContext.length = 0;
|
|
13898
14362
|
}
|
|
13899
14363
|
const delta = await refreshCatalogs().catch((e) => {
|
|
13900
|
-
|
|
14364
|
+
log26.debug("catalog refresh failed", e);
|
|
13901
14365
|
return "";
|
|
13902
14366
|
});
|
|
13903
14367
|
if (delta) {
|
|
@@ -14075,8 +14539,8 @@ ${task}`;
|
|
|
14075
14539
|
context: {
|
|
14076
14540
|
desc: "context-window usage (messages + estimated tokens)",
|
|
14077
14541
|
run: () => {
|
|
14078
|
-
const est = estimateTranscriptTokens(face.transcript);
|
|
14079
|
-
const cap = face.options
|
|
14542
|
+
const est = estimateTranscriptTokens(face.transcript, face.options.keepRecentImages);
|
|
14543
|
+
const cap = Agent.contextCap(face.options);
|
|
14080
14544
|
err(dim(` ${face.transcript.length} message(s) \xB7 ~${(est / 1e3).toFixed(1)}k tokens (~${Math.round(est / cap * 100)}% of ${Math.round(cap / 1e3)}k budget)
|
|
14081
14545
|
`));
|
|
14082
14546
|
}
|
|
@@ -14161,7 +14625,7 @@ ${task}`;
|
|
|
14161
14625
|
desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
|
|
14162
14626
|
run: async () => {
|
|
14163
14627
|
await refreshCatalogs().catch((e) => {
|
|
14164
|
-
|
|
14628
|
+
log26.debug("catalog refresh failed", e);
|
|
14165
14629
|
});
|
|
14166
14630
|
face.reprepare();
|
|
14167
14631
|
err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
|
|
@@ -14454,13 +14918,21 @@ ${task}`;
|
|
|
14454
14918
|
desc: "summarize older context to free up the window \u2014 /compact [what to preserve]",
|
|
14455
14919
|
run: (a) => {
|
|
14456
14920
|
const focus = a.join(" ").trim() || void 0;
|
|
14457
|
-
const
|
|
14921
|
+
const r = face.compactNow(12, focus);
|
|
14458
14922
|
session.messages = face.transcript;
|
|
14459
14923
|
try {
|
|
14460
14924
|
store.save(session);
|
|
14461
14925
|
} catch {
|
|
14462
14926
|
}
|
|
14463
|
-
|
|
14927
|
+
const pct = (t) => `${Math.round(t / r.cap * 100)}%`;
|
|
14928
|
+
const k = (t) => `${(t / 1e3).toFixed(1)}k`;
|
|
14929
|
+
const freed = r.tokensBefore - r.tokensAfter;
|
|
14930
|
+
if (freed <= 0) {
|
|
14931
|
+
err(dim(` nothing to compact \u2014 ~${k(r.tokensAfter)} tok (${pct(r.tokensAfter)} of window) is already at the floor
|
|
14932
|
+
`));
|
|
14933
|
+
return;
|
|
14934
|
+
}
|
|
14935
|
+
err(dim(` compacted \u2014 ~${k(r.tokensBefore)} \u2192 ~${k(r.tokensAfter)} tok (${pct(r.tokensBefore)} \u2192 ${pct(r.tokensAfter)} of window), freed ~${k(freed)}; folded ${r.removed} message(s)${focus ? ` (preserving: ${focus})` : ""}
|
|
14464
14936
|
`));
|
|
14465
14937
|
}
|
|
14466
14938
|
},
|
|
@@ -14655,7 +15127,7 @@ ${task}`;
|
|
|
14655
15127
|
try {
|
|
14656
15128
|
for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
|
|
14657
15129
|
} catch (e) {
|
|
14658
|
-
|
|
15130
|
+
log26.debug(`loadAgents(${d}) failed`, e);
|
|
14659
15131
|
}
|
|
14660
15132
|
}
|
|
14661
15133
|
if (!seen.size) {
|
|
@@ -14743,7 +15215,7 @@ ${task}`;
|
|
|
14743
15215
|
}
|
|
14744
15216
|
if (idx >= 0) {
|
|
14745
15217
|
const old = mounted.splice(idx, 1)[0];
|
|
14746
|
-
await old.client.close().catch((e) =>
|
|
15218
|
+
await old.client.close().catch((e) => log26.debug("mcp close failed", e));
|
|
14747
15219
|
}
|
|
14748
15220
|
try {
|
|
14749
15221
|
const m = await mountMcpServer(name, conf);
|
|
@@ -14771,7 +15243,7 @@ ${task}`;
|
|
|
14771
15243
|
}
|
|
14772
15244
|
const m = mounted.splice(idx, 1)[0];
|
|
14773
15245
|
remountMcpTools();
|
|
14774
|
-
await m.client.close().catch((e) =>
|
|
15246
|
+
await m.client.close().catch((e) => log26.debug("mcp close failed", e));
|
|
14775
15247
|
err(dim(` removed "${name}"
|
|
14776
15248
|
`));
|
|
14777
15249
|
return;
|
|
@@ -14887,7 +15359,7 @@ ${task}`;
|
|
|
14887
15359
|
}
|
|
14888
15360
|
const md = exportMarkdown(session.meta, shown);
|
|
14889
15361
|
const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join14(".agent", "exports", `${session.meta.id}.md`);
|
|
14890
|
-
const path =
|
|
15362
|
+
const path = resolve4(cwd, name);
|
|
14891
15363
|
try {
|
|
14892
15364
|
mkdirSync12(dirname5(path), { recursive: true });
|
|
14893
15365
|
writeFileSync9(path, md);
|
|
@@ -14974,7 +15446,7 @@ ${task}`;
|
|
|
14974
15446
|
try {
|
|
14975
15447
|
return readdirSync4(join14(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
|
|
14976
15448
|
} catch (e) {
|
|
14977
|
-
|
|
15449
|
+
log26.debug("completion readdir failed", absDir, e);
|
|
14978
15450
|
return null;
|
|
14979
15451
|
}
|
|
14980
15452
|
};
|
|
@@ -15110,7 +15582,7 @@ ${out}
|
|
|
15110
15582
|
return;
|
|
15111
15583
|
}
|
|
15112
15584
|
await refreshCatalogs().catch((e) => {
|
|
15113
|
-
|
|
15585
|
+
log26.debug("catalog refresh failed", e);
|
|
15114
15586
|
});
|
|
15115
15587
|
const sk = skills.find((x) => x.name === name);
|
|
15116
15588
|
if (sk) {
|
|
@@ -15326,8 +15798,8 @@ ${out}
|
|
|
15326
15798
|
err("\n");
|
|
15327
15799
|
const initial = prefill ?? (carry || void 0);
|
|
15328
15800
|
prefill = void 0;
|
|
15329
|
-
const ctxTok = estimateTranscriptTokens(face.transcript);
|
|
15330
|
-
const ctxCap = face.options
|
|
15801
|
+
const ctxTok = estimateTranscriptTokens(face.transcript, face.options.keepRecentImages);
|
|
15802
|
+
const ctxCap = Agent.contextCap(face.options);
|
|
15331
15803
|
{
|
|
15332
15804
|
const pct = Math.round(ctxTok / ctxCap * 100);
|
|
15333
15805
|
const step = pct >= 90 ? 90 : pct >= 80 ? 80 : 0;
|
|
@@ -15541,7 +16013,7 @@ async function main() {
|
|
|
15541
16013
|
let worktreeCleanup;
|
|
15542
16014
|
if (args.worktree) {
|
|
15543
16015
|
const { enterWorktree: enterWorktree2, exitWorktree: exitWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
|
|
15544
|
-
const session = enterWorktree2(args.worktree,
|
|
16016
|
+
const session = enterWorktree2(args.worktree, resolve4(args.cwd ?? process.cwd()));
|
|
15545
16017
|
err(dim(` \u2295 worktree: ${session.worktreePath} (branch ${session.branch})
|
|
15546
16018
|
`));
|
|
15547
16019
|
worktreeCleanup = () => {
|
|
@@ -15551,7 +16023,7 @@ async function main() {
|
|
|
15551
16023
|
`));
|
|
15552
16024
|
};
|
|
15553
16025
|
}
|
|
15554
|
-
const cwd =
|
|
16026
|
+
const cwd = resolve4(args.cwd ?? process.cwd());
|
|
15555
16027
|
const cfg = await loadConfig(cwd);
|
|
15556
16028
|
cfg.permissions = mergePerms(loadClaudeSettings(cwd), mergePerms(loadPersistedRules(cwd), cfg.permissions));
|
|
15557
16029
|
if (canPrompt && !args.yes && !isTrusted(cwd)) {
|