@wrongstack/core 0.1.8 → 0.1.10
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-6KPqsFx6.d.ts +33 -0
- package/dist/compactor-B4mQZXf2.d.ts +17 -0
- package/dist/config-BU9f_5yH.d.ts +193 -0
- package/dist/{provider-txgB0Oq9.d.ts → context-BmM2xGUZ.d.ts} +532 -472
- package/dist/coordination/index.d.ts +694 -0
- package/dist/coordination/index.js +1995 -0
- package/dist/coordination/index.js.map +1 -0
- package/dist/defaults/index.d.ts +34 -2206
- package/dist/defaults/index.js +4116 -3790
- package/dist/defaults/index.js.map +1 -1
- package/dist/events-BMNaEFZl.d.ts +218 -0
- package/dist/execution/index.d.ts +260 -0
- package/dist/execution/index.js +1625 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/index.d.ts +50 -12
- package/dist/index.js +6669 -5909
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +10 -0
- package/dist/infrastructure/index.js +575 -0
- package/dist/infrastructure/index.js.map +1 -0
- package/dist/input-reader-E-ffP2ee.d.ts +12 -0
- package/dist/kernel/index.d.ts +15 -4
- package/dist/kernel/index.js.map +1 -1
- package/dist/logger-BH6AE0W9.d.ts +24 -0
- package/dist/logger-BMQgxvdy.d.ts +12 -0
- package/dist/mcp-servers-Dzgg4x1w.d.ts +100 -0
- package/dist/memory-CEXuo7sz.d.ts +16 -0
- package/dist/mode-CV077NjV.d.ts +27 -0
- package/dist/models/index.d.ts +60 -0
- package/dist/models/index.js +621 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models-registry-DqzwpBQy.d.ts +46 -0
- package/dist/models-registry-Y2xbog0E.d.ts +95 -0
- package/dist/multi-agent-fmkRHtof.d.ts +283 -0
- package/dist/observability/index.d.ts +353 -0
- package/dist/observability/index.js +691 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability-BhnVLBLS.d.ts +67 -0
- package/dist/path-resolver-CPRj4bFY.d.ts +10 -0
- package/dist/path-resolver-DBjaoXFq.d.ts +54 -0
- package/dist/plugin-DJk6LL8B.d.ts +434 -0
- package/dist/renderer-rk_1Swwc.d.ts +158 -0
- package/dist/sdd/index.d.ts +206 -0
- package/dist/sdd/index.js +864 -0
- package/dist/sdd/index.js.map +1 -0
- package/dist/secret-scrubber-CicHLN4G.d.ts +31 -0
- package/dist/secret-scrubber-DF88luOe.d.ts +54 -0
- package/dist/secret-vault-DoISxaKO.d.ts +19 -0
- package/dist/security/index.d.ts +30 -0
- package/dist/security/index.js +524 -0
- package/dist/security/index.js.map +1 -0
- package/dist/selector-BbJqiEP4.d.ts +51 -0
- package/dist/session-reader-Drq8RvJu.d.ts +150 -0
- package/dist/skill-DhfSizKv.d.ts +72 -0
- package/dist/storage/index.d.ts +382 -0
- package/dist/storage/index.js +1530 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/{system-prompt-vAB0F54-.d.ts → system-prompt-BC_8ypCG.d.ts} +1 -1
- package/dist/task-graph-BITvWt4t.d.ts +160 -0
- package/dist/tool-executor-CpuJPYm9.d.ts +97 -0
- package/dist/types/index.d.ts +26 -4
- package/dist/types/index.js +1787 -4
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +49 -2
- package/dist/utils/index.js +100 -2
- package/dist/utils/index.js.map +1 -1
- package/package.json +34 -2
- package/skills/audit-log/SKILL.md +67 -0
- package/skills/bug-hunter/SKILL.md +87 -0
- package/skills/refactor-planner/SKILL.md +94 -0
- package/skills/security-scanner/SKILL.md +117 -0
- package/dist/mode-Pjt5vMS6.d.ts +0 -815
- package/dist/session-reader-9sOTgmeC.d.ts +0 -1087
package/dist/types/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { randomBytes, createCipheriv, createDecipheriv, randomUUID } from 'crypto';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as fs4 from 'fs/promises';
|
|
4
|
+
import * as path3 from 'path';
|
|
5
|
+
|
|
1
6
|
// src/types/blocks.ts
|
|
2
7
|
function isTextBlock(b) {
|
|
3
8
|
return b.type === "text";
|
|
@@ -11,6 +16,9 @@ function isToolResultBlock(b) {
|
|
|
11
16
|
function isImageBlock(b) {
|
|
12
17
|
return b.type === "image";
|
|
13
18
|
}
|
|
19
|
+
function isThinkingBlock(b) {
|
|
20
|
+
return b.type === "thinking";
|
|
21
|
+
}
|
|
14
22
|
|
|
15
23
|
// src/types/messages.ts
|
|
16
24
|
function asBlocks(content) {
|
|
@@ -228,6 +236,1069 @@ function providerStatusToCode(status, type) {
|
|
|
228
236
|
// src/types/secret-vault.ts
|
|
229
237
|
var ENCRYPTED_PREFIX = "enc:v1:";
|
|
230
238
|
|
|
239
|
+
// src/security/secret-vault.ts
|
|
240
|
+
var KEY_BYTES = 32;
|
|
241
|
+
var IV_BYTES = 12;
|
|
242
|
+
var TAG_BYTES = 16;
|
|
243
|
+
var ALGO = "aes-256-gcm";
|
|
244
|
+
var DefaultSecretVault = class {
|
|
245
|
+
keyFile;
|
|
246
|
+
key;
|
|
247
|
+
constructor(opts) {
|
|
248
|
+
this.keyFile = opts.keyFile;
|
|
249
|
+
}
|
|
250
|
+
isEncrypted(value) {
|
|
251
|
+
return typeof value === "string" && value.startsWith(ENCRYPTED_PREFIX);
|
|
252
|
+
}
|
|
253
|
+
encrypt(plaintext) {
|
|
254
|
+
if (this.isEncrypted(plaintext)) return plaintext;
|
|
255
|
+
const key = this.loadOrCreateKey();
|
|
256
|
+
const iv = randomBytes(IV_BYTES);
|
|
257
|
+
const cipher = createCipheriv(ALGO, key, iv);
|
|
258
|
+
const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
259
|
+
const tag = cipher.getAuthTag();
|
|
260
|
+
return `${ENCRYPTED_PREFIX}${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
|
|
261
|
+
}
|
|
262
|
+
decrypt(value) {
|
|
263
|
+
if (!this.isEncrypted(value)) return value;
|
|
264
|
+
const rest = value.slice(ENCRYPTED_PREFIX.length);
|
|
265
|
+
const parts = rest.split(":");
|
|
266
|
+
if (parts.length !== 3) {
|
|
267
|
+
throw new Error("SecretVault: malformed encrypted value");
|
|
268
|
+
}
|
|
269
|
+
const [ivB64, tagB64, ctB64] = parts;
|
|
270
|
+
const iv = Buffer.from(ivB64, "base64");
|
|
271
|
+
const tag = Buffer.from(tagB64, "base64");
|
|
272
|
+
const ct = Buffer.from(ctB64, "base64");
|
|
273
|
+
if (iv.length !== IV_BYTES) throw new Error("SecretVault: bad IV length");
|
|
274
|
+
if (tag.length !== TAG_BYTES) throw new Error("SecretVault: bad tag length");
|
|
275
|
+
const key = this.loadOrCreateKey();
|
|
276
|
+
const decipher = createDecipheriv(ALGO, key, iv);
|
|
277
|
+
decipher.setAuthTag(tag);
|
|
278
|
+
const pt = Buffer.concat([decipher.update(ct), decipher.final()]);
|
|
279
|
+
return pt.toString("utf8");
|
|
280
|
+
}
|
|
281
|
+
loadOrCreateKey() {
|
|
282
|
+
if (this.key) return this.key;
|
|
283
|
+
try {
|
|
284
|
+
const buf = fs.readFileSync(this.keyFile);
|
|
285
|
+
if (buf.length !== KEY_BYTES) {
|
|
286
|
+
throw new Error(`SecretVault: key file ${this.keyFile} has wrong size`);
|
|
287
|
+
}
|
|
288
|
+
this.key = buf;
|
|
289
|
+
return this.key;
|
|
290
|
+
} catch (err) {
|
|
291
|
+
if (err.code !== "ENOENT") throw err;
|
|
292
|
+
}
|
|
293
|
+
fs.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
|
|
294
|
+
const key = randomBytes(KEY_BYTES);
|
|
295
|
+
try {
|
|
296
|
+
fs.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
|
|
297
|
+
} catch (err) {
|
|
298
|
+
if (err.code !== "EEXIST") throw err;
|
|
299
|
+
const buf = fs.readFileSync(this.keyFile);
|
|
300
|
+
if (buf.length !== KEY_BYTES) {
|
|
301
|
+
throw new Error(`SecretVault: key file ${this.keyFile} has wrong size`);
|
|
302
|
+
}
|
|
303
|
+
this.key = buf;
|
|
304
|
+
return this.key;
|
|
305
|
+
}
|
|
306
|
+
this.key = key;
|
|
307
|
+
return key;
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
function decryptConfigSecrets(cfg, vault) {
|
|
311
|
+
return walk(cfg, vault, (v, key) => {
|
|
312
|
+
try {
|
|
313
|
+
return vault.decrypt(v);
|
|
314
|
+
} catch (err) {
|
|
315
|
+
console.warn(
|
|
316
|
+
`[secret-vault] Failed to decrypt "${key}":`,
|
|
317
|
+
err instanceof Error ? err.message : err
|
|
318
|
+
);
|
|
319
|
+
return "";
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
function encryptConfigSecrets(cfg, vault) {
|
|
324
|
+
return walk(cfg, vault, (v) => vault.encrypt(v));
|
|
325
|
+
}
|
|
326
|
+
function walk(node, vault, transform) {
|
|
327
|
+
if (node === null || node === void 0) return node;
|
|
328
|
+
if (typeof node !== "object") return node;
|
|
329
|
+
if (Array.isArray(node)) {
|
|
330
|
+
return node.map((item) => walk(item, vault, transform));
|
|
331
|
+
}
|
|
332
|
+
const out = {};
|
|
333
|
+
for (const [k, v] of Object.entries(node)) {
|
|
334
|
+
if (typeof v === "string" && isSecretField(k)) {
|
|
335
|
+
out[k] = transform(v, k);
|
|
336
|
+
} else if (typeof v === "object" && v !== null) {
|
|
337
|
+
out[k] = walk(v, vault, transform);
|
|
338
|
+
} else {
|
|
339
|
+
out[k] = v;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return out;
|
|
343
|
+
}
|
|
344
|
+
var SECRET_KEY_PATTERN = /(?:apikey|api_key|authtoken|auth_token|bearer|secret|password|passwd|pwd|refreshtoken|refresh_token|sessionkey|session_key|access[_-]?token|private[_-]?key)/i;
|
|
345
|
+
var NON_SECRET_OVERRIDES = /* @__PURE__ */ new Set(["publickey", "public_key"]);
|
|
346
|
+
function isSecretField(name) {
|
|
347
|
+
const lc = name.toLowerCase();
|
|
348
|
+
if (NON_SECRET_OVERRIDES.has(lc)) return false;
|
|
349
|
+
return SECRET_KEY_PATTERN.test(lc);
|
|
350
|
+
}
|
|
351
|
+
async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
352
|
+
let current = {};
|
|
353
|
+
try {
|
|
354
|
+
const raw = await fs4.readFile(configPath, "utf8");
|
|
355
|
+
current = JSON.parse(raw);
|
|
356
|
+
} catch {
|
|
357
|
+
}
|
|
358
|
+
const merged = deepMerge(current, patch ?? {});
|
|
359
|
+
const encrypted = encryptConfigSecrets(merged, vault);
|
|
360
|
+
await fs4.mkdir(path3.dirname(configPath), { recursive: true });
|
|
361
|
+
await fs4.writeFile(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
362
|
+
try {
|
|
363
|
+
await fs4.chmod(configPath, 384);
|
|
364
|
+
} catch {
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
async function migratePlaintextSecrets(configPath, vault) {
|
|
368
|
+
let raw;
|
|
369
|
+
try {
|
|
370
|
+
raw = await fs4.readFile(configPath, "utf8");
|
|
371
|
+
} catch {
|
|
372
|
+
return { migrated: 0, file: configPath };
|
|
373
|
+
}
|
|
374
|
+
let parsed;
|
|
375
|
+
try {
|
|
376
|
+
parsed = JSON.parse(raw);
|
|
377
|
+
} catch {
|
|
378
|
+
return { migrated: 0, file: configPath };
|
|
379
|
+
}
|
|
380
|
+
const counter = { n: 0 };
|
|
381
|
+
const migrated = walkCount(parsed, vault, counter);
|
|
382
|
+
if (counter.n === 0) return { migrated: 0, file: configPath };
|
|
383
|
+
await fs4.writeFile(configPath, JSON.stringify(migrated, null, 2), { mode: 384 });
|
|
384
|
+
try {
|
|
385
|
+
await fs4.chmod(configPath, 384);
|
|
386
|
+
} catch {
|
|
387
|
+
}
|
|
388
|
+
return { migrated: counter.n, file: configPath };
|
|
389
|
+
}
|
|
390
|
+
function walkCount(node, vault, counter) {
|
|
391
|
+
if (node === null || node === void 0) return node;
|
|
392
|
+
if (typeof node !== "object") return node;
|
|
393
|
+
if (Array.isArray(node)) {
|
|
394
|
+
return node.map((item) => walkCount(item, vault, counter));
|
|
395
|
+
}
|
|
396
|
+
const out = {};
|
|
397
|
+
for (const [k, v] of Object.entries(node)) {
|
|
398
|
+
if (typeof v === "string" && isSecretField(k) && !vault.isEncrypted(v) && v.length > 0) {
|
|
399
|
+
out[k] = vault.encrypt(v);
|
|
400
|
+
counter.n++;
|
|
401
|
+
} else if (typeof v === "object" && v !== null) {
|
|
402
|
+
out[k] = walkCount(v, vault, counter);
|
|
403
|
+
} else {
|
|
404
|
+
out[k] = v;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return out;
|
|
408
|
+
}
|
|
409
|
+
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
410
|
+
function deepMerge(a, b) {
|
|
411
|
+
const out = { ...a };
|
|
412
|
+
for (const [k, v] of Object.entries(b)) {
|
|
413
|
+
if (FORBIDDEN_PROTO_KEYS.has(k)) continue;
|
|
414
|
+
const existing = out[k];
|
|
415
|
+
if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
416
|
+
out[k] = deepMerge(existing, v);
|
|
417
|
+
} else {
|
|
418
|
+
out[k] = v;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return out;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// src/utils/color.ts
|
|
425
|
+
var isColorTty = () => {
|
|
426
|
+
if (process.env.NO_COLOR) return false;
|
|
427
|
+
if (process.env.FORCE_COLOR) return true;
|
|
428
|
+
return Boolean(process.stdout?.isTTY);
|
|
429
|
+
};
|
|
430
|
+
var COLOR = isColorTty();
|
|
431
|
+
var wrap = (open2, close) => (s) => COLOR ? `\x1B[${open2}m${s}\x1B[${close}m` : s;
|
|
432
|
+
var color = {
|
|
433
|
+
reset: wrap("0", "0"),
|
|
434
|
+
bold: wrap("1", "22"),
|
|
435
|
+
dim: wrap("2", "22"),
|
|
436
|
+
italic: wrap("3", "23"),
|
|
437
|
+
underline: wrap("4", "24"),
|
|
438
|
+
red: wrap("31", "39"),
|
|
439
|
+
green: wrap("32", "39"),
|
|
440
|
+
yellow: wrap("33", "39"),
|
|
441
|
+
blue: wrap("34", "39"),
|
|
442
|
+
magenta: wrap("35", "39"),
|
|
443
|
+
cyan: wrap("36", "39"),
|
|
444
|
+
gray: wrap("90", "39"),
|
|
445
|
+
amber: wrap("38;5;214", "39"),
|
|
446
|
+
pink: wrap("38;5;205", "39"),
|
|
447
|
+
bgRed: wrap("41", "49"),
|
|
448
|
+
bgGreen: wrap("42", "49")
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
// src/infrastructure/logger.ts
|
|
452
|
+
var LEVEL_RANK = {
|
|
453
|
+
error: 0,
|
|
454
|
+
warn: 1,
|
|
455
|
+
info: 2,
|
|
456
|
+
debug: 3,
|
|
457
|
+
trace: 4
|
|
458
|
+
};
|
|
459
|
+
var COLORS = {
|
|
460
|
+
error: color.red,
|
|
461
|
+
warn: color.yellow,
|
|
462
|
+
info: color.cyan,
|
|
463
|
+
debug: color.gray,
|
|
464
|
+
trace: color.dim
|
|
465
|
+
};
|
|
466
|
+
var DefaultLogger = class _DefaultLogger {
|
|
467
|
+
level;
|
|
468
|
+
file;
|
|
469
|
+
bindings;
|
|
470
|
+
pretty;
|
|
471
|
+
constructor(opts = {}) {
|
|
472
|
+
this.level = opts.level ?? process.env.WRONGSTACK_LOG_LEVEL ?? "info";
|
|
473
|
+
this.file = opts.file;
|
|
474
|
+
this.bindings = opts.bindings ?? {};
|
|
475
|
+
this.pretty = opts.pretty ?? true;
|
|
476
|
+
if (this.file) {
|
|
477
|
+
try {
|
|
478
|
+
fs.mkdirSync(path3.dirname(this.file), { recursive: true });
|
|
479
|
+
} catch {
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
error(msg, ctx) {
|
|
484
|
+
this.log("error", msg, ctx);
|
|
485
|
+
}
|
|
486
|
+
warn(msg, ctx) {
|
|
487
|
+
this.log("warn", msg, ctx);
|
|
488
|
+
}
|
|
489
|
+
info(msg, ctx) {
|
|
490
|
+
this.log("info", msg, ctx);
|
|
491
|
+
}
|
|
492
|
+
debug(msg, ctx) {
|
|
493
|
+
this.log("debug", msg, ctx);
|
|
494
|
+
}
|
|
495
|
+
trace(msg, ctx) {
|
|
496
|
+
this.log("trace", msg, ctx);
|
|
497
|
+
}
|
|
498
|
+
child(bindings) {
|
|
499
|
+
return new _DefaultLogger({
|
|
500
|
+
level: this.level,
|
|
501
|
+
file: this.file,
|
|
502
|
+
pretty: this.pretty,
|
|
503
|
+
bindings: { ...this.bindings, ...bindings }
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
log(level, msg, ctx) {
|
|
507
|
+
const r = LEVEL_RANK[level];
|
|
508
|
+
const allowed = LEVEL_RANK[this.level];
|
|
509
|
+
if (r > allowed) return;
|
|
510
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
511
|
+
const entry = { ts, level, msg, ...this.bindings };
|
|
512
|
+
if (ctx !== void 0) {
|
|
513
|
+
entry.ctx = ctx instanceof Error ? { message: ctx.message, stack: ctx.stack } : ctx;
|
|
514
|
+
}
|
|
515
|
+
if (this.file) {
|
|
516
|
+
try {
|
|
517
|
+
fs.appendFileSync(this.file, `${JSON.stringify(entry)}
|
|
518
|
+
`);
|
|
519
|
+
} catch {
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (r <= LEVEL_RANK.warn || this.level === "debug" || this.level === "trace") {
|
|
523
|
+
const head = `${color.dim(ts)} ${COLORS[level](level.toUpperCase().padEnd(5))} ${msg}`;
|
|
524
|
+
if (ctx !== void 0) {
|
|
525
|
+
process.stderr.write(`${head} ${formatCtx(ctx)}
|
|
526
|
+
`);
|
|
527
|
+
} else {
|
|
528
|
+
process.stderr.write(`${head}
|
|
529
|
+
`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
function formatCtx(ctx) {
|
|
535
|
+
if (ctx instanceof Error) return color.dim(ctx.message);
|
|
536
|
+
if (typeof ctx === "string") return color.dim(ctx);
|
|
537
|
+
try {
|
|
538
|
+
return color.dim(JSON.stringify(ctx));
|
|
539
|
+
} catch {
|
|
540
|
+
return color.dim(String(ctx));
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// src/infrastructure/token-counter.ts
|
|
545
|
+
var DefaultTokenCounter = class {
|
|
546
|
+
input = 0;
|
|
547
|
+
output = 0;
|
|
548
|
+
cacheRead = 0;
|
|
549
|
+
cacheWrite = 0;
|
|
550
|
+
costInput = 0;
|
|
551
|
+
costOutput = 0;
|
|
552
|
+
registry;
|
|
553
|
+
providerId;
|
|
554
|
+
events;
|
|
555
|
+
priceCache = /* @__PURE__ */ new Map();
|
|
556
|
+
constructor(opts = {}) {
|
|
557
|
+
this.registry = opts.registry;
|
|
558
|
+
this.providerId = opts.providerId;
|
|
559
|
+
this.events = opts.events;
|
|
560
|
+
}
|
|
561
|
+
account(usage, model) {
|
|
562
|
+
this.input += usage.input;
|
|
563
|
+
this.output += usage.output;
|
|
564
|
+
this.cacheRead += usage.cacheRead ?? 0;
|
|
565
|
+
this.cacheWrite += usage.cacheWrite ?? 0;
|
|
566
|
+
const price = model ? this.priceCache.get(model) : void 0;
|
|
567
|
+
if (price) {
|
|
568
|
+
this.applyPrice(usage, price);
|
|
569
|
+
} else if (this.registry && this.providerId && model) {
|
|
570
|
+
void this.registry.getModel(this.providerId, model).then((m) => {
|
|
571
|
+
if (m) {
|
|
572
|
+
const p = priceFromModel(m);
|
|
573
|
+
this.priceCache.set(model, p);
|
|
574
|
+
this.applyPrice(usage, p);
|
|
575
|
+
}
|
|
576
|
+
}).catch(() => {
|
|
577
|
+
this.events?.emit("token.cost_estimate_unavailable", { model: model ?? "<unknown>" });
|
|
578
|
+
return void 0;
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
/** Synchronous variant for code paths that have already resolved the model. */
|
|
583
|
+
accountWithModel(usage, resolved) {
|
|
584
|
+
this.input += usage.input;
|
|
585
|
+
this.output += usage.output;
|
|
586
|
+
this.cacheRead += usage.cacheRead ?? 0;
|
|
587
|
+
this.cacheWrite += usage.cacheWrite ?? 0;
|
|
588
|
+
const price = priceFromModel(resolved);
|
|
589
|
+
this.priceCache.set(resolved.modelId, price);
|
|
590
|
+
this.applyPrice(usage, price);
|
|
591
|
+
}
|
|
592
|
+
total() {
|
|
593
|
+
return {
|
|
594
|
+
input: this.input,
|
|
595
|
+
output: this.output,
|
|
596
|
+
cacheRead: this.cacheRead,
|
|
597
|
+
cacheWrite: this.cacheWrite
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
estimateCost() {
|
|
601
|
+
return {
|
|
602
|
+
input: round4(this.costInput),
|
|
603
|
+
output: round4(this.costOutput),
|
|
604
|
+
total: round4(this.costInput + this.costOutput),
|
|
605
|
+
currency: "USD"
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
cacheStats() {
|
|
609
|
+
const denom = this.cacheRead + this.input;
|
|
610
|
+
return {
|
|
611
|
+
readTokens: this.cacheRead,
|
|
612
|
+
writeTokens: this.cacheWrite,
|
|
613
|
+
hitRatio: denom === 0 ? 0 : this.cacheRead / denom
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
/** Invalidate cached prices so the next account() call fetches fresh data. */
|
|
617
|
+
invalidateCache() {
|
|
618
|
+
this.priceCache.clear();
|
|
619
|
+
}
|
|
620
|
+
reset() {
|
|
621
|
+
this.input = 0;
|
|
622
|
+
this.output = 0;
|
|
623
|
+
this.cacheRead = 0;
|
|
624
|
+
this.cacheWrite = 0;
|
|
625
|
+
this.costInput = 0;
|
|
626
|
+
this.costOutput = 0;
|
|
627
|
+
}
|
|
628
|
+
applyPrice(usage, price) {
|
|
629
|
+
if (price.input) this.costInput += usage.input / 1e6 * price.input;
|
|
630
|
+
if (price.output) this.costOutput += usage.output / 1e6 * price.output;
|
|
631
|
+
if (usage.cacheRead && price.cacheRead) {
|
|
632
|
+
this.costInput += usage.cacheRead / 1e6 * price.cacheRead;
|
|
633
|
+
}
|
|
634
|
+
if (usage.cacheWrite && price.cacheWrite) {
|
|
635
|
+
this.costInput += usage.cacheWrite / 1e6 * price.cacheWrite;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
function priceFromModel(m) {
|
|
640
|
+
return {
|
|
641
|
+
input: m.cost?.input,
|
|
642
|
+
output: m.cost?.output,
|
|
643
|
+
cacheRead: m.cost?.cache_read,
|
|
644
|
+
cacheWrite: m.cost?.cache_write
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
function round4(n) {
|
|
648
|
+
return Math.round(n * 1e4) / 1e4;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// src/utils/token-estimate.ts
|
|
652
|
+
var RoughTokenEstimate = (text) => Math.max(1, Math.ceil(text.length / 4));
|
|
653
|
+
var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
|
|
654
|
+
var ESTIMATE_CACHE_MAX_SIZE = 1e4;
|
|
655
|
+
function getCachedEstimate(key, compute) {
|
|
656
|
+
const existing = ESTIMATE_CACHE.get(key);
|
|
657
|
+
if (existing !== void 0) return existing;
|
|
658
|
+
if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
|
|
659
|
+
const keys = [...ESTIMATE_CACHE.keys()];
|
|
660
|
+
for (let i = 0; i < Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4); i++) {
|
|
661
|
+
ESTIMATE_CACHE.delete(keys[i]);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
const estimate = compute();
|
|
665
|
+
ESTIMATE_CACHE.set(key, estimate);
|
|
666
|
+
return estimate;
|
|
667
|
+
}
|
|
668
|
+
function estimateToolInputTokens(input) {
|
|
669
|
+
if (typeof input === "string") return RoughTokenEstimate(input);
|
|
670
|
+
if (input === null || typeof input !== "object") {
|
|
671
|
+
return RoughTokenEstimate(String(input));
|
|
672
|
+
}
|
|
673
|
+
const key = JSON.stringify(input);
|
|
674
|
+
return getCachedEstimate(key, () => RoughTokenEstimate(key));
|
|
675
|
+
}
|
|
676
|
+
function estimateToolResultTokens(content) {
|
|
677
|
+
if (typeof content === "string") return RoughTokenEstimate(content);
|
|
678
|
+
const key = JSON.stringify(content);
|
|
679
|
+
return getCachedEstimate(key, () => RoughTokenEstimate(key));
|
|
680
|
+
}
|
|
681
|
+
function estimateTextTokens(text) {
|
|
682
|
+
return RoughTokenEstimate(text);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// src/execution/compactor.ts
|
|
686
|
+
var HybridCompactor = class {
|
|
687
|
+
preserveK;
|
|
688
|
+
eliseThreshold;
|
|
689
|
+
estimator;
|
|
690
|
+
constructor(opts = {}) {
|
|
691
|
+
this.preserveK = opts.preserveK ?? 10;
|
|
692
|
+
this.eliseThreshold = opts.eliseThreshold ?? 2e3;
|
|
693
|
+
this.estimator = opts.estimator ?? estimateTextTokens;
|
|
694
|
+
}
|
|
695
|
+
async compact(ctx, opts = {}) {
|
|
696
|
+
const beforeTokens = this.estimateMessages(ctx.messages);
|
|
697
|
+
const reductions = [];
|
|
698
|
+
const phase1Saved = this.eliseOldToolResults(ctx);
|
|
699
|
+
if (phase1Saved > 0) reductions.push({ phase: "elision", saved: phase1Saved });
|
|
700
|
+
if (opts.aggressive) {
|
|
701
|
+
const phase2Saved = this.collapseAncientTurns(ctx);
|
|
702
|
+
if (phase2Saved > 0) reductions.push({ phase: "summary", saved: phase2Saved });
|
|
703
|
+
}
|
|
704
|
+
const afterTokens = this.estimateMessages(ctx.messages);
|
|
705
|
+
return { before: beforeTokens, after: afterTokens, reductions };
|
|
706
|
+
}
|
|
707
|
+
eliseOldToolResults(ctx) {
|
|
708
|
+
const messages = ctx.messages;
|
|
709
|
+
let pairCount = 0;
|
|
710
|
+
let preserveStart = messages.length;
|
|
711
|
+
for (let i = messages.length - 1; i >= 0 && pairCount < this.preserveK; i--) {
|
|
712
|
+
const m = messages[i];
|
|
713
|
+
if (!m) continue;
|
|
714
|
+
if (m.role === "user" || m.role === "assistant") {
|
|
715
|
+
pairCount++;
|
|
716
|
+
preserveStart = i;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
let saved = 0;
|
|
720
|
+
let changed = false;
|
|
721
|
+
const nextMessages = new Array(messages.length);
|
|
722
|
+
for (let i = 0; i < messages.length; i++) {
|
|
723
|
+
const msg = messages[i];
|
|
724
|
+
if (i >= preserveStart) {
|
|
725
|
+
nextMessages[i] = msg;
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
if (!msg || !Array.isArray(msg.content)) {
|
|
729
|
+
nextMessages[i] = msg;
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
const newContent = msg.content.map((b) => {
|
|
733
|
+
if (b.type !== "tool_result") return b;
|
|
734
|
+
const tokens = estimateToolResultTokens(b.content);
|
|
735
|
+
if (tokens < this.eliseThreshold) return b;
|
|
736
|
+
saved += tokens;
|
|
737
|
+
const elided = {
|
|
738
|
+
type: "tool_result",
|
|
739
|
+
tool_use_id: b.tool_use_id,
|
|
740
|
+
content: `[elided: ~${tokens} tokens removed. Call the tool again if needed.]`,
|
|
741
|
+
is_error: b.is_error
|
|
742
|
+
};
|
|
743
|
+
return elided;
|
|
744
|
+
});
|
|
745
|
+
if (newContent.length === msg.content.length && newContent.every((b, idx) => b === msg.content[idx])) {
|
|
746
|
+
nextMessages[i] = msg;
|
|
747
|
+
} else {
|
|
748
|
+
nextMessages[i] = { ...msg, content: newContent };
|
|
749
|
+
changed = true;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
if (changed) ctx.state.replaceMessages(nextMessages);
|
|
753
|
+
return saved;
|
|
754
|
+
}
|
|
755
|
+
collapseAncientTurns(ctx) {
|
|
756
|
+
const messages = ctx.messages;
|
|
757
|
+
const cutTarget = Math.max(0, messages.length - this.preserveK * 2);
|
|
758
|
+
if (cutTarget <= 0) return 0;
|
|
759
|
+
let boundary = -1;
|
|
760
|
+
for (let i = cutTarget; i < messages.length; i++) {
|
|
761
|
+
const m = messages[i];
|
|
762
|
+
if (!m) continue;
|
|
763
|
+
if (m.role === "user" && hasTextContent(m)) {
|
|
764
|
+
boundary = i;
|
|
765
|
+
break;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
if (boundary <= 0) return 0;
|
|
769
|
+
const removed = messages.slice(0, boundary);
|
|
770
|
+
const removedTokens = this.estimateMessages(removed);
|
|
771
|
+
const summary = [
|
|
772
|
+
{
|
|
773
|
+
role: "user",
|
|
774
|
+
content: `[previous_session_summary: ${removed.length} earlier turns compacted. Todo state preserved in context.]`
|
|
775
|
+
},
|
|
776
|
+
{ role: "assistant", content: "Continuing from compacted context." }
|
|
777
|
+
];
|
|
778
|
+
const tail = ctx.messages.slice(boundary);
|
|
779
|
+
ctx.state.replaceMessages([...summary, ...tail]);
|
|
780
|
+
return Math.max(0, removedTokens - this.estimateMessages(summary));
|
|
781
|
+
}
|
|
782
|
+
estimateMessages(messages) {
|
|
783
|
+
let total = 0;
|
|
784
|
+
for (const m of messages) {
|
|
785
|
+
if (typeof m.content === "string") {
|
|
786
|
+
total += this.estimator(m.content);
|
|
787
|
+
} else {
|
|
788
|
+
for (const b of m.content) {
|
|
789
|
+
if (b.type === "text") total += this.estimator(b.text);
|
|
790
|
+
else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
|
|
791
|
+
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return total;
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
function hasTextContent(m) {
|
|
799
|
+
if (typeof m.content === "string") return m.content.trim().length > 0;
|
|
800
|
+
return m.content.some((b) => b.type === "text" && b.text.trim().length > 0);
|
|
801
|
+
}
|
|
802
|
+
var PROJECT_MARKERS = [
|
|
803
|
+
".git",
|
|
804
|
+
"package.json",
|
|
805
|
+
"pnpm-workspace.yaml",
|
|
806
|
+
"go.mod",
|
|
807
|
+
"Cargo.toml",
|
|
808
|
+
"pyproject.toml",
|
|
809
|
+
".wrongstack"
|
|
810
|
+
];
|
|
811
|
+
var DefaultPathResolver = class {
|
|
812
|
+
projectRoot;
|
|
813
|
+
cwd;
|
|
814
|
+
constructor(cwd = process.cwd()) {
|
|
815
|
+
this.cwd = path3.resolve(cwd);
|
|
816
|
+
this.projectRoot = this.detectProjectRoot(this.cwd);
|
|
817
|
+
}
|
|
818
|
+
detectProjectRoot(start) {
|
|
819
|
+
let dir = path3.resolve(start);
|
|
820
|
+
const root = path3.parse(dir).root;
|
|
821
|
+
while (dir !== root) {
|
|
822
|
+
for (const marker of PROJECT_MARKERS) {
|
|
823
|
+
try {
|
|
824
|
+
fs.accessSync(path3.join(dir, marker));
|
|
825
|
+
return dir;
|
|
826
|
+
} catch {
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
const parent = path3.dirname(dir);
|
|
830
|
+
if (parent === dir) break;
|
|
831
|
+
dir = parent;
|
|
832
|
+
}
|
|
833
|
+
return path3.resolve(start);
|
|
834
|
+
}
|
|
835
|
+
resolve(input) {
|
|
836
|
+
const abs = path3.isAbsolute(input) ? input : path3.resolve(this.cwd, input);
|
|
837
|
+
let real;
|
|
838
|
+
try {
|
|
839
|
+
real = fs.realpathSync(abs);
|
|
840
|
+
} catch {
|
|
841
|
+
real = path3.normalize(abs);
|
|
842
|
+
}
|
|
843
|
+
return real;
|
|
844
|
+
}
|
|
845
|
+
isInsideRoot(absPath) {
|
|
846
|
+
const normalized = path3.normalize(absPath);
|
|
847
|
+
const root = path3.normalize(this.projectRoot);
|
|
848
|
+
if (normalized === root) return true;
|
|
849
|
+
const rel = path3.relative(root, normalized);
|
|
850
|
+
return !rel.startsWith("..") && !path3.isAbsolute(rel);
|
|
851
|
+
}
|
|
852
|
+
ensureInsideRoot(absPath) {
|
|
853
|
+
const resolved = this.resolve(absPath);
|
|
854
|
+
if (!this.isInsideRoot(resolved)) {
|
|
855
|
+
const display = path3.isAbsolute(absPath) ? path3.basename(absPath) : absPath;
|
|
856
|
+
const err = new Error(`Path "${display}" resolves outside the project root`);
|
|
857
|
+
err.fullPath = absPath;
|
|
858
|
+
err.projectRoot = this.projectRoot;
|
|
859
|
+
throw err;
|
|
860
|
+
}
|
|
861
|
+
return resolved;
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
|
|
865
|
+
// src/execution/error-handler.ts
|
|
866
|
+
var CONTEXT_OVERFLOW_RE = /context|too long|tokens/i;
|
|
867
|
+
var NETWORK_ERR_RE = /ECONN|ETIMEDOUT|ETIME|ENOTFOUND|EAI_AGAIN|fetch failed/i;
|
|
868
|
+
function buildRecoveryStrategies(opts) {
|
|
869
|
+
return [
|
|
870
|
+
{
|
|
871
|
+
label: "context_overflow_reduce",
|
|
872
|
+
compactor: opts?.compactor,
|
|
873
|
+
async attempt(err, ctx) {
|
|
874
|
+
if (!(err instanceof ProviderError)) return null;
|
|
875
|
+
if (err.status !== 413 && !CONTEXT_OVERFLOW_RE.test(err.message)) return null;
|
|
876
|
+
if (this.compactor) {
|
|
877
|
+
try {
|
|
878
|
+
const report = await this.compactor.compact(ctx, { aggressive: true });
|
|
879
|
+
if (report.after < report.before) {
|
|
880
|
+
return { action: "retry", reason: "context_compacted" };
|
|
881
|
+
}
|
|
882
|
+
} catch {
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return null;
|
|
886
|
+
}
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
label: "rate_limit_backoff",
|
|
890
|
+
async attempt(err) {
|
|
891
|
+
if (!(err instanceof ProviderError) || err.status !== 429) return null;
|
|
892
|
+
const delayMs = err.body?.retryAfterMs ?? 5e3;
|
|
893
|
+
const delay = Math.max(1e3, Math.min(delayMs, 6e4));
|
|
894
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
895
|
+
return { action: "retry", reason: "rate_limit_backoff" };
|
|
896
|
+
}
|
|
897
|
+
},
|
|
898
|
+
{
|
|
899
|
+
label: "downgrade_model",
|
|
900
|
+
async attempt(err, ctx) {
|
|
901
|
+
if (!(err instanceof ProviderError)) return null;
|
|
902
|
+
if (err.status !== 429 && err.status !== 529 && err.status < 500) return null;
|
|
903
|
+
const registry = opts?.modelsRegistry;
|
|
904
|
+
if (!registry) return null;
|
|
905
|
+
try {
|
|
906
|
+
const providerId = ctx.provider?.id;
|
|
907
|
+
if (!providerId) return null;
|
|
908
|
+
const provider = await registry.getProvider(providerId);
|
|
909
|
+
if (!provider) return null;
|
|
910
|
+
const currentModel = await registry.getModel(providerId, ctx.model);
|
|
911
|
+
if (!currentModel) return null;
|
|
912
|
+
const candidates = provider.models.filter((m) => {
|
|
913
|
+
const modelCost = m.cost?.input ?? Number.POSITIVE_INFINITY;
|
|
914
|
+
const currentCost = currentModel.cost?.input ?? Number.POSITIVE_INFINITY;
|
|
915
|
+
if (modelCost >= currentCost) return false;
|
|
916
|
+
if (currentModel.capabilities.tools && !m.tool_call) return false;
|
|
917
|
+
if (currentModel.capabilities.vision && !m.modalities?.input?.includes("image"))
|
|
918
|
+
return false;
|
|
919
|
+
return true;
|
|
920
|
+
});
|
|
921
|
+
if (candidates.length === 0) return null;
|
|
922
|
+
const fallback = candidates.reduce(
|
|
923
|
+
(prev, curr) => (curr.cost?.input ?? 0) < (prev.cost?.input ?? 0) ? curr : prev
|
|
924
|
+
);
|
|
925
|
+
return {
|
|
926
|
+
action: "retry",
|
|
927
|
+
reason: "model_downgrade",
|
|
928
|
+
model: fallback.id
|
|
929
|
+
};
|
|
930
|
+
} catch {
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
];
|
|
936
|
+
}
|
|
937
|
+
var DEFAULT_RECOVERY_STRATEGIES = buildRecoveryStrategies();
|
|
938
|
+
var DefaultErrorHandler = class {
|
|
939
|
+
strategies;
|
|
940
|
+
constructor(strategies = DEFAULT_RECOVERY_STRATEGIES) {
|
|
941
|
+
this.strategies = strategies;
|
|
942
|
+
}
|
|
943
|
+
classify(err) {
|
|
944
|
+
if (typeof DOMException !== "undefined" && err instanceof DOMException && err.name === "AbortError") {
|
|
945
|
+
return { kind: "abort", retryable: false };
|
|
946
|
+
}
|
|
947
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
948
|
+
return { kind: "abort", retryable: false };
|
|
949
|
+
}
|
|
950
|
+
if (err instanceof ProviderError) {
|
|
951
|
+
if (err.status === 429) return { kind: "rate_limit", retryable: true };
|
|
952
|
+
if (err.status === 529) return { kind: "overloaded", retryable: true };
|
|
953
|
+
if (err.status >= 500) return { kind: "server", retryable: true };
|
|
954
|
+
if (err.status === 413 || CONTEXT_OVERFLOW_RE.test(err.message)) {
|
|
955
|
+
return { kind: "context_overflow", retryable: false };
|
|
956
|
+
}
|
|
957
|
+
if (err.status >= 400) return { kind: "client", retryable: false };
|
|
958
|
+
}
|
|
959
|
+
if (err instanceof Error && NETWORK_ERR_RE.test(err.message)) {
|
|
960
|
+
return { kind: "network", retryable: true };
|
|
961
|
+
}
|
|
962
|
+
return { kind: "unknown", retryable: false };
|
|
963
|
+
}
|
|
964
|
+
async recover(err, ctx) {
|
|
965
|
+
for (const strategy of this.strategies) {
|
|
966
|
+
const result = await strategy.attempt(err, ctx);
|
|
967
|
+
if (result !== null) return result;
|
|
968
|
+
}
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
|
|
973
|
+
// src/execution/retry-policy.ts
|
|
974
|
+
var DefaultRetryPolicy = class _DefaultRetryPolicy {
|
|
975
|
+
static NETWORK_ERR_RE = /ECONN|ETIMEDOUT|ETIME|ENOTFOUND|EAI_AGAIN|fetch failed/i;
|
|
976
|
+
shouldRetry(err, attempt) {
|
|
977
|
+
if (err instanceof ProviderError) {
|
|
978
|
+
if (!err.retryable) return false;
|
|
979
|
+
return attempt < this.maxAttempts(err);
|
|
980
|
+
}
|
|
981
|
+
const msg = err.message ?? "";
|
|
982
|
+
const isNetwork = _DefaultRetryPolicy.NETWORK_ERR_RE.test(msg);
|
|
983
|
+
if (isNetwork) return attempt < 2;
|
|
984
|
+
return false;
|
|
985
|
+
}
|
|
986
|
+
maxAttempts(err) {
|
|
987
|
+
if (err instanceof ProviderError) {
|
|
988
|
+
if (err.status === 429) return 5;
|
|
989
|
+
if (err.status === 529) return 3;
|
|
990
|
+
if (err.status >= 500) return 3;
|
|
991
|
+
return 0;
|
|
992
|
+
}
|
|
993
|
+
return 2;
|
|
994
|
+
}
|
|
995
|
+
delayMs(attempt) {
|
|
996
|
+
const base = 1e3;
|
|
997
|
+
const exp = base * 2 ** attempt;
|
|
998
|
+
const jitter = Math.random() * base;
|
|
999
|
+
return Math.min(3e4, exp + jitter);
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
|
|
1003
|
+
// src/security/secret-scrubber.ts
|
|
1004
|
+
var PATTERNS = [
|
|
1005
|
+
// Anchored at the start where possible so partial matches inside larger
|
|
1006
|
+
// strings don't trigger false positives.
|
|
1007
|
+
{
|
|
1008
|
+
type: "anthropic_key",
|
|
1009
|
+
regex: /(?<![A-Za-z0-9])sk-ant-api\d+-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g
|
|
1010
|
+
},
|
|
1011
|
+
{ type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g },
|
|
1012
|
+
{ type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g },
|
|
1013
|
+
{ type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g },
|
|
1014
|
+
{ type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g },
|
|
1015
|
+
{ type: "gcp_key", regex: /(?<![A-Za-z0-9])AIza[0-9A-Za-z_-]{35}(?![A-Za-z0-9])/g },
|
|
1016
|
+
{ type: "slack_token", regex: /(?<![A-Za-z0-9-])xox[abpos]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g },
|
|
1017
|
+
{
|
|
1018
|
+
type: "stripe_key",
|
|
1019
|
+
regex: /(?<![A-Za-z0-9])sk_(?:live|test)_[A-Za-z0-9]{24,}(?![A-Za-z0-9])/g
|
|
1020
|
+
},
|
|
1021
|
+
{ type: "twilio_sid", regex: /(?<![A-Za-z0-9])AC[a-f0-9]{32}(?![A-Za-z0-9])/g },
|
|
1022
|
+
{
|
|
1023
|
+
type: "jwt",
|
|
1024
|
+
// Anchored: look for literal "eyJ" which is unambiguous for JWT header
|
|
1025
|
+
regex: /(?<![A-Za-z0-9/+=])eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}(?![A-Za-z0-9/+=])/g
|
|
1026
|
+
},
|
|
1027
|
+
{
|
|
1028
|
+
type: "private_key",
|
|
1029
|
+
// Anchored: start must be BEGIN, end must be END with no extra dashes after END
|
|
1030
|
+
regex: /(?:^|\n)-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----[\s\S]*?-----END[^-]*-----(?:\n|$)/g
|
|
1031
|
+
},
|
|
1032
|
+
{ type: "mongodb_uri", regex: /mongodb(?:\+srv)?:\/\/[^\s"'`]+/g },
|
|
1033
|
+
{ type: "postgres_uri", regex: /postgres(?:ql)?:\/\/[^\s"'`]+/g },
|
|
1034
|
+
{ type: "mysql_uri", regex: /mysql:\/\/[^\s"'`]+/g },
|
|
1035
|
+
{ type: "redis_uri", regex: /redis:\/\/[^\s"'`]+/g },
|
|
1036
|
+
{
|
|
1037
|
+
type: "bearer_token",
|
|
1038
|
+
regex: /(?<![A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{20,}=*(?![A-Za-z0-9_.~+/-])/g
|
|
1039
|
+
},
|
|
1040
|
+
{
|
|
1041
|
+
type: "high_entropy_env",
|
|
1042
|
+
// Value-side word boundary + length gate to avoid matching short random strings
|
|
1043
|
+
regex: /\b([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,})['"]?(?!\s*[A-Za-z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))/g
|
|
1044
|
+
}
|
|
1045
|
+
];
|
|
1046
|
+
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
1047
|
+
var DefaultSecretScrubber = class {
|
|
1048
|
+
scrub(text) {
|
|
1049
|
+
if (!text) return text;
|
|
1050
|
+
if (text.length <= SCRUB_CHUNK_BYTES) {
|
|
1051
|
+
return this.scrubOne(text);
|
|
1052
|
+
}
|
|
1053
|
+
const out = [];
|
|
1054
|
+
let i = 0;
|
|
1055
|
+
while (i < text.length) {
|
|
1056
|
+
let end = Math.min(i + SCRUB_CHUNK_BYTES, text.length);
|
|
1057
|
+
if (end < text.length) {
|
|
1058
|
+
const nl = text.lastIndexOf("\n", end);
|
|
1059
|
+
if (nl > i + SCRUB_CHUNK_BYTES / 2) end = nl + 1;
|
|
1060
|
+
}
|
|
1061
|
+
out.push(this.scrubOne(text.slice(i, end)));
|
|
1062
|
+
i = end;
|
|
1063
|
+
}
|
|
1064
|
+
return out.join("");
|
|
1065
|
+
}
|
|
1066
|
+
scrubOne(text) {
|
|
1067
|
+
let out = text;
|
|
1068
|
+
for (const p of PATTERNS) {
|
|
1069
|
+
out = out.replace(p.regex, (_match, group1, group2) => {
|
|
1070
|
+
if (p.type === "high_entropy_env" && group1 && group2) {
|
|
1071
|
+
return `${group1}=[REDACTED:${p.type}]`;
|
|
1072
|
+
}
|
|
1073
|
+
return `[REDACTED:${p.type}]`;
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
return out;
|
|
1077
|
+
}
|
|
1078
|
+
scrubObject(obj) {
|
|
1079
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
1080
|
+
const visit = (v) => {
|
|
1081
|
+
if (typeof v === "string") return this.scrub(v);
|
|
1082
|
+
if (v === null || typeof v !== "object") return v;
|
|
1083
|
+
if (seen.has(v)) return v;
|
|
1084
|
+
seen.add(v);
|
|
1085
|
+
if (Array.isArray(v)) return v.map(visit);
|
|
1086
|
+
const out = {};
|
|
1087
|
+
for (const [k, val] of Object.entries(v)) {
|
|
1088
|
+
out[k] = visit(val);
|
|
1089
|
+
}
|
|
1090
|
+
return out;
|
|
1091
|
+
};
|
|
1092
|
+
return visit(obj);
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
async function atomicWrite(targetPath, content, opts = {}) {
|
|
1096
|
+
const dir = path3.dirname(targetPath);
|
|
1097
|
+
await fs4.mkdir(dir, { recursive: true });
|
|
1098
|
+
const tmp = path3.join(dir, `.${path3.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
|
|
1099
|
+
try {
|
|
1100
|
+
if (typeof content === "string") {
|
|
1101
|
+
await fs4.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
1102
|
+
} else {
|
|
1103
|
+
await fs4.writeFile(tmp, content, { flag: "wx" });
|
|
1104
|
+
}
|
|
1105
|
+
try {
|
|
1106
|
+
const fh = await fs4.open(tmp, "r+");
|
|
1107
|
+
try {
|
|
1108
|
+
await fh.sync();
|
|
1109
|
+
} finally {
|
|
1110
|
+
await fh.close();
|
|
1111
|
+
}
|
|
1112
|
+
} catch {
|
|
1113
|
+
}
|
|
1114
|
+
let mode;
|
|
1115
|
+
try {
|
|
1116
|
+
const stat2 = await fs4.stat(targetPath);
|
|
1117
|
+
mode = stat2.mode & 511;
|
|
1118
|
+
} catch {
|
|
1119
|
+
mode = opts.mode;
|
|
1120
|
+
}
|
|
1121
|
+
if (mode !== void 0) {
|
|
1122
|
+
await fs4.chmod(tmp, mode);
|
|
1123
|
+
}
|
|
1124
|
+
await fs4.rename(tmp, targetPath);
|
|
1125
|
+
} catch (err) {
|
|
1126
|
+
try {
|
|
1127
|
+
await fs4.unlink(tmp);
|
|
1128
|
+
} catch {
|
|
1129
|
+
}
|
|
1130
|
+
throw err;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// src/models/models-registry.ts
|
|
1135
|
+
var DEFAULT_URL = "https://models.dev/api.json";
|
|
1136
|
+
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
1137
|
+
var FAMILY_BY_NPM = {
|
|
1138
|
+
"@ai-sdk/anthropic": "anthropic",
|
|
1139
|
+
"@ai-sdk/google-vertex/anthropic": "anthropic",
|
|
1140
|
+
"@ai-sdk/openai": "openai",
|
|
1141
|
+
"@ai-sdk/openai-compatible": "openai-compatible",
|
|
1142
|
+
"@ai-sdk/groq": "openai-compatible",
|
|
1143
|
+
"@ai-sdk/xai": "openai-compatible",
|
|
1144
|
+
"@ai-sdk/cerebras": "openai-compatible",
|
|
1145
|
+
"@ai-sdk/togetherai": "openai-compatible",
|
|
1146
|
+
"@ai-sdk/perplexity": "openai-compatible",
|
|
1147
|
+
"@ai-sdk/deepinfra": "openai-compatible",
|
|
1148
|
+
"@openrouter/ai-sdk-provider": "openai-compatible",
|
|
1149
|
+
"ai-gateway-provider": "openai-compatible",
|
|
1150
|
+
"@ai-sdk/vercel": "openai-compatible",
|
|
1151
|
+
"@ai-sdk/gateway": "openai-compatible",
|
|
1152
|
+
"@aihubmix/ai-sdk-provider": "openai-compatible",
|
|
1153
|
+
"venice-ai-sdk-provider": "openai-compatible",
|
|
1154
|
+
"@ai-sdk/google": "google"
|
|
1155
|
+
};
|
|
1156
|
+
function classifyFamily(npm) {
|
|
1157
|
+
if (!npm) return "unsupported";
|
|
1158
|
+
return FAMILY_BY_NPM[npm] ?? "unsupported";
|
|
1159
|
+
}
|
|
1160
|
+
var DefaultModelsRegistry = class {
|
|
1161
|
+
payload;
|
|
1162
|
+
fetchedAt;
|
|
1163
|
+
cacheFile;
|
|
1164
|
+
url;
|
|
1165
|
+
ttlMs;
|
|
1166
|
+
fetchImpl;
|
|
1167
|
+
seed;
|
|
1168
|
+
maxStaleAgeMs;
|
|
1169
|
+
constructor(opts) {
|
|
1170
|
+
this.cacheFile = opts.cacheFile;
|
|
1171
|
+
this.url = opts.url ?? DEFAULT_URL;
|
|
1172
|
+
this.ttlMs = (opts.ttlSeconds ?? DEFAULT_TTL_SECONDS) * 1e3;
|
|
1173
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
1174
|
+
this.seed = opts.seed;
|
|
1175
|
+
const maxStaleSeconds = opts.maxStaleAgeSeconds ?? 7 * 24 * 3600;
|
|
1176
|
+
this.maxStaleAgeMs = maxStaleSeconds * 1e3;
|
|
1177
|
+
}
|
|
1178
|
+
async load(opts = {}) {
|
|
1179
|
+
if (this.payload && !opts.force) return this.payload;
|
|
1180
|
+
if (this.seed) {
|
|
1181
|
+
this.payload = this.seed;
|
|
1182
|
+
this.fetchedAt = /* @__PURE__ */ new Date();
|
|
1183
|
+
return this.payload;
|
|
1184
|
+
}
|
|
1185
|
+
if (!opts.force) {
|
|
1186
|
+
const cached = await this.readCache();
|
|
1187
|
+
if (cached && this.isFresh(cached.fetchedAt)) {
|
|
1188
|
+
this.payload = cached.payload;
|
|
1189
|
+
this.fetchedAt = new Date(cached.fetchedAt);
|
|
1190
|
+
return cached.payload;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
try {
|
|
1194
|
+
return await this.refresh();
|
|
1195
|
+
} catch (err) {
|
|
1196
|
+
const cached = await this.readCache();
|
|
1197
|
+
if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
|
|
1198
|
+
this.payload = cached.payload;
|
|
1199
|
+
this.fetchedAt = new Date(cached.fetchedAt);
|
|
1200
|
+
return cached.payload;
|
|
1201
|
+
}
|
|
1202
|
+
throw err;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
async refresh() {
|
|
1206
|
+
const res = await this.fetchImpl(this.url, {
|
|
1207
|
+
method: "GET",
|
|
1208
|
+
headers: { accept: "application/json" }
|
|
1209
|
+
});
|
|
1210
|
+
if (!res.ok) {
|
|
1211
|
+
throw new Error(`ModelsRegistry: HTTP ${res.status} fetching ${this.url}`);
|
|
1212
|
+
}
|
|
1213
|
+
const json = await res.json();
|
|
1214
|
+
this.payload = json;
|
|
1215
|
+
this.fetchedAt = /* @__PURE__ */ new Date();
|
|
1216
|
+
const envelope = {
|
|
1217
|
+
fetchedAt: this.fetchedAt.toISOString(),
|
|
1218
|
+
url: this.url,
|
|
1219
|
+
payload: json
|
|
1220
|
+
};
|
|
1221
|
+
await atomicWrite(this.cacheFile, JSON.stringify(envelope));
|
|
1222
|
+
return json;
|
|
1223
|
+
}
|
|
1224
|
+
async listProviders() {
|
|
1225
|
+
const payload = await this.load();
|
|
1226
|
+
return Object.values(payload).map((p) => this.resolveProvider(p));
|
|
1227
|
+
}
|
|
1228
|
+
async getProvider(id) {
|
|
1229
|
+
const payload = await this.load();
|
|
1230
|
+
const p = payload[id];
|
|
1231
|
+
return p ? this.resolveProvider(p) : void 0;
|
|
1232
|
+
}
|
|
1233
|
+
async getModel(providerId, modelId) {
|
|
1234
|
+
const provider = await this.getProvider(providerId);
|
|
1235
|
+
if (!provider) return void 0;
|
|
1236
|
+
const model = provider.models.find((m) => m.id === modelId);
|
|
1237
|
+
if (!model) return void 0;
|
|
1238
|
+
return {
|
|
1239
|
+
providerId,
|
|
1240
|
+
modelId,
|
|
1241
|
+
capabilities: {
|
|
1242
|
+
tools: model.tool_call ?? false,
|
|
1243
|
+
vision: Boolean(model.modalities?.input?.includes("image")),
|
|
1244
|
+
reasoning: model.reasoning ?? false,
|
|
1245
|
+
maxContext: model.limit?.context ?? 0,
|
|
1246
|
+
maxOutput: model.limit?.output,
|
|
1247
|
+
knowledge: model.knowledge
|
|
1248
|
+
},
|
|
1249
|
+
cost: model.cost
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
async suggestModel(providerId) {
|
|
1253
|
+
const provider = await this.getProvider(providerId);
|
|
1254
|
+
if (!provider || provider.models.length === 0) return void 0;
|
|
1255
|
+
const ranked = [...provider.models].sort((a, b) => {
|
|
1256
|
+
const at = a.release_date ?? a.last_updated ?? "";
|
|
1257
|
+
const bt = b.release_date ?? b.last_updated ?? "";
|
|
1258
|
+
return bt.localeCompare(at);
|
|
1259
|
+
});
|
|
1260
|
+
return ranked[0]?.id;
|
|
1261
|
+
}
|
|
1262
|
+
async ageSeconds() {
|
|
1263
|
+
if (!this.fetchedAt) {
|
|
1264
|
+
const cached = await this.readCache();
|
|
1265
|
+
if (!cached) return Number.POSITIVE_INFINITY;
|
|
1266
|
+
return (Date.now() - new Date(cached.fetchedAt).getTime()) / 1e3;
|
|
1267
|
+
}
|
|
1268
|
+
return (Date.now() - this.fetchedAt.getTime()) / 1e3;
|
|
1269
|
+
}
|
|
1270
|
+
resolveProvider(p) {
|
|
1271
|
+
return {
|
|
1272
|
+
id: p.id,
|
|
1273
|
+
name: p.name,
|
|
1274
|
+
family: classifyFamily(p.npm),
|
|
1275
|
+
apiBase: p.api,
|
|
1276
|
+
envVars: p.env ?? [],
|
|
1277
|
+
doc: p.doc,
|
|
1278
|
+
models: Object.values(p.models ?? {}),
|
|
1279
|
+
npm: p.npm
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
isFresh(fetchedAtIso) {
|
|
1283
|
+
return Date.now() - new Date(fetchedAtIso).getTime() < this.ttlMs;
|
|
1284
|
+
}
|
|
1285
|
+
isWithinMaxStaleAge(fetchedAtIso) {
|
|
1286
|
+
return Date.now() - new Date(fetchedAtIso).getTime() < this.maxStaleAgeMs;
|
|
1287
|
+
}
|
|
1288
|
+
async readCache() {
|
|
1289
|
+
try {
|
|
1290
|
+
const raw = await fs4.readFile(this.cacheFile, "utf8");
|
|
1291
|
+
return JSON.parse(raw);
|
|
1292
|
+
} catch {
|
|
1293
|
+
return void 0;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
/** Used by `wstack models refresh` to expose where the cache lives. */
|
|
1297
|
+
cacheLocation() {
|
|
1298
|
+
return path3.resolve(this.cacheFile);
|
|
1299
|
+
}
|
|
1300
|
+
};
|
|
1301
|
+
|
|
231
1302
|
// src/types/mode.ts
|
|
232
1303
|
var DEFAULT_MODES = [
|
|
233
1304
|
{
|
|
@@ -363,6 +1434,144 @@ When refactoring code:
|
|
|
363
1434
|
}
|
|
364
1435
|
];
|
|
365
1436
|
|
|
1437
|
+
// src/coordination/in-memory-transport.ts
|
|
1438
|
+
var InMemoryBridgeTransport = class {
|
|
1439
|
+
subs = /* @__PURE__ */ new Map();
|
|
1440
|
+
send(msg, to) {
|
|
1441
|
+
if (to === "*") {
|
|
1442
|
+
for (const [id, handlers2] of this.subs) {
|
|
1443
|
+
if (id === msg.from) continue;
|
|
1444
|
+
for (const h of handlers2) {
|
|
1445
|
+
try {
|
|
1446
|
+
h(msg);
|
|
1447
|
+
} catch {
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
return Promise.resolve();
|
|
1452
|
+
}
|
|
1453
|
+
const handlers = this.subs.get(to);
|
|
1454
|
+
if (handlers) {
|
|
1455
|
+
for (const h of handlers) {
|
|
1456
|
+
try {
|
|
1457
|
+
h(msg);
|
|
1458
|
+
} catch {
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
return Promise.resolve();
|
|
1463
|
+
}
|
|
1464
|
+
subscribe(agentId, handler) {
|
|
1465
|
+
if (!this.subs.has(agentId)) this.subs.set(agentId, /* @__PURE__ */ new Set());
|
|
1466
|
+
this.subs.get(agentId).add(handler);
|
|
1467
|
+
return () => this.subs.get(agentId)?.delete(handler);
|
|
1468
|
+
}
|
|
1469
|
+
close(agentId) {
|
|
1470
|
+
this.subs.delete(agentId);
|
|
1471
|
+
return Promise.resolve();
|
|
1472
|
+
}
|
|
1473
|
+
};
|
|
1474
|
+
|
|
1475
|
+
// src/coordination/agent-bridge.ts
|
|
1476
|
+
var InMemoryAgentBridge = class {
|
|
1477
|
+
agentId;
|
|
1478
|
+
coordinatorId;
|
|
1479
|
+
transport;
|
|
1480
|
+
subscriptions = /* @__PURE__ */ new Set();
|
|
1481
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
1482
|
+
stopped = false;
|
|
1483
|
+
timeoutMs;
|
|
1484
|
+
/** Guards request() so concurrent calls on the same id can't silently overwrite. */
|
|
1485
|
+
inflightGuards = /* @__PURE__ */ new Set();
|
|
1486
|
+
constructor(config, transport) {
|
|
1487
|
+
this.agentId = config.agentId;
|
|
1488
|
+
this.coordinatorId = config.coordinatorId;
|
|
1489
|
+
this.transport = transport;
|
|
1490
|
+
this.timeoutMs = config.timeoutMs ?? 3e4;
|
|
1491
|
+
this.transport.subscribe(this.agentId, (msg) => {
|
|
1492
|
+
if (msg.type === "heartbeat") return;
|
|
1493
|
+
const pending = this.pendingRequests.get(msg.id);
|
|
1494
|
+
if (pending) {
|
|
1495
|
+
clearTimeout(pending.timer);
|
|
1496
|
+
this.pendingRequests.delete(msg.id);
|
|
1497
|
+
this.inflightGuards.delete(msg.id);
|
|
1498
|
+
pending.resolve(msg);
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1501
|
+
for (const h of this.subscriptions) {
|
|
1502
|
+
try {
|
|
1503
|
+
h(msg);
|
|
1504
|
+
} catch {
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
async send(msg) {
|
|
1510
|
+
msg.timestamp = Date.now();
|
|
1511
|
+
await this.transport.send(msg, msg.to ?? this.coordinatorId);
|
|
1512
|
+
}
|
|
1513
|
+
async broadcast(msg) {
|
|
1514
|
+
msg.timestamp = Date.now();
|
|
1515
|
+
msg.to = "*";
|
|
1516
|
+
await this.transport.send(msg, "*");
|
|
1517
|
+
}
|
|
1518
|
+
subscribe(handler) {
|
|
1519
|
+
this.subscriptions.add(handler);
|
|
1520
|
+
return () => this.subscriptions.delete(handler);
|
|
1521
|
+
}
|
|
1522
|
+
async request(msg, timeoutMs) {
|
|
1523
|
+
if (this.stopped) throw new Error("Bridge is stopped");
|
|
1524
|
+
const timeout = timeoutMs ?? this.timeoutMs;
|
|
1525
|
+
const correlationId = msg.id;
|
|
1526
|
+
if (this.inflightGuards.has(correlationId)) {
|
|
1527
|
+
throw new Error(
|
|
1528
|
+
`Bridge request id "${correlationId}" collides with an in-flight request \u2014 caller is reusing message ids`
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
this.inflightGuards.add(correlationId);
|
|
1532
|
+
return new Promise((resolve3, reject) => {
|
|
1533
|
+
const timer = setTimeout(() => {
|
|
1534
|
+
this.inflightGuards.delete(correlationId);
|
|
1535
|
+
this.pendingRequests.delete(correlationId);
|
|
1536
|
+
reject(new Error(`Request ${correlationId} timed out after ${timeout}ms`));
|
|
1537
|
+
}, timeout);
|
|
1538
|
+
this.pendingRequests.set(correlationId, {
|
|
1539
|
+
resolve: resolve3,
|
|
1540
|
+
reject,
|
|
1541
|
+
timer
|
|
1542
|
+
});
|
|
1543
|
+
msg.timestamp = Date.now();
|
|
1544
|
+
this.transport.send(msg, msg.to ?? this.coordinatorId).catch((e) => {
|
|
1545
|
+
clearTimeout(timer);
|
|
1546
|
+
this.inflightGuards.delete(correlationId);
|
|
1547
|
+
this.pendingRequests.delete(correlationId);
|
|
1548
|
+
reject(e);
|
|
1549
|
+
});
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
async stop() {
|
|
1553
|
+
this.stopped = true;
|
|
1554
|
+
for (const [, p] of this.pendingRequests) {
|
|
1555
|
+
clearTimeout(p.timer);
|
|
1556
|
+
}
|
|
1557
|
+
this.pendingRequests.clear();
|
|
1558
|
+
this.inflightGuards.clear();
|
|
1559
|
+
this.subscriptions.clear();
|
|
1560
|
+
await this.transport.close(this.agentId);
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
function createMessage(type, from, payload, to) {
|
|
1564
|
+
return {
|
|
1565
|
+
id: randomUUID(),
|
|
1566
|
+
type,
|
|
1567
|
+
from,
|
|
1568
|
+
to,
|
|
1569
|
+
payload,
|
|
1570
|
+
timestamp: Date.now(),
|
|
1571
|
+
priority: "normal"
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
|
|
366
1575
|
// src/types/spec.ts
|
|
367
1576
|
var DEFAULT_SPEC_TEMPLATE = {
|
|
368
1577
|
id: "default",
|
|
@@ -379,14 +1588,26 @@ var DEFAULT_SPEC_TEMPLATE = {
|
|
|
379
1588
|
],
|
|
380
1589
|
defaultRequirements: [
|
|
381
1590
|
{ type: "functional", priority: "high", acceptanceCriteria: [], blockedBy: [], implements: [] },
|
|
382
|
-
{
|
|
1591
|
+
{
|
|
1592
|
+
type: "non-functional",
|
|
1593
|
+
priority: "medium",
|
|
1594
|
+
acceptanceCriteria: [],
|
|
1595
|
+
blockedBy: [],
|
|
1596
|
+
implements: []
|
|
1597
|
+
}
|
|
383
1598
|
]
|
|
384
1599
|
};
|
|
385
1600
|
|
|
386
1601
|
// src/types/task-graph.ts
|
|
387
1602
|
function computeTaskProgress(graph) {
|
|
388
|
-
let completed = 0
|
|
389
|
-
let
|
|
1603
|
+
let completed = 0;
|
|
1604
|
+
let pending = 0;
|
|
1605
|
+
let inProgress = 0;
|
|
1606
|
+
let blocked = 0;
|
|
1607
|
+
let failed = 0;
|
|
1608
|
+
let review = 0;
|
|
1609
|
+
let estimatedHours = 0;
|
|
1610
|
+
let actualHours = 0;
|
|
390
1611
|
for (const n of graph.nodes.values()) {
|
|
391
1612
|
switch (n.status) {
|
|
392
1613
|
case "completed":
|
|
@@ -458,6 +1679,568 @@ function topologicalSort(graph) {
|
|
|
458
1679
|
return result;
|
|
459
1680
|
}
|
|
460
1681
|
|
|
461
|
-
|
|
1682
|
+
// src/utils/tool-output-serializer.ts
|
|
1683
|
+
function createToolOutputSerializer(opts = {}) {
|
|
1684
|
+
const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
|
|
1685
|
+
function serialize(value) {
|
|
1686
|
+
if (typeof value === "string") return value;
|
|
1687
|
+
if (value === null || value === void 0) return "";
|
|
1688
|
+
if (typeof value === "object") {
|
|
1689
|
+
if (Array.isArray(value)) return value.map(serialize).join("\n");
|
|
1690
|
+
if ("text" in value) {
|
|
1691
|
+
const t = value.text;
|
|
1692
|
+
return typeof t === "string" ? t : JSON.stringify(value, null, 2);
|
|
1693
|
+
}
|
|
1694
|
+
try {
|
|
1695
|
+
return JSON.stringify(value, null, 2);
|
|
1696
|
+
} catch {
|
|
1697
|
+
return String(value);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
return String(value);
|
|
1701
|
+
}
|
|
1702
|
+
function enforceCap(text, remainingBudget) {
|
|
1703
|
+
if (remainingBudget <= 0) {
|
|
1704
|
+
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
1705
|
+
}
|
|
1706
|
+
const textBytes = Buffer.byteLength(text, "utf8");
|
|
1707
|
+
if (textBytes <= remainingBudget) {
|
|
1708
|
+
return { text, newBudget: remainingBudget - textBytes };
|
|
1709
|
+
}
|
|
1710
|
+
const marker = `
|
|
1711
|
+
\u2026[truncated ${textBytes - remainingBudget} bytes]\u2026
|
|
1712
|
+
`;
|
|
1713
|
+
const markerBytes = Buffer.byteLength(marker, "utf8");
|
|
1714
|
+
const available = remainingBudget - markerBytes;
|
|
1715
|
+
if (available <= 0) {
|
|
1716
|
+
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
1717
|
+
}
|
|
1718
|
+
const half = Math.floor(available / 2);
|
|
1719
|
+
const first = text.slice(0, half);
|
|
1720
|
+
const second = text.slice(text.length - half);
|
|
1721
|
+
return { text: `${first}${marker}${second}`, newBudget: 0 };
|
|
1722
|
+
}
|
|
1723
|
+
return { serialize, enforceCap, capBytes };
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
// src/execution/tool-executor.ts
|
|
1727
|
+
var ToolExecutor = class {
|
|
1728
|
+
constructor(registry, opts) {
|
|
1729
|
+
this.registry = registry;
|
|
1730
|
+
this.opts = opts;
|
|
1731
|
+
this.iterationTimeoutMs = opts.iterationTimeoutMs ?? 3e5;
|
|
1732
|
+
this.serializer = createToolOutputSerializer({
|
|
1733
|
+
perIterationOutputCapBytes: opts.perIterationOutputCapBytes ?? 1e5
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
registry;
|
|
1737
|
+
opts;
|
|
1738
|
+
serializer;
|
|
1739
|
+
iterationTimeoutMs;
|
|
1740
|
+
/**
|
|
1741
|
+
* Execute a batch of tool uses using the configured strategy.
|
|
1742
|
+
* Returns the execution results and the remaining output budget.
|
|
1743
|
+
*/
|
|
1744
|
+
async executeBatch(toolUses, ctx, strategy) {
|
|
1745
|
+
let budget = this.opts.perIterationOutputCapBytes ?? 1e5;
|
|
1746
|
+
const runOne = async (use) => {
|
|
1747
|
+
const start = Date.now();
|
|
1748
|
+
const tool = this.registry.get(use.name);
|
|
1749
|
+
if (!tool) {
|
|
1750
|
+
const result = this.unknownToolResult(use, () => this.registry.list().map((t) => t.name));
|
|
1751
|
+
budget = this.decrementBudget(result, budget);
|
|
1752
|
+
return { result, tool, durationMs: Date.now() - start };
|
|
1753
|
+
}
|
|
1754
|
+
const decision = await this.opts.permissionPolicy.evaluate(tool, use.input, ctx);
|
|
1755
|
+
if (decision.permission === "deny") {
|
|
1756
|
+
const result = this.deniedResult(use, decision.reason);
|
|
1757
|
+
budget = this.decrementBudget(result, budget);
|
|
1758
|
+
return { result, tool, durationMs: Date.now() - start };
|
|
1759
|
+
}
|
|
1760
|
+
if (decision.permission === "confirm") {
|
|
1761
|
+
if (this.opts.confirmAwaiter) {
|
|
1762
|
+
const choice = await this.opts.confirmAwaiter(tool, use.input, use.id, tool.name);
|
|
1763
|
+
if (choice !== "yes" && choice !== "always") {
|
|
1764
|
+
const result = {
|
|
1765
|
+
type: "tool_result",
|
|
1766
|
+
tool_use_id: use.id,
|
|
1767
|
+
content: `Tool "${tool.name}" denied by user.`,
|
|
1768
|
+
is_error: true
|
|
1769
|
+
};
|
|
1770
|
+
budget = this.decrementBudget(result, budget);
|
|
1771
|
+
return { result, tool, durationMs: Date.now() - start };
|
|
1772
|
+
}
|
|
1773
|
+
} else {
|
|
1774
|
+
const suggestedPattern = this.subjectFor(tool.name, use.input, tool.subjectKey) ?? tool.name;
|
|
1775
|
+
const pending = {
|
|
1776
|
+
type: "tool_confirm_pending",
|
|
1777
|
+
toolUseId: use.id,
|
|
1778
|
+
toolName: tool.name,
|
|
1779
|
+
input: use.input,
|
|
1780
|
+
suggestedPattern
|
|
1781
|
+
};
|
|
1782
|
+
return { result: pending, tool, durationMs: Date.now() - start };
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
const span = this.opts.tracer?.startSpan(`tool.${tool.name}`, {
|
|
1786
|
+
"tool.name": tool.name,
|
|
1787
|
+
"tool.mutating": tool.mutating,
|
|
1788
|
+
"tool.permission": tool.permission
|
|
1789
|
+
});
|
|
1790
|
+
try {
|
|
1791
|
+
const result = await this.executeTool(tool, use, ctx, budget);
|
|
1792
|
+
budget = this.decrementBudget(result, budget);
|
|
1793
|
+
span?.setAttribute("tool.is_error", !!result.is_error);
|
|
1794
|
+
span?.setAttribute(
|
|
1795
|
+
"tool.output_bytes",
|
|
1796
|
+
typeof result.content === "string" ? result.content.length : 0
|
|
1797
|
+
);
|
|
1798
|
+
return { result, tool, durationMs: Date.now() - start };
|
|
1799
|
+
} catch (err) {
|
|
1800
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1801
|
+
const scrubbed = this.opts.secretScrubber.scrub(msg);
|
|
1802
|
+
this.opts.renderer?.writeToolResult(tool.name, scrubbed, true);
|
|
1803
|
+
const result = {
|
|
1804
|
+
type: "tool_result",
|
|
1805
|
+
tool_use_id: use.id,
|
|
1806
|
+
content: `Tool "${tool.name}" threw: ${scrubbed}`,
|
|
1807
|
+
is_error: true
|
|
1808
|
+
};
|
|
1809
|
+
budget = this.decrementBudget(result, budget);
|
|
1810
|
+
if (err instanceof Error) span?.recordError(err);
|
|
1811
|
+
span?.setAttribute("tool.is_error", true);
|
|
1812
|
+
return { result, tool, durationMs: Date.now() - start };
|
|
1813
|
+
} finally {
|
|
1814
|
+
span?.end();
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
const safeRun = async (use) => {
|
|
1818
|
+
try {
|
|
1819
|
+
return await runOne(use);
|
|
1820
|
+
} catch (err) {
|
|
1821
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1822
|
+
const scrubbed = this.opts.secretScrubber.scrub(msg);
|
|
1823
|
+
const result = {
|
|
1824
|
+
type: "tool_result",
|
|
1825
|
+
tool_use_id: use.id,
|
|
1826
|
+
content: `Tool "${use.name}" execution failed: ${scrubbed}`,
|
|
1827
|
+
is_error: true
|
|
1828
|
+
};
|
|
1829
|
+
budget = this.decrementBudget(result, budget);
|
|
1830
|
+
return { result, tool: this.registry.get(use.name), durationMs: 0 };
|
|
1831
|
+
}
|
|
1832
|
+
};
|
|
1833
|
+
if (strategy === "sequential") {
|
|
1834
|
+
const outputs = [];
|
|
1835
|
+
for (const use of toolUses) {
|
|
1836
|
+
if (use) outputs.push(await safeRun(use));
|
|
1837
|
+
}
|
|
1838
|
+
return { outputs, remainingBudget: budget };
|
|
1839
|
+
}
|
|
1840
|
+
if (strategy === "parallel") {
|
|
1841
|
+
const outputs = await Promise.all(toolUses.map((use) => safeRun(use)));
|
|
1842
|
+
return { outputs, remainingBudget: budget };
|
|
1843
|
+
}
|
|
1844
|
+
const nonMutating = [];
|
|
1845
|
+
const mutating = [];
|
|
1846
|
+
for (const use of toolUses) {
|
|
1847
|
+
if (!use) continue;
|
|
1848
|
+
const tool = this.registry.get(use.name);
|
|
1849
|
+
if (tool?.mutating) mutating.push(use);
|
|
1850
|
+
else nonMutating.push(use);
|
|
1851
|
+
}
|
|
1852
|
+
const firstPass = await Promise.all(nonMutating.map((use) => safeRun(use)));
|
|
1853
|
+
const secondPass = [];
|
|
1854
|
+
for (const use of mutating) {
|
|
1855
|
+
secondPass.push(await safeRun(use));
|
|
1856
|
+
}
|
|
1857
|
+
return {
|
|
1858
|
+
outputs: [...firstPass, ...secondPass],
|
|
1859
|
+
remainingBudget: budget
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
/**
|
|
1863
|
+
* Execute a single tool with timeout, permission check, and output capping.
|
|
1864
|
+
* Emits `tool.started` via the injected EventBus (if any) right before
|
|
1865
|
+
* invoking the tool — closes the observability gap between "model decided
|
|
1866
|
+
* to call a tool" and "tool.executed".
|
|
1867
|
+
*/
|
|
1868
|
+
async executeTool(tool, use, ctx, budget) {
|
|
1869
|
+
this.opts.events?.emit("tool.started", {
|
|
1870
|
+
name: tool.name,
|
|
1871
|
+
id: use.id,
|
|
1872
|
+
input: use.input
|
|
1873
|
+
});
|
|
1874
|
+
this.opts.renderer?.writeToolCall(tool.name, use.input);
|
|
1875
|
+
const output = await this.runWithTimeout(tool, use.input, ctx.signal, ctx, use.id);
|
|
1876
|
+
const text = this.serializer.serialize(output);
|
|
1877
|
+
const scrubbed = this.opts.secretScrubber.scrub(text);
|
|
1878
|
+
const { text: capped } = this.serializer.enforceCap(scrubbed, budget);
|
|
1879
|
+
this.opts.renderer?.writeToolResult(tool.name, capped, false);
|
|
1880
|
+
return {
|
|
1881
|
+
type: "tool_result",
|
|
1882
|
+
tool_use_id: use.id,
|
|
1883
|
+
name: tool.name,
|
|
1884
|
+
content: capped,
|
|
1885
|
+
is_error: false
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
async runWithTimeout(tool, input, parentSignal, ctx, toolUseId) {
|
|
1889
|
+
if (parentSignal.aborted) {
|
|
1890
|
+
if (parentSignal.reason instanceof Error) throw parentSignal.reason;
|
|
1891
|
+
throw new Error(typeof parentSignal.reason === "string" ? parentSignal.reason : "aborted");
|
|
1892
|
+
}
|
|
1893
|
+
const timeoutMs = tool.timeoutMs ?? this.iterationTimeoutMs;
|
|
1894
|
+
const ctrl = new AbortController();
|
|
1895
|
+
const timer = setTimeout(() => ctrl.abort(new Error("tool timeout")), timeoutMs);
|
|
1896
|
+
const combined = AbortSignal.any([parentSignal, ctrl.signal]);
|
|
1897
|
+
try {
|
|
1898
|
+
if (typeof tool.executeStream === "function") {
|
|
1899
|
+
return await this.runStreamedTool(tool, input, ctx, combined, toolUseId);
|
|
1900
|
+
}
|
|
1901
|
+
return await tool.execute(input, ctx, { signal: combined });
|
|
1902
|
+
} catch (err) {
|
|
1903
|
+
if (combined.aborted && typeof tool.cleanup === "function") {
|
|
1904
|
+
try {
|
|
1905
|
+
await tool.cleanup(input, ctx);
|
|
1906
|
+
} catch {
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
throw err;
|
|
1910
|
+
} finally {
|
|
1911
|
+
clearTimeout(timer);
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
async runStreamedTool(tool, input, ctx, signal, toolUseId) {
|
|
1915
|
+
let finalOutput;
|
|
1916
|
+
let sawFinal = false;
|
|
1917
|
+
const stream = tool.executeStream(input, ctx, { signal });
|
|
1918
|
+
for await (const ev of stream) {
|
|
1919
|
+
if (ev.type === "final") {
|
|
1920
|
+
finalOutput = ev.output;
|
|
1921
|
+
sawFinal = true;
|
|
1922
|
+
break;
|
|
1923
|
+
}
|
|
1924
|
+
this.opts.events?.emit("tool.progress", {
|
|
1925
|
+
name: tool.name,
|
|
1926
|
+
id: toolUseId ?? "<unknown>",
|
|
1927
|
+
event: ev
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
if (!sawFinal) {
|
|
1931
|
+
throw new Error(`tool "${tool.name}" executeStream completed without a 'final' event`);
|
|
1932
|
+
}
|
|
1933
|
+
return finalOutput;
|
|
1934
|
+
}
|
|
1935
|
+
unknownToolResult(use, listFns) {
|
|
1936
|
+
return {
|
|
1937
|
+
type: "tool_result",
|
|
1938
|
+
tool_use_id: use.id,
|
|
1939
|
+
content: `Tool "${use.name}" is not registered. Available tools: ${listFns().join(", ")}`,
|
|
1940
|
+
is_error: true
|
|
1941
|
+
};
|
|
1942
|
+
}
|
|
1943
|
+
deniedResult(use, reason) {
|
|
1944
|
+
return {
|
|
1945
|
+
type: "tool_result",
|
|
1946
|
+
tool_use_id: use.id,
|
|
1947
|
+
content: `Tool "${use.name}" denied: ${reason ?? "policy"}`,
|
|
1948
|
+
is_error: true
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
decrementBudget(result, budget) {
|
|
1952
|
+
const contentBytes = typeof result.content === "string" ? Buffer.byteLength(result.content, "utf8") : Buffer.byteLength(JSON.stringify(result.content), "utf8");
|
|
1953
|
+
return Math.max(0, budget - contentBytes);
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* Compute the suggestedPattern string for a tool+input pair.
|
|
1957
|
+
* Matches the logic in DefaultPermissionPolicy so the TUI shows the
|
|
1958
|
+
* same subject that the trust file would use.
|
|
1959
|
+
*/
|
|
1960
|
+
subjectFor(toolName, input, subjectKey) {
|
|
1961
|
+
if (!input || typeof input !== "object") return void 0;
|
|
1962
|
+
const obj = input;
|
|
1963
|
+
const globChars = /[*?\[\]]/g;
|
|
1964
|
+
const escapeGlob = (s) => s.replace(globChars, (c) => `\\${c}`);
|
|
1965
|
+
const normalizePath = (s) => escapeGlob(s.replace(/\\/g, "/"));
|
|
1966
|
+
if (subjectKey) {
|
|
1967
|
+
const v = obj[subjectKey];
|
|
1968
|
+
if (typeof v === "string") {
|
|
1969
|
+
return subjectKey === "path" || subjectKey === "file" || subjectKey === "files" ? normalizePath(v) : escapeGlob(v);
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
if (toolName === "bash" && typeof obj.command === "string") {
|
|
1973
|
+
return escapeGlob(obj.command);
|
|
1974
|
+
}
|
|
1975
|
+
if (typeof obj.path === "string") {
|
|
1976
|
+
return normalizePath(obj.path);
|
|
1977
|
+
}
|
|
1978
|
+
if (typeof obj.url === "string") {
|
|
1979
|
+
return escapeGlob(obj.url);
|
|
1980
|
+
}
|
|
1981
|
+
if (typeof obj.name === "string") {
|
|
1982
|
+
return escapeGlob(obj.name);
|
|
1983
|
+
}
|
|
1984
|
+
return void 0;
|
|
1985
|
+
}
|
|
1986
|
+
};
|
|
1987
|
+
|
|
1988
|
+
// src/storage/session-reader.ts
|
|
1989
|
+
var DefaultSessionReader = class {
|
|
1990
|
+
store;
|
|
1991
|
+
constructor(opts) {
|
|
1992
|
+
this.store = opts.store;
|
|
1993
|
+
}
|
|
1994
|
+
async query(q = {}) {
|
|
1995
|
+
const raw = await this.store.list(q.limit ? Math.max(q.limit * 4, 100) : 1e3);
|
|
1996
|
+
const titleNeedle = q.titleContains?.toLowerCase();
|
|
1997
|
+
const filtered = raw.filter((s) => {
|
|
1998
|
+
if (q.since && s.startedAt < q.since) return false;
|
|
1999
|
+
if (q.until && s.startedAt > q.until) return false;
|
|
2000
|
+
if (q.provider && s.provider !== q.provider) return false;
|
|
2001
|
+
if (q.model && s.model !== q.model) return false;
|
|
2002
|
+
if (q.minTokens !== void 0 && s.tokenTotal < q.minTokens) return false;
|
|
2003
|
+
if (titleNeedle && !s.title.toLowerCase().includes(titleNeedle)) return false;
|
|
2004
|
+
return true;
|
|
2005
|
+
});
|
|
2006
|
+
const out = filtered.map((s) => ({
|
|
2007
|
+
id: s.id,
|
|
2008
|
+
title: s.title,
|
|
2009
|
+
startedAt: s.startedAt,
|
|
2010
|
+
provider: s.provider,
|
|
2011
|
+
model: s.model,
|
|
2012
|
+
tokenTotal: s.tokenTotal
|
|
2013
|
+
}));
|
|
2014
|
+
return q.limit ? out.slice(0, q.limit) : out;
|
|
2015
|
+
}
|
|
2016
|
+
async *replay(sessionId) {
|
|
2017
|
+
const data = await this.store.load(sessionId);
|
|
2018
|
+
for (const e of data.events) yield e;
|
|
2019
|
+
}
|
|
2020
|
+
async search(q, sessionId) {
|
|
2021
|
+
const limit = q.limit ?? 100;
|
|
2022
|
+
const matcher = buildMatcher(q);
|
|
2023
|
+
const allowedTypes = q.types ? new Set(q.types) : null;
|
|
2024
|
+
const ids = sessionId ? [sessionId] : (await this.store.list(1e3)).map((s) => s.id);
|
|
2025
|
+
const hits = [];
|
|
2026
|
+
for (const id of ids) {
|
|
2027
|
+
let data;
|
|
2028
|
+
try {
|
|
2029
|
+
data = await this.store.load(id);
|
|
2030
|
+
} catch {
|
|
2031
|
+
continue;
|
|
2032
|
+
}
|
|
2033
|
+
for (let i = 0; i < data.events.length; i++) {
|
|
2034
|
+
const ev = data.events[i];
|
|
2035
|
+
if (allowedTypes && !allowedTypes.has(ev.type)) continue;
|
|
2036
|
+
const text = eventText(ev);
|
|
2037
|
+
if (text === null) continue;
|
|
2038
|
+
const hit = matcher(text);
|
|
2039
|
+
if (!hit) continue;
|
|
2040
|
+
hits.push({
|
|
2041
|
+
sessionId: id,
|
|
2042
|
+
eventIndex: i,
|
|
2043
|
+
ts: ev.ts,
|
|
2044
|
+
type: ev.type,
|
|
2045
|
+
snippet: snippetOf(text, hit.start, hit.end)
|
|
2046
|
+
});
|
|
2047
|
+
if (hits.length >= limit) return hits;
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
return hits;
|
|
2051
|
+
}
|
|
2052
|
+
async export(sessionId, opts) {
|
|
2053
|
+
const data = await this.store.load(sessionId);
|
|
2054
|
+
const includeTools = opts.includeTools ?? true;
|
|
2055
|
+
const includeDiagnostics = opts.includeDiagnostics ?? true;
|
|
2056
|
+
const filtered = data.events.filter((e) => {
|
|
2057
|
+
if (!includeTools && (e.type === "tool_use" || e.type === "tool_result" || e.type === "tool_call_start" || e.type === "tool_call_end")) {
|
|
2058
|
+
return false;
|
|
2059
|
+
}
|
|
2060
|
+
if (!includeDiagnostics && (e.type === "error" || e.type === "compaction" || e.type === "message_truncated")) {
|
|
2061
|
+
return false;
|
|
2062
|
+
}
|
|
2063
|
+
return true;
|
|
2064
|
+
});
|
|
2065
|
+
if (opts.format === "json") {
|
|
2066
|
+
return JSON.stringify({ metadata: data.metadata, events: filtered }, null, 2);
|
|
2067
|
+
}
|
|
2068
|
+
if (opts.format === "text") {
|
|
2069
|
+
return renderPlainText(data.metadata, filtered);
|
|
2070
|
+
}
|
|
2071
|
+
return renderMarkdown(data.metadata, filtered);
|
|
2072
|
+
}
|
|
2073
|
+
async metadata(sessionId) {
|
|
2074
|
+
const data = await this.store.load(sessionId);
|
|
2075
|
+
return data.metadata;
|
|
2076
|
+
}
|
|
2077
|
+
};
|
|
2078
|
+
function buildMatcher(q) {
|
|
2079
|
+
const ci = q.caseInsensitive ?? true;
|
|
2080
|
+
if (q.regex) {
|
|
2081
|
+
const flags = ci ? "i" : "";
|
|
2082
|
+
const re = new RegExp(q.query, flags);
|
|
2083
|
+
return (text) => {
|
|
2084
|
+
const m = re.exec(text);
|
|
2085
|
+
return m ? { start: m.index, end: m.index + m[0].length } : null;
|
|
2086
|
+
};
|
|
2087
|
+
}
|
|
2088
|
+
const needle = ci ? q.query.toLowerCase() : q.query;
|
|
2089
|
+
return (text) => {
|
|
2090
|
+
const hay = ci ? text.toLowerCase() : text;
|
|
2091
|
+
const idx = hay.indexOf(needle);
|
|
2092
|
+
return idx === -1 ? null : { start: idx, end: idx + needle.length };
|
|
2093
|
+
};
|
|
2094
|
+
}
|
|
2095
|
+
function eventText(e) {
|
|
2096
|
+
switch (e.type) {
|
|
2097
|
+
case "user_input":
|
|
2098
|
+
return contentToString(e.content);
|
|
2099
|
+
case "llm_response":
|
|
2100
|
+
return contentToString(e.content);
|
|
2101
|
+
case "tool_use":
|
|
2102
|
+
return `${e.name} ${JSON.stringify(e.input)}`;
|
|
2103
|
+
case "tool_result":
|
|
2104
|
+
return typeof e.content === "string" ? e.content : JSON.stringify(e.content);
|
|
2105
|
+
case "error":
|
|
2106
|
+
return `${e.phase}: ${e.message}`;
|
|
2107
|
+
case "session_start":
|
|
2108
|
+
case "session_resumed":
|
|
2109
|
+
return `${e.model}/${e.provider}`;
|
|
2110
|
+
case "task_created":
|
|
2111
|
+
case "task_completed":
|
|
2112
|
+
return e.title;
|
|
2113
|
+
case "task_failed":
|
|
2114
|
+
return `${e.title}: ${e.error}`;
|
|
2115
|
+
case "skill_activated":
|
|
2116
|
+
case "skill_deactivated":
|
|
2117
|
+
return e.skillName;
|
|
2118
|
+
default:
|
|
2119
|
+
return null;
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
function contentToString(content) {
|
|
2123
|
+
if (typeof content === "string") return content;
|
|
2124
|
+
return content.map((b) => {
|
|
2125
|
+
switch (b.type) {
|
|
2126
|
+
case "text":
|
|
2127
|
+
return b.text;
|
|
2128
|
+
case "tool_use":
|
|
2129
|
+
return `[tool_use:${b.name} ${JSON.stringify(b.input)}]`;
|
|
2130
|
+
case "tool_result":
|
|
2131
|
+
return typeof b.content === "string" ? b.content : JSON.stringify(b.content);
|
|
2132
|
+
default:
|
|
2133
|
+
return "";
|
|
2134
|
+
}
|
|
2135
|
+
}).join("\n");
|
|
2136
|
+
}
|
|
2137
|
+
var SNIPPET_RADIUS = 60;
|
|
2138
|
+
function snippetOf(text, start, end) {
|
|
2139
|
+
const from = Math.max(0, start - SNIPPET_RADIUS);
|
|
2140
|
+
const to = Math.min(text.length, end + SNIPPET_RADIUS);
|
|
2141
|
+
const prefix = from > 0 ? "\u2026" : "";
|
|
2142
|
+
const suffix = to < text.length ? "\u2026" : "";
|
|
2143
|
+
return prefix + text.slice(from, to).replace(/\s+/g, " ").trim() + suffix;
|
|
2144
|
+
}
|
|
2145
|
+
function renderMarkdown(meta, events) {
|
|
2146
|
+
const lines = [];
|
|
2147
|
+
lines.push(`# Session ${meta.id}`);
|
|
2148
|
+
lines.push("");
|
|
2149
|
+
if (meta.model || meta.provider) {
|
|
2150
|
+
lines.push(`- **Model:** ${meta.provider ?? "?"}/${meta.model ?? "?"}`);
|
|
2151
|
+
}
|
|
2152
|
+
lines.push(`- **Started:** ${meta.startedAt}`);
|
|
2153
|
+
if (meta.endedAt) lines.push(`- **Ended:** ${meta.endedAt}`);
|
|
2154
|
+
lines.push("");
|
|
2155
|
+
lines.push("---");
|
|
2156
|
+
lines.push("");
|
|
2157
|
+
for (const e of events) {
|
|
2158
|
+
switch (e.type) {
|
|
2159
|
+
case "user_input": {
|
|
2160
|
+
lines.push(`## User \u2014 ${e.ts}`);
|
|
2161
|
+
lines.push("");
|
|
2162
|
+
lines.push(contentToString(e.content));
|
|
2163
|
+
lines.push("");
|
|
2164
|
+
break;
|
|
2165
|
+
}
|
|
2166
|
+
case "llm_response": {
|
|
2167
|
+
lines.push(`## Assistant \u2014 ${e.ts}`);
|
|
2168
|
+
lines.push("");
|
|
2169
|
+
lines.push(contentToString(e.content));
|
|
2170
|
+
if (e.stopReason && e.stopReason !== "end_turn") {
|
|
2171
|
+
lines.push("");
|
|
2172
|
+
lines.push(`*stop: ${e.stopReason}*`);
|
|
2173
|
+
}
|
|
2174
|
+
lines.push("");
|
|
2175
|
+
break;
|
|
2176
|
+
}
|
|
2177
|
+
case "tool_use": {
|
|
2178
|
+
lines.push(`### Tool call: \`${e.name}\``);
|
|
2179
|
+
lines.push("");
|
|
2180
|
+
lines.push("```json");
|
|
2181
|
+
lines.push(JSON.stringify(e.input, null, 2));
|
|
2182
|
+
lines.push("```");
|
|
2183
|
+
lines.push("");
|
|
2184
|
+
break;
|
|
2185
|
+
}
|
|
2186
|
+
case "tool_result": {
|
|
2187
|
+
const body = typeof e.content === "string" ? e.content : JSON.stringify(e.content, null, 2);
|
|
2188
|
+
lines.push(`### Tool result${e.isError ? " (error)" : ""}`);
|
|
2189
|
+
lines.push("");
|
|
2190
|
+
lines.push("```");
|
|
2191
|
+
lines.push(body);
|
|
2192
|
+
lines.push("```");
|
|
2193
|
+
lines.push("");
|
|
2194
|
+
break;
|
|
2195
|
+
}
|
|
2196
|
+
case "error": {
|
|
2197
|
+
lines.push(`> **Error** (${e.phase}): ${e.message}`);
|
|
2198
|
+
lines.push("");
|
|
2199
|
+
break;
|
|
2200
|
+
}
|
|
2201
|
+
case "compaction": {
|
|
2202
|
+
lines.push(`> **Compaction**: ${e.before} \u2192 ${e.after} tokens`);
|
|
2203
|
+
lines.push("");
|
|
2204
|
+
break;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
return lines.join("\n");
|
|
2209
|
+
}
|
|
2210
|
+
function renderPlainText(meta, events) {
|
|
2211
|
+
const lines = [];
|
|
2212
|
+
lines.push(
|
|
2213
|
+
`Session ${meta.id} \u2014 ${meta.provider ?? "?"}/${meta.model ?? "?"} \u2014 started ${meta.startedAt}`
|
|
2214
|
+
);
|
|
2215
|
+
lines.push("".padEnd(72, "-"));
|
|
2216
|
+
for (const e of events) {
|
|
2217
|
+
switch (e.type) {
|
|
2218
|
+
case "user_input":
|
|
2219
|
+
lines.push(`[${e.ts}] USER`);
|
|
2220
|
+
lines.push(contentToString(e.content));
|
|
2221
|
+
lines.push("");
|
|
2222
|
+
break;
|
|
2223
|
+
case "llm_response":
|
|
2224
|
+
lines.push(`[${e.ts}] ASSISTANT`);
|
|
2225
|
+
lines.push(contentToString(e.content));
|
|
2226
|
+
lines.push("");
|
|
2227
|
+
break;
|
|
2228
|
+
case "tool_use":
|
|
2229
|
+
lines.push(`[${e.ts}] TOOL_USE ${e.name} ${JSON.stringify(e.input)}`);
|
|
2230
|
+
break;
|
|
2231
|
+
case "tool_result":
|
|
2232
|
+
lines.push(
|
|
2233
|
+
`[${e.ts}] TOOL_RESULT${e.isError ? " (error)" : ""} ${typeof e.content === "string" ? e.content : JSON.stringify(e.content)}`
|
|
2234
|
+
);
|
|
2235
|
+
break;
|
|
2236
|
+
case "error":
|
|
2237
|
+
lines.push(`[${e.ts}] ERROR (${e.phase}): ${e.message}`);
|
|
2238
|
+
break;
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
return lines.join("\n");
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
export { AgentError, ConfigError, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SPEC_TEMPLATE, DefaultErrorHandler, DefaultLogger, DefaultModelsRegistry, DefaultPathResolver, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultTokenCounter, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, PluginError, ProviderError, SessionError, ToolError, ToolExecutor, WrongStackError, asBlocks, asText, buildRecoveryStrategies, classifyFamily, computeTaskProgress, createMessage, decryptConfigSecrets, encryptConfigSecrets, findCriticalPath, isAgentError, isConfigError, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, migratePlaintextSecrets, rewriteConfigEncrypted, toWrongStackError, topologicalSort };
|
|
462
2245
|
//# sourceMappingURL=index.js.map
|
|
463
2246
|
//# sourceMappingURL=index.js.map
|