@wrongstack/core 0.264.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-D8sa1vtv.d.ts → agent-bridge-DrkBxszZ.d.ts} +1 -1
- package/dist/{agent-subagent-runner-c9DLkaas.d.ts → agent-subagent-runner-DM2pP-B6.d.ts} +113 -11
- package/dist/{brain-O1IdKPaK.d.ts → brain-BXd_61kQ.d.ts} +31 -2
- package/dist/{compactor-BBy0rCtB.d.ts → compactor-B8pOf45Y.d.ts} +1 -1
- package/dist/{config-Dz2F3H2K.d.ts → config-BMCj_XDs.d.ts} +80 -12
- package/dist/{context-BGSpZNSE.d.ts → context-MRk5PhNv.d.ts} +26 -12
- package/dist/coordination/index.d.ts +77 -21
- package/dist/coordination/index.js +557 -159
- 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 +609 -195
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +16 -16
- package/dist/execution/index.js +394 -155
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +2 -2
- package/dist/execution/prompt-enhancer.js +1 -1
- package/dist/execution/prompt-enhancer.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/{goal-preamble-DzjFuN3p.d.ts → goal-preamble-DvHDSKSe.d.ts} +14 -10
- package/dist/{goal-store-CxWmCGbH.d.ts → goal-store-DtLMySNb.d.ts} +1 -1
- package/dist/{index-CYIQrXVF.d.ts → index-B-ch8K9C.d.ts} +8 -8
- package/dist/{index-CbLSI66_.d.ts → index-CEDeNodM.d.ts} +5 -5
- package/dist/index.d.ts +183 -52
- package/dist/index.js +1779 -673
- 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 +1 -1
- package/dist/kernel/index.js.map +1 -1
- package/dist/{llm-selector-DzxuZnNz.d.ts → llm-selector-C0tfTCUe.d.ts} +14 -2
- package/dist/{mcp-servers-DC4QRPUI.d.ts → mcp-servers-2x4w6Jn9.d.ts} +3 -3
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js +74 -30
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-B_siPxqN.d.ts → models-registry-DmJlKuNp.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-CK5Jdj9K.d.ts → multi-agent-coordinator-DyCkCZnU.d.ts} +1 -1
- package/dist/{null-fleet-bus-DgvD4SCO.d.ts → null-fleet-bus-CG9QY2aP.d.ts} +6 -6
- package/dist/observability/index.d.ts +2 -2
- package/dist/{parallel-eternal-engine-bK0JQBR_.d.ts → parallel-eternal-engine-Jw9uhEoT.d.ts} +9 -9
- package/dist/{path-resolver-BPEDlN38.d.ts → path-resolver-Dy2ej-gE.d.ts} +3 -3
- package/dist/{permission-4yvGmMRB.d.ts → permission-B9SB45lp.d.ts} +1 -1
- package/dist/{permission-policy-C6XpsBOy.d.ts → permission-policy-CkjSXabK.d.ts} +2 -2
- package/dist/{pipeline-CXCeMz8J.d.ts → pipeline-DPDxH_7m.d.ts} +3 -3
- package/dist/{plan-templates-BvzRBkJc.d.ts → plan-templates-CzD9GnAU.d.ts} +32 -8
- package/dist/{provider-runner-C5aQpDWE.d.ts → provider-runner-DMa70ODu.d.ts} +3 -3
- package/dist/{retry-policy-CFhdtRzz.d.ts → retry-policy-CN0khdlj.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +274 -93
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-vault-CxiVLbt1.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 +204 -23
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-gIuhRTkN.d.ts → selector-CzHh_igB.d.ts} +1 -1
- package/dist/{session-event-bridge-DkvvrpDt.d.ts → session-event-bridge-BUI6Jf-4.d.ts} +1 -1
- package/dist/{session-reader-KdfVwkKP.d.ts → session-reader-CMgdMSRP.d.ts} +1 -1
- package/dist/storage/index.d.ts +112 -15
- package/dist/storage/index.js +419 -81
- package/dist/storage/index.js.map +1 -1
- package/dist/tools/index.d.ts +2 -2
- package/dist/types/index.d.ts +21 -21
- package/dist/types/index.js +261 -53
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +3 -3
- package/dist/utils/index.js +3 -5
- 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 +1 -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/secret-vault-BJDY28ev.d.ts +0 -25
package/dist/execution/index.js
CHANGED
|
@@ -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
|
}
|
|
@@ -1374,7 +1436,8 @@ function resolveWstackPaths(opts) {
|
|
|
1374
1436
|
projectSddSession: path2.join(projectDir, "sdd-session.json"),
|
|
1375
1437
|
projectPlan: path2.join(projectDir, "plan.json"),
|
|
1376
1438
|
projectAutophase: path2.join(projectDir, "autophase"),
|
|
1377
|
-
syncConfig: path2.join(globalRoot, "sync.json")
|
|
1439
|
+
syncConfig: path2.join(globalRoot, "sync.json"),
|
|
1440
|
+
projectStatus: (projectHash2) => path2.join(globalRoot, "projects", projectHash2, "status.json")
|
|
1378
1441
|
};
|
|
1379
1442
|
}
|
|
1380
1443
|
|
|
@@ -3592,6 +3655,7 @@ ${recentJournal}` : ""
|
|
|
3592
3655
|
|
|
3593
3656
|
// src/coordination/subagent-budget.ts
|
|
3594
3657
|
var TIMEOUT_PREEMPT_FRACTION = 0.85;
|
|
3658
|
+
var DECISION_TIMEOUT_MS = 6e4;
|
|
3595
3659
|
var BudgetExceededError = class extends Error {
|
|
3596
3660
|
kind;
|
|
3597
3661
|
limit;
|
|
@@ -3621,6 +3685,31 @@ var BudgetThresholdSignal = class extends Error {
|
|
|
3621
3685
|
};
|
|
3622
3686
|
var SubagentBudget = class _SubagentBudget {
|
|
3623
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
|
+
}
|
|
3624
3713
|
iterations = 0;
|
|
3625
3714
|
toolCalls = 0;
|
|
3626
3715
|
tokenInput = 0;
|
|
@@ -3641,12 +3730,44 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
3641
3730
|
* or hung listener (Director not built / event filter detached mid-run)
|
|
3642
3731
|
* leaves the budget over-limit and never enforces anything.
|
|
3643
3732
|
*/
|
|
3644
|
-
static DECISION_TIMEOUT_MS =
|
|
3733
|
+
static DECISION_TIMEOUT_MS = DECISION_TIMEOUT_MS;
|
|
3645
3734
|
/**
|
|
3646
3735
|
* Injected by the runner when wiring the budget to its EventBus.
|
|
3647
3736
|
* Used to emit `budget.threshold_reached` events in `'auto'` mode.
|
|
3648
3737
|
*/
|
|
3649
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
|
+
}
|
|
3650
3771
|
/**
|
|
3651
3772
|
* Negotiation mode — controls whether a threshold hit tries to emit
|
|
3652
3773
|
* `budget.threshold_reached` and wait for a coordinator decision, or
|
|
@@ -3747,7 +3868,8 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
3747
3868
|
if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
|
|
3748
3869
|
exceeded.push({ kind: "idle_timeout", used: idle, limit: this.limits.idleTimeoutMs });
|
|
3749
3870
|
}
|
|
3750
|
-
|
|
3871
|
+
const wallOwnedByWatchdog = this._onThreshold !== void 0 && this._watchdogActive === this.limits.timeoutMs;
|
|
3872
|
+
if (this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs && !wallOwnedByWatchdog) {
|
|
3751
3873
|
exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
|
|
3752
3874
|
}
|
|
3753
3875
|
}
|
|
@@ -3761,19 +3883,99 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
3761
3883
|
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
3762
3884
|
}
|
|
3763
3885
|
const bus = this._events;
|
|
3764
|
-
if (!bus
|
|
3886
|
+
if (!bus) {
|
|
3765
3887
|
const first2 = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
|
|
3766
3888
|
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
3767
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;
|
|
3768
3901
|
for (const entry of exceeded) {
|
|
3769
3902
|
if (this._pendingNegotiations.has(entry.kind)) continue;
|
|
3770
|
-
const
|
|
3771
|
-
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);
|
|
3772
3908
|
}
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
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
|
+
});
|
|
3777
3979
|
}
|
|
3778
3980
|
/**
|
|
3779
3981
|
* Per-kind in-flight negotiation Promises. Each budget kind can have its
|
|
@@ -3793,77 +3995,33 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
3793
3995
|
* `{ extend: {} }` — keep going without patching; next overrun fires
|
|
3794
3996
|
* a fresh signal.
|
|
3795
3997
|
*/
|
|
3796
|
-
async _negotiateExtension(
|
|
3998
|
+
async _negotiateExtension(entry) {
|
|
3797
3999
|
if (!this._onThreshold) {
|
|
3798
4000
|
return "stop";
|
|
3799
4001
|
}
|
|
3800
4002
|
try {
|
|
3801
|
-
const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
|
|
3802
4003
|
const result = this._onThreshold({
|
|
3803
|
-
kind:
|
|
3804
|
-
used:
|
|
3805
|
-
limit:
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
if (resolved) return;
|
|
3815
|
-
resolved = true;
|
|
3816
|
-
resolve2(d);
|
|
3817
|
-
};
|
|
3818
|
-
const fallback = setTimeout(
|
|
3819
|
-
() => respond("stop"),
|
|
3820
|
-
_SubagentBudget.DECISION_TIMEOUT_MS
|
|
3821
|
-
);
|
|
3822
|
-
for (const { kind: kind2, used, limit } of exceeded) {
|
|
3823
|
-
bus.emit("budget.threshold_reached", {
|
|
3824
|
-
kind: kind2,
|
|
3825
|
-
used,
|
|
3826
|
-
limit,
|
|
3827
|
-
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
3828
|
-
extend: (extra) => {
|
|
3829
|
-
clearTimeout(fallback);
|
|
3830
|
-
respond({ extend: extra });
|
|
3831
|
-
},
|
|
3832
|
-
deny: () => {
|
|
3833
|
-
clearTimeout(fallback);
|
|
3834
|
-
respond("stop");
|
|
3835
|
-
}
|
|
3836
|
-
});
|
|
3837
|
-
}
|
|
3838
|
-
});
|
|
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: () => {
|
|
3839
4015
|
}
|
|
3840
4016
|
});
|
|
3841
4017
|
if (result === "throw") return "stop";
|
|
3842
4018
|
if (result === "continue") return { extend: {} };
|
|
3843
4019
|
const decision = await result;
|
|
3844
4020
|
if (decision === "stop") return "stop";
|
|
3845
|
-
|
|
3846
|
-
if (ext.maxIterations !== void 0) {
|
|
3847
|
-
this.limits.maxIterations = ext.maxIterations;
|
|
3848
|
-
}
|
|
3849
|
-
if (ext.maxToolCalls !== void 0) {
|
|
3850
|
-
this.limits.maxToolCalls = ext.maxToolCalls;
|
|
3851
|
-
}
|
|
3852
|
-
if (ext.maxTokens !== void 0) {
|
|
3853
|
-
this.limits.maxTokens = ext.maxTokens;
|
|
3854
|
-
}
|
|
3855
|
-
if (ext.maxCostUsd !== void 0) {
|
|
3856
|
-
this.limits.maxCostUsd = ext.maxCostUsd;
|
|
3857
|
-
}
|
|
3858
|
-
if (ext.timeoutMs !== void 0) {
|
|
3859
|
-
this.limits.timeoutMs = ext.timeoutMs;
|
|
3860
|
-
}
|
|
3861
|
-
if (ext.idleTimeoutMs !== void 0) {
|
|
3862
|
-
this.limits.idleTimeoutMs = ext.idleTimeoutMs;
|
|
3863
|
-
}
|
|
4021
|
+
this.patchLimits(decision.extend);
|
|
3864
4022
|
return decision;
|
|
3865
4023
|
} finally {
|
|
3866
|
-
this._pendingNegotiations.delete(kind);
|
|
4024
|
+
this._pendingNegotiations.delete(entry.kind);
|
|
3867
4025
|
}
|
|
3868
4026
|
}
|
|
3869
4027
|
recordIteration() {
|
|
@@ -3906,7 +4064,8 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
3906
4064
|
const { timeoutMs, idleTimeoutMs } = this.limits;
|
|
3907
4065
|
if (timeoutMs === void 0 && idleTimeoutMs === void 0) return;
|
|
3908
4066
|
const elapsed = Date.now() - this.startTime;
|
|
3909
|
-
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;
|
|
3910
4069
|
const idleTripped = idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs;
|
|
3911
4070
|
if (!wallTripped && !idleTripped) return;
|
|
3912
4071
|
void this.checkLimits(elapsed);
|
|
@@ -7225,6 +7384,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7225
7384
|
terminating = /* @__PURE__ */ new Set();
|
|
7226
7385
|
constructor(config, options = {}) {
|
|
7227
7386
|
super();
|
|
7387
|
+
this.setMaxListeners(0);
|
|
7228
7388
|
this.coordinatorId = config.coordinatorId;
|
|
7229
7389
|
this.config = config;
|
|
7230
7390
|
this.runner = options.runner;
|
|
@@ -7619,7 +7779,13 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7619
7779
|
let result;
|
|
7620
7780
|
budget.start();
|
|
7621
7781
|
try {
|
|
7622
|
-
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
|
+
);
|
|
7623
7789
|
result = {
|
|
7624
7790
|
subagentId,
|
|
7625
7791
|
taskId: task.id,
|
|
@@ -7646,7 +7812,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7646
7812
|
}
|
|
7647
7813
|
this.recordCompletion(result);
|
|
7648
7814
|
}
|
|
7649
|
-
async executeWithTimeout(runner, task, ctx, budget) {
|
|
7815
|
+
async executeWithTimeout(runner, task, ctx, budget, preemptFraction = TIMEOUT_PREEMPT_FRACTION) {
|
|
7650
7816
|
const initialTimeoutMs = budget.limits.timeoutMs;
|
|
7651
7817
|
const idleLimitMs = budget.limits.idleTimeoutMs;
|
|
7652
7818
|
if (initialTimeoutMs === void 0 && idleLimitMs === void 0) {
|
|
@@ -7654,8 +7820,21 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7654
7820
|
}
|
|
7655
7821
|
const start = Date.now();
|
|
7656
7822
|
let timer = null;
|
|
7657
|
-
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;
|
|
7658
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
|
+
};
|
|
7659
7838
|
const armFor = (ms) => {
|
|
7660
7839
|
if (timer) clearTimeout(timer);
|
|
7661
7840
|
timer = setTimeout(onTick, Math.max(0, ms));
|
|
@@ -7664,7 +7843,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7664
7843
|
const wallLimit = budget.limits.timeoutMs ?? initialTimeoutMs;
|
|
7665
7844
|
const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : wallLimit - (Date.now() - start);
|
|
7666
7845
|
const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
|
|
7667
|
-
const preemptRemaining = initialTimeoutMs === void 0 ||
|
|
7846
|
+
const preemptRemaining = initialTimeoutMs === void 0 || preemptedCeiling === wallLimit ? Number.POSITIVE_INFINITY : wallLimit * preemptFraction - (Date.now() - start);
|
|
7668
7847
|
armFor(Math.max(25, Math.min(wallRemaining, idleRemaining, preemptRemaining)));
|
|
7669
7848
|
};
|
|
7670
7849
|
const negotiateTimeout = async (used, limit) => {
|
|
@@ -7674,16 +7853,42 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7674
7853
|
kind: "timeout",
|
|
7675
7854
|
used,
|
|
7676
7855
|
limit,
|
|
7677
|
-
requestDecision: () =>
|
|
7678
|
-
budget._events?.
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
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
|
+
});
|
|
7685
7890
|
});
|
|
7686
|
-
}
|
|
7891
|
+
}
|
|
7687
7892
|
});
|
|
7688
7893
|
return typeof result === "string" ? result : await result;
|
|
7689
7894
|
};
|
|
@@ -7694,21 +7899,45 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7694
7899
|
const wallExceeded = wallLimit !== void 0 && elapsed >= wallLimit;
|
|
7695
7900
|
const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
|
|
7696
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
|
+
});
|
|
7697
7912
|
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
7698
|
-
reject(new BudgetExceededError("
|
|
7913
|
+
reject(new BudgetExceededError("idle_timeout", idleLimit ?? 0, budget.idleMs()));
|
|
7699
7914
|
return;
|
|
7700
7915
|
}
|
|
7701
|
-
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);
|
|
7702
7925
|
try {
|
|
7703
7926
|
const decision = await negotiateTimeout(elapsed, wallLimit);
|
|
7704
7927
|
if (typeof decision !== "string" && decision.extend.timeoutMs !== void 0) {
|
|
7705
|
-
budget.
|
|
7706
|
-
|
|
7928
|
+
budget.patchLimits({ timeoutMs: decision.extend.timeoutMs });
|
|
7929
|
+
lastGrantActivityTs = Date.now() - budget.idleMs();
|
|
7930
|
+
preemptState = "active" /* ACTIVE */;
|
|
7931
|
+
preemptedCeiling = null;
|
|
7707
7932
|
} else {
|
|
7708
|
-
|
|
7933
|
+
preemptState = "locked" /* LOCKED */;
|
|
7934
|
+
preemptedCeiling = wallLimit;
|
|
7709
7935
|
}
|
|
7710
7936
|
} catch {
|
|
7711
|
-
|
|
7937
|
+
preemptState = "locked" /* LOCKED */;
|
|
7938
|
+
preemptedCeiling = wallLimit;
|
|
7939
|
+
} finally {
|
|
7940
|
+
budget.clearWatchdogNegotiation();
|
|
7712
7941
|
}
|
|
7713
7942
|
scheduleNext();
|
|
7714
7943
|
return;
|
|
@@ -7723,26 +7952,41 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7723
7952
|
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
7724
7953
|
return;
|
|
7725
7954
|
}
|
|
7955
|
+
budget.setWatchdogNegotiation(limit);
|
|
7726
7956
|
try {
|
|
7727
7957
|
const decision = await negotiateTimeout(elapsed, limit);
|
|
7728
|
-
if (decision === "
|
|
7729
|
-
|
|
7958
|
+
if (decision === "throw") {
|
|
7959
|
+
terminate("timeout", limit, elapsed);
|
|
7960
|
+
return;
|
|
7961
|
+
}
|
|
7962
|
+
if (decision === "continue") {
|
|
7963
|
+
preemptState = "locked" /* LOCKED */;
|
|
7964
|
+
preemptedCeiling = wallLimit;
|
|
7730
7965
|
armFor(Math.max(1e3, limit));
|
|
7731
7966
|
return;
|
|
7732
7967
|
}
|
|
7968
|
+
if (decision === "stop") {
|
|
7969
|
+
terminate("timeout", limit, elapsed);
|
|
7970
|
+
return;
|
|
7971
|
+
}
|
|
7733
7972
|
if (decision.extend.timeoutMs !== void 0) {
|
|
7734
|
-
budget.
|
|
7735
|
-
|
|
7973
|
+
budget.patchLimits({ timeoutMs: decision.extend.timeoutMs });
|
|
7974
|
+
lastGrantActivityTs = Date.now() - budget.idleMs();
|
|
7975
|
+
preemptState = "active" /* ACTIVE */;
|
|
7976
|
+
preemptedCeiling = null;
|
|
7736
7977
|
scheduleNext();
|
|
7737
7978
|
return;
|
|
7738
7979
|
}
|
|
7739
|
-
|
|
7740
|
-
|
|
7980
|
+
terminate("timeout", limit, elapsed);
|
|
7981
|
+
return;
|
|
7741
7982
|
} catch (err) {
|
|
7742
7983
|
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
7743
7984
|
reject(
|
|
7744
7985
|
err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
|
|
7745
7986
|
);
|
|
7987
|
+
return;
|
|
7988
|
+
} finally {
|
|
7989
|
+
budget.clearWatchdogNegotiation();
|
|
7746
7990
|
}
|
|
7747
7991
|
};
|
|
7748
7992
|
scheduleNext();
|
|
@@ -8620,12 +8864,12 @@ var DefaultSkillLoader = class {
|
|
|
8620
8864
|
}
|
|
8621
8865
|
async find(name) {
|
|
8622
8866
|
const all = await this.list();
|
|
8623
|
-
|
|
8867
|
+
const lower = name.toLowerCase();
|
|
8868
|
+
return all.find((s) => s.name.toLowerCase() === lower);
|
|
8624
8869
|
}
|
|
8625
8870
|
async manifestText() {
|
|
8626
|
-
const skills = await this.list();
|
|
8627
|
-
if (skills.length === 0) return "";
|
|
8628
8871
|
const entries = await this.listEntries();
|
|
8872
|
+
if (entries.length === 0) return "";
|
|
8629
8873
|
const lines = ["## Available skills"];
|
|
8630
8874
|
for (const e of entries) {
|
|
8631
8875
|
const scopeTag = e.scope.length > 0 ? ` \u2014 ${e.scope.slice(0, 3).join(", ")}` : "";
|
|
@@ -8639,12 +8883,8 @@ var DefaultSkillLoader = class {
|
|
|
8639
8883
|
const skills = await this.list();
|
|
8640
8884
|
const entries = [];
|
|
8641
8885
|
for (const s of skills) {
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
const { trigger, scope } = parseDescription(raw);
|
|
8645
|
-
entries.push({ name: s.name, trigger, scope, source: s.source, path: s.path });
|
|
8646
|
-
} catch {
|
|
8647
|
-
}
|
|
8886
|
+
const { trigger, scope } = parseDescriptionFromText(s.description ?? "");
|
|
8887
|
+
entries.push({ name: s.name, trigger, scope, source: s.source, path: s.path });
|
|
8648
8888
|
}
|
|
8649
8889
|
this.entriesCache = entries;
|
|
8650
8890
|
return entries;
|
|
@@ -8655,16 +8895,17 @@ var DefaultSkillLoader = class {
|
|
|
8655
8895
|
this.bodyCache.clear();
|
|
8656
8896
|
}
|
|
8657
8897
|
async readBody(name) {
|
|
8658
|
-
const
|
|
8898
|
+
const key = name.toLowerCase();
|
|
8899
|
+
const cached = this.bodyCache.get(key);
|
|
8659
8900
|
if (cached !== void 0) return cached;
|
|
8660
8901
|
const m = await this.find(name);
|
|
8661
8902
|
if (!m) throw new Error(`Skill "${name}" not found`);
|
|
8662
8903
|
const body = await fs.readFile(m.path, "utf8");
|
|
8663
|
-
this.bodyCache.set(
|
|
8904
|
+
this.bodyCache.set(key, body);
|
|
8664
8905
|
return body;
|
|
8665
8906
|
}
|
|
8666
8907
|
async readSaveBody(name) {
|
|
8667
|
-
const key = `save:${name}`;
|
|
8908
|
+
const key = `save:${name.toLowerCase()}`;
|
|
8668
8909
|
const cached = this.bodyCache.get(key);
|
|
8669
8910
|
if (cached !== void 0) return cached;
|
|
8670
8911
|
const m = await this.find(name);
|
|
@@ -8725,9 +8966,7 @@ function parseFrontmatter(raw) {
|
|
|
8725
8966
|
flush();
|
|
8726
8967
|
return out;
|
|
8727
8968
|
}
|
|
8728
|
-
function
|
|
8729
|
-
const fm = parseFrontmatter(raw);
|
|
8730
|
-
const desc = fm.description ?? "";
|
|
8969
|
+
function parseDescriptionFromText(desc) {
|
|
8731
8970
|
const firstSentenceEnd = desc.indexOf(". ");
|
|
8732
8971
|
const trigger = firstSentenceEnd !== -1 ? desc.slice(0, firstSentenceEnd + 1).trim() : desc.trim().split("\n")[0] ?? "";
|
|
8733
8972
|
const scope = [];
|