@wrongstack/core 0.155.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-BbZU5TPN.d.ts → agent-bridge-Cimv7bK7.d.ts} +1 -1
- package/dist/{agent-subagent-runner-Bsueu0J2.d.ts → agent-subagent-runner-C658wj_c.d.ts} +9 -8
- package/dist/{brain-CS_B0vIE.d.ts → brain-sCZ3lCjq.d.ts} +26 -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 +1983 -145
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +26 -26
- package/dist/defaults/index.js +1105 -289
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +45 -16
- package/dist/execution/index.js +224 -53
- 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-CbV8pXLD.d.ts → goal-preamble-CnbzyVvl.d.ts} +19 -10
- package/dist/{index-CI1hRfPt.d.ts → index-BlMqh5GO.d.ts} +8 -8
- package/dist/{index-B5wz-GXm.d.ts → index-C2eSNPsB.d.ts} +7 -5
- package/dist/index.d.ts +438 -128
- package/dist/index.js +4974 -836
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +7 -7
- package/dist/infrastructure/index.js +61 -13
- 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-CPERR2De.d.ts → mcp-servers-DFbirBv6.d.ts} +3 -3
- 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-BSKSFNhv.d.ts → multi-agent-coordinator-60weDZoA.d.ts} +8 -8
- package/dist/{null-fleet-bus-CGOez8Le.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-CYoTKjsz.d.ts → parallel-eternal-engine-DtG1fjc9.d.ts} +13 -9
- package/dist/{path-resolver-DuhlmPil.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-DbH7lg-t.d.ts → plan-templates-DPABrDvy.d.ts} +18 -7
- package/dist/{provider-runner-Cocq0O9E.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 +215 -79
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-vault-w8MbUe2Q.d.ts → secret-vault-CeVNiy_f.d.ts} +3 -2
- package/dist/security/index.d.ts +5 -4
- package/dist/security/index.js +155 -13
- 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 +150 -12
- package/dist/storage/index.js +1041 -214
- package/dist/storage/index.js.map +1 -1
- package/dist/types/index.d.ts +67 -20
- package/dist/types/index.js +557 -52
- 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 +16 -4
- package/dist/utils/index.js +40 -14
- 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,7 +442,7 @@ 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;
|
|
@@ -489,6 +512,26 @@ var KEY_BYTES = 32;
|
|
|
489
512
|
var IV_BYTES = 12;
|
|
490
513
|
var TAG_BYTES = 16;
|
|
491
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
|
+
}
|
|
492
535
|
var DefaultSecretVault = class {
|
|
493
536
|
keyFile;
|
|
494
537
|
key;
|
|
@@ -512,14 +555,26 @@ var DefaultSecretVault = class {
|
|
|
512
555
|
const rest = value.slice(ENCRYPTED_PREFIX.length);
|
|
513
556
|
const parts = rest.split(":");
|
|
514
557
|
if (parts.length !== 3) {
|
|
515
|
-
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
|
+
});
|
|
516
563
|
}
|
|
517
564
|
const [ivB64, tagB64, ctB64] = parts;
|
|
518
565
|
const iv = Buffer.from(ivB64, "base64");
|
|
519
566
|
const tag = Buffer.from(tagB64, "base64");
|
|
520
567
|
const ct = Buffer.from(ctB64, "base64");
|
|
521
|
-
if (iv.length !== IV_BYTES) throw new
|
|
522
|
-
|
|
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
|
+
});
|
|
523
578
|
const key = this.loadOrCreateKey();
|
|
524
579
|
const decipher = createDecipheriv(ALGO, key, iv);
|
|
525
580
|
decipher.setAuthTag(tag);
|
|
@@ -531,11 +586,14 @@ var DefaultSecretVault = class {
|
|
|
531
586
|
try {
|
|
532
587
|
const buf = fs2.readFileSync(this.keyFile);
|
|
533
588
|
if (buf.length !== KEY_BYTES) {
|
|
534
|
-
throw new
|
|
535
|
-
`SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES}). Remove it manually to generate a new key
|
|
536
|
-
|
|
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
|
+
});
|
|
537
594
|
}
|
|
538
595
|
this.key = buf;
|
|
596
|
+
checkKeyFilePermissions(this.keyFile);
|
|
539
597
|
return this.key;
|
|
540
598
|
} catch (err) {
|
|
541
599
|
if (err.code !== "ENOENT") throw err;
|
|
@@ -548,11 +606,14 @@ var DefaultSecretVault = class {
|
|
|
548
606
|
if (err.code !== "EEXIST") throw err;
|
|
549
607
|
const buf = fs2.readFileSync(this.keyFile);
|
|
550
608
|
if (buf.length !== KEY_BYTES) {
|
|
551
|
-
throw new
|
|
552
|
-
`SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES}). Remove it manually to generate a new key
|
|
553
|
-
|
|
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
|
+
});
|
|
554
614
|
}
|
|
555
615
|
this.key = buf;
|
|
616
|
+
checkKeyFilePermissions(this.keyFile);
|
|
556
617
|
return this.key;
|
|
557
618
|
}
|
|
558
619
|
this.key = key;
|
|
@@ -613,7 +674,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
|
613
674
|
await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
614
675
|
await restrictFilePermissions(configPath);
|
|
615
676
|
}
|
|
616
|
-
async function migratePlaintextSecrets(configPath, vault) {
|
|
677
|
+
async function migratePlaintextSecrets(configPath, vault, logger) {
|
|
617
678
|
let raw;
|
|
618
679
|
try {
|
|
619
680
|
raw = await fs.readFile(configPath, "utf8");
|
|
@@ -630,11 +691,14 @@ async function migratePlaintextSecrets(configPath, vault) {
|
|
|
630
691
|
const migrated = walkCount(parsed, vault, counter);
|
|
631
692
|
if (counter.n === 0) return { migrated: 0, file: configPath };
|
|
632
693
|
await atomicWrite(configPath, JSON.stringify(migrated, null, 2), { mode: 384 });
|
|
633
|
-
await restrictFilePermissions(
|
|
694
|
+
await restrictFilePermissions(
|
|
695
|
+
configPath,
|
|
696
|
+
logger ? { warn: (msg) => logger.warn(msg) } : void 0
|
|
697
|
+
);
|
|
634
698
|
return { migrated: counter.n, file: configPath };
|
|
635
699
|
}
|
|
636
700
|
async function restrictFilePermissions(filePath, opts) {
|
|
637
|
-
const warn = ((msg) => console.warn(msg));
|
|
701
|
+
const warn = opts?.warn ?? ((msg) => console.warn(msg));
|
|
638
702
|
if (process.platform === "win32") {
|
|
639
703
|
try {
|
|
640
704
|
const { execFile } = await import('child_process');
|
|
@@ -751,18 +815,24 @@ var COLORS = {
|
|
|
751
815
|
trace: color.dim
|
|
752
816
|
};
|
|
753
817
|
var LOG_LEVELS = /* @__PURE__ */ new Set(["error", "warn", "info", "debug", "trace"]);
|
|
818
|
+
var LOG_FORMATS = /* @__PURE__ */ new Set(["pretty", "json"]);
|
|
754
819
|
var DefaultLogger = class _DefaultLogger {
|
|
820
|
+
/** How many file writes between rotation size checks (statSync is not free). */
|
|
821
|
+
static ROTATE_CHECK_EVERY = 100;
|
|
755
822
|
level;
|
|
756
823
|
file;
|
|
757
824
|
bindings;
|
|
758
|
-
|
|
825
|
+
format;
|
|
759
826
|
stderr;
|
|
827
|
+
maxFileBytes;
|
|
828
|
+
writesSinceRotateCheck = 0;
|
|
760
829
|
constructor(opts = {}) {
|
|
761
830
|
this.level = opts.level ?? parseLogLevel(process.env.WRONGSTACK_LOG_LEVEL);
|
|
762
831
|
this.file = opts.file;
|
|
763
832
|
this.bindings = opts.bindings ?? {};
|
|
764
|
-
this.
|
|
833
|
+
this.format = opts.format ?? parseLogFormat(process.env.WRONGSTACK_LOG_FORMAT);
|
|
765
834
|
this.stderr = opts.stderr !== false;
|
|
835
|
+
this.maxFileBytes = opts.maxFileBytes ?? 10 * 1024 * 1024;
|
|
766
836
|
if (this.file) {
|
|
767
837
|
try {
|
|
768
838
|
fs2.mkdirSync(path4.dirname(this.file), { recursive: true });
|
|
@@ -789,11 +859,30 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
789
859
|
return new _DefaultLogger({
|
|
790
860
|
level: this.level,
|
|
791
861
|
file: this.file,
|
|
792
|
-
|
|
862
|
+
format: this.format,
|
|
793
863
|
stderr: this.stderr,
|
|
864
|
+
maxFileBytes: this.maxFileBytes,
|
|
794
865
|
bindings: { ...this.bindings, ...bindings }
|
|
795
866
|
});
|
|
796
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
|
+
}
|
|
797
886
|
log(level, msg, ctx) {
|
|
798
887
|
const r = LEVEL_RANK[level];
|
|
799
888
|
const allowed = LEVEL_RANK[this.level];
|
|
@@ -805,13 +894,17 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
805
894
|
}
|
|
806
895
|
if (this.file) {
|
|
807
896
|
try {
|
|
897
|
+
this.maybeRotate(this.file);
|
|
808
898
|
fs2.appendFileSync(this.file, `${JSON.stringify(entry)}
|
|
809
899
|
`);
|
|
810
900
|
} catch {
|
|
811
901
|
}
|
|
812
902
|
}
|
|
813
903
|
if (!this.stderr) return;
|
|
814
|
-
if (
|
|
904
|
+
if (this.format === "json") {
|
|
905
|
+
writeErr(`${JSON.stringify(entry)}
|
|
906
|
+
`);
|
|
907
|
+
} else {
|
|
815
908
|
const head = `${color.dim(ts)} ${COLORS[level](level.toUpperCase().padEnd(5))} ${msg}`;
|
|
816
909
|
if (ctx !== void 0) {
|
|
817
910
|
writeErr(`${head} ${formatCtx(ctx)}
|
|
@@ -826,6 +919,9 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
826
919
|
function parseLogLevel(raw) {
|
|
827
920
|
return raw && LOG_LEVELS.has(raw) ? raw : "info";
|
|
828
921
|
}
|
|
922
|
+
function parseLogFormat(raw) {
|
|
923
|
+
return raw && LOG_FORMATS.has(raw) ? raw : "pretty";
|
|
924
|
+
}
|
|
829
925
|
function formatCtx(ctx) {
|
|
830
926
|
if (ctx instanceof Error) return color.dim(ctx.message);
|
|
831
927
|
if (typeof ctx === "string") return color.dim(ctx);
|
|
@@ -835,6 +931,22 @@ function formatCtx(ctx) {
|
|
|
835
931
|
return color.dim(String(ctx));
|
|
836
932
|
}
|
|
837
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
|
+
};
|
|
838
950
|
|
|
839
951
|
// src/infrastructure/token-counter.ts
|
|
840
952
|
var PRICE_CACHE_MAX_SIZE = 100;
|
|
@@ -975,7 +1087,9 @@ var MEMORY_TYPE_LABELS = {
|
|
|
975
1087
|
// src/utils/expect-defined.ts
|
|
976
1088
|
function expectDefined(value, label) {
|
|
977
1089
|
if (value === null || value === void 0) {
|
|
978
|
-
|
|
1090
|
+
const err = new Error("Expected value to be defined");
|
|
1091
|
+
err.name = "ExpectDefinedError";
|
|
1092
|
+
throw err;
|
|
979
1093
|
}
|
|
980
1094
|
return value;
|
|
981
1095
|
}
|
|
@@ -1023,22 +1137,31 @@ function estimateToolResultTokens(content) {
|
|
|
1023
1137
|
function estimateTextTokens(text) {
|
|
1024
1138
|
return RoughTokenEstimate(text);
|
|
1025
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
|
+
}
|
|
1026
1151
|
function estimateMessageTokens(messages) {
|
|
1027
1152
|
let total = 0;
|
|
1028
1153
|
for (const m of messages) {
|
|
1029
|
-
if (typeof m.
|
|
1030
|
-
total +=
|
|
1031
|
-
|
|
1032
|
-
for (const b of m.content) {
|
|
1033
|
-
if (b.type === "text") total += estimateTextTokens(b.text);
|
|
1034
|
-
else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
|
|
1035
|
-
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
1036
|
-
}
|
|
1154
|
+
if (typeof m._estTokens === "number" && m._estTokens > 0) {
|
|
1155
|
+
total += m._estTokens;
|
|
1156
|
+
continue;
|
|
1037
1157
|
}
|
|
1158
|
+
total += computeMessageTokens(m);
|
|
1038
1159
|
}
|
|
1039
1160
|
return total;
|
|
1040
1161
|
}
|
|
1041
1162
|
function estimateToolDefTokens(tool) {
|
|
1163
|
+
const cached = tool._estDefTokens;
|
|
1164
|
+
if (typeof cached === "number" && cached > 0) return cached;
|
|
1042
1165
|
return RoughTokenEstimate(tool.name) + RoughTokenEstimate(tool.description ?? "") + RoughTokenEstimate(JSON.stringify(tool.inputSchema));
|
|
1043
1166
|
}
|
|
1044
1167
|
function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
@@ -1048,6 +1171,11 @@ function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = C
|
|
|
1048
1171
|
} else if (Array.isArray(messages)) {
|
|
1049
1172
|
for (const m of messages) {
|
|
1050
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
|
+
}
|
|
1051
1179
|
const content = m.content;
|
|
1052
1180
|
if (typeof content === "string") {
|
|
1053
1181
|
messagesTokens += RoughTokenEstimate(content);
|
|
@@ -1213,6 +1341,18 @@ function findPreserveStart(messages, preserveK) {
|
|
|
1213
1341
|
}
|
|
1214
1342
|
function eliseOldToolResults(messages, opts) {
|
|
1215
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 };
|
|
1216
1356
|
let saved = 0;
|
|
1217
1357
|
let changed = false;
|
|
1218
1358
|
const next = new Array(messages.length);
|
|
@@ -2393,6 +2533,77 @@ Remember: your job is to make the user a better developer, not just to complete
|
|
|
2393
2533
|
tags: ["teaching", "mentor", "learning"],
|
|
2394
2534
|
toolPreferences: ["read", "edit", "explain"],
|
|
2395
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"]
|
|
2396
2607
|
}
|
|
2397
2608
|
];
|
|
2398
2609
|
|
|
@@ -2563,28 +2774,40 @@ var InMemoryAgentBridge = class {
|
|
|
2563
2774
|
return () => this.subscriptions.delete(handler);
|
|
2564
2775
|
}
|
|
2565
2776
|
async request(msg, timeoutMs) {
|
|
2566
|
-
if (this.stopped) throw new
|
|
2777
|
+
if (this.stopped) throw new AgentError({
|
|
2778
|
+
message: "Bridge is stopped",
|
|
2779
|
+
code: ERROR_CODES.AGENT_ABORTED
|
|
2780
|
+
});
|
|
2567
2781
|
const timeout = timeoutMs ?? this.timeoutMs;
|
|
2568
2782
|
const correlationId = msg.id;
|
|
2569
2783
|
if (this.inflightGuards.has(correlationId)) {
|
|
2570
|
-
throw new
|
|
2571
|
-
`Bridge request id "${correlationId}" collides with an in-flight request \u2014 caller is reusing message ids
|
|
2572
|
-
|
|
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
|
+
});
|
|
2573
2789
|
}
|
|
2574
2790
|
this.inflightGuards.add(correlationId);
|
|
2575
|
-
return new Promise((
|
|
2791
|
+
return new Promise((resolve4, reject) => {
|
|
2576
2792
|
const timer = setTimeout(() => {
|
|
2577
2793
|
this.inflightGuards.delete(correlationId);
|
|
2578
2794
|
this.pendingRequests.delete(correlationId);
|
|
2579
|
-
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
|
+
}));
|
|
2580
2800
|
}, timeout);
|
|
2581
2801
|
if (!this.inflightGuards.has(correlationId)) {
|
|
2582
2802
|
clearTimeout(timer);
|
|
2583
|
-
reject(new
|
|
2803
|
+
reject(new AgentError({
|
|
2804
|
+
message: "Bridge stopped",
|
|
2805
|
+
code: ERROR_CODES.AGENT_ABORTED
|
|
2806
|
+
}));
|
|
2584
2807
|
return;
|
|
2585
2808
|
}
|
|
2586
2809
|
this.pendingRequests.set(correlationId, {
|
|
2587
|
-
resolve:
|
|
2810
|
+
resolve: resolve4,
|
|
2588
2811
|
reject,
|
|
2589
2812
|
timer
|
|
2590
2813
|
});
|
|
@@ -2601,7 +2824,10 @@ var InMemoryAgentBridge = class {
|
|
|
2601
2824
|
this.stopped = true;
|
|
2602
2825
|
for (const [, p] of this.pendingRequests) {
|
|
2603
2826
|
clearTimeout(p.timer);
|
|
2604
|
-
p.reject(new
|
|
2827
|
+
p.reject(new AgentError({
|
|
2828
|
+
message: "Bridge stopped",
|
|
2829
|
+
code: ERROR_CODES.AGENT_ABORTED
|
|
2830
|
+
}));
|
|
2605
2831
|
}
|
|
2606
2832
|
this.pendingRequests.clear();
|
|
2607
2833
|
this.inflightGuards.clear();
|
|
@@ -2780,11 +3006,11 @@ function validateAgainstSchema(value, schema) {
|
|
|
2780
3006
|
walk2(value, schema, "", errors);
|
|
2781
3007
|
return { ok: errors.length === 0, errors };
|
|
2782
3008
|
}
|
|
2783
|
-
function walk2(value, schema,
|
|
3009
|
+
function walk2(value, schema, path7, errors) {
|
|
2784
3010
|
if (schema.enum !== void 0) {
|
|
2785
3011
|
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
2786
3012
|
errors.push({
|
|
2787
|
-
path:
|
|
3013
|
+
path: path7 || "<root>",
|
|
2788
3014
|
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
2789
3015
|
});
|
|
2790
3016
|
return;
|
|
@@ -2793,7 +3019,7 @@ function walk2(value, schema, path6, errors) {
|
|
|
2793
3019
|
if (typeof schema.type === "string") {
|
|
2794
3020
|
if (!checkType(value, schema.type)) {
|
|
2795
3021
|
errors.push({
|
|
2796
|
-
path:
|
|
3022
|
+
path: path7 || "<root>",
|
|
2797
3023
|
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
2798
3024
|
});
|
|
2799
3025
|
return;
|
|
@@ -2803,19 +3029,19 @@ function walk2(value, schema, path6, errors) {
|
|
|
2803
3029
|
const obj = value;
|
|
2804
3030
|
for (const req of schema.required ?? []) {
|
|
2805
3031
|
if (!(req in obj)) {
|
|
2806
|
-
errors.push({ path: joinPath(
|
|
3032
|
+
errors.push({ path: joinPath(path7, req), message: "required property missing" });
|
|
2807
3033
|
}
|
|
2808
3034
|
}
|
|
2809
3035
|
if (schema.properties) {
|
|
2810
3036
|
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
2811
3037
|
if (key in obj) {
|
|
2812
|
-
walk2(obj[key], subSchema, joinPath(
|
|
3038
|
+
walk2(obj[key], subSchema, joinPath(path7, key), errors);
|
|
2813
3039
|
}
|
|
2814
3040
|
}
|
|
2815
3041
|
}
|
|
2816
3042
|
}
|
|
2817
3043
|
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
2818
|
-
value.forEach((item, i) => walk2(item, schema.items, `${
|
|
3044
|
+
value.forEach((item, i) => walk2(item, schema.items, `${path7}[${i}]`, errors));
|
|
2819
3045
|
}
|
|
2820
3046
|
}
|
|
2821
3047
|
function checkType(value, type) {
|
|
@@ -2913,7 +3139,7 @@ function createToolOutputSerializer(opts = {}) {
|
|
|
2913
3139
|
}
|
|
2914
3140
|
|
|
2915
3141
|
// src/execution/tool-executor.ts
|
|
2916
|
-
var ToolExecutor = class {
|
|
3142
|
+
var ToolExecutor = class _ToolExecutor {
|
|
2917
3143
|
constructor(registry, opts) {
|
|
2918
3144
|
this.registry = registry;
|
|
2919
3145
|
this.opts = opts;
|
|
@@ -2925,6 +3151,10 @@ var ToolExecutor = class {
|
|
|
2925
3151
|
}
|
|
2926
3152
|
registry;
|
|
2927
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;
|
|
2928
3158
|
serializer;
|
|
2929
3159
|
iterationTimeoutMs;
|
|
2930
3160
|
maxToolTimeoutMs;
|
|
@@ -2970,9 +3200,6 @@ Please call the tool again with arguments that match its inputSchema. You can us
|
|
|
2970
3200
|
return { result, tool, durationMs: Date.now() - start };
|
|
2971
3201
|
}
|
|
2972
3202
|
const toolDangerousCaps = getDangerousCapabilities(tool);
|
|
2973
|
-
if (toolDangerousCaps.length > 0) {
|
|
2974
|
-
if (this.opts.events) ;
|
|
2975
|
-
}
|
|
2976
3203
|
if (hasMalformedArguments(use.input)) {
|
|
2977
3204
|
const result = this.malformedInputResult(use, extractMalformedRaw(use.input));
|
|
2978
3205
|
budget = this.decrementBudget(result, budget);
|
|
@@ -3210,17 +3437,48 @@ ${post.additionalContext}` };
|
|
|
3210
3437
|
throw new Error(`Tool "${tool.name}" does not support streaming execution`);
|
|
3211
3438
|
}
|
|
3212
3439
|
const stream = tool.executeStream(input, ctx, { signal });
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
break;
|
|
3218
|
-
}
|
|
3440
|
+
const iter = stream[Symbol.asyncIterator]();
|
|
3441
|
+
let progressTail = "";
|
|
3442
|
+
let lastProgressEmitAt = 0;
|
|
3443
|
+
const emitProgress = (ev) => {
|
|
3219
3444
|
this.opts.events?.emit("tool.progress", {
|
|
3220
3445
|
name: tool.name,
|
|
3221
3446
|
id: toolUseId ?? "<unknown>",
|
|
3222
3447
|
event: ev
|
|
3223
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);
|
|
3224
3482
|
}
|
|
3225
3483
|
if (!sawFinal) {
|
|
3226
3484
|
throw new Error(`tool "${tool.name}" executeStream completed without a 'final' event`);
|
|
@@ -3329,6 +3587,253 @@ function extractMalformedRaw(input) {
|
|
|
3329
3587
|
}
|
|
3330
3588
|
}
|
|
3331
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
|
+
|
|
3332
3837
|
// src/utils/regex-guard.ts
|
|
3333
3838
|
var MAX_PATTERN_LEN = 512;
|
|
3334
3839
|
var DANGEROUS_PATTERNS = [
|
|
@@ -3641,6 +4146,6 @@ function renderPlainText(meta, events) {
|
|
|
3641
4146
|
return lines.join("\n");
|
|
3642
4147
|
}
|
|
3643
4148
|
|
|
3644
|
-
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 };
|
|
3645
4150
|
//# sourceMappingURL=index.js.map
|
|
3646
4151
|
//# sourceMappingURL=index.js.map
|