@wrongstack/core 0.2.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-bridge-DmBiCipY.d.ts → agent-bridge-C3DUGjSb.d.ts} +1 -1
- package/dist/{compactor-DSl2FK7a.d.ts → compactor-DpJBI1YH.d.ts} +8 -2
- package/dist/{config-DXrqb41m.d.ts → config-D2qvAxVd.d.ts} +39 -2
- package/dist/{context-u0bryklF.d.ts → context-IovtuTf8.d.ts} +2 -0
- package/dist/coordination/index.d.ts +11 -11
- package/dist/coordination/index.js +307 -245
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +30 -15
- package/dist/defaults/index.js +1077 -479
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-B6Q03pTu.d.ts → events-BHIQs4o1.d.ts} +34 -1
- package/dist/execution/index.d.ts +17 -14
- package/dist/execution/index.js +166 -18
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +9 -0
- package/dist/extension/index.js +241 -0
- package/dist/extension/index.js.map +1 -0
- package/dist/{plugin-CoYYZKdn.d.ts → index-hWNybrNZ.d.ts} +368 -11
- package/dist/index.d.ts +76 -26
- package/dist/index.js +1595 -748
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +191 -20
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +12 -9
- package/dist/kernel/index.js +73 -7
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-BA1Ofmfj.d.ts → mcp-servers-C2OopXOn.d.ts} +21 -5
- package/dist/models/index.d.ts +2 -2
- package/dist/models/index.js +24 -1
- package/dist/models/index.js.map +1 -1
- package/dist/{multi-agent-BDfkxL5C.d.ts → multi-agent-B9a6sflH.d.ts} +2 -2
- package/dist/observability/index.d.ts +2 -2
- package/dist/{path-resolver-Crkt8wTQ.d.ts → path-resolver--59rCou3.d.ts} +2 -2
- package/dist/provider-runner-B39miKRw.d.ts +36 -0
- package/dist/sdd/index.d.ts +3 -3
- package/dist/{secret-scrubber-3TLUkiCV.d.ts → secret-scrubber-CgG2tV2B.d.ts} +1 -1
- package/dist/{secret-scrubber-CwYliRWd.d.ts → secret-scrubber-Cuy5afaQ.d.ts} +1 -1
- package/dist/security/index.d.ts +3 -3
- package/dist/security/index.js +24 -1
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-BRqzvugb.d.ts → selector-wT2fv9Fg.d.ts} +1 -1
- package/dist/{session-reader-C3x96CDR.d.ts → session-reader-CcPi4BQ8.d.ts} +1 -1
- package/dist/{skill-Bx8jxznf.d.ts → skill-C_7znCIC.d.ts} +2 -2
- package/dist/storage/index.d.ts +7 -6
- package/dist/storage/index.js +204 -14
- package/dist/storage/index.js.map +1 -1
- package/dist/{renderer-0A2ZEtca.d.ts → system-prompt-Dk1qm8ey.d.ts} +30 -2
- package/dist/{tool-executor-CYdZdtno.d.ts → tool-executor-HsBLGRaA.d.ts} +5 -5
- package/dist/types/index.d.ts +16 -16
- package/dist/types/index.js +230 -10
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +23 -2
- package/dist/utils/index.js +117 -2
- package/dist/utils/index.js.map +1 -1
- package/package.json +5 -1
- package/dist/system-prompt-CG9jU5-5.d.ts +0 -31
package/dist/defaults/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as fs5 from 'fs';
|
|
2
2
|
import * as path3 from 'path';
|
|
3
3
|
import * as crypto2 from 'crypto';
|
|
4
4
|
import { randomBytes, randomUUID, createCipheriv, createDecipheriv } from 'crypto';
|
|
@@ -62,7 +62,7 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
62
62
|
this.pretty = opts.pretty ?? true;
|
|
63
63
|
if (this.file) {
|
|
64
64
|
try {
|
|
65
|
-
|
|
65
|
+
fs5.mkdirSync(path3.dirname(this.file), { recursive: true });
|
|
66
66
|
} catch {
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -101,7 +101,7 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
101
101
|
}
|
|
102
102
|
if (this.file) {
|
|
103
103
|
try {
|
|
104
|
-
|
|
104
|
+
fs5.appendFileSync(this.file, `${JSON.stringify(entry)}
|
|
105
105
|
`);
|
|
106
106
|
} catch {
|
|
107
107
|
}
|
|
@@ -156,7 +156,7 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
156
156
|
if (mode !== void 0) {
|
|
157
157
|
await fsp.chmod(tmp, mode);
|
|
158
158
|
}
|
|
159
|
-
await
|
|
159
|
+
await renameWithRetry(tmp, targetPath);
|
|
160
160
|
} catch (err) {
|
|
161
161
|
try {
|
|
162
162
|
await fsp.unlink(tmp);
|
|
@@ -168,6 +168,121 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
168
168
|
async function ensureDir(dir) {
|
|
169
169
|
await fsp.mkdir(dir, { recursive: true });
|
|
170
170
|
}
|
|
171
|
+
var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
|
|
172
|
+
async function renameWithRetry(from, to) {
|
|
173
|
+
if (process.platform !== "win32") {
|
|
174
|
+
await fsp.rename(from, to);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const delays = [10, 25, 60, 120, 250];
|
|
178
|
+
let lastErr;
|
|
179
|
+
for (let i = 0; i <= delays.length; i++) {
|
|
180
|
+
try {
|
|
181
|
+
await fsp.rename(from, to);
|
|
182
|
+
return;
|
|
183
|
+
} catch (err) {
|
|
184
|
+
lastErr = err;
|
|
185
|
+
const code = err?.code;
|
|
186
|
+
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
await new Promise((resolve2) => setTimeout(resolve2, delays[i]));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
throw lastErr;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/utils/message-invariants.ts
|
|
196
|
+
function repairToolUseAdjacency(messages) {
|
|
197
|
+
const removedToolUses = [];
|
|
198
|
+
const removedToolResults = [];
|
|
199
|
+
let removedMessages = 0;
|
|
200
|
+
let changed = false;
|
|
201
|
+
const out = [];
|
|
202
|
+
for (let i = 0; i < messages.length; i++) {
|
|
203
|
+
const original = messages[i];
|
|
204
|
+
let msg = original;
|
|
205
|
+
if (hasToolUse(msg)) {
|
|
206
|
+
const nextIds = toolResultIds(messages[i + 1]);
|
|
207
|
+
const filtered = mapContent(msg, (blocks) => {
|
|
208
|
+
const next = [];
|
|
209
|
+
for (const block of blocks) {
|
|
210
|
+
if (block.type === "tool_use" && !nextIds.has(block.id)) {
|
|
211
|
+
removedToolUses.push(block.id);
|
|
212
|
+
changed = true;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
next.push(block);
|
|
216
|
+
}
|
|
217
|
+
return next;
|
|
218
|
+
});
|
|
219
|
+
msg = filtered ?? msg;
|
|
220
|
+
}
|
|
221
|
+
if (hasToolResult(msg)) {
|
|
222
|
+
const allowed = toolUseIds(out[out.length - 1]);
|
|
223
|
+
const filtered = mapContent(msg, (blocks) => {
|
|
224
|
+
const next = [];
|
|
225
|
+
for (const block of blocks) {
|
|
226
|
+
if (block.type === "tool_result" && !allowed.has(block.tool_use_id)) {
|
|
227
|
+
removedToolResults.push(block.tool_use_id);
|
|
228
|
+
changed = true;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
next.push(block);
|
|
232
|
+
}
|
|
233
|
+
return next;
|
|
234
|
+
});
|
|
235
|
+
msg = filtered ?? msg;
|
|
236
|
+
}
|
|
237
|
+
if (isEmptyMessage(msg)) {
|
|
238
|
+
removedMessages++;
|
|
239
|
+
changed = true;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
out.push(msg);
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
messages: changed ? out : messages,
|
|
246
|
+
report: { changed, removedToolUses, removedToolResults, removedMessages }
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function hasToolUse(msg) {
|
|
250
|
+
return contentBlocks(msg).some((b) => b.type === "tool_use");
|
|
251
|
+
}
|
|
252
|
+
function hasToolResult(msg) {
|
|
253
|
+
return contentBlocks(msg).some((b) => b.type === "tool_result");
|
|
254
|
+
}
|
|
255
|
+
function toolUseIds(msg) {
|
|
256
|
+
const ids = /* @__PURE__ */ new Set();
|
|
257
|
+
if (!msg || msg.role !== "assistant") return ids;
|
|
258
|
+
for (const block of contentBlocks(msg)) {
|
|
259
|
+
if (block.type === "tool_use") ids.add(block.id);
|
|
260
|
+
}
|
|
261
|
+
return ids;
|
|
262
|
+
}
|
|
263
|
+
function toolResultIds(msg) {
|
|
264
|
+
const ids = /* @__PURE__ */ new Set();
|
|
265
|
+
if (!msg || msg.role !== "user") return ids;
|
|
266
|
+
for (const block of contentBlocks(msg)) {
|
|
267
|
+
if (block.type === "tool_result") ids.add(block.tool_use_id);
|
|
268
|
+
}
|
|
269
|
+
return ids;
|
|
270
|
+
}
|
|
271
|
+
function contentBlocks(msg) {
|
|
272
|
+
return msg && Array.isArray(msg.content) ? msg.content : [];
|
|
273
|
+
}
|
|
274
|
+
function mapContent(msg, fn) {
|
|
275
|
+
if (!Array.isArray(msg.content)) return msg;
|
|
276
|
+
const next = fn(msg.content);
|
|
277
|
+
if (next.length === msg.content.length && next.every((b, idx) => b === msg.content[idx])) {
|
|
278
|
+
return msg;
|
|
279
|
+
}
|
|
280
|
+
return { ...msg, content: next };
|
|
281
|
+
}
|
|
282
|
+
function isEmptyMessage(msg) {
|
|
283
|
+
if (typeof msg.content === "string") return msg.content.trim().length === 0;
|
|
284
|
+
return msg.content.length === 0;
|
|
285
|
+
}
|
|
171
286
|
|
|
172
287
|
// src/storage/session-store.ts
|
|
173
288
|
var DefaultSessionStore = class {
|
|
@@ -371,11 +486,17 @@ var DefaultSessionStore = class {
|
|
|
371
486
|
if (openToolUses.size > 0) {
|
|
372
487
|
this.events?.emit("session.damaged", {
|
|
373
488
|
sessionId,
|
|
374
|
-
detail: `${openToolUses.size} tool_use blocks without matching results
|
|
489
|
+
detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`
|
|
375
490
|
});
|
|
376
|
-
return { messages, usage };
|
|
377
491
|
}
|
|
378
|
-
|
|
492
|
+
const repaired = repairToolUseAdjacency(messages);
|
|
493
|
+
if (repaired.report.changed) {
|
|
494
|
+
this.events?.emit("session.damaged", {
|
|
495
|
+
sessionId,
|
|
496
|
+
detail: `Repaired replay adjacency: removed ${repaired.report.removedToolUses.length} tool_use, ${repaired.report.removedToolResults.length} tool_result, ${repaired.report.removedMessages} empty messages`
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
return { messages: repaired.messages, usage };
|
|
379
500
|
}
|
|
380
501
|
};
|
|
381
502
|
var FileSessionWriter = class {
|
|
@@ -401,6 +522,7 @@ var FileSessionWriter = class {
|
|
|
401
522
|
startedAt;
|
|
402
523
|
meta;
|
|
403
524
|
closed = false;
|
|
525
|
+
closing = false;
|
|
404
526
|
manifestFile;
|
|
405
527
|
summary;
|
|
406
528
|
tokenIn = 0;
|
|
@@ -416,9 +538,7 @@ var FileSessionWriter = class {
|
|
|
416
538
|
resumed;
|
|
417
539
|
appendFailCount = 0;
|
|
418
540
|
lastAppendWarnAt = 0;
|
|
419
|
-
async
|
|
420
|
-
if (this.initDone || this.closed) return;
|
|
421
|
-
this.initDone = true;
|
|
541
|
+
async writeSessionStartLazy() {
|
|
422
542
|
const record = `${JSON.stringify({
|
|
423
543
|
type: this.resumed ? "session_resumed" : "session_start",
|
|
424
544
|
ts: this.startedAt,
|
|
@@ -437,7 +557,8 @@ var FileSessionWriter = class {
|
|
|
437
557
|
async append(event) {
|
|
438
558
|
if (this.closed) return;
|
|
439
559
|
if (!this.initDone) {
|
|
440
|
-
|
|
560
|
+
this.initDone = true;
|
|
561
|
+
await this.writeSessionStartLazy();
|
|
441
562
|
}
|
|
442
563
|
this.observeForSummary(event);
|
|
443
564
|
try {
|
|
@@ -478,7 +599,8 @@ var FileSessionWriter = class {
|
|
|
478
599
|
}
|
|
479
600
|
}
|
|
480
601
|
async close() {
|
|
481
|
-
if (this.
|
|
602
|
+
if (this.closing) return;
|
|
603
|
+
this.closing = true;
|
|
482
604
|
this.closed = true;
|
|
483
605
|
if (this.manifestFile) {
|
|
484
606
|
try {
|
|
@@ -881,80 +1003,7 @@ function deepFreeze(obj) {
|
|
|
881
1003
|
return Object.freeze(obj);
|
|
882
1004
|
}
|
|
883
1005
|
|
|
884
|
-
// src/
|
|
885
|
-
var ENCRYPTED_PREFIX = "enc:v1:";
|
|
886
|
-
|
|
887
|
-
// src/security/secret-vault.ts
|
|
888
|
-
var KEY_BYTES = 32;
|
|
889
|
-
var IV_BYTES = 12;
|
|
890
|
-
var TAG_BYTES = 16;
|
|
891
|
-
var ALGO = "aes-256-gcm";
|
|
892
|
-
var DefaultSecretVault = class {
|
|
893
|
-
keyFile;
|
|
894
|
-
key;
|
|
895
|
-
constructor(opts) {
|
|
896
|
-
this.keyFile = opts.keyFile;
|
|
897
|
-
}
|
|
898
|
-
isEncrypted(value) {
|
|
899
|
-
return typeof value === "string" && value.startsWith(ENCRYPTED_PREFIX);
|
|
900
|
-
}
|
|
901
|
-
encrypt(plaintext) {
|
|
902
|
-
if (this.isEncrypted(plaintext)) return plaintext;
|
|
903
|
-
const key = this.loadOrCreateKey();
|
|
904
|
-
const iv = randomBytes(IV_BYTES);
|
|
905
|
-
const cipher = createCipheriv(ALGO, key, iv);
|
|
906
|
-
const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
907
|
-
const tag = cipher.getAuthTag();
|
|
908
|
-
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
|
|
909
|
-
}
|
|
910
|
-
decrypt(value) {
|
|
911
|
-
if (!this.isEncrypted(value)) return value;
|
|
912
|
-
const rest = value.slice(ENCRYPTED_PREFIX.length);
|
|
913
|
-
const parts = rest.split(":");
|
|
914
|
-
if (parts.length !== 3) {
|
|
915
|
-
throw new Error("SecretVault: malformed encrypted value");
|
|
916
|
-
}
|
|
917
|
-
const [ivB64, tagB64, ctB64] = parts;
|
|
918
|
-
const iv = Buffer.from(ivB64, "base64");
|
|
919
|
-
const tag = Buffer.from(tagB64, "base64");
|
|
920
|
-
const ct = Buffer.from(ctB64, "base64");
|
|
921
|
-
if (iv.length !== IV_BYTES) throw new Error("SecretVault: bad IV length");
|
|
922
|
-
if (tag.length !== TAG_BYTES) throw new Error("SecretVault: bad tag length");
|
|
923
|
-
const key = this.loadOrCreateKey();
|
|
924
|
-
const decipher = createDecipheriv(ALGO, key, iv);
|
|
925
|
-
decipher.setAuthTag(tag);
|
|
926
|
-
const pt = Buffer.concat([decipher.update(ct), decipher.final()]);
|
|
927
|
-
return pt.toString("utf8");
|
|
928
|
-
}
|
|
929
|
-
loadOrCreateKey() {
|
|
930
|
-
if (this.key) return this.key;
|
|
931
|
-
try {
|
|
932
|
-
const buf = fs4.readFileSync(this.keyFile);
|
|
933
|
-
if (buf.length !== KEY_BYTES) {
|
|
934
|
-
throw new Error(`SecretVault: key file ${this.keyFile} has wrong size`);
|
|
935
|
-
}
|
|
936
|
-
this.key = buf;
|
|
937
|
-
return this.key;
|
|
938
|
-
} catch (err) {
|
|
939
|
-
if (err.code !== "ENOENT") throw err;
|
|
940
|
-
}
|
|
941
|
-
fs4.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
|
|
942
|
-
const key = randomBytes(KEY_BYTES);
|
|
943
|
-
try {
|
|
944
|
-
fs4.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
|
|
945
|
-
} catch (err) {
|
|
946
|
-
if (err.code !== "EEXIST") throw err;
|
|
947
|
-
const buf = fs4.readFileSync(this.keyFile);
|
|
948
|
-
if (buf.length !== KEY_BYTES) {
|
|
949
|
-
throw new Error(`SecretVault: key file ${this.keyFile} has wrong size`);
|
|
950
|
-
}
|
|
951
|
-
this.key = buf;
|
|
952
|
-
return this.key;
|
|
953
|
-
}
|
|
954
|
-
this.key = key;
|
|
955
|
-
return key;
|
|
956
|
-
}
|
|
957
|
-
};
|
|
1006
|
+
// src/security/config-secrets.ts
|
|
958
1007
|
function decryptConfigSecrets(cfg, vault) {
|
|
959
1008
|
return walk(cfg, vault, (v, key) => {
|
|
960
1009
|
try {
|
|
@@ -968,9 +1017,6 @@ function decryptConfigSecrets(cfg, vault) {
|
|
|
968
1017
|
}
|
|
969
1018
|
});
|
|
970
1019
|
}
|
|
971
|
-
function encryptConfigSecrets(cfg, vault) {
|
|
972
|
-
return walk(cfg, vault, (v) => vault.encrypt(v));
|
|
973
|
-
}
|
|
974
1020
|
function walk(node, vault, transform) {
|
|
975
1021
|
if (node === null || node === void 0) return node;
|
|
976
1022
|
if (typeof node !== "object") return node;
|
|
@@ -996,77 +1042,56 @@ function isSecretField(name) {
|
|
|
996
1042
|
if (NON_SECRET_OVERRIDES.has(lc)) return false;
|
|
997
1043
|
return SECRET_KEY_PATTERN.test(lc);
|
|
998
1044
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1045
|
+
|
|
1046
|
+
// src/types/context-window.ts
|
|
1047
|
+
var DEFAULT_CONTEXT_WINDOW_MODE_ID = "balanced";
|
|
1048
|
+
var CONTEXT_WINDOW_MODES = Object.freeze([
|
|
1049
|
+
{
|
|
1050
|
+
id: "balanced",
|
|
1051
|
+
name: "Balanced",
|
|
1052
|
+
description: "Default rolling compaction: recent work stays verbatim, old tool output is trimmed.",
|
|
1053
|
+
thresholds: { warn: 0.6, soft: 0.75, hard: 0.9 },
|
|
1054
|
+
aggressiveOn: "soft",
|
|
1055
|
+
preserveK: 10,
|
|
1056
|
+
eliseThreshold: 2e3,
|
|
1057
|
+
targetLoad: 0.65
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
id: "frugal",
|
|
1061
|
+
name: "Frugal",
|
|
1062
|
+
description: "Token-saver mode: compacts early and keeps a tighter verbatim tail.",
|
|
1063
|
+
thresholds: { warn: 0.45, soft: 0.6, hard: 0.75 },
|
|
1064
|
+
aggressiveOn: "warn",
|
|
1065
|
+
preserveK: 6,
|
|
1066
|
+
eliseThreshold: 700,
|
|
1067
|
+
targetLoad: 0.5
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
id: "deep",
|
|
1071
|
+
name: "Deep",
|
|
1072
|
+
description: "Long-reasoning mode: delays compaction and keeps more recent turns intact.",
|
|
1073
|
+
thresholds: { warn: 0.72, soft: 0.86, hard: 0.96 },
|
|
1074
|
+
aggressiveOn: "hard",
|
|
1075
|
+
preserveK: 18,
|
|
1076
|
+
eliseThreshold: 5e3,
|
|
1077
|
+
targetLoad: 0.78
|
|
1078
|
+
},
|
|
1079
|
+
{
|
|
1080
|
+
id: "archival",
|
|
1081
|
+
name: "Archival",
|
|
1082
|
+
description: "Decision-preserving mode: compacts steadily while keeping summaries prominent.",
|
|
1083
|
+
thresholds: { warn: 0.55, soft: 0.7, hard: 0.84 },
|
|
1084
|
+
aggressiveOn: "soft",
|
|
1085
|
+
preserveK: 8,
|
|
1086
|
+
eliseThreshold: 1200,
|
|
1087
|
+
targetLoad: 0.58
|
|
1088
|
+
}
|
|
1089
|
+
]);
|
|
1090
|
+
function listContextWindowModes() {
|
|
1091
|
+
return CONTEXT_WINDOW_MODES.map((m) => ({ ...m, thresholds: { ...m.thresholds } }));
|
|
1037
1092
|
}
|
|
1038
|
-
function
|
|
1039
|
-
|
|
1040
|
-
if (typeof node !== "object") return node;
|
|
1041
|
-
if (Array.isArray(node)) {
|
|
1042
|
-
return node.map((item) => walkCount(item, vault, counter));
|
|
1043
|
-
}
|
|
1044
|
-
const out = {};
|
|
1045
|
-
for (const [k, v] of Object.entries(node)) {
|
|
1046
|
-
if (typeof v === "string" && isSecretField(k) && !vault.isEncrypted(v) && v.length > 0) {
|
|
1047
|
-
out[k] = vault.encrypt(v);
|
|
1048
|
-
counter.n++;
|
|
1049
|
-
} else if (typeof v === "object" && v !== null) {
|
|
1050
|
-
out[k] = walkCount(v, vault, counter);
|
|
1051
|
-
} else {
|
|
1052
|
-
out[k] = v;
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
return out;
|
|
1056
|
-
}
|
|
1057
|
-
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1058
|
-
function deepMerge(a, b) {
|
|
1059
|
-
const out = { ...a };
|
|
1060
|
-
for (const [k, v] of Object.entries(b)) {
|
|
1061
|
-
if (FORBIDDEN_PROTO_KEYS.has(k)) continue;
|
|
1062
|
-
const existing = out[k];
|
|
1063
|
-
if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
1064
|
-
out[k] = deepMerge(existing, v);
|
|
1065
|
-
} else {
|
|
1066
|
-
out[k] = v;
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
return out;
|
|
1093
|
+
function isContextWindowModeId(id) {
|
|
1094
|
+
return CONTEXT_WINDOW_MODES.some((m) => m.id === id);
|
|
1070
1095
|
}
|
|
1071
1096
|
|
|
1072
1097
|
// src/utils/safe-json.ts
|
|
@@ -1088,6 +1113,7 @@ function safeParse(input, maxBytes = 5e6) {
|
|
|
1088
1113
|
var BEHAVIOR_DEFAULTS = {
|
|
1089
1114
|
version: 1,
|
|
1090
1115
|
context: {
|
|
1116
|
+
mode: DEFAULT_CONTEXT_WINDOW_MODE_ID,
|
|
1091
1117
|
warnThreshold: 0.6,
|
|
1092
1118
|
softThreshold: 0.75,
|
|
1093
1119
|
hardThreshold: 0.9,
|
|
@@ -1133,13 +1159,13 @@ var ENV_MAP = {
|
|
|
1133
1159
|
function isPrimitiveArray(a) {
|
|
1134
1160
|
return a.every((v) => v === null || typeof v !== "object");
|
|
1135
1161
|
}
|
|
1136
|
-
var
|
|
1137
|
-
function
|
|
1162
|
+
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1163
|
+
function deepMerge(base, patch) {
|
|
1138
1164
|
if (typeof base !== "object" || base === null) return patch ?? base;
|
|
1139
1165
|
if (typeof patch !== "object" || patch === null) return base;
|
|
1140
1166
|
const out = { ...base };
|
|
1141
1167
|
for (const [k, v] of Object.entries(patch)) {
|
|
1142
|
-
if (
|
|
1168
|
+
if (FORBIDDEN_PROTO_KEYS.has(k)) continue;
|
|
1143
1169
|
const existing = out[k];
|
|
1144
1170
|
if (Array.isArray(v)) {
|
|
1145
1171
|
if (Array.isArray(existing) && isPrimitiveArray(v) && isPrimitiveArray(existing)) {
|
|
@@ -1153,7 +1179,7 @@ function deepMerge2(base, patch) {
|
|
|
1153
1179
|
}
|
|
1154
1180
|
}
|
|
1155
1181
|
} else if (typeof v === "object" && v !== null && typeof existing === "object" && existing !== null) {
|
|
1156
|
-
out[k] =
|
|
1182
|
+
out[k] = deepMerge(existing, v);
|
|
1157
1183
|
} else if (v !== void 0) {
|
|
1158
1184
|
out[k] = v;
|
|
1159
1185
|
}
|
|
@@ -1177,8 +1203,8 @@ var DefaultConfigLoader = class {
|
|
|
1177
1203
|
this.readJson(this.paths.globalConfig),
|
|
1178
1204
|
this.readJson(this.paths.projectLocalConfig)
|
|
1179
1205
|
]);
|
|
1180
|
-
cfg =
|
|
1181
|
-
cfg =
|
|
1206
|
+
cfg = deepMerge(cfg, global);
|
|
1207
|
+
cfg = deepMerge(cfg, local);
|
|
1182
1208
|
for (const [key, fn] of Object.entries(ENV_MAP)) {
|
|
1183
1209
|
const v = process.env[key];
|
|
1184
1210
|
if (v) fn(cfg, v);
|
|
@@ -1192,14 +1218,14 @@ var DefaultConfigLoader = class {
|
|
|
1192
1218
|
try {
|
|
1193
1219
|
const patch = await src.read();
|
|
1194
1220
|
if (patch && Object.keys(patch).length > 0) {
|
|
1195
|
-
cfg =
|
|
1221
|
+
cfg = deepMerge(cfg, patch);
|
|
1196
1222
|
}
|
|
1197
1223
|
} catch (err) {
|
|
1198
1224
|
console.warn(`Config source "${src.name}" failed`, err);
|
|
1199
1225
|
}
|
|
1200
1226
|
}
|
|
1201
1227
|
if (opts.cliFlags) {
|
|
1202
|
-
cfg =
|
|
1228
|
+
cfg = deepMerge(cfg, opts.cliFlags);
|
|
1203
1229
|
}
|
|
1204
1230
|
if (this.vault) {
|
|
1205
1231
|
cfg = decryptConfigSecrets(cfg, this.vault);
|
|
@@ -1262,6 +1288,10 @@ var DefaultConfigLoader = class {
|
|
|
1262
1288
|
if (c.warnThreshold >= c.softThreshold || c.softThreshold >= c.hardThreshold) {
|
|
1263
1289
|
throw new Error("Config: context thresholds must satisfy warn < soft < hard");
|
|
1264
1290
|
}
|
|
1291
|
+
if (c.mode !== void 0 && !isContextWindowModeId(c.mode)) {
|
|
1292
|
+
const known = listContextWindowModes().map((m) => m.id).join(", ");
|
|
1293
|
+
throw new Error(`Config: context.mode must be one of: ${known}`);
|
|
1294
|
+
}
|
|
1265
1295
|
}
|
|
1266
1296
|
validateIdentity(cfg) {
|
|
1267
1297
|
if (!cfg.provider) {
|
|
@@ -1838,24 +1868,35 @@ async function saveTodosCheckpoint(filePath, sessionId, todos) {
|
|
|
1838
1868
|
function attachTodosCheckpoint(state, filePath, sessionId) {
|
|
1839
1869
|
let timer = null;
|
|
1840
1870
|
let pending = null;
|
|
1871
|
+
let writeChain = Promise.resolve();
|
|
1872
|
+
const enqueueWrite = (todos) => {
|
|
1873
|
+
writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos));
|
|
1874
|
+
return writeChain;
|
|
1875
|
+
};
|
|
1841
1876
|
const flush = () => {
|
|
1842
1877
|
timer = null;
|
|
1843
1878
|
if (pending) {
|
|
1844
|
-
|
|
1879
|
+
const todos = pending;
|
|
1845
1880
|
pending = null;
|
|
1881
|
+
return enqueueWrite(todos);
|
|
1846
1882
|
}
|
|
1883
|
+
return writeChain;
|
|
1847
1884
|
};
|
|
1848
1885
|
const unsubscribe = state.onChange((change) => {
|
|
1849
1886
|
if (change.kind !== "todos_replaced") return;
|
|
1850
1887
|
pending = change.todos;
|
|
1851
1888
|
if (timer) clearTimeout(timer);
|
|
1852
|
-
timer = setTimeout(
|
|
1889
|
+
timer = setTimeout(() => {
|
|
1890
|
+
void flush();
|
|
1891
|
+
}, 150);
|
|
1853
1892
|
});
|
|
1854
|
-
return () => {
|
|
1893
|
+
return async () => {
|
|
1855
1894
|
unsubscribe();
|
|
1856
1895
|
if (timer) {
|
|
1857
1896
|
clearTimeout(timer);
|
|
1858
|
-
flush();
|
|
1897
|
+
await flush();
|
|
1898
|
+
} else {
|
|
1899
|
+
await writeChain;
|
|
1859
1900
|
}
|
|
1860
1901
|
};
|
|
1861
1902
|
}
|
|
@@ -2160,7 +2201,195 @@ var DefaultSecretScrubber = class {
|
|
|
2160
2201
|
};
|
|
2161
2202
|
return visit(obj);
|
|
2162
2203
|
}
|
|
2163
|
-
};
|
|
2204
|
+
};
|
|
2205
|
+
|
|
2206
|
+
// src/types/secret-vault.ts
|
|
2207
|
+
var ENCRYPTED_PREFIX = "enc:v1:";
|
|
2208
|
+
|
|
2209
|
+
// src/security/secret-vault.ts
|
|
2210
|
+
var KEY_BYTES = 32;
|
|
2211
|
+
var IV_BYTES = 12;
|
|
2212
|
+
var TAG_BYTES = 16;
|
|
2213
|
+
var ALGO = "aes-256-gcm";
|
|
2214
|
+
var DefaultSecretVault = class {
|
|
2215
|
+
keyFile;
|
|
2216
|
+
key;
|
|
2217
|
+
constructor(opts) {
|
|
2218
|
+
this.keyFile = opts.keyFile;
|
|
2219
|
+
}
|
|
2220
|
+
isEncrypted(value) {
|
|
2221
|
+
return typeof value === "string" && value.startsWith(ENCRYPTED_PREFIX);
|
|
2222
|
+
}
|
|
2223
|
+
encrypt(plaintext) {
|
|
2224
|
+
if (this.isEncrypted(plaintext)) return plaintext;
|
|
2225
|
+
const key = this.loadOrCreateKey();
|
|
2226
|
+
const iv = randomBytes(IV_BYTES);
|
|
2227
|
+
const cipher = createCipheriv(ALGO, key, iv);
|
|
2228
|
+
const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
2229
|
+
const tag = cipher.getAuthTag();
|
|
2230
|
+
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
|
|
2231
|
+
}
|
|
2232
|
+
decrypt(value) {
|
|
2233
|
+
if (!this.isEncrypted(value)) return value;
|
|
2234
|
+
const rest = value.slice(ENCRYPTED_PREFIX.length);
|
|
2235
|
+
const parts = rest.split(":");
|
|
2236
|
+
if (parts.length !== 3) {
|
|
2237
|
+
throw new Error("SecretVault: malformed encrypted value");
|
|
2238
|
+
}
|
|
2239
|
+
const [ivB64, tagB64, ctB64] = parts;
|
|
2240
|
+
const iv = Buffer.from(ivB64, "base64");
|
|
2241
|
+
const tag = Buffer.from(tagB64, "base64");
|
|
2242
|
+
const ct = Buffer.from(ctB64, "base64");
|
|
2243
|
+
if (iv.length !== IV_BYTES) throw new Error("SecretVault: bad IV length");
|
|
2244
|
+
if (tag.length !== TAG_BYTES) throw new Error("SecretVault: bad tag length");
|
|
2245
|
+
const key = this.loadOrCreateKey();
|
|
2246
|
+
const decipher = createDecipheriv(ALGO, key, iv);
|
|
2247
|
+
decipher.setAuthTag(tag);
|
|
2248
|
+
const pt = Buffer.concat([decipher.update(ct), decipher.final()]);
|
|
2249
|
+
return pt.toString("utf8");
|
|
2250
|
+
}
|
|
2251
|
+
loadOrCreateKey() {
|
|
2252
|
+
if (this.key) return this.key;
|
|
2253
|
+
try {
|
|
2254
|
+
const buf = fs5.readFileSync(this.keyFile);
|
|
2255
|
+
if (buf.length !== KEY_BYTES) {
|
|
2256
|
+
throw new Error(`SecretVault: key file ${this.keyFile} has wrong size`);
|
|
2257
|
+
}
|
|
2258
|
+
this.key = buf;
|
|
2259
|
+
return this.key;
|
|
2260
|
+
} catch (err) {
|
|
2261
|
+
if (err.code !== "ENOENT") throw err;
|
|
2262
|
+
}
|
|
2263
|
+
fs5.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
|
|
2264
|
+
const key = randomBytes(KEY_BYTES);
|
|
2265
|
+
try {
|
|
2266
|
+
fs5.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
|
|
2267
|
+
} catch (err) {
|
|
2268
|
+
if (err.code !== "EEXIST") throw err;
|
|
2269
|
+
const buf = fs5.readFileSync(this.keyFile);
|
|
2270
|
+
if (buf.length !== KEY_BYTES) {
|
|
2271
|
+
throw new Error(`SecretVault: key file ${this.keyFile} has wrong size`);
|
|
2272
|
+
}
|
|
2273
|
+
this.key = buf;
|
|
2274
|
+
return this.key;
|
|
2275
|
+
}
|
|
2276
|
+
this.key = key;
|
|
2277
|
+
return key;
|
|
2278
|
+
}
|
|
2279
|
+
};
|
|
2280
|
+
function decryptConfigSecrets2(cfg, vault) {
|
|
2281
|
+
return walk2(cfg, vault, (v, key) => {
|
|
2282
|
+
try {
|
|
2283
|
+
return vault.decrypt(v);
|
|
2284
|
+
} catch (err) {
|
|
2285
|
+
console.warn(
|
|
2286
|
+
`[secret-vault] Failed to decrypt "${key}":`,
|
|
2287
|
+
err instanceof Error ? err.message : err
|
|
2288
|
+
);
|
|
2289
|
+
return "";
|
|
2290
|
+
}
|
|
2291
|
+
});
|
|
2292
|
+
}
|
|
2293
|
+
function encryptConfigSecrets(cfg, vault) {
|
|
2294
|
+
return walk2(cfg, vault, (v) => vault.encrypt(v));
|
|
2295
|
+
}
|
|
2296
|
+
function walk2(node, vault, transform) {
|
|
2297
|
+
if (node === null || node === void 0) return node;
|
|
2298
|
+
if (typeof node !== "object") return node;
|
|
2299
|
+
if (Array.isArray(node)) {
|
|
2300
|
+
return node.map((item) => walk2(item, vault, transform));
|
|
2301
|
+
}
|
|
2302
|
+
const out = {};
|
|
2303
|
+
for (const [k, v] of Object.entries(node)) {
|
|
2304
|
+
if (typeof v === "string" && isSecretField2(k)) {
|
|
2305
|
+
out[k] = transform(v, k);
|
|
2306
|
+
} else if (typeof v === "object" && v !== null) {
|
|
2307
|
+
out[k] = walk2(v, vault, transform);
|
|
2308
|
+
} else {
|
|
2309
|
+
out[k] = v;
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
return out;
|
|
2313
|
+
}
|
|
2314
|
+
var SECRET_KEY_PATTERN2 = /(?:apikey|api_key|authtoken|auth_token|bearer|secret|password|passwd|pwd|refreshtoken|refresh_token|sessionkey|session_key|access[_-]?token|private[_-]?key)/i;
|
|
2315
|
+
var NON_SECRET_OVERRIDES2 = /* @__PURE__ */ new Set(["publickey", "public_key"]);
|
|
2316
|
+
function isSecretField2(name) {
|
|
2317
|
+
const lc = name.toLowerCase();
|
|
2318
|
+
if (NON_SECRET_OVERRIDES2.has(lc)) return false;
|
|
2319
|
+
return SECRET_KEY_PATTERN2.test(lc);
|
|
2320
|
+
}
|
|
2321
|
+
async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
2322
|
+
let current = {};
|
|
2323
|
+
try {
|
|
2324
|
+
const raw = await fsp.readFile(configPath, "utf8");
|
|
2325
|
+
current = JSON.parse(raw);
|
|
2326
|
+
} catch {
|
|
2327
|
+
}
|
|
2328
|
+
const merged = deepMerge2(current, patch ?? {});
|
|
2329
|
+
const encrypted = encryptConfigSecrets(merged, vault);
|
|
2330
|
+
await fsp.mkdir(path3.dirname(configPath), { recursive: true });
|
|
2331
|
+
await fsp.writeFile(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
2332
|
+
try {
|
|
2333
|
+
await fsp.chmod(configPath, 384);
|
|
2334
|
+
} catch {
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
async function migratePlaintextSecrets(configPath, vault) {
|
|
2338
|
+
let raw;
|
|
2339
|
+
try {
|
|
2340
|
+
raw = await fsp.readFile(configPath, "utf8");
|
|
2341
|
+
} catch {
|
|
2342
|
+
return { migrated: 0, file: configPath };
|
|
2343
|
+
}
|
|
2344
|
+
let parsed;
|
|
2345
|
+
try {
|
|
2346
|
+
parsed = JSON.parse(raw);
|
|
2347
|
+
} catch {
|
|
2348
|
+
return { migrated: 0, file: configPath };
|
|
2349
|
+
}
|
|
2350
|
+
const counter = { n: 0 };
|
|
2351
|
+
const migrated = walkCount(parsed, vault, counter);
|
|
2352
|
+
if (counter.n === 0) return { migrated: 0, file: configPath };
|
|
2353
|
+
await fsp.writeFile(configPath, JSON.stringify(migrated, null, 2), { mode: 384 });
|
|
2354
|
+
try {
|
|
2355
|
+
await fsp.chmod(configPath, 384);
|
|
2356
|
+
} catch {
|
|
2357
|
+
}
|
|
2358
|
+
return { migrated: counter.n, file: configPath };
|
|
2359
|
+
}
|
|
2360
|
+
function walkCount(node, vault, counter) {
|
|
2361
|
+
if (node === null || node === void 0) return node;
|
|
2362
|
+
if (typeof node !== "object") return node;
|
|
2363
|
+
if (Array.isArray(node)) {
|
|
2364
|
+
return node.map((item) => walkCount(item, vault, counter));
|
|
2365
|
+
}
|
|
2366
|
+
const out = {};
|
|
2367
|
+
for (const [k, v] of Object.entries(node)) {
|
|
2368
|
+
if (typeof v === "string" && isSecretField2(k) && !vault.isEncrypted(v) && v.length > 0) {
|
|
2369
|
+
out[k] = vault.encrypt(v);
|
|
2370
|
+
counter.n++;
|
|
2371
|
+
} else if (typeof v === "object" && v !== null) {
|
|
2372
|
+
out[k] = walkCount(v, vault, counter);
|
|
2373
|
+
} else {
|
|
2374
|
+
out[k] = v;
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
return out;
|
|
2378
|
+
}
|
|
2379
|
+
var FORBIDDEN_PROTO_KEYS2 = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
2380
|
+
function deepMerge2(a, b) {
|
|
2381
|
+
const out = { ...a };
|
|
2382
|
+
for (const [k, v] of Object.entries(b)) {
|
|
2383
|
+
if (FORBIDDEN_PROTO_KEYS2.has(k)) continue;
|
|
2384
|
+
const existing = out[k];
|
|
2385
|
+
if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
2386
|
+
out[k] = deepMerge2(existing, v);
|
|
2387
|
+
} else {
|
|
2388
|
+
out[k] = v;
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
return out;
|
|
2392
|
+
}
|
|
2164
2393
|
|
|
2165
2394
|
// src/utils/glob-match.ts
|
|
2166
2395
|
function escapeRegex(s) {
|
|
@@ -2718,6 +2947,302 @@ function parseDescription(raw) {
|
|
|
2718
2947
|
return { trigger, scope };
|
|
2719
2948
|
}
|
|
2720
2949
|
|
|
2950
|
+
// src/core/streaming-response-builder.ts
|
|
2951
|
+
function buildResponse(state) {
|
|
2952
|
+
const content = [];
|
|
2953
|
+
for (const b of state.blockOrder) {
|
|
2954
|
+
if (b.kind === "text") {
|
|
2955
|
+
const txt = state.textBuffers[b.idx] ?? "";
|
|
2956
|
+
if (txt) content.push({ type: "text", text: txt });
|
|
2957
|
+
} else if (b.kind === "thinking") {
|
|
2958
|
+
const t = state.thinking[b.idx];
|
|
2959
|
+
if (!t) continue;
|
|
2960
|
+
if (!t.textBuf && !t.signature) continue;
|
|
2961
|
+
const block = { type: "thinking", thinking: t.textBuf };
|
|
2962
|
+
if (t.signature) block.signature = t.signature;
|
|
2963
|
+
if (t.providerMeta && Object.keys(t.providerMeta).length > 0) {
|
|
2964
|
+
block.providerMeta = t.providerMeta;
|
|
2965
|
+
}
|
|
2966
|
+
content.push(block);
|
|
2967
|
+
} else {
|
|
2968
|
+
const tb = state.tools.get(b.id);
|
|
2969
|
+
if (tb) {
|
|
2970
|
+
const block = {
|
|
2971
|
+
type: "tool_use",
|
|
2972
|
+
id: b.id,
|
|
2973
|
+
name: tb.name,
|
|
2974
|
+
input: tb.input ?? {}
|
|
2975
|
+
};
|
|
2976
|
+
if (tb.providerMeta && Object.keys(tb.providerMeta).length > 0) {
|
|
2977
|
+
block.providerMeta = tb.providerMeta;
|
|
2978
|
+
}
|
|
2979
|
+
content.push(block);
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
if (content.length === 0) content.push({ type: "text", text: "" });
|
|
2984
|
+
return { content, stopReason: state.stopReason, usage: state.usage, model: state.model };
|
|
2985
|
+
}
|
|
2986
|
+
function createStreamingState(model) {
|
|
2987
|
+
return {
|
|
2988
|
+
model,
|
|
2989
|
+
stopReason: "end_turn",
|
|
2990
|
+
usage: { input: 0, output: 0 },
|
|
2991
|
+
textBuffers: [],
|
|
2992
|
+
currentTextIndex: -1,
|
|
2993
|
+
tools: /* @__PURE__ */ new Map(),
|
|
2994
|
+
thinking: [],
|
|
2995
|
+
currentThinkingIndex: -1,
|
|
2996
|
+
blockOrder: []
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2999
|
+
function handleMessageStart(state, model) {
|
|
3000
|
+
state.model = model;
|
|
3001
|
+
}
|
|
3002
|
+
function handleContentBlockStart(state, ev) {
|
|
3003
|
+
const kind = ev.kind ?? "text";
|
|
3004
|
+
if (kind === "text") {
|
|
3005
|
+
state.currentTextIndex = state.textBuffers.length;
|
|
3006
|
+
state.textBuffers.push("");
|
|
3007
|
+
state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
|
|
3008
|
+
} else if (kind === "tool_use") {
|
|
3009
|
+
const id = ev.id ?? crypto.randomUUID();
|
|
3010
|
+
state.tools.set(id, { name: ev.name ?? "unknown", partial: "" });
|
|
3011
|
+
state.blockOrder.push({ kind: "tool", id });
|
|
3012
|
+
state.currentTextIndex = -1;
|
|
3013
|
+
} else if (kind === "thinking") {
|
|
3014
|
+
state.currentThinkingIndex = state.thinking.length;
|
|
3015
|
+
state.thinking.push({
|
|
3016
|
+
textBuf: "",
|
|
3017
|
+
...ev.providerMeta ? { providerMeta: ev.providerMeta } : {}
|
|
3018
|
+
});
|
|
3019
|
+
state.blockOrder.push({ kind: "thinking", idx: state.currentThinkingIndex });
|
|
3020
|
+
state.currentTextIndex = -1;
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
function handleContentBlockStop(state, ev) {
|
|
3024
|
+
}
|
|
3025
|
+
function handleTextDelta(state, text) {
|
|
3026
|
+
if (state.currentTextIndex === -1) {
|
|
3027
|
+
state.currentTextIndex = state.textBuffers.length;
|
|
3028
|
+
state.textBuffers.push("");
|
|
3029
|
+
state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
|
|
3030
|
+
}
|
|
3031
|
+
state.textBuffers[state.currentTextIndex] = (state.textBuffers[state.currentTextIndex] ?? "") + text;
|
|
3032
|
+
}
|
|
3033
|
+
function handleToolUseStart(state, ev) {
|
|
3034
|
+
state.currentTextIndex = -1;
|
|
3035
|
+
state.tools.set(ev.id, { name: ev.name, partial: "" });
|
|
3036
|
+
state.blockOrder.push({ kind: "tool", id: ev.id });
|
|
3037
|
+
}
|
|
3038
|
+
function handleToolUseInputDelta(state, ev) {
|
|
3039
|
+
const t = state.tools.get(ev.id);
|
|
3040
|
+
if (t) t.partial += ev.partial;
|
|
3041
|
+
}
|
|
3042
|
+
function safeJsonOrRaw(s) {
|
|
3043
|
+
if (!s) return {};
|
|
3044
|
+
try {
|
|
3045
|
+
return JSON.parse(s);
|
|
3046
|
+
} catch {
|
|
3047
|
+
return { _raw: s };
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
function handleToolUseStop(state, ev) {
|
|
3051
|
+
const t = state.tools.get(ev.id);
|
|
3052
|
+
if (t) {
|
|
3053
|
+
t.input = ev.input !== void 0 ? ev.input : safeJsonOrRaw(t.partial);
|
|
3054
|
+
if (ev.providerMeta) t.providerMeta = ev.providerMeta;
|
|
3055
|
+
}
|
|
3056
|
+
state.currentTextIndex = -1;
|
|
3057
|
+
}
|
|
3058
|
+
function handleThinkingStart(state, ev) {
|
|
3059
|
+
state.currentThinkingIndex = state.thinking.length;
|
|
3060
|
+
state.thinking.push({
|
|
3061
|
+
textBuf: "",
|
|
3062
|
+
...ev.providerMeta ? { providerMeta: ev.providerMeta } : {}
|
|
3063
|
+
});
|
|
3064
|
+
state.blockOrder.push({ kind: "thinking", idx: state.currentThinkingIndex });
|
|
3065
|
+
state.currentTextIndex = -1;
|
|
3066
|
+
}
|
|
3067
|
+
function handleThinkingDelta(state, text) {
|
|
3068
|
+
if (state.currentThinkingIndex === -1) {
|
|
3069
|
+
handleThinkingStart(state, {});
|
|
3070
|
+
}
|
|
3071
|
+
const t = state.thinking[state.currentThinkingIndex];
|
|
3072
|
+
if (t) t.textBuf += text;
|
|
3073
|
+
}
|
|
3074
|
+
function handleThinkingSignature(state, signature) {
|
|
3075
|
+
if (state.currentThinkingIndex === -1) {
|
|
3076
|
+
handleThinkingStart(state, {});
|
|
3077
|
+
}
|
|
3078
|
+
const t = state.thinking[state.currentThinkingIndex];
|
|
3079
|
+
if (t) t.signature = signature;
|
|
3080
|
+
}
|
|
3081
|
+
function handleThinkingStop(state) {
|
|
3082
|
+
state.currentThinkingIndex = -1;
|
|
3083
|
+
}
|
|
3084
|
+
function handleMessageStop(state, ev) {
|
|
3085
|
+
state.stopReason = ev.stopReason ?? "end_turn";
|
|
3086
|
+
state.usage = ev.usage ?? { input: 0, output: 0 };
|
|
3087
|
+
}
|
|
3088
|
+
async function streamProviderToResponse(provider, req, signal, ctx, events) {
|
|
3089
|
+
const state = createStreamingState(req.model);
|
|
3090
|
+
const iter = provider.stream(req, { signal })[Symbol.asyncIterator]();
|
|
3091
|
+
try {
|
|
3092
|
+
for (; ; ) {
|
|
3093
|
+
const next = await iter.next();
|
|
3094
|
+
if (next.done) break;
|
|
3095
|
+
const ev = next.value;
|
|
3096
|
+
switch (ev.type) {
|
|
3097
|
+
case "message_start":
|
|
3098
|
+
handleMessageStart(state, ev.model);
|
|
3099
|
+
break;
|
|
3100
|
+
case "content_block_start":
|
|
3101
|
+
handleContentBlockStart(state, ev);
|
|
3102
|
+
break;
|
|
3103
|
+
case "content_block_stop":
|
|
3104
|
+
handleContentBlockStop(state, ev);
|
|
3105
|
+
break;
|
|
3106
|
+
case "text_delta":
|
|
3107
|
+
handleTextDelta(state, ev.text);
|
|
3108
|
+
events.emit("provider.text_delta", { ctx, text: ev.text });
|
|
3109
|
+
break;
|
|
3110
|
+
case "tool_use_start":
|
|
3111
|
+
handleToolUseStart(state, ev);
|
|
3112
|
+
events.emit("provider.tool_use_start", { ctx, id: ev.id, name: ev.name });
|
|
3113
|
+
break;
|
|
3114
|
+
case "tool_use_input_delta":
|
|
3115
|
+
handleToolUseInputDelta(state, ev);
|
|
3116
|
+
break;
|
|
3117
|
+
case "tool_use_stop":
|
|
3118
|
+
handleToolUseStop(state, ev);
|
|
3119
|
+
events.emit("provider.tool_use_stop", { ctx, id: ev.id });
|
|
3120
|
+
break;
|
|
3121
|
+
case "thinking_start":
|
|
3122
|
+
handleThinkingStart(state, ev);
|
|
3123
|
+
break;
|
|
3124
|
+
case "thinking_delta":
|
|
3125
|
+
handleThinkingDelta(state, ev.text);
|
|
3126
|
+
events.emit("provider.thinking_delta", { ctx, text: ev.text });
|
|
3127
|
+
break;
|
|
3128
|
+
case "thinking_signature":
|
|
3129
|
+
handleThinkingSignature(state, ev.signature);
|
|
3130
|
+
break;
|
|
3131
|
+
case "thinking_stop":
|
|
3132
|
+
handleThinkingStop(state);
|
|
3133
|
+
break;
|
|
3134
|
+
case "message_stop":
|
|
3135
|
+
handleMessageStop(state, ev);
|
|
3136
|
+
break;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
} catch (err) {
|
|
3140
|
+
if (signal.aborted) {
|
|
3141
|
+
state.stopReason = "end_turn";
|
|
3142
|
+
return buildResponse(state);
|
|
3143
|
+
}
|
|
3144
|
+
throw err;
|
|
3145
|
+
} finally {
|
|
3146
|
+
try {
|
|
3147
|
+
let drainTimer = null;
|
|
3148
|
+
try {
|
|
3149
|
+
await Promise.race([
|
|
3150
|
+
Promise.resolve(iter.return?.()),
|
|
3151
|
+
new Promise((resolve2) => {
|
|
3152
|
+
drainTimer = setTimeout(resolve2, 500);
|
|
3153
|
+
})
|
|
3154
|
+
]);
|
|
3155
|
+
} finally {
|
|
3156
|
+
if (drainTimer) clearTimeout(drainTimer);
|
|
3157
|
+
}
|
|
3158
|
+
} catch {
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
return buildResponse(state);
|
|
3162
|
+
}
|
|
3163
|
+
|
|
3164
|
+
// src/core/provider-runner.ts
|
|
3165
|
+
async function runProviderWithRetry(opts) {
|
|
3166
|
+
const { provider, request, signal, ctx, events, retry, logger, tracer } = opts;
|
|
3167
|
+
let attempt = 0;
|
|
3168
|
+
for (; ; ) {
|
|
3169
|
+
const span = tracer?.startSpan("provider.complete", {
|
|
3170
|
+
"provider.id": provider.id,
|
|
3171
|
+
"provider.model": request.model,
|
|
3172
|
+
"provider.streaming": provider.capabilities.streaming,
|
|
3173
|
+
"provider.attempt": attempt
|
|
3174
|
+
});
|
|
3175
|
+
try {
|
|
3176
|
+
const res = provider.capabilities.streaming ? await streamProviderToResponse(provider, request, signal, ctx, events) : await provider.complete(request, { signal });
|
|
3177
|
+
span?.setAttribute("provider.stopReason", res.stopReason);
|
|
3178
|
+
span?.setAttribute("provider.usage_in", res.usage.input);
|
|
3179
|
+
span?.setAttribute("provider.usage_out", res.usage.output);
|
|
3180
|
+
span?.end();
|
|
3181
|
+
return res;
|
|
3182
|
+
} catch (err) {
|
|
3183
|
+
if (err instanceof Error) span?.recordError(err);
|
|
3184
|
+
span?.end();
|
|
3185
|
+
if (signal.aborted) throw err;
|
|
3186
|
+
const isProviderErr = err instanceof ProviderError;
|
|
3187
|
+
const errAsErr = err instanceof Error ? err : new Error(String(err));
|
|
3188
|
+
const canRetry = retry.shouldRetry(isProviderErr ? err : errAsErr, attempt);
|
|
3189
|
+
const description = isProviderErr ? err.describe() : errAsErr.message;
|
|
3190
|
+
if (!canRetry) {
|
|
3191
|
+
if (isProviderErr) {
|
|
3192
|
+
events.emit("provider.error", {
|
|
3193
|
+
providerId: err.providerId,
|
|
3194
|
+
status: err.status,
|
|
3195
|
+
description,
|
|
3196
|
+
retryable: false
|
|
3197
|
+
});
|
|
3198
|
+
}
|
|
3199
|
+
throw err;
|
|
3200
|
+
}
|
|
3201
|
+
const delay = Math.round(retry.delayMs(attempt));
|
|
3202
|
+
const attemptNum = attempt + 1;
|
|
3203
|
+
logger.warn(`Provider retry ${attemptNum} in ${delay}ms \u2014 ${description}`);
|
|
3204
|
+
if (isProviderErr) {
|
|
3205
|
+
events.emit("provider.retry", {
|
|
3206
|
+
providerId: err.providerId,
|
|
3207
|
+
attempt: attemptNum,
|
|
3208
|
+
delayMs: delay,
|
|
3209
|
+
status: err.status,
|
|
3210
|
+
description
|
|
3211
|
+
});
|
|
3212
|
+
}
|
|
3213
|
+
await new Promise((resolve2, reject) => {
|
|
3214
|
+
let settled = false;
|
|
3215
|
+
const onAbort = () => {
|
|
3216
|
+
if (settled) return;
|
|
3217
|
+
settled = true;
|
|
3218
|
+
clearTimeout(t);
|
|
3219
|
+
reject(new Error("aborted"));
|
|
3220
|
+
};
|
|
3221
|
+
const t = setTimeout(() => {
|
|
3222
|
+
if (settled) return;
|
|
3223
|
+
settled = true;
|
|
3224
|
+
clearTimeout(t);
|
|
3225
|
+
signal.removeEventListener("abort", onAbort);
|
|
3226
|
+
resolve2();
|
|
3227
|
+
}, delay);
|
|
3228
|
+
if (signal.aborted) {
|
|
3229
|
+
onAbort();
|
|
3230
|
+
return;
|
|
3231
|
+
}
|
|
3232
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3233
|
+
});
|
|
3234
|
+
attempt++;
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
// src/execution/provider-runner-impl.ts
|
|
3240
|
+
var DefaultProviderRunner = class {
|
|
3241
|
+
async run(opts) {
|
|
3242
|
+
return runProviderWithRetry(opts);
|
|
3243
|
+
}
|
|
3244
|
+
};
|
|
3245
|
+
|
|
2721
3246
|
// src/utils/token-estimate.ts
|
|
2722
3247
|
var RoughTokenEstimate = (text) => Math.max(1, Math.ceil(text.length / 4));
|
|
2723
3248
|
var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
|
|
@@ -2765,20 +3290,36 @@ var HybridCompactor = class {
|
|
|
2765
3290
|
async compact(ctx, opts = {}) {
|
|
2766
3291
|
const beforeTokens = this.estimateMessages(ctx.messages);
|
|
2767
3292
|
const reductions = [];
|
|
2768
|
-
const
|
|
3293
|
+
const policy = readContextWindowPolicy(ctx);
|
|
3294
|
+
const preserveK = policy?.preserveK ?? this.preserveK;
|
|
3295
|
+
const eliseThreshold = policy?.eliseThreshold ?? this.eliseThreshold;
|
|
3296
|
+
const phase1Saved = this.eliseOldToolResults(ctx, preserveK, eliseThreshold);
|
|
2769
3297
|
if (phase1Saved > 0) reductions.push({ phase: "elision", saved: phase1Saved });
|
|
2770
3298
|
if (opts.aggressive) {
|
|
2771
|
-
const phase2Saved = this.collapseAncientTurns(ctx);
|
|
3299
|
+
const phase2Saved = this.collapseAncientTurns(ctx, preserveK);
|
|
2772
3300
|
if (phase2Saved > 0) reductions.push({ phase: "summary", saved: phase2Saved });
|
|
2773
3301
|
}
|
|
3302
|
+
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
3303
|
+
if (repaired.report.changed) {
|
|
3304
|
+
ctx.state.replaceMessages(repaired.messages);
|
|
3305
|
+
}
|
|
2774
3306
|
const afterTokens = this.estimateMessages(ctx.messages);
|
|
2775
|
-
return {
|
|
3307
|
+
return {
|
|
3308
|
+
before: beforeTokens,
|
|
3309
|
+
after: afterTokens,
|
|
3310
|
+
reductions,
|
|
3311
|
+
repaired: repaired.report.changed ? {
|
|
3312
|
+
removedToolUses: repaired.report.removedToolUses,
|
|
3313
|
+
removedToolResults: repaired.report.removedToolResults,
|
|
3314
|
+
removedMessages: repaired.report.removedMessages
|
|
3315
|
+
} : void 0
|
|
3316
|
+
};
|
|
2776
3317
|
}
|
|
2777
|
-
eliseOldToolResults(ctx) {
|
|
3318
|
+
eliseOldToolResults(ctx, preserveK = this.preserveK, eliseThreshold = this.eliseThreshold) {
|
|
2778
3319
|
const messages = ctx.messages;
|
|
2779
3320
|
let pairCount = 0;
|
|
2780
3321
|
let preserveStart = messages.length;
|
|
2781
|
-
for (let i = messages.length - 1; i >= 0 && pairCount <
|
|
3322
|
+
for (let i = messages.length - 1; i >= 0 && pairCount < preserveK; i--) {
|
|
2782
3323
|
const m = messages[i];
|
|
2783
3324
|
if (!m) continue;
|
|
2784
3325
|
if (m.role === "user" || m.role === "assistant") {
|
|
@@ -2802,7 +3343,7 @@ var HybridCompactor = class {
|
|
|
2802
3343
|
const newContent = msg.content.map((b) => {
|
|
2803
3344
|
if (b.type !== "tool_result") return b;
|
|
2804
3345
|
const tokens = estimateToolResultTokens(b.content);
|
|
2805
|
-
if (tokens <
|
|
3346
|
+
if (tokens < eliseThreshold) return b;
|
|
2806
3347
|
saved += tokens;
|
|
2807
3348
|
const elided = {
|
|
2808
3349
|
type: "tool_result",
|
|
@@ -2822,9 +3363,9 @@ var HybridCompactor = class {
|
|
|
2822
3363
|
if (changed) ctx.state.replaceMessages(nextMessages);
|
|
2823
3364
|
return saved;
|
|
2824
3365
|
}
|
|
2825
|
-
collapseAncientTurns(ctx) {
|
|
3366
|
+
collapseAncientTurns(ctx, preserveK = this.preserveK) {
|
|
2826
3367
|
const messages = ctx.messages;
|
|
2827
|
-
const cutTarget = Math.max(0, messages.length -
|
|
3368
|
+
const cutTarget = Math.max(0, messages.length - preserveK * 2);
|
|
2828
3369
|
if (cutTarget <= 0) return 0;
|
|
2829
3370
|
let boundary = -1;
|
|
2830
3371
|
for (let i = cutTarget; i < messages.length; i++) {
|
|
@@ -2865,6 +3406,15 @@ var HybridCompactor = class {
|
|
|
2865
3406
|
return total;
|
|
2866
3407
|
}
|
|
2867
3408
|
};
|
|
3409
|
+
function readContextWindowPolicy(ctx) {
|
|
3410
|
+
const policy = ctx.meta?.["contextWindowPolicy"];
|
|
3411
|
+
if (!policy || typeof policy !== "object") return null;
|
|
3412
|
+
const candidate = policy;
|
|
3413
|
+
if (typeof candidate.preserveK !== "number" || typeof candidate.eliseThreshold !== "number") {
|
|
3414
|
+
return null;
|
|
3415
|
+
}
|
|
3416
|
+
return candidate;
|
|
3417
|
+
}
|
|
2868
3418
|
function hasTextContent(m) {
|
|
2869
3419
|
if (typeof m.content === "string") return m.content.trim().length > 0;
|
|
2870
3420
|
return m.content.some((b) => b.type === "text" && b.text.trim().length > 0);
|
|
@@ -2911,8 +3461,19 @@ var IntelligentCompactor = class {
|
|
|
2911
3461
|
const saved2 = this.lightweightCompact(ctx);
|
|
2912
3462
|
if (saved2 > 0) reductions.push({ phase: "elision", saved: saved2 });
|
|
2913
3463
|
}
|
|
3464
|
+
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
3465
|
+
if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
|
|
2914
3466
|
const afterTokens = this.estimateTokens(ctx.messages);
|
|
2915
|
-
return {
|
|
3467
|
+
return {
|
|
3468
|
+
before: beforeTokens,
|
|
3469
|
+
after: afterTokens,
|
|
3470
|
+
reductions,
|
|
3471
|
+
repaired: repaired.report.changed ? {
|
|
3472
|
+
removedToolUses: repaired.report.removedToolUses,
|
|
3473
|
+
removedToolResults: repaired.report.removedToolResults,
|
|
3474
|
+
removedMessages: repaired.report.removedMessages
|
|
3475
|
+
} : void 0
|
|
3476
|
+
};
|
|
2916
3477
|
}
|
|
2917
3478
|
async summarizeAncientTurns(ctx) {
|
|
2918
3479
|
const messages = ctx.messages;
|
|
@@ -2952,8 +3513,8 @@ var IntelligentCompactor = class {
|
|
|
2952
3513
|
const m = messages[i];
|
|
2953
3514
|
if (!m) continue;
|
|
2954
3515
|
if (m.role === "assistant") {
|
|
2955
|
-
const
|
|
2956
|
-
if (!
|
|
3516
|
+
const hasToolUse2 = Array.isArray(m.content) ? m.content.some((b) => b.type === "tool_use") : false;
|
|
3517
|
+
if (!hasToolUse2) {
|
|
2957
3518
|
return i + 1;
|
|
2958
3519
|
}
|
|
2959
3520
|
} else if (m.role !== "user") ; else {
|
|
@@ -3259,8 +3820,9 @@ var SelectiveCompactor = class {
|
|
|
3259
3820
|
if (!shouldCompact) {
|
|
3260
3821
|
const saved = this.eliseOldToolResults(ctx);
|
|
3261
3822
|
if (saved > 0) reductions.push({ phase: "elision", saved });
|
|
3823
|
+
const repair2 = this.repairProtocolAdjacency(ctx);
|
|
3262
3824
|
const afterTokens2 = this.estimateTokens(ctx.messages);
|
|
3263
|
-
return { before: beforeTokens, after: afterTokens2, reductions };
|
|
3825
|
+
return { before: beforeTokens, after: afterTokens2, reductions, repaired: repair2 };
|
|
3264
3826
|
}
|
|
3265
3827
|
const savedElision = this.eliseOldToolResults(ctx);
|
|
3266
3828
|
if (savedElision > 0) reductions.push({ phase: "elision", saved: savedElision });
|
|
@@ -3270,8 +3832,18 @@ var SelectiveCompactor = class {
|
|
|
3270
3832
|
const savedSelective = await this.runSelector(ctx, targetBudget);
|
|
3271
3833
|
if (savedSelective > 0) reductions.push({ phase: "selective", saved: savedSelective });
|
|
3272
3834
|
}
|
|
3835
|
+
const repair = this.repairProtocolAdjacency(ctx);
|
|
3273
3836
|
const afterTokens = this.estimateTokens(ctx.messages);
|
|
3274
|
-
return { before: beforeTokens, after: afterTokens, reductions };
|
|
3837
|
+
return { before: beforeTokens, after: afterTokens, reductions, repaired: repair };
|
|
3838
|
+
}
|
|
3839
|
+
repairProtocolAdjacency(ctx) {
|
|
3840
|
+
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
3841
|
+
if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
|
|
3842
|
+
return repaired.report.changed ? {
|
|
3843
|
+
removedToolUses: repaired.report.removedToolUses,
|
|
3844
|
+
removedToolResults: repaired.report.removedToolResults,
|
|
3845
|
+
removedMessages: repaired.report.removedMessages
|
|
3846
|
+
} : void 0;
|
|
3275
3847
|
}
|
|
3276
3848
|
/**
|
|
3277
3849
|
* Run the LLM selector to decide what to keep vs collapse.
|
|
@@ -3459,6 +4031,7 @@ var AutoCompactionMiddleware = class {
|
|
|
3459
4031
|
aggressiveOn;
|
|
3460
4032
|
events;
|
|
3461
4033
|
failureMode;
|
|
4034
|
+
policyProvider;
|
|
3462
4035
|
/**
|
|
3463
4036
|
* @param compactor Compactor to use for compaction.
|
|
3464
4037
|
* @param maxContext Provider's max context window in tokens.
|
|
@@ -3481,17 +4054,25 @@ var AutoCompactionMiddleware = class {
|
|
|
3481
4054
|
this.aggressiveOn = opts.aggressiveOn ?? "soft";
|
|
3482
4055
|
this.events = opts.events;
|
|
3483
4056
|
this.failureMode = opts.failureMode ?? "throw_on_hard";
|
|
4057
|
+
this.policyProvider = opts.policyProvider;
|
|
3484
4058
|
}
|
|
3485
4059
|
handler() {
|
|
3486
4060
|
return async (ctx, next) => {
|
|
3487
4061
|
const tokens = this.estimator(ctx);
|
|
3488
4062
|
const load = tokens / this.maxContext;
|
|
3489
|
-
|
|
4063
|
+
const policy = this.policyProvider?.(ctx);
|
|
4064
|
+
const thresholds = policy?.thresholds ?? {
|
|
4065
|
+
warn: this.warnThreshold,
|
|
4066
|
+
soft: this.softThreshold,
|
|
4067
|
+
hard: this.hardThreshold
|
|
4068
|
+
};
|
|
4069
|
+
const aggressiveOn = policy?.aggressiveOn ?? this.aggressiveOn;
|
|
4070
|
+
if (load >= thresholds.hard) {
|
|
3490
4071
|
await this.compact(ctx, true, { level: "hard", tokens, load });
|
|
3491
|
-
} else if (load >=
|
|
3492
|
-
await this.compact(ctx,
|
|
3493
|
-
} else if (load >=
|
|
3494
|
-
await this.compact(ctx,
|
|
4072
|
+
} else if (load >= thresholds.soft) {
|
|
4073
|
+
await this.compact(ctx, aggressiveOn !== "hard", { level: "soft", tokens, load });
|
|
4074
|
+
} else if (load >= thresholds.warn) {
|
|
4075
|
+
await this.compact(ctx, aggressiveOn === "warn", { level: "warn", tokens, load });
|
|
3495
4076
|
}
|
|
3496
4077
|
return next(ctx);
|
|
3497
4078
|
};
|
|
@@ -4094,6 +4675,7 @@ var InMemoryAgentBridge = class {
|
|
|
4094
4675
|
this.stopped = true;
|
|
4095
4676
|
for (const [, p] of this.pendingRequests) {
|
|
4096
4677
|
clearTimeout(p.timer);
|
|
4678
|
+
p.reject(new Error("Bridge stopped"));
|
|
4097
4679
|
}
|
|
4098
4680
|
this.pendingRequests.clear();
|
|
4099
4681
|
this.inflightGuards.clear();
|
|
@@ -4867,41 +5449,214 @@ function classifySubagentError(err, hints = {}) {
|
|
|
4867
5449
|
return { kind: "context_overflow", message: baseMessage, retryable: false, cause };
|
|
4868
5450
|
}
|
|
4869
5451
|
return {
|
|
4870
|
-
kind: "unknown",
|
|
4871
|
-
message: baseMessage,
|
|
4872
|
-
retryable: false,
|
|
4873
|
-
cause
|
|
5452
|
+
kind: "unknown",
|
|
5453
|
+
message: baseMessage,
|
|
5454
|
+
retryable: false,
|
|
5455
|
+
cause
|
|
5456
|
+
};
|
|
5457
|
+
}
|
|
5458
|
+
function providerErrorToSubagentError(err, message, cause) {
|
|
5459
|
+
const status = err.status;
|
|
5460
|
+
if (status === 429 || err.body?.type === "rate_limit_error") {
|
|
5461
|
+
return {
|
|
5462
|
+
kind: "provider_rate_limit",
|
|
5463
|
+
message,
|
|
5464
|
+
retryable: true,
|
|
5465
|
+
// Conservative default: 5s. Provider-specific code can override
|
|
5466
|
+
// by emitting an error whose body carries an explicit hint.
|
|
5467
|
+
backoffMs: 5e3,
|
|
5468
|
+
cause
|
|
5469
|
+
};
|
|
5470
|
+
}
|
|
5471
|
+
if (status === 401 || status === 403 || err.body?.type === "authentication_error") {
|
|
5472
|
+
return { kind: "provider_auth", message, retryable: false, cause };
|
|
5473
|
+
}
|
|
5474
|
+
if (status === 408 || status === 0) {
|
|
5475
|
+
return { kind: "provider_timeout", message, retryable: true, cause };
|
|
5476
|
+
}
|
|
5477
|
+
if (status >= 500 && status < 600) {
|
|
5478
|
+
return {
|
|
5479
|
+
kind: "provider_5xx",
|
|
5480
|
+
message,
|
|
5481
|
+
retryable: true,
|
|
5482
|
+
backoffMs: 3e3,
|
|
5483
|
+
cause
|
|
5484
|
+
};
|
|
5485
|
+
}
|
|
5486
|
+
return { kind: "unknown", message, retryable: err.retryable, cause };
|
|
5487
|
+
}
|
|
5488
|
+
function makeSpawnTool(director, roster) {
|
|
5489
|
+
const inputSchema = {
|
|
5490
|
+
type: "object",
|
|
5491
|
+
properties: {
|
|
5492
|
+
role: { type: "string", description: "Roster role id (preferred). When set, the spawn uses the matching config from the roster and ignores other fields." },
|
|
5493
|
+
name: { type: "string", description: "Display name for the subagent. Required when not using roster." },
|
|
5494
|
+
provider: { type: "string", description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.' },
|
|
5495
|
+
model: { type: "string", description: "Model id within the provider. Defaults to the leader model when omitted." },
|
|
5496
|
+
systemPromptOverride: { type: "string", description: "Extra prompt text appended after the role-base prompt." },
|
|
5497
|
+
maxIterations: { type: "number" },
|
|
5498
|
+
maxToolCalls: { type: "number" },
|
|
5499
|
+
maxCostUsd: { type: "number" }
|
|
5500
|
+
},
|
|
5501
|
+
required: []
|
|
5502
|
+
};
|
|
5503
|
+
return {
|
|
5504
|
+
name: "spawn_subagent",
|
|
5505
|
+
description: "Create a new subagent under this director. Returns the subagent id.",
|
|
5506
|
+
usageHint: "Either pass `role` (matches the roster) OR pass `name` + optional `provider`/`model`. Returns `{ subagentId }`.",
|
|
5507
|
+
permission: "auto",
|
|
5508
|
+
mutating: false,
|
|
5509
|
+
inputSchema,
|
|
5510
|
+
async execute(input) {
|
|
5511
|
+
const i = input ?? {};
|
|
5512
|
+
const role = typeof i.role === "string" ? i.role : void 0;
|
|
5513
|
+
const base = role && roster ? roster[role] : void 0;
|
|
5514
|
+
if (role && !base) {
|
|
5515
|
+
return { error: `unknown role "${role}". roster has: ${roster ? Object.keys(roster).join(", ") : "(empty)"}` };
|
|
5516
|
+
}
|
|
5517
|
+
const cfg = { ...base ?? { name: i.name ?? "subagent" } };
|
|
5518
|
+
if (typeof i.name === "string") cfg.name = i.name;
|
|
5519
|
+
if (typeof i.provider === "string") cfg.provider = i.provider;
|
|
5520
|
+
if (typeof i.model === "string") cfg.model = i.model;
|
|
5521
|
+
if (typeof i.systemPromptOverride === "string") cfg.systemPromptOverride = i.systemPromptOverride;
|
|
5522
|
+
if (typeof i.maxIterations === "number") cfg.maxIterations = i.maxIterations;
|
|
5523
|
+
if (typeof i.maxToolCalls === "number") cfg.maxToolCalls = i.maxToolCalls;
|
|
5524
|
+
if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
|
|
5525
|
+
try {
|
|
5526
|
+
const subagentId = await director.spawn(cfg);
|
|
5527
|
+
return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name };
|
|
5528
|
+
} catch (err) {
|
|
5529
|
+
if (err instanceof DirectorBudgetError) {
|
|
5530
|
+
return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
|
|
5531
|
+
}
|
|
5532
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
5533
|
+
}
|
|
5534
|
+
}
|
|
5535
|
+
};
|
|
5536
|
+
}
|
|
5537
|
+
function makeAssignTool(director) {
|
|
5538
|
+
const inputSchema = {
|
|
5539
|
+
type: "object",
|
|
5540
|
+
properties: {
|
|
5541
|
+
subagentId: { type: "string", description: "Target subagent id. Required." },
|
|
5542
|
+
description: { type: "string", description: "The task in natural language \u2014 what you want this subagent to do." },
|
|
5543
|
+
maxToolCalls: { type: "number", description: "Optional per-task tool-call budget override." },
|
|
5544
|
+
timeoutMs: { type: "number", description: "Optional per-task timeout in ms." }
|
|
5545
|
+
},
|
|
5546
|
+
required: ["subagentId", "description"]
|
|
5547
|
+
};
|
|
5548
|
+
return {
|
|
5549
|
+
name: "assign_task",
|
|
5550
|
+
description: "Hand a task to a previously spawned subagent. Returns the task id.",
|
|
5551
|
+
permission: "auto",
|
|
5552
|
+
mutating: false,
|
|
5553
|
+
inputSchema,
|
|
5554
|
+
async execute(input) {
|
|
5555
|
+
const i = input;
|
|
5556
|
+
const task = { id: randomUUID(), description: i.description, subagentId: i.subagentId, maxToolCalls: i.maxToolCalls, timeoutMs: i.timeoutMs };
|
|
5557
|
+
const taskId = await director.assign(task);
|
|
5558
|
+
return { taskId, subagentId: i.subagentId };
|
|
5559
|
+
}
|
|
5560
|
+
};
|
|
5561
|
+
}
|
|
5562
|
+
function makeAwaitTasksTool(director) {
|
|
5563
|
+
return {
|
|
5564
|
+
name: "await_tasks",
|
|
5565
|
+
description: "Block until every named task completes. Returns the array of TaskResult.",
|
|
5566
|
+
permission: "auto",
|
|
5567
|
+
mutating: false,
|
|
5568
|
+
inputSchema: { type: "object", properties: { taskIds: { type: "array", items: { type: "string" }, description: "One or more task ids returned by `assign_task`." } }, required: ["taskIds"] },
|
|
5569
|
+
async execute(input) {
|
|
5570
|
+
const i = input;
|
|
5571
|
+
const results = await director.awaitTasks(i.taskIds);
|
|
5572
|
+
return { results };
|
|
5573
|
+
}
|
|
5574
|
+
};
|
|
5575
|
+
}
|
|
5576
|
+
function makeAskTool(director) {
|
|
5577
|
+
return {
|
|
5578
|
+
name: "ask_subagent",
|
|
5579
|
+
description: "Synchronously ask a subagent a question. Blocks until the subagent replies via the bridge.",
|
|
5580
|
+
permission: "auto",
|
|
5581
|
+
mutating: false,
|
|
5582
|
+
inputSchema: {
|
|
5583
|
+
type: "object",
|
|
5584
|
+
properties: {
|
|
5585
|
+
subagentId: { type: "string", description: "Subagent to ask. Must be a previously spawned id." },
|
|
5586
|
+
question: { type: "string", description: "The question or instruction." },
|
|
5587
|
+
timeoutMs: { type: "number", description: "Optional timeout in ms (default 30s)." }
|
|
5588
|
+
},
|
|
5589
|
+
required: ["subagentId", "question"]
|
|
5590
|
+
},
|
|
5591
|
+
async execute(input) {
|
|
5592
|
+
const i = input;
|
|
5593
|
+
try {
|
|
5594
|
+
const answer = await director.ask(i.subagentId, { question: i.question }, i.timeoutMs);
|
|
5595
|
+
return { ok: true, answer };
|
|
5596
|
+
} catch (err) {
|
|
5597
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
5598
|
+
}
|
|
5599
|
+
}
|
|
5600
|
+
};
|
|
5601
|
+
}
|
|
5602
|
+
function makeRollUpTool(director) {
|
|
5603
|
+
return {
|
|
5604
|
+
name: "roll_up",
|
|
5605
|
+
description: "Aggregate completed task results into a single formatted summary.",
|
|
5606
|
+
permission: "auto",
|
|
5607
|
+
mutating: false,
|
|
5608
|
+
inputSchema: {
|
|
5609
|
+
type: "object",
|
|
5610
|
+
properties: {
|
|
5611
|
+
taskIds: { type: "array", items: { type: "string" }, description: "Completed task ids to aggregate." },
|
|
5612
|
+
style: { type: "string", enum: ["markdown", "json"], description: "Output flavor \u2014 markdown (default) or json." }
|
|
5613
|
+
},
|
|
5614
|
+
required: ["taskIds"]
|
|
5615
|
+
},
|
|
5616
|
+
async execute(input) {
|
|
5617
|
+
const i = input;
|
|
5618
|
+
const summary = director.rollUp(i.taskIds, i.style ?? "markdown");
|
|
5619
|
+
return { summary, count: i.taskIds.length };
|
|
5620
|
+
}
|
|
5621
|
+
};
|
|
5622
|
+
}
|
|
5623
|
+
function makeTerminateTool(director) {
|
|
5624
|
+
return {
|
|
5625
|
+
name: "terminate_subagent",
|
|
5626
|
+
description: "Forcibly abort a subagent.",
|
|
5627
|
+
permission: "auto",
|
|
5628
|
+
mutating: true,
|
|
5629
|
+
inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
|
|
5630
|
+
async execute(input) {
|
|
5631
|
+
const i = input;
|
|
5632
|
+
await director.terminate(i.subagentId);
|
|
5633
|
+
return { ok: true };
|
|
5634
|
+
}
|
|
5635
|
+
};
|
|
5636
|
+
}
|
|
5637
|
+
function makeFleetStatusTool(director) {
|
|
5638
|
+
return {
|
|
5639
|
+
name: "fleet_status",
|
|
5640
|
+
description: "Snapshot of the fleet \u2014 every subagent's current status, pending vs. completed task counts.",
|
|
5641
|
+
permission: "auto",
|
|
5642
|
+
mutating: false,
|
|
5643
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
5644
|
+
async execute() {
|
|
5645
|
+
return director.status();
|
|
5646
|
+
}
|
|
4874
5647
|
};
|
|
4875
5648
|
}
|
|
4876
|
-
function
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
};
|
|
4888
|
-
}
|
|
4889
|
-
if (status === 401 || status === 403 || err.body?.type === "authentication_error") {
|
|
4890
|
-
return { kind: "provider_auth", message, retryable: false, cause };
|
|
4891
|
-
}
|
|
4892
|
-
if (status === 408 || status === 0) {
|
|
4893
|
-
return { kind: "provider_timeout", message, retryable: true, cause };
|
|
4894
|
-
}
|
|
4895
|
-
if (status >= 500 && status < 600) {
|
|
4896
|
-
return {
|
|
4897
|
-
kind: "provider_5xx",
|
|
4898
|
-
message,
|
|
4899
|
-
retryable: true,
|
|
4900
|
-
backoffMs: 3e3,
|
|
4901
|
-
cause
|
|
4902
|
-
};
|
|
4903
|
-
}
|
|
4904
|
-
return { kind: "unknown", message, retryable: err.retryable, cause };
|
|
5649
|
+
function makeFleetUsageTool(director) {
|
|
5650
|
+
return {
|
|
5651
|
+
name: "fleet_usage",
|
|
5652
|
+
description: "Token + cost breakdown across the fleet, per-subagent and totals.",
|
|
5653
|
+
permission: "auto",
|
|
5654
|
+
mutating: false,
|
|
5655
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
5656
|
+
async execute() {
|
|
5657
|
+
return director.snapshot();
|
|
5658
|
+
}
|
|
5659
|
+
};
|
|
4905
5660
|
}
|
|
4906
5661
|
|
|
4907
5662
|
// src/coordination/director.ts
|
|
@@ -5461,242 +6216,6 @@ var Director = class {
|
|
|
5461
6216
|
return t;
|
|
5462
6217
|
}
|
|
5463
6218
|
};
|
|
5464
|
-
function makeSpawnTool(director, roster) {
|
|
5465
|
-
const inputSchema = {
|
|
5466
|
-
type: "object",
|
|
5467
|
-
properties: {
|
|
5468
|
-
role: {
|
|
5469
|
-
type: "string",
|
|
5470
|
-
description: "Roster role id (preferred). When set, the spawn uses the matching config from the roster and ignores other fields."
|
|
5471
|
-
},
|
|
5472
|
-
name: {
|
|
5473
|
-
type: "string",
|
|
5474
|
-
description: "Display name for the subagent. Required when not using roster."
|
|
5475
|
-
},
|
|
5476
|
-
provider: {
|
|
5477
|
-
type: "string",
|
|
5478
|
-
description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.'
|
|
5479
|
-
},
|
|
5480
|
-
model: {
|
|
5481
|
-
type: "string",
|
|
5482
|
-
description: "Model id within the provider. Defaults to the leader model when omitted."
|
|
5483
|
-
},
|
|
5484
|
-
systemPromptOverride: {
|
|
5485
|
-
type: "string",
|
|
5486
|
-
description: "Extra prompt text appended after the role-base prompt."
|
|
5487
|
-
},
|
|
5488
|
-
maxIterations: { type: "number" },
|
|
5489
|
-
maxToolCalls: { type: "number" },
|
|
5490
|
-
maxCostUsd: { type: "number" }
|
|
5491
|
-
},
|
|
5492
|
-
required: []
|
|
5493
|
-
};
|
|
5494
|
-
return {
|
|
5495
|
-
name: "spawn_subagent",
|
|
5496
|
-
description: "Create a new subagent under this director. Returns the subagent id. Use this when you need a worker with a specific provider, model, or role to handle a piece of the plan.",
|
|
5497
|
-
usageHint: "Either pass `role` (matches the roster) OR pass `name` + optional `provider`/`model`. Returns `{ subagentId }`.",
|
|
5498
|
-
permission: "auto",
|
|
5499
|
-
mutating: false,
|
|
5500
|
-
inputSchema,
|
|
5501
|
-
async execute(input) {
|
|
5502
|
-
const i = input ?? {};
|
|
5503
|
-
const role = typeof i.role === "string" ? i.role : void 0;
|
|
5504
|
-
const base = role && roster ? roster[role] : void 0;
|
|
5505
|
-
if (role && !base) {
|
|
5506
|
-
return {
|
|
5507
|
-
error: `unknown role "${role}". roster has: ${roster ? Object.keys(roster).join(", ") : "(empty)"}`
|
|
5508
|
-
};
|
|
5509
|
-
}
|
|
5510
|
-
const cfg = {
|
|
5511
|
-
...base ?? { name: i.name ?? "subagent" }
|
|
5512
|
-
};
|
|
5513
|
-
if (typeof i.name === "string") cfg.name = i.name;
|
|
5514
|
-
if (typeof i.provider === "string") cfg.provider = i.provider;
|
|
5515
|
-
if (typeof i.model === "string") cfg.model = i.model;
|
|
5516
|
-
if (typeof i.systemPromptOverride === "string")
|
|
5517
|
-
cfg.systemPromptOverride = i.systemPromptOverride;
|
|
5518
|
-
if (typeof i.maxIterations === "number") cfg.maxIterations = i.maxIterations;
|
|
5519
|
-
if (typeof i.maxToolCalls === "number") cfg.maxToolCalls = i.maxToolCalls;
|
|
5520
|
-
if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
|
|
5521
|
-
try {
|
|
5522
|
-
const subagentId = await director.spawn(cfg);
|
|
5523
|
-
return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name };
|
|
5524
|
-
} catch (err) {
|
|
5525
|
-
if (err instanceof DirectorBudgetError) {
|
|
5526
|
-
return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
|
|
5527
|
-
}
|
|
5528
|
-
return { error: err instanceof Error ? err.message : String(err) };
|
|
5529
|
-
}
|
|
5530
|
-
}
|
|
5531
|
-
};
|
|
5532
|
-
}
|
|
5533
|
-
function makeAssignTool(director) {
|
|
5534
|
-
const inputSchema = {
|
|
5535
|
-
type: "object",
|
|
5536
|
-
properties: {
|
|
5537
|
-
subagentId: { type: "string", description: "Target subagent id. Required." },
|
|
5538
|
-
description: {
|
|
5539
|
-
type: "string",
|
|
5540
|
-
description: "The task in natural language \u2014 what you want this subagent to do."
|
|
5541
|
-
},
|
|
5542
|
-
maxToolCalls: { type: "number", description: "Optional per-task tool-call budget override." },
|
|
5543
|
-
timeoutMs: { type: "number", description: "Optional per-task timeout in ms." }
|
|
5544
|
-
},
|
|
5545
|
-
required: ["subagentId", "description"]
|
|
5546
|
-
};
|
|
5547
|
-
return {
|
|
5548
|
-
name: "assign_task",
|
|
5549
|
-
description: "Hand a task to a previously spawned subagent. Returns the task id \u2014 pass it to `await_tasks` to block on completion.",
|
|
5550
|
-
permission: "auto",
|
|
5551
|
-
mutating: false,
|
|
5552
|
-
inputSchema,
|
|
5553
|
-
async execute(input) {
|
|
5554
|
-
const i = input;
|
|
5555
|
-
const task = {
|
|
5556
|
-
id: randomUUID(),
|
|
5557
|
-
description: i.description,
|
|
5558
|
-
subagentId: i.subagentId,
|
|
5559
|
-
maxToolCalls: i.maxToolCalls,
|
|
5560
|
-
timeoutMs: i.timeoutMs
|
|
5561
|
-
};
|
|
5562
|
-
const taskId = await director.assign(task);
|
|
5563
|
-
return { taskId, subagentId: i.subagentId };
|
|
5564
|
-
}
|
|
5565
|
-
};
|
|
5566
|
-
}
|
|
5567
|
-
function makeAwaitTasksTool(director) {
|
|
5568
|
-
const inputSchema = {
|
|
5569
|
-
type: "object",
|
|
5570
|
-
properties: {
|
|
5571
|
-
taskIds: {
|
|
5572
|
-
type: "array",
|
|
5573
|
-
items: { type: "string" },
|
|
5574
|
-
description: "One or more task ids returned by `assign_task`. The call blocks until every id resolves."
|
|
5575
|
-
}
|
|
5576
|
-
},
|
|
5577
|
-
required: ["taskIds"]
|
|
5578
|
-
};
|
|
5579
|
-
return {
|
|
5580
|
-
name: "await_tasks",
|
|
5581
|
-
description: "Block until every named task completes. Returns the array of TaskResult \u2014 use this to gather subagent output before deciding the next step.",
|
|
5582
|
-
permission: "auto",
|
|
5583
|
-
mutating: false,
|
|
5584
|
-
inputSchema,
|
|
5585
|
-
async execute(input) {
|
|
5586
|
-
const i = input;
|
|
5587
|
-
const results = await director.awaitTasks(i.taskIds);
|
|
5588
|
-
return { results };
|
|
5589
|
-
}
|
|
5590
|
-
};
|
|
5591
|
-
}
|
|
5592
|
-
function makeAskTool(director) {
|
|
5593
|
-
const inputSchema = {
|
|
5594
|
-
type: "object",
|
|
5595
|
-
properties: {
|
|
5596
|
-
subagentId: {
|
|
5597
|
-
type: "string",
|
|
5598
|
-
description: "Subagent to ask. Must be a previously spawned id."
|
|
5599
|
-
},
|
|
5600
|
-
question: {
|
|
5601
|
-
type: "string",
|
|
5602
|
-
description: "The question or instruction. Sent as the bridge message payload."
|
|
5603
|
-
},
|
|
5604
|
-
timeoutMs: { type: "number", description: "Optional timeout in ms (default 30s)." }
|
|
5605
|
-
},
|
|
5606
|
-
required: ["subagentId", "question"]
|
|
5607
|
-
};
|
|
5608
|
-
return {
|
|
5609
|
-
name: "ask_subagent",
|
|
5610
|
-
description: "Synchronously ask a subagent a question. Blocks until the subagent replies via the bridge (or the timeout fires). Use this when you need a one-shot answer without spawning a fresh task.",
|
|
5611
|
-
permission: "auto",
|
|
5612
|
-
mutating: false,
|
|
5613
|
-
inputSchema,
|
|
5614
|
-
async execute(input) {
|
|
5615
|
-
const i = input;
|
|
5616
|
-
try {
|
|
5617
|
-
const answer = await director.ask(i.subagentId, { question: i.question }, i.timeoutMs);
|
|
5618
|
-
return { ok: true, answer };
|
|
5619
|
-
} catch (err) {
|
|
5620
|
-
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
5621
|
-
}
|
|
5622
|
-
}
|
|
5623
|
-
};
|
|
5624
|
-
}
|
|
5625
|
-
function makeRollUpTool(director) {
|
|
5626
|
-
const inputSchema = {
|
|
5627
|
-
type: "object",
|
|
5628
|
-
properties: {
|
|
5629
|
-
taskIds: {
|
|
5630
|
-
type: "array",
|
|
5631
|
-
items: { type: "string" },
|
|
5632
|
-
description: "Completed task ids to aggregate. Pass the ids returned by previous `assign_task` calls."
|
|
5633
|
-
},
|
|
5634
|
-
style: {
|
|
5635
|
-
type: "string",
|
|
5636
|
-
enum: ["markdown", "json"],
|
|
5637
|
-
description: "Output flavor \u2014 markdown (default) for in-prompt summarization, json for structured downstream processing."
|
|
5638
|
-
}
|
|
5639
|
-
},
|
|
5640
|
-
required: ["taskIds"]
|
|
5641
|
-
};
|
|
5642
|
-
return {
|
|
5643
|
-
name: "roll_up",
|
|
5644
|
-
description: "Aggregate completed task results into a single formatted summary. Use this after `await_tasks` to fold subagent outputs back into the director's context before deciding the next step.",
|
|
5645
|
-
permission: "auto",
|
|
5646
|
-
mutating: false,
|
|
5647
|
-
inputSchema,
|
|
5648
|
-
async execute(input) {
|
|
5649
|
-
const i = input;
|
|
5650
|
-
const summary = director.rollUp(i.taskIds, i.style ?? "markdown");
|
|
5651
|
-
return { summary, count: i.taskIds.length };
|
|
5652
|
-
}
|
|
5653
|
-
};
|
|
5654
|
-
}
|
|
5655
|
-
function makeTerminateTool(director) {
|
|
5656
|
-
const inputSchema = {
|
|
5657
|
-
type: "object",
|
|
5658
|
-
properties: {
|
|
5659
|
-
subagentId: { type: "string", description: "Subagent to abort." }
|
|
5660
|
-
},
|
|
5661
|
-
required: ["subagentId"]
|
|
5662
|
-
};
|
|
5663
|
-
return {
|
|
5664
|
-
name: "terminate_subagent",
|
|
5665
|
-
description: 'Forcibly abort a subagent. Use sparingly \u2014 prefer waiting on the natural budget to expire. The current task (if any) ends with status "stopped".',
|
|
5666
|
-
permission: "auto",
|
|
5667
|
-
mutating: true,
|
|
5668
|
-
inputSchema,
|
|
5669
|
-
async execute(input) {
|
|
5670
|
-
const i = input;
|
|
5671
|
-
await director.terminate(i.subagentId);
|
|
5672
|
-
return { ok: true };
|
|
5673
|
-
}
|
|
5674
|
-
};
|
|
5675
|
-
}
|
|
5676
|
-
function makeFleetStatusTool(director) {
|
|
5677
|
-
return {
|
|
5678
|
-
name: "fleet_status",
|
|
5679
|
-
description: "Snapshot of the fleet \u2014 every subagent's current status, pending vs. completed task counts, and the running total iteration count. Cheap; call freely.",
|
|
5680
|
-
permission: "auto",
|
|
5681
|
-
mutating: false,
|
|
5682
|
-
inputSchema: { type: "object", properties: {}, required: [] },
|
|
5683
|
-
async execute() {
|
|
5684
|
-
return director.status();
|
|
5685
|
-
}
|
|
5686
|
-
};
|
|
5687
|
-
}
|
|
5688
|
-
function makeFleetUsageTool(director) {
|
|
5689
|
-
return {
|
|
5690
|
-
name: "fleet_usage",
|
|
5691
|
-
description: "Token + cost breakdown across the fleet, per-subagent and totals. Use this to reason about which workers to assign costly tasks to or when to wrap up to stay within budget.",
|
|
5692
|
-
permission: "auto",
|
|
5693
|
-
mutating: false,
|
|
5694
|
-
inputSchema: { type: "object", properties: {}, required: [] },
|
|
5695
|
-
async execute() {
|
|
5696
|
-
return director.snapshot();
|
|
5697
|
-
}
|
|
5698
|
-
};
|
|
5699
|
-
}
|
|
5700
6219
|
function createDelegateTool(opts) {
|
|
5701
6220
|
const defaultTimeoutMs = opts.defaultTimeoutMs ?? 4 * 60 * 60 * 1e3;
|
|
5702
6221
|
const rosterIds = opts.roster ? Object.keys(opts.roster) : [];
|
|
@@ -8234,13 +8753,13 @@ function roughEstimate(messages) {
|
|
|
8234
8753
|
function createContextManagerTool(opts = {}) {
|
|
8235
8754
|
return {
|
|
8236
8755
|
name: CONTEXT_MANAGER_TOOL_NAME,
|
|
8237
|
-
description: 'Inspect or reorganize the conversation context window. Use "check" to see token budget. Use "summary" to collapse a message range into a concise note (provide "text" for custom summary). Use "prune" to remove specific messages by index. Use "add_note" to inject a summary note. Use "compact" to run aggressive compaction.',
|
|
8756
|
+
description: 'Inspect or reorganize the conversation context window. Use "check" to see token budget. Use "summary" to collapse a message range into a concise note (provide "text" for custom summary). Use "prune" to remove specific messages by index. Use "add_note" to inject a summary note. Use "compact" to run aggressive compaction. Use "repair" to remove orphan tool_use/tool_result blocks after manual context surgery.',
|
|
8238
8757
|
inputSchema: {
|
|
8239
8758
|
type: "object",
|
|
8240
8759
|
properties: {
|
|
8241
8760
|
action: {
|
|
8242
8761
|
type: "string",
|
|
8243
|
-
enum: ["check", "summary", "prune", "add_note", "compact"],
|
|
8762
|
+
enum: ["check", "summary", "prune", "add_note", "compact", "repair"],
|
|
8244
8763
|
description: "The context operation to perform."
|
|
8245
8764
|
},
|
|
8246
8765
|
from: {
|
|
@@ -8268,12 +8787,15 @@ function createContextManagerTool(opts = {}) {
|
|
|
8268
8787
|
const messages = ctx.messages;
|
|
8269
8788
|
const beforeTokens = roughEstimate(messages);
|
|
8270
8789
|
const applyMessages = (next) => {
|
|
8790
|
+
const repaired = repairToolUseAdjacency(next);
|
|
8791
|
+
const finalMessages = repaired.messages;
|
|
8271
8792
|
if (ctx.state) {
|
|
8272
|
-
ctx.state.replaceMessages(
|
|
8793
|
+
ctx.state.replaceMessages(finalMessages);
|
|
8273
8794
|
} else {
|
|
8274
8795
|
messages.length = 0;
|
|
8275
|
-
messages.splice(0, 0, ...
|
|
8796
|
+
messages.splice(0, 0, ...finalMessages);
|
|
8276
8797
|
}
|
|
8798
|
+
return repaired.report;
|
|
8277
8799
|
};
|
|
8278
8800
|
switch (input.action) {
|
|
8279
8801
|
case "check": {
|
|
@@ -8290,6 +8812,22 @@ function createContextManagerTool(opts = {}) {
|
|
|
8290
8812
|
})
|
|
8291
8813
|
};
|
|
8292
8814
|
}
|
|
8815
|
+
case "repair": {
|
|
8816
|
+
const repair = applyMessages([...messages]);
|
|
8817
|
+
const afterTokens = roughEstimate(ctx.messages);
|
|
8818
|
+
return {
|
|
8819
|
+
action: "repair",
|
|
8820
|
+
beforeTokens,
|
|
8821
|
+
afterTokens,
|
|
8822
|
+
messageCount: ctx.messages.length,
|
|
8823
|
+
repaired: repair.changed ? {
|
|
8824
|
+
removedToolUses: repair.removedToolUses,
|
|
8825
|
+
removedToolResults: repair.removedToolResults,
|
|
8826
|
+
removedMessages: repair.removedMessages
|
|
8827
|
+
} : void 0,
|
|
8828
|
+
notes: repair.changed ? "Context tool-call adjacency repaired." : "Context tool-call adjacency already valid."
|
|
8829
|
+
};
|
|
8830
|
+
}
|
|
8293
8831
|
case "compact": {
|
|
8294
8832
|
if (!opts.compactor) {
|
|
8295
8833
|
return {
|
|
@@ -8300,11 +8838,19 @@ function createContextManagerTool(opts = {}) {
|
|
|
8300
8838
|
};
|
|
8301
8839
|
}
|
|
8302
8840
|
const report = await opts.compactor.compact(ctx);
|
|
8841
|
+
const repair = applyMessages([...ctx.messages]);
|
|
8842
|
+
const afterTokens = repair.changed ? roughEstimate(ctx.messages) : report.after;
|
|
8843
|
+
const repaired = report.repaired ?? (repair.changed ? repair : void 0);
|
|
8303
8844
|
return {
|
|
8304
8845
|
action: "compact",
|
|
8305
8846
|
beforeTokens,
|
|
8306
|
-
afterTokens
|
|
8307
|
-
messageCount: messages.length
|
|
8847
|
+
afterTokens,
|
|
8848
|
+
messageCount: ctx.messages.length,
|
|
8849
|
+
repaired: repaired ? {
|
|
8850
|
+
removedToolUses: repaired.removedToolUses,
|
|
8851
|
+
removedToolResults: repaired.removedToolResults,
|
|
8852
|
+
removedMessages: repaired.removedMessages
|
|
8853
|
+
} : void 0
|
|
8308
8854
|
};
|
|
8309
8855
|
}
|
|
8310
8856
|
case "prune": {
|
|
@@ -8320,14 +8866,19 @@ function createContextManagerTool(opts = {}) {
|
|
|
8320
8866
|
}
|
|
8321
8867
|
const copy = [...messages];
|
|
8322
8868
|
const removed = copy.splice(from, to - from + 1);
|
|
8323
|
-
applyMessages(copy);
|
|
8324
|
-
const afterTokens = roughEstimate(
|
|
8869
|
+
const repair = applyMessages(copy);
|
|
8870
|
+
const afterTokens = roughEstimate(ctx.messages);
|
|
8325
8871
|
return {
|
|
8326
8872
|
action: "prune",
|
|
8327
8873
|
beforeTokens,
|
|
8328
8874
|
afterTokens,
|
|
8329
|
-
messageCount:
|
|
8330
|
-
removedCount: removed.length
|
|
8875
|
+
messageCount: ctx.messages.length,
|
|
8876
|
+
removedCount: removed.length,
|
|
8877
|
+
repaired: repair.changed ? {
|
|
8878
|
+
removedToolUses: repair.removedToolUses,
|
|
8879
|
+
removedToolResults: repair.removedToolResults,
|
|
8880
|
+
removedMessages: repair.removedMessages
|
|
8881
|
+
} : void 0
|
|
8331
8882
|
};
|
|
8332
8883
|
}
|
|
8333
8884
|
case "add_note": {
|
|
@@ -8339,14 +8890,19 @@ function createContextManagerTool(opts = {}) {
|
|
|
8339
8890
|
};
|
|
8340
8891
|
const copy = [...messages];
|
|
8341
8892
|
copy.splice(afterIdx, 0, noteMsg);
|
|
8342
|
-
applyMessages(copy);
|
|
8343
|
-
const afterTokens = roughEstimate(
|
|
8893
|
+
const repair = applyMessages(copy);
|
|
8894
|
+
const afterTokens = roughEstimate(ctx.messages);
|
|
8344
8895
|
return {
|
|
8345
8896
|
action: "add_note",
|
|
8346
8897
|
beforeTokens,
|
|
8347
8898
|
afterTokens,
|
|
8348
|
-
messageCount:
|
|
8349
|
-
summary: noteText
|
|
8899
|
+
messageCount: ctx.messages.length,
|
|
8900
|
+
summary: noteText,
|
|
8901
|
+
repaired: repair.changed ? {
|
|
8902
|
+
removedToolUses: repair.removedToolUses,
|
|
8903
|
+
removedToolResults: repair.removedToolResults,
|
|
8904
|
+
removedMessages: repair.removedMessages
|
|
8905
|
+
} : void 0
|
|
8350
8906
|
};
|
|
8351
8907
|
}
|
|
8352
8908
|
case "summary": {
|
|
@@ -8367,14 +8923,19 @@ function createContextManagerTool(opts = {}) {
|
|
|
8367
8923
|
};
|
|
8368
8924
|
const copy = [...messages];
|
|
8369
8925
|
copy.splice(from, to - from + 1, summaryMsg);
|
|
8370
|
-
applyMessages(copy);
|
|
8371
|
-
const afterTokens = roughEstimate(
|
|
8926
|
+
const repair = applyMessages(copy);
|
|
8927
|
+
const afterTokens = roughEstimate(ctx.messages);
|
|
8372
8928
|
return {
|
|
8373
8929
|
action: "summary",
|
|
8374
8930
|
beforeTokens,
|
|
8375
8931
|
afterTokens,
|
|
8376
|
-
messageCount:
|
|
8377
|
-
summary: summaryText
|
|
8932
|
+
messageCount: ctx.messages.length,
|
|
8933
|
+
summary: summaryText,
|
|
8934
|
+
repaired: repair.changed ? {
|
|
8935
|
+
removedToolUses: repair.removedToolUses,
|
|
8936
|
+
removedToolResults: repair.removedToolResults,
|
|
8937
|
+
removedMessages: repair.removedMessages
|
|
8938
|
+
} : void 0
|
|
8378
8939
|
};
|
|
8379
8940
|
}
|
|
8380
8941
|
default:
|
|
@@ -8478,6 +9039,41 @@ var sentinelServer = () => ({
|
|
|
8478
9039
|
permission: "deny"
|
|
8479
9040
|
// security tool — require explicit confirmation
|
|
8480
9041
|
});
|
|
9042
|
+
var zaiVisionServer = () => ({
|
|
9043
|
+
name: "zai-vision",
|
|
9044
|
+
description: "Z.AI Vision MCP \u2014 image analysis and screenshot understanding",
|
|
9045
|
+
transport: "stdio",
|
|
9046
|
+
command: "npx",
|
|
9047
|
+
args: ["-y", "@z_ai/mcp-server@latest"],
|
|
9048
|
+
env: {
|
|
9049
|
+
Z_AI_API_KEY: process.env.Z_AI_API_KEY ?? "",
|
|
9050
|
+
Z_AI_MODE: process.env.Z_AI_MODE ?? "ZAI"
|
|
9051
|
+
},
|
|
9052
|
+
allowedTools: [
|
|
9053
|
+
"image_analysis",
|
|
9054
|
+
"extract_text_from_screenshot",
|
|
9055
|
+
"diagnose_error_screenshot",
|
|
9056
|
+
"understand_technical_diagram",
|
|
9057
|
+
"analyze_data_visualization",
|
|
9058
|
+
"ui_diff_check"
|
|
9059
|
+
],
|
|
9060
|
+
permission: "auto"
|
|
9061
|
+
});
|
|
9062
|
+
var miniMaxVisionServer = () => ({
|
|
9063
|
+
name: "minimax-vision",
|
|
9064
|
+
description: "MiniMax MCP \u2014 image understanding via understand_image",
|
|
9065
|
+
transport: "stdio",
|
|
9066
|
+
command: "uvx",
|
|
9067
|
+
args: ["minimax-coding-plan-mcp", "-y"],
|
|
9068
|
+
env: {
|
|
9069
|
+
MINIMAX_API_KEY: process.env.MINIMAX_API_KEY ?? "",
|
|
9070
|
+
MINIMAX_MCP_BASE_PATH: process.env.MINIMAX_MCP_BASE_PATH ?? "./.wrongstack/minimax-output",
|
|
9071
|
+
MINIMAX_API_HOST: process.env.MINIMAX_API_HOST ?? "https://api.minimax.io",
|
|
9072
|
+
MINIMAX_API_RESOURCE_MODE: process.env.MINIMAX_API_RESOURCE_MODE ?? "url"
|
|
9073
|
+
},
|
|
9074
|
+
allowedTools: ["understand_image"],
|
|
9075
|
+
permission: "auto"
|
|
9076
|
+
});
|
|
8481
9077
|
var allServers = () => ({
|
|
8482
9078
|
filesystem: { ...filesystemServer(), enabled: false },
|
|
8483
9079
|
github: { ...githubServer(), enabled: false },
|
|
@@ -8488,9 +9084,11 @@ var allServers = () => ({
|
|
|
8488
9084
|
slack: { ...slackServer(), enabled: false },
|
|
8489
9085
|
aws: { ...awsServer(), enabled: false },
|
|
8490
9086
|
"google-maps": { ...googleMapsServer(), enabled: false },
|
|
8491
|
-
sentinel: { ...sentinelServer(), enabled: false }
|
|
9087
|
+
sentinel: { ...sentinelServer(), enabled: false },
|
|
9088
|
+
"zai-vision": { ...zaiVisionServer(), enabled: false },
|
|
9089
|
+
"minimax-vision": { ...miniMaxVisionServer(), enabled: false }
|
|
8492
9090
|
});
|
|
8493
9091
|
|
|
8494
|
-
export { ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorBudgetError, DirectorStateCheckpoint, DoneConditionChecker, FLEET_ROSTER, FleetBus, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SubagentBudget, TaskFlow, TaskGenerator, TaskTracker, ToolExecutor, addPlanItem, allServers, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createContextManagerTool, createDelegateTool, createMessage, decryptConfigSecrets, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, githubServer, googleMapsServer, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, migratePlaintextSecrets, removePlanItem, renderPrometheus, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, wireMetricsToEvents };
|
|
9092
|
+
export { ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorBudgetError, DirectorStateCheckpoint, DoneConditionChecker, FLEET_ROSTER, FleetBus, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SubagentBudget, TaskFlow, TaskGenerator, TaskTracker, ToolExecutor, addPlanItem, allServers, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createContextManagerTool, createDelegateTool, createMessage, decryptConfigSecrets2 as decryptConfigSecrets, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, githubServer, googleMapsServer, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, migratePlaintextSecrets, miniMaxVisionServer, removePlanItem, renderPrometheus, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, wireMetricsToEvents, zaiVisionServer };
|
|
8495
9093
|
//# sourceMappingURL=index.js.map
|
|
8496
9094
|
//# sourceMappingURL=index.js.map
|