@wrongstack/core 0.148.0 → 0.236.0
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-r9y6gdn4.d.ts → agent-bridge-Cimv7bK7.d.ts} +1 -1
- package/dist/{agent-subagent-runner-1GeQE_L0.d.ts → agent-subagent-runner-C658wj_c.d.ts} +9 -8
- package/dist/{brain-Cp_3GIS2.d.ts → brain-sCZ3lCjq.d.ts} +28 -2
- package/dist/{compactor-BueGt7LG.d.ts → compactor-BRfg3QPd.d.ts} +1 -1
- package/dist/{config-BaVThgnT.d.ts → config-Koq6f3fs.d.ts} +2 -2
- package/dist/{context-C7G_MtLV.d.ts → context-CLz3z_E8.d.ts} +126 -2
- package/dist/coordination/index.d.ts +70 -13
- package/dist/coordination/index.js +2126 -151
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +27 -27
- package/dist/defaults/index.js +1328 -354
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +45 -16
- package/dist/execution/index.js +367 -59
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +86 -0
- package/dist/execution/prompt-enhancer.js +125 -0
- package/dist/execution/prompt-enhancer.js.map +1 -0
- package/dist/extension/index.d.ts +6 -6
- package/dist/extension/index.js +3 -1
- package/dist/extension/index.js.map +1 -1
- package/dist/{goal-preamble-CYJLg0wk.d.ts → goal-preamble-CnbzyVvl.d.ts} +19 -10
- package/dist/{index-BZdezm3g.d.ts → index-BlMqh5GO.d.ts} +8 -8
- package/dist/{index-CPweVoFM.d.ts → index-C2eSNPsB.d.ts} +7 -5
- package/dist/index.d.ts +439 -129
- package/dist/index.js +5206 -905
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +7 -7
- package/dist/infrastructure/index.js +72 -15
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js +7 -1
- package/dist/kernel/index.js.map +1 -1
- package/dist/{llm-selector-CP72f1lC.d.ts → llm-selector-D22R4AFz.d.ts} +2 -2
- package/dist/logger-DmmQhf4P.d.ts +65 -0
- package/dist/{mcp-servers-Bl5LTvQg.d.ts → mcp-servers-DFbirBv6.d.ts} +11 -4
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js +89 -9
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-D90K9UnM.d.ts → models-registry-CnJRjTXc.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-QWEzJDlm.d.ts → multi-agent-coordinator-60weDZoA.d.ts} +8 -8
- package/dist/{null-fleet-bus-BUyfqh23.d.ts → null-fleet-bus-1068dEnr.d.ts} +7 -7
- package/dist/observability/index.d.ts +2 -2
- package/dist/package-outdated-watcher-pzJ5w7y8.d.ts +560 -0
- package/dist/{parallel-eternal-engine-C75QuhAI.d.ts → parallel-eternal-engine-DtG1fjc9.d.ts} +13 -9
- package/dist/{path-resolver-DRjQBkoO.d.ts → path-resolver-CA1ULU0J.d.ts} +3 -3
- package/dist/{permission-B7nKnEvQ.d.ts → permission-DbWPbuoA.d.ts} +1 -1
- package/dist/{permission-policy-8-6zBmfA.d.ts → permission-policy-AOk0LVsV.d.ts} +2 -2
- package/dist/pipeline-DsmlwTXu.d.ts +493 -0
- package/dist/{plan-templates-CkKNPU3I.d.ts → plan-templates-DPABrDvy.d.ts} +19 -8
- package/dist/{provider-runner-BNpuIyOL.d.ts → provider-runner-D0HgUqwV.d.ts} +3 -3
- package/dist/{retry-policy-rutAfVeR.d.ts → retry-policy-BVnkbMET.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +358 -85
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-vault-DoISxaKO.d.ts → secret-vault-BJDY28ev.d.ts} +7 -1
- package/dist/{secret-vault-BTcC_T5v.d.ts → secret-vault-CeVNiy_f.d.ts} +4 -3
- package/dist/security/index.d.ts +6 -5
- package/dist/security/index.js +214 -35
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-4vDFZKt3.d.ts → selector-Cb4_9-hf.d.ts} +1 -1
- package/dist/{session-event-bridge-DWlvglC2.d.ts → session-event-bridge-BhtkkFFy.d.ts} +4 -2
- package/dist/{session-reader-BAtCxdaw.d.ts → session-reader-CCOssnBS.d.ts} +1 -1
- package/dist/skills/index.js +171 -21
- package/dist/skills/index.js.map +1 -1
- package/dist/storage/index.d.ts +151 -13
- package/dist/storage/index.js +1117 -256
- package/dist/storage/index.js.map +1 -1
- package/dist/types/index.d.ts +68 -21
- package/dist/types/index.js +616 -74
- package/dist/types/index.js.map +1 -1
- package/dist/utils/expect-defined.js +3 -1
- package/dist/utils/expect-defined.js.map +1 -1
- package/dist/utils/index.d.ts +80 -4
- package/dist/utils/index.js +100 -15
- package/dist/utils/index.js.map +1 -1
- package/dist/{wstack-paths-DD50Omgn.d.ts → wstack-paths-CJjEwPXn.d.ts} +14 -1
- package/package.json +7 -3
- package/skills/chimera/SKILL.md +105 -0
- package/skills/research-web/SKILL.md +342 -0
- package/dist/logger-B9J5puGM.d.ts +0 -32
- package/dist/pipeline-BG7UgbDc.d.ts +0 -239
package/dist/types/index.js
CHANGED
|
@@ -77,7 +77,13 @@ var ERROR_CODES = {
|
|
|
77
77
|
FS_MKDIR_FAILED: "FS_MKDIR_FAILED",
|
|
78
78
|
FS_DELETE_FAILED: "FS_DELETE_FAILED",
|
|
79
79
|
FS_ATOMIC_WRITE_FAILED: "FS_ATOMIC_WRITE_FAILED",
|
|
80
|
+
// SDD (Spec-Driven Development)
|
|
81
|
+
SDD_VALIDATION_FAILED: "SDD_VALIDATION_FAILED",
|
|
82
|
+
SDD_PARSE_FAILED: "SDD_PARSE_FAILED",
|
|
83
|
+
SDD_INVALID_STATE: "SDD_INVALID_STATE",
|
|
84
|
+
SDD_NOT_READY: "SDD_NOT_READY",
|
|
80
85
|
// General
|
|
86
|
+
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
81
87
|
UNKNOWN: "UNKNOWN"
|
|
82
88
|
};
|
|
83
89
|
var WrongStackError = class extends Error {
|
|
@@ -192,6 +198,20 @@ var SessionError = class extends WrongStackError {
|
|
|
192
198
|
this.sessionId = opts.sessionId;
|
|
193
199
|
}
|
|
194
200
|
};
|
|
201
|
+
var SddError = class extends WrongStackError {
|
|
202
|
+
constructor(opts) {
|
|
203
|
+
super({
|
|
204
|
+
message: opts.message,
|
|
205
|
+
code: opts.code,
|
|
206
|
+
subsystem: "sdd",
|
|
207
|
+
severity: opts.code === ERROR_CODES.SDD_PARSE_FAILED ? "warning" : "error",
|
|
208
|
+
recoverable: opts.code === ERROR_CODES.SDD_NOT_READY,
|
|
209
|
+
context: opts.context,
|
|
210
|
+
cause: opts.cause
|
|
211
|
+
});
|
|
212
|
+
this.name = "SddError";
|
|
213
|
+
}
|
|
214
|
+
};
|
|
195
215
|
var FsError = class extends WrongStackError {
|
|
196
216
|
path;
|
|
197
217
|
constructor(opts) {
|
|
@@ -229,6 +249,9 @@ function isAgentError(err) {
|
|
|
229
249
|
function isFsError(err) {
|
|
230
250
|
return err instanceof FsError;
|
|
231
251
|
}
|
|
252
|
+
function isSddError(err) {
|
|
253
|
+
return err instanceof SddError;
|
|
254
|
+
}
|
|
232
255
|
|
|
233
256
|
// src/types/provider.ts
|
|
234
257
|
var ProviderError = class extends WrongStackError {
|
|
@@ -419,17 +442,96 @@ async function renameWithRetry(from, to) {
|
|
|
419
442
|
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
420
443
|
throw err;
|
|
421
444
|
}
|
|
422
|
-
await new Promise((
|
|
445
|
+
await new Promise((resolve4) => setTimeout(resolve4, delays[i]));
|
|
423
446
|
}
|
|
424
447
|
}
|
|
425
448
|
throw lastErr;
|
|
426
449
|
}
|
|
427
450
|
|
|
451
|
+
// src/utils/deep-merge.ts
|
|
452
|
+
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
453
|
+
"__proto__",
|
|
454
|
+
"constructor",
|
|
455
|
+
"prototype",
|
|
456
|
+
"__defineGetter__",
|
|
457
|
+
"__defineSetter__",
|
|
458
|
+
"__lookupGetter__",
|
|
459
|
+
"__lookupSetter__"
|
|
460
|
+
]);
|
|
461
|
+
function isPrimitiveArray(a) {
|
|
462
|
+
return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
|
|
463
|
+
}
|
|
464
|
+
function deepMerge(base, patch, options = {}) {
|
|
465
|
+
const {
|
|
466
|
+
conflictResolution = "prefer-patch",
|
|
467
|
+
arrayMode = "replace",
|
|
468
|
+
protectProto = true,
|
|
469
|
+
onNonPrimitiveArrayReplace
|
|
470
|
+
} = options;
|
|
471
|
+
if (typeof base !== "object" || base === null) {
|
|
472
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
473
|
+
}
|
|
474
|
+
if (typeof patch !== "object" || patch === null) {
|
|
475
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
476
|
+
}
|
|
477
|
+
if (Array.isArray(base) && Array.isArray(patch)) {
|
|
478
|
+
if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
|
|
479
|
+
return [.../* @__PURE__ */ new Set([...base, ...patch])];
|
|
480
|
+
}
|
|
481
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
482
|
+
}
|
|
483
|
+
if (Array.isArray(base) || Array.isArray(patch)) {
|
|
484
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
485
|
+
}
|
|
486
|
+
const baseObj = base;
|
|
487
|
+
const patchObj = patch;
|
|
488
|
+
const out = { ...baseObj };
|
|
489
|
+
for (const [k, v] of Object.entries(patchObj)) {
|
|
490
|
+
if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
|
|
491
|
+
const existing = out[k];
|
|
492
|
+
if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
493
|
+
out[k] = deepMerge(existing, v, options);
|
|
494
|
+
} else if (Array.isArray(v) && Array.isArray(existing)) {
|
|
495
|
+
if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
|
|
496
|
+
onNonPrimitiveArrayReplace(k, existing.length, v.length);
|
|
497
|
+
}
|
|
498
|
+
out[k] = deepMerge(existing, v, options);
|
|
499
|
+
} else if (v !== void 0) {
|
|
500
|
+
if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
|
|
501
|
+
const existingLen = Array.isArray(existing) ? existing.length : 0;
|
|
502
|
+
onNonPrimitiveArrayReplace(k, existingLen, v.length);
|
|
503
|
+
}
|
|
504
|
+
out[k] = v;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return out;
|
|
508
|
+
}
|
|
509
|
+
|
|
428
510
|
// src/security/secret-vault.ts
|
|
429
511
|
var KEY_BYTES = 32;
|
|
430
512
|
var IV_BYTES = 12;
|
|
431
513
|
var TAG_BYTES = 16;
|
|
432
514
|
var ALGO = "aes-256-gcm";
|
|
515
|
+
var KEY_FILE_MODE = 384;
|
|
516
|
+
function checkKeyFilePermissions(keyFile) {
|
|
517
|
+
if (process.platform === "win32") return;
|
|
518
|
+
try {
|
|
519
|
+
const stat2 = fs2.statSync(keyFile);
|
|
520
|
+
const actualMode = stat2.mode & 511;
|
|
521
|
+
if (actualMode !== KEY_FILE_MODE) {
|
|
522
|
+
console.warn(JSON.stringify({
|
|
523
|
+
level: "warn",
|
|
524
|
+
event: "vault.key_file_wrong_permissions",
|
|
525
|
+
message: `Key file ${keyFile} has mode ${actualMode.toString(8)} \u2014 expected ${KEY_FILE_MODE.toString(8)}. Run: chmod ${KEY_FILE_MODE.toString(8)} ${keyFile}`,
|
|
526
|
+
keyFile,
|
|
527
|
+
expectedMode: KEY_FILE_MODE,
|
|
528
|
+
actualMode,
|
|
529
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
530
|
+
}));
|
|
531
|
+
}
|
|
532
|
+
} catch {
|
|
533
|
+
}
|
|
534
|
+
}
|
|
433
535
|
var DefaultSecretVault = class {
|
|
434
536
|
keyFile;
|
|
435
537
|
key;
|
|
@@ -453,14 +555,26 @@ var DefaultSecretVault = class {
|
|
|
453
555
|
const rest = value.slice(ENCRYPTED_PREFIX.length);
|
|
454
556
|
const parts = rest.split(":");
|
|
455
557
|
if (parts.length !== 3) {
|
|
456
|
-
throw new
|
|
558
|
+
throw new ConfigError({
|
|
559
|
+
message: "SecretVault: malformed encrypted value",
|
|
560
|
+
code: ERROR_CODES.CONFIG_PARSE_FAILED,
|
|
561
|
+
context: { field: "encrypted_value" }
|
|
562
|
+
});
|
|
457
563
|
}
|
|
458
564
|
const [ivB64, tagB64, ctB64] = parts;
|
|
459
565
|
const iv = Buffer.from(ivB64, "base64");
|
|
460
566
|
const tag = Buffer.from(tagB64, "base64");
|
|
461
567
|
const ct = Buffer.from(ctB64, "base64");
|
|
462
|
-
if (iv.length !== IV_BYTES) throw new
|
|
463
|
-
|
|
568
|
+
if (iv.length !== IV_BYTES) throw new ConfigError({
|
|
569
|
+
message: "SecretVault: bad IV length",
|
|
570
|
+
code: ERROR_CODES.CONFIG_PARSE_FAILED,
|
|
571
|
+
context: { expected: IV_BYTES, actual: iv.length }
|
|
572
|
+
});
|
|
573
|
+
if (tag.length !== TAG_BYTES) throw new ConfigError({
|
|
574
|
+
message: "SecretVault: bad tag length",
|
|
575
|
+
code: ERROR_CODES.CONFIG_PARSE_FAILED,
|
|
576
|
+
context: { expected: TAG_BYTES, actual: tag.length }
|
|
577
|
+
});
|
|
464
578
|
const key = this.loadOrCreateKey();
|
|
465
579
|
const decipher = createDecipheriv(ALGO, key, iv);
|
|
466
580
|
decipher.setAuthTag(tag);
|
|
@@ -472,11 +586,14 @@ var DefaultSecretVault = class {
|
|
|
472
586
|
try {
|
|
473
587
|
const buf = fs2.readFileSync(this.keyFile);
|
|
474
588
|
if (buf.length !== KEY_BYTES) {
|
|
475
|
-
throw new
|
|
476
|
-
`SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES}). Remove it manually to generate a new key
|
|
477
|
-
|
|
589
|
+
throw new ConfigError({
|
|
590
|
+
message: `SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES}). Remove it manually to generate a new key.`,
|
|
591
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
592
|
+
context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: buf.length }
|
|
593
|
+
});
|
|
478
594
|
}
|
|
479
595
|
this.key = buf;
|
|
596
|
+
checkKeyFilePermissions(this.keyFile);
|
|
480
597
|
return this.key;
|
|
481
598
|
} catch (err) {
|
|
482
599
|
if (err.code !== "ENOENT") throw err;
|
|
@@ -489,11 +606,14 @@ var DefaultSecretVault = class {
|
|
|
489
606
|
if (err.code !== "EEXIST") throw err;
|
|
490
607
|
const buf = fs2.readFileSync(this.keyFile);
|
|
491
608
|
if (buf.length !== KEY_BYTES) {
|
|
492
|
-
throw new
|
|
493
|
-
`SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES}). Remove it manually to generate a new key
|
|
494
|
-
|
|
609
|
+
throw new ConfigError({
|
|
610
|
+
message: `SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES}). Remove it manually to generate a new key.`,
|
|
611
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
612
|
+
context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: buf.length }
|
|
613
|
+
});
|
|
495
614
|
}
|
|
496
615
|
this.key = buf;
|
|
616
|
+
checkKeyFilePermissions(this.keyFile);
|
|
497
617
|
return this.key;
|
|
498
618
|
}
|
|
499
619
|
this.key = key;
|
|
@@ -554,7 +674,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
|
554
674
|
await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
555
675
|
await restrictFilePermissions(configPath);
|
|
556
676
|
}
|
|
557
|
-
async function migratePlaintextSecrets(configPath, vault) {
|
|
677
|
+
async function migratePlaintextSecrets(configPath, vault, logger) {
|
|
558
678
|
let raw;
|
|
559
679
|
try {
|
|
560
680
|
raw = await fs.readFile(configPath, "utf8");
|
|
@@ -571,11 +691,14 @@ async function migratePlaintextSecrets(configPath, vault) {
|
|
|
571
691
|
const migrated = walkCount(parsed, vault, counter);
|
|
572
692
|
if (counter.n === 0) return { migrated: 0, file: configPath };
|
|
573
693
|
await atomicWrite(configPath, JSON.stringify(migrated, null, 2), { mode: 384 });
|
|
574
|
-
await restrictFilePermissions(
|
|
694
|
+
await restrictFilePermissions(
|
|
695
|
+
configPath,
|
|
696
|
+
logger ? { warn: (msg) => logger.warn(msg) } : void 0
|
|
697
|
+
);
|
|
575
698
|
return { migrated: counter.n, file: configPath };
|
|
576
699
|
}
|
|
577
700
|
async function restrictFilePermissions(filePath, opts) {
|
|
578
|
-
const warn = ((msg) => console.warn(msg));
|
|
701
|
+
const warn = opts?.warn ?? ((msg) => console.warn(msg));
|
|
579
702
|
if (process.platform === "win32") {
|
|
580
703
|
try {
|
|
581
704
|
const { execFile } = await import('child_process');
|
|
@@ -627,28 +750,6 @@ function walkCount(node, vault, counter) {
|
|
|
627
750
|
}
|
|
628
751
|
return out;
|
|
629
752
|
}
|
|
630
|
-
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
631
|
-
"__proto__",
|
|
632
|
-
"constructor",
|
|
633
|
-
"prototype",
|
|
634
|
-
"__defineGetter__",
|
|
635
|
-
"__defineSetter__",
|
|
636
|
-
"__lookupGetter__",
|
|
637
|
-
"__lookupSetter__"
|
|
638
|
-
]);
|
|
639
|
-
function deepMerge(a, b) {
|
|
640
|
-
const out = { ...a };
|
|
641
|
-
for (const [k, v] of Object.entries(b)) {
|
|
642
|
-
if (FORBIDDEN_PROTO_KEYS.has(k)) continue;
|
|
643
|
-
const existing = out[k];
|
|
644
|
-
if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
645
|
-
out[k] = deepMerge(existing, v);
|
|
646
|
-
} else {
|
|
647
|
-
out[k] = v;
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
return out;
|
|
651
|
-
}
|
|
652
753
|
|
|
653
754
|
// src/utils/term.ts
|
|
654
755
|
var hasStdout = () => typeof process !== "undefined" && !!process.stdout;
|
|
@@ -714,18 +815,24 @@ var COLORS = {
|
|
|
714
815
|
trace: color.dim
|
|
715
816
|
};
|
|
716
817
|
var LOG_LEVELS = /* @__PURE__ */ new Set(["error", "warn", "info", "debug", "trace"]);
|
|
818
|
+
var LOG_FORMATS = /* @__PURE__ */ new Set(["pretty", "json"]);
|
|
717
819
|
var DefaultLogger = class _DefaultLogger {
|
|
820
|
+
/** How many file writes between rotation size checks (statSync is not free). */
|
|
821
|
+
static ROTATE_CHECK_EVERY = 100;
|
|
718
822
|
level;
|
|
719
823
|
file;
|
|
720
824
|
bindings;
|
|
721
|
-
|
|
825
|
+
format;
|
|
722
826
|
stderr;
|
|
827
|
+
maxFileBytes;
|
|
828
|
+
writesSinceRotateCheck = 0;
|
|
723
829
|
constructor(opts = {}) {
|
|
724
830
|
this.level = opts.level ?? parseLogLevel(process.env.WRONGSTACK_LOG_LEVEL);
|
|
725
831
|
this.file = opts.file;
|
|
726
832
|
this.bindings = opts.bindings ?? {};
|
|
727
|
-
this.
|
|
833
|
+
this.format = opts.format ?? parseLogFormat(process.env.WRONGSTACK_LOG_FORMAT);
|
|
728
834
|
this.stderr = opts.stderr !== false;
|
|
835
|
+
this.maxFileBytes = opts.maxFileBytes ?? 10 * 1024 * 1024;
|
|
729
836
|
if (this.file) {
|
|
730
837
|
try {
|
|
731
838
|
fs2.mkdirSync(path4.dirname(this.file), { recursive: true });
|
|
@@ -752,11 +859,30 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
752
859
|
return new _DefaultLogger({
|
|
753
860
|
level: this.level,
|
|
754
861
|
file: this.file,
|
|
755
|
-
|
|
862
|
+
format: this.format,
|
|
756
863
|
stderr: this.stderr,
|
|
864
|
+
maxFileBytes: this.maxFileBytes,
|
|
757
865
|
bindings: { ...this.bindings, ...bindings }
|
|
758
866
|
});
|
|
759
867
|
}
|
|
868
|
+
/**
|
|
869
|
+
* Size-based rotation: when the file outgrows `maxFileBytes`, rename it to
|
|
870
|
+
* `<file>.1` (dropping the previous `.1`) so the live file restarts empty.
|
|
871
|
+
* Checked on the first write and every ROTATE_CHECK_EVERY writes after.
|
|
872
|
+
* Best-effort: a rename can fail on Windows while another process holds
|
|
873
|
+
* the file — the next check retries. Multiple processes appending to the
|
|
874
|
+
* same log all run this check; whoever crosses the threshold first wins.
|
|
875
|
+
*/
|
|
876
|
+
maybeRotate(file) {
|
|
877
|
+
if (this.writesSinceRotateCheck++ % _DefaultLogger.ROTATE_CHECK_EVERY !== 0) return;
|
|
878
|
+
try {
|
|
879
|
+
const st = fs2.statSync(file);
|
|
880
|
+
if (st.size < this.maxFileBytes) return;
|
|
881
|
+
fs2.rmSync(`${file}.1`, { force: true });
|
|
882
|
+
fs2.renameSync(file, `${file}.1`);
|
|
883
|
+
} catch {
|
|
884
|
+
}
|
|
885
|
+
}
|
|
760
886
|
log(level, msg, ctx) {
|
|
761
887
|
const r = LEVEL_RANK[level];
|
|
762
888
|
const allowed = LEVEL_RANK[this.level];
|
|
@@ -768,13 +894,17 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
768
894
|
}
|
|
769
895
|
if (this.file) {
|
|
770
896
|
try {
|
|
897
|
+
this.maybeRotate(this.file);
|
|
771
898
|
fs2.appendFileSync(this.file, `${JSON.stringify(entry)}
|
|
772
899
|
`);
|
|
773
900
|
} catch {
|
|
774
901
|
}
|
|
775
902
|
}
|
|
776
903
|
if (!this.stderr) return;
|
|
777
|
-
if (
|
|
904
|
+
if (this.format === "json") {
|
|
905
|
+
writeErr(`${JSON.stringify(entry)}
|
|
906
|
+
`);
|
|
907
|
+
} else {
|
|
778
908
|
const head = `${color.dim(ts)} ${COLORS[level](level.toUpperCase().padEnd(5))} ${msg}`;
|
|
779
909
|
if (ctx !== void 0) {
|
|
780
910
|
writeErr(`${head} ${formatCtx(ctx)}
|
|
@@ -789,6 +919,9 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
789
919
|
function parseLogLevel(raw) {
|
|
790
920
|
return raw && LOG_LEVELS.has(raw) ? raw : "info";
|
|
791
921
|
}
|
|
922
|
+
function parseLogFormat(raw) {
|
|
923
|
+
return raw && LOG_FORMATS.has(raw) ? raw : "pretty";
|
|
924
|
+
}
|
|
792
925
|
function formatCtx(ctx) {
|
|
793
926
|
if (ctx instanceof Error) return color.dim(ctx.message);
|
|
794
927
|
if (typeof ctx === "string") return color.dim(ctx);
|
|
@@ -798,6 +931,22 @@ function formatCtx(ctx) {
|
|
|
798
931
|
return color.dim(String(ctx));
|
|
799
932
|
}
|
|
800
933
|
}
|
|
934
|
+
var noOpLogger = {
|
|
935
|
+
// 'error' is the quietest level the Logger contract offers; the methods
|
|
936
|
+
// discard everything regardless, this only matters to level checks.
|
|
937
|
+
level: "error",
|
|
938
|
+
error: () => {
|
|
939
|
+
},
|
|
940
|
+
warn: () => {
|
|
941
|
+
},
|
|
942
|
+
info: () => {
|
|
943
|
+
},
|
|
944
|
+
debug: () => {
|
|
945
|
+
},
|
|
946
|
+
trace: () => {
|
|
947
|
+
},
|
|
948
|
+
child: () => noOpLogger
|
|
949
|
+
};
|
|
801
950
|
|
|
802
951
|
// src/infrastructure/token-counter.ts
|
|
803
952
|
var PRICE_CACHE_MAX_SIZE = 100;
|
|
@@ -938,7 +1087,9 @@ var MEMORY_TYPE_LABELS = {
|
|
|
938
1087
|
// src/utils/expect-defined.ts
|
|
939
1088
|
function expectDefined(value, label) {
|
|
940
1089
|
if (value === null || value === void 0) {
|
|
941
|
-
|
|
1090
|
+
const err = new Error("Expected value to be defined");
|
|
1091
|
+
err.name = "ExpectDefinedError";
|
|
1092
|
+
throw err;
|
|
942
1093
|
}
|
|
943
1094
|
return value;
|
|
944
1095
|
}
|
|
@@ -986,22 +1137,31 @@ function estimateToolResultTokens(content) {
|
|
|
986
1137
|
function estimateTextTokens(text) {
|
|
987
1138
|
return RoughTokenEstimate(text);
|
|
988
1139
|
}
|
|
1140
|
+
function computeMessageTokens(msg) {
|
|
1141
|
+
if (typeof msg.content === "string") return estimateTextTokens(msg.content);
|
|
1142
|
+
let total = 0;
|
|
1143
|
+
for (const b of msg.content) {
|
|
1144
|
+
if (b.type === "text") total += estimateTextTokens(b.text);
|
|
1145
|
+
else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
|
|
1146
|
+
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
1147
|
+
else total += RoughTokenEstimate(JSON.stringify(b));
|
|
1148
|
+
}
|
|
1149
|
+
return total;
|
|
1150
|
+
}
|
|
989
1151
|
function estimateMessageTokens(messages) {
|
|
990
1152
|
let total = 0;
|
|
991
1153
|
for (const m of messages) {
|
|
992
|
-
if (typeof m.
|
|
993
|
-
total +=
|
|
994
|
-
|
|
995
|
-
for (const b of m.content) {
|
|
996
|
-
if (b.type === "text") total += estimateTextTokens(b.text);
|
|
997
|
-
else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
|
|
998
|
-
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
999
|
-
}
|
|
1154
|
+
if (typeof m._estTokens === "number" && m._estTokens > 0) {
|
|
1155
|
+
total += m._estTokens;
|
|
1156
|
+
continue;
|
|
1000
1157
|
}
|
|
1158
|
+
total += computeMessageTokens(m);
|
|
1001
1159
|
}
|
|
1002
1160
|
return total;
|
|
1003
1161
|
}
|
|
1004
1162
|
function estimateToolDefTokens(tool) {
|
|
1163
|
+
const cached = tool._estDefTokens;
|
|
1164
|
+
if (typeof cached === "number" && cached > 0) return cached;
|
|
1005
1165
|
return RoughTokenEstimate(tool.name) + RoughTokenEstimate(tool.description ?? "") + RoughTokenEstimate(JSON.stringify(tool.inputSchema));
|
|
1006
1166
|
}
|
|
1007
1167
|
function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
@@ -1011,6 +1171,11 @@ function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = C
|
|
|
1011
1171
|
} else if (Array.isArray(messages)) {
|
|
1012
1172
|
for (const m of messages) {
|
|
1013
1173
|
if (typeof m === "object" && m !== null && "content" in m) {
|
|
1174
|
+
const cached = m._estTokens;
|
|
1175
|
+
if (typeof cached === "number" && cached > 0) {
|
|
1176
|
+
messagesTokens += cached;
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1014
1179
|
const content = m.content;
|
|
1015
1180
|
if (typeof content === "string") {
|
|
1016
1181
|
messagesTokens += RoughTokenEstimate(content);
|
|
@@ -1176,6 +1341,18 @@ function findPreserveStart(messages, preserveK) {
|
|
|
1176
1341
|
}
|
|
1177
1342
|
function eliseOldToolResults(messages, opts) {
|
|
1178
1343
|
const preserveStart = findPreserveStart(messages, opts.preserveK);
|
|
1344
|
+
let hasOversized = false;
|
|
1345
|
+
for (let i = 0; i < preserveStart && !hasOversized; i++) {
|
|
1346
|
+
const msg = messages[i];
|
|
1347
|
+
if (!msg || !Array.isArray(msg.content)) continue;
|
|
1348
|
+
for (const b of msg.content) {
|
|
1349
|
+
if (b.type === "tool_result" && estimateToolResultTokens(b.content) >= opts.eliseThreshold) {
|
|
1350
|
+
hasOversized = true;
|
|
1351
|
+
break;
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
if (!hasOversized) return { messages, saved: 0, changed: false };
|
|
1179
1356
|
let saved = 0;
|
|
1180
1357
|
let changed = false;
|
|
1181
1358
|
const next = new Array(messages.length);
|
|
@@ -2356,6 +2533,77 @@ Remember: your job is to make the user a better developer, not just to complete
|
|
|
2356
2533
|
tags: ["teaching", "mentor", "learning"],
|
|
2357
2534
|
toolPreferences: ["read", "edit", "explain"],
|
|
2358
2535
|
suggestedSkills: ["prompt-engineering", "skill-creator", "node-modern", "typescript-strict"]
|
|
2536
|
+
},
|
|
2537
|
+
{
|
|
2538
|
+
id: "research-web",
|
|
2539
|
+
name: "Research Web",
|
|
2540
|
+
description: "Current-data research \u2014 search web, verify, inject findings into context",
|
|
2541
|
+
prompt: `## Research Web Mode
|
|
2542
|
+
|
|
2543
|
+
You are in research mode. Your role: find, verify, and incorporate
|
|
2544
|
+
current web data. Your training data is stale \u2014 every factual claim
|
|
2545
|
+
about version numbers, API surfaces, package status, or ecosystem
|
|
2546
|
+
changes must be verified against live sources.
|
|
2547
|
+
|
|
2548
|
+
### When to research
|
|
2549
|
+
- The user asks "is this still the case?", "what's current?", "latest version?"
|
|
2550
|
+
- You're about to claim a version number, deprecation, or API change
|
|
2551
|
+
- You're comparing tools, packages, or approaches released in the last 12 months
|
|
2552
|
+
- You realize your knowledge may be >6 months old on a fast-moving topic
|
|
2553
|
+
|
|
2554
|
+
### Research methodology
|
|
2555
|
+
1. **Search first, fetch selectively.** Use web_search with 5-8 results for
|
|
2556
|
+
broad queries. Then web_fetch the 1-2 most authoritative results for detail.
|
|
2557
|
+
Don't fetch every result \u2014 you'll burn tokens on noise.
|
|
2558
|
+
2. **Cross-reference.** One source is a data point. Two sources that agree
|
|
2559
|
+
is a signal. Three is confirmation. Flag single-source claims as tentative.
|
|
2560
|
+
3. **Cite sources.** Every factual claim from web data must include where it
|
|
2561
|
+
came from: domain name, and date if visible on the page.
|
|
2562
|
+
4. **Know when to stop.** 2-3 searches + 1-2 fetches is usually sufficient.
|
|
2563
|
+
If you're on your 5th search without a clear answer, pause and tell the user
|
|
2564
|
+
what you've found and what's still unclear \u2014 let them decide to dig deeper.
|
|
2565
|
+
5. **Inject findings for reuse.** After gathering current data, use
|
|
2566
|
+
context_manager with add_note to inject a structured "Research Findings"
|
|
2567
|
+
block into the conversation. Future turns see this and don't re-search.
|
|
2568
|
+
|
|
2569
|
+
### Self-injection pattern
|
|
2570
|
+
When you discover current data mid-research, inject it so subsequent turns
|
|
2571
|
+
benefit without re-searching:
|
|
2572
|
+
|
|
2573
|
+
web_search("Next.js middleware breaking changes 2025")
|
|
2574
|
+
\u2192 Surfaced: Next.js 15.2 changed middleware runtime from edge to node
|
|
2575
|
+
web_fetch("https://nextjs.org/docs/messages/middleware-upgrade-guide")
|
|
2576
|
+
\u2192 Confirmed: middleware now runs on Node.js runtime by default
|
|
2577
|
+
context_manager: add_note(
|
|
2578
|
+
"## Research: Next.js middleware
|
|
2579
|
+
- Next.js 15.2: middleware defaults to Node.js runtime (was edge)
|
|
2580
|
+
- Breaking: edge-only APIs (crypto.subtle, WebSocket) no longer available
|
|
2581
|
+
- Migration: use node:* equivalents or set runtime: 'edge' explicitly
|
|
2582
|
+
- Source: nextjs.org/docs/messages/middleware-upgrade-guide"
|
|
2583
|
+
)
|
|
2584
|
+
|
|
2585
|
+
The add_note persists in conversation \u2014 you won't re-search on the next turn.
|
|
2586
|
+
|
|
2587
|
+
### Anti-patterns
|
|
2588
|
+
- Don't research things already in the conversation context (including
|
|
2589
|
+
earlier add_note blocks you injected)
|
|
2590
|
+
- Don't treat a single web search result as ground truth \u2014 cross-reference
|
|
2591
|
+
- Don't inject raw JSON or search result dumps via add_note \u2014 summarize
|
|
2592
|
+
- Don't research while the user is waiting for a quick code edit \u2014 toggle
|
|
2593
|
+
research-web mode only during analysis/discussion phases
|
|
2594
|
+
- Don't research-loop: 5+ searches on one topic \u2192 stop and ask the user
|
|
2595
|
+
|
|
2596
|
+
### Exiting research mode
|
|
2597
|
+
When the user no longer needs current-data research, suggest switching back
|
|
2598
|
+
to the previous mode. You stay in research mode until explicitly told to
|
|
2599
|
+
switch \u2014 but don't force web searches on every turn. The methodology rules
|
|
2600
|
+
above already gate when to actually search.
|
|
2601
|
+
|
|
2602
|
+
When you're done with research: suggest the user run \`/mode default\` or
|
|
2603
|
+
their previous mode.`,
|
|
2604
|
+
tags: ["research", "web", "current-data", "up-to-date"],
|
|
2605
|
+
toolPreferences: ["web_search", "web_fetch", "search", "fetch", "context_manager"],
|
|
2606
|
+
suggestedSkills: ["research-web", "tech-stack", "node-modern", "security-scanner", "react-modern"]
|
|
2359
2607
|
}
|
|
2360
2608
|
];
|
|
2361
2609
|
|
|
@@ -2526,28 +2774,40 @@ var InMemoryAgentBridge = class {
|
|
|
2526
2774
|
return () => this.subscriptions.delete(handler);
|
|
2527
2775
|
}
|
|
2528
2776
|
async request(msg, timeoutMs) {
|
|
2529
|
-
if (this.stopped) throw new
|
|
2777
|
+
if (this.stopped) throw new AgentError({
|
|
2778
|
+
message: "Bridge is stopped",
|
|
2779
|
+
code: ERROR_CODES.AGENT_ABORTED
|
|
2780
|
+
});
|
|
2530
2781
|
const timeout = timeoutMs ?? this.timeoutMs;
|
|
2531
2782
|
const correlationId = msg.id;
|
|
2532
2783
|
if (this.inflightGuards.has(correlationId)) {
|
|
2533
|
-
throw new
|
|
2534
|
-
`Bridge request id "${correlationId}" collides with an in-flight request \u2014 caller is reusing message ids
|
|
2535
|
-
|
|
2784
|
+
throw new AgentError({
|
|
2785
|
+
message: `Bridge request id "${correlationId}" collides with an in-flight request \u2014 caller is reusing message ids`,
|
|
2786
|
+
code: ERROR_CODES.AGENT_RUN_FAILED,
|
|
2787
|
+
context: { correlationId }
|
|
2788
|
+
});
|
|
2536
2789
|
}
|
|
2537
2790
|
this.inflightGuards.add(correlationId);
|
|
2538
|
-
return new Promise((
|
|
2791
|
+
return new Promise((resolve4, reject) => {
|
|
2539
2792
|
const timer = setTimeout(() => {
|
|
2540
2793
|
this.inflightGuards.delete(correlationId);
|
|
2541
2794
|
this.pendingRequests.delete(correlationId);
|
|
2542
|
-
reject(new
|
|
2795
|
+
reject(new AgentError({
|
|
2796
|
+
message: `Request ${correlationId} timed out after ${timeout}ms`,
|
|
2797
|
+
code: ERROR_CODES.AGENT_RUN_FAILED,
|
|
2798
|
+
context: { correlationId, timeoutMs: timeout }
|
|
2799
|
+
}));
|
|
2543
2800
|
}, timeout);
|
|
2544
2801
|
if (!this.inflightGuards.has(correlationId)) {
|
|
2545
2802
|
clearTimeout(timer);
|
|
2546
|
-
reject(new
|
|
2803
|
+
reject(new AgentError({
|
|
2804
|
+
message: "Bridge stopped",
|
|
2805
|
+
code: ERROR_CODES.AGENT_ABORTED
|
|
2806
|
+
}));
|
|
2547
2807
|
return;
|
|
2548
2808
|
}
|
|
2549
2809
|
this.pendingRequests.set(correlationId, {
|
|
2550
|
-
resolve:
|
|
2810
|
+
resolve: resolve4,
|
|
2551
2811
|
reject,
|
|
2552
2812
|
timer
|
|
2553
2813
|
});
|
|
@@ -2564,7 +2824,10 @@ var InMemoryAgentBridge = class {
|
|
|
2564
2824
|
this.stopped = true;
|
|
2565
2825
|
for (const [, p] of this.pendingRequests) {
|
|
2566
2826
|
clearTimeout(p.timer);
|
|
2567
|
-
p.reject(new
|
|
2827
|
+
p.reject(new AgentError({
|
|
2828
|
+
message: "Bridge stopped",
|
|
2829
|
+
code: ERROR_CODES.AGENT_ABORTED
|
|
2830
|
+
}));
|
|
2568
2831
|
}
|
|
2569
2832
|
this.pendingRequests.clear();
|
|
2570
2833
|
this.inflightGuards.clear();
|
|
@@ -2743,11 +3006,11 @@ function validateAgainstSchema(value, schema) {
|
|
|
2743
3006
|
walk2(value, schema, "", errors);
|
|
2744
3007
|
return { ok: errors.length === 0, errors };
|
|
2745
3008
|
}
|
|
2746
|
-
function walk2(value, schema,
|
|
3009
|
+
function walk2(value, schema, path7, errors) {
|
|
2747
3010
|
if (schema.enum !== void 0) {
|
|
2748
3011
|
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
2749
3012
|
errors.push({
|
|
2750
|
-
path:
|
|
3013
|
+
path: path7 || "<root>",
|
|
2751
3014
|
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
2752
3015
|
});
|
|
2753
3016
|
return;
|
|
@@ -2756,7 +3019,7 @@ function walk2(value, schema, path6, errors) {
|
|
|
2756
3019
|
if (typeof schema.type === "string") {
|
|
2757
3020
|
if (!checkType(value, schema.type)) {
|
|
2758
3021
|
errors.push({
|
|
2759
|
-
path:
|
|
3022
|
+
path: path7 || "<root>",
|
|
2760
3023
|
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
2761
3024
|
});
|
|
2762
3025
|
return;
|
|
@@ -2766,19 +3029,19 @@ function walk2(value, schema, path6, errors) {
|
|
|
2766
3029
|
const obj = value;
|
|
2767
3030
|
for (const req of schema.required ?? []) {
|
|
2768
3031
|
if (!(req in obj)) {
|
|
2769
|
-
errors.push({ path: joinPath(
|
|
3032
|
+
errors.push({ path: joinPath(path7, req), message: "required property missing" });
|
|
2770
3033
|
}
|
|
2771
3034
|
}
|
|
2772
3035
|
if (schema.properties) {
|
|
2773
3036
|
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
2774
3037
|
if (key in obj) {
|
|
2775
|
-
walk2(obj[key], subSchema, joinPath(
|
|
3038
|
+
walk2(obj[key], subSchema, joinPath(path7, key), errors);
|
|
2776
3039
|
}
|
|
2777
3040
|
}
|
|
2778
3041
|
}
|
|
2779
3042
|
}
|
|
2780
3043
|
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
2781
|
-
value.forEach((item, i) => walk2(item, schema.items, `${
|
|
3044
|
+
value.forEach((item, i) => walk2(item, schema.items, `${path7}[${i}]`, errors));
|
|
2782
3045
|
}
|
|
2783
3046
|
}
|
|
2784
3047
|
function checkType(value, type) {
|
|
@@ -2876,7 +3139,7 @@ function createToolOutputSerializer(opts = {}) {
|
|
|
2876
3139
|
}
|
|
2877
3140
|
|
|
2878
3141
|
// src/execution/tool-executor.ts
|
|
2879
|
-
var ToolExecutor = class {
|
|
3142
|
+
var ToolExecutor = class _ToolExecutor {
|
|
2880
3143
|
constructor(registry, opts) {
|
|
2881
3144
|
this.registry = registry;
|
|
2882
3145
|
this.opts = opts;
|
|
@@ -2888,6 +3151,10 @@ var ToolExecutor = class {
|
|
|
2888
3151
|
}
|
|
2889
3152
|
registry;
|
|
2890
3153
|
opts;
|
|
3154
|
+
/** Minimum gap between coalesced `partial_output` tool.progress emits. */
|
|
3155
|
+
static PROGRESS_EMIT_INTERVAL_MS = 100;
|
|
3156
|
+
/** Max chars of accumulated stream text carried per coalesced emit. */
|
|
3157
|
+
static PROGRESS_TAIL_CHARS = 16384;
|
|
2891
3158
|
serializer;
|
|
2892
3159
|
iterationTimeoutMs;
|
|
2893
3160
|
maxToolTimeoutMs;
|
|
@@ -2933,9 +3200,6 @@ Please call the tool again with arguments that match its inputSchema. You can us
|
|
|
2933
3200
|
return { result, tool, durationMs: Date.now() - start };
|
|
2934
3201
|
}
|
|
2935
3202
|
const toolDangerousCaps = getDangerousCapabilities(tool);
|
|
2936
|
-
if (toolDangerousCaps.length > 0) {
|
|
2937
|
-
if (this.opts.events) ;
|
|
2938
|
-
}
|
|
2939
3203
|
if (hasMalformedArguments(use.input)) {
|
|
2940
3204
|
const result = this.malformedInputResult(use, extractMalformedRaw(use.input));
|
|
2941
3205
|
budget = this.decrementBudget(result, budget);
|
|
@@ -3173,17 +3437,48 @@ ${post.additionalContext}` };
|
|
|
3173
3437
|
throw new Error(`Tool "${tool.name}" does not support streaming execution`);
|
|
3174
3438
|
}
|
|
3175
3439
|
const stream = tool.executeStream(input, ctx, { signal });
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
break;
|
|
3181
|
-
}
|
|
3440
|
+
const iter = stream[Symbol.asyncIterator]();
|
|
3441
|
+
let progressTail = "";
|
|
3442
|
+
let lastProgressEmitAt = 0;
|
|
3443
|
+
const emitProgress = (ev) => {
|
|
3182
3444
|
this.opts.events?.emit("tool.progress", {
|
|
3183
3445
|
name: tool.name,
|
|
3184
3446
|
id: toolUseId ?? "<unknown>",
|
|
3185
3447
|
event: ev
|
|
3186
3448
|
});
|
|
3449
|
+
};
|
|
3450
|
+
const flushProgressTail = (force) => {
|
|
3451
|
+
if (progressTail.length === 0) return;
|
|
3452
|
+
const now = Date.now();
|
|
3453
|
+
if (!force && now - lastProgressEmitAt < _ToolExecutor.PROGRESS_EMIT_INTERVAL_MS) return;
|
|
3454
|
+
const text = progressTail;
|
|
3455
|
+
progressTail = "";
|
|
3456
|
+
lastProgressEmitAt = now;
|
|
3457
|
+
emitProgress({ type: "partial_output", text });
|
|
3458
|
+
};
|
|
3459
|
+
try {
|
|
3460
|
+
while (true) {
|
|
3461
|
+
const { done, value: ev } = await iter.next();
|
|
3462
|
+
if (done) break;
|
|
3463
|
+
if (ev.type === "final") {
|
|
3464
|
+
finalOutput = ev.output;
|
|
3465
|
+
sawFinal = true;
|
|
3466
|
+
break;
|
|
3467
|
+
}
|
|
3468
|
+
if (ev.type === "partial_output" && typeof ev.text === "string") {
|
|
3469
|
+
progressTail += ev.text;
|
|
3470
|
+
if (progressTail.length > _ToolExecutor.PROGRESS_TAIL_CHARS) {
|
|
3471
|
+
progressTail = progressTail.slice(-_ToolExecutor.PROGRESS_TAIL_CHARS);
|
|
3472
|
+
}
|
|
3473
|
+
flushProgressTail(false);
|
|
3474
|
+
continue;
|
|
3475
|
+
}
|
|
3476
|
+
flushProgressTail(true);
|
|
3477
|
+
emitProgress(ev);
|
|
3478
|
+
}
|
|
3479
|
+
flushProgressTail(true);
|
|
3480
|
+
} finally {
|
|
3481
|
+
await iter.return?.(void 0);
|
|
3187
3482
|
}
|
|
3188
3483
|
if (!sawFinal) {
|
|
3189
3484
|
throw new Error(`tool "${tool.name}" executeStream completed without a 'final' event`);
|
|
@@ -3292,6 +3587,253 @@ function extractMalformedRaw(input) {
|
|
|
3292
3587
|
}
|
|
3293
3588
|
}
|
|
3294
3589
|
|
|
3590
|
+
// src/core/conversation-state.ts
|
|
3591
|
+
var ConversationState = class {
|
|
3592
|
+
ctx;
|
|
3593
|
+
listeners = /* @__PURE__ */ new Set();
|
|
3594
|
+
constructor(ctx) {
|
|
3595
|
+
this.ctx = ctx;
|
|
3596
|
+
}
|
|
3597
|
+
get messages() {
|
|
3598
|
+
return this.ctx.messages;
|
|
3599
|
+
}
|
|
3600
|
+
get todos() {
|
|
3601
|
+
return this.ctx.todos;
|
|
3602
|
+
}
|
|
3603
|
+
get meta() {
|
|
3604
|
+
return this.ctx.meta;
|
|
3605
|
+
}
|
|
3606
|
+
/**
|
|
3607
|
+
* Cheap immutable snapshot. Useful for tests and for compaction passes
|
|
3608
|
+
* that need a stable view across an async boundary.
|
|
3609
|
+
*
|
|
3610
|
+
* Uses shallow-freeze instead of deep-freeze: only the wrapper object
|
|
3611
|
+
* and the three content arrays are frozen. Individual message/todo
|
|
3612
|
+
* objects are NOT recursively frozen — they are reconstructed via
|
|
3613
|
+
* spread copies and are immutable by convention. This cuts the freeze
|
|
3614
|
+
* count from O(n·m·d) (n=messages, m=content blocks, d=depth) to O(1).
|
|
3615
|
+
*/
|
|
3616
|
+
snapshot() {
|
|
3617
|
+
const snap = {
|
|
3618
|
+
messages: [...this.ctx.messages],
|
|
3619
|
+
todos: [...this.ctx.todos],
|
|
3620
|
+
meta: { ...this.ctx.meta }
|
|
3621
|
+
};
|
|
3622
|
+
Object.freeze(snap.messages);
|
|
3623
|
+
Object.freeze(snap.todos);
|
|
3624
|
+
Object.freeze(snap.meta);
|
|
3625
|
+
return Object.freeze(snap);
|
|
3626
|
+
}
|
|
3627
|
+
appendMessage(message) {
|
|
3628
|
+
if (message._estTokens === void 0) {
|
|
3629
|
+
message._estTokens = computeMessageTokens(message);
|
|
3630
|
+
}
|
|
3631
|
+
this.ctx.messages.splice(this.ctx.messages.length, 0, message);
|
|
3632
|
+
this.emit({ kind: "message_appended", message });
|
|
3633
|
+
}
|
|
3634
|
+
replaceMessages(messages) {
|
|
3635
|
+
for (const m of messages) {
|
|
3636
|
+
if (m._estTokens === void 0) {
|
|
3637
|
+
m._estTokens = computeMessageTokens(m);
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
const arr = this.ctx.messages;
|
|
3641
|
+
if (messages.length < arr.length) {
|
|
3642
|
+
arr.length = messages.length;
|
|
3643
|
+
}
|
|
3644
|
+
for (let i = 0; i < messages.length; i++) {
|
|
3645
|
+
arr[i] = messages[i];
|
|
3646
|
+
}
|
|
3647
|
+
if (messages.some(
|
|
3648
|
+
(m) => Array.isArray(m.content) && m.content.some((b) => b.type === "tool_use" || b.type === "tool_result")
|
|
3649
|
+
)) {
|
|
3650
|
+
this.ctx.toolAdjacencyDirty = true;
|
|
3651
|
+
}
|
|
3652
|
+
this.emit({ kind: "messages_replaced", messages: [...messages] });
|
|
3653
|
+
}
|
|
3654
|
+
replaceTodos(todos) {
|
|
3655
|
+
const allDone = todos.length > 0 && todos.every((t) => t.status === "completed");
|
|
3656
|
+
const effective = allDone ? [] : todos;
|
|
3657
|
+
this.ctx.todos.length = 0;
|
|
3658
|
+
this.ctx.todos.splice(0, 0, ...effective);
|
|
3659
|
+
this.emit({ kind: "todos_replaced", todos: [...effective] });
|
|
3660
|
+
}
|
|
3661
|
+
setMeta(key, value) {
|
|
3662
|
+
this.ctx.meta[key] = value;
|
|
3663
|
+
this.emit({ kind: "meta_set", key, value });
|
|
3664
|
+
}
|
|
3665
|
+
deleteMeta(key) {
|
|
3666
|
+
if (!(key in this.ctx.meta)) return;
|
|
3667
|
+
delete this.ctx.meta[key];
|
|
3668
|
+
this.emit({ kind: "meta_deleted", key });
|
|
3669
|
+
}
|
|
3670
|
+
clearMeta() {
|
|
3671
|
+
const keys = Object.keys(this.ctx.meta);
|
|
3672
|
+
if (keys.length === 0) return;
|
|
3673
|
+
for (const key of keys) delete this.ctx.meta[key];
|
|
3674
|
+
this.emit({ kind: "meta_cleared" });
|
|
3675
|
+
}
|
|
3676
|
+
/**
|
|
3677
|
+
* Subscribe to mutations that go through this wrapper. Direct mutations of
|
|
3678
|
+
* the compatibility arrays are intentionally not observed.
|
|
3679
|
+
*/
|
|
3680
|
+
onChange(listener) {
|
|
3681
|
+
this.listeners.add(listener);
|
|
3682
|
+
return () => this.listeners.delete(listener);
|
|
3683
|
+
}
|
|
3684
|
+
emit(change) {
|
|
3685
|
+
for (const h of this.listeners) {
|
|
3686
|
+
try {
|
|
3687
|
+
h(change, this);
|
|
3688
|
+
} catch {
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
};
|
|
3693
|
+
|
|
3694
|
+
// src/core/context.ts
|
|
3695
|
+
var Context = class {
|
|
3696
|
+
messages = [];
|
|
3697
|
+
todos = [];
|
|
3698
|
+
readFiles = /* @__PURE__ */ new Set();
|
|
3699
|
+
fileMtimes = /* @__PURE__ */ new Map();
|
|
3700
|
+
systemPrompt;
|
|
3701
|
+
provider;
|
|
3702
|
+
session;
|
|
3703
|
+
signal;
|
|
3704
|
+
tokenCounter;
|
|
3705
|
+
cwd;
|
|
3706
|
+
projectRoot;
|
|
3707
|
+
/** Mutable working directory — starts as `cwd`. Change via `setWorkingDir()`. */
|
|
3708
|
+
workingDir;
|
|
3709
|
+
model;
|
|
3710
|
+
tools = [];
|
|
3711
|
+
meta = {};
|
|
3712
|
+
/** Agent id performing this run (e.g. 'leader', 'executor'). */
|
|
3713
|
+
agentId;
|
|
3714
|
+
/** Human-readable agent name. */
|
|
3715
|
+
agentName;
|
|
3716
|
+
/** Callbacks fired when `setWorkingDir()` changes the working directory. */
|
|
3717
|
+
_onWorkingDirChanged = [];
|
|
3718
|
+
/**
|
|
3719
|
+
* Set to true when the conversation gains new tool_use or tool_result
|
|
3720
|
+
* blocks — the only time repairToolUseAdjacency() can find new issues.
|
|
3721
|
+
* buildAndRunRequestPipeline() checks this flag to skip an O(n) scan
|
|
3722
|
+
* on iterations where no tool content was added (pure text responses).
|
|
3723
|
+
*/
|
|
3724
|
+
toolAdjacencyDirty = false;
|
|
3725
|
+
constructor(init) {
|
|
3726
|
+
this.systemPrompt = init.systemPrompt;
|
|
3727
|
+
this.provider = init.provider;
|
|
3728
|
+
this.session = init.session;
|
|
3729
|
+
this.signal = init.signal;
|
|
3730
|
+
this.tokenCounter = init.tokenCounter;
|
|
3731
|
+
this.cwd = init.cwd;
|
|
3732
|
+
this.projectRoot = init.projectRoot;
|
|
3733
|
+
this.workingDir = init.workingDir ?? init.cwd;
|
|
3734
|
+
this.model = init.model;
|
|
3735
|
+
this.tools = init.tools ?? [];
|
|
3736
|
+
this.agentId = init.agentId ?? "unknown";
|
|
3737
|
+
this.agentName = init.agentName ?? "Unknown Agent";
|
|
3738
|
+
}
|
|
3739
|
+
/**
|
|
3740
|
+
* Observable wrapper over the mutable conversation state. Lazy so
|
|
3741
|
+
* subsystems that don't subscribe pay nothing. Mutations made directly
|
|
3742
|
+
* on `ctx.messages` / `ctx.todos` are still visible through this
|
|
3743
|
+
* wrapper's read API (it holds a reference, not a copy) but only
|
|
3744
|
+
* mutations that go through `state.appendMessage()` etc. fire
|
|
3745
|
+
* `onChange`. New code should prefer the wrapper API.
|
|
3746
|
+
*/
|
|
3747
|
+
_state = null;
|
|
3748
|
+
get state() {
|
|
3749
|
+
if (!this._state) this._state = new ConversationState(this);
|
|
3750
|
+
return this._state;
|
|
3751
|
+
}
|
|
3752
|
+
/**
|
|
3753
|
+
* Register a teardown hook tied to the current run's abort signal.
|
|
3754
|
+
* Hooks registered before a run starts are stored and fired when the
|
|
3755
|
+
* next run ends; there is no immediate fire when no run is active.
|
|
3756
|
+
*
|
|
3757
|
+
* **Scope:** these hooks fire on the **whole agent run's** abort, not on
|
|
3758
|
+
* an individual tool call. For per-tool teardown of resources owned by
|
|
3759
|
+
* the tool author (child processes, handles), prefer `Tool.cleanup` —
|
|
3760
|
+
* see its JSDoc for the full rule.
|
|
3761
|
+
*/
|
|
3762
|
+
abortHooks = /* @__PURE__ */ new Set();
|
|
3763
|
+
registerAbortHook(fn) {
|
|
3764
|
+
this.abortHooks.add(fn);
|
|
3765
|
+
return () => this.abortHooks.delete(fn);
|
|
3766
|
+
}
|
|
3767
|
+
async drainAbortHooks() {
|
|
3768
|
+
const snapshot = [...this.abortHooks].reverse();
|
|
3769
|
+
this.abortHooks.clear();
|
|
3770
|
+
for (const fn of snapshot) {
|
|
3771
|
+
try {
|
|
3772
|
+
await fn();
|
|
3773
|
+
} catch {
|
|
3774
|
+
}
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3777
|
+
recordRead(absPath, mtimeMs) {
|
|
3778
|
+
this.readFiles.add(absPath);
|
|
3779
|
+
this.fileMtimes.set(absPath, mtimeMs);
|
|
3780
|
+
}
|
|
3781
|
+
/** Clear accumulated file-read metadata after compaction or at boundaries
|
|
3782
|
+
* where stale read history could cause tools to skip legitimate re-reads.
|
|
3783
|
+
* The agent re-populates this naturally on the next file access. */
|
|
3784
|
+
clearFileTracking() {
|
|
3785
|
+
this.readFiles.clear();
|
|
3786
|
+
this.fileMtimes.clear();
|
|
3787
|
+
}
|
|
3788
|
+
hasRead(absPath) {
|
|
3789
|
+
return this.readFiles.has(absPath);
|
|
3790
|
+
}
|
|
3791
|
+
lastReadMtime(absPath) {
|
|
3792
|
+
return this.fileMtimes.get(absPath);
|
|
3793
|
+
}
|
|
3794
|
+
/**
|
|
3795
|
+
* Change the working directory for path resolution. Resolves relative paths
|
|
3796
|
+
* against `projectRoot` and validates the result is within the project root.
|
|
3797
|
+
* Fires all registered `onWorkingDirChanged` callbacks.
|
|
3798
|
+
* Returns the resolved absolute path.
|
|
3799
|
+
*/
|
|
3800
|
+
setWorkingDir(dir) {
|
|
3801
|
+
const resolved = path4.isAbsolute(dir) ? path4.resolve(dir) : path4.resolve(this.projectRoot, dir);
|
|
3802
|
+
const root = path4.resolve(this.projectRoot);
|
|
3803
|
+
const rel = path4.relative(root, resolved);
|
|
3804
|
+
if (rel.startsWith("..") || path4.isAbsolute(rel)) {
|
|
3805
|
+
throw new Error(
|
|
3806
|
+
`Working directory "${resolved}" is outside project root "${root}"`
|
|
3807
|
+
);
|
|
3808
|
+
}
|
|
3809
|
+
const old = this.workingDir;
|
|
3810
|
+
this.workingDir = resolved;
|
|
3811
|
+
for (const cb of this._onWorkingDirChanged) {
|
|
3812
|
+
try {
|
|
3813
|
+
cb(resolved, old);
|
|
3814
|
+
} catch {
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
return resolved;
|
|
3818
|
+
}
|
|
3819
|
+
/**
|
|
3820
|
+
* Register a callback that fires when the working directory changes.
|
|
3821
|
+
* Returns an unsubscribe function. Callbacks are fired synchronously
|
|
3822
|
+
* inside `setWorkingDir()` — errors in callbacks are swallowed so one
|
|
3823
|
+
* bad listener doesn't prevent others from executing.
|
|
3824
|
+
*/
|
|
3825
|
+
onWorkingDirChanged(cb) {
|
|
3826
|
+
this._onWorkingDirChanged.push(cb);
|
|
3827
|
+
return () => {
|
|
3828
|
+
const idx = this._onWorkingDirChanged.indexOf(cb);
|
|
3829
|
+
if (idx >= 0) this._onWorkingDirChanged.splice(idx, 1);
|
|
3830
|
+
};
|
|
3831
|
+
}
|
|
3832
|
+
usage() {
|
|
3833
|
+
return this.tokenCounter.total();
|
|
3834
|
+
}
|
|
3835
|
+
};
|
|
3836
|
+
|
|
3295
3837
|
// src/utils/regex-guard.ts
|
|
3296
3838
|
var MAX_PATTERN_LEN = 512;
|
|
3297
3839
|
var DANGEROUS_PATTERNS = [
|
|
@@ -3604,6 +4146,6 @@ function renderPlainText(meta, events) {
|
|
|
3604
4146
|
return lines.join("\n");
|
|
3605
4147
|
}
|
|
3606
4148
|
|
|
3607
|
-
export { AgentError, CONTEXT_WINDOW_MODES, ConfigError, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONTEXT_CONFIG, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SESSION_LOGGING_CONFIG, DEFAULT_SESSION_PRUNE_DAYS, DEFAULT_SPEC_TEMPLATE, DEFAULT_TOOLS_CONFIG, DefaultErrorHandler, DefaultLogger, DefaultModelsRegistry, DefaultPathResolver, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultTokenCounter, ERROR_CODES, FsError, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, MEMORY_TYPE_LABELS, PluginError, ProviderError, SessionError, StreamHangError, ToolError, ToolExecutor, WrongStackError, asBlocks, asText, buildRecoveryStrategies, classifyFamily, computeTaskProgress, createMessage, decryptConfigSecrets, encryptConfigSecrets, findCriticalPath, formatContextWindowModeList, getContextWindowMode, isAgentError, isConfigError, isContextWindowModeId, isFsError, isImageBlock, isPluginError, isSecretField, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, migratePlaintextSecrets, resolveContextWindowPolicy, rewriteConfigEncrypted, toWrongStackError, topologicalSort };
|
|
4149
|
+
export { AgentError, CONTEXT_WINDOW_MODES, ConfigError, Context, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONTEXT_CONFIG, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SESSION_LOGGING_CONFIG, DEFAULT_SESSION_PRUNE_DAYS, DEFAULT_SPEC_TEMPLATE, DEFAULT_TOOLS_CONFIG, DefaultErrorHandler, DefaultLogger, DefaultModelsRegistry, DefaultPathResolver, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultTokenCounter, ERROR_CODES, FsError, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, MEMORY_TYPE_LABELS, PluginError, ProviderError, SddError, SessionError, StreamHangError, ToolError, ToolExecutor, WrongStackError, asBlocks, asText, buildRecoveryStrategies, classifyFamily, computeTaskProgress, createMessage, decryptConfigSecrets, encryptConfigSecrets, findCriticalPath, formatContextWindowModeList, getContextWindowMode, isAgentError, isConfigError, isContextWindowModeId, isFsError, isImageBlock, isPluginError, isSddError, isSecretField, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, migratePlaintextSecrets, noOpLogger, resolveContextWindowPolicy, rewriteConfigEncrypted, toWrongStackError, topologicalSort };
|
|
3608
4150
|
//# sourceMappingURL=index.js.map
|
|
3609
4151
|
//# sourceMappingURL=index.js.map
|