@livx.cc/agentx 0.99.27 → 0.99.30
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-DC7FEEjF.d.ts} +58 -12
- package/dist/cli.d.ts +10 -7
- package/dist/cli.js +775 -299
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +10 -5
- package/dist/index.js +618 -142
- 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). */
|
|
@@ -3293,8 +3615,10 @@ var AgentOptions = class {
|
|
|
3293
3615
|
/** Stream tokens from the model. Takes effect only with a `host.notify`; off => current (non-stream) behavior. */
|
|
3294
3616
|
stream = false;
|
|
3295
3617
|
/** Closure reflex for delegated runtimes (cursor/claude-code): when such a turn ends on a tool call with
|
|
3296
|
-
* no assistant text after it (the runtime went silent post-work), nudge ONE more step
|
|
3297
|
-
*
|
|
3618
|
+
* no assistant text after it (the runtime went silent post-work), nudge ONE more step to ANSWER the user
|
|
3619
|
+
* from the tool results already in the transcript — so the turn delivers a reply instead of trailing off
|
|
3620
|
+
* mid-narration. Deliberately not a "summarize what you did" nudge: that produced process reports and
|
|
3621
|
+
* permission-asking instead of answers (see the nudge text in runLoop).
|
|
3298
3622
|
* Fires at most once per run and only for delegated runtimes (native turns never emit the trigger). */
|
|
3299
3623
|
closeDelegatedTurns = true;
|
|
3300
3624
|
/** Fold the dropped middle of an over-long transcript into a synthetic summary (edge-safe, no LLM). Off => drop-oldest. */
|
|
@@ -3481,7 +3805,7 @@ var Agent = class _Agent {
|
|
|
3481
3805
|
const disk = new NodeDiskFilesystem2(process.cwd());
|
|
3482
3806
|
await disk.init();
|
|
3483
3807
|
this.options.fs = new JailedFilesystem2(disk);
|
|
3484
|
-
|
|
3808
|
+
log6.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
|
|
3485
3809
|
}
|
|
3486
3810
|
this.buildCtx();
|
|
3487
3811
|
}
|
|
@@ -3599,19 +3923,42 @@ var Agent = class _Agent {
|
|
|
3599
3923
|
return this.runLoop();
|
|
3600
3924
|
}
|
|
3601
3925
|
/**
|
|
3602
|
-
* Fold the conversation in place (manual `/compact`)
|
|
3603
|
-
*
|
|
3604
|
-
*
|
|
3605
|
-
*
|
|
3606
|
-
*
|
|
3926
|
+
* Fold the conversation in place (manual `/compact`) until it fits a TOKEN budget — not a message
|
|
3927
|
+
* count. Message-count folding alone frees almost nothing when a few recent tool results are huge,
|
|
3928
|
+
* so we escalate: fold the middle into a synthetic recap → elide oversized tool-result bodies in the
|
|
3929
|
+
* retained window → halve the window → drop-oldest backstop. Deterministic, no LLM call.
|
|
3930
|
+
* `focus` (e.g. `/compact keep the API details`) preserves matching lines from the dropped span verbatim.
|
|
3931
|
+
* Returns before/after token estimates so the caller can report honestly (0 freed => say so).
|
|
3607
3932
|
*/
|
|
3608
3933
|
compactNow(maxMessages = 12, focus) {
|
|
3609
|
-
const
|
|
3610
|
-
|
|
3934
|
+
const cap = _Agent.contextCap(this.options);
|
|
3935
|
+
const target = Math.round(cap * _Agent.COMPACT_TARGET);
|
|
3936
|
+
const kr = this.options.keepRecentImages ?? 1;
|
|
3937
|
+
const tokensBefore = estimateTokens(this.transcript, kr);
|
|
3938
|
+
const msgsBefore = this.transcript.length;
|
|
3939
|
+
let max = Math.max(2, maxMessages);
|
|
3940
|
+
if (this.transcript.length <= max && tokensBefore <= target)
|
|
3941
|
+
return { tokensBefore, tokensAfter: tokensBefore, target, cap, removed: 0 };
|
|
3611
3942
|
void this.activeHooks?.onPreCompact?.(this.transcript);
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3943
|
+
let out = this.transcript;
|
|
3944
|
+
if (out.length > max) out = compact(out, max, focus);
|
|
3945
|
+
if (estimateTokens(out, kr) > target) out = elideOversizedImages(out, target, true, kr);
|
|
3946
|
+
if (estimateTokens(out, kr) > target) out = capOversizedBodies(out, target, kr);
|
|
3947
|
+
if (estimateTokens(out, kr) > target) out = elideOversizedImages(out, target, false, kr);
|
|
3948
|
+
while (estimateTokens(out, kr) > target && max > 2 && out.length > 2) {
|
|
3949
|
+
max = Math.max(2, Math.floor(max / 2));
|
|
3950
|
+
out = capOversizedBodies(compact(out, max, focus), target, kr);
|
|
3951
|
+
}
|
|
3952
|
+
if (estimateTokens(out, kr) > target) out = fitTokenBudget(out, target, kr);
|
|
3953
|
+
out = dropOrphanToolResults(out);
|
|
3954
|
+
this.transcript = out;
|
|
3955
|
+
this.releaseStaleImages();
|
|
3956
|
+
return { tokensBefore, tokensAfter: estimateTokens(this.transcript, kr), target, cap, removed: msgsBefore - this.transcript.length };
|
|
3957
|
+
}
|
|
3958
|
+
/** Drop inline base64 images from older messages so Bun/JSC can reclaim the heap (see contentBytes). */
|
|
3959
|
+
releaseStaleImages(keepRecent = 1) {
|
|
3960
|
+
const next = elideStaleImages(this.transcript, keepRecent);
|
|
3961
|
+
if (next !== this.transcript) this.transcript = next;
|
|
3615
3962
|
}
|
|
3616
3963
|
async runLoop() {
|
|
3617
3964
|
const o = this.options;
|
|
@@ -3627,15 +3974,16 @@ var Agent = class _Agent {
|
|
|
3627
3974
|
let lastFp = "";
|
|
3628
3975
|
let repeats = 0;
|
|
3629
3976
|
let closureNudged = false;
|
|
3977
|
+
let lastProgressAt = start;
|
|
3630
3978
|
const kill = (finishReason) => {
|
|
3631
|
-
|
|
3979
|
+
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
3980
|
this.ctx.jobs?.killAll();
|
|
3633
3981
|
return { text: lastAssistantText(this.transcript), steps, finishReason, messages: this.transcript, usage, usageEstimated };
|
|
3634
3982
|
};
|
|
3635
3983
|
while (true) {
|
|
3636
3984
|
if (o.signal?.aborted) return kill("aborted");
|
|
3637
3985
|
if (steps >= o.maxSteps) return kill("max_steps");
|
|
3638
|
-
if (o.timeoutMs && Date.now() -
|
|
3986
|
+
if (o.timeoutMs && Date.now() - lastProgressAt - this.parkedMs >= o.timeoutMs) return kill("timeout");
|
|
3639
3987
|
const budgetTokens = usage.totalTokens - 0.9 * usage.cacheReadTokens;
|
|
3640
3988
|
if (o.maxTokens && budgetTokens >= o.maxTokens) return kill("budget");
|
|
3641
3989
|
steps++;
|
|
@@ -3675,27 +4023,38 @@ var Agent = class _Agent {
|
|
|
3675
4023
|
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
4024
|
res = r;
|
|
3677
4025
|
}
|
|
4026
|
+
if (steps === 1 && this.isEmptyStop(res) && !this.pendingInjections.length) {
|
|
4027
|
+
const e = new Error("model returned an empty turn (finishReason=stop, no text, no tools)");
|
|
4028
|
+
e.code = "empty";
|
|
4029
|
+
throw e;
|
|
4030
|
+
}
|
|
3678
4031
|
break;
|
|
3679
4032
|
} catch (err2) {
|
|
3680
4033
|
const sc = err2?.statusCode;
|
|
3681
4034
|
const serverSide = sc >= 500 || sc === 408 || /Service Unavailable|overloaded|Internal server error|Bad gateway|Gateway time/i.test(String(err2?.message ?? ""));
|
|
3682
4035
|
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
4036
|
const stalled = err2?.code === "stall";
|
|
3684
|
-
const
|
|
4037
|
+
const empty = err2?.code === "empty" || err2?.code === "cursor_silent";
|
|
4038
|
+
const maxAttempts = empty ? Math.max(o.emptyRetries, 0) : 2;
|
|
4039
|
+
const transient = !o.signal?.aborted && !isAbortError(err2) && attempt < maxAttempts && (network || serverSide || stalled || empty);
|
|
3685
4040
|
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})` });
|
|
4041
|
+
const waitMs = (empty ? o.emptyRetryBaseMs : 1e3) * (attempt + 1);
|
|
4042
|
+
log6.warn(`${empty ? "empty model turn" : stalled ? "model stall" : "network drop"} mid-step (${err2?.message ?? err2}) \u2014 retrying in ${waitMs}ms`);
|
|
4043
|
+
o.host?.notify?.({ kind: "retry", message: `${empty ? "model returned nothing" : stalled ? "model stalled" : "connection dropped"} \u2014 retrying step (#${attempt + 1})` });
|
|
3689
4044
|
await new Promise((r) => setTimeout(r, waitMs));
|
|
3690
4045
|
}
|
|
3691
4046
|
}
|
|
3692
4047
|
} catch (err2) {
|
|
3693
4048
|
if (err2?.code === "budget") return kill("budget");
|
|
3694
4049
|
if (err2?.code === "stall") {
|
|
3695
|
-
|
|
4050
|
+
log6.warn(`model stalled out after retries: ${err2?.message ?? err2}`);
|
|
3696
4051
|
return kill("timeout");
|
|
3697
4052
|
}
|
|
3698
4053
|
if (o.signal?.aborted || isAbortError(err2)) return kill("aborted");
|
|
4054
|
+
if (err2?.code === "empty" || err2?.code === "cursor_silent") {
|
|
4055
|
+
log6.warn(`model returned empty/silent turns after retries \u2014 surfacing as failed`);
|
|
4056
|
+
return { text: "", steps, finishReason: "empty", messages: this.transcript, usage, usageEstimated, error: err2 };
|
|
4057
|
+
}
|
|
3699
4058
|
const body = err2?.body ?? err2?.response?.data ?? err2?.error;
|
|
3700
4059
|
let bodyStr;
|
|
3701
4060
|
try {
|
|
@@ -3705,7 +4064,7 @@ var Agent = class _Agent {
|
|
|
3705
4064
|
}
|
|
3706
4065
|
if (bodyStr && err2 instanceof Error && !err2.message.includes(bodyStr)) err2.detail = bodyStr;
|
|
3707
4066
|
const errLog = err2 instanceof Error ? { message: err2.message, ...err2.detail ? { detail: err2.detail } : {}, stack: err2.stack } : err2;
|
|
3708
|
-
|
|
4067
|
+
log6.error(`chat() failed: ${err2?.message ?? err2}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, errLog);
|
|
3709
4068
|
return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err2 };
|
|
3710
4069
|
}
|
|
3711
4070
|
if (o.signal?.aborted) return kill("aborted");
|
|
@@ -3749,6 +4108,7 @@ var Agent = class _Agent {
|
|
|
3749
4108
|
}
|
|
3750
4109
|
const toolCalls = res.toolCalls ?? [];
|
|
3751
4110
|
const emptyTurn = toolCalls.length === 0 && delegatedTools.length === 0 && contentText(res.content ?? "").trim() === "";
|
|
4111
|
+
if (!emptyTurn) lastProgressAt = Date.now();
|
|
3752
4112
|
if (!emptyTurn) {
|
|
3753
4113
|
this.transcript.push({
|
|
3754
4114
|
role: "assistant",
|
|
@@ -3760,12 +4120,16 @@ var Agent = class _Agent {
|
|
|
3760
4120
|
if (this.drainInjections()) continue;
|
|
3761
4121
|
if (o.closeDelegatedTurns && res.endedOnToolNoText && !closureNudged && steps < o.maxSteps) {
|
|
3762
4122
|
closureNudged = true;
|
|
3763
|
-
|
|
3764
|
-
this.transcript.push({
|
|
4123
|
+
log6.verbose("delegated turn went silent after a tool \u2014 nudging a closing answer");
|
|
4124
|
+
this.transcript.push({
|
|
4125
|
+
role: "user",
|
|
4126
|
+
content: "You ran tools but did not reply. Answer the user now using the tool results above; if a tool failed, say so plainly. Do NOT re-run those tools, do NOT narrate what you did, and do NOT ask for permission to continue."
|
|
4127
|
+
});
|
|
3765
4128
|
continue;
|
|
3766
4129
|
}
|
|
3767
|
-
|
|
4130
|
+
log6.verbose(`completed in ${steps} step(s)`);
|
|
3768
4131
|
await this.ctx.jobs?.drain();
|
|
4132
|
+
this.releaseStaleImages();
|
|
3769
4133
|
await this.activeHooks?.onStop?.(res.content ?? "");
|
|
3770
4134
|
return { text: res.content ?? "", steps, finishReason: "stop", messages: this.transcript, usage, usageEstimated };
|
|
3771
4135
|
}
|
|
@@ -3791,7 +4155,7 @@ var Agent = class _Agent {
|
|
|
3791
4155
|
if (this.ctx.needsInput) {
|
|
3792
4156
|
const question = this.ctx.needsInput;
|
|
3793
4157
|
this.ctx.needsInput = void 0;
|
|
3794
|
-
|
|
4158
|
+
log6.warn(`needs_input: ${question.question} (steps=${steps})`);
|
|
3795
4159
|
await this.ctx.jobs?.drain();
|
|
3796
4160
|
const text = [
|
|
3797
4161
|
`OPEN QUESTION (run paused \u2014 no human was reachable): ${question.question}`,
|
|
@@ -3848,6 +4212,12 @@ var Agent = class _Agent {
|
|
|
3848
4212
|
}
|
|
3849
4213
|
};
|
|
3850
4214
|
}
|
|
4215
|
+
/** A clean-stop turn that carries no assistant text AND no tool calls (native or delegated) — a
|
|
4216
|
+
* non-result the model produced by going silent. Callers retry it rather than report "done". */
|
|
4217
|
+
isEmptyStop(res) {
|
|
4218
|
+
const stop = res.finishReason === "stop" || res.finishReason == null;
|
|
4219
|
+
return stop && (res.toolCalls?.length ?? 0) === 0 && (res.delegatedTools?.length ?? 0) === 0 && contentText(res.content ?? "").trim() === "";
|
|
4220
|
+
}
|
|
3851
4221
|
async consumeStream(stream, poke) {
|
|
3852
4222
|
let content = "";
|
|
3853
4223
|
let finishReason;
|
|
@@ -3906,13 +4276,13 @@ var Agent = class _Agent {
|
|
|
3906
4276
|
const decision = await this.park(Promise.resolve(hooks?.preToolUse?.(call, meta)));
|
|
3907
4277
|
if (decision?.block) {
|
|
3908
4278
|
const blocked = `Blocked by hook: ${decision.reason ?? "no reason given"}`;
|
|
3909
|
-
|
|
4279
|
+
log6.debug(`${tc.function.name} -> ${blocked}`);
|
|
3910
4280
|
await hooks?.postToolUse?.(call, blocked, meta);
|
|
3911
4281
|
return blocked;
|
|
3912
4282
|
}
|
|
3913
4283
|
this.options.host?.notify?.({ kind: "tool_use", id: tc.id ?? "", name: tc.function.name, input: args });
|
|
3914
4284
|
if (earlyError) {
|
|
3915
|
-
|
|
4285
|
+
log6.debug(`${tc.function.name} -> ${earlyError}`);
|
|
3916
4286
|
await hooks?.postToolUse?.(call, earlyError, meta);
|
|
3917
4287
|
this.options.host?.notify?.({ kind: "tool_result", id: tc.id ?? "", output: earlyError, isError: true });
|
|
3918
4288
|
return earlyError;
|
|
@@ -3921,12 +4291,12 @@ var Agent = class _Agent {
|
|
|
3921
4291
|
let images;
|
|
3922
4292
|
let threw = false;
|
|
3923
4293
|
try {
|
|
3924
|
-
|
|
4294
|
+
log6.debug(`${tc.function.name}(${tc.function.arguments})`);
|
|
3925
4295
|
this.ctx.emit = hooks?.onToolOutput ? (chunk) => {
|
|
3926
4296
|
try {
|
|
3927
4297
|
hooks.onToolOutput(call, chunk, meta);
|
|
3928
4298
|
} catch (e) {
|
|
3929
|
-
|
|
4299
|
+
log6.debug(`onToolOutput hook error: ${e}`);
|
|
3930
4300
|
}
|
|
3931
4301
|
} : void 0;
|
|
3932
4302
|
const raw = await tool.run(args, this.ctx);
|
|
@@ -3938,7 +4308,7 @@ var Agent = class _Agent {
|
|
|
3938
4308
|
}
|
|
3939
4309
|
} catch (e) {
|
|
3940
4310
|
const msg = e instanceof Error ? e.message : String(e);
|
|
3941
|
-
|
|
4311
|
+
log6.debug(`${tc.function.name} -> error: ${msg}`);
|
|
3942
4312
|
result = `Error: ${msg}`;
|
|
3943
4313
|
threw = true;
|
|
3944
4314
|
} finally {
|
|
@@ -3964,6 +4334,16 @@ var Agent = class _Agent {
|
|
|
3964
4334
|
}
|
|
3965
4335
|
return images?.length ? { text: result, images } : result;
|
|
3966
4336
|
}
|
|
4337
|
+
/** `/compact` aims to land the transcript at this fraction of the context window. */
|
|
4338
|
+
static COMPACT_TARGET = 0.5;
|
|
4339
|
+
/** THE context-window cap resolution — `/compact`'s budget and the CLI's context footer/warnings both
|
|
4340
|
+
* route here, so the percentage the user reads and the one compaction targets can never drift. */
|
|
4341
|
+
static contextCap(o) {
|
|
4342
|
+
if (o.maxContextTokens) return o.maxContextTokens;
|
|
4343
|
+
const w = o.contextWindow || 0;
|
|
4344
|
+
if (w > ANTHROPIC_WINDOW_WITHOUT_1M_BETA && isAnthropicModel(o.model)) return ANTHROPIC_WINDOW_WITHOUT_1M_BETA;
|
|
4345
|
+
return w || 2e5;
|
|
4346
|
+
}
|
|
3967
4347
|
static WRITE_CLASS = ["Write", "Edit", "MultiEdit", "ApplyEdits"];
|
|
3968
4348
|
/** Append an autoTest failure section to a write-class tool result, if configured. */
|
|
3969
4349
|
async maybeAutoTest(toolName, result) {
|
|
@@ -3997,26 +4377,27 @@ ${out}`;
|
|
|
3997
4377
|
const browserKeep = this.browserActive ? o.browserTrim?.keepOutputs : void 0;
|
|
3998
4378
|
const keep = browserKeep != null ? o.keepToolOutputs ? Math.min(o.keepToolOutputs, browserKeep) : browserKeep : o.keepToolOutputs;
|
|
3999
4379
|
if (keep) out = stubOldToolResults(out ?? m, keep);
|
|
4380
|
+
out = expandImagesForSend(dropOrphanToolResults(out ?? m), o.keepRecentImages ?? 1);
|
|
4000
4381
|
if (o.maxContextTokens) {
|
|
4001
|
-
const pre =
|
|
4002
|
-
out = fitTokenBudget(out
|
|
4382
|
+
const pre = out.length;
|
|
4383
|
+
out = fitTokenBudget(out, o.maxContextTokens, o.keepRecentImages ?? 1);
|
|
4003
4384
|
const dropped = pre - out.length;
|
|
4004
4385
|
if (dropped > 0 && dropped !== this.lastTrimNotified) {
|
|
4005
4386
|
this.lastTrimNotified = dropped;
|
|
4006
4387
|
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
4388
|
}
|
|
4008
4389
|
}
|
|
4009
|
-
return dropOrphanToolResults(out
|
|
4390
|
+
return dropOrphanToolResults(out);
|
|
4010
4391
|
}
|
|
4011
4392
|
};
|
|
4012
4393
|
function dropOldest(m, max) {
|
|
4013
4394
|
const head = m[0]?.role === "system" ? [m[0]] : [];
|
|
4014
4395
|
return [...head, ...m.slice(-(max - head.length))];
|
|
4015
4396
|
}
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
return Math.ceil(
|
|
4397
|
+
var ANTHROPIC_WINDOW_WITHOUT_1M_BETA = 2e5;
|
|
4398
|
+
var isAnthropicModel = (m) => !!m && (m.startsWith("anthropic/") || /(^|\/)claude-/.test(m));
|
|
4399
|
+
function estimateTokens(m, keepRecentImages = 1) {
|
|
4400
|
+
return Math.ceil(sendBytes(m, keepRecentImages) / 4);
|
|
4020
4401
|
}
|
|
4021
4402
|
function cropResult(result, cap) {
|
|
4022
4403
|
const head = result.slice(0, cap);
|
|
@@ -4050,6 +4431,60 @@ function stubOldToolResults(messages, keep) {
|
|
|
4050
4431
|
return { ...x, content: `[${x.name ?? "tool"}${where ? ` ${where}` : ""} output elided \u2014 ${lines} lines; re-run the tool to view]` };
|
|
4051
4432
|
});
|
|
4052
4433
|
}
|
|
4434
|
+
function elideOversizedImages(messages, target, protectNewest = false, keepRecentImages = 1) {
|
|
4435
|
+
let out = messages;
|
|
4436
|
+
const withImages = out.map((_, i) => i).filter((i) => messageHasImage(out[i].content));
|
|
4437
|
+
const newest = protectNewest ? withImages[withImages.length - 1] : -1;
|
|
4438
|
+
const weight = sendBytesPer(out, keepRecentImages);
|
|
4439
|
+
const heaviestFirst = withImages.filter((i) => i !== newest).sort((a, b) => weight[b] - weight[a]);
|
|
4440
|
+
for (const i of heaviestFirst) {
|
|
4441
|
+
if (estimateTokens(out, keepRecentImages) <= target) break;
|
|
4442
|
+
const c = elideImages(out[i].content);
|
|
4443
|
+
if (c === out[i].content) continue;
|
|
4444
|
+
out = out.map((m, j) => j === i ? { ...m, content: c } : m);
|
|
4445
|
+
}
|
|
4446
|
+
return out;
|
|
4447
|
+
}
|
|
4448
|
+
function capOversizedBodies(messages, target, keepRecentImages = 1) {
|
|
4449
|
+
let out = messages;
|
|
4450
|
+
for (const cap of [8e3, 4e3, 2e3, 800, 300, 120]) {
|
|
4451
|
+
if (estimateTokens(out, keepRecentImages) <= target) break;
|
|
4452
|
+
out = out.map((x) => x.role === "system" ? x : elideBody(x, cap));
|
|
4453
|
+
}
|
|
4454
|
+
return out;
|
|
4455
|
+
}
|
|
4456
|
+
function elideBody(x, cap) {
|
|
4457
|
+
const note = (bytes, imgs2) => `
|
|
4458
|
+
|
|
4459
|
+
[${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` : ""}]`;
|
|
4460
|
+
if (!Array.isArray(x.content)) {
|
|
4461
|
+
const text = contentText(x.content);
|
|
4462
|
+
if (text.length <= cap) return x;
|
|
4463
|
+
return { ...x, content: `${text.slice(0, cap)}${note(text.length - cap, 0)}` };
|
|
4464
|
+
}
|
|
4465
|
+
const imgs = x.content.filter((p) => p.type !== "text").length;
|
|
4466
|
+
let left = cap;
|
|
4467
|
+
let elided = 0;
|
|
4468
|
+
const parts = [];
|
|
4469
|
+
for (const p of x.content) {
|
|
4470
|
+
if (p.type !== "text") {
|
|
4471
|
+
parts.push(p);
|
|
4472
|
+
continue;
|
|
4473
|
+
}
|
|
4474
|
+
const t = p.text ?? "";
|
|
4475
|
+
if (t.length <= left) {
|
|
4476
|
+
left -= t.length;
|
|
4477
|
+
if (t) parts.push(p);
|
|
4478
|
+
continue;
|
|
4479
|
+
}
|
|
4480
|
+
const keep = left;
|
|
4481
|
+
left = 0;
|
|
4482
|
+
elided += t.length - keep;
|
|
4483
|
+
if (keep > 0) parts.push({ ...p, text: t.slice(0, keep) });
|
|
4484
|
+
}
|
|
4485
|
+
if (!elided) return parts.length === x.content.length || !parts.length ? x : { ...x, content: parts };
|
|
4486
|
+
return { ...x, content: [...parts, { type: "text", text: note(elided, imgs).trimStart() }] };
|
|
4487
|
+
}
|
|
4053
4488
|
var callIdSet = (msgs) => {
|
|
4054
4489
|
const ids = /* @__PURE__ */ new Set();
|
|
4055
4490
|
for (const m of msgs) if (m.role === "assistant") for (const tc of m.tool_calls ?? []) ids.add(tc.id);
|
|
@@ -4060,8 +4495,8 @@ function dropOrphanToolResults(messages) {
|
|
|
4060
4495
|
const ok = (m) => m.role !== "tool" || ids.has(m.tool_call_id ?? "");
|
|
4061
4496
|
return messages.every(ok) ? messages : messages.filter(ok);
|
|
4062
4497
|
}
|
|
4063
|
-
function fitTokenBudget(messages, maxTokens) {
|
|
4064
|
-
const per = messages.map((
|
|
4498
|
+
function fitTokenBudget(messages, maxTokens, keepRecentImages = 1) {
|
|
4499
|
+
const per = sendBytesPer(messages, keepRecentImages).map((b) => Math.ceil(b / 4));
|
|
4065
4500
|
let total = per.reduce((a, b) => a + b, 0);
|
|
4066
4501
|
if (total <= maxTokens) return messages;
|
|
4067
4502
|
const head = messages[0]?.role === "system" ? [messages[0]] : [];
|
|
@@ -4070,7 +4505,7 @@ function fitTokenBudget(messages, maxTokens) {
|
|
|
4070
4505
|
const ids = callIdSet(messages.slice(from));
|
|
4071
4506
|
while (from < messages.length && messages[from].role === "tool" && !ids.has(messages[from].tool_call_id ?? "")) total -= per[from++];
|
|
4072
4507
|
if (total > maxTokens)
|
|
4073
|
-
|
|
4508
|
+
log6.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
|
|
4074
4509
|
return [...head, ...messages.slice(from)];
|
|
4075
4510
|
}
|
|
4076
4511
|
function compact(m, max, focus) {
|
|
@@ -4082,11 +4517,24 @@ function compact(m, max, focus) {
|
|
|
4082
4517
|
if (dropped.length === 0) return [...head, ...tail];
|
|
4083
4518
|
return [...head, { role: "system", content: summarize(dropped, focus) }, ...tail];
|
|
4084
4519
|
}
|
|
4520
|
+
var SUMMARY_HEAD = "[summary of ";
|
|
4521
|
+
var MAX_CARRIED_COUNT = 1e4;
|
|
4522
|
+
var isSyntheticSummary = (m) => m.role === "system" && typeof m.content === "string" && m.content.startsWith(SUMMARY_HEAD);
|
|
4085
4523
|
function summarize(messages, focus) {
|
|
4086
4524
|
const tools = /* @__PURE__ */ new Set();
|
|
4087
4525
|
const files = /* @__PURE__ */ new Set();
|
|
4526
|
+
const carried = [];
|
|
4527
|
+
let carriedCount = 0;
|
|
4528
|
+
let priors = 0;
|
|
4088
4529
|
let lastAssistant = "";
|
|
4089
4530
|
for (const msg of messages) {
|
|
4531
|
+
if (isSyntheticSummary(msg)) {
|
|
4532
|
+
priors++;
|
|
4533
|
+
const [head, ...rest] = String(msg.content).split("\n");
|
|
4534
|
+
carriedCount += Math.min(Math.max(Number(/of (\d+)/.exec(head)?.[1]) || 0, 0), MAX_CARRIED_COUNT);
|
|
4535
|
+
for (const l of rest) if (l.trim() && !carried.includes(l) && carried.length < 20) carried.push(l);
|
|
4536
|
+
continue;
|
|
4537
|
+
}
|
|
4090
4538
|
for (const tc of msg.tool_calls ?? []) {
|
|
4091
4539
|
tools.add(tc.function.name);
|
|
4092
4540
|
try {
|
|
@@ -4097,14 +4545,16 @@ function summarize(messages, focus) {
|
|
|
4097
4545
|
}
|
|
4098
4546
|
if (msg.role === "assistant" && msg.content) lastAssistant = contentText(msg.content);
|
|
4099
4547
|
}
|
|
4100
|
-
const lines = [
|
|
4548
|
+
const lines = [`${SUMMARY_HEAD}${messages.length - priors + carriedCount} earlier message(s)]`];
|
|
4101
4549
|
if (tools.size) lines.push(`tools used: ${[...tools].join(", ")}`);
|
|
4102
4550
|
if (files.size) lines.push(`files touched: ${[...files].join(", ")}`);
|
|
4103
4551
|
if (lastAssistant) lines.push(`last assistant: ${lastAssistant}`);
|
|
4552
|
+
if (carried.length) lines.push("earlier recap:", ...carried.map((l) => ` ${l}`));
|
|
4104
4553
|
if (focus?.trim()) {
|
|
4105
4554
|
const words = focus.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
|
|
4106
4555
|
const kept = [];
|
|
4107
|
-
|
|
4556
|
+
const ordered = [...messages.filter((m) => !isSyntheticSummary(m)), ...messages.filter(isSyntheticSummary)];
|
|
4557
|
+
outer: for (const msg of ordered) {
|
|
4108
4558
|
if (msg.role === "tool") continue;
|
|
4109
4559
|
for (const line of contentText(msg.content).split("\n")) {
|
|
4110
4560
|
const l = line.toLowerCase();
|
|
@@ -4627,7 +5077,7 @@ init_tools_web();
|
|
|
4627
5077
|
init_tools();
|
|
4628
5078
|
init_tools_structured();
|
|
4629
5079
|
init_logging();
|
|
4630
|
-
var
|
|
5080
|
+
var log7 = forComponent("scratch");
|
|
4631
5081
|
var SCRATCH_DIR = "/scratch";
|
|
4632
5082
|
function shortArgs(args) {
|
|
4633
5083
|
try {
|
|
@@ -4667,7 +5117,7 @@ var Scratch = class {
|
|
|
4667
5117
|
await (this.dirReady ??= mkdirp(this.fs, dir));
|
|
4668
5118
|
await this.fs.writeFile(path, header + raw);
|
|
4669
5119
|
} catch (e) {
|
|
4670
|
-
|
|
5120
|
+
log7.debug("scratch write failed; returning raw", e);
|
|
4671
5121
|
return raw;
|
|
4672
5122
|
}
|
|
4673
5123
|
const preview = raw.slice(0, previewChars).replace(/\s+/g, " ").trim();
|
|
@@ -4697,7 +5147,7 @@ To pull a specific detail, Grep/Read ${path}, or call Ask({ question: "\u2026",
|
|
|
4697
5147
|
await (this.dirReady ??= mkdirp(this.fs, dir));
|
|
4698
5148
|
await this.fs.writeFile(path, header + full);
|
|
4699
5149
|
} catch (e) {
|
|
4700
|
-
|
|
5150
|
+
log7.debug("scratch spill failed; cropping lossy", e);
|
|
4701
5151
|
return full.slice(0, pageBytes) + `
|
|
4702
5152
|
|
|
4703
5153
|
[output cropped to ${pageBytes} of ${full.length} bytes; full output unavailable (scratch write failed) \u2014 refine your query]`;
|
|
@@ -4743,7 +5193,7 @@ Question: ${q2}`);
|
|
|
4743
5193
|
const answer = (res.text ?? "").trim();
|
|
4744
5194
|
return answer || "(no answer found in scratch)";
|
|
4745
5195
|
} catch (e) {
|
|
4746
|
-
|
|
5196
|
+
log7.debug("Ask peek failed", e);
|
|
4747
5197
|
return `Error querying scratch: ${e?.message ?? e}`;
|
|
4748
5198
|
}
|
|
4749
5199
|
}
|
|
@@ -4752,7 +5202,7 @@ Question: ${q2}`);
|
|
|
4752
5202
|
|
|
4753
5203
|
// src/lessons.ts
|
|
4754
5204
|
init_logging();
|
|
4755
|
-
var
|
|
5205
|
+
var log8 = forComponent("Lessons");
|
|
4756
5206
|
var LessonOptionsDefaults = class {
|
|
4757
5207
|
minRepeats = 2;
|
|
4758
5208
|
};
|
|
@@ -4777,15 +5227,16 @@ function lessonCapture(options) {
|
|
|
4777
5227
|
counts.set(lesson.slug, n);
|
|
4778
5228
|
if (n < o.minRepeats) return;
|
|
4779
5229
|
written.add(lesson.slug);
|
|
4780
|
-
await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) =>
|
|
4781
|
-
|
|
5230
|
+
await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) => log8.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
|
|
5231
|
+
log8.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
|
|
4782
5232
|
}
|
|
4783
5233
|
};
|
|
4784
5234
|
}
|
|
4785
5235
|
|
|
4786
5236
|
// src/reflect.ts
|
|
5237
|
+
init_llm();
|
|
4787
5238
|
init_logging();
|
|
4788
|
-
var
|
|
5239
|
+
var log9 = forComponent("Reflect");
|
|
4789
5240
|
async function reflectOnRun(o) {
|
|
4790
5241
|
const digest = digestRun(o.result.messages, o.maxDigestChars ?? 6e3);
|
|
4791
5242
|
if (!digest.trim()) return null;
|
|
@@ -4801,7 +5252,7 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
|
|
|
4801
5252
|
const r = await o.ai.chat({ model: o.model, messages: [{ role: "user", content: prompt }], stream: false });
|
|
4802
5253
|
text = r?.content ?? "";
|
|
4803
5254
|
} catch (e) {
|
|
4804
|
-
|
|
5255
|
+
log9.warn(`reflection call failed: ${e?.message ?? e}`);
|
|
4805
5256
|
return null;
|
|
4806
5257
|
}
|
|
4807
5258
|
const m = text.match(/LESSON:\s*(.+)/i);
|
|
@@ -4811,10 +5262,10 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
|
|
|
4811
5262
|
try {
|
|
4812
5263
|
await writeFact(o.fs, o.dir, slug2, lesson);
|
|
4813
5264
|
} catch (e) {
|
|
4814
|
-
|
|
5265
|
+
log9.warn(`could not persist lesson: ${e?.message ?? e}`);
|
|
4815
5266
|
return null;
|
|
4816
5267
|
}
|
|
4817
|
-
|
|
5268
|
+
log9.debug(`reflection persisted ${slug2}`);
|
|
4818
5269
|
return slug2;
|
|
4819
5270
|
}
|
|
4820
5271
|
function digestRun(messages, maxChars) {
|
|
@@ -4830,6 +5281,7 @@ function digestRun(messages, maxChars) {
|
|
|
4830
5281
|
|
|
4831
5282
|
// src/duplex.ts
|
|
4832
5283
|
import { MemFilesystem as MemFilesystem2 } from "@livx.cc/wcli/core";
|
|
5284
|
+
init_llm();
|
|
4833
5285
|
init_logging();
|
|
4834
5286
|
|
|
4835
5287
|
// node_modules/@bod.ee/voice/src/core/logging.ts
|
|
@@ -4862,7 +5314,7 @@ function forComponent2(name) {
|
|
|
4862
5314
|
}
|
|
4863
5315
|
|
|
4864
5316
|
// node_modules/@bod.ee/voice/src/core/emotion.ts
|
|
4865
|
-
var
|
|
5317
|
+
var log10 = forComponent2("Emotion");
|
|
4866
5318
|
var EMOTIONS = [
|
|
4867
5319
|
// primary (best results)
|
|
4868
5320
|
"neutral",
|
|
@@ -4994,7 +5446,7 @@ function renderEmotions(text, opts = { show: true }) {
|
|
|
4994
5446
|
speech += cartesiaTag(tag);
|
|
4995
5447
|
if (opts.show) display += m[0];
|
|
4996
5448
|
} else {
|
|
4997
|
-
|
|
5449
|
+
log10.debug(`dropping unknown emotion tag ${m[0]}`);
|
|
4998
5450
|
}
|
|
4999
5451
|
last = m.index + m[0].length;
|
|
5000
5452
|
}
|
|
@@ -5036,7 +5488,7 @@ var EmotionStream = class {
|
|
|
5036
5488
|
if (tag) {
|
|
5037
5489
|
this.pending = tag;
|
|
5038
5490
|
if (this.show) display += m[0];
|
|
5039
|
-
} else
|
|
5491
|
+
} else log10.debug(`dropping unknown emotion tag ${m[0]}`);
|
|
5040
5492
|
last = m.index + m[0].length;
|
|
5041
5493
|
}
|
|
5042
5494
|
this.emit(body.slice(last), (s, d, p) => {
|
|
@@ -5059,7 +5511,7 @@ var EmotionStream = class {
|
|
|
5059
5511
|
};
|
|
5060
5512
|
|
|
5061
5513
|
// node_modules/@bod.ee/voice/src/core/engine.ts
|
|
5062
|
-
var
|
|
5514
|
+
var log11 = forComponent2("VoiceEngine");
|
|
5063
5515
|
var realClock = {
|
|
5064
5516
|
now: () => performance.now(),
|
|
5065
5517
|
setTimeout: (fn, ms) => setTimeout(fn, ms),
|
|
@@ -5334,7 +5786,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5334
5786
|
await Promise.all([this.tts.connect(), this.stt.start()]);
|
|
5335
5787
|
this.tts.warmup?.();
|
|
5336
5788
|
this.setState("listening");
|
|
5337
|
-
|
|
5789
|
+
log11.debug(`voice I/O up (${this.stt.usingAec ? "AEC" : "heuristic echo"} capture)`);
|
|
5338
5790
|
}
|
|
5339
5791
|
get usingAec() {
|
|
5340
5792
|
return this.stt.usingAec;
|
|
@@ -5356,7 +5808,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5356
5808
|
this.options.onDiag({ t: this.clock.now(), kind, ...fields });
|
|
5357
5809
|
} catch (e) {
|
|
5358
5810
|
this.diagOn = false;
|
|
5359
|
-
|
|
5811
|
+
log11.debug(`onDiag threw \u2014 diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
5360
5812
|
}
|
|
5361
5813
|
}
|
|
5362
5814
|
idleWaiters = [];
|
|
@@ -5465,7 +5917,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5465
5917
|
if (prose.trim()) this.repliedSinceDispatch = true;
|
|
5466
5918
|
for (const w of this.words(this.reply)) this.echoWords.add(w);
|
|
5467
5919
|
this.tts.speak(forSpeech(speech), true);
|
|
5468
|
-
if (!this.spokeDeltas && this.turnStartAt)
|
|
5920
|
+
if (!this.spokeDeltas && this.turnStartAt) log11.debug(`ttft: ${Math.round(this.clock.now() - this.turnStartAt)}ms`);
|
|
5469
5921
|
this.spokeDeltas = true;
|
|
5470
5922
|
this.setState("speaking");
|
|
5471
5923
|
if (this.options.revealMode !== "off" && display) {
|
|
@@ -5542,7 +5994,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5542
5994
|
this.drainTimer = null;
|
|
5543
5995
|
this.speaking = false;
|
|
5544
5996
|
this.stopWordReveal(true);
|
|
5545
|
-
if (this.turnStartAt)
|
|
5997
|
+
if (this.turnStartAt) log11.debug(`turn: ${Math.round(this.clock.now() - this.turnStartAt)}ms (incl. playback)`);
|
|
5546
5998
|
this.echoUntil = this.clock.now() + 2500;
|
|
5547
5999
|
if (!this.usingAec) this.stt.reset();
|
|
5548
6000
|
this.setState("listening");
|
|
@@ -5630,7 +6082,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5630
6082
|
this.clearAckTimer("interrupt");
|
|
5631
6083
|
this.adaptiveAckFired = false;
|
|
5632
6084
|
this.ackProbe = null;
|
|
5633
|
-
if (this.uttQueue.length)
|
|
6085
|
+
if (this.uttQueue.length) log11.info(`barge-in dropped ${this.uttQueue.length} queued worker utterance(s)`);
|
|
5634
6086
|
const droppedQueued = this.uttQueue.length;
|
|
5635
6087
|
this.uttQueue = [];
|
|
5636
6088
|
if (!this.speaking && !this.drainTimer) return;
|
|
@@ -5776,7 +6228,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5776
6228
|
if (flat && this.lastDispatchFlat && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS && (this.lastDispatchFlat.startsWith(flat) || flat.startsWith(this.lastDispatchFlat))) return;
|
|
5777
6229
|
this.specSpent = true;
|
|
5778
6230
|
this.specText = this.specPartial;
|
|
5779
|
-
|
|
6231
|
+
log11.debug(`speculate: "${this.specText.slice(0, 60)}"`);
|
|
5780
6232
|
this.diag("speculate", { text: this.specText });
|
|
5781
6233
|
this.options.onSpeculate(this.specText);
|
|
5782
6234
|
}, this.options.speculativeMs);
|
|
@@ -5845,7 +6297,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5845
6297
|
};
|
|
5846
6298
|
this.tts.newContext();
|
|
5847
6299
|
this.tts.speak(phrase, false);
|
|
5848
|
-
|
|
6300
|
+
log11.debug(`backchannel: "${phrase}"`);
|
|
5849
6301
|
this.diag("backchannel", { phrase });
|
|
5850
6302
|
this.options.onBackchannel(phrase);
|
|
5851
6303
|
}
|
|
@@ -5878,7 +6330,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5878
6330
|
});
|
|
5879
6331
|
if (idx < 0) return text;
|
|
5880
6332
|
toks.splice(idx, 1);
|
|
5881
|
-
|
|
6333
|
+
log11.verbose(`stripped backchannel echo "${this.lastBcPhrase}" from dispatching final`);
|
|
5882
6334
|
return toks.join(" ");
|
|
5883
6335
|
}
|
|
5884
6336
|
/** Merge a resumed utterance into the pending one, deduping any word-overlap. Soniox re-finalizes
|
|
@@ -5969,7 +6421,7 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
5969
6421
|
this.pendingUtt = this.mergeUtterance(this.pendingUtt, text);
|
|
5970
6422
|
if (this.pendingTimer) this.clock.clearTimeout(this.pendingTimer);
|
|
5971
6423
|
if (this.options.incompleteMergeMs && this.looksIncomplete(this.pendingUtt)) {
|
|
5972
|
-
|
|
6424
|
+
log11.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
|
|
5973
6425
|
this.diag("hold", { reason: "incomplete", tail: this.pendingUtt.slice(-40) });
|
|
5974
6426
|
this.options.onHold();
|
|
5975
6427
|
if (this.options.holdFiller && !this.speaking) this.speakFiller(this.options.holdFiller);
|
|
@@ -6004,13 +6456,13 @@ var VoiceEngine = class _VoiceEngine {
|
|
|
6004
6456
|
if (text) {
|
|
6005
6457
|
const flat = text.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
6006
6458
|
if (flat && flat === this.lastDispatchFlat && !this.repliedSinceDispatch && this.clock.now() - this.lastDispatchAt < _VoiceEngine.DUP_FINAL_MS) {
|
|
6007
|
-
|
|
6459
|
+
log11.verbose(`dropped duplicate final "${text.slice(0, 40)}" (identical to the just-dispatched utterance, no reply between)`);
|
|
6008
6460
|
this.diag("utterance_drop", { reason: "dup_final", text: text.slice(0, 60) });
|
|
6009
6461
|
return;
|
|
6010
6462
|
}
|
|
6011
6463
|
const refinal = this.refinalizes(text);
|
|
6012
6464
|
if (refinal && this.repliedSinceDispatch) {
|
|
6013
|
-
|
|
6465
|
+
log11.verbose(`dropped re-finalization "${text.slice(0, 60)}" (superset of the just-dispatched utterance, reply already flowing)`);
|
|
6014
6466
|
this.diag("utterance_drop", { reason: "refinalized", text: text.slice(0, 60) });
|
|
6015
6467
|
this.lastDispatchFlat = flat;
|
|
6016
6468
|
this.lastDispatchWords = text.trim().split(/\s+/);
|
|
@@ -6170,7 +6622,7 @@ var SpokenSplitter = class {
|
|
|
6170
6622
|
};
|
|
6171
6623
|
|
|
6172
6624
|
// node_modules/@bod.ee/voice/src/adapters/soniox.ts
|
|
6173
|
-
var
|
|
6625
|
+
var log12 = forComponent2("SonioxSTT");
|
|
6174
6626
|
var now = () => performance.now();
|
|
6175
6627
|
var SonioxSTTOptions = class {
|
|
6176
6628
|
auth = "";
|
|
@@ -6211,7 +6663,7 @@ var SonioxSTT = class {
|
|
|
6211
6663
|
this.onDiag({ t: now(), kind, ...fields });
|
|
6212
6664
|
} catch (e) {
|
|
6213
6665
|
this.diagOn = false;
|
|
6214
|
-
|
|
6666
|
+
log12.debug(`onDiag threw \u2014 STT diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
6215
6667
|
}
|
|
6216
6668
|
}
|
|
6217
6669
|
lastChunkAt = 0;
|
|
@@ -6253,11 +6705,11 @@ var SonioxSTT = class {
|
|
|
6253
6705
|
this.ws.onmessage = (ev) => this.handle(JSON.parse(String(ev.data)));
|
|
6254
6706
|
this.ws.onclose = (ev) => {
|
|
6255
6707
|
if (this.stopped) return;
|
|
6256
|
-
|
|
6708
|
+
log12.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
|
|
6257
6709
|
this.diag("stt_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: true });
|
|
6258
6710
|
this.reset();
|
|
6259
6711
|
this.connectWs().catch((e) => {
|
|
6260
|
-
|
|
6712
|
+
log12.error(`soniox reconnect failed: ${e.message}`);
|
|
6261
6713
|
this.diag("stt_reconnect_failed", { message: e.message });
|
|
6262
6714
|
});
|
|
6263
6715
|
};
|
|
@@ -6269,7 +6721,7 @@ var SonioxSTT = class {
|
|
|
6269
6721
|
this.endpointTimer = setInterval(() => {
|
|
6270
6722
|
const combined = (this.finalText + this.partialText).trim();
|
|
6271
6723
|
if (!combined || now() - this.lastChangeAt < this.options.silenceEndpointMs) return;
|
|
6272
|
-
if (this.firstTokenAt)
|
|
6724
|
+
if (this.firstTokenAt) log12.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
|
|
6273
6725
|
this.reset();
|
|
6274
6726
|
this.onUtterance(combined, now());
|
|
6275
6727
|
}, 120);
|
|
@@ -6281,7 +6733,7 @@ var SonioxSTT = class {
|
|
|
6281
6733
|
if (this.stopped) return;
|
|
6282
6734
|
const ref = this.lastChunkAt || this.startedChunksAt;
|
|
6283
6735
|
if (now() - ref > noAudioMs) {
|
|
6284
|
-
|
|
6736
|
+
log12.error(`stt: no mic audio for >${Math.round(noAudioMs / 1e3)}s \u2014 capture device stopped delivering`);
|
|
6285
6737
|
this.diag("stt_watchdog_fatal", { noAudioMs });
|
|
6286
6738
|
this.onFatal("microphone stopped delivering audio (try a different input device, e.g. AirPods, or check System Settings \u2192 Sound \u2192 Input)");
|
|
6287
6739
|
this.stop();
|
|
@@ -6304,7 +6756,7 @@ var SonioxSTT = class {
|
|
|
6304
6756
|
handle(m) {
|
|
6305
6757
|
if (m.error_message) {
|
|
6306
6758
|
this.diag("stt_error", { message: String(m.error_message), code: m.error_code });
|
|
6307
|
-
return
|
|
6759
|
+
return log12.error(`soniox: ${m.error_message}`);
|
|
6308
6760
|
}
|
|
6309
6761
|
let endpoint = false;
|
|
6310
6762
|
for (const t of m.tokens ?? []) {
|
|
@@ -6321,7 +6773,7 @@ var SonioxSTT = class {
|
|
|
6321
6773
|
this.onPartial(combined);
|
|
6322
6774
|
if (endpoint && this.finalText.trim()) {
|
|
6323
6775
|
const utterance = this.finalText.trim();
|
|
6324
|
-
if (this.firstTokenAt)
|
|
6776
|
+
if (this.firstTokenAt) log12.debug(`stt: ${Math.round(now() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
|
|
6325
6777
|
this.reset();
|
|
6326
6778
|
this.onUtterance(utterance, now());
|
|
6327
6779
|
}
|
|
@@ -6343,7 +6795,7 @@ var SonioxSTT = class {
|
|
|
6343
6795
|
};
|
|
6344
6796
|
|
|
6345
6797
|
// node_modules/@bod.ee/voice/src/adapters/cartesia.ts
|
|
6346
|
-
var
|
|
6798
|
+
var log13 = forComponent2("CartesiaTTS");
|
|
6347
6799
|
var now2 = () => performance.now();
|
|
6348
6800
|
var CartesiaTTSOptions = class {
|
|
6349
6801
|
auth = "";
|
|
@@ -6379,7 +6831,7 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
6379
6831
|
this.onDiag({ t: now2(), kind, ...fields });
|
|
6380
6832
|
} catch (e) {
|
|
6381
6833
|
this.diagOn = false;
|
|
6382
|
-
|
|
6834
|
+
log13.debug(`onDiag threw \u2014 TTS diagnostics disabled: ${e instanceof Error ? e.message : e}`);
|
|
6383
6835
|
}
|
|
6384
6836
|
}
|
|
6385
6837
|
firstAudioAt = 0;
|
|
@@ -6414,11 +6866,11 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
6414
6866
|
this.ws.onerror = (e) => rej(new Error(`cartesia ws: ${e.message || "connect failed"}`));
|
|
6415
6867
|
});
|
|
6416
6868
|
this.ws.onclose = (ev) => {
|
|
6417
|
-
|
|
6869
|
+
log13.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
|
|
6418
6870
|
this.diag("tts_ws_closed", { code: ev.code, reason: String(ev.reason || ""), reconnecting: !this.closed });
|
|
6419
6871
|
if (!this.closed) {
|
|
6420
6872
|
this.connecting = this.doConnect().catch((e) => {
|
|
6421
|
-
|
|
6873
|
+
log13.error(`cartesia reconnect failed: ${e.message}`);
|
|
6422
6874
|
this.diag("tts_reconnect_failed", { message: e.message });
|
|
6423
6875
|
});
|
|
6424
6876
|
}
|
|
@@ -6446,12 +6898,12 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
6446
6898
|
this.down = true;
|
|
6447
6899
|
this.downAt = now2();
|
|
6448
6900
|
this.consecutiveOk = 0;
|
|
6449
|
-
|
|
6901
|
+
log13.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
|
|
6450
6902
|
this.diag("tts_breaker_open", { errors: this.consecutiveErrors });
|
|
6451
6903
|
this.onDone();
|
|
6452
6904
|
this.startProbe();
|
|
6453
6905
|
} else if (!this.down) {
|
|
6454
|
-
(/No valid transcripts/i.test(m.message || "") ?
|
|
6906
|
+
(/No valid transcripts/i.test(m.message || "") ? log13.debug : log13.warn)(`cartesia: ${JSON.stringify(m)}`);
|
|
6455
6907
|
}
|
|
6456
6908
|
}
|
|
6457
6909
|
};
|
|
@@ -6466,7 +6918,7 @@ var CartesiaTTS = class _CartesiaTTS {
|
|
|
6466
6918
|
this.stopProbe();
|
|
6467
6919
|
const downMs = this.downAt ? now2() - this.downAt : 0;
|
|
6468
6920
|
this.diag("tts_breaker_close", { downMs: Math.round(downMs) });
|
|
6469
|
-
(downMs < 2e3 ?
|
|
6921
|
+
(downMs < 2e3 ? log13.debug : log13.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
|
|
6470
6922
|
}
|
|
6471
6923
|
/** Ensure the WS is open before sending — reconnects if idle-closed. */
|
|
6472
6924
|
async ensureConnected() {
|
|
@@ -6557,7 +7009,7 @@ configureLogging((name) => {
|
|
|
6557
7009
|
});
|
|
6558
7010
|
|
|
6559
7011
|
// src/duplex.ts
|
|
6560
|
-
var
|
|
7012
|
+
var log14 = forComponent("DuplexAgent");
|
|
6561
7013
|
function describeCall(call) {
|
|
6562
7014
|
const v = call.args && Object.values(call.args).find((x) => typeof x === "string" && x.trim());
|
|
6563
7015
|
const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
|
|
@@ -6768,7 +7220,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
6768
7220
|
const m = this.reflexBuf.match(RESERVED_EVENT_MARKER) ?? this.reflexBuf.match(RESERVED_EVENT_OPENER);
|
|
6769
7221
|
if (m) {
|
|
6770
7222
|
this.fabricationCut = true;
|
|
6771
|
-
|
|
7223
|
+
log14.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
|
|
6772
7224
|
const safe = this.reflexBuf.slice(this.reflexForwarded, m.index);
|
|
6773
7225
|
if (!safe) return;
|
|
6774
7226
|
if (safe.trim()) this.spokeThisTurn = true;
|
|
@@ -6861,7 +7313,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
6861
7313
|
return {
|
|
6862
7314
|
preToolUse: (call) => {
|
|
6863
7315
|
if (this.spec?.state === "pending" && !_DuplexAgent.SPEC_SAFE_TOOLS.has(call.name)) {
|
|
6864
|
-
|
|
7316
|
+
log14.verbose(`speculation aborted: reflex called ${call.name} on unconfirmed input`);
|
|
6865
7317
|
this.abortSpeculation();
|
|
6866
7318
|
return { block: true, reason: "Speculative turn aborted." };
|
|
6867
7319
|
}
|
|
@@ -6899,7 +7351,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
6899
7351
|
try {
|
|
6900
7352
|
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
7353
|
} catch (e) {
|
|
6902
|
-
|
|
7354
|
+
log14.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
|
|
6903
7355
|
} finally {
|
|
6904
7356
|
this.nudging = false;
|
|
6905
7357
|
}
|
|
@@ -6992,7 +7444,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
6992
7444
|
try {
|
|
6993
7445
|
res = await this.voice.send(spec.text);
|
|
6994
7446
|
} catch (e) {
|
|
6995
|
-
|
|
7447
|
+
log14.warn(`speculative turn failed: ${e instanceof Error ? e.message : e}`);
|
|
6996
7448
|
} finally {
|
|
6997
7449
|
this.voice.options.signal = prevSignal;
|
|
6998
7450
|
}
|
|
@@ -7006,7 +7458,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
7006
7458
|
if (d === "abort") {
|
|
7007
7459
|
if (this.voice.transcript.length > base) this.voice.transcript.length = base;
|
|
7008
7460
|
this.speculativeAbortedCalls++;
|
|
7009
|
-
|
|
7461
|
+
log14.verbose(`speculation aborted (${this.speculativeAbortedCalls} total): "${spec.text.slice(0, 50)}"`);
|
|
7010
7462
|
if (this.spec === spec) this.spec = void 0;
|
|
7011
7463
|
return res ?? empty;
|
|
7012
7464
|
}
|
|
@@ -7271,7 +7723,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
7271
7723
|
this.notify("task_verify", `task ${id}: verifying`, { id });
|
|
7272
7724
|
const cres = await new Agent(checkerOpts).run(checkBrief);
|
|
7273
7725
|
if (cres.finishReason !== "stop") {
|
|
7274
|
-
|
|
7726
|
+
log14.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
|
|
7275
7727
|
this.notify("task_verify", `task ${id}: verify inconclusive (${cres.finishReason})`, { id, finishReason: cres.finishReason });
|
|
7276
7728
|
}
|
|
7277
7729
|
const sum = (a = 0, b = 0) => a + b;
|
|
@@ -7337,14 +7789,14 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
7337
7789
|
* '[task <id> asks] …' and resolves with the user's answer via AnswerTask — or '' on timeout/
|
|
7338
7790
|
* task settle (callers map '' to deny / best-judgment). Workers never block forever. */
|
|
7339
7791
|
parkQuestion(askId, question) {
|
|
7340
|
-
return new Promise((
|
|
7792
|
+
return new Promise((resolve5) => {
|
|
7341
7793
|
let settled = false;
|
|
7342
7794
|
const finish = (answer) => {
|
|
7343
7795
|
if (settled) return;
|
|
7344
7796
|
settled = true;
|
|
7345
7797
|
clearTimeout(timer);
|
|
7346
7798
|
this.pendingAsks.delete(askId);
|
|
7347
|
-
|
|
7799
|
+
resolve5(answer);
|
|
7348
7800
|
};
|
|
7349
7801
|
const timer = setTimeout(() => {
|
|
7350
7802
|
this.notify("task_ask_timeout", `task ${askId}: question timed out \u2014 proceeding without an answer`);
|
|
@@ -7406,8 +7858,8 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
7406
7858
|
}
|
|
7407
7859
|
rec.status = "done";
|
|
7408
7860
|
rec.result = res.text;
|
|
7409
|
-
const incomplete = res.finishReason !== "stop";
|
|
7410
|
-
|
|
7861
|
+
const incomplete = res.finishReason !== "stop" && res.finishReason !== "empty";
|
|
7862
|
+
log14.verbose(`task ${id} done (${res.steps} steps${incomplete ? `, INCOMPLETE: ${res.finishReason}` : ""})`);
|
|
7411
7863
|
this.notify("task_done", `task ${id} (${rec.label}) completed`, {
|
|
7412
7864
|
id,
|
|
7413
7865
|
text: res.text,
|
|
@@ -7449,14 +7901,14 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
7449
7901
|
foldIfSuperseded(rec, note) {
|
|
7450
7902
|
if (!(rec.deliveryParked && this.suppressParkedResume)) return false;
|
|
7451
7903
|
if (note.trim()) this.voice.transcript.push({ role: "assistant", content: note });
|
|
7452
|
-
|
|
7904
|
+
log14.verbose(`task ${rec.id} settled but superseded (${rec.status}) \u2014 folded into transcript, not voiced`);
|
|
7453
7905
|
return true;
|
|
7454
7906
|
}
|
|
7455
7907
|
failTask(rec, msg) {
|
|
7456
7908
|
this.dropAsk(rec.id);
|
|
7457
7909
|
rec.status = "error";
|
|
7458
7910
|
rec.result = msg;
|
|
7459
|
-
|
|
7911
|
+
log14.warn(`task ${rec.id} failed: ${msg}`);
|
|
7460
7912
|
this.notify("task_error", `task ${rec.id} (${rec.label}) failed: ${msg}`);
|
|
7461
7913
|
if (this.foldIfSuperseded(rec, `The "${rec.label}" task failed: ${msg}`)) return;
|
|
7462
7914
|
this.queueRevoice(this.integrationPrompt(rec, "error", msg, "error"), true);
|
|
@@ -7761,11 +8213,11 @@ function buildMcpCatalog(servers) {
|
|
|
7761
8213
|
}
|
|
7762
8214
|
return { specs, routes };
|
|
7763
8215
|
}
|
|
7764
|
-
function searchOverCatalog(servers, specs, routes,
|
|
8216
|
+
function searchOverCatalog(servers, specs, routes, resolve5, options) {
|
|
7765
8217
|
const tools = specs.length ? makeMcpToolSearch(specs, (name, args) => {
|
|
7766
8218
|
const r = routes.get(name);
|
|
7767
8219
|
if (!r) throw new Error(`unknown MCP tool '${name}' \u2014 use ToolSearch to find valid names`);
|
|
7768
|
-
return
|
|
8220
|
+
return resolve5(r.server, r.rawName, args ?? {});
|
|
7769
8221
|
}, options) : [];
|
|
7770
8222
|
return { tools, serverNames: servers, toolCount: specs.length };
|
|
7771
8223
|
}
|
|
@@ -7777,6 +8229,7 @@ function makeMcpToolSearchFromMounted(mounted, options) {
|
|
|
7777
8229
|
|
|
7778
8230
|
// src/index.ts
|
|
7779
8231
|
init_logging();
|
|
8232
|
+
init_llm();
|
|
7780
8233
|
init_worktree();
|
|
7781
8234
|
import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor as CommandExecutor2, registerHeadlessCommands as registerHeadlessCommands2 } from "@livx.cc/wcli/core";
|
|
7782
8235
|
|
|
@@ -7784,7 +8237,7 @@ import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor a
|
|
|
7784
8237
|
init_logging();
|
|
7785
8238
|
import { spawn } from "child_process";
|
|
7786
8239
|
import { createHash } from "crypto";
|
|
7787
|
-
var
|
|
8240
|
+
var log15 = forComponent("mcp");
|
|
7788
8241
|
var PROTOCOL_VERSION = "2025-06-18";
|
|
7789
8242
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
7790
8243
|
var StdioTransport = class {
|
|
@@ -7803,7 +8256,7 @@ var StdioTransport = class {
|
|
|
7803
8256
|
proc.stdout.setEncoding("utf8");
|
|
7804
8257
|
proc.stdout.on("data", (chunk) => this.onData(chunk));
|
|
7805
8258
|
proc.stderr.setEncoding("utf8");
|
|
7806
|
-
proc.stderr.on("data", (chunk) =>
|
|
8259
|
+
proc.stderr.on("data", (chunk) => log15.debug(`[${command}] stderr:`, chunk.trimEnd()));
|
|
7807
8260
|
proc.on("exit", (code) => this.failAll(new Error(`MCP server "${command}" exited (code ${code})`)));
|
|
7808
8261
|
proc.on("error", (e) => this.failAll(e instanceof Error ? e : new Error(String(e))));
|
|
7809
8262
|
}
|
|
@@ -7817,7 +8270,7 @@ var StdioTransport = class {
|
|
|
7817
8270
|
try {
|
|
7818
8271
|
this.dispatch(JSON.parse(line));
|
|
7819
8272
|
} catch (e) {
|
|
7820
|
-
|
|
8273
|
+
log15.debug("dropping non-JSON line from MCP server:", line, e);
|
|
7821
8274
|
}
|
|
7822
8275
|
}
|
|
7823
8276
|
}
|
|
@@ -7843,12 +8296,12 @@ var StdioTransport = class {
|
|
|
7843
8296
|
request(method, params) {
|
|
7844
8297
|
const id = this.nextId++;
|
|
7845
8298
|
const timeoutMs = this.spec.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
7846
|
-
return new Promise((
|
|
8299
|
+
return new Promise((resolve5, reject) => {
|
|
7847
8300
|
const timer = setTimeout(() => {
|
|
7848
8301
|
this.pending.delete(id);
|
|
7849
8302
|
reject(new Error(`MCP request "${method}" timed out after ${timeoutMs}ms`));
|
|
7850
8303
|
}, timeoutMs);
|
|
7851
|
-
this.pending.set(id, { resolve:
|
|
8304
|
+
this.pending.set(id, { resolve: resolve5, reject, timer });
|
|
7852
8305
|
try {
|
|
7853
8306
|
this.write({ jsonrpc: "2.0", id, method, params });
|
|
7854
8307
|
} catch (e) {
|
|
@@ -7866,7 +8319,7 @@ var StdioTransport = class {
|
|
|
7866
8319
|
try {
|
|
7867
8320
|
this.proc?.stdin?.end();
|
|
7868
8321
|
} catch (e) {
|
|
7869
|
-
|
|
8322
|
+
log15.debug("stdin end failed", e);
|
|
7870
8323
|
}
|
|
7871
8324
|
this.proc?.kill();
|
|
7872
8325
|
}
|
|
@@ -7935,7 +8388,7 @@ function parseSseResponse(body) {
|
|
|
7935
8388
|
const obj = JSON.parse(trimmed.slice(5).trim());
|
|
7936
8389
|
if (obj && (obj.result !== void 0 || obj.error !== void 0)) return obj;
|
|
7937
8390
|
} catch (e) {
|
|
7938
|
-
|
|
8391
|
+
log15.debug("skipping unparseable SSE data line", e);
|
|
7939
8392
|
}
|
|
7940
8393
|
}
|
|
7941
8394
|
return {};
|
|
@@ -7981,12 +8434,12 @@ function buildTransport(cfg) {
|
|
|
7981
8434
|
}
|
|
7982
8435
|
function withTimeout(p, ms, label) {
|
|
7983
8436
|
if (!ms || ms <= 0) return p;
|
|
7984
|
-
return new Promise((
|
|
8437
|
+
return new Promise((resolve5, reject) => {
|
|
7985
8438
|
const timer = setTimeout(() => reject(new Error(`MCP "${label}" mount exceeded ${ms}ms`)), ms);
|
|
7986
8439
|
timer.unref?.();
|
|
7987
8440
|
p.then((v) => {
|
|
7988
8441
|
clearTimeout(timer);
|
|
7989
|
-
|
|
8442
|
+
resolve5(v);
|
|
7990
8443
|
}, (e) => {
|
|
7991
8444
|
clearTimeout(timer);
|
|
7992
8445
|
reject(e);
|
|
@@ -8003,7 +8456,7 @@ async function mountWithDeadline(name, cfg, mountTimeoutMs) {
|
|
|
8003
8456
|
return { name, client, tools, specs, serverInfo: init?.serverInfo, config: cfg };
|
|
8004
8457
|
})(), mountTimeoutMs, name);
|
|
8005
8458
|
} catch (e) {
|
|
8006
|
-
await client.close().catch((err2) =>
|
|
8459
|
+
await client.close().catch((err2) => log15.debug(`close after failed mount of "${name}": ${err2}`));
|
|
8007
8460
|
throw e;
|
|
8008
8461
|
}
|
|
8009
8462
|
}
|
|
@@ -8014,15 +8467,15 @@ function validEntries(servers) {
|
|
|
8014
8467
|
return Object.entries(servers).filter(([name, cfg]) => {
|
|
8015
8468
|
if (!cfg || cfg.disabled) return false;
|
|
8016
8469
|
if (!cfg.command && !cfg.url) {
|
|
8017
|
-
|
|
8470
|
+
log15.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
|
|
8018
8471
|
return false;
|
|
8019
8472
|
}
|
|
8020
8473
|
return true;
|
|
8021
8474
|
});
|
|
8022
8475
|
}
|
|
8023
8476
|
function logMountFailure(name, e) {
|
|
8024
|
-
if (e instanceof McpAuthError)
|
|
8025
|
-
else
|
|
8477
|
+
if (e instanceof McpAuthError) log15.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
|
|
8478
|
+
else log15.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
|
|
8026
8479
|
}
|
|
8027
8480
|
async function mountMcpServers(servers = {}, opts = {}) {
|
|
8028
8481
|
const entries = validEntries(servers);
|
|
@@ -8032,7 +8485,7 @@ async function mountMcpServers(servers = {}, opts = {}) {
|
|
|
8032
8485
|
const name = entries[i][0];
|
|
8033
8486
|
if (r.status === "fulfilled") {
|
|
8034
8487
|
out.push(r.value);
|
|
8035
|
-
|
|
8488
|
+
log15.debug(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
|
|
8036
8489
|
} else logMountFailure(name, r.reason);
|
|
8037
8490
|
});
|
|
8038
8491
|
return out;
|
|
@@ -8072,7 +8525,7 @@ var McpPool = class {
|
|
|
8072
8525
|
const prev = this.warm.get(key);
|
|
8073
8526
|
if (prev) {
|
|
8074
8527
|
clearTimeout(prev.timer);
|
|
8075
|
-
if (prev.client !== client) void prev.client.close().catch((err2) =>
|
|
8528
|
+
if (prev.client !== client) void prev.client.close().catch((err2) => log15.debug(`warm-pool replace close failed: ${err2}`));
|
|
8076
8529
|
}
|
|
8077
8530
|
const e = { client, timer: void 0 };
|
|
8078
8531
|
this.warm.set(key, e);
|
|
@@ -8089,7 +8542,7 @@ var McpPool = class {
|
|
|
8089
8542
|
const e = this.warm.get(key);
|
|
8090
8543
|
if (!e) return;
|
|
8091
8544
|
this.warm.delete(key);
|
|
8092
|
-
await e.client.close().catch((err2) =>
|
|
8545
|
+
await e.client.close().catch((err2) => log15.debug(`warm-pool evict close failed: ${err2}`));
|
|
8093
8546
|
}
|
|
8094
8547
|
async closeAll() {
|
|
8095
8548
|
for (const e of this.warm.values()) {
|
|
@@ -8105,8 +8558,8 @@ var defaultPool = new McpPool();
|
|
|
8105
8558
|
|
|
8106
8559
|
// cli/mcpOAuth.ts
|
|
8107
8560
|
import { createServer } from "http";
|
|
8108
|
-
import { randomBytes, createHash as createHash2 } from "crypto";
|
|
8109
|
-
import { readFileSync as
|
|
8561
|
+
import { randomBytes as randomBytes2, createHash as createHash2 } from "crypto";
|
|
8562
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync } from "fs";
|
|
8110
8563
|
import { dirname as dirname2 } from "path";
|
|
8111
8564
|
var McpOAuthOptions = class {
|
|
8112
8565
|
/** Where to persist tokens. Mode 0600. */
|
|
@@ -8134,7 +8587,7 @@ var McpOAuth = class {
|
|
|
8134
8587
|
// -- store I/O ----------------------------------------------------------------
|
|
8135
8588
|
load() {
|
|
8136
8589
|
try {
|
|
8137
|
-
return existsSync(this.options.storePath) ? JSON.parse(
|
|
8590
|
+
return existsSync(this.options.storePath) ? JSON.parse(readFileSync3(this.options.storePath, "utf8")) : {};
|
|
8138
8591
|
} catch {
|
|
8139
8592
|
return {};
|
|
8140
8593
|
}
|
|
@@ -8148,9 +8601,9 @@ var McpOAuth = class {
|
|
|
8148
8601
|
async register(serverUrl) {
|
|
8149
8602
|
const meta = await this.discover(serverUrl);
|
|
8150
8603
|
const clientId = this.options.clientId ?? await this.registerClient(meta, serverUrl);
|
|
8151
|
-
const verifier = b64url(
|
|
8604
|
+
const verifier = b64url(randomBytes2(32));
|
|
8152
8605
|
const challenge = b64url(createHash2("sha256").update(verifier).digest());
|
|
8153
|
-
const state = b64url(
|
|
8606
|
+
const state = b64url(randomBytes2(16));
|
|
8154
8607
|
const { code, redirectUri } = await this.captureCode(meta.authorization_endpoint, clientId, challenge, state);
|
|
8155
8608
|
const tok = await this.exchange(meta.token_endpoint, {
|
|
8156
8609
|
grant_type: "authorization_code",
|
|
@@ -8232,7 +8685,7 @@ var McpOAuth = class {
|
|
|
8232
8685
|
}
|
|
8233
8686
|
/** Start a one-route loopback server, open the browser, and resolve with the returned auth code. */
|
|
8234
8687
|
captureCode(authEndpoint, clientId, challenge, state) {
|
|
8235
|
-
return new Promise((
|
|
8688
|
+
return new Promise((resolve5, reject) => {
|
|
8236
8689
|
const server = createServer((req, res) => {
|
|
8237
8690
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
8238
8691
|
if (url.pathname !== "/callback") {
|
|
@@ -8246,7 +8699,7 @@ var McpOAuth = class {
|
|
|
8246
8699
|
clearTimeout(timer);
|
|
8247
8700
|
if (gotState !== state) return reject(new Error("OAuth state mismatch (possible CSRF) \u2014 aborting"));
|
|
8248
8701
|
if (!code) return reject(new Error(`authorization failed: ${url.searchParams.get("error") ?? "no code"}`));
|
|
8249
|
-
|
|
8702
|
+
resolve5({ code, redirectUri });
|
|
8250
8703
|
});
|
|
8251
8704
|
let redirectUri = "";
|
|
8252
8705
|
const timer = setTimeout(() => {
|
|
@@ -8299,7 +8752,7 @@ function defaultOpenBrowser(url) {
|
|
|
8299
8752
|
// cli/core.ts
|
|
8300
8753
|
import { randomUUID } from "crypto";
|
|
8301
8754
|
import { execFile as execFile2 } from "child_process";
|
|
8302
|
-
import { resolve, basename, join as join5 } from "path";
|
|
8755
|
+
import { resolve as resolve2, basename, join as join5 } from "path";
|
|
8303
8756
|
import { existsSync as existsSync3, mkdirSync as mkdirSync4 } from "fs";
|
|
8304
8757
|
import { platform, arch, release, userInfo, homedir as homedir2, tmpdir } from "os";
|
|
8305
8758
|
init_tools_shell();
|
|
@@ -8307,7 +8760,7 @@ init_tools_shell();
|
|
|
8307
8760
|
// src/tools.notify.ts
|
|
8308
8761
|
init_logging();
|
|
8309
8762
|
import { execFile } from "child_process";
|
|
8310
|
-
var
|
|
8763
|
+
var log17 = forComponent("notify");
|
|
8311
8764
|
function makeNotifyTool(opts = {}) {
|
|
8312
8765
|
const platform2 = opts.platform ?? process.platform;
|
|
8313
8766
|
const run = opts.exec ?? execFile;
|
|
@@ -8328,12 +8781,12 @@ function makeNotifyTool(opts = {}) {
|
|
|
8328
8781
|
if (!msg) return "Error: empty message";
|
|
8329
8782
|
const argv = platform2 === "darwin" ? ["osascript", ["-e", `display notification ${JSON.stringify(msg)} with title ${JSON.stringify(head)}`]] : platform2 === "linux" ? ["notify-send", [head, msg]] : null;
|
|
8330
8783
|
if (!argv) return `Notifications unavailable on ${platform2}.`;
|
|
8331
|
-
return new Promise((
|
|
8784
|
+
return new Promise((resolve5) => {
|
|
8332
8785
|
run(argv[0], argv[1], { timeout: 5e3 }, (e) => {
|
|
8333
8786
|
if (e) {
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
} else
|
|
8787
|
+
log17.debug("notification failed", e);
|
|
8788
|
+
resolve5(`Notification failed: ${e.message}`);
|
|
8789
|
+
} else resolve5("Notification shown.");
|
|
8337
8790
|
});
|
|
8338
8791
|
});
|
|
8339
8792
|
}
|
|
@@ -8345,9 +8798,9 @@ import { BodDB as BodDB2 } from "@bod.ee/db";
|
|
|
8345
8798
|
|
|
8346
8799
|
// cli/util.ts
|
|
8347
8800
|
init_logging();
|
|
8348
|
-
import { existsSync as existsSync2, readFileSync as
|
|
8801
|
+
import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
|
|
8349
8802
|
import { homedir } from "os";
|
|
8350
|
-
var
|
|
8803
|
+
var log18 = forComponent("cli-util");
|
|
8351
8804
|
function dotDirs(base, sub, opts = {}) {
|
|
8352
8805
|
const home = opts.home ?? homedir();
|
|
8353
8806
|
const dirs = [`${base}/.agent/${sub}`, `${base}/.claude/${sub}`, `${home}/.agent/${sub}`, `${home}/.claude/${sub}`];
|
|
@@ -8364,7 +8817,7 @@ function parseJson(text, fallback, what = "json") {
|
|
|
8364
8817
|
try {
|
|
8365
8818
|
return JSON.parse(text);
|
|
8366
8819
|
} catch (e) {
|
|
8367
|
-
|
|
8820
|
+
log18.debug(`parseJson(${what}) failed: ${e.message}`);
|
|
8368
8821
|
return fallback;
|
|
8369
8822
|
}
|
|
8370
8823
|
}
|
|
@@ -8372,9 +8825,9 @@ function readJsonFile(path, fallback) {
|
|
|
8372
8825
|
if (!existsSync2(path)) return fallback;
|
|
8373
8826
|
let text;
|
|
8374
8827
|
try {
|
|
8375
|
-
text =
|
|
8828
|
+
text = readFileSync4(path, "utf8");
|
|
8376
8829
|
} catch (e) {
|
|
8377
|
-
|
|
8830
|
+
log18.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
|
|
8378
8831
|
return fallback;
|
|
8379
8832
|
}
|
|
8380
8833
|
return parseJson(text, fallback, path);
|
|
@@ -8466,7 +8919,7 @@ async function buildAgent(o) {
|
|
|
8466
8919
|
throw new Error(
|
|
8467
8920
|
"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
8921
|
);
|
|
8469
|
-
const cwd =
|
|
8922
|
+
const cwd = resolve2(o.cwd ?? process.cwd());
|
|
8470
8923
|
const disk = new NodeDiskFilesystem("/", { denySymlinks: false });
|
|
8471
8924
|
await disk.init();
|
|
8472
8925
|
const jailedDisk = new JailedFilesystem(disk);
|
|
@@ -8487,7 +8940,7 @@ async function buildAgent(o) {
|
|
|
8487
8940
|
mem.setCwd(cwd);
|
|
8488
8941
|
fs = mem;
|
|
8489
8942
|
} else if (o.boddb) {
|
|
8490
|
-
const root =
|
|
8943
|
+
const root = resolve2(o.boddb);
|
|
8491
8944
|
mkdirSync4(root, { recursive: true });
|
|
8492
8945
|
const db = new BodDB2({ path: join5(root, "meta.db"), sweepInterval: 0, vfs: { storageRoot: join5(root, "files") } });
|
|
8493
8946
|
const bod = new BodDbFilesystem(db);
|
|
@@ -8503,7 +8956,7 @@ async function buildAgent(o) {
|
|
|
8503
8956
|
const mounts = [];
|
|
8504
8957
|
const notes = [];
|
|
8505
8958
|
for (const d of o.addDirs) {
|
|
8506
|
-
const abs =
|
|
8959
|
+
const abs = resolve2(d);
|
|
8507
8960
|
let name = basename(abs) || "dir", prefix = `/${name}`, n = 2;
|
|
8508
8961
|
while (used.has(prefix)) prefix = `/${name}-${n++}`;
|
|
8509
8962
|
used.add(prefix);
|
|
@@ -8638,6 +9091,7 @@ The filesystem root '/' is the real machine root \u2014 you have full filesystem
|
|
|
8638
9091
|
// Context-v2: by default, note-take over a long session (stub tool outputs older than the last 8).
|
|
8639
9092
|
keepToolOutputs: o.keepToolOutputs ?? 8,
|
|
8640
9093
|
...o.maxContextTokens != null ? { maxContextTokens: o.maxContextTokens } : {},
|
|
9094
|
+
...o.contextWindow ? { contextWindow: o.contextWindow } : {},
|
|
8641
9095
|
...o.browserTrim ? { browserTrim: o.browserTrim } : {}
|
|
8642
9096
|
});
|
|
8643
9097
|
}
|
|
@@ -8658,11 +9112,11 @@ var trunc = (s, n) => (s == null ? "" : String(s).length > n ? String(s).slice(0
|
|
|
8658
9112
|
// cli/voice.ts
|
|
8659
9113
|
init_logging();
|
|
8660
9114
|
import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
|
|
8661
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as
|
|
9115
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
8662
9116
|
import { homedir as homedir3 } from "os";
|
|
8663
9117
|
import { dirname as dirname3, join as join6 } from "path";
|
|
8664
9118
|
import { fileURLToPath } from "url";
|
|
8665
|
-
var
|
|
9119
|
+
var log19 = forComponent("VoiceIO");
|
|
8666
9120
|
var now3 = () => performance.now();
|
|
8667
9121
|
var Player = class {
|
|
8668
9122
|
proc = null;
|
|
@@ -8676,7 +9130,7 @@ var Player = class {
|
|
|
8676
9130
|
["-loglevel", "quiet", "-nodisp", "-fflags", "nobuffer", "-flags", "low_delay", "-probesize", "32", "-f", "s16le", "-ar", String(TTS_SAMPLE_RATE), "-ch_layout", "mono", "-i", "-"],
|
|
8677
9131
|
{ stdio: ["pipe", "ignore", "ignore"] }
|
|
8678
9132
|
);
|
|
8679
|
-
this.proc.on("error", (e) =>
|
|
9133
|
+
this.proc.on("error", (e) => log19.warn(`ffplay error: ${e.message}`));
|
|
8680
9134
|
this.proc.stdin.on("error", () => {
|
|
8681
9135
|
});
|
|
8682
9136
|
this.bytesWritten = 0;
|
|
@@ -8714,7 +9168,7 @@ function detectFfmpegMic() {
|
|
|
8714
9168
|
const devices = [...audio.matchAll(/\[(\d+)\] (.+)/g)].map(([, idx, name]) => ({ idx, name: name.trim() }));
|
|
8715
9169
|
const mic = devices.find((d) => /microphone|built-in/i.test(d.name) && !/teams|blackhole|loopback/i.test(d.name)) ?? devices[0];
|
|
8716
9170
|
if (!mic) throw new Error("no audio input device found");
|
|
8717
|
-
|
|
9171
|
+
log19.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
|
|
8718
9172
|
return `:${mic.idx}`;
|
|
8719
9173
|
}
|
|
8720
9174
|
function detectedInputDevice() {
|
|
@@ -8747,18 +9201,18 @@ function resolveAecBinary() {
|
|
|
8747
9201
|
if (!existsSync4(src)) return null;
|
|
8748
9202
|
const cacheDir = join6(homedir3(), ".agent", "cache");
|
|
8749
9203
|
const bin = join6(cacheDir, "mic-aec");
|
|
8750
|
-
if (existsSync4(bin) &&
|
|
9204
|
+
if (existsSync4(bin) && statSync4(bin).mtimeMs >= statSync4(src).mtimeMs) return bin;
|
|
8751
9205
|
if (spawnSync2("which", ["swiftc"]).status !== 0) return null;
|
|
8752
9206
|
mkdirSync5(cacheDir, { recursive: true });
|
|
8753
|
-
|
|
9207
|
+
log19.info("compiling AEC mic helper (first run)\u2026");
|
|
8754
9208
|
const build = spawnSync2("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
|
|
8755
9209
|
if (build.status !== 0) {
|
|
8756
|
-
|
|
9210
|
+
log19.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
|
|
8757
9211
|
return null;
|
|
8758
9212
|
}
|
|
8759
9213
|
const sign = spawnSync2("codesign", ["-fs", "-", bin], { encoding: "utf8" });
|
|
8760
9214
|
if (sign.status !== 0) {
|
|
8761
|
-
|
|
9215
|
+
log19.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
|
|
8762
9216
|
return null;
|
|
8763
9217
|
}
|
|
8764
9218
|
return bin;
|
|
@@ -8800,16 +9254,16 @@ var NodeMicSource = class {
|
|
|
8800
9254
|
this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
|
|
8801
9255
|
} else {
|
|
8802
9256
|
if (spawnSync2("which", ["ffmpeg"]).status !== 0) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
|
|
8803
|
-
|
|
9257
|
+
log19.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
|
|
8804
9258
|
this.proc = spawn2(
|
|
8805
9259
|
"ffmpeg",
|
|
8806
9260
|
["-loglevel", "error", "-f", "avfoundation", "-i", detectFfmpegMic(), "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
|
|
8807
9261
|
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
8808
9262
|
);
|
|
8809
|
-
this.proc.stderr.on("data", (d) =>
|
|
9263
|
+
this.proc.stderr.on("data", (d) => log19.warn(`ffmpeg: ${String(d).trim()}`));
|
|
8810
9264
|
}
|
|
8811
9265
|
this.proc.on("exit", (c) => {
|
|
8812
|
-
if (c && !this.stopped)
|
|
9266
|
+
if (c && !this.stopped) log19.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
|
|
8813
9267
|
});
|
|
8814
9268
|
this.proc.stdout.on("data", (chunk) => onChunk(chunk));
|
|
8815
9269
|
}
|
|
@@ -8875,7 +9329,7 @@ var AecDuplexAudio = class {
|
|
|
8875
9329
|
this.proc.stdin.on("error", () => {
|
|
8876
9330
|
});
|
|
8877
9331
|
this.proc.on("exit", (c) => {
|
|
8878
|
-
if (c && !this.stopped)
|
|
9332
|
+
if (c && !this.stopped) log19.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
|
|
8879
9333
|
});
|
|
8880
9334
|
this.proc.stdout.on("data", (chunk) => {
|
|
8881
9335
|
this.gotChunk = true;
|
|
@@ -8891,7 +9345,7 @@ var AecDuplexAudio = class {
|
|
|
8891
9345
|
openMicSettings();
|
|
8892
9346
|
this.onFatal?.("microphone permission denied \u2014 enable it in System Settings \u2192 Privacy & Security \u2192 Microphone for your terminal, then restart it");
|
|
8893
9347
|
}
|
|
8894
|
-
} else
|
|
9348
|
+
} else log19.debug(`mic-aec: ${s}`);
|
|
8895
9349
|
}
|
|
8896
9350
|
});
|
|
8897
9351
|
if (!this.noVpio && !this.triedFallback) {
|
|
@@ -8900,7 +9354,7 @@ var AecDuplexAudio = class {
|
|
|
8900
9354
|
this.triedFallback = true;
|
|
8901
9355
|
this.noVpio = true;
|
|
8902
9356
|
this._aec = false;
|
|
8903
|
-
|
|
9357
|
+
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
9358
|
this.onDegrade?.();
|
|
8905
9359
|
this.killProc();
|
|
8906
9360
|
this.spawnHelper();
|
|
@@ -9077,7 +9531,7 @@ function fakeVoiceParts(uttFile) {
|
|
|
9077
9531
|
timer = setInterval(() => {
|
|
9078
9532
|
let buf;
|
|
9079
9533
|
try {
|
|
9080
|
-
buf =
|
|
9534
|
+
buf = readFileSync5(uttFile, "utf8");
|
|
9081
9535
|
} catch {
|
|
9082
9536
|
return;
|
|
9083
9537
|
}
|
|
@@ -9120,7 +9574,7 @@ function fakeVoiceParts(uttFile) {
|
|
|
9120
9574
|
init_logging();
|
|
9121
9575
|
import { appendFile, mkdirSync as mkdirSync6 } from "fs";
|
|
9122
9576
|
import { dirname as dirname4 } from "path";
|
|
9123
|
-
var
|
|
9577
|
+
var log20 = forComponent("VoiceDiag");
|
|
9124
9578
|
var JsonlDiagSink = class {
|
|
9125
9579
|
constructor(path) {
|
|
9126
9580
|
this.path = path;
|
|
@@ -9161,7 +9615,7 @@ var JsonlDiagSink = class {
|
|
|
9161
9615
|
disable(e) {
|
|
9162
9616
|
this.disabled = true;
|
|
9163
9617
|
this.queue.length = 0;
|
|
9164
|
-
|
|
9618
|
+
log20.debug(`voice diag sink disabled (${this.path}): ${e instanceof Error ? e.message : e}`);
|
|
9165
9619
|
}
|
|
9166
9620
|
/** True once a failure permanently disabled the sink (tests/inspection). */
|
|
9167
9621
|
get isDisabled() {
|
|
@@ -9175,7 +9629,7 @@ var JsonlDiagSink = class {
|
|
|
9175
9629
|
|
|
9176
9630
|
// cli/config.ts
|
|
9177
9631
|
import { homedir as homedir4 } from "os";
|
|
9178
|
-
import { existsSync as existsSync5, readFileSync as
|
|
9632
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
9179
9633
|
import { join as join7 } from "path";
|
|
9180
9634
|
import { pathToFileURL } from "url";
|
|
9181
9635
|
var FILES = ["config.ts", "config.js", "config.mjs", "config.json"];
|
|
@@ -9197,7 +9651,7 @@ function loadSettings(dir) {
|
|
|
9197
9651
|
const p = join7(dir, ".agent", "settings.json");
|
|
9198
9652
|
if (!existsSync5(p)) return {};
|
|
9199
9653
|
try {
|
|
9200
|
-
const raw = JSON.parse(
|
|
9654
|
+
const raw = JSON.parse(readFileSync6(p, "utf8"));
|
|
9201
9655
|
const cfg = {};
|
|
9202
9656
|
if (raw.mcpServers && typeof raw.mcpServers === "object") cfg.mcpServers = raw.mcpServers;
|
|
9203
9657
|
if (raw.permissions && typeof raw.permissions === "object") cfg.permissions = raw.permissions;
|
|
@@ -9231,7 +9685,7 @@ async function loadConfig(cwd) {
|
|
|
9231
9685
|
|
|
9232
9686
|
// cli/hooks-config.ts
|
|
9233
9687
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
9234
|
-
var
|
|
9688
|
+
var log21 = forComponent("hooks");
|
|
9235
9689
|
var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
9236
9690
|
function ruleMatches(rule, toolName) {
|
|
9237
9691
|
if (!rule.tool || rule.tool === "*") return true;
|
|
@@ -9248,7 +9702,7 @@ function runCmd(rule, env) {
|
|
|
9248
9702
|
});
|
|
9249
9703
|
return { code: r.status ?? 1, out: ((r.stdout ?? "") + (r.stderr ?? "")).trim() };
|
|
9250
9704
|
} catch (e) {
|
|
9251
|
-
|
|
9705
|
+
log21.debug(`hook command failed: ${rule.command}`, e);
|
|
9252
9706
|
return { code: 1, out: String(e?.message ?? e) };
|
|
9253
9707
|
}
|
|
9254
9708
|
}
|
|
@@ -9352,10 +9806,10 @@ function formatDiff(ops, opts = {}) {
|
|
|
9352
9806
|
}
|
|
9353
9807
|
|
|
9354
9808
|
// cli/session.ts
|
|
9355
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as
|
|
9809
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync4, readdirSync, renameSync, symlinkSync as symlinkSync2, unlinkSync, readlinkSync } from "fs";
|
|
9356
9810
|
import { homedir as homedir5 } from "os";
|
|
9357
9811
|
import { join as join8 } from "path";
|
|
9358
|
-
var
|
|
9812
|
+
var log22 = forComponent("session");
|
|
9359
9813
|
var globalDir = () => join8(homedir5(), ".agent", "sessions");
|
|
9360
9814
|
var SessionStore = class {
|
|
9361
9815
|
dir;
|
|
@@ -9400,15 +9854,15 @@ var SessionStore = class {
|
|
|
9400
9854
|
}
|
|
9401
9855
|
load(id) {
|
|
9402
9856
|
if (!this.safeId(id)) {
|
|
9403
|
-
|
|
9857
|
+
log22.debug(`rejecting unsafe session id: ${id}`);
|
|
9404
9858
|
return void 0;
|
|
9405
9859
|
}
|
|
9406
9860
|
const path = join8(this.dir, `${id}.json`);
|
|
9407
9861
|
if (!existsSync6(path)) return void 0;
|
|
9408
9862
|
try {
|
|
9409
|
-
return JSON.parse(
|
|
9863
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
9410
9864
|
} catch (e) {
|
|
9411
|
-
|
|
9865
|
+
log22.debug(`unreadable session ${id} \u2014 ignoring`, e);
|
|
9412
9866
|
return void 0;
|
|
9413
9867
|
}
|
|
9414
9868
|
}
|
|
@@ -9419,9 +9873,9 @@ var SessionStore = class {
|
|
|
9419
9873
|
for (const f of readdirSync(this.dir)) {
|
|
9420
9874
|
if (!f.endsWith(".json")) continue;
|
|
9421
9875
|
try {
|
|
9422
|
-
metas.push(JSON.parse(
|
|
9876
|
+
metas.push(JSON.parse(readFileSync7(join8(this.dir, f), "utf8")).meta);
|
|
9423
9877
|
} catch (e) {
|
|
9424
|
-
|
|
9878
|
+
log22.debug(`skipping unreadable session file ${f}`, e);
|
|
9425
9879
|
}
|
|
9426
9880
|
}
|
|
9427
9881
|
return metas.sort((a, b) => b.updated - a.updated);
|
|
@@ -9442,7 +9896,7 @@ function globalSessionLoad(idOrPrefix) {
|
|
|
9442
9896
|
if (existsSync6(exact)) {
|
|
9443
9897
|
try {
|
|
9444
9898
|
const target = readlinkSync(exact);
|
|
9445
|
-
return JSON.parse(
|
|
9899
|
+
return JSON.parse(readFileSync7(target, "utf8"));
|
|
9446
9900
|
} catch {
|
|
9447
9901
|
return void 0;
|
|
9448
9902
|
}
|
|
@@ -9453,7 +9907,7 @@ function globalSessionLoad(idOrPrefix) {
|
|
|
9453
9907
|
const base = f.slice(0, -5);
|
|
9454
9908
|
if (base.includes(idOrPrefix) || base.endsWith(idOrPrefix)) {
|
|
9455
9909
|
const target = readlinkSync(join8(gd, f));
|
|
9456
|
-
return JSON.parse(
|
|
9910
|
+
return JSON.parse(readFileSync7(target, "utf8"));
|
|
9457
9911
|
}
|
|
9458
9912
|
}
|
|
9459
9913
|
} catch {
|
|
@@ -9475,7 +9929,7 @@ function globalSessionList() {
|
|
|
9475
9929
|
}
|
|
9476
9930
|
continue;
|
|
9477
9931
|
}
|
|
9478
|
-
metas.push(JSON.parse(
|
|
9932
|
+
metas.push(JSON.parse(readFileSync7(target, "utf8")).meta);
|
|
9479
9933
|
} catch {
|
|
9480
9934
|
}
|
|
9481
9935
|
}
|
|
@@ -9589,13 +10043,13 @@ import { execFile as execFile3 } from "child_process";
|
|
|
9589
10043
|
import { promisify } from "util";
|
|
9590
10044
|
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync8 } from "fs";
|
|
9591
10045
|
import { homedir as homedir6 } from "os";
|
|
9592
|
-
import { join as join9, resolve as
|
|
9593
|
-
var
|
|
10046
|
+
import { join as join9, resolve as resolve3, sep as sep3, parse as parsePath } from "path";
|
|
10047
|
+
var log23 = forComponent("checkpoints");
|
|
9594
10048
|
var exec = promisify(execFile3);
|
|
9595
10049
|
var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
|
|
9596
10050
|
function isBroadRoot(p) {
|
|
9597
|
-
const abs =
|
|
9598
|
-
return abs ===
|
|
10051
|
+
const abs = resolve3(p);
|
|
10052
|
+
return abs === resolve3(homedir6()) || abs === parsePath(abs).root;
|
|
9599
10053
|
}
|
|
9600
10054
|
var ShadowRepo = class {
|
|
9601
10055
|
// undefined = unprobed; false = git/this root unusable
|
|
@@ -9636,7 +10090,7 @@ var ShadowRepo = class {
|
|
|
9636
10090
|
writeFileSync5(join9(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
|
|
9637
10091
|
this.ready = true;
|
|
9638
10092
|
} catch (e) {
|
|
9639
|
-
|
|
10093
|
+
log23.debug(`git checkpoints unavailable for ${this.workTree}`, e);
|
|
9640
10094
|
this.ready = false;
|
|
9641
10095
|
}
|
|
9642
10096
|
return this.ready;
|
|
@@ -9647,7 +10101,7 @@ var ShadowRepo = class {
|
|
|
9647
10101
|
}
|
|
9648
10102
|
async commit(label, forced = []) {
|
|
9649
10103
|
await this.run("add", "-A");
|
|
9650
|
-
for (const p of forced) await this.run("add", "-f", "--", p).catch((e) =>
|
|
10104
|
+
for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log23.debug(`force-add failed: ${p}`, e));
|
|
9651
10105
|
await this.run("commit", "--allow-empty", "-q", "-m", label);
|
|
9652
10106
|
}
|
|
9653
10107
|
/** Inject the CURRENT (pre-edit) content of `paths` into the turn-open restore point by amending it.
|
|
@@ -9655,8 +10109,8 @@ var ShadowRepo = class {
|
|
|
9655
10109
|
* turn-boundary `add -A` would never have captured it. Amend (vs a new commit) keeps one restore
|
|
9656
10110
|
* point per turn, so the REPL's turn↔frame mapping stays intact. */
|
|
9657
10111
|
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) =>
|
|
10112
|
+
for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log23.debug(`force-capture failed: ${p}`, e));
|
|
10113
|
+
await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log23.debug("amend failed", e));
|
|
9660
10114
|
}
|
|
9661
10115
|
/** Commits on `ref`, oldest-first (canonical index space). */
|
|
9662
10116
|
async log(ref) {
|
|
@@ -9716,7 +10170,7 @@ var ShadowRepo = class {
|
|
|
9716
10170
|
await this.run("gc", "--auto", "-q").catch(() => {
|
|
9717
10171
|
});
|
|
9718
10172
|
} catch (e) {
|
|
9719
|
-
|
|
10173
|
+
log23.debug("checkpoint prune failed", e);
|
|
9720
10174
|
}
|
|
9721
10175
|
}
|
|
9722
10176
|
};
|
|
@@ -9742,12 +10196,12 @@ var GitCheckpoints = class {
|
|
|
9742
10196
|
}
|
|
9743
10197
|
/** cwd + any add-dir mounts that live OUTSIDE cwd (in-cwd mounts are already covered by the cwd root). */
|
|
9744
10198
|
roots() {
|
|
9745
|
-
const cwd =
|
|
10199
|
+
const cwd = resolve3(this.options.workTree);
|
|
9746
10200
|
const out = [{ workTree: cwd, gitDir: this.options.gitDir }];
|
|
9747
10201
|
for (const d of this.options.addDirs ?? []) {
|
|
9748
|
-
const abs =
|
|
9749
|
-
if (abs === cwd || abs.startsWith(cwd +
|
|
9750
|
-
if (cwd.startsWith(abs +
|
|
10202
|
+
const abs = resolve3(d);
|
|
10203
|
+
if (abs === cwd || abs.startsWith(cwd + sep3)) continue;
|
|
10204
|
+
if (cwd.startsWith(abs + sep3)) continue;
|
|
9751
10205
|
out.push({ workTree: abs, gitDir: join9(abs, ".agent", "checkpoints.git") });
|
|
9752
10206
|
}
|
|
9753
10207
|
return out;
|
|
@@ -9784,7 +10238,7 @@ var GitCheckpoints = class {
|
|
|
9784
10238
|
use(sessionId) {
|
|
9785
10239
|
if (sessionId === this.session) return;
|
|
9786
10240
|
this.session = sessionId;
|
|
9787
|
-
if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) =>
|
|
10241
|
+
if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log23.debug("re-point failed", e));
|
|
9788
10242
|
}
|
|
9789
10243
|
async begin(label) {
|
|
9790
10244
|
if (!await this.start()) return;
|
|
@@ -9804,7 +10258,7 @@ var GitCheckpoints = class {
|
|
|
9804
10258
|
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
10259
|
`);
|
|
9806
10260
|
} else {
|
|
9807
|
-
|
|
10261
|
+
log23.debug("checkpoint commit failed", e);
|
|
9808
10262
|
survivors.push(this.repos[i]);
|
|
9809
10263
|
survivorCaches.push(this.caches[i] ?? []);
|
|
9810
10264
|
}
|
|
@@ -9819,8 +10273,8 @@ var GitCheckpoints = class {
|
|
|
9819
10273
|
repoIndexFor(abs) {
|
|
9820
10274
|
let best = -1, bestLen = -1;
|
|
9821
10275
|
this.repos.forEach((r, i) => {
|
|
9822
|
-
const wt = r.root.endsWith(
|
|
9823
|
-
if ((abs === wt || abs.startsWith(wt +
|
|
10276
|
+
const wt = r.root.endsWith(sep3) ? r.root.slice(0, -1) : r.root;
|
|
10277
|
+
if ((abs === wt || abs.startsWith(wt + sep3)) && wt.length > bestLen) {
|
|
9824
10278
|
best = i;
|
|
9825
10279
|
bestLen = wt.length;
|
|
9826
10280
|
}
|
|
@@ -9835,11 +10289,11 @@ var GitCheckpoints = class {
|
|
|
9835
10289
|
preToolUse: async (call) => {
|
|
9836
10290
|
if (!this.snapshotted || !this.repos.length) return;
|
|
9837
10291
|
for (const rel of mutatedPaths(call)) {
|
|
9838
|
-
const abs =
|
|
10292
|
+
const abs = resolve3(this.options.workTree, rel);
|
|
9839
10293
|
if (this.forced.has(abs)) continue;
|
|
9840
10294
|
this.forced.add(abs);
|
|
9841
10295
|
const i = this.repoIndexFor(abs);
|
|
9842
|
-
if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) =>
|
|
10296
|
+
if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log23.debug("amendForced failed", e));
|
|
9843
10297
|
}
|
|
9844
10298
|
}
|
|
9845
10299
|
};
|
|
@@ -10039,7 +10493,7 @@ function completePath(listDir, ref) {
|
|
|
10039
10493
|
// cli/lineEditor.ts
|
|
10040
10494
|
import { emitKeypressEvents } from "readline";
|
|
10041
10495
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
10042
|
-
import { writeFileSync as writeFileSync7, readFileSync as
|
|
10496
|
+
import { writeFileSync as writeFileSync7, readFileSync as readFileSync8, unlinkSync as unlinkSync2 } from "fs";
|
|
10043
10497
|
import { tmpdir as tmpdir2 } from "os";
|
|
10044
10498
|
import { join as join11 } from "path";
|
|
10045
10499
|
|
|
@@ -10635,7 +11089,8 @@ var EditorState = class _EditorState {
|
|
|
10635
11089
|
const lines = text.split("\n").length;
|
|
10636
11090
|
const id = ++this.pasteSeq;
|
|
10637
11091
|
const measure = multiline ? `+${lines} line${lines === 1 ? "" : "s"}` : `+${text.length} chars`;
|
|
10638
|
-
const
|
|
11092
|
+
const preview = pastePreview(text);
|
|
11093
|
+
const placeholder = `[Pasted #${id}${preview ? ` "${preview}"` : ""} ${measure}]`;
|
|
10639
11094
|
this.pastes.set(placeholder, text);
|
|
10640
11095
|
this.insert(placeholder);
|
|
10641
11096
|
} else {
|
|
@@ -10914,6 +11369,12 @@ var EditorState = class _EditorState {
|
|
|
10914
11369
|
}
|
|
10915
11370
|
};
|
|
10916
11371
|
var REWIND = "\0REWIND";
|
|
11372
|
+
var PASTE_PREVIEW_MAX = 40;
|
|
11373
|
+
function pastePreview(text) {
|
|
11374
|
+
const line = text.split("\n").find((l) => l.trim()) ?? "";
|
|
11375
|
+
const clean2 = line.replace(/[[\]]/g, "").replace(/\s+/g, " ").trim();
|
|
11376
|
+
return clean2.length > PASTE_PREVIEW_MAX ? clean2.slice(0, PASTE_PREVIEW_MAX) + "\u2026" : clean2;
|
|
11377
|
+
}
|
|
10917
11378
|
function applyVimNormal(s, key, str) {
|
|
10918
11379
|
const k = key?.name;
|
|
10919
11380
|
if (key?.ctrl || key?.meta) return "pass";
|
|
@@ -11228,7 +11689,7 @@ function createLineEditor(out, input = keyInput) {
|
|
|
11228
11689
|
out.write("\x1B[?2004l");
|
|
11229
11690
|
const r = spawnSync4(cmd, [...cargs, file], { stdio: "inherit" });
|
|
11230
11691
|
if (r.status === 0) {
|
|
11231
|
-
const text =
|
|
11692
|
+
const text = readFileSync8(file, "utf8").replace(/\n$/, "");
|
|
11232
11693
|
s.reset();
|
|
11233
11694
|
if (text) s.insert(text);
|
|
11234
11695
|
}
|
|
@@ -11264,10 +11725,10 @@ function createLineEditor(out, input = keyInput) {
|
|
|
11264
11725
|
render(s, promptOf(), maxVisible, opts.status);
|
|
11265
11726
|
};
|
|
11266
11727
|
process.on("SIGWINCH", onResize);
|
|
11267
|
-
return new Promise((
|
|
11728
|
+
return new Promise((resolve5) => {
|
|
11268
11729
|
activeAbort = () => {
|
|
11269
11730
|
finish();
|
|
11270
|
-
|
|
11731
|
+
resolve5(null);
|
|
11271
11732
|
};
|
|
11272
11733
|
const redraw = () => render(s, promptOf(), maxVisible, opts.status);
|
|
11273
11734
|
let lastStatus = opts.status?.() ?? "";
|
|
@@ -11347,20 +11808,20 @@ function createLineEditor(out, input = keyInput) {
|
|
|
11347
11808
|
if (action === "submit") {
|
|
11348
11809
|
finish();
|
|
11349
11810
|
opts.onSubmit?.();
|
|
11350
|
-
return
|
|
11811
|
+
return resolve5(s.expand());
|
|
11351
11812
|
}
|
|
11352
11813
|
if (action === "eof") {
|
|
11353
11814
|
finish();
|
|
11354
|
-
return
|
|
11815
|
+
return resolve5(null);
|
|
11355
11816
|
}
|
|
11356
11817
|
if (action === "rewind") {
|
|
11357
11818
|
finish();
|
|
11358
|
-
return
|
|
11819
|
+
return resolve5(REWIND);
|
|
11359
11820
|
}
|
|
11360
11821
|
if (action === "cancel") {
|
|
11361
11822
|
if (s.buf.length === 0) {
|
|
11362
11823
|
finish();
|
|
11363
|
-
return
|
|
11824
|
+
return resolve5(null);
|
|
11364
11825
|
}
|
|
11365
11826
|
s.reset();
|
|
11366
11827
|
s.refresh();
|
|
@@ -11473,7 +11934,7 @@ function selectMenu(out, opts) {
|
|
|
11473
11934
|
out.write(dim2(` ${filterHint}\u2191/\u2193 select \xB7 ${back}\u23CE choose \xB7 esc cancel${more}`));
|
|
11474
11935
|
prevLines++;
|
|
11475
11936
|
};
|
|
11476
|
-
return new Promise((
|
|
11937
|
+
return new Promise((resolve5) => {
|
|
11477
11938
|
const wasRaw = !!keyInput.isRaw;
|
|
11478
11939
|
menusOpen++;
|
|
11479
11940
|
emitKeypressEvents(keyInput);
|
|
@@ -11491,7 +11952,7 @@ function selectMenu(out, opts) {
|
|
|
11491
11952
|
} catch {
|
|
11492
11953
|
}
|
|
11493
11954
|
}
|
|
11494
|
-
|
|
11955
|
+
resolve5(val);
|
|
11495
11956
|
};
|
|
11496
11957
|
const drillIn = () => {
|
|
11497
11958
|
const it = items[sel];
|
|
@@ -11612,19 +12073,19 @@ function windowAround(count, sel, max) {
|
|
|
11612
12073
|
var plainBuf = "";
|
|
11613
12074
|
var plainEnded = false;
|
|
11614
12075
|
function readPlainLine() {
|
|
11615
|
-
return new Promise((
|
|
12076
|
+
return new Promise((resolve5) => {
|
|
11616
12077
|
const emit = () => {
|
|
11617
12078
|
const nl = plainBuf.indexOf("\n");
|
|
11618
12079
|
if (nl >= 0) {
|
|
11619
12080
|
const line = plainBuf.slice(0, nl);
|
|
11620
12081
|
plainBuf = plainBuf.slice(nl + 1);
|
|
11621
|
-
|
|
12082
|
+
resolve5(line);
|
|
11622
12083
|
return true;
|
|
11623
12084
|
}
|
|
11624
12085
|
if (plainEnded) {
|
|
11625
12086
|
const rest = plainBuf;
|
|
11626
12087
|
plainBuf = "";
|
|
11627
|
-
|
|
12088
|
+
resolve5(rest.length ? rest : null);
|
|
11628
12089
|
return true;
|
|
11629
12090
|
}
|
|
11630
12091
|
return false;
|
|
@@ -11655,7 +12116,7 @@ import { spawnSync as spawnSync5 } from "child_process";
|
|
|
11655
12116
|
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync7 } from "fs";
|
|
11656
12117
|
import { homedir as homedir8 } from "os";
|
|
11657
12118
|
import { join as join12 } from "path";
|
|
11658
|
-
var
|
|
12119
|
+
var log24 = forComponent("os-sched");
|
|
11659
12120
|
var OsScheduler = class {
|
|
11660
12121
|
options;
|
|
11661
12122
|
constructor(options) {
|
|
@@ -11717,7 +12178,7 @@ var OsScheduler = class {
|
|
|
11717
12178
|
}
|
|
11718
12179
|
}
|
|
11719
12180
|
} catch (e) {
|
|
11720
|
-
|
|
12181
|
+
log24.debug(`cancel ${id}`, e);
|
|
11721
12182
|
}
|
|
11722
12183
|
for (const f of [`${id}.json`, `${id}.sh`]) {
|
|
11723
12184
|
try {
|
|
@@ -11834,7 +12295,7 @@ import { spawn as spawn3 } from "child_process";
|
|
|
11834
12295
|
import { existsSync as existsSync8, mkdirSync as mkdirSync11, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
|
|
11835
12296
|
import { homedir as homedir9 } from "os";
|
|
11836
12297
|
import { join as join13 } from "path";
|
|
11837
|
-
var
|
|
12298
|
+
var log25 = forComponent("remote-trigger");
|
|
11838
12299
|
var TRIGGER_DIR = () => join13(homedir9(), ".agent", "triggers");
|
|
11839
12300
|
var sockPath = (sessionId, dir = TRIGGER_DIR()) => join13(dir, `${sessionId}.sock`);
|
|
11840
12301
|
var TriggerServer = class {
|
|
@@ -11872,13 +12333,13 @@ var TriggerServer = class {
|
|
|
11872
12333
|
conn.end(JSON.stringify({ ok: false, error: String(e) }) + "\n");
|
|
11873
12334
|
}
|
|
11874
12335
|
});
|
|
11875
|
-
conn.on("error", (e) =>
|
|
12336
|
+
conn.on("error", (e) => log25.debug("trigger conn error", e));
|
|
11876
12337
|
});
|
|
11877
|
-
this.server.on("error", (e) =>
|
|
12338
|
+
this.server.on("error", (e) => log25.debug("trigger server error", e));
|
|
11878
12339
|
this.server.listen(p);
|
|
11879
12340
|
this.path = p;
|
|
11880
12341
|
} catch (e) {
|
|
11881
|
-
|
|
12342
|
+
log25.debug("trigger server unavailable", e);
|
|
11882
12343
|
}
|
|
11883
12344
|
}
|
|
11884
12345
|
/** Re-bind on /resume (the session id changed). */
|
|
@@ -12000,10 +12461,10 @@ var strike = C("9");
|
|
|
12000
12461
|
var link = (text, url) => useColor ? `\x1B]8;;${url}\x1B\\${cyan(text)}\x1B]8;;\x1B\\` : `${text} (${url})`;
|
|
12001
12462
|
var mdPalette = { bold, dim, cyan, italic, strike, green, link };
|
|
12002
12463
|
var err = (s) => process.stderr.write(s);
|
|
12003
|
-
var
|
|
12464
|
+
var log26 = forComponent("cli");
|
|
12004
12465
|
var VERSION = (() => {
|
|
12005
12466
|
try {
|
|
12006
|
-
return JSON.parse(
|
|
12467
|
+
return JSON.parse(readFileSync9(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
|
|
12007
12468
|
} catch {
|
|
12008
12469
|
return "?";
|
|
12009
12470
|
}
|
|
@@ -12255,7 +12716,7 @@ REPL multi-line: Option/Alt+Enter inserts a newline, or end a line with \\ to co
|
|
|
12255
12716
|
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
12717
|
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
12718
|
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
|
|
12719
|
+
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
12720
|
function newestModel() {
|
|
12260
12721
|
return listModels().slice().sort((a, b) => (getModelInfo(b)?.releaseDate ?? "").localeCompare(getModelInfo(a)?.releaseDate ?? ""))[0] ?? "";
|
|
12261
12722
|
}
|
|
@@ -12290,7 +12751,7 @@ function resolveModelOrNewest(model) {
|
|
|
12290
12751
|
var ENV_KEY_ALIASES = { google: ["GEMINI_API_KEY"] };
|
|
12291
12752
|
function loadEnvFile(file) {
|
|
12292
12753
|
if (!existsSync9(file)) return;
|
|
12293
|
-
for (const line of
|
|
12754
|
+
for (const line of readFileSync9(file, "utf8").split("\n")) {
|
|
12294
12755
|
const m = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
12295
12756
|
if (!m || m[1] in process.env) continue;
|
|
12296
12757
|
let val = m[2].trim();
|
|
@@ -12745,7 +13206,7 @@ function costOf(pricing, promptTokens = 0, completionTokens = 0, cacheCreationTo
|
|
|
12745
13206
|
function turnCost(model, usage) {
|
|
12746
13207
|
return costOf(getModelInfo(model)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, model);
|
|
12747
13208
|
}
|
|
12748
|
-
async function evaluateGoal(ai, condition, transcript,
|
|
13209
|
+
async function evaluateGoal(ai, condition, transcript, log27) {
|
|
12749
13210
|
const recent = transcript.filter((m) => m.role === "assistant").slice(-8).map((m) => {
|
|
12750
13211
|
const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
|
|
12751
13212
|
return text.slice(0, 600);
|
|
@@ -12765,7 +13226,7 @@ ${recent}` }
|
|
|
12765
13226
|
const match = r.content.match(/\{[\s\S]*\}/);
|
|
12766
13227
|
if (match) return JSON.parse(match[0]);
|
|
12767
13228
|
} catch (e) {
|
|
12768
|
-
|
|
13229
|
+
log27(dim(` (goal evaluator error: ${e?.message ?? e})
|
|
12769
13230
|
`));
|
|
12770
13231
|
}
|
|
12771
13232
|
return { met: false, reason: "evaluation unclear" };
|
|
@@ -12776,10 +13237,8 @@ function errInfo(e) {
|
|
|
12776
13237
|
function fmtUsd(n) {
|
|
12777
13238
|
return n >= 1 ? `$${n.toFixed(2)}` : `$${n.toFixed(4)}`;
|
|
12778
13239
|
}
|
|
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);
|
|
13240
|
+
function estimateTranscriptTokens(messages, keepRecentImages = 1) {
|
|
13241
|
+
return estimateTokens(messages, keepRecentImages);
|
|
12783
13242
|
}
|
|
12784
13243
|
function formatStatus(s) {
|
|
12785
13244
|
const L = (k, v) => ` ${k.padEnd(12)} ${v}
|
|
@@ -12891,9 +13350,12 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
|
|
|
12891
13350
|
const rules = [...flagRules, ...configRules, ...askRules];
|
|
12892
13351
|
if (configRules.length) err(dim(` \u2295 ${configRules.length} permission rule(s) from config
|
|
12893
13352
|
`));
|
|
13353
|
+
const model = resolveModelOrNewest(args.model ?? cfg.model);
|
|
12894
13354
|
return {
|
|
12895
13355
|
ai,
|
|
12896
|
-
model
|
|
13356
|
+
model,
|
|
13357
|
+
// The real context window drives the context % and `/compact`'s budget (never `maxTokens` — that's spend).
|
|
13358
|
+
contextWindow: getModelInfo(model)?.contextWindow ?? 0,
|
|
12897
13359
|
cwd: args.cwd,
|
|
12898
13360
|
tools: cfg.tools,
|
|
12899
13361
|
extraTools,
|
|
@@ -12909,7 +13371,7 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
|
|
|
12909
13371
|
stream: args.stream,
|
|
12910
13372
|
host: makeHost(args.outputFormat, { stream: true }),
|
|
12911
13373
|
planMode: args.plan || cfg.planMode || false,
|
|
12912
|
-
permissions: rules.length ? new PermissionPolicy({ rules, default: "allow", host: makeHost(), ask: makeAskResolver(
|
|
13374
|
+
permissions: rules.length ? new PermissionPolicy({ rules, default: "allow", host: makeHost(), ask: makeAskResolver(resolve4(args.cwd ?? process.cwd())) }) : void 0,
|
|
12913
13375
|
subagents: args.subagents || cfg.subagents || false,
|
|
12914
13376
|
maxSteps: args.maxSteps ?? cfg.maxSteps,
|
|
12915
13377
|
reasoning: args.reasoning ?? cfg.reasoning,
|
|
@@ -12985,7 +13447,7 @@ function mcpAgentTools(mounted, opts) {
|
|
|
12985
13447
|
return tools;
|
|
12986
13448
|
}
|
|
12987
13449
|
async function closeMcp(mounted) {
|
|
12988
|
-
await Promise.all(mounted.map((m) => m.client.close().catch((e) =>
|
|
13450
|
+
await Promise.all(mounted.map((m) => m.client.close().catch((e) => log26.debug("mcp close failed", e))));
|
|
12989
13451
|
}
|
|
12990
13452
|
var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
|
|
12991
13453
|
function mentionRefs(line) {
|
|
@@ -12998,11 +13460,13 @@ function readImageParts(cwd, line) {
|
|
|
12998
13460
|
for (const ref of refs) {
|
|
12999
13461
|
const mime = IMG_EXT[extname(ref).toLowerCase()];
|
|
13000
13462
|
if (!mime) continue;
|
|
13001
|
-
const abs = ref.startsWith("~/") ? untilde(ref) :
|
|
13463
|
+
const abs = ref.startsWith("~/") ? untilde(ref) : resolve4(cwd, ref);
|
|
13002
13464
|
try {
|
|
13003
|
-
|
|
13465
|
+
statSync5(abs);
|
|
13004
13466
|
} catch {
|
|
13467
|
+
continue;
|
|
13005
13468
|
}
|
|
13469
|
+
parts.push(imageRefPart(abs, mime));
|
|
13006
13470
|
}
|
|
13007
13471
|
return parts;
|
|
13008
13472
|
}
|
|
@@ -13012,9 +13476,9 @@ function pastePathClassifier(cwd) {
|
|
|
13012
13476
|
if (!t || t.includes("\n")) return null;
|
|
13013
13477
|
t = t.replace(/\\ /g, " ").replace(/^['"]|['"]$/g, "");
|
|
13014
13478
|
if (!/^(\/|~\/|\.\/|\.\.\/)/.test(t)) return null;
|
|
13015
|
-
const abs = t.startsWith("~/") ? untilde(t) :
|
|
13479
|
+
const abs = t.startsWith("~/") ? untilde(t) : resolve4(cwd, t);
|
|
13016
13480
|
try {
|
|
13017
|
-
if (!
|
|
13481
|
+
if (!statSync5(abs).isFile()) return null;
|
|
13018
13482
|
} catch {
|
|
13019
13483
|
return null;
|
|
13020
13484
|
}
|
|
@@ -13035,7 +13499,7 @@ async function expandMentions(fs, line) {
|
|
|
13035
13499
|
if (loaded.includes(ref) || missing.includes(ref)) continue;
|
|
13036
13500
|
if (ref.includes(":") && mcpMentionResolver) {
|
|
13037
13501
|
const body = await mcpMentionResolver(ref).catch((e) => {
|
|
13038
|
-
|
|
13502
|
+
log26.debug("mcp mention resolve failed", e);
|
|
13039
13503
|
return null;
|
|
13040
13504
|
});
|
|
13041
13505
|
if (body != null) {
|
|
@@ -13121,7 +13585,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
13121
13585
|
try {
|
|
13122
13586
|
store.save(session);
|
|
13123
13587
|
} catch (ex) {
|
|
13124
|
-
|
|
13588
|
+
log26.debug("mid-turn session flush failed", ex);
|
|
13125
13589
|
}
|
|
13126
13590
|
}
|
|
13127
13591
|
return origNotify(e);
|
|
@@ -13152,9 +13616,8 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
13152
13616
|
const cost = turnCost(agent.options.model, res.usage);
|
|
13153
13617
|
const m = res.usageEstimated ? "~" : "";
|
|
13154
13618
|
const tok = res.usage?.totalTokens ? `${m}${(res.usage.totalTokens / 1e3).toFixed(1)}k tok \xB7 ${cost > 0 ? m + fmtUsd(cost) + " \xB7 " : ""}` : "";
|
|
13155
|
-
const lastUser = res.messages.map((m2) => m2.role).lastIndexOf("user");
|
|
13156
13619
|
agent.options.host?.takeToolCount?.();
|
|
13157
|
-
const tools = res.messages.slice(
|
|
13620
|
+
const tools = res.messages.slice(msgsBefore).filter((m2) => m2.role === "tool").length;
|
|
13158
13621
|
const ok = res.finishReason === "stop";
|
|
13159
13622
|
const shortId = session.meta.id.replace(/^\d{8}-/, "");
|
|
13160
13623
|
const didWork = agent.transcript.slice(msgsBefore).some((m2) => m2.role === "assistant" || m2.role === "tool");
|
|
@@ -13190,10 +13653,12 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
13190
13653
|
}
|
|
13191
13654
|
return { ok, res };
|
|
13192
13655
|
}
|
|
13656
|
+
var remintSessionImages = (data, store, cwd) => remintImageRefs(data.messages, [join14(store.dir, `${data.meta.id}.images`), cwd]);
|
|
13193
13657
|
function startSession(args, store, agent, cwd) {
|
|
13194
13658
|
if (args.resume || args.cont) {
|
|
13195
13659
|
const data = args.resume ? store.load(args.resume) ?? globalSessionLoad(args.resume) : store.latestData();
|
|
13196
13660
|
if (data) {
|
|
13661
|
+
data.messages = elideStaleImages(remintSessionImages(data, store, cwd), 1);
|
|
13197
13662
|
agent.transcript = data.messages;
|
|
13198
13663
|
if (args.fork) {
|
|
13199
13664
|
const now5 = Date.now();
|
|
@@ -13249,7 +13714,7 @@ function initInstructions(cwd) {
|
|
|
13249
13714
|
function persistSetting(cwd, key, value) {
|
|
13250
13715
|
const path = join14(cwd, ".agent", "settings.json");
|
|
13251
13716
|
try {
|
|
13252
|
-
const obj = existsSync9(path) ? JSON.parse(
|
|
13717
|
+
const obj = existsSync9(path) ? JSON.parse(readFileSync9(path, "utf8")) : {};
|
|
13253
13718
|
if (obj[key] === value) return;
|
|
13254
13719
|
obj[key] = value;
|
|
13255
13720
|
mkdirSync12(dirname5(path), { recursive: true });
|
|
@@ -13269,14 +13734,14 @@ var isCancelTeardown = (e) => {
|
|
|
13269
13734
|
function installCancelGuards(mounted) {
|
|
13270
13735
|
process.on("unhandledRejection", (e) => {
|
|
13271
13736
|
if (isCancelTeardown(e)) {
|
|
13272
|
-
|
|
13737
|
+
log26.debug("suppressed unhandledRejection (cursor stream cancel)", e);
|
|
13273
13738
|
return;
|
|
13274
13739
|
}
|
|
13275
|
-
|
|
13740
|
+
log26.error("unhandledRejection", e);
|
|
13276
13741
|
});
|
|
13277
13742
|
process.on("uncaughtException", (e) => {
|
|
13278
13743
|
if (isCancelTeardown(e)) {
|
|
13279
|
-
|
|
13744
|
+
log26.debug("suppressed uncaughtException (cursor stream cancel)", e);
|
|
13280
13745
|
return;
|
|
13281
13746
|
}
|
|
13282
13747
|
console.error(e);
|
|
@@ -13308,7 +13773,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
13308
13773
|
},
|
|
13309
13774
|
tickMs: 15e3
|
|
13310
13775
|
});
|
|
13311
|
-
const agentxInvocation = () => process.argv[1] ? `${process.execPath} ${
|
|
13776
|
+
const agentxInvocation = () => process.argv[1] ? `${process.execPath} ${resolve4(process.argv[1])}` : "agentx";
|
|
13312
13777
|
const osSched = new OsScheduler({ agentx: agentxInvocation() });
|
|
13313
13778
|
const osBackend = osSched.available() ? {
|
|
13314
13779
|
get sessionId() {
|
|
@@ -13547,7 +14012,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
13547
14012
|
quickLook: {
|
|
13548
14013
|
branch: () => {
|
|
13549
14014
|
try {
|
|
13550
|
-
const head =
|
|
14015
|
+
const head = readFileSync9(join14(cwd, ".git", "HEAD"), "utf8").trim();
|
|
13551
14016
|
return head.startsWith("ref: refs/heads/") ? `branch: ${head.slice("ref: refs/heads/".length)}` : `detached HEAD at ${head.slice(0, 12)}`;
|
|
13552
14017
|
} catch {
|
|
13553
14018
|
return "not a git repository";
|
|
@@ -13613,6 +14078,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
13613
14078
|
};
|
|
13614
14079
|
const setModel = (m) => {
|
|
13615
14080
|
work.model = m;
|
|
14081
|
+
work.contextWindow = getModelInfo(m)?.contextWindow ?? 0;
|
|
13616
14082
|
work.providerOptions = providerOptionsFor(m, cwd, cfg.mcpServers);
|
|
13617
14083
|
if (dx) dx.options.actModel = m;
|
|
13618
14084
|
persistModel(cwd, m);
|
|
@@ -13676,10 +14142,11 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
13676
14142
|
const pendingImages = [];
|
|
13677
14143
|
const bangContext = [];
|
|
13678
14144
|
const grabClipboardAttachment = () => {
|
|
13679
|
-
const dir = join14(
|
|
14145
|
+
const dir = join14(store.dir, `${session.meta.id}.images`);
|
|
13680
14146
|
try {
|
|
13681
14147
|
mkdirSync12(dir, { recursive: true });
|
|
13682
|
-
} catch {
|
|
14148
|
+
} catch (e) {
|
|
14149
|
+
log26.debug("paste image dir create failed", e);
|
|
13683
14150
|
}
|
|
13684
14151
|
const img = grabClipboardImage(dir, String(Date.now()));
|
|
13685
14152
|
return img ? { display: "Image", ref: "@" + img.path, path: img.path } : null;
|
|
@@ -13786,13 +14253,13 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
|
|
|
13786
14253
|
</system-reminder>`;
|
|
13787
14254
|
};
|
|
13788
14255
|
const histPath = join14(cwd, ".agent", "history");
|
|
13789
|
-
const history = existsSync9(histPath) ?
|
|
14256
|
+
const history = existsSync9(histPath) ? readFileSync9(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
|
|
13790
14257
|
const remember = (line) => {
|
|
13791
14258
|
try {
|
|
13792
14259
|
mkdirSync12(join14(cwd, ".agent"), { recursive: true });
|
|
13793
14260
|
appendFileSync(histPath, line + "\n");
|
|
13794
14261
|
} catch (e) {
|
|
13795
|
-
|
|
14262
|
+
log26.debug("history write failed", e);
|
|
13796
14263
|
}
|
|
13797
14264
|
};
|
|
13798
14265
|
const ago = (t) => {
|
|
@@ -13800,6 +14267,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
|
|
|
13800
14267
|
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
14268
|
};
|
|
13802
14269
|
const resumeInto = (data) => {
|
|
14270
|
+
data.messages = elideStaleImages(remintSessionImages(data, store, cwd), 1);
|
|
13803
14271
|
face.transcript = data.messages;
|
|
13804
14272
|
session = data;
|
|
13805
14273
|
checkpoints.use?.(data.meta.id);
|
|
@@ -13863,7 +14331,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
|
|
|
13863
14331
|
try {
|
|
13864
14332
|
store.save(session);
|
|
13865
14333
|
} catch (e) {
|
|
13866
|
-
|
|
14334
|
+
log26.debug("session save after rewind failed", e);
|
|
13867
14335
|
}
|
|
13868
14336
|
err(green(" \u27F2 jumped back") + dim(` \u2014 ${face.transcript.length} message(s) kept; edit + resend
|
|
13869
14337
|
`));
|
|
@@ -13897,7 +14365,7 @@ ${task}`;
|
|
|
13897
14365
|
bangContext.length = 0;
|
|
13898
14366
|
}
|
|
13899
14367
|
const delta = await refreshCatalogs().catch((e) => {
|
|
13900
|
-
|
|
14368
|
+
log26.debug("catalog refresh failed", e);
|
|
13901
14369
|
return "";
|
|
13902
14370
|
});
|
|
13903
14371
|
if (delta) {
|
|
@@ -14075,8 +14543,8 @@ ${task}`;
|
|
|
14075
14543
|
context: {
|
|
14076
14544
|
desc: "context-window usage (messages + estimated tokens)",
|
|
14077
14545
|
run: () => {
|
|
14078
|
-
const est = estimateTranscriptTokens(face.transcript);
|
|
14079
|
-
const cap = face.options
|
|
14546
|
+
const est = estimateTranscriptTokens(face.transcript, face.options.keepRecentImages);
|
|
14547
|
+
const cap = Agent.contextCap(face.options);
|
|
14080
14548
|
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
14549
|
`));
|
|
14082
14550
|
}
|
|
@@ -14161,7 +14629,7 @@ ${task}`;
|
|
|
14161
14629
|
desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
|
|
14162
14630
|
run: async () => {
|
|
14163
14631
|
await refreshCatalogs().catch((e) => {
|
|
14164
|
-
|
|
14632
|
+
log26.debug("catalog refresh failed", e);
|
|
14165
14633
|
});
|
|
14166
14634
|
face.reprepare();
|
|
14167
14635
|
err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
|
|
@@ -14454,13 +14922,21 @@ ${task}`;
|
|
|
14454
14922
|
desc: "summarize older context to free up the window \u2014 /compact [what to preserve]",
|
|
14455
14923
|
run: (a) => {
|
|
14456
14924
|
const focus = a.join(" ").trim() || void 0;
|
|
14457
|
-
const
|
|
14925
|
+
const r = face.compactNow(12, focus);
|
|
14458
14926
|
session.messages = face.transcript;
|
|
14459
14927
|
try {
|
|
14460
14928
|
store.save(session);
|
|
14461
14929
|
} catch {
|
|
14462
14930
|
}
|
|
14463
|
-
|
|
14931
|
+
const pct = (t) => `${Math.round(t / r.cap * 100)}%`;
|
|
14932
|
+
const k = (t) => `${(t / 1e3).toFixed(1)}k`;
|
|
14933
|
+
const freed = r.tokensBefore - r.tokensAfter;
|
|
14934
|
+
if (freed <= 0) {
|
|
14935
|
+
err(dim(` nothing to compact \u2014 ~${k(r.tokensAfter)} tok (${pct(r.tokensAfter)} of window) is already at the floor
|
|
14936
|
+
`));
|
|
14937
|
+
return;
|
|
14938
|
+
}
|
|
14939
|
+
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
14940
|
`));
|
|
14465
14941
|
}
|
|
14466
14942
|
},
|
|
@@ -14655,7 +15131,7 @@ ${task}`;
|
|
|
14655
15131
|
try {
|
|
14656
15132
|
for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
|
|
14657
15133
|
} catch (e) {
|
|
14658
|
-
|
|
15134
|
+
log26.debug(`loadAgents(${d}) failed`, e);
|
|
14659
15135
|
}
|
|
14660
15136
|
}
|
|
14661
15137
|
if (!seen.size) {
|
|
@@ -14743,7 +15219,7 @@ ${task}`;
|
|
|
14743
15219
|
}
|
|
14744
15220
|
if (idx >= 0) {
|
|
14745
15221
|
const old = mounted.splice(idx, 1)[0];
|
|
14746
|
-
await old.client.close().catch((e) =>
|
|
15222
|
+
await old.client.close().catch((e) => log26.debug("mcp close failed", e));
|
|
14747
15223
|
}
|
|
14748
15224
|
try {
|
|
14749
15225
|
const m = await mountMcpServer(name, conf);
|
|
@@ -14771,7 +15247,7 @@ ${task}`;
|
|
|
14771
15247
|
}
|
|
14772
15248
|
const m = mounted.splice(idx, 1)[0];
|
|
14773
15249
|
remountMcpTools();
|
|
14774
|
-
await m.client.close().catch((e) =>
|
|
15250
|
+
await m.client.close().catch((e) => log26.debug("mcp close failed", e));
|
|
14775
15251
|
err(dim(` removed "${name}"
|
|
14776
15252
|
`));
|
|
14777
15253
|
return;
|
|
@@ -14887,7 +15363,7 @@ ${task}`;
|
|
|
14887
15363
|
}
|
|
14888
15364
|
const md = exportMarkdown(session.meta, shown);
|
|
14889
15365
|
const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join14(".agent", "exports", `${session.meta.id}.md`);
|
|
14890
|
-
const path =
|
|
15366
|
+
const path = resolve4(cwd, name);
|
|
14891
15367
|
try {
|
|
14892
15368
|
mkdirSync12(dirname5(path), { recursive: true });
|
|
14893
15369
|
writeFileSync9(path, md);
|
|
@@ -14974,7 +15450,7 @@ ${task}`;
|
|
|
14974
15450
|
try {
|
|
14975
15451
|
return readdirSync4(join14(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
|
|
14976
15452
|
} catch (e) {
|
|
14977
|
-
|
|
15453
|
+
log26.debug("completion readdir failed", absDir, e);
|
|
14978
15454
|
return null;
|
|
14979
15455
|
}
|
|
14980
15456
|
};
|
|
@@ -15110,7 +15586,7 @@ ${out}
|
|
|
15110
15586
|
return;
|
|
15111
15587
|
}
|
|
15112
15588
|
await refreshCatalogs().catch((e) => {
|
|
15113
|
-
|
|
15589
|
+
log26.debug("catalog refresh failed", e);
|
|
15114
15590
|
});
|
|
15115
15591
|
const sk = skills.find((x) => x.name === name);
|
|
15116
15592
|
if (sk) {
|
|
@@ -15326,8 +15802,8 @@ ${out}
|
|
|
15326
15802
|
err("\n");
|
|
15327
15803
|
const initial = prefill ?? (carry || void 0);
|
|
15328
15804
|
prefill = void 0;
|
|
15329
|
-
const ctxTok = estimateTranscriptTokens(face.transcript);
|
|
15330
|
-
const ctxCap = face.options
|
|
15805
|
+
const ctxTok = estimateTranscriptTokens(face.transcript, face.options.keepRecentImages);
|
|
15806
|
+
const ctxCap = Agent.contextCap(face.options);
|
|
15331
15807
|
{
|
|
15332
15808
|
const pct = Math.round(ctxTok / ctxCap * 100);
|
|
15333
15809
|
const step = pct >= 90 ? 90 : pct >= 80 ? 80 : 0;
|
|
@@ -15541,7 +16017,7 @@ async function main() {
|
|
|
15541
16017
|
let worktreeCleanup;
|
|
15542
16018
|
if (args.worktree) {
|
|
15543
16019
|
const { enterWorktree: enterWorktree2, exitWorktree: exitWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
|
|
15544
|
-
const session = enterWorktree2(args.worktree,
|
|
16020
|
+
const session = enterWorktree2(args.worktree, resolve4(args.cwd ?? process.cwd()));
|
|
15545
16021
|
err(dim(` \u2295 worktree: ${session.worktreePath} (branch ${session.branch})
|
|
15546
16022
|
`));
|
|
15547
16023
|
worktreeCleanup = () => {
|
|
@@ -15551,7 +16027,7 @@ async function main() {
|
|
|
15551
16027
|
`));
|
|
15552
16028
|
};
|
|
15553
16029
|
}
|
|
15554
|
-
const cwd =
|
|
16030
|
+
const cwd = resolve4(args.cwd ?? process.cwd());
|
|
15555
16031
|
const cfg = await loadConfig(cwd);
|
|
15556
16032
|
cfg.permissions = mergePerms(loadClaudeSettings(cwd), mergePerms(loadPersistedRules(cwd), cfg.permissions));
|
|
15557
16033
|
if (canPrompt && !args.yes && !isTrusted(cwd)) {
|