@wrongstack/core 0.155.0 → 0.250.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-4gc0vfW2.d.ts} +1 -1
- package/dist/{agent-subagent-runner-Bsueu0J2.d.ts → agent-subagent-runner-Dz-9kiE6.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-eSsrto5d.d.ts} +8 -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 +1986 -146
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +26 -26
- package/dist/defaults/index.js +1110 -296
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +45 -16
- package/dist/execution/index.js +229 -56
- 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-BjJpnLW4.d.ts} +19 -10
- package/dist/{index-CI1hRfPt.d.ts → index-Dy8OwfBD.d.ts} +8 -8
- package/dist/{index-B5wz-GXm.d.ts → index-IehiNryU.d.ts} +7 -5
- package/dist/index.d.ts +438 -128
- package/dist/index.js +4989 -849
- 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-DfXxCASH.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-DpanBg8D.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-BSKSFNhv.d.ts → multi-agent-coordinator-CnbEqpv0.d.ts} +8 -8
- package/dist/{null-fleet-bus-CGOez8Le.d.ts → null-fleet-bus-Do1OLYpj.d.ts} +7 -7
- package/dist/observability/index.d.ts +2 -2
- package/dist/package-outdated-watcher-CA5GGB4C.d.ts +560 -0
- package/dist/{parallel-eternal-engine-CYoTKjsz.d.ts → parallel-eternal-engine-UZg1xOzE.d.ts} +13 -9
- package/dist/{path-resolver-DuhlmPil.d.ts → path-resolver-BaP06Owy.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-D1n-gQI-.d.ts +493 -0
- package/dist/{plan-templates-DbH7lg-t.d.ts → plan-templates-BUVRY0pU.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 +221 -87
- 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 +562 -55
- 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 +25 -4
- package/dist/utils/index.js +45 -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,14 @@ 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
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/utils/string.ts
|
|
257
|
+
function truncate(s, max) {
|
|
258
|
+
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
259
|
+
}
|
|
232
260
|
|
|
233
261
|
// src/types/provider.ts
|
|
234
262
|
var ProviderError = class extends WrongStackError {
|
|
@@ -288,9 +316,6 @@ function describeStatus(status, type) {
|
|
|
288
316
|
if (type) return `${type} (${status})`;
|
|
289
317
|
return `HTTP ${status}`;
|
|
290
318
|
}
|
|
291
|
-
function truncate(s, n) {
|
|
292
|
-
return s.length <= n ? s : `${s.slice(0, n - 1)}\u2026`;
|
|
293
|
-
}
|
|
294
319
|
var StreamHangError = class extends ProviderError {
|
|
295
320
|
/** Name of the provider that hung, e.g. "zai", "anthropic". */
|
|
296
321
|
hungProviderId;
|
|
@@ -419,7 +444,7 @@ async function renameWithRetry(from, to) {
|
|
|
419
444
|
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
420
445
|
throw err;
|
|
421
446
|
}
|
|
422
|
-
await new Promise((
|
|
447
|
+
await new Promise((resolve4) => setTimeout(resolve4, delays[i]));
|
|
423
448
|
}
|
|
424
449
|
}
|
|
425
450
|
throw lastErr;
|
|
@@ -489,6 +514,26 @@ var KEY_BYTES = 32;
|
|
|
489
514
|
var IV_BYTES = 12;
|
|
490
515
|
var TAG_BYTES = 16;
|
|
491
516
|
var ALGO = "aes-256-gcm";
|
|
517
|
+
var KEY_FILE_MODE = 384;
|
|
518
|
+
function checkKeyFilePermissions(keyFile) {
|
|
519
|
+
if (process.platform === "win32") return;
|
|
520
|
+
try {
|
|
521
|
+
const stat2 = fs2.statSync(keyFile);
|
|
522
|
+
const actualMode = stat2.mode & 511;
|
|
523
|
+
if (actualMode !== KEY_FILE_MODE) {
|
|
524
|
+
console.warn(JSON.stringify({
|
|
525
|
+
level: "warn",
|
|
526
|
+
event: "vault.key_file_wrong_permissions",
|
|
527
|
+
message: `Key file ${keyFile} has mode ${actualMode.toString(8)} \u2014 expected ${KEY_FILE_MODE.toString(8)}. Run: chmod ${KEY_FILE_MODE.toString(8)} ${keyFile}`,
|
|
528
|
+
keyFile,
|
|
529
|
+
expectedMode: KEY_FILE_MODE,
|
|
530
|
+
actualMode,
|
|
531
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
532
|
+
}));
|
|
533
|
+
}
|
|
534
|
+
} catch {
|
|
535
|
+
}
|
|
536
|
+
}
|
|
492
537
|
var DefaultSecretVault = class {
|
|
493
538
|
keyFile;
|
|
494
539
|
key;
|
|
@@ -512,14 +557,26 @@ var DefaultSecretVault = class {
|
|
|
512
557
|
const rest = value.slice(ENCRYPTED_PREFIX.length);
|
|
513
558
|
const parts = rest.split(":");
|
|
514
559
|
if (parts.length !== 3) {
|
|
515
|
-
throw new
|
|
560
|
+
throw new ConfigError({
|
|
561
|
+
message: "SecretVault: malformed encrypted value",
|
|
562
|
+
code: ERROR_CODES.CONFIG_PARSE_FAILED,
|
|
563
|
+
context: { field: "encrypted_value" }
|
|
564
|
+
});
|
|
516
565
|
}
|
|
517
566
|
const [ivB64, tagB64, ctB64] = parts;
|
|
518
567
|
const iv = Buffer.from(ivB64, "base64");
|
|
519
568
|
const tag = Buffer.from(tagB64, "base64");
|
|
520
569
|
const ct = Buffer.from(ctB64, "base64");
|
|
521
|
-
if (iv.length !== IV_BYTES) throw new
|
|
522
|
-
|
|
570
|
+
if (iv.length !== IV_BYTES) throw new ConfigError({
|
|
571
|
+
message: "SecretVault: bad IV length",
|
|
572
|
+
code: ERROR_CODES.CONFIG_PARSE_FAILED,
|
|
573
|
+
context: { expected: IV_BYTES, actual: iv.length }
|
|
574
|
+
});
|
|
575
|
+
if (tag.length !== TAG_BYTES) throw new ConfigError({
|
|
576
|
+
message: "SecretVault: bad tag length",
|
|
577
|
+
code: ERROR_CODES.CONFIG_PARSE_FAILED,
|
|
578
|
+
context: { expected: TAG_BYTES, actual: tag.length }
|
|
579
|
+
});
|
|
523
580
|
const key = this.loadOrCreateKey();
|
|
524
581
|
const decipher = createDecipheriv(ALGO, key, iv);
|
|
525
582
|
decipher.setAuthTag(tag);
|
|
@@ -531,11 +588,14 @@ var DefaultSecretVault = class {
|
|
|
531
588
|
try {
|
|
532
589
|
const buf = fs2.readFileSync(this.keyFile);
|
|
533
590
|
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
|
-
|
|
591
|
+
throw new ConfigError({
|
|
592
|
+
message: `SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES}). Remove it manually to generate a new key.`,
|
|
593
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
594
|
+
context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: buf.length }
|
|
595
|
+
});
|
|
537
596
|
}
|
|
538
597
|
this.key = buf;
|
|
598
|
+
checkKeyFilePermissions(this.keyFile);
|
|
539
599
|
return this.key;
|
|
540
600
|
} catch (err) {
|
|
541
601
|
if (err.code !== "ENOENT") throw err;
|
|
@@ -548,11 +608,14 @@ var DefaultSecretVault = class {
|
|
|
548
608
|
if (err.code !== "EEXIST") throw err;
|
|
549
609
|
const buf = fs2.readFileSync(this.keyFile);
|
|
550
610
|
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
|
-
|
|
611
|
+
throw new ConfigError({
|
|
612
|
+
message: `SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES}). Remove it manually to generate a new key.`,
|
|
613
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
614
|
+
context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: buf.length }
|
|
615
|
+
});
|
|
554
616
|
}
|
|
555
617
|
this.key = buf;
|
|
618
|
+
checkKeyFilePermissions(this.keyFile);
|
|
556
619
|
return this.key;
|
|
557
620
|
}
|
|
558
621
|
this.key = key;
|
|
@@ -613,7 +676,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
|
613
676
|
await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
614
677
|
await restrictFilePermissions(configPath);
|
|
615
678
|
}
|
|
616
|
-
async function migratePlaintextSecrets(configPath, vault) {
|
|
679
|
+
async function migratePlaintextSecrets(configPath, vault, logger) {
|
|
617
680
|
let raw;
|
|
618
681
|
try {
|
|
619
682
|
raw = await fs.readFile(configPath, "utf8");
|
|
@@ -630,11 +693,14 @@ async function migratePlaintextSecrets(configPath, vault) {
|
|
|
630
693
|
const migrated = walkCount(parsed, vault, counter);
|
|
631
694
|
if (counter.n === 0) return { migrated: 0, file: configPath };
|
|
632
695
|
await atomicWrite(configPath, JSON.stringify(migrated, null, 2), { mode: 384 });
|
|
633
|
-
await restrictFilePermissions(
|
|
696
|
+
await restrictFilePermissions(
|
|
697
|
+
configPath,
|
|
698
|
+
logger ? { warn: (msg) => logger.warn(msg) } : void 0
|
|
699
|
+
);
|
|
634
700
|
return { migrated: counter.n, file: configPath };
|
|
635
701
|
}
|
|
636
702
|
async function restrictFilePermissions(filePath, opts) {
|
|
637
|
-
const warn = ((msg) => console.warn(msg));
|
|
703
|
+
const warn = opts?.warn ?? ((msg) => console.warn(msg));
|
|
638
704
|
if (process.platform === "win32") {
|
|
639
705
|
try {
|
|
640
706
|
const { execFile } = await import('child_process');
|
|
@@ -751,18 +817,24 @@ var COLORS = {
|
|
|
751
817
|
trace: color.dim
|
|
752
818
|
};
|
|
753
819
|
var LOG_LEVELS = /* @__PURE__ */ new Set(["error", "warn", "info", "debug", "trace"]);
|
|
820
|
+
var LOG_FORMATS = /* @__PURE__ */ new Set(["pretty", "json"]);
|
|
754
821
|
var DefaultLogger = class _DefaultLogger {
|
|
822
|
+
/** How many file writes between rotation size checks (statSync is not free). */
|
|
823
|
+
static ROTATE_CHECK_EVERY = 100;
|
|
755
824
|
level;
|
|
756
825
|
file;
|
|
757
826
|
bindings;
|
|
758
|
-
|
|
827
|
+
format;
|
|
759
828
|
stderr;
|
|
829
|
+
maxFileBytes;
|
|
830
|
+
writesSinceRotateCheck = 0;
|
|
760
831
|
constructor(opts = {}) {
|
|
761
832
|
this.level = opts.level ?? parseLogLevel(process.env.WRONGSTACK_LOG_LEVEL);
|
|
762
833
|
this.file = opts.file;
|
|
763
834
|
this.bindings = opts.bindings ?? {};
|
|
764
|
-
this.
|
|
835
|
+
this.format = opts.format ?? parseLogFormat(process.env.WRONGSTACK_LOG_FORMAT);
|
|
765
836
|
this.stderr = opts.stderr !== false;
|
|
837
|
+
this.maxFileBytes = opts.maxFileBytes ?? 10 * 1024 * 1024;
|
|
766
838
|
if (this.file) {
|
|
767
839
|
try {
|
|
768
840
|
fs2.mkdirSync(path4.dirname(this.file), { recursive: true });
|
|
@@ -789,11 +861,30 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
789
861
|
return new _DefaultLogger({
|
|
790
862
|
level: this.level,
|
|
791
863
|
file: this.file,
|
|
792
|
-
|
|
864
|
+
format: this.format,
|
|
793
865
|
stderr: this.stderr,
|
|
866
|
+
maxFileBytes: this.maxFileBytes,
|
|
794
867
|
bindings: { ...this.bindings, ...bindings }
|
|
795
868
|
});
|
|
796
869
|
}
|
|
870
|
+
/**
|
|
871
|
+
* Size-based rotation: when the file outgrows `maxFileBytes`, rename it to
|
|
872
|
+
* `<file>.1` (dropping the previous `.1`) so the live file restarts empty.
|
|
873
|
+
* Checked on the first write and every ROTATE_CHECK_EVERY writes after.
|
|
874
|
+
* Best-effort: a rename can fail on Windows while another process holds
|
|
875
|
+
* the file — the next check retries. Multiple processes appending to the
|
|
876
|
+
* same log all run this check; whoever crosses the threshold first wins.
|
|
877
|
+
*/
|
|
878
|
+
maybeRotate(file) {
|
|
879
|
+
if (this.writesSinceRotateCheck++ % _DefaultLogger.ROTATE_CHECK_EVERY !== 0) return;
|
|
880
|
+
try {
|
|
881
|
+
const st = fs2.statSync(file);
|
|
882
|
+
if (st.size < this.maxFileBytes) return;
|
|
883
|
+
fs2.rmSync(`${file}.1`, { force: true });
|
|
884
|
+
fs2.renameSync(file, `${file}.1`);
|
|
885
|
+
} catch {
|
|
886
|
+
}
|
|
887
|
+
}
|
|
797
888
|
log(level, msg, ctx) {
|
|
798
889
|
const r = LEVEL_RANK[level];
|
|
799
890
|
const allowed = LEVEL_RANK[this.level];
|
|
@@ -805,13 +896,17 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
805
896
|
}
|
|
806
897
|
if (this.file) {
|
|
807
898
|
try {
|
|
899
|
+
this.maybeRotate(this.file);
|
|
808
900
|
fs2.appendFileSync(this.file, `${JSON.stringify(entry)}
|
|
809
901
|
`);
|
|
810
902
|
} catch {
|
|
811
903
|
}
|
|
812
904
|
}
|
|
813
905
|
if (!this.stderr) return;
|
|
814
|
-
if (
|
|
906
|
+
if (this.format === "json") {
|
|
907
|
+
writeErr(`${JSON.stringify(entry)}
|
|
908
|
+
`);
|
|
909
|
+
} else {
|
|
815
910
|
const head = `${color.dim(ts)} ${COLORS[level](level.toUpperCase().padEnd(5))} ${msg}`;
|
|
816
911
|
if (ctx !== void 0) {
|
|
817
912
|
writeErr(`${head} ${formatCtx(ctx)}
|
|
@@ -826,6 +921,9 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
826
921
|
function parseLogLevel(raw) {
|
|
827
922
|
return raw && LOG_LEVELS.has(raw) ? raw : "info";
|
|
828
923
|
}
|
|
924
|
+
function parseLogFormat(raw) {
|
|
925
|
+
return raw && LOG_FORMATS.has(raw) ? raw : "pretty";
|
|
926
|
+
}
|
|
829
927
|
function formatCtx(ctx) {
|
|
830
928
|
if (ctx instanceof Error) return color.dim(ctx.message);
|
|
831
929
|
if (typeof ctx === "string") return color.dim(ctx);
|
|
@@ -835,6 +933,22 @@ function formatCtx(ctx) {
|
|
|
835
933
|
return color.dim(String(ctx));
|
|
836
934
|
}
|
|
837
935
|
}
|
|
936
|
+
var noOpLogger = {
|
|
937
|
+
// 'error' is the quietest level the Logger contract offers; the methods
|
|
938
|
+
// discard everything regardless, this only matters to level checks.
|
|
939
|
+
level: "error",
|
|
940
|
+
error: () => {
|
|
941
|
+
},
|
|
942
|
+
warn: () => {
|
|
943
|
+
},
|
|
944
|
+
info: () => {
|
|
945
|
+
},
|
|
946
|
+
debug: () => {
|
|
947
|
+
},
|
|
948
|
+
trace: () => {
|
|
949
|
+
},
|
|
950
|
+
child: () => noOpLogger
|
|
951
|
+
};
|
|
838
952
|
|
|
839
953
|
// src/infrastructure/token-counter.ts
|
|
840
954
|
var PRICE_CACHE_MAX_SIZE = 100;
|
|
@@ -975,7 +1089,9 @@ var MEMORY_TYPE_LABELS = {
|
|
|
975
1089
|
// src/utils/expect-defined.ts
|
|
976
1090
|
function expectDefined(value, label) {
|
|
977
1091
|
if (value === null || value === void 0) {
|
|
978
|
-
|
|
1092
|
+
const err = new Error("Expected value to be defined");
|
|
1093
|
+
err.name = "ExpectDefinedError";
|
|
1094
|
+
throw err;
|
|
979
1095
|
}
|
|
980
1096
|
return value;
|
|
981
1097
|
}
|
|
@@ -1023,22 +1139,31 @@ function estimateToolResultTokens(content) {
|
|
|
1023
1139
|
function estimateTextTokens(text) {
|
|
1024
1140
|
return RoughTokenEstimate(text);
|
|
1025
1141
|
}
|
|
1142
|
+
function computeMessageTokens(msg) {
|
|
1143
|
+
if (typeof msg.content === "string") return estimateTextTokens(msg.content);
|
|
1144
|
+
let total = 0;
|
|
1145
|
+
for (const b of msg.content) {
|
|
1146
|
+
if (b.type === "text") total += estimateTextTokens(b.text);
|
|
1147
|
+
else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
|
|
1148
|
+
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
1149
|
+
else total += RoughTokenEstimate(JSON.stringify(b));
|
|
1150
|
+
}
|
|
1151
|
+
return total;
|
|
1152
|
+
}
|
|
1026
1153
|
function estimateMessageTokens(messages) {
|
|
1027
1154
|
let total = 0;
|
|
1028
1155
|
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
|
-
}
|
|
1156
|
+
if (typeof m._estTokens === "number" && m._estTokens > 0) {
|
|
1157
|
+
total += m._estTokens;
|
|
1158
|
+
continue;
|
|
1037
1159
|
}
|
|
1160
|
+
total += computeMessageTokens(m);
|
|
1038
1161
|
}
|
|
1039
1162
|
return total;
|
|
1040
1163
|
}
|
|
1041
1164
|
function estimateToolDefTokens(tool) {
|
|
1165
|
+
const cached = tool._estDefTokens;
|
|
1166
|
+
if (typeof cached === "number" && cached > 0) return cached;
|
|
1042
1167
|
return RoughTokenEstimate(tool.name) + RoughTokenEstimate(tool.description ?? "") + RoughTokenEstimate(JSON.stringify(tool.inputSchema));
|
|
1043
1168
|
}
|
|
1044
1169
|
function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
@@ -1048,6 +1173,11 @@ function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = C
|
|
|
1048
1173
|
} else if (Array.isArray(messages)) {
|
|
1049
1174
|
for (const m of messages) {
|
|
1050
1175
|
if (typeof m === "object" && m !== null && "content" in m) {
|
|
1176
|
+
const cached = m._estTokens;
|
|
1177
|
+
if (typeof cached === "number" && cached > 0) {
|
|
1178
|
+
messagesTokens += cached;
|
|
1179
|
+
continue;
|
|
1180
|
+
}
|
|
1051
1181
|
const content = m.content;
|
|
1052
1182
|
if (typeof content === "string") {
|
|
1053
1183
|
messagesTokens += RoughTokenEstimate(content);
|
|
@@ -1213,6 +1343,18 @@ function findPreserveStart(messages, preserveK) {
|
|
|
1213
1343
|
}
|
|
1214
1344
|
function eliseOldToolResults(messages, opts) {
|
|
1215
1345
|
const preserveStart = findPreserveStart(messages, opts.preserveK);
|
|
1346
|
+
let hasOversized = false;
|
|
1347
|
+
for (let i = 0; i < preserveStart && !hasOversized; i++) {
|
|
1348
|
+
const msg = messages[i];
|
|
1349
|
+
if (!msg || !Array.isArray(msg.content)) continue;
|
|
1350
|
+
for (const b of msg.content) {
|
|
1351
|
+
if (b.type === "tool_result" && estimateToolResultTokens(b.content) >= opts.eliseThreshold) {
|
|
1352
|
+
hasOversized = true;
|
|
1353
|
+
break;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
if (!hasOversized) return { messages, saved: 0, changed: false };
|
|
1216
1358
|
let saved = 0;
|
|
1217
1359
|
let changed = false;
|
|
1218
1360
|
const next = new Array(messages.length);
|
|
@@ -2393,6 +2535,77 @@ Remember: your job is to make the user a better developer, not just to complete
|
|
|
2393
2535
|
tags: ["teaching", "mentor", "learning"],
|
|
2394
2536
|
toolPreferences: ["read", "edit", "explain"],
|
|
2395
2537
|
suggestedSkills: ["prompt-engineering", "skill-creator", "node-modern", "typescript-strict"]
|
|
2538
|
+
},
|
|
2539
|
+
{
|
|
2540
|
+
id: "research-web",
|
|
2541
|
+
name: "Research Web",
|
|
2542
|
+
description: "Current-data research \u2014 search web, verify, inject findings into context",
|
|
2543
|
+
prompt: `## Research Web Mode
|
|
2544
|
+
|
|
2545
|
+
You are in research mode. Your role: find, verify, and incorporate
|
|
2546
|
+
current web data. Your training data is stale \u2014 every factual claim
|
|
2547
|
+
about version numbers, API surfaces, package status, or ecosystem
|
|
2548
|
+
changes must be verified against live sources.
|
|
2549
|
+
|
|
2550
|
+
### When to research
|
|
2551
|
+
- The user asks "is this still the case?", "what's current?", "latest version?"
|
|
2552
|
+
- You're about to claim a version number, deprecation, or API change
|
|
2553
|
+
- You're comparing tools, packages, or approaches released in the last 12 months
|
|
2554
|
+
- You realize your knowledge may be >6 months old on a fast-moving topic
|
|
2555
|
+
|
|
2556
|
+
### Research methodology
|
|
2557
|
+
1. **Search first, fetch selectively.** Use web_search with 5-8 results for
|
|
2558
|
+
broad queries. Then web_fetch the 1-2 most authoritative results for detail.
|
|
2559
|
+
Don't fetch every result \u2014 you'll burn tokens on noise.
|
|
2560
|
+
2. **Cross-reference.** One source is a data point. Two sources that agree
|
|
2561
|
+
is a signal. Three is confirmation. Flag single-source claims as tentative.
|
|
2562
|
+
3. **Cite sources.** Every factual claim from web data must include where it
|
|
2563
|
+
came from: domain name, and date if visible on the page.
|
|
2564
|
+
4. **Know when to stop.** 2-3 searches + 1-2 fetches is usually sufficient.
|
|
2565
|
+
If you're on your 5th search without a clear answer, pause and tell the user
|
|
2566
|
+
what you've found and what's still unclear \u2014 let them decide to dig deeper.
|
|
2567
|
+
5. **Inject findings for reuse.** After gathering current data, use
|
|
2568
|
+
context_manager with add_note to inject a structured "Research Findings"
|
|
2569
|
+
block into the conversation. Future turns see this and don't re-search.
|
|
2570
|
+
|
|
2571
|
+
### Self-injection pattern
|
|
2572
|
+
When you discover current data mid-research, inject it so subsequent turns
|
|
2573
|
+
benefit without re-searching:
|
|
2574
|
+
|
|
2575
|
+
web_search("Next.js middleware breaking changes 2025")
|
|
2576
|
+
\u2192 Surfaced: Next.js 15.2 changed middleware runtime from edge to node
|
|
2577
|
+
web_fetch("https://nextjs.org/docs/messages/middleware-upgrade-guide")
|
|
2578
|
+
\u2192 Confirmed: middleware now runs on Node.js runtime by default
|
|
2579
|
+
context_manager: add_note(
|
|
2580
|
+
"## Research: Next.js middleware
|
|
2581
|
+
- Next.js 15.2: middleware defaults to Node.js runtime (was edge)
|
|
2582
|
+
- Breaking: edge-only APIs (crypto.subtle, WebSocket) no longer available
|
|
2583
|
+
- Migration: use node:* equivalents or set runtime: 'edge' explicitly
|
|
2584
|
+
- Source: nextjs.org/docs/messages/middleware-upgrade-guide"
|
|
2585
|
+
)
|
|
2586
|
+
|
|
2587
|
+
The add_note persists in conversation \u2014 you won't re-search on the next turn.
|
|
2588
|
+
|
|
2589
|
+
### Anti-patterns
|
|
2590
|
+
- Don't research things already in the conversation context (including
|
|
2591
|
+
earlier add_note blocks you injected)
|
|
2592
|
+
- Don't treat a single web search result as ground truth \u2014 cross-reference
|
|
2593
|
+
- Don't inject raw JSON or search result dumps via add_note \u2014 summarize
|
|
2594
|
+
- Don't research while the user is waiting for a quick code edit \u2014 toggle
|
|
2595
|
+
research-web mode only during analysis/discussion phases
|
|
2596
|
+
- Don't research-loop: 5+ searches on one topic \u2192 stop and ask the user
|
|
2597
|
+
|
|
2598
|
+
### Exiting research mode
|
|
2599
|
+
When the user no longer needs current-data research, suggest switching back
|
|
2600
|
+
to the previous mode. You stay in research mode until explicitly told to
|
|
2601
|
+
switch \u2014 but don't force web searches on every turn. The methodology rules
|
|
2602
|
+
above already gate when to actually search.
|
|
2603
|
+
|
|
2604
|
+
When you're done with research: suggest the user run \`/mode default\` or
|
|
2605
|
+
their previous mode.`,
|
|
2606
|
+
tags: ["research", "web", "current-data", "up-to-date"],
|
|
2607
|
+
toolPreferences: ["web_search", "web_fetch", "search", "fetch", "context_manager"],
|
|
2608
|
+
suggestedSkills: ["research-web", "tech-stack", "node-modern", "security-scanner", "react-modern"]
|
|
2396
2609
|
}
|
|
2397
2610
|
];
|
|
2398
2611
|
|
|
@@ -2563,28 +2776,40 @@ var InMemoryAgentBridge = class {
|
|
|
2563
2776
|
return () => this.subscriptions.delete(handler);
|
|
2564
2777
|
}
|
|
2565
2778
|
async request(msg, timeoutMs) {
|
|
2566
|
-
if (this.stopped) throw new
|
|
2779
|
+
if (this.stopped) throw new AgentError({
|
|
2780
|
+
message: "Bridge is stopped",
|
|
2781
|
+
code: ERROR_CODES.AGENT_ABORTED
|
|
2782
|
+
});
|
|
2567
2783
|
const timeout = timeoutMs ?? this.timeoutMs;
|
|
2568
2784
|
const correlationId = msg.id;
|
|
2569
2785
|
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
|
-
|
|
2786
|
+
throw new AgentError({
|
|
2787
|
+
message: `Bridge request id "${correlationId}" collides with an in-flight request \u2014 caller is reusing message ids`,
|
|
2788
|
+
code: ERROR_CODES.AGENT_RUN_FAILED,
|
|
2789
|
+
context: { correlationId }
|
|
2790
|
+
});
|
|
2573
2791
|
}
|
|
2574
2792
|
this.inflightGuards.add(correlationId);
|
|
2575
|
-
return new Promise((
|
|
2793
|
+
return new Promise((resolve4, reject) => {
|
|
2576
2794
|
const timer = setTimeout(() => {
|
|
2577
2795
|
this.inflightGuards.delete(correlationId);
|
|
2578
2796
|
this.pendingRequests.delete(correlationId);
|
|
2579
|
-
reject(new
|
|
2797
|
+
reject(new AgentError({
|
|
2798
|
+
message: `Request ${correlationId} timed out after ${timeout}ms`,
|
|
2799
|
+
code: ERROR_CODES.AGENT_RUN_FAILED,
|
|
2800
|
+
context: { correlationId, timeoutMs: timeout }
|
|
2801
|
+
}));
|
|
2580
2802
|
}, timeout);
|
|
2581
2803
|
if (!this.inflightGuards.has(correlationId)) {
|
|
2582
2804
|
clearTimeout(timer);
|
|
2583
|
-
reject(new
|
|
2805
|
+
reject(new AgentError({
|
|
2806
|
+
message: "Bridge stopped",
|
|
2807
|
+
code: ERROR_CODES.AGENT_ABORTED
|
|
2808
|
+
}));
|
|
2584
2809
|
return;
|
|
2585
2810
|
}
|
|
2586
2811
|
this.pendingRequests.set(correlationId, {
|
|
2587
|
-
resolve:
|
|
2812
|
+
resolve: resolve4,
|
|
2588
2813
|
reject,
|
|
2589
2814
|
timer
|
|
2590
2815
|
});
|
|
@@ -2601,7 +2826,10 @@ var InMemoryAgentBridge = class {
|
|
|
2601
2826
|
this.stopped = true;
|
|
2602
2827
|
for (const [, p] of this.pendingRequests) {
|
|
2603
2828
|
clearTimeout(p.timer);
|
|
2604
|
-
p.reject(new
|
|
2829
|
+
p.reject(new AgentError({
|
|
2830
|
+
message: "Bridge stopped",
|
|
2831
|
+
code: ERROR_CODES.AGENT_ABORTED
|
|
2832
|
+
}));
|
|
2605
2833
|
}
|
|
2606
2834
|
this.pendingRequests.clear();
|
|
2607
2835
|
this.inflightGuards.clear();
|
|
@@ -2780,11 +3008,11 @@ function validateAgainstSchema(value, schema) {
|
|
|
2780
3008
|
walk2(value, schema, "", errors);
|
|
2781
3009
|
return { ok: errors.length === 0, errors };
|
|
2782
3010
|
}
|
|
2783
|
-
function walk2(value, schema,
|
|
3011
|
+
function walk2(value, schema, path7, errors) {
|
|
2784
3012
|
if (schema.enum !== void 0) {
|
|
2785
3013
|
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
2786
3014
|
errors.push({
|
|
2787
|
-
path:
|
|
3015
|
+
path: path7 || "<root>",
|
|
2788
3016
|
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
2789
3017
|
});
|
|
2790
3018
|
return;
|
|
@@ -2793,7 +3021,7 @@ function walk2(value, schema, path6, errors) {
|
|
|
2793
3021
|
if (typeof schema.type === "string") {
|
|
2794
3022
|
if (!checkType(value, schema.type)) {
|
|
2795
3023
|
errors.push({
|
|
2796
|
-
path:
|
|
3024
|
+
path: path7 || "<root>",
|
|
2797
3025
|
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
2798
3026
|
});
|
|
2799
3027
|
return;
|
|
@@ -2803,19 +3031,19 @@ function walk2(value, schema, path6, errors) {
|
|
|
2803
3031
|
const obj = value;
|
|
2804
3032
|
for (const req of schema.required ?? []) {
|
|
2805
3033
|
if (!(req in obj)) {
|
|
2806
|
-
errors.push({ path: joinPath(
|
|
3034
|
+
errors.push({ path: joinPath(path7, req), message: "required property missing" });
|
|
2807
3035
|
}
|
|
2808
3036
|
}
|
|
2809
3037
|
if (schema.properties) {
|
|
2810
3038
|
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
2811
3039
|
if (key in obj) {
|
|
2812
|
-
walk2(obj[key], subSchema, joinPath(
|
|
3040
|
+
walk2(obj[key], subSchema, joinPath(path7, key), errors);
|
|
2813
3041
|
}
|
|
2814
3042
|
}
|
|
2815
3043
|
}
|
|
2816
3044
|
}
|
|
2817
3045
|
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
2818
|
-
value.forEach((item, i) => walk2(item, schema.items, `${
|
|
3046
|
+
value.forEach((item, i) => walk2(item, schema.items, `${path7}[${i}]`, errors));
|
|
2819
3047
|
}
|
|
2820
3048
|
}
|
|
2821
3049
|
function checkType(value, type) {
|
|
@@ -2913,7 +3141,7 @@ function createToolOutputSerializer(opts = {}) {
|
|
|
2913
3141
|
}
|
|
2914
3142
|
|
|
2915
3143
|
// src/execution/tool-executor.ts
|
|
2916
|
-
var ToolExecutor = class {
|
|
3144
|
+
var ToolExecutor = class _ToolExecutor {
|
|
2917
3145
|
constructor(registry, opts) {
|
|
2918
3146
|
this.registry = registry;
|
|
2919
3147
|
this.opts = opts;
|
|
@@ -2925,6 +3153,10 @@ var ToolExecutor = class {
|
|
|
2925
3153
|
}
|
|
2926
3154
|
registry;
|
|
2927
3155
|
opts;
|
|
3156
|
+
/** Minimum gap between coalesced `partial_output` tool.progress emits. */
|
|
3157
|
+
static PROGRESS_EMIT_INTERVAL_MS = 100;
|
|
3158
|
+
/** Max chars of accumulated stream text carried per coalesced emit. */
|
|
3159
|
+
static PROGRESS_TAIL_CHARS = 16384;
|
|
2928
3160
|
serializer;
|
|
2929
3161
|
iterationTimeoutMs;
|
|
2930
3162
|
maxToolTimeoutMs;
|
|
@@ -2970,9 +3202,6 @@ Please call the tool again with arguments that match its inputSchema. You can us
|
|
|
2970
3202
|
return { result, tool, durationMs: Date.now() - start };
|
|
2971
3203
|
}
|
|
2972
3204
|
const toolDangerousCaps = getDangerousCapabilities(tool);
|
|
2973
|
-
if (toolDangerousCaps.length > 0) {
|
|
2974
|
-
if (this.opts.events) ;
|
|
2975
|
-
}
|
|
2976
3205
|
if (hasMalformedArguments(use.input)) {
|
|
2977
3206
|
const result = this.malformedInputResult(use, extractMalformedRaw(use.input));
|
|
2978
3207
|
budget = this.decrementBudget(result, budget);
|
|
@@ -3210,17 +3439,48 @@ ${post.additionalContext}` };
|
|
|
3210
3439
|
throw new Error(`Tool "${tool.name}" does not support streaming execution`);
|
|
3211
3440
|
}
|
|
3212
3441
|
const stream = tool.executeStream(input, ctx, { signal });
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
break;
|
|
3218
|
-
}
|
|
3442
|
+
const iter = stream[Symbol.asyncIterator]();
|
|
3443
|
+
let progressTail = "";
|
|
3444
|
+
let lastProgressEmitAt = 0;
|
|
3445
|
+
const emitProgress = (ev) => {
|
|
3219
3446
|
this.opts.events?.emit("tool.progress", {
|
|
3220
3447
|
name: tool.name,
|
|
3221
3448
|
id: toolUseId ?? "<unknown>",
|
|
3222
3449
|
event: ev
|
|
3223
3450
|
});
|
|
3451
|
+
};
|
|
3452
|
+
const flushProgressTail = (force) => {
|
|
3453
|
+
if (progressTail.length === 0) return;
|
|
3454
|
+
const now = Date.now();
|
|
3455
|
+
if (!force && now - lastProgressEmitAt < _ToolExecutor.PROGRESS_EMIT_INTERVAL_MS) return;
|
|
3456
|
+
const text = progressTail;
|
|
3457
|
+
progressTail = "";
|
|
3458
|
+
lastProgressEmitAt = now;
|
|
3459
|
+
emitProgress({ type: "partial_output", text });
|
|
3460
|
+
};
|
|
3461
|
+
try {
|
|
3462
|
+
while (true) {
|
|
3463
|
+
const { done, value: ev } = await iter.next();
|
|
3464
|
+
if (done) break;
|
|
3465
|
+
if (ev.type === "final") {
|
|
3466
|
+
finalOutput = ev.output;
|
|
3467
|
+
sawFinal = true;
|
|
3468
|
+
break;
|
|
3469
|
+
}
|
|
3470
|
+
if (ev.type === "partial_output" && typeof ev.text === "string") {
|
|
3471
|
+
progressTail += ev.text;
|
|
3472
|
+
if (progressTail.length > _ToolExecutor.PROGRESS_TAIL_CHARS) {
|
|
3473
|
+
progressTail = progressTail.slice(-_ToolExecutor.PROGRESS_TAIL_CHARS);
|
|
3474
|
+
}
|
|
3475
|
+
flushProgressTail(false);
|
|
3476
|
+
continue;
|
|
3477
|
+
}
|
|
3478
|
+
flushProgressTail(true);
|
|
3479
|
+
emitProgress(ev);
|
|
3480
|
+
}
|
|
3481
|
+
flushProgressTail(true);
|
|
3482
|
+
} finally {
|
|
3483
|
+
await iter.return?.(void 0);
|
|
3224
3484
|
}
|
|
3225
3485
|
if (!sawFinal) {
|
|
3226
3486
|
throw new Error(`tool "${tool.name}" executeStream completed without a 'final' event`);
|
|
@@ -3329,6 +3589,253 @@ function extractMalformedRaw(input) {
|
|
|
3329
3589
|
}
|
|
3330
3590
|
}
|
|
3331
3591
|
|
|
3592
|
+
// src/core/conversation-state.ts
|
|
3593
|
+
var ConversationState = class {
|
|
3594
|
+
ctx;
|
|
3595
|
+
listeners = /* @__PURE__ */ new Set();
|
|
3596
|
+
constructor(ctx) {
|
|
3597
|
+
this.ctx = ctx;
|
|
3598
|
+
}
|
|
3599
|
+
get messages() {
|
|
3600
|
+
return this.ctx.messages;
|
|
3601
|
+
}
|
|
3602
|
+
get todos() {
|
|
3603
|
+
return this.ctx.todos;
|
|
3604
|
+
}
|
|
3605
|
+
get meta() {
|
|
3606
|
+
return this.ctx.meta;
|
|
3607
|
+
}
|
|
3608
|
+
/**
|
|
3609
|
+
* Cheap immutable snapshot. Useful for tests and for compaction passes
|
|
3610
|
+
* that need a stable view across an async boundary.
|
|
3611
|
+
*
|
|
3612
|
+
* Uses shallow-freeze instead of deep-freeze: only the wrapper object
|
|
3613
|
+
* and the three content arrays are frozen. Individual message/todo
|
|
3614
|
+
* objects are NOT recursively frozen — they are reconstructed via
|
|
3615
|
+
* spread copies and are immutable by convention. This cuts the freeze
|
|
3616
|
+
* count from O(n·m·d) (n=messages, m=content blocks, d=depth) to O(1).
|
|
3617
|
+
*/
|
|
3618
|
+
snapshot() {
|
|
3619
|
+
const snap = {
|
|
3620
|
+
messages: [...this.ctx.messages],
|
|
3621
|
+
todos: [...this.ctx.todos],
|
|
3622
|
+
meta: { ...this.ctx.meta }
|
|
3623
|
+
};
|
|
3624
|
+
Object.freeze(snap.messages);
|
|
3625
|
+
Object.freeze(snap.todos);
|
|
3626
|
+
Object.freeze(snap.meta);
|
|
3627
|
+
return Object.freeze(snap);
|
|
3628
|
+
}
|
|
3629
|
+
appendMessage(message) {
|
|
3630
|
+
if (message._estTokens === void 0) {
|
|
3631
|
+
message._estTokens = computeMessageTokens(message);
|
|
3632
|
+
}
|
|
3633
|
+
this.ctx.messages.splice(this.ctx.messages.length, 0, message);
|
|
3634
|
+
this.emit({ kind: "message_appended", message });
|
|
3635
|
+
}
|
|
3636
|
+
replaceMessages(messages) {
|
|
3637
|
+
for (const m of messages) {
|
|
3638
|
+
if (m._estTokens === void 0) {
|
|
3639
|
+
m._estTokens = computeMessageTokens(m);
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
const arr = this.ctx.messages;
|
|
3643
|
+
if (messages.length < arr.length) {
|
|
3644
|
+
arr.length = messages.length;
|
|
3645
|
+
}
|
|
3646
|
+
for (let i = 0; i < messages.length; i++) {
|
|
3647
|
+
arr[i] = messages[i];
|
|
3648
|
+
}
|
|
3649
|
+
if (messages.some(
|
|
3650
|
+
(m) => Array.isArray(m.content) && m.content.some((b) => b.type === "tool_use" || b.type === "tool_result")
|
|
3651
|
+
)) {
|
|
3652
|
+
this.ctx.toolAdjacencyDirty = true;
|
|
3653
|
+
}
|
|
3654
|
+
this.emit({ kind: "messages_replaced", messages: [...messages] });
|
|
3655
|
+
}
|
|
3656
|
+
replaceTodos(todos) {
|
|
3657
|
+
const allDone = todos.length > 0 && todos.every((t) => t.status === "completed");
|
|
3658
|
+
const effective = allDone ? [] : todos;
|
|
3659
|
+
this.ctx.todos.length = 0;
|
|
3660
|
+
this.ctx.todos.splice(0, 0, ...effective);
|
|
3661
|
+
this.emit({ kind: "todos_replaced", todos: [...effective] });
|
|
3662
|
+
}
|
|
3663
|
+
setMeta(key, value) {
|
|
3664
|
+
this.ctx.meta[key] = value;
|
|
3665
|
+
this.emit({ kind: "meta_set", key, value });
|
|
3666
|
+
}
|
|
3667
|
+
deleteMeta(key) {
|
|
3668
|
+
if (!(key in this.ctx.meta)) return;
|
|
3669
|
+
delete this.ctx.meta[key];
|
|
3670
|
+
this.emit({ kind: "meta_deleted", key });
|
|
3671
|
+
}
|
|
3672
|
+
clearMeta() {
|
|
3673
|
+
const keys = Object.keys(this.ctx.meta);
|
|
3674
|
+
if (keys.length === 0) return;
|
|
3675
|
+
for (const key of keys) delete this.ctx.meta[key];
|
|
3676
|
+
this.emit({ kind: "meta_cleared" });
|
|
3677
|
+
}
|
|
3678
|
+
/**
|
|
3679
|
+
* Subscribe to mutations that go through this wrapper. Direct mutations of
|
|
3680
|
+
* the compatibility arrays are intentionally not observed.
|
|
3681
|
+
*/
|
|
3682
|
+
onChange(listener) {
|
|
3683
|
+
this.listeners.add(listener);
|
|
3684
|
+
return () => this.listeners.delete(listener);
|
|
3685
|
+
}
|
|
3686
|
+
emit(change) {
|
|
3687
|
+
for (const h of this.listeners) {
|
|
3688
|
+
try {
|
|
3689
|
+
h(change, this);
|
|
3690
|
+
} catch {
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
};
|
|
3695
|
+
|
|
3696
|
+
// src/core/context.ts
|
|
3697
|
+
var Context = class {
|
|
3698
|
+
messages = [];
|
|
3699
|
+
todos = [];
|
|
3700
|
+
readFiles = /* @__PURE__ */ new Set();
|
|
3701
|
+
fileMtimes = /* @__PURE__ */ new Map();
|
|
3702
|
+
systemPrompt;
|
|
3703
|
+
provider;
|
|
3704
|
+
session;
|
|
3705
|
+
signal;
|
|
3706
|
+
tokenCounter;
|
|
3707
|
+
cwd;
|
|
3708
|
+
projectRoot;
|
|
3709
|
+
/** Mutable working directory — starts as `cwd`. Change via `setWorkingDir()`. */
|
|
3710
|
+
workingDir;
|
|
3711
|
+
model;
|
|
3712
|
+
tools = [];
|
|
3713
|
+
meta = {};
|
|
3714
|
+
/** Agent id performing this run (e.g. 'leader', 'executor'). */
|
|
3715
|
+
agentId;
|
|
3716
|
+
/** Human-readable agent name. */
|
|
3717
|
+
agentName;
|
|
3718
|
+
/** Callbacks fired when `setWorkingDir()` changes the working directory. */
|
|
3719
|
+
_onWorkingDirChanged = [];
|
|
3720
|
+
/**
|
|
3721
|
+
* Set to true when the conversation gains new tool_use or tool_result
|
|
3722
|
+
* blocks — the only time repairToolUseAdjacency() can find new issues.
|
|
3723
|
+
* buildAndRunRequestPipeline() checks this flag to skip an O(n) scan
|
|
3724
|
+
* on iterations where no tool content was added (pure text responses).
|
|
3725
|
+
*/
|
|
3726
|
+
toolAdjacencyDirty = false;
|
|
3727
|
+
constructor(init) {
|
|
3728
|
+
this.systemPrompt = init.systemPrompt;
|
|
3729
|
+
this.provider = init.provider;
|
|
3730
|
+
this.session = init.session;
|
|
3731
|
+
this.signal = init.signal;
|
|
3732
|
+
this.tokenCounter = init.tokenCounter;
|
|
3733
|
+
this.cwd = init.cwd;
|
|
3734
|
+
this.projectRoot = init.projectRoot;
|
|
3735
|
+
this.workingDir = init.workingDir ?? init.cwd;
|
|
3736
|
+
this.model = init.model;
|
|
3737
|
+
this.tools = init.tools ?? [];
|
|
3738
|
+
this.agentId = init.agentId ?? "unknown";
|
|
3739
|
+
this.agentName = init.agentName ?? "Unknown Agent";
|
|
3740
|
+
}
|
|
3741
|
+
/**
|
|
3742
|
+
* Observable wrapper over the mutable conversation state. Lazy so
|
|
3743
|
+
* subsystems that don't subscribe pay nothing. Mutations made directly
|
|
3744
|
+
* on `ctx.messages` / `ctx.todos` are still visible through this
|
|
3745
|
+
* wrapper's read API (it holds a reference, not a copy) but only
|
|
3746
|
+
* mutations that go through `state.appendMessage()` etc. fire
|
|
3747
|
+
* `onChange`. New code should prefer the wrapper API.
|
|
3748
|
+
*/
|
|
3749
|
+
_state = null;
|
|
3750
|
+
get state() {
|
|
3751
|
+
if (!this._state) this._state = new ConversationState(this);
|
|
3752
|
+
return this._state;
|
|
3753
|
+
}
|
|
3754
|
+
/**
|
|
3755
|
+
* Register a teardown hook tied to the current run's abort signal.
|
|
3756
|
+
* Hooks registered before a run starts are stored and fired when the
|
|
3757
|
+
* next run ends; there is no immediate fire when no run is active.
|
|
3758
|
+
*
|
|
3759
|
+
* **Scope:** these hooks fire on the **whole agent run's** abort, not on
|
|
3760
|
+
* an individual tool call. For per-tool teardown of resources owned by
|
|
3761
|
+
* the tool author (child processes, handles), prefer `Tool.cleanup` —
|
|
3762
|
+
* see its JSDoc for the full rule.
|
|
3763
|
+
*/
|
|
3764
|
+
abortHooks = /* @__PURE__ */ new Set();
|
|
3765
|
+
registerAbortHook(fn) {
|
|
3766
|
+
this.abortHooks.add(fn);
|
|
3767
|
+
return () => this.abortHooks.delete(fn);
|
|
3768
|
+
}
|
|
3769
|
+
async drainAbortHooks() {
|
|
3770
|
+
const snapshot = [...this.abortHooks].reverse();
|
|
3771
|
+
this.abortHooks.clear();
|
|
3772
|
+
for (const fn of snapshot) {
|
|
3773
|
+
try {
|
|
3774
|
+
await fn();
|
|
3775
|
+
} catch {
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
recordRead(absPath, mtimeMs) {
|
|
3780
|
+
this.readFiles.add(absPath);
|
|
3781
|
+
this.fileMtimes.set(absPath, mtimeMs);
|
|
3782
|
+
}
|
|
3783
|
+
/** Clear accumulated file-read metadata after compaction or at boundaries
|
|
3784
|
+
* where stale read history could cause tools to skip legitimate re-reads.
|
|
3785
|
+
* The agent re-populates this naturally on the next file access. */
|
|
3786
|
+
clearFileTracking() {
|
|
3787
|
+
this.readFiles.clear();
|
|
3788
|
+
this.fileMtimes.clear();
|
|
3789
|
+
}
|
|
3790
|
+
hasRead(absPath) {
|
|
3791
|
+
return this.readFiles.has(absPath);
|
|
3792
|
+
}
|
|
3793
|
+
lastReadMtime(absPath) {
|
|
3794
|
+
return this.fileMtimes.get(absPath);
|
|
3795
|
+
}
|
|
3796
|
+
/**
|
|
3797
|
+
* Change the working directory for path resolution. Resolves relative paths
|
|
3798
|
+
* against `projectRoot` and validates the result is within the project root.
|
|
3799
|
+
* Fires all registered `onWorkingDirChanged` callbacks.
|
|
3800
|
+
* Returns the resolved absolute path.
|
|
3801
|
+
*/
|
|
3802
|
+
setWorkingDir(dir) {
|
|
3803
|
+
const resolved = path4.isAbsolute(dir) ? path4.resolve(dir) : path4.resolve(this.projectRoot, dir);
|
|
3804
|
+
const root = path4.resolve(this.projectRoot);
|
|
3805
|
+
const rel = path4.relative(root, resolved);
|
|
3806
|
+
if (rel.startsWith("..") || path4.isAbsolute(rel)) {
|
|
3807
|
+
throw new Error(
|
|
3808
|
+
`Working directory "${resolved}" is outside project root "${root}"`
|
|
3809
|
+
);
|
|
3810
|
+
}
|
|
3811
|
+
const old = this.workingDir;
|
|
3812
|
+
this.workingDir = resolved;
|
|
3813
|
+
for (const cb of this._onWorkingDirChanged) {
|
|
3814
|
+
try {
|
|
3815
|
+
cb(resolved, old);
|
|
3816
|
+
} catch {
|
|
3817
|
+
}
|
|
3818
|
+
}
|
|
3819
|
+
return resolved;
|
|
3820
|
+
}
|
|
3821
|
+
/**
|
|
3822
|
+
* Register a callback that fires when the working directory changes.
|
|
3823
|
+
* Returns an unsubscribe function. Callbacks are fired synchronously
|
|
3824
|
+
* inside `setWorkingDir()` — errors in callbacks are swallowed so one
|
|
3825
|
+
* bad listener doesn't prevent others from executing.
|
|
3826
|
+
*/
|
|
3827
|
+
onWorkingDirChanged(cb) {
|
|
3828
|
+
this._onWorkingDirChanged.push(cb);
|
|
3829
|
+
return () => {
|
|
3830
|
+
const idx = this._onWorkingDirChanged.indexOf(cb);
|
|
3831
|
+
if (idx >= 0) this._onWorkingDirChanged.splice(idx, 1);
|
|
3832
|
+
};
|
|
3833
|
+
}
|
|
3834
|
+
usage() {
|
|
3835
|
+
return this.tokenCounter.total();
|
|
3836
|
+
}
|
|
3837
|
+
};
|
|
3838
|
+
|
|
3332
3839
|
// src/utils/regex-guard.ts
|
|
3333
3840
|
var MAX_PATTERN_LEN = 512;
|
|
3334
3841
|
var DANGEROUS_PATTERNS = [
|
|
@@ -3641,6 +4148,6 @@ function renderPlainText(meta, events) {
|
|
|
3641
4148
|
return lines.join("\n");
|
|
3642
4149
|
}
|
|
3643
4150
|
|
|
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 };
|
|
4151
|
+
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
4152
|
//# sourceMappingURL=index.js.map
|
|
3646
4153
|
//# sourceMappingURL=index.js.map
|