@wrongstack/core 0.260.0 → 0.265.1
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-BbskZ7HH.d.ts → agent-bridge-DrkBxszZ.d.ts} +1 -1
- package/dist/{agent-subagent-runner-BNIGZx18.d.ts → agent-subagent-runner-DM2pP-B6.d.ts} +116 -12
- package/dist/{brain-C2yDd7Lw.d.ts → brain-BXd_61kQ.d.ts} +32 -3
- package/dist/{compactor-t0R_AIt_.d.ts → compactor-B8pOf45Y.d.ts} +1 -1
- package/dist/{config-FG6As4H5.d.ts → config-BMCj_XDs.d.ts} +86 -12
- package/dist/{context-JFOVvu6z.d.ts → context-MRk5PhNv.d.ts} +26 -1
- package/dist/coordination/index.d.ts +1737 -15
- package/dist/coordination/index.js +3152 -494
- package/dist/coordination/index.js.map +1 -1
- package/dist/{default-config-CXsDvOmP.d.ts → default-config-B0cj-Hry.d.ts} +11 -1
- package/dist/defaults/index.d.ts +28 -28
- package/dist/defaults/index.js +1804 -1363
- package/dist/defaults/index.js.map +1 -1
- package/dist/dispatcher-types.d-BBeXBQgS.d.ts +66 -0
- package/dist/execution/index.d.ts +16 -16
- package/dist/execution/index.js +933 -672
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +1 -1
- package/dist/execution/prompt-enhancer.js +7 -1
- package/dist/execution/prompt-enhancer.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/extension/index.js.map +1 -1
- package/dist/{goal-preamble-B1IXJtLX.d.ts → goal-preamble-DvHDSKSe.d.ts} +26 -10
- package/dist/{goal-store-CPXz6Mml.d.ts → goal-store-DtLMySNb.d.ts} +1 -1
- package/dist/{index-CebbJB94.d.ts → index-B-ch8K9C.d.ts} +8 -8
- package/dist/{index-BPcg4N3M.d.ts → index-CEDeNodM.d.ts} +5 -5
- package/dist/index.d.ts +189 -104
- package/dist/index.js +24693 -21162
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +12 -8
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js +7 -2
- package/dist/kernel/index.js.map +1 -1
- package/dist/{llm-selector-DXxI2tlu.d.ts → llm-selector-C0tfTCUe.d.ts} +14 -2
- package/dist/{mcp-servers-OwNHo43-.d.ts → mcp-servers-2x4w6Jn9.d.ts} +3 -3
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js +80 -31
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-Djlmq4uB.d.ts → models-registry-DmJlKuNp.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-CEmrSCMJ.d.ts → multi-agent-coordinator-DyCkCZnU.d.ts} +2 -2
- package/dist/{null-fleet-bus-DT92xqgJ.d.ts → null-fleet-bus-CG9QY2aP.d.ts} +6 -6
- package/dist/observability/index.d.ts +2 -2
- package/dist/observability/index.js +8 -3
- package/dist/observability/index.js.map +1 -1
- package/dist/{parallel-eternal-engine-0SItuq5r.d.ts → parallel-eternal-engine-Jw9uhEoT.d.ts} +9 -9
- package/dist/{path-resolver-DKBh6Jlo.d.ts → path-resolver-Dy2ej-gE.d.ts} +3 -3
- package/dist/{permission-BJ7eO9Vl.d.ts → permission-B9SB45lp.d.ts} +1 -1
- package/dist/{permission-policy-DEXOfnpm.d.ts → permission-policy-CkjSXabK.d.ts} +2 -2
- package/dist/{pipeline-zflkI2dp.d.ts → pipeline-DPDxH_7m.d.ts} +59 -4
- package/dist/{plan-templates-BFXyRkEK.d.ts → plan-templates-CzD9GnAU.d.ts} +32 -8
- package/dist/{provider-runner-BC-uywtT.d.ts → provider-runner-DMa70ODu.d.ts} +3 -3
- package/dist/{retry-policy-Cavrzmtk.d.ts → retry-policy-CN0khdlj.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +313 -122
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-vault-CDvDYXWX.d.ts → secret-vault-B2yw84VT.d.ts} +43 -4
- package/dist/secret-vault-BAKpgFw_.d.ts +57 -0
- package/dist/security/index.d.ts +5 -5
- package/dist/security/index.js +411 -225
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-B7AivHsu.d.ts → selector-CzHh_igB.d.ts} +1 -1
- package/dist/{session-event-bridge-BmIDxdJd.d.ts → session-event-bridge-BUI6Jf-4.d.ts} +8 -2
- package/dist/{session-reader-DtofsB-2.d.ts → session-reader-CMgdMSRP.d.ts} +1 -1
- package/dist/skills/index.js +67 -64
- package/dist/skills/index.js.map +1 -1
- package/dist/storage/index.d.ts +132 -16
- package/dist/storage/index.js +851 -432
- package/dist/storage/index.js.map +1 -1
- package/dist/tools/index.d.ts +57 -0
- package/dist/tools/index.js +411 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types/index.d.ts +21 -21
- package/dist/types/index.js +928 -711
- package/dist/types/index.js.map +1 -1
- package/dist/utils/error.d.ts +7 -0
- package/dist/utils/error.js +8 -0
- package/dist/utils/error.js.map +1 -0
- package/dist/utils/index.d.ts +8 -68
- package/dist/utils/index.js +20 -10
- package/dist/utils/index.js.map +1 -1
- package/dist/{wstack-paths-CJjEwPXn.d.ts → wstack-paths-hOpNLmvf.d.ts} +2 -0
- package/package.json +5 -1
- package/skills/api-design/SKILL.md +1 -1
- package/skills/audit-log/SKILL.md +6 -6
- package/skills/bug-hunter/SKILL.md +5 -5
- package/skills/chimera/SKILL.md +4 -4
- package/skills/docker-deploy/SKILL.md +1 -1
- package/skills/git-flow/SKILL.md +3 -3
- package/skills/multi-agent/SKILL.md +3 -3
- package/skills/node-modern/SKILL.md +1 -0
- package/skills/observability/SKILL.md +2 -2
- package/skills/output-standards/SKILL.md +51 -28
- package/skills/refactor-planner/SKILL.md +3 -3
- package/skills/security-scanner/SKILL.md +4 -3
- package/skills/tech-stack/SKILL.md +1 -2
- package/dist/package-outdated-watcher-C70ag2G9.d.ts +0 -581
- package/dist/secret-vault-BJDY28ev.d.ts +0 -25
package/dist/execution/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { execFile } from 'child_process';
|
|
2
|
-
import { promisify } from 'util';
|
|
3
|
-
import * as fs from 'fs/promises';
|
|
4
1
|
import { randomUUID, randomBytes, createHash } from 'crypto';
|
|
2
|
+
import * as fs from 'fs/promises';
|
|
5
3
|
import * as path2 from 'path';
|
|
6
4
|
import * as os from 'os';
|
|
5
|
+
import { execFile } from 'child_process';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
7
|
import { EventEmitter } from 'events';
|
|
8
8
|
|
|
9
9
|
// src/utils/token-estimate.ts
|
|
@@ -25,12 +25,9 @@ function getCachedEstimate(key, compute) {
|
|
|
25
25
|
const existing = ESTIMATE_CACHE.get(key);
|
|
26
26
|
if (existing !== void 0) return existing;
|
|
27
27
|
if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
|
|
28
|
-
let evicted = 0;
|
|
29
|
-
const maxEvict = Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4);
|
|
30
28
|
for (const k of ESTIMATE_CACHE.keys()) {
|
|
31
|
-
if (
|
|
29
|
+
if (ESTIMATE_CACHE.size <= Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) break;
|
|
32
30
|
ESTIMATE_CACHE.delete(k);
|
|
33
|
-
evicted++;
|
|
34
31
|
}
|
|
35
32
|
}
|
|
36
33
|
const estimate = compute(key);
|
|
@@ -261,7 +258,11 @@ function isTextBlock(b) {
|
|
|
261
258
|
}
|
|
262
259
|
|
|
263
260
|
// src/execution/compaction-core.ts
|
|
261
|
+
function compactionDebugEnabled() {
|
|
262
|
+
return process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1";
|
|
263
|
+
}
|
|
264
264
|
function emitCompactionMetrics(event, metrics) {
|
|
265
|
+
if (!compactionDebugEnabled()) return;
|
|
265
266
|
console.log(
|
|
266
267
|
JSON.stringify({
|
|
267
268
|
level: "debug",
|
|
@@ -316,18 +317,20 @@ function findPreserveStart(messages, preserveK) {
|
|
|
316
317
|
}
|
|
317
318
|
}
|
|
318
319
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
320
|
+
if (compactionDebugEnabled()) {
|
|
321
|
+
console.log(
|
|
322
|
+
JSON.stringify({
|
|
323
|
+
level: "debug",
|
|
324
|
+
event: "compaction.find_preserve_start.ended",
|
|
325
|
+
messageCount: messages.length,
|
|
326
|
+
preserveK,
|
|
327
|
+
preserveStart,
|
|
328
|
+
forwardWalkIterations,
|
|
329
|
+
forwardWalkInnerIterations,
|
|
330
|
+
forwardWalkInnerPerOuter: forwardWalkIterations > 0 ? forwardWalkInnerIterations / forwardWalkIterations : 0
|
|
331
|
+
})
|
|
332
|
+
);
|
|
333
|
+
}
|
|
331
334
|
return preserveStart;
|
|
332
335
|
}
|
|
333
336
|
function eliseOldToolResults(messages, opts) {
|
|
@@ -394,7 +397,7 @@ function eliseOldToolResults(messages, opts) {
|
|
|
394
397
|
changed = true;
|
|
395
398
|
}
|
|
396
399
|
fullPassInnerIterations += original.length;
|
|
397
|
-
if (
|
|
400
|
+
if (compactionDebugEnabled()) {
|
|
398
401
|
const ratio = fullPassInnerIterations / fullPassIterations;
|
|
399
402
|
if (ratio > 10) {
|
|
400
403
|
console.error(
|
|
@@ -801,7 +804,12 @@ var IntelligentCompactor = class {
|
|
|
801
804
|
};
|
|
802
805
|
const ac = ctx.signal ? void 0 : new AbortController();
|
|
803
806
|
const signal = ctx.signal ?? ac?.signal;
|
|
804
|
-
|
|
807
|
+
let res;
|
|
808
|
+
try {
|
|
809
|
+
res = await this.provider.complete(req, { signal });
|
|
810
|
+
} finally {
|
|
811
|
+
ac?.abort();
|
|
812
|
+
}
|
|
805
813
|
const textBlocks = res.content.filter(isTextBlock);
|
|
806
814
|
return textBlocks.map((b) => b.text).join("\n").trim() || "(empty summary)";
|
|
807
815
|
}
|
|
@@ -845,9 +853,9 @@ Rules:
|
|
|
845
853
|
- If unsure, keep rather than collapse (errors are more costly than waste)
|
|
846
854
|
|
|
847
855
|
Return ONLY the JSON object, no markdown, no explanation outside the JSON.`;
|
|
848
|
-
function formatMessages(messages,
|
|
856
|
+
function formatMessages(messages, maxTokens = 2048) {
|
|
849
857
|
const lines = [];
|
|
850
|
-
let
|
|
858
|
+
let usedTokens = 0;
|
|
851
859
|
for (let i = 0; i < messages.length; i++) {
|
|
852
860
|
const m = expectDefined(messages[i]);
|
|
853
861
|
const role = m.role.padEnd(10, " ");
|
|
@@ -859,13 +867,14 @@ function formatMessages(messages, maxChars = 8e3) {
|
|
|
859
867
|
text = content.filter(isTextBlock).map((b) => b.text).join(" ");
|
|
860
868
|
const toolUses = content.filter((b) => b.type === "tool_use");
|
|
861
869
|
if (toolUses.length > 0) {
|
|
862
|
-
text += ` [tools: ${toolUses.map((b) => b.name).join(", ")}]`;
|
|
870
|
+
text += ` [tools: ${toolUses.map((b) => b.name).filter(Boolean).join(", ")}]`;
|
|
863
871
|
}
|
|
864
872
|
}
|
|
865
873
|
const line = `[${i}][${role}]: ${text}`;
|
|
866
|
-
|
|
874
|
+
const lineTokens = estimateTextTokens(line);
|
|
875
|
+
if (usedTokens + lineTokens > maxTokens) break;
|
|
867
876
|
lines.push(line);
|
|
868
|
-
|
|
877
|
+
usedTokens += lineTokens;
|
|
869
878
|
}
|
|
870
879
|
return lines.join("\n");
|
|
871
880
|
}
|
|
@@ -874,20 +883,29 @@ var LLMSelector = class {
|
|
|
874
883
|
model;
|
|
875
884
|
maxContextTokens;
|
|
876
885
|
systemPrompt;
|
|
886
|
+
maxOutputTokens;
|
|
877
887
|
constructor(opts) {
|
|
878
888
|
this.provider = opts.provider;
|
|
879
889
|
this.model = opts.model ?? "unknown";
|
|
890
|
+
if (this.model === "unknown" && (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1")) {
|
|
891
|
+
console.warn(
|
|
892
|
+
"[LLMSelector] model not set \u2014 selector will use the provider default. Set `model` explicitly in LLMSelectorOptions to silence this warning."
|
|
893
|
+
);
|
|
894
|
+
}
|
|
880
895
|
this.maxContextTokens = opts.maxContextTokens ?? 4e4;
|
|
881
896
|
this.systemPrompt = opts.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
897
|
+
this.maxOutputTokens = opts.maxOutputTokens ?? 1024;
|
|
882
898
|
}
|
|
883
899
|
async select(messages, maxToKeep) {
|
|
884
900
|
const effectiveBudget = Math.min(maxToKeep, this.maxContextTokens);
|
|
885
|
-
const historyText = formatMessages(messages);
|
|
886
901
|
const totalTokens = estimateMessageTokens(messages);
|
|
887
902
|
const systemText = `${this.systemPrompt}
|
|
888
903
|
|
|
889
904
|
Conversation (${messages.length} messages, ~${totalTokens} tokens, budget: ${effectiveBudget}):
|
|
890
905
|
`;
|
|
906
|
+
const systemTokens = estimateTextTokens(systemText);
|
|
907
|
+
const historyBudget = Math.max(512, effectiveBudget - systemTokens - this.maxOutputTokens);
|
|
908
|
+
const historyText = formatMessages(messages, historyBudget);
|
|
891
909
|
const budgetInstruction = totalTokens > effectiveBudget ? `
|
|
892
910
|
|
|
893
911
|
IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiveBudget}). You MUST collapse enough to fit. Prefer collapsing older/lower-importance ranges.` : "";
|
|
@@ -895,18 +913,26 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
895
913
|
model: this.model,
|
|
896
914
|
system: [{ type: "text", text: systemText + budgetInstruction }],
|
|
897
915
|
messages: [{ role: "user", content: historyText }],
|
|
898
|
-
maxTokens:
|
|
916
|
+
maxTokens: this.maxOutputTokens
|
|
899
917
|
};
|
|
900
918
|
let raw;
|
|
919
|
+
const ac = new AbortController();
|
|
901
920
|
try {
|
|
902
|
-
const
|
|
903
|
-
const res = await this.provider.complete(req, {
|
|
921
|
+
const timeoutSignal = AbortSignal.timeout(3e4);
|
|
922
|
+
const res = await this.provider.complete(req, {
|
|
923
|
+
signal: AbortSignal.any([ac.signal, timeoutSignal])
|
|
924
|
+
});
|
|
904
925
|
const textBlocks = res.content.filter(isTextBlock);
|
|
905
926
|
raw = textBlocks.map((b) => b.text).join("\n").trim();
|
|
906
|
-
} catch (
|
|
927
|
+
} catch (err) {
|
|
928
|
+
if (err instanceof Error) {
|
|
929
|
+
console.warn("[LLMSelector] selector call failed, using recency fallback:", err.message);
|
|
930
|
+
}
|
|
907
931
|
return this.fallbackSelect(messages, effectiveBudget);
|
|
932
|
+
} finally {
|
|
933
|
+
ac.abort();
|
|
908
934
|
}
|
|
909
|
-
return this.parseSelectorOutput(raw, messages
|
|
935
|
+
return this.parseSelectorOutput(raw, messages);
|
|
910
936
|
}
|
|
911
937
|
fallbackSelect(messages, budget) {
|
|
912
938
|
const toKeep = [];
|
|
@@ -933,34 +959,63 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
933
959
|
reasoning: `Fallback: kept last ${messages.length - startIdx} messages within ${budget} token budget`
|
|
934
960
|
};
|
|
935
961
|
}
|
|
936
|
-
|
|
962
|
+
/**
|
|
963
|
+
* Parse and validate the raw LLM output into a SelectorResult.
|
|
964
|
+
* Falls back to recency-based selection if the LLM output is malformed,
|
|
965
|
+
* out-of-bounds, or internally inconsistent.
|
|
966
|
+
*/
|
|
967
|
+
parseSelectorOutput(raw, messages) {
|
|
968
|
+
const messageCount = messages.length;
|
|
969
|
+
if (messageCount === 0) {
|
|
970
|
+
return { kept: [], collapsed: [], reasoning: "empty session" };
|
|
971
|
+
}
|
|
937
972
|
const jsonStart = raw.indexOf("{");
|
|
938
973
|
const jsonEnd = raw.lastIndexOf("}");
|
|
939
974
|
if (jsonStart === -1 || jsonEnd === -1) {
|
|
940
|
-
return this.fallbackSelect(
|
|
941
|
-
Array.from({ length: messageCount }, () => ({ role: "user", content: "" })),
|
|
942
|
-
this.maxContextTokens
|
|
943
|
-
);
|
|
975
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
944
976
|
}
|
|
945
977
|
let parsed;
|
|
946
978
|
try {
|
|
947
979
|
parsed = JSON.parse(raw.slice(jsonStart, jsonEnd + 1));
|
|
948
980
|
} catch {
|
|
949
|
-
return this.fallbackSelect(
|
|
950
|
-
Array.from({ length: messageCount }, () => ({ role: "user", content: "" })),
|
|
951
|
-
this.maxContextTokens
|
|
952
|
-
);
|
|
981
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
953
982
|
}
|
|
954
983
|
const obj = parsed;
|
|
955
|
-
const
|
|
956
|
-
const
|
|
957
|
-
|
|
958
|
-
|
|
984
|
+
const keptRaw = obj.kept ?? [];
|
|
985
|
+
const collapsedRaw = obj.collapsed ?? [];
|
|
986
|
+
const kept = [];
|
|
987
|
+
for (const k of keptRaw) {
|
|
988
|
+
if (typeof k.from !== "number" || typeof k.to !== "number" || k.from < 0 || k.to >= messageCount || k.from > k.to) {
|
|
989
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
990
|
+
}
|
|
991
|
+
kept.push({
|
|
959
992
|
from: k.from,
|
|
960
993
|
to: k.to,
|
|
961
994
|
importance: k.importance ?? "medium"
|
|
962
|
-
})
|
|
963
|
-
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
const collapsed = [];
|
|
998
|
+
for (const c of collapsedRaw) {
|
|
999
|
+
if (typeof c.from !== "number" || typeof c.to !== "number" || c.from < 0 || c.to >= messageCount || c.from > c.to) {
|
|
1000
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
1001
|
+
}
|
|
1002
|
+
collapsed.push({ from: c.from, to: c.to, summary: c.summary });
|
|
1003
|
+
}
|
|
1004
|
+
const allRanges = [...kept, ...collapsed];
|
|
1005
|
+
for (let i = 0; i < allRanges.length; i++) {
|
|
1006
|
+
const a = allRanges[i];
|
|
1007
|
+
if (!a) continue;
|
|
1008
|
+
for (let j = i + 1; j < allRanges.length; j++) {
|
|
1009
|
+
const b = allRanges[j];
|
|
1010
|
+
if (!b) continue;
|
|
1011
|
+
if (a.from <= b.to && a.to >= b.from) {
|
|
1012
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return {
|
|
1017
|
+
kept,
|
|
1018
|
+
collapsed,
|
|
964
1019
|
reasoning: typeof obj.reasoning === "string" ? obj.reasoning : ""
|
|
965
1020
|
};
|
|
966
1021
|
}
|
|
@@ -980,7 +1035,7 @@ var SelectiveCompactor = class {
|
|
|
980
1035
|
summarizerPrompt;
|
|
981
1036
|
constructor(opts) {
|
|
982
1037
|
this.provider = opts.provider;
|
|
983
|
-
this.selector = opts.selector ?? new LLMSelector({ provider: opts.provider, model: opts.selectorModel });
|
|
1038
|
+
this.selector = opts.selector ?? new LLMSelector({ provider: opts.provider, model: opts.selectorModel, maxOutputTokens: opts.selectorMaxOutputTokens });
|
|
984
1039
|
this.warnThreshold = opts.warnThreshold ?? 0.6;
|
|
985
1040
|
this.softThreshold = opts.softThreshold ?? 0.75;
|
|
986
1041
|
this.hardThreshold = opts.hardThreshold ?? 0.9;
|
|
@@ -988,6 +1043,11 @@ var SelectiveCompactor = class {
|
|
|
988
1043
|
this.preserveK = opts.preserveK ?? 4;
|
|
989
1044
|
this.eliseThreshold = opts.eliseThreshold ?? 500;
|
|
990
1045
|
this.summarizerModel = opts.summarizerModel ?? opts.selectorModel ?? "unknown";
|
|
1046
|
+
if (this.summarizerModel === "unknown" && (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1")) {
|
|
1047
|
+
console.warn(
|
|
1048
|
+
"[SelectiveCompactor] summarizerModel not set \u2014 will use provider default. Set `summarizerModel` explicitly to silence this warning."
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
991
1051
|
this.summarizerPrompt = opts.summarizerPrompt ?? "You are a context summarizer. Given a list of messages, produce a concise summary that preserves all factual information, decisions, file changes, and state changes. Do not add commentary or opinions.";
|
|
992
1052
|
}
|
|
993
1053
|
async compact(ctx, opts = {}) {
|
|
@@ -1099,8 +1159,9 @@ Summarize the following message range:`;
|
|
|
1099
1159
|
maxTokens: 512
|
|
1100
1160
|
};
|
|
1101
1161
|
try {
|
|
1162
|
+
const timeoutSignal = AbortSignal.timeout(3e4);
|
|
1102
1163
|
const res = await this.provider.complete(req, {
|
|
1103
|
-
signal: ctx.signal
|
|
1164
|
+
signal: AbortSignal.any([ctx.signal, timeoutSignal])
|
|
1104
1165
|
});
|
|
1105
1166
|
return res.content.filter(isTextBlock).map((b) => b.text).join("\n").trim() || "(empty)";
|
|
1106
1167
|
} catch {
|
|
@@ -1234,6 +1295,7 @@ var ProviderBackedCompactor = class {
|
|
|
1234
1295
|
return new SelectiveCompactor({
|
|
1235
1296
|
...common,
|
|
1236
1297
|
selectorModel: this.opts.summarizerModel,
|
|
1298
|
+
selectorMaxOutputTokens: this.opts.selectorMaxOutputTokens,
|
|
1237
1299
|
summarizerModel: this.opts.summarizerModel
|
|
1238
1300
|
});
|
|
1239
1301
|
}
|
|
@@ -1250,168 +1312,503 @@ function readPolicy(ctx) {
|
|
|
1250
1312
|
if (typeof candidate.preserveK !== "number" || !candidate.thresholds) return null;
|
|
1251
1313
|
return candidate;
|
|
1252
1314
|
}
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
*/
|
|
1290
|
-
describe() {
|
|
1291
|
-
const ctx = this.context ? ` ${formatContext(this.context)}` : "";
|
|
1292
|
-
return `${this.code}: ${this.message}${ctx}`;
|
|
1315
|
+
async function atomicWrite(targetPath, content, opts = {}) {
|
|
1316
|
+
const dir = path2.dirname(targetPath);
|
|
1317
|
+
await fs.mkdir(dir, { recursive: true });
|
|
1318
|
+
const tmp = path2.join(dir, `.${path2.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
|
|
1319
|
+
try {
|
|
1320
|
+
if (typeof content === "string") {
|
|
1321
|
+
await fs.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
1322
|
+
} else {
|
|
1323
|
+
await fs.writeFile(tmp, content, { flag: "wx" });
|
|
1324
|
+
}
|
|
1325
|
+
try {
|
|
1326
|
+
const fh = await fs.open(tmp, "r+");
|
|
1327
|
+
try {
|
|
1328
|
+
await fh.sync();
|
|
1329
|
+
} finally {
|
|
1330
|
+
await fh.close();
|
|
1331
|
+
}
|
|
1332
|
+
} catch {
|
|
1333
|
+
}
|
|
1334
|
+
let mode;
|
|
1335
|
+
try {
|
|
1336
|
+
const stat2 = await fs.stat(targetPath);
|
|
1337
|
+
mode = stat2.mode & 511;
|
|
1338
|
+
} catch {
|
|
1339
|
+
mode = opts.mode;
|
|
1340
|
+
}
|
|
1341
|
+
if (mode !== void 0) {
|
|
1342
|
+
await fs.chmod(tmp, mode);
|
|
1343
|
+
}
|
|
1344
|
+
await renameWithRetry(tmp, targetPath);
|
|
1345
|
+
} catch (err) {
|
|
1346
|
+
try {
|
|
1347
|
+
await fs.unlink(tmp);
|
|
1348
|
+
} catch {
|
|
1349
|
+
}
|
|
1350
|
+
throw err;
|
|
1293
1351
|
}
|
|
1294
|
-
};
|
|
1295
|
-
function formatContext(ctx) {
|
|
1296
|
-
const parts = Object.entries(ctx).filter(([, v]) => v !== void 0).slice(0, 3).map(([k, v]) => `${k}=${String(v)}`);
|
|
1297
|
-
return parts.length > 0 ? `[${parts.join(" ")}]` : "";
|
|
1298
1352
|
}
|
|
1299
|
-
var
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
subsystem: "agent",
|
|
1305
|
-
severity: opts.code === ERROR_CODES.AGENT_ABORTED ? "warning" : "error",
|
|
1306
|
-
recoverable: opts.recoverable ?? opts.code === ERROR_CODES.AGENT_ITERATION_LIMIT,
|
|
1307
|
-
context: opts.context,
|
|
1308
|
-
cause: opts.cause
|
|
1309
|
-
});
|
|
1310
|
-
this.name = "AgentError";
|
|
1353
|
+
var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
|
|
1354
|
+
async function renameWithRetry(from, to) {
|
|
1355
|
+
if (process.platform !== "win32") {
|
|
1356
|
+
await fs.rename(from, to);
|
|
1357
|
+
return;
|
|
1311
1358
|
}
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
message: opts.message,
|
|
1327
|
-
code: opts.code,
|
|
1328
|
-
subsystem: "fs",
|
|
1329
|
-
severity: "error",
|
|
1330
|
-
recoverable: opts.code !== ERROR_CODES.FS_READ_FAILED,
|
|
1331
|
-
context: { path: opts.path, ...opts.context },
|
|
1332
|
-
cause: opts.cause
|
|
1333
|
-
});
|
|
1334
|
-
this.name = "FsError";
|
|
1335
|
-
this.path = opts.path;
|
|
1359
|
+
const delays = [10, 25, 60, 120, 250];
|
|
1360
|
+
let lastErr;
|
|
1361
|
+
for (let i = 0; i <= delays.length; i++) {
|
|
1362
|
+
try {
|
|
1363
|
+
await fs.rename(from, to);
|
|
1364
|
+
return;
|
|
1365
|
+
} catch (err) {
|
|
1366
|
+
lastErr = err;
|
|
1367
|
+
const code = err?.code;
|
|
1368
|
+
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
1369
|
+
throw err;
|
|
1370
|
+
}
|
|
1371
|
+
await new Promise((resolve2) => setTimeout(resolve2, delays[i]));
|
|
1372
|
+
}
|
|
1336
1373
|
}
|
|
1337
|
-
|
|
1374
|
+
throw lastErr;
|
|
1375
|
+
}
|
|
1338
1376
|
|
|
1339
|
-
// src/
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
function truncateDigest(digest) {
|
|
1343
|
-
if (digest.length <= MAX_DIGEST_LOG_CHARS) return digest;
|
|
1344
|
-
return `${digest.slice(0, MAX_DIGEST_LOG_CHARS)}\u2026 [+${digest.length - MAX_DIGEST_LOG_CHARS} chars; full turns in session log]`;
|
|
1377
|
+
// src/utils/error.ts
|
|
1378
|
+
function toErrorMessage(err) {
|
|
1379
|
+
return err instanceof Error ? err.message : String(err);
|
|
1345
1380
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1381
|
+
|
|
1382
|
+
// src/utils/string.ts
|
|
1383
|
+
function truncate(s, max) {
|
|
1384
|
+
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
1385
|
+
}
|
|
1386
|
+
function projectHash(absRoot) {
|
|
1387
|
+
return createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 12);
|
|
1388
|
+
}
|
|
1389
|
+
function projectSlug(absRoot) {
|
|
1390
|
+
const base = slugify(path2.basename(absRoot));
|
|
1391
|
+
const hash = createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 6);
|
|
1392
|
+
return `${base}-${hash}`;
|
|
1393
|
+
}
|
|
1394
|
+
function slugify(name) {
|
|
1395
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
|
|
1396
|
+
}
|
|
1397
|
+
function wstackGlobalRoot() {
|
|
1398
|
+
const fromEnv = process.env["WRONGSTACK_HOME"];
|
|
1399
|
+
if (fromEnv && fromEnv.trim().length > 0) return path2.resolve(fromEnv);
|
|
1400
|
+
return path2.join(os.homedir(), ".wrongstack");
|
|
1401
|
+
}
|
|
1402
|
+
function resolveWstackPaths(opts) {
|
|
1403
|
+
const globalRoot = opts.globalRoot ?? (opts.userHome ? path2.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
|
|
1404
|
+
const hash = projectHash(opts.projectRoot);
|
|
1405
|
+
const slug = projectSlug(opts.projectRoot);
|
|
1406
|
+
const projectDir = path2.join(globalRoot, "projects", slug);
|
|
1407
|
+
return {
|
|
1408
|
+
globalRoot,
|
|
1409
|
+
configDir: globalRoot,
|
|
1410
|
+
globalConfig: path2.join(globalRoot, "config.json"),
|
|
1411
|
+
secretsKey: path2.join(globalRoot, ".key"),
|
|
1412
|
+
globalMemory: path2.join(globalRoot, "memory.md"),
|
|
1413
|
+
globalSkills: path2.join(globalRoot, "skills"),
|
|
1414
|
+
globalPrompts: path2.join(globalRoot, "prompts"),
|
|
1415
|
+
cacheDir: path2.join(globalRoot, "cache"),
|
|
1416
|
+
modelsCache: path2.join(globalRoot, "cache", "models.dev.json"),
|
|
1417
|
+
modelsOverlayCache: path2.join(globalRoot, "cache", "models-overlay.json"),
|
|
1418
|
+
historyFile: path2.join(globalRoot, "history"),
|
|
1419
|
+
logFile: path2.join(globalRoot, "logs", "wrongstack.log"),
|
|
1420
|
+
projectDir,
|
|
1421
|
+
projectCodebaseIndex: path2.join(projectDir, "codebase-index"),
|
|
1422
|
+
projectMemory: path2.join(projectDir, "memory.md"),
|
|
1423
|
+
projectSessions: path2.join(projectDir, "sessions"),
|
|
1424
|
+
projectTrust: path2.join(projectDir, "trust.json"),
|
|
1425
|
+
projectMeta: path2.join(projectDir, "meta.json"),
|
|
1426
|
+
projectLocalConfig: path2.join(projectDir, "config.local.json"),
|
|
1427
|
+
inProjectConfig: path2.join(opts.projectRoot, ".wrongstack", "config.json"),
|
|
1428
|
+
inProjectAgentsFile: path2.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
|
|
1429
|
+
inProjectSkills: path2.join(opts.projectRoot, ".wrongstack", "skills"),
|
|
1430
|
+
inProjectWorktrees: path2.join(opts.projectRoot, ".wrongstack", "worktrees"),
|
|
1431
|
+
projectHash: hash,
|
|
1432
|
+
projectSlug: slug,
|
|
1433
|
+
projectGoal: path2.join(projectDir, "goal.json"),
|
|
1434
|
+
projectSpecs: path2.join(projectDir, "specs"),
|
|
1435
|
+
projectTaskGraphs: path2.join(projectDir, "task-graphs"),
|
|
1436
|
+
projectSddSession: path2.join(projectDir, "sdd-session.json"),
|
|
1437
|
+
projectPlan: path2.join(projectDir, "plan.json"),
|
|
1438
|
+
projectAutophase: path2.join(projectDir, "autophase"),
|
|
1439
|
+
syncConfig: path2.join(globalRoot, "sync.json"),
|
|
1440
|
+
projectStatus: (projectHash2) => path2.join(globalRoot, "projects", projectHash2, "status.json")
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
// src/utils/sleep.ts
|
|
1445
|
+
function sleep(ms) {
|
|
1446
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// src/utils/assert-never.ts
|
|
1450
|
+
function assertNever(x, message) {
|
|
1451
|
+
const err = new Error(
|
|
1452
|
+
`Unhandled case: ${JSON.stringify(x)}`
|
|
1453
|
+
);
|
|
1454
|
+
err.name = "AssertNeverError";
|
|
1455
|
+
throw err;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// src/utils/tool-output-serializer.ts
|
|
1459
|
+
function createToolOutputSerializer(opts = {}) {
|
|
1460
|
+
const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
|
|
1461
|
+
function serialize(value) {
|
|
1462
|
+
if (typeof value === "string") return value;
|
|
1463
|
+
if (value === null || value === void 0) return "";
|
|
1464
|
+
if (typeof value === "object") {
|
|
1465
|
+
if (Array.isArray(value)) return value.map(serialize).join("\n");
|
|
1466
|
+
if ("text" in value) {
|
|
1467
|
+
const t = value.text;
|
|
1468
|
+
return typeof t === "string" ? t : JSON.stringify(value, null, 2);
|
|
1469
|
+
}
|
|
1470
|
+
try {
|
|
1471
|
+
return JSON.stringify(value, null, 2);
|
|
1472
|
+
} catch {
|
|
1473
|
+
return String(value);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
return String(value);
|
|
1477
|
+
}
|
|
1478
|
+
function enforceCap(text, remainingBudget) {
|
|
1479
|
+
if (remainingBudget <= 0) {
|
|
1480
|
+
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
1481
|
+
}
|
|
1482
|
+
const textBytes = Buffer.byteLength(text, "utf8");
|
|
1483
|
+
if (textBytes <= remainingBudget) {
|
|
1484
|
+
return { text, newBudget: remainingBudget - textBytes };
|
|
1485
|
+
}
|
|
1486
|
+
const marker = `
|
|
1487
|
+
\u2026[truncated ${textBytes - remainingBudget} bytes]\u2026
|
|
1488
|
+
`;
|
|
1489
|
+
const markerBytes = Buffer.byteLength(marker, "utf8");
|
|
1490
|
+
const available = remainingBudget - markerBytes;
|
|
1491
|
+
if (available <= 0) {
|
|
1492
|
+
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
1493
|
+
}
|
|
1494
|
+
const half = Math.floor(available / 2);
|
|
1495
|
+
const first = text.slice(0, half);
|
|
1496
|
+
const second = text.slice(text.length - half);
|
|
1497
|
+
return { text: `${first}${marker}${second}`, newBudget: 0 };
|
|
1498
|
+
}
|
|
1499
|
+
return { serialize, enforceCap, capBytes };
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// src/utils/json-schema-validate.ts
|
|
1503
|
+
function validateAgainstSchema(value, schema) {
|
|
1504
|
+
const errors = [];
|
|
1505
|
+
walk(value, schema, "", errors);
|
|
1506
|
+
return { ok: errors.length === 0, errors };
|
|
1507
|
+
}
|
|
1508
|
+
function walk(value, schema, path4, errors) {
|
|
1509
|
+
if (schema.enum !== void 0) {
|
|
1510
|
+
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
1511
|
+
errors.push({
|
|
1512
|
+
path: path4 || "<root>",
|
|
1513
|
+
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
1514
|
+
});
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
if (typeof schema.type === "string") {
|
|
1519
|
+
if (!checkType(value, schema.type)) {
|
|
1520
|
+
errors.push({
|
|
1521
|
+
path: path4 || "<root>",
|
|
1522
|
+
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
1523
|
+
});
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
if (schema.type === "object" && isPlainObject(value)) {
|
|
1528
|
+
const obj = value;
|
|
1529
|
+
for (const req of schema.required ?? []) {
|
|
1530
|
+
if (!(req in obj)) {
|
|
1531
|
+
errors.push({ path: joinPath(path4, req), message: "required property missing" });
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
if (schema.properties) {
|
|
1535
|
+
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
1536
|
+
if (key in obj) {
|
|
1537
|
+
walk(obj[key], subSchema, joinPath(path4, key), errors);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
1543
|
+
for (let i = 0; i < value.length; i++) {
|
|
1544
|
+
walk(value[i], schema.items, `${path4}[${i}]`, errors);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
function checkType(value, type) {
|
|
1549
|
+
switch (type) {
|
|
1550
|
+
case "string":
|
|
1551
|
+
return typeof value === "string";
|
|
1552
|
+
case "number":
|
|
1553
|
+
return typeof value === "number" && !Number.isNaN(value);
|
|
1554
|
+
case "integer":
|
|
1555
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
1556
|
+
case "boolean":
|
|
1557
|
+
return typeof value === "boolean";
|
|
1558
|
+
case "null":
|
|
1559
|
+
return value === null;
|
|
1560
|
+
case "array":
|
|
1561
|
+
return Array.isArray(value);
|
|
1562
|
+
case "object":
|
|
1563
|
+
return isPlainObject(value);
|
|
1564
|
+
default:
|
|
1565
|
+
return true;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
function isPlainObject(v) {
|
|
1569
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1570
|
+
}
|
|
1571
|
+
function describeType(v) {
|
|
1572
|
+
if (v === null) return "null";
|
|
1573
|
+
if (Array.isArray(v)) return "array";
|
|
1574
|
+
return typeof v;
|
|
1575
|
+
}
|
|
1576
|
+
function joinPath(parent, key) {
|
|
1577
|
+
if (!parent) return key;
|
|
1578
|
+
return `${parent}.${key}`;
|
|
1579
|
+
}
|
|
1580
|
+
function deepEqual(a, b) {
|
|
1581
|
+
if (a === b) return true;
|
|
1582
|
+
if (typeof a !== typeof b) return false;
|
|
1583
|
+
if (a === null || b === null) return a === b;
|
|
1584
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
1585
|
+
return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
|
|
1586
|
+
}
|
|
1587
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
1588
|
+
const ak = Object.keys(a);
|
|
1589
|
+
const bk = Object.keys(b);
|
|
1590
|
+
if (ak.length !== bk.length) return false;
|
|
1591
|
+
return ak.every(
|
|
1592
|
+
(k) => deepEqual(a[k], b[k])
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
return false;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// src/utils/regex-guard.ts
|
|
1599
|
+
var MAX_PATTERN_LEN = 512;
|
|
1600
|
+
var DANGEROUS_PATTERNS = [
|
|
1601
|
+
/(\([^)]*[+*][^)]*\))[+*]/,
|
|
1602
|
+
// (a+)+, (.*)+, etc
|
|
1603
|
+
/(\(\?:[^)]*[+*][^)]*\))[+*]/
|
|
1604
|
+
// same, with non-capturing group
|
|
1605
|
+
];
|
|
1606
|
+
function compileUserRegex(pattern, flags) {
|
|
1607
|
+
if (typeof pattern !== "string") {
|
|
1608
|
+
return { ok: false, reason: "pattern must be a string" };
|
|
1609
|
+
}
|
|
1610
|
+
if (pattern.length === 0) {
|
|
1611
|
+
return { ok: false, reason: "pattern is empty" };
|
|
1612
|
+
}
|
|
1613
|
+
if (pattern.length > MAX_PATTERN_LEN) {
|
|
1614
|
+
return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
|
|
1615
|
+
}
|
|
1616
|
+
for (const rx of DANGEROUS_PATTERNS) {
|
|
1617
|
+
if (rx.test(pattern)) {
|
|
1618
|
+
return {
|
|
1619
|
+
ok: false,
|
|
1620
|
+
reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
try {
|
|
1625
|
+
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
1626
|
+
} catch (err) {
|
|
1627
|
+
return {
|
|
1628
|
+
ok: false,
|
|
1629
|
+
reason: err instanceof Error ? err.message : "invalid regex"
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
// src/types/errors.ts
|
|
1635
|
+
var ERROR_CODES = {
|
|
1636
|
+
// Provider
|
|
1637
|
+
PROVIDER_RATE_LIMITED: "PROVIDER_RATE_LIMITED",
|
|
1638
|
+
PROVIDER_AUTH_FAILED: "PROVIDER_AUTH_FAILED",
|
|
1639
|
+
PROVIDER_OVERLOADED: "PROVIDER_OVERLOADED",
|
|
1640
|
+
PROVIDER_INVALID_REQUEST: "PROVIDER_INVALID_REQUEST",
|
|
1641
|
+
PROVIDER_SERVER_ERROR: "PROVIDER_SERVER_ERROR",
|
|
1642
|
+
PROVIDER_NETWORK_ERROR: "PROVIDER_NETWORK_ERROR",
|
|
1643
|
+
// Agent
|
|
1644
|
+
AGENT_ITERATION_LIMIT: "AGENT_ITERATION_LIMIT",
|
|
1645
|
+
AGENT_CONTEXT_OVERFLOW: "AGENT_CONTEXT_OVERFLOW",
|
|
1646
|
+
AGENT_ABORTED: "AGENT_ABORTED",
|
|
1647
|
+
AGENT_RUN_FAILED: "AGENT_RUN_FAILED",
|
|
1648
|
+
// File system
|
|
1649
|
+
FS_READ_FAILED: "FS_READ_FAILED",
|
|
1650
|
+
FS_ATOMIC_WRITE_FAILED: "FS_ATOMIC_WRITE_FAILED"};
|
|
1651
|
+
var WrongStackError = class extends Error {
|
|
1652
|
+
code;
|
|
1653
|
+
subsystem;
|
|
1654
|
+
severity;
|
|
1655
|
+
recoverable;
|
|
1656
|
+
context;
|
|
1657
|
+
constructor(opts) {
|
|
1658
|
+
super(opts.message, { cause: opts.cause });
|
|
1659
|
+
this.name = "WrongStackError";
|
|
1660
|
+
this.code = opts.code;
|
|
1661
|
+
this.subsystem = opts.subsystem;
|
|
1662
|
+
this.severity = opts.severity ?? "error";
|
|
1663
|
+
this.recoverable = opts.recoverable ?? false;
|
|
1664
|
+
this.context = opts.context;
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Render a one-line user-facing description.
|
|
1668
|
+
* Subclasses should override for domain-specific formatting.
|
|
1669
|
+
*/
|
|
1670
|
+
describe() {
|
|
1671
|
+
const ctx = this.context ? ` ${formatContext(this.context)}` : "";
|
|
1672
|
+
return `${this.code}: ${this.message}${ctx}`;
|
|
1673
|
+
}
|
|
1674
|
+
};
|
|
1675
|
+
function formatContext(ctx) {
|
|
1676
|
+
const parts = Object.entries(ctx).filter(([, v]) => v !== void 0).slice(0, 3).map(([k, v]) => `${k}=${String(v)}`);
|
|
1677
|
+
return parts.length > 0 ? `[${parts.join(" ")}]` : "";
|
|
1678
|
+
}
|
|
1679
|
+
var AgentError = class extends WrongStackError {
|
|
1680
|
+
constructor(opts) {
|
|
1681
|
+
super({
|
|
1682
|
+
message: opts.message,
|
|
1683
|
+
code: opts.code,
|
|
1684
|
+
subsystem: "agent",
|
|
1685
|
+
severity: opts.code === ERROR_CODES.AGENT_ABORTED ? "warning" : "error",
|
|
1686
|
+
recoverable: opts.recoverable ?? opts.code === ERROR_CODES.AGENT_ITERATION_LIMIT,
|
|
1687
|
+
context: opts.context,
|
|
1688
|
+
cause: opts.cause
|
|
1689
|
+
});
|
|
1690
|
+
this.name = "AgentError";
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
function toWrongStackError(err, code = ERROR_CODES.AGENT_RUN_FAILED) {
|
|
1694
|
+
if (err instanceof WrongStackError) return err;
|
|
1695
|
+
const message = toErrorMessage(err);
|
|
1696
|
+
return new AgentError({
|
|
1697
|
+
message,
|
|
1698
|
+
code: code === "UNKNOWN" ? ERROR_CODES.AGENT_RUN_FAILED : code,
|
|
1699
|
+
cause: err
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
var FsError = class extends WrongStackError {
|
|
1703
|
+
path;
|
|
1704
|
+
constructor(opts) {
|
|
1705
|
+
super({
|
|
1706
|
+
message: opts.message,
|
|
1707
|
+
code: opts.code,
|
|
1708
|
+
subsystem: "fs",
|
|
1709
|
+
severity: "error",
|
|
1710
|
+
recoverable: opts.code !== ERROR_CODES.FS_READ_FAILED,
|
|
1711
|
+
context: { path: opts.path, ...opts.context },
|
|
1712
|
+
cause: opts.cause
|
|
1713
|
+
});
|
|
1714
|
+
this.name = "FsError";
|
|
1715
|
+
this.path = opts.path;
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
|
|
1719
|
+
// src/execution/auto-compaction-middleware.ts
|
|
1720
|
+
var LEVEL_RANK = { warn: 0, soft: 1, hard: 2 };
|
|
1721
|
+
var MAX_DIGEST_LOG_CHARS = 4e3;
|
|
1722
|
+
function truncateDigest(digest) {
|
|
1723
|
+
if (digest.length <= MAX_DIGEST_LOG_CHARS) return digest;
|
|
1724
|
+
return `${digest.slice(0, MAX_DIGEST_LOG_CHARS)}\u2026 [+${digest.length - MAX_DIGEST_LOG_CHARS} chars; full turns in session log]`;
|
|
1725
|
+
}
|
|
1726
|
+
var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
1727
|
+
name = "AutoCompaction";
|
|
1728
|
+
compactor;
|
|
1729
|
+
/** Deprecated. Kept for backward compat with tests that pass simpleEstimator. */
|
|
1730
|
+
_estimator;
|
|
1731
|
+
warnThreshold;
|
|
1732
|
+
softThreshold;
|
|
1733
|
+
hardThreshold;
|
|
1734
|
+
/** Writable so model-switch can update the denominator without re-registering the middleware. */
|
|
1735
|
+
_maxContext;
|
|
1736
|
+
/**
|
|
1737
|
+
* Runtime on/off gate. The middleware is always installed in the pipeline so
|
|
1738
|
+
* auto-compaction can be toggled live from the TUI `/settings` picker; when
|
|
1739
|
+
* disabled the handler is a pass-through. Defaults to enabled.
|
|
1740
|
+
*/
|
|
1741
|
+
_enabled = true;
|
|
1742
|
+
aggressiveOn;
|
|
1743
|
+
events;
|
|
1744
|
+
failureMode;
|
|
1745
|
+
policyProvider;
|
|
1746
|
+
sessionBridge;
|
|
1747
|
+
/**
|
|
1748
|
+
* Once a compaction attempt reduces nothing (preserveK protects everything,
|
|
1749
|
+
* no oversized tool_results remain to elide), retrying on every iteration
|
|
1750
|
+
* just spams `compaction.fired` events without making progress. We remember
|
|
1751
|
+
* the no-op and skip until either the pressure level escalates or context
|
|
1752
|
+
* has grown by at least this many tokens since the failed attempt.
|
|
1753
|
+
*/
|
|
1754
|
+
static NOOP_RETRY_DELTA_TOKENS = 2e3;
|
|
1755
|
+
/** Tracks the most recent no-op attempt so we can avoid re-firing per turn. */
|
|
1756
|
+
lastNoopAttempt = null;
|
|
1757
|
+
/**
|
|
1758
|
+
* Cached token estimate from the last handler() invocation. When the
|
|
1759
|
+
* message count and tool count haven't changed since the last estimate
|
|
1760
|
+
* (autonomous idle loops), we skip the expensive O(n) token estimation
|
|
1761
|
+
* and reuse this value. Reset to -1 when the context changes.
|
|
1762
|
+
*/
|
|
1763
|
+
_cachedTokens = -1;
|
|
1764
|
+
_cachedMsgCount = -1;
|
|
1765
|
+
_cachedToolCount = -1;
|
|
1766
|
+
/**
|
|
1767
|
+
* @param compactor Compactor to use for compaction.
|
|
1768
|
+
* @param maxContext Provider's max context window in tokens.
|
|
1769
|
+
* @param _estimator Deprecated parameter kept for backward compatibility.
|
|
1770
|
+
* The middleware now uses `estimateRequestTokens` internally
|
|
1771
|
+
* for accurate full-request token counting (messages +
|
|
1772
|
+
* systemPrompt + toolDefs).
|
|
1773
|
+
* @param thresholds Threshold fractions (0-1) of maxContext.
|
|
1774
|
+
* @param opts Optional behavior. By default, failures at the
|
|
1775
|
+
* hard threshold throw AGENT_CONTEXT_OVERFLOW so
|
|
1776
|
+
* the agent does not continue into a likely
|
|
1777
|
+
* provider context overflow. Warn/soft failures
|
|
1778
|
+
* still emit compaction.failed and continue.
|
|
1779
|
+
*/
|
|
1780
|
+
constructor(compactor, maxContext, _estimator, thresholds, optsOrAggressiveOn = {}, events) {
|
|
1781
|
+
const opts = typeof optsOrAggressiveOn === "string" ? { aggressiveOn: optsOrAggressiveOn, events } : optsOrAggressiveOn;
|
|
1782
|
+
this.compactor = compactor;
|
|
1783
|
+
this._maxContext = maxContext;
|
|
1784
|
+
this._estimator = _estimator;
|
|
1785
|
+
this.warnThreshold = thresholds.warn;
|
|
1786
|
+
this.softThreshold = thresholds.soft;
|
|
1787
|
+
this.hardThreshold = thresholds.hard;
|
|
1788
|
+
this.aggressiveOn = opts.aggressiveOn ?? "soft";
|
|
1789
|
+
this.events = opts.events;
|
|
1790
|
+
this.failureMode = opts.failureMode ?? "throw_on_hard";
|
|
1791
|
+
this.policyProvider = opts.policyProvider;
|
|
1792
|
+
this.sessionBridge = opts.sessionBridge;
|
|
1793
|
+
}
|
|
1794
|
+
/** Allow callers (e.g. model-switch in WebUI) to update the context window
|
|
1795
|
+
* denominator when the active model changes. */
|
|
1796
|
+
setMaxContext(maxContext) {
|
|
1797
|
+
this._maxContext = maxContext;
|
|
1798
|
+
}
|
|
1799
|
+
/** Whether auto-compaction is currently active. */
|
|
1800
|
+
get enabled() {
|
|
1801
|
+
return this._enabled;
|
|
1802
|
+
}
|
|
1803
|
+
/** Toggle auto-compaction on a live session (TUI `/settings`). When disabled
|
|
1804
|
+
* the middleware passes every iteration straight through without estimating
|
|
1805
|
+
* tokens or compacting. */
|
|
1806
|
+
setEnabled(enabled) {
|
|
1807
|
+
this._enabled = enabled;
|
|
1808
|
+
}
|
|
1809
|
+
handler() {
|
|
1414
1810
|
return async (ctx, next) => {
|
|
1811
|
+
if (!this._enabled) return next(ctx);
|
|
1415
1812
|
const msgCount = ctx.messages.length;
|
|
1416
1813
|
const toolCount = (ctx.tools ?? []).length;
|
|
1417
1814
|
let tokens;
|
|
@@ -1597,144 +1994,6 @@ function getDangerousCapabilities(toolOrCaps) {
|
|
|
1597
1994
|
);
|
|
1598
1995
|
}
|
|
1599
1996
|
|
|
1600
|
-
// src/utils/json-schema-validate.ts
|
|
1601
|
-
function validateAgainstSchema(value, schema) {
|
|
1602
|
-
const errors = [];
|
|
1603
|
-
walk(value, schema, "", errors);
|
|
1604
|
-
return { ok: errors.length === 0, errors };
|
|
1605
|
-
}
|
|
1606
|
-
function walk(value, schema, path4, errors) {
|
|
1607
|
-
if (schema.enum !== void 0) {
|
|
1608
|
-
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
1609
|
-
errors.push({
|
|
1610
|
-
path: path4 || "<root>",
|
|
1611
|
-
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
1612
|
-
});
|
|
1613
|
-
return;
|
|
1614
|
-
}
|
|
1615
|
-
}
|
|
1616
|
-
if (typeof schema.type === "string") {
|
|
1617
|
-
if (!checkType(value, schema.type)) {
|
|
1618
|
-
errors.push({
|
|
1619
|
-
path: path4 || "<root>",
|
|
1620
|
-
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
1621
|
-
});
|
|
1622
|
-
return;
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
if (schema.type === "object" && isPlainObject(value)) {
|
|
1626
|
-
const obj = value;
|
|
1627
|
-
for (const req of schema.required ?? []) {
|
|
1628
|
-
if (!(req in obj)) {
|
|
1629
|
-
errors.push({ path: joinPath(path4, req), message: "required property missing" });
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
if (schema.properties) {
|
|
1633
|
-
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
1634
|
-
if (key in obj) {
|
|
1635
|
-
walk(obj[key], subSchema, joinPath(path4, key), errors);
|
|
1636
|
-
}
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
}
|
|
1640
|
-
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
1641
|
-
value.forEach((item, i) => walk(item, schema.items, `${path4}[${i}]`, errors));
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
function checkType(value, type) {
|
|
1645
|
-
switch (type) {
|
|
1646
|
-
case "string":
|
|
1647
|
-
return typeof value === "string";
|
|
1648
|
-
case "number":
|
|
1649
|
-
return typeof value === "number" && !Number.isNaN(value);
|
|
1650
|
-
case "integer":
|
|
1651
|
-
return typeof value === "number" && Number.isInteger(value);
|
|
1652
|
-
case "boolean":
|
|
1653
|
-
return typeof value === "boolean";
|
|
1654
|
-
case "null":
|
|
1655
|
-
return value === null;
|
|
1656
|
-
case "array":
|
|
1657
|
-
return Array.isArray(value);
|
|
1658
|
-
case "object":
|
|
1659
|
-
return isPlainObject(value);
|
|
1660
|
-
default:
|
|
1661
|
-
return true;
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
function isPlainObject(v) {
|
|
1665
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1666
|
-
}
|
|
1667
|
-
function describeType(v) {
|
|
1668
|
-
if (v === null) return "null";
|
|
1669
|
-
if (Array.isArray(v)) return "array";
|
|
1670
|
-
return typeof v;
|
|
1671
|
-
}
|
|
1672
|
-
function joinPath(parent, key) {
|
|
1673
|
-
if (!parent) return key;
|
|
1674
|
-
return `${parent}.${key}`;
|
|
1675
|
-
}
|
|
1676
|
-
function deepEqual(a, b) {
|
|
1677
|
-
if (a === b) return true;
|
|
1678
|
-
if (typeof a !== typeof b) return false;
|
|
1679
|
-
if (a === null || b === null) return a === b;
|
|
1680
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
1681
|
-
return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
|
|
1682
|
-
}
|
|
1683
|
-
if (typeof a === "object" && typeof b === "object") {
|
|
1684
|
-
const ak = Object.keys(a);
|
|
1685
|
-
const bk = Object.keys(b);
|
|
1686
|
-
if (ak.length !== bk.length) return false;
|
|
1687
|
-
return ak.every(
|
|
1688
|
-
(k) => deepEqual(a[k], b[k])
|
|
1689
|
-
);
|
|
1690
|
-
}
|
|
1691
|
-
return false;
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
// src/utils/tool-output-serializer.ts
|
|
1695
|
-
function createToolOutputSerializer(opts = {}) {
|
|
1696
|
-
const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
|
|
1697
|
-
function serialize(value) {
|
|
1698
|
-
if (typeof value === "string") return value;
|
|
1699
|
-
if (value === null || value === void 0) return "";
|
|
1700
|
-
if (typeof value === "object") {
|
|
1701
|
-
if (Array.isArray(value)) return value.map(serialize).join("\n");
|
|
1702
|
-
if ("text" in value) {
|
|
1703
|
-
const t = value.text;
|
|
1704
|
-
return typeof t === "string" ? t : JSON.stringify(value, null, 2);
|
|
1705
|
-
}
|
|
1706
|
-
try {
|
|
1707
|
-
return JSON.stringify(value, null, 2);
|
|
1708
|
-
} catch {
|
|
1709
|
-
return String(value);
|
|
1710
|
-
}
|
|
1711
|
-
}
|
|
1712
|
-
return String(value);
|
|
1713
|
-
}
|
|
1714
|
-
function enforceCap(text, remainingBudget) {
|
|
1715
|
-
if (remainingBudget <= 0) {
|
|
1716
|
-
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
1717
|
-
}
|
|
1718
|
-
const textBytes = Buffer.byteLength(text, "utf8");
|
|
1719
|
-
if (textBytes <= remainingBudget) {
|
|
1720
|
-
return { text, newBudget: remainingBudget - textBytes };
|
|
1721
|
-
}
|
|
1722
|
-
const marker = `
|
|
1723
|
-
\u2026[truncated ${textBytes - remainingBudget} bytes]\u2026
|
|
1724
|
-
`;
|
|
1725
|
-
const markerBytes = Buffer.byteLength(marker, "utf8");
|
|
1726
|
-
const available = remainingBudget - markerBytes;
|
|
1727
|
-
if (available <= 0) {
|
|
1728
|
-
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
1729
|
-
}
|
|
1730
|
-
const half = Math.floor(available / 2);
|
|
1731
|
-
const first = text.slice(0, half);
|
|
1732
|
-
const second = text.slice(text.length - half);
|
|
1733
|
-
return { text: `${first}${marker}${second}`, newBudget: 0 };
|
|
1734
|
-
}
|
|
1735
|
-
return { serialize, enforceCap, capBytes };
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
1997
|
// src/execution/tool-executor.ts
|
|
1739
1998
|
var ToolExecutor = class _ToolExecutor {
|
|
1740
1999
|
constructor(registry, opts) {
|
|
@@ -1898,7 +2157,7 @@ ${post.additionalContext}`;
|
|
|
1898
2157
|
);
|
|
1899
2158
|
return { result, tool, durationMs: Date.now() - start };
|
|
1900
2159
|
} catch (err) {
|
|
1901
|
-
const msg =
|
|
2160
|
+
const msg = toErrorMessage(err);
|
|
1902
2161
|
const scrubbed = this.opts.secretScrubber.scrub(msg);
|
|
1903
2162
|
this.opts.renderer?.writeToolResult(tool.name, scrubbed, true);
|
|
1904
2163
|
const result = {
|
|
@@ -1919,7 +2178,7 @@ ${post.additionalContext}`;
|
|
|
1919
2178
|
try {
|
|
1920
2179
|
return await runOne(use);
|
|
1921
2180
|
} catch (err) {
|
|
1922
|
-
const msg =
|
|
2181
|
+
const msg = toErrorMessage(err);
|
|
1923
2182
|
const scrubbed = this.opts.secretScrubber.scrub(msg);
|
|
1924
2183
|
const result = {
|
|
1925
2184
|
type: "tool_result",
|
|
@@ -2161,89 +2420,44 @@ ${excerpt}`;
|
|
|
2161
2420
|
return isPathKey ? normalizePath(v) : escapeGlob(v);
|
|
2162
2421
|
}
|
|
2163
2422
|
}
|
|
2164
|
-
if (toolName === "bash" && typeof obj.command === "string") {
|
|
2165
|
-
return escapeGlob(obj.command);
|
|
2166
|
-
}
|
|
2167
|
-
if (typeof obj.path === "string") {
|
|
2168
|
-
return normalizePath(obj.path);
|
|
2169
|
-
}
|
|
2170
|
-
if (typeof obj.url === "string") {
|
|
2171
|
-
return escapeGlob(obj.url);
|
|
2172
|
-
}
|
|
2173
|
-
if (typeof obj.name === "string") {
|
|
2174
|
-
return escapeGlob(obj.name);
|
|
2175
|
-
}
|
|
2176
|
-
return void 0;
|
|
2177
|
-
}
|
|
2178
|
-
};
|
|
2179
|
-
function clampTimeoutMs(timeoutMs, maxTimeoutMs) {
|
|
2180
|
-
const fallback = 3e5;
|
|
2181
|
-
const finiteTimeout = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : fallback;
|
|
2182
|
-
const finiteMax = Number.isFinite(maxTimeoutMs) && maxTimeoutMs > 0 ? maxTimeoutMs : fallback;
|
|
2183
|
-
return Math.max(1, Math.min(finiteTimeout, finiteMax));
|
|
2184
|
-
}
|
|
2185
|
-
var MALFORMED_ARG_MARKERS = ["__raw", "__raw_arguments", "_raw"];
|
|
2186
|
-
function hasMalformedArguments(input) {
|
|
2187
|
-
if (!input || typeof input !== "object" || Array.isArray(input)) return false;
|
|
2188
|
-
const obj = input;
|
|
2189
|
-
const keys = Object.keys(obj);
|
|
2190
|
-
return keys.length === 1 && MALFORMED_ARG_MARKERS.includes(keys[0]);
|
|
2191
|
-
}
|
|
2192
|
-
function extractMalformedRaw(input) {
|
|
2193
|
-
if (!hasMalformedArguments(input)) return void 0;
|
|
2194
|
-
const obj = input;
|
|
2195
|
-
const value = obj[expectDefined(Object.keys(obj)[0])];
|
|
2196
|
-
if (value === void 0 || value === null) return void 0;
|
|
2197
|
-
if (typeof value === "string") return value;
|
|
2198
|
-
try {
|
|
2199
|
-
return JSON.stringify(value);
|
|
2200
|
-
} catch {
|
|
2201
|
-
return String(value);
|
|
2202
|
-
}
|
|
2203
|
-
}
|
|
2204
|
-
|
|
2205
|
-
// src/utils/assert-never.ts
|
|
2206
|
-
function assertNever(x, message) {
|
|
2207
|
-
const err = new Error(
|
|
2208
|
-
`Unhandled case: ${JSON.stringify(x)}`
|
|
2209
|
-
);
|
|
2210
|
-
err.name = "AssertNeverError";
|
|
2211
|
-
throw err;
|
|
2212
|
-
}
|
|
2213
|
-
|
|
2214
|
-
// src/utils/regex-guard.ts
|
|
2215
|
-
var MAX_PATTERN_LEN = 512;
|
|
2216
|
-
var DANGEROUS_PATTERNS = [
|
|
2217
|
-
/(\([^)]*[+*][^)]*\))[+*]/,
|
|
2218
|
-
// (a+)+, (.*)+, etc
|
|
2219
|
-
/(\(\?:[^)]*[+*][^)]*\))[+*]/
|
|
2220
|
-
// same, with non-capturing group
|
|
2221
|
-
];
|
|
2222
|
-
function compileUserRegex(pattern, flags) {
|
|
2223
|
-
if (typeof pattern !== "string") {
|
|
2224
|
-
return { ok: false, reason: "pattern must be a string" };
|
|
2225
|
-
}
|
|
2226
|
-
if (pattern.length === 0) {
|
|
2227
|
-
return { ok: false, reason: "pattern is empty" };
|
|
2228
|
-
}
|
|
2229
|
-
if (pattern.length > MAX_PATTERN_LEN) {
|
|
2230
|
-
return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
|
|
2231
|
-
}
|
|
2232
|
-
for (const rx of DANGEROUS_PATTERNS) {
|
|
2233
|
-
if (rx.test(pattern)) {
|
|
2234
|
-
return {
|
|
2235
|
-
ok: false,
|
|
2236
|
-
reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
|
|
2237
|
-
};
|
|
2238
|
-
}
|
|
2423
|
+
if (toolName === "bash" && typeof obj.command === "string") {
|
|
2424
|
+
return escapeGlob(obj.command);
|
|
2425
|
+
}
|
|
2426
|
+
if (typeof obj.path === "string") {
|
|
2427
|
+
return normalizePath(obj.path);
|
|
2428
|
+
}
|
|
2429
|
+
if (typeof obj.url === "string") {
|
|
2430
|
+
return escapeGlob(obj.url);
|
|
2431
|
+
}
|
|
2432
|
+
if (typeof obj.name === "string") {
|
|
2433
|
+
return escapeGlob(obj.name);
|
|
2434
|
+
}
|
|
2435
|
+
return void 0;
|
|
2239
2436
|
}
|
|
2437
|
+
};
|
|
2438
|
+
function clampTimeoutMs(timeoutMs, maxTimeoutMs) {
|
|
2439
|
+
const fallback = 3e5;
|
|
2440
|
+
const finiteTimeout = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : fallback;
|
|
2441
|
+
const finiteMax = Number.isFinite(maxTimeoutMs) && maxTimeoutMs > 0 ? maxTimeoutMs : fallback;
|
|
2442
|
+
return Math.max(1, Math.min(finiteTimeout, finiteMax));
|
|
2443
|
+
}
|
|
2444
|
+
var MALFORMED_ARG_MARKERS = ["__raw", "__raw_arguments", "_raw"];
|
|
2445
|
+
function hasMalformedArguments(input) {
|
|
2446
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return false;
|
|
2447
|
+
const obj = input;
|
|
2448
|
+
const keys = Object.keys(obj);
|
|
2449
|
+
return keys.length === 1 && MALFORMED_ARG_MARKERS.includes(keys[0]);
|
|
2450
|
+
}
|
|
2451
|
+
function extractMalformedRaw(input) {
|
|
2452
|
+
if (!hasMalformedArguments(input)) return void 0;
|
|
2453
|
+
const obj = input;
|
|
2454
|
+
const value = obj[expectDefined(Object.keys(obj)[0])];
|
|
2455
|
+
if (value === void 0 || value === null) return void 0;
|
|
2456
|
+
if (typeof value === "string") return value;
|
|
2240
2457
|
try {
|
|
2241
|
-
return
|
|
2242
|
-
} catch
|
|
2243
|
-
return
|
|
2244
|
-
ok: false,
|
|
2245
|
-
reason: err instanceof Error ? err.message : "invalid regex"
|
|
2246
|
-
};
|
|
2458
|
+
return JSON.stringify(value);
|
|
2459
|
+
} catch {
|
|
2460
|
+
return String(value);
|
|
2247
2461
|
}
|
|
2248
2462
|
}
|
|
2249
2463
|
|
|
@@ -2423,125 +2637,6 @@ var AutonomousRunner = class {
|
|
|
2423
2637
|
this.stopped = true;
|
|
2424
2638
|
}
|
|
2425
2639
|
};
|
|
2426
|
-
async function atomicWrite(targetPath, content, opts = {}) {
|
|
2427
|
-
const dir = path2.dirname(targetPath);
|
|
2428
|
-
await fs.mkdir(dir, { recursive: true });
|
|
2429
|
-
const tmp = path2.join(dir, `.${path2.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
|
|
2430
|
-
try {
|
|
2431
|
-
if (typeof content === "string") {
|
|
2432
|
-
await fs.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
2433
|
-
} else {
|
|
2434
|
-
await fs.writeFile(tmp, content, { flag: "wx" });
|
|
2435
|
-
}
|
|
2436
|
-
try {
|
|
2437
|
-
const fh = await fs.open(tmp, "r+");
|
|
2438
|
-
try {
|
|
2439
|
-
await fh.sync();
|
|
2440
|
-
} finally {
|
|
2441
|
-
await fh.close();
|
|
2442
|
-
}
|
|
2443
|
-
} catch {
|
|
2444
|
-
}
|
|
2445
|
-
let mode;
|
|
2446
|
-
try {
|
|
2447
|
-
const stat2 = await fs.stat(targetPath);
|
|
2448
|
-
mode = stat2.mode & 511;
|
|
2449
|
-
} catch {
|
|
2450
|
-
mode = opts.mode;
|
|
2451
|
-
}
|
|
2452
|
-
if (mode !== void 0) {
|
|
2453
|
-
await fs.chmod(tmp, mode);
|
|
2454
|
-
}
|
|
2455
|
-
await renameWithRetry(tmp, targetPath);
|
|
2456
|
-
} catch (err) {
|
|
2457
|
-
try {
|
|
2458
|
-
await fs.unlink(tmp);
|
|
2459
|
-
} catch {
|
|
2460
|
-
}
|
|
2461
|
-
throw err;
|
|
2462
|
-
}
|
|
2463
|
-
}
|
|
2464
|
-
var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
|
|
2465
|
-
async function renameWithRetry(from, to) {
|
|
2466
|
-
if (process.platform !== "win32") {
|
|
2467
|
-
await fs.rename(from, to);
|
|
2468
|
-
return;
|
|
2469
|
-
}
|
|
2470
|
-
const delays = [10, 25, 60, 120, 250];
|
|
2471
|
-
let lastErr;
|
|
2472
|
-
for (let i = 0; i <= delays.length; i++) {
|
|
2473
|
-
try {
|
|
2474
|
-
await fs.rename(from, to);
|
|
2475
|
-
return;
|
|
2476
|
-
} catch (err) {
|
|
2477
|
-
lastErr = err;
|
|
2478
|
-
const code = err?.code;
|
|
2479
|
-
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
2480
|
-
throw err;
|
|
2481
|
-
}
|
|
2482
|
-
await new Promise((resolve2) => setTimeout(resolve2, delays[i]));
|
|
2483
|
-
}
|
|
2484
|
-
}
|
|
2485
|
-
throw lastErr;
|
|
2486
|
-
}
|
|
2487
|
-
function projectHash(absRoot) {
|
|
2488
|
-
return createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 12);
|
|
2489
|
-
}
|
|
2490
|
-
function projectSlug(absRoot) {
|
|
2491
|
-
const base = slugify(path2.basename(absRoot));
|
|
2492
|
-
const hash = createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 6);
|
|
2493
|
-
return `${base}-${hash}`;
|
|
2494
|
-
}
|
|
2495
|
-
function slugify(name) {
|
|
2496
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
|
|
2497
|
-
}
|
|
2498
|
-
function wstackGlobalRoot() {
|
|
2499
|
-
const fromEnv = process.env["WRONGSTACK_HOME"];
|
|
2500
|
-
if (fromEnv && fromEnv.trim().length > 0) return path2.resolve(fromEnv);
|
|
2501
|
-
return path2.join(os.homedir(), ".wrongstack");
|
|
2502
|
-
}
|
|
2503
|
-
function resolveWstackPaths(opts) {
|
|
2504
|
-
const globalRoot = opts.globalRoot ?? (opts.userHome ? path2.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
|
|
2505
|
-
const hash = projectHash(opts.projectRoot);
|
|
2506
|
-
const slug = projectSlug(opts.projectRoot);
|
|
2507
|
-
const projectDir = path2.join(globalRoot, "projects", slug);
|
|
2508
|
-
return {
|
|
2509
|
-
globalRoot,
|
|
2510
|
-
configDir: globalRoot,
|
|
2511
|
-
globalConfig: path2.join(globalRoot, "config.json"),
|
|
2512
|
-
secretsKey: path2.join(globalRoot, ".key"),
|
|
2513
|
-
globalMemory: path2.join(globalRoot, "memory.md"),
|
|
2514
|
-
globalSkills: path2.join(globalRoot, "skills"),
|
|
2515
|
-
globalPrompts: path2.join(globalRoot, "prompts"),
|
|
2516
|
-
cacheDir: path2.join(globalRoot, "cache"),
|
|
2517
|
-
modelsCache: path2.join(globalRoot, "cache", "models.dev.json"),
|
|
2518
|
-
modelsOverlayCache: path2.join(globalRoot, "cache", "models-overlay.json"),
|
|
2519
|
-
historyFile: path2.join(globalRoot, "history"),
|
|
2520
|
-
logFile: path2.join(globalRoot, "logs", "wrongstack.log"),
|
|
2521
|
-
projectDir,
|
|
2522
|
-
projectCodebaseIndex: path2.join(projectDir, "codebase-index"),
|
|
2523
|
-
projectMemory: path2.join(projectDir, "memory.md"),
|
|
2524
|
-
projectSessions: path2.join(projectDir, "sessions"),
|
|
2525
|
-
projectTrust: path2.join(projectDir, "trust.json"),
|
|
2526
|
-
projectMeta: path2.join(projectDir, "meta.json"),
|
|
2527
|
-
projectLocalConfig: path2.join(projectDir, "config.local.json"),
|
|
2528
|
-
inProjectConfig: path2.join(opts.projectRoot, ".wrongstack", "config.json"),
|
|
2529
|
-
inProjectAgentsFile: path2.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
|
|
2530
|
-
inProjectSkills: path2.join(opts.projectRoot, ".wrongstack", "skills"),
|
|
2531
|
-
inProjectWorktrees: path2.join(opts.projectRoot, ".wrongstack", "worktrees"),
|
|
2532
|
-
projectHash: hash,
|
|
2533
|
-
projectSlug: slug,
|
|
2534
|
-
projectGoal: path2.join(projectDir, "goal.json"),
|
|
2535
|
-
projectSpecs: path2.join(projectDir, "specs"),
|
|
2536
|
-
projectTaskGraphs: path2.join(projectDir, "task-graphs"),
|
|
2537
|
-
projectSddSession: path2.join(projectDir, "sdd-session.json"),
|
|
2538
|
-
projectPlan: path2.join(projectDir, "plan.json"),
|
|
2539
|
-
projectAutophase: path2.join(projectDir, "autophase"),
|
|
2540
|
-
syncConfig: path2.join(globalRoot, "sync.json")
|
|
2541
|
-
};
|
|
2542
|
-
}
|
|
2543
|
-
|
|
2544
|
-
// src/storage/goal-store.ts
|
|
2545
2640
|
var MAX_JOURNAL_ENTRIES = 500;
|
|
2546
2641
|
function goalFilePath(projectRoot) {
|
|
2547
2642
|
return resolveWstackPaths({ projectRoot }).projectGoal;
|
|
@@ -2569,7 +2664,7 @@ async function loadGoal(filePath, events) {
|
|
|
2569
2664
|
store: "goal",
|
|
2570
2665
|
filePath,
|
|
2571
2666
|
operation: "load",
|
|
2572
|
-
error:
|
|
2667
|
+
error: toErrorMessage(err),
|
|
2573
2668
|
recoverable: false
|
|
2574
2669
|
});
|
|
2575
2670
|
throw err;
|
|
@@ -2642,11 +2737,11 @@ async function saveGoal(filePath, goal, events) {
|
|
|
2642
2737
|
store: "goal",
|
|
2643
2738
|
filePath,
|
|
2644
2739
|
operation: "save",
|
|
2645
|
-
error:
|
|
2740
|
+
error: toErrorMessage(err),
|
|
2646
2741
|
recoverable: false
|
|
2647
2742
|
});
|
|
2648
2743
|
throw new FsError({
|
|
2649
|
-
message:
|
|
2744
|
+
message: toErrorMessage(err),
|
|
2650
2745
|
code: ERROR_CODES.FS_ATOMIC_WRITE_FAILED,
|
|
2651
2746
|
path: filePath,
|
|
2652
2747
|
cause: err
|
|
@@ -2700,11 +2795,6 @@ function computeTrend(history) {
|
|
|
2700
2795
|
return "steady";
|
|
2701
2796
|
}
|
|
2702
2797
|
|
|
2703
|
-
// src/utils/sleep.ts
|
|
2704
|
-
function sleep(ms) {
|
|
2705
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2706
|
-
}
|
|
2707
|
-
|
|
2708
2798
|
// src/execution/autonomy-brain.ts
|
|
2709
2799
|
var RISK_LEVELS = {
|
|
2710
2800
|
low: 0,
|
|
@@ -2941,7 +3031,7 @@ var EternalAutonomyEngine = class {
|
|
|
2941
3031
|
console.error(JSON.stringify({
|
|
2942
3032
|
level: "error",
|
|
2943
3033
|
event: "engine.persist_state_failed",
|
|
2944
|
-
message:
|
|
3034
|
+
message: toErrorMessage(err),
|
|
2945
3035
|
context: { expectedState: "stopped" },
|
|
2946
3036
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2947
3037
|
}));
|
|
@@ -2974,7 +3064,7 @@ var EternalAutonomyEngine = class {
|
|
|
2974
3064
|
} catch (err) {
|
|
2975
3065
|
this.consecutiveFailures++;
|
|
2976
3066
|
this.opts.onError?.(err instanceof Error ? err : new Error(String(err)), this.consecutiveFailures);
|
|
2977
|
-
await this.appendFailure("engine error",
|
|
3067
|
+
await this.appendFailure("engine error", toErrorMessage(err));
|
|
2978
3068
|
}
|
|
2979
3069
|
if (iterationOk) {
|
|
2980
3070
|
this.consecutiveFailures = 0;
|
|
@@ -3075,7 +3165,7 @@ var EternalAutonomyEngine = class {
|
|
|
3075
3165
|
} catch (err) {
|
|
3076
3166
|
const isAbort = err instanceof Error && (err.name === "AbortError" || err.message.includes("abort"));
|
|
3077
3167
|
status = isAbort ? "aborted" : "failure";
|
|
3078
|
-
note =
|
|
3168
|
+
note = toErrorMessage(err);
|
|
3079
3169
|
if (!isAbort && typeof err?.recoverable === "boolean") {
|
|
3080
3170
|
isTransientFailure = err.recoverable;
|
|
3081
3171
|
}
|
|
@@ -3565,6 +3655,7 @@ ${recentJournal}` : ""
|
|
|
3565
3655
|
|
|
3566
3656
|
// src/coordination/subagent-budget.ts
|
|
3567
3657
|
var TIMEOUT_PREEMPT_FRACTION = 0.85;
|
|
3658
|
+
var DECISION_TIMEOUT_MS = 6e4;
|
|
3568
3659
|
var BudgetExceededError = class extends Error {
|
|
3569
3660
|
kind;
|
|
3570
3661
|
limit;
|
|
@@ -3594,6 +3685,31 @@ var BudgetThresholdSignal = class extends Error {
|
|
|
3594
3685
|
};
|
|
3595
3686
|
var SubagentBudget = class _SubagentBudget {
|
|
3596
3687
|
limits;
|
|
3688
|
+
/** Patch one or more budget limits in-place after construction.
|
|
3689
|
+
* Used by the coordinator watchdog when granting an extension.
|
|
3690
|
+
* All fields are optional — only provided fields are updated.
|
|
3691
|
+
* This is the single write path for limit mutations so that future
|
|
3692
|
+
* validation or side-effects live in one place (M1). */
|
|
3693
|
+
patchLimits(ext) {
|
|
3694
|
+
if (ext.maxIterations !== void 0) {
|
|
3695
|
+
this.limits.maxIterations = ext.maxIterations;
|
|
3696
|
+
}
|
|
3697
|
+
if (ext.maxToolCalls !== void 0) {
|
|
3698
|
+
this.limits.maxToolCalls = ext.maxToolCalls;
|
|
3699
|
+
}
|
|
3700
|
+
if (ext.maxTokens !== void 0) {
|
|
3701
|
+
this.limits.maxTokens = ext.maxTokens;
|
|
3702
|
+
}
|
|
3703
|
+
if (ext.maxCostUsd !== void 0) {
|
|
3704
|
+
this.limits.maxCostUsd = ext.maxCostUsd;
|
|
3705
|
+
}
|
|
3706
|
+
if (ext.timeoutMs !== void 0) {
|
|
3707
|
+
this.limits.timeoutMs = ext.timeoutMs;
|
|
3708
|
+
}
|
|
3709
|
+
if (ext.idleTimeoutMs !== void 0) {
|
|
3710
|
+
this.limits.idleTimeoutMs = ext.idleTimeoutMs;
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3597
3713
|
iterations = 0;
|
|
3598
3714
|
toolCalls = 0;
|
|
3599
3715
|
tokenInput = 0;
|
|
@@ -3614,12 +3730,44 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
3614
3730
|
* or hung listener (Director not built / event filter detached mid-run)
|
|
3615
3731
|
* leaves the budget over-limit and never enforces anything.
|
|
3616
3732
|
*/
|
|
3617
|
-
static DECISION_TIMEOUT_MS =
|
|
3733
|
+
static DECISION_TIMEOUT_MS = DECISION_TIMEOUT_MS;
|
|
3618
3734
|
/**
|
|
3619
3735
|
* Injected by the runner when wiring the budget to its EventBus.
|
|
3620
3736
|
* Used to emit `budget.threshold_reached` events in `'auto'` mode.
|
|
3621
3737
|
*/
|
|
3622
3738
|
_events;
|
|
3739
|
+
/**
|
|
3740
|
+
* Guard against dual-path races between the coordinator watchdog
|
|
3741
|
+
* (`executeWithTimeout`) and the budget's own `checkTimeout()`.
|
|
3742
|
+
* Both paths detect `elapsed >= timeoutMs` and can emit
|
|
3743
|
+
* `budget.threshold_reached` for kind `'timeout'` simultaneously.
|
|
3744
|
+
* Set to the current `timeoutMs` ceiling by the coordinator BEFORE
|
|
3745
|
+
* calling `onThreshold`, and cleared after the negotiation resolves.
|
|
3746
|
+
* `checkTimeout()` skips its wall-clock check while this is set so
|
|
3747
|
+
* the coordinator's watchdog is the sole source of wall-clock timeout
|
|
3748
|
+
* events — `checkTimeout()` focuses exclusively on `idle_timeout`.
|
|
3749
|
+
*/
|
|
3750
|
+
_watchdogActive;
|
|
3751
|
+
/** Returns the timeout ceiling currently being negotiated by the watchdog,
|
|
3752
|
+
* or `undefined` when no wall-clock negotiation is in flight.
|
|
3753
|
+
* Used by `executeWithTimeout` to detect a stale lock (M3). */
|
|
3754
|
+
get watchdogActive() {
|
|
3755
|
+
return this._watchdogActive;
|
|
3756
|
+
}
|
|
3757
|
+
/** Called by the coordinator watchdog BEFORE calling `onThreshold` so that
|
|
3758
|
+
* `checkTimeout()` skips its wall-clock check for this ceiling. Prevents
|
|
3759
|
+
* the budget's own `checkTimeout()` from emitting a second
|
|
3760
|
+
* `budget.threshold_reached` event while the watchdog is already
|
|
3761
|
+
* negotiating the same wall-clock deadline (C1). */
|
|
3762
|
+
setWatchdogNegotiation(timeoutMs) {
|
|
3763
|
+
this._watchdogActive = timeoutMs;
|
|
3764
|
+
}
|
|
3765
|
+
/** Clears the watchdog guard after negotiation resolves. Called in the
|
|
3766
|
+
* `finally` block of both the pre-empt and deadline branches so it fires
|
|
3767
|
+
* on every exit path: grant, deny, throw, or error. */
|
|
3768
|
+
clearWatchdogNegotiation() {
|
|
3769
|
+
this._watchdogActive = void 0;
|
|
3770
|
+
}
|
|
3623
3771
|
/**
|
|
3624
3772
|
* Negotiation mode — controls whether a threshold hit tries to emit
|
|
3625
3773
|
* `budget.threshold_reached` and wait for a coordinator decision, or
|
|
@@ -3720,7 +3868,8 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
3720
3868
|
if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
|
|
3721
3869
|
exceeded.push({ kind: "idle_timeout", used: idle, limit: this.limits.idleTimeoutMs });
|
|
3722
3870
|
}
|
|
3723
|
-
|
|
3871
|
+
const wallOwnedByWatchdog = this._onThreshold !== void 0 && this._watchdogActive === this.limits.timeoutMs;
|
|
3872
|
+
if (this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs && !wallOwnedByWatchdog) {
|
|
3724
3873
|
exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
|
|
3725
3874
|
}
|
|
3726
3875
|
}
|
|
@@ -3734,19 +3883,99 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
3734
3883
|
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
3735
3884
|
}
|
|
3736
3885
|
const bus = this._events;
|
|
3737
|
-
if (!bus
|
|
3886
|
+
if (!bus) {
|
|
3738
3887
|
const first2 = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
|
|
3739
3888
|
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
3740
3889
|
}
|
|
3890
|
+
const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
|
|
3891
|
+
if (bus.hasListenerFor("budget.threshold_reached")) {
|
|
3892
|
+
for (const entry of exceeded) {
|
|
3893
|
+
if (this._pendingNegotiations.has(entry.kind)) continue;
|
|
3894
|
+
this._pendingNegotiations.set(entry.kind, this._negotiateExtension(entry));
|
|
3895
|
+
}
|
|
3896
|
+
const decision = this._pendingNegotiations.get(first.kind);
|
|
3897
|
+
if (!decision) throw new Error(`No pending negotiation for ${first.kind}`);
|
|
3898
|
+
throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
|
|
3899
|
+
}
|
|
3900
|
+
let hardStop = null;
|
|
3741
3901
|
for (const entry of exceeded) {
|
|
3742
3902
|
if (this._pendingNegotiations.has(entry.kind)) continue;
|
|
3743
|
-
const
|
|
3744
|
-
this._pendingNegotiations.set(entry.kind,
|
|
3903
|
+
const marker = Promise.resolve("stop");
|
|
3904
|
+
this._pendingNegotiations.set(entry.kind, marker);
|
|
3905
|
+
void marker.finally(() => this._pendingNegotiations.delete(entry.kind));
|
|
3906
|
+
const sync = this._invokeHandlerSync(entry);
|
|
3907
|
+
if (!sync) hardStop ??= new BudgetExceededError(entry.kind, entry.limit, entry.used);
|
|
3745
3908
|
}
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3909
|
+
if (hardStop) throw hardStop;
|
|
3910
|
+
return exceeded;
|
|
3911
|
+
}
|
|
3912
|
+
/**
|
|
3913
|
+
* Invoke `onThreshold` once for `entry` on the NO-LISTENER path and report
|
|
3914
|
+
* whether it decided synchronously. Returns `true` when the handler returned
|
|
3915
|
+
* a synchronous decision (already honored — an `extend` patched the limits),
|
|
3916
|
+
* or `false` when it returned a Promise (async; the caller hard-stops, since
|
|
3917
|
+
* there is no listener to resolve the negotiation). The handler is given the
|
|
3918
|
+
* full info shape (`requestDecision` plus direct `extend`/`deny`) so both
|
|
3919
|
+
* recording handlers and policy handlers work without a wired listener.
|
|
3920
|
+
*/
|
|
3921
|
+
_invokeHandlerSync(entry) {
|
|
3922
|
+
const handler = this._onThreshold;
|
|
3923
|
+
if (!handler) return false;
|
|
3924
|
+
let extendArg;
|
|
3925
|
+
const result = handler({
|
|
3926
|
+
kind: entry.kind,
|
|
3927
|
+
used: entry.used,
|
|
3928
|
+
limit: entry.limit,
|
|
3929
|
+
requestDecision: () => this._busRequestDecision(entry),
|
|
3930
|
+
// Direct hooks for synchronous policy/recording handlers.
|
|
3931
|
+
extend: (extra) => {
|
|
3932
|
+
extendArg = extra;
|
|
3933
|
+
},
|
|
3934
|
+
deny: () => {
|
|
3935
|
+
}
|
|
3936
|
+
});
|
|
3937
|
+
if (result && typeof result.then === "function") return false;
|
|
3938
|
+
if (result === "throw") return false;
|
|
3939
|
+
if (result && typeof result === "object" && "extend" in result) {
|
|
3940
|
+
extendArg = result.extend;
|
|
3941
|
+
}
|
|
3942
|
+
if (extendArg) this.patchLimits(extendArg);
|
|
3943
|
+
return true;
|
|
3944
|
+
}
|
|
3945
|
+
/**
|
|
3946
|
+
* Emit `budget.threshold_reached` and resolve to the listener's verdict.
|
|
3947
|
+
* Resolves to `'stop'` immediately when there is no listener (or no bus) so
|
|
3948
|
+
* no negotiation can hang and no fallback timer leaks. Mirrors the
|
|
3949
|
+
* coordinator watchdog's own request path so both agree on the no-listener
|
|
3950
|
+
* default.
|
|
3951
|
+
*/
|
|
3952
|
+
_busRequestDecision(entry) {
|
|
3953
|
+
const bus = this._events;
|
|
3954
|
+
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
3955
|
+
return Promise.resolve("stop");
|
|
3956
|
+
}
|
|
3957
|
+
return new Promise((resolve2) => {
|
|
3958
|
+
let resolved = false;
|
|
3959
|
+
const respond = (d) => {
|
|
3960
|
+
if (resolved) return;
|
|
3961
|
+
resolved = true;
|
|
3962
|
+
clearTimeout(fallback);
|
|
3963
|
+
resolve2(d);
|
|
3964
|
+
};
|
|
3965
|
+
const fallback = setTimeout(() => respond("stop"), _SubagentBudget.DECISION_TIMEOUT_MS);
|
|
3966
|
+
bus.emit("budget.threshold_reached", {
|
|
3967
|
+
kind: entry.kind,
|
|
3968
|
+
used: entry.used,
|
|
3969
|
+
limit: entry.limit,
|
|
3970
|
+
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
3971
|
+
// deny() wins over a same-dispatch extend(): a listener that both grants
|
|
3972
|
+
// and denies (or two listeners disagreeing) is resolved as a stop. The
|
|
3973
|
+
// grant is deferred a microtask so a synchronous deny in the same emit
|
|
3974
|
+
// pre-empts it; async grants still resolve normally.
|
|
3975
|
+
extend: (extra) => queueMicrotask(() => respond({ extend: extra })),
|
|
3976
|
+
deny: () => respond("stop")
|
|
3977
|
+
});
|
|
3978
|
+
});
|
|
3750
3979
|
}
|
|
3751
3980
|
/**
|
|
3752
3981
|
* Per-kind in-flight negotiation Promises. Each budget kind can have its
|
|
@@ -3766,77 +3995,33 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
3766
3995
|
* `{ extend: {} }` — keep going without patching; next overrun fires
|
|
3767
3996
|
* a fresh signal.
|
|
3768
3997
|
*/
|
|
3769
|
-
async _negotiateExtension(
|
|
3998
|
+
async _negotiateExtension(entry) {
|
|
3770
3999
|
if (!this._onThreshold) {
|
|
3771
4000
|
return "stop";
|
|
3772
4001
|
}
|
|
3773
4002
|
try {
|
|
3774
|
-
const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
|
|
3775
4003
|
const result = this._onThreshold({
|
|
3776
|
-
kind:
|
|
3777
|
-
used:
|
|
3778
|
-
limit:
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
if (resolved) return;
|
|
3788
|
-
resolved = true;
|
|
3789
|
-
resolve2(d);
|
|
3790
|
-
};
|
|
3791
|
-
const fallback = setTimeout(
|
|
3792
|
-
() => respond("stop"),
|
|
3793
|
-
_SubagentBudget.DECISION_TIMEOUT_MS
|
|
3794
|
-
);
|
|
3795
|
-
for (const { kind: kind2, used, limit } of exceeded) {
|
|
3796
|
-
bus.emit("budget.threshold_reached", {
|
|
3797
|
-
kind: kind2,
|
|
3798
|
-
used,
|
|
3799
|
-
limit,
|
|
3800
|
-
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
3801
|
-
extend: (extra) => {
|
|
3802
|
-
clearTimeout(fallback);
|
|
3803
|
-
respond({ extend: extra });
|
|
3804
|
-
},
|
|
3805
|
-
deny: () => {
|
|
3806
|
-
clearTimeout(fallback);
|
|
3807
|
-
respond("stop");
|
|
3808
|
-
}
|
|
3809
|
-
});
|
|
3810
|
-
}
|
|
3811
|
-
});
|
|
4004
|
+
kind: entry.kind,
|
|
4005
|
+
used: entry.used,
|
|
4006
|
+
limit: entry.limit,
|
|
4007
|
+
// One event for THIS kind only — each exceeded kind has its own
|
|
4008
|
+
// negotiation (and its own resolve), so there is no cross-kind
|
|
4009
|
+
// first-wins drop and no O(N^2) re-emission.
|
|
4010
|
+
requestDecision: () => this._busRequestDecision(entry),
|
|
4011
|
+
extend: (extra) => {
|
|
4012
|
+
this.patchLimits(extra);
|
|
4013
|
+
},
|
|
4014
|
+
deny: () => {
|
|
3812
4015
|
}
|
|
3813
4016
|
});
|
|
3814
4017
|
if (result === "throw") return "stop";
|
|
3815
4018
|
if (result === "continue") return { extend: {} };
|
|
3816
4019
|
const decision = await result;
|
|
3817
4020
|
if (decision === "stop") return "stop";
|
|
3818
|
-
|
|
3819
|
-
if (ext.maxIterations !== void 0) {
|
|
3820
|
-
this.limits.maxIterations = ext.maxIterations;
|
|
3821
|
-
}
|
|
3822
|
-
if (ext.maxToolCalls !== void 0) {
|
|
3823
|
-
this.limits.maxToolCalls = ext.maxToolCalls;
|
|
3824
|
-
}
|
|
3825
|
-
if (ext.maxTokens !== void 0) {
|
|
3826
|
-
this.limits.maxTokens = ext.maxTokens;
|
|
3827
|
-
}
|
|
3828
|
-
if (ext.maxCostUsd !== void 0) {
|
|
3829
|
-
this.limits.maxCostUsd = ext.maxCostUsd;
|
|
3830
|
-
}
|
|
3831
|
-
if (ext.timeoutMs !== void 0) {
|
|
3832
|
-
this.limits.timeoutMs = ext.timeoutMs;
|
|
3833
|
-
}
|
|
3834
|
-
if (ext.idleTimeoutMs !== void 0) {
|
|
3835
|
-
this.limits.idleTimeoutMs = ext.idleTimeoutMs;
|
|
3836
|
-
}
|
|
4021
|
+
this.patchLimits(decision.extend);
|
|
3837
4022
|
return decision;
|
|
3838
4023
|
} finally {
|
|
3839
|
-
this._pendingNegotiations.delete(kind);
|
|
4024
|
+
this._pendingNegotiations.delete(entry.kind);
|
|
3840
4025
|
}
|
|
3841
4026
|
}
|
|
3842
4027
|
recordIteration() {
|
|
@@ -3879,7 +4064,8 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
3879
4064
|
const { timeoutMs, idleTimeoutMs } = this.limits;
|
|
3880
4065
|
if (timeoutMs === void 0 && idleTimeoutMs === void 0) return;
|
|
3881
4066
|
const elapsed = Date.now() - this.startTime;
|
|
3882
|
-
const
|
|
4067
|
+
const wallSkipped = this._onThreshold !== void 0 && this._watchdogActive !== void 0 && timeoutMs !== void 0 && this._watchdogActive === timeoutMs;
|
|
4068
|
+
const wallTripped = wallSkipped ? false : timeoutMs !== void 0 && elapsed > timeoutMs;
|
|
3883
4069
|
const idleTripped = idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs;
|
|
3884
4070
|
if (!wallTripped && !idleTripped) return;
|
|
3885
4071
|
void this.checkLimits(elapsed);
|
|
@@ -6819,11 +7005,6 @@ async function dispatchAgent(task, opts = {}) {
|
|
|
6819
7005
|
};
|
|
6820
7006
|
}
|
|
6821
7007
|
|
|
6822
|
-
// src/utils/string.ts
|
|
6823
|
-
function truncate(s, max) {
|
|
6824
|
-
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
6825
|
-
}
|
|
6826
|
-
|
|
6827
7008
|
// src/types/provider.ts
|
|
6828
7009
|
var ProviderError = class extends WrongStackError {
|
|
6829
7010
|
status;
|
|
@@ -6903,7 +7084,7 @@ function classifySubagentError(err, hints = {}) {
|
|
|
6903
7084
|
const baseMessage2 = err.describe();
|
|
6904
7085
|
return providerErrorToSubagentError(err, baseMessage2, cause);
|
|
6905
7086
|
}
|
|
6906
|
-
const baseMessage =
|
|
7087
|
+
const baseMessage = toErrorMessage(err);
|
|
6907
7088
|
if (err instanceof BudgetExceededError) {
|
|
6908
7089
|
const map = {
|
|
6909
7090
|
iterations: "budget_iterations",
|
|
@@ -7203,6 +7384,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7203
7384
|
terminating = /* @__PURE__ */ new Set();
|
|
7204
7385
|
constructor(config, options = {}) {
|
|
7205
7386
|
super();
|
|
7387
|
+
this.setMaxListeners(0);
|
|
7206
7388
|
this.coordinatorId = config.coordinatorId;
|
|
7207
7389
|
this.config = config;
|
|
7208
7390
|
this.runner = options.runner;
|
|
@@ -7597,7 +7779,13 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7597
7779
|
let result;
|
|
7598
7780
|
budget.start();
|
|
7599
7781
|
try {
|
|
7600
|
-
const outcome = await this.executeWithTimeout(
|
|
7782
|
+
const outcome = await this.executeWithTimeout(
|
|
7783
|
+
this.runner,
|
|
7784
|
+
task,
|
|
7785
|
+
runCtx,
|
|
7786
|
+
budget,
|
|
7787
|
+
subagent.config.preemptFraction
|
|
7788
|
+
);
|
|
7601
7789
|
result = {
|
|
7602
7790
|
subagentId,
|
|
7603
7791
|
taskId: task.id,
|
|
@@ -7624,7 +7812,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7624
7812
|
}
|
|
7625
7813
|
this.recordCompletion(result);
|
|
7626
7814
|
}
|
|
7627
|
-
async executeWithTimeout(runner, task, ctx, budget) {
|
|
7815
|
+
async executeWithTimeout(runner, task, ctx, budget, preemptFraction = TIMEOUT_PREEMPT_FRACTION) {
|
|
7628
7816
|
const initialTimeoutMs = budget.limits.timeoutMs;
|
|
7629
7817
|
const idleLimitMs = budget.limits.idleTimeoutMs;
|
|
7630
7818
|
if (initialTimeoutMs === void 0 && idleLimitMs === void 0) {
|
|
@@ -7632,8 +7820,21 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7632
7820
|
}
|
|
7633
7821
|
const start = Date.now();
|
|
7634
7822
|
let timer = null;
|
|
7635
|
-
let
|
|
7823
|
+
let PreemptState;
|
|
7824
|
+
((PreemptState2) => {
|
|
7825
|
+
PreemptState2["ACTIVE"] = "active";
|
|
7826
|
+
PreemptState2["LOCKED"] = "locked";
|
|
7827
|
+
})(PreemptState || (PreemptState = {}));
|
|
7828
|
+
let preemptedCeiling = null;
|
|
7829
|
+
let preemptState = "active" /* ACTIVE */;
|
|
7830
|
+
let lastGrantActivityTs = -1;
|
|
7636
7831
|
const timeoutPromise = new Promise((_, reject) => {
|
|
7832
|
+
const terminate = (kind, limit, used) => {
|
|
7833
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
7834
|
+
reject(
|
|
7835
|
+
budget._events?.hasListenerFor("budget.threshold_reached") ? new Error(`subagent stopped: budget ${kind} (limit=${limit}, used=${used})`) : new BudgetExceededError(kind, limit, used)
|
|
7836
|
+
);
|
|
7837
|
+
};
|
|
7637
7838
|
const armFor = (ms) => {
|
|
7638
7839
|
if (timer) clearTimeout(timer);
|
|
7639
7840
|
timer = setTimeout(onTick, Math.max(0, ms));
|
|
@@ -7642,7 +7843,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7642
7843
|
const wallLimit = budget.limits.timeoutMs ?? initialTimeoutMs;
|
|
7643
7844
|
const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : wallLimit - (Date.now() - start);
|
|
7644
7845
|
const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
|
|
7645
|
-
const preemptRemaining = initialTimeoutMs === void 0 ||
|
|
7846
|
+
const preemptRemaining = initialTimeoutMs === void 0 || preemptedCeiling === wallLimit ? Number.POSITIVE_INFINITY : wallLimit * preemptFraction - (Date.now() - start);
|
|
7646
7847
|
armFor(Math.max(25, Math.min(wallRemaining, idleRemaining, preemptRemaining)));
|
|
7647
7848
|
};
|
|
7648
7849
|
const negotiateTimeout = async (used, limit) => {
|
|
@@ -7652,16 +7853,42 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7652
7853
|
kind: "timeout",
|
|
7653
7854
|
used,
|
|
7654
7855
|
limit,
|
|
7655
|
-
requestDecision: () =>
|
|
7656
|
-
budget._events?.
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
7662
|
-
|
|
7856
|
+
requestDecision: () => {
|
|
7857
|
+
if (!budget._events?.hasListenerFor("budget.threshold_reached")) {
|
|
7858
|
+
return Promise.resolve("stop");
|
|
7859
|
+
}
|
|
7860
|
+
return new Promise((resolveDecision) => {
|
|
7861
|
+
let settled = false;
|
|
7862
|
+
const resolve2 = (d) => {
|
|
7863
|
+
if (settled) return;
|
|
7864
|
+
settled = true;
|
|
7865
|
+
resolveDecision(d);
|
|
7866
|
+
};
|
|
7867
|
+
const fallback = setTimeout(() => resolve2("stop"), DECISION_TIMEOUT_MS);
|
|
7868
|
+
budget._events?.emit("budget.threshold_reached", {
|
|
7869
|
+
kind: "timeout",
|
|
7870
|
+
used,
|
|
7871
|
+
limit,
|
|
7872
|
+
// Informational: the budget's own decision deadline. Listeners may use
|
|
7873
|
+
// this to display a countdown. The coordinator does NOT enforce it —
|
|
7874
|
+
// it is the budget's own `setTimeout(fallback)` that races against
|
|
7875
|
+
// the listener's `extend()`/`deny()` call to guarantee progress.
|
|
7876
|
+
timeoutMs: DECISION_TIMEOUT_MS,
|
|
7877
|
+
// deny() wins over a same-dispatch extend(): defer the grant a
|
|
7878
|
+
// microtask so a synchronous deny in the same emit pre-empts it
|
|
7879
|
+
// (a listener that both grants and denies, or two listeners
|
|
7880
|
+
// disagreeing, resolves as a stop). Async grants still resolve.
|
|
7881
|
+
extend: (extra) => {
|
|
7882
|
+
clearTimeout(fallback);
|
|
7883
|
+
queueMicrotask(() => resolve2({ extend: extra }));
|
|
7884
|
+
},
|
|
7885
|
+
deny: () => {
|
|
7886
|
+
clearTimeout(fallback);
|
|
7887
|
+
resolve2("stop");
|
|
7888
|
+
}
|
|
7889
|
+
});
|
|
7663
7890
|
});
|
|
7664
|
-
}
|
|
7891
|
+
}
|
|
7665
7892
|
});
|
|
7666
7893
|
return typeof result === "string" ? result : await result;
|
|
7667
7894
|
};
|
|
@@ -7672,21 +7899,45 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7672
7899
|
const wallExceeded = wallLimit !== void 0 && elapsed >= wallLimit;
|
|
7673
7900
|
const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
|
|
7674
7901
|
if (idleExceeded && !wallExceeded) {
|
|
7902
|
+
budget._events?.emit("budget.threshold_reached", {
|
|
7903
|
+
kind: "idle_timeout",
|
|
7904
|
+
used: budget.idleMs(),
|
|
7905
|
+
limit: idleLimit ?? 0,
|
|
7906
|
+
timeoutMs: DECISION_TIMEOUT_MS,
|
|
7907
|
+
extend: () => {
|
|
7908
|
+
},
|
|
7909
|
+
deny: () => {
|
|
7910
|
+
}
|
|
7911
|
+
});
|
|
7675
7912
|
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
7676
|
-
reject(new BudgetExceededError("
|
|
7913
|
+
reject(new BudgetExceededError("idle_timeout", idleLimit ?? 0, budget.idleMs()));
|
|
7677
7914
|
return;
|
|
7678
7915
|
}
|
|
7679
|
-
if (wallLimit !== void 0 && !wallExceeded && budget.onThreshold &&
|
|
7916
|
+
if (wallLimit !== void 0 && !wallExceeded && budget.onThreshold && preemptState === "active" /* ACTIVE */ && elapsed >= wallLimit * preemptFraction) {
|
|
7917
|
+
const activityTs = Date.now() - budget.idleMs();
|
|
7918
|
+
if (activityTs <= lastGrantActivityTs) {
|
|
7919
|
+
preemptState = "locked" /* LOCKED */;
|
|
7920
|
+
preemptedCeiling = wallLimit;
|
|
7921
|
+
scheduleNext();
|
|
7922
|
+
return;
|
|
7923
|
+
}
|
|
7924
|
+
budget.setWatchdogNegotiation(wallLimit);
|
|
7680
7925
|
try {
|
|
7681
7926
|
const decision = await negotiateTimeout(elapsed, wallLimit);
|
|
7682
7927
|
if (typeof decision !== "string" && decision.extend.timeoutMs !== void 0) {
|
|
7683
|
-
budget.
|
|
7684
|
-
|
|
7928
|
+
budget.patchLimits({ timeoutMs: decision.extend.timeoutMs });
|
|
7929
|
+
lastGrantActivityTs = Date.now() - budget.idleMs();
|
|
7930
|
+
preemptState = "active" /* ACTIVE */;
|
|
7931
|
+
preemptedCeiling = null;
|
|
7685
7932
|
} else {
|
|
7686
|
-
|
|
7933
|
+
preemptState = "locked" /* LOCKED */;
|
|
7934
|
+
preemptedCeiling = wallLimit;
|
|
7687
7935
|
}
|
|
7688
7936
|
} catch {
|
|
7689
|
-
|
|
7937
|
+
preemptState = "locked" /* LOCKED */;
|
|
7938
|
+
preemptedCeiling = wallLimit;
|
|
7939
|
+
} finally {
|
|
7940
|
+
budget.clearWatchdogNegotiation();
|
|
7690
7941
|
}
|
|
7691
7942
|
scheduleNext();
|
|
7692
7943
|
return;
|
|
@@ -7701,26 +7952,41 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7701
7952
|
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
7702
7953
|
return;
|
|
7703
7954
|
}
|
|
7955
|
+
budget.setWatchdogNegotiation(limit);
|
|
7704
7956
|
try {
|
|
7705
7957
|
const decision = await negotiateTimeout(elapsed, limit);
|
|
7706
|
-
if (decision === "
|
|
7707
|
-
|
|
7958
|
+
if (decision === "throw") {
|
|
7959
|
+
terminate("timeout", limit, elapsed);
|
|
7960
|
+
return;
|
|
7961
|
+
}
|
|
7962
|
+
if (decision === "continue") {
|
|
7963
|
+
preemptState = "locked" /* LOCKED */;
|
|
7964
|
+
preemptedCeiling = wallLimit;
|
|
7708
7965
|
armFor(Math.max(1e3, limit));
|
|
7709
7966
|
return;
|
|
7710
7967
|
}
|
|
7968
|
+
if (decision === "stop") {
|
|
7969
|
+
terminate("timeout", limit, elapsed);
|
|
7970
|
+
return;
|
|
7971
|
+
}
|
|
7711
7972
|
if (decision.extend.timeoutMs !== void 0) {
|
|
7712
|
-
budget.
|
|
7713
|
-
|
|
7973
|
+
budget.patchLimits({ timeoutMs: decision.extend.timeoutMs });
|
|
7974
|
+
lastGrantActivityTs = Date.now() - budget.idleMs();
|
|
7975
|
+
preemptState = "active" /* ACTIVE */;
|
|
7976
|
+
preemptedCeiling = null;
|
|
7714
7977
|
scheduleNext();
|
|
7715
7978
|
return;
|
|
7716
7979
|
}
|
|
7717
|
-
|
|
7718
|
-
|
|
7980
|
+
terminate("timeout", limit, elapsed);
|
|
7981
|
+
return;
|
|
7719
7982
|
} catch (err) {
|
|
7720
7983
|
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
7721
7984
|
reject(
|
|
7722
7985
|
err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
|
|
7723
7986
|
);
|
|
7987
|
+
return;
|
|
7988
|
+
} finally {
|
|
7989
|
+
budget.clearWatchdogNegotiation();
|
|
7724
7990
|
}
|
|
7725
7991
|
};
|
|
7726
7992
|
scheduleNext();
|
|
@@ -7883,7 +8149,7 @@ var ParallelEternalEngine = class {
|
|
|
7883
8149
|
console.error(JSON.stringify({
|
|
7884
8150
|
level: "error",
|
|
7885
8151
|
event: "engine.persist_state_failed",
|
|
7886
|
-
message:
|
|
8152
|
+
message: toErrorMessage(err),
|
|
7887
8153
|
context: { expectedState: "stopped" },
|
|
7888
8154
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
7889
8155
|
}));
|
|
@@ -7919,7 +8185,7 @@ var ParallelEternalEngine = class {
|
|
|
7919
8185
|
);
|
|
7920
8186
|
await this.appendFailure(
|
|
7921
8187
|
"engine error",
|
|
7922
|
-
|
|
8188
|
+
toErrorMessage(err)
|
|
7923
8189
|
);
|
|
7924
8190
|
}
|
|
7925
8191
|
if (this.stopRequested) break;
|
|
@@ -8598,12 +8864,12 @@ var DefaultSkillLoader = class {
|
|
|
8598
8864
|
}
|
|
8599
8865
|
async find(name) {
|
|
8600
8866
|
const all = await this.list();
|
|
8601
|
-
|
|
8867
|
+
const lower = name.toLowerCase();
|
|
8868
|
+
return all.find((s) => s.name.toLowerCase() === lower);
|
|
8602
8869
|
}
|
|
8603
8870
|
async manifestText() {
|
|
8604
|
-
const skills = await this.list();
|
|
8605
|
-
if (skills.length === 0) return "";
|
|
8606
8871
|
const entries = await this.listEntries();
|
|
8872
|
+
if (entries.length === 0) return "";
|
|
8607
8873
|
const lines = ["## Available skills"];
|
|
8608
8874
|
for (const e of entries) {
|
|
8609
8875
|
const scopeTag = e.scope.length > 0 ? ` \u2014 ${e.scope.slice(0, 3).join(", ")}` : "";
|
|
@@ -8617,12 +8883,8 @@ var DefaultSkillLoader = class {
|
|
|
8617
8883
|
const skills = await this.list();
|
|
8618
8884
|
const entries = [];
|
|
8619
8885
|
for (const s of skills) {
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
const { trigger, scope } = parseDescription(raw);
|
|
8623
|
-
entries.push({ name: s.name, trigger, scope, source: s.source, path: s.path });
|
|
8624
|
-
} catch {
|
|
8625
|
-
}
|
|
8886
|
+
const { trigger, scope } = parseDescriptionFromText(s.description ?? "");
|
|
8887
|
+
entries.push({ name: s.name, trigger, scope, source: s.source, path: s.path });
|
|
8626
8888
|
}
|
|
8627
8889
|
this.entriesCache = entries;
|
|
8628
8890
|
return entries;
|
|
@@ -8633,16 +8895,17 @@ var DefaultSkillLoader = class {
|
|
|
8633
8895
|
this.bodyCache.clear();
|
|
8634
8896
|
}
|
|
8635
8897
|
async readBody(name) {
|
|
8636
|
-
const
|
|
8898
|
+
const key = name.toLowerCase();
|
|
8899
|
+
const cached = this.bodyCache.get(key);
|
|
8637
8900
|
if (cached !== void 0) return cached;
|
|
8638
8901
|
const m = await this.find(name);
|
|
8639
8902
|
if (!m) throw new Error(`Skill "${name}" not found`);
|
|
8640
8903
|
const body = await fs.readFile(m.path, "utf8");
|
|
8641
|
-
this.bodyCache.set(
|
|
8904
|
+
this.bodyCache.set(key, body);
|
|
8642
8905
|
return body;
|
|
8643
8906
|
}
|
|
8644
8907
|
async readSaveBody(name) {
|
|
8645
|
-
const key = `save:${name}`;
|
|
8908
|
+
const key = `save:${name.toLowerCase()}`;
|
|
8646
8909
|
const cached = this.bodyCache.get(key);
|
|
8647
8910
|
if (cached !== void 0) return cached;
|
|
8648
8911
|
const m = await this.find(name);
|
|
@@ -8703,15 +8966,13 @@ function parseFrontmatter(raw) {
|
|
|
8703
8966
|
flush();
|
|
8704
8967
|
return out;
|
|
8705
8968
|
}
|
|
8706
|
-
function
|
|
8707
|
-
const fm = parseFrontmatter(raw);
|
|
8708
|
-
const desc = fm.description ?? "";
|
|
8969
|
+
function parseDescriptionFromText(desc) {
|
|
8709
8970
|
const firstSentenceEnd = desc.indexOf(". ");
|
|
8710
8971
|
const trigger = firstSentenceEnd !== -1 ? desc.slice(0, firstSentenceEnd + 1).trim() : desc.trim().split("\n")[0] ?? "";
|
|
8711
8972
|
const scope = [];
|
|
8712
8973
|
const coversMatch = /(?:covers|for|including)\s+([^.]+)/i.exec(desc);
|
|
8713
8974
|
if (coversMatch) {
|
|
8714
|
-
const items = coversMatch[1] ?? "".replace(/[·•]/g, ",").split(",").map((s) => s.trim()).filter(Boolean);
|
|
8975
|
+
const items = (coversMatch[1] ?? "").replace(/[·•]/g, ",").split(",").map((s) => s.trim()).filter(Boolean);
|
|
8715
8976
|
scope.push(...items);
|
|
8716
8977
|
}
|
|
8717
8978
|
return { trigger, scope };
|