@wrongstack/core 0.285.0 → 0.286.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/autophase/phase-orchestrator.d.ts.map +1 -1
- package/dist/boot.d.ts.map +1 -1
- package/dist/coordination/agent-monitor.d.ts +29 -0
- package/dist/coordination/agent-monitor.d.ts.map +1 -1
- package/dist/coordination/autonomous-brain.d.ts +0 -2
- package/dist/coordination/autonomous-brain.d.ts.map +1 -1
- package/dist/coordination/autonomous-coordinator.d.ts +4 -0
- package/dist/coordination/autonomous-coordinator.d.ts.map +1 -1
- package/dist/coordination/brain-ledger.d.ts +142 -0
- package/dist/coordination/brain-ledger.d.ts.map +1 -0
- package/dist/coordination/brain-monitor.d.ts +31 -2
- package/dist/coordination/brain-monitor.d.ts.map +1 -1
- package/dist/coordination/brain.d.ts +43 -0
- package/dist/coordination/brain.d.ts.map +1 -1
- package/dist/coordination/delegate-tool.d.ts.map +1 -1
- package/dist/coordination/director-construction.d.ts.map +1 -1
- package/dist/coordination/director.d.ts +1 -1
- package/dist/coordination/director.d.ts.map +1 -1
- package/dist/coordination/fleet.d.ts +4 -4
- package/dist/coordination/fleet.d.ts.map +1 -1
- package/dist/coordination/global-mailbox.d.ts +63 -2
- package/dist/coordination/global-mailbox.d.ts.map +1 -1
- package/dist/coordination/index.d.ts +2 -1
- package/dist/coordination/index.d.ts.map +1 -1
- package/dist/coordination/index.js +1566 -462
- package/dist/coordination/index.js.map +4 -4
- package/dist/coordination/knowledge-graph.d.ts +13 -1
- package/dist/coordination/knowledge-graph.d.ts.map +1 -1
- package/dist/coordination/mailbox-actions.d.ts +12 -13
- package/dist/coordination/mailbox-actions.d.ts.map +1 -1
- package/dist/coordination/mailbox-constants.d.ts +64 -0
- package/dist/coordination/mailbox-constants.d.ts.map +1 -0
- package/dist/coordination/mailbox-events.d.ts +32 -0
- package/dist/coordination/mailbox-events.d.ts.map +1 -0
- package/dist/coordination/mailbox-message-codec.d.ts +8 -0
- package/dist/coordination/mailbox-message-codec.d.ts.map +1 -0
- package/dist/coordination/mailbox-types.d.ts +88 -4
- package/dist/coordination/mailbox-types.d.ts.map +1 -1
- package/dist/coordination/mailbox.d.ts +21 -2
- package/dist/coordination/mailbox.d.ts.map +1 -1
- package/dist/coordination/package-author-tracker.d.ts.map +1 -1
- package/dist/coordination/subagent-result-tool.d.ts.map +1 -1
- package/dist/defaults/index.js +846 -523
- package/dist/defaults/index.js.map +4 -4
- package/dist/execution/autonomous-runner.d.ts +7 -1
- package/dist/execution/autonomous-runner.d.ts.map +1 -1
- package/dist/execution/autonomy-brain.d.ts +72 -11
- package/dist/execution/autonomy-brain.d.ts.map +1 -1
- package/dist/execution/brain-chain.d.ts +53 -0
- package/dist/execution/brain-chain.d.ts.map +1 -0
- package/dist/execution/brain-evaluation.d.ts +66 -0
- package/dist/execution/brain-evaluation.d.ts.map +1 -0
- package/dist/execution/brain-runtime.d.ts +171 -0
- package/dist/execution/brain-runtime.d.ts.map +1 -0
- package/dist/execution/council-brain.d.ts +79 -0
- package/dist/execution/council-brain.d.ts.map +1 -0
- package/dist/execution/eternal-autonomy.d.ts +12 -0
- package/dist/execution/eternal-autonomy.d.ts.map +1 -1
- package/dist/execution/index.d.ts +5 -1
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +1350 -377
- package/dist/execution/index.js.map +4 -4
- package/dist/execution/parallel-eternal-engine.d.ts +12 -0
- package/dist/execution/parallel-eternal-engine.d.ts.map +1 -1
- package/dist/execution/tool-executor.d.ts.map +1 -1
- package/dist/hq/auth-store.d.ts +21 -3
- package/dist/hq/auth-store.d.ts.map +1 -1
- package/dist/hq/factory.d.ts +3 -0
- package/dist/hq/factory.d.ts.map +1 -1
- package/dist/hq/index.js +628 -72
- package/dist/hq/index.js.map +4 -4
- package/dist/hq/protocol.d.ts +54 -2
- package/dist/hq/protocol.d.ts.map +1 -1
- package/dist/hq/publisher.d.ts +4 -0
- package/dist/hq/publisher.d.ts.map +1 -1
- package/dist/index.d.ts +12 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3482 -1495
- package/dist/index.js.map +4 -4
- package/dist/kernel/events.d.ts +22 -4
- package/dist/kernel/events.d.ts.map +1 -1
- package/dist/kernel/index.js.map +2 -2
- package/dist/mailbox-attach.d.ts.map +1 -1
- package/dist/observability/index.js +16 -15
- package/dist/observability/index.js.map +2 -2
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +253 -21
- package/dist/security/index.js.map +4 -4
- package/dist/security/permission-policy-schema.d.ts +77 -0
- package/dist/security/permission-policy-schema.d.ts.map +1 -0
- package/dist/security/permission-policy.d.ts +5 -0
- package/dist/security/permission-policy.d.ts.map +1 -1
- package/dist/security/secret-vault.d.ts +9 -0
- package/dist/security/secret-vault.d.ts.map +1 -1
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/storage/config-loader.d.ts +10 -0
- package/dist/storage/config-loader.d.ts.map +1 -1
- package/dist/storage/goal-store.d.ts +1 -1
- package/dist/storage/goal-store.d.ts.map +1 -1
- package/dist/storage/index.js +147 -162
- package/dist/storage/index.js.map +2 -2
- package/dist/storage/plan-store.d.ts +1 -1
- package/dist/storage/plan-store.d.ts.map +1 -1
- package/dist/storage/queue-store.d.ts +4 -0
- package/dist/storage/queue-store.d.ts.map +1 -1
- package/dist/storage/session-store.d.ts +9 -0
- package/dist/storage/session-store.d.ts.map +1 -1
- package/dist/storage/task-store.d.ts +1 -1
- package/dist/storage/task-store.d.ts.map +1 -1
- package/dist/storage/todos-checkpoint.d.ts +2 -2
- package/dist/storage/todos-checkpoint.d.ts.map +1 -1
- package/dist/types/config.d.ts +209 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/index.js +11 -0
- package/dist/types/index.js.map +2 -2
- package/dist/types/multi-agent.d.ts +8 -0
- package/dist/types/multi-agent.d.ts.map +1 -1
- package/dist/types/observability.d.ts +5 -0
- package/dist/types/observability.d.ts.map +1 -1
- package/dist/types/tool.d.ts +7 -0
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/utils/glob-expand.d.ts.map +1 -1
- package/dist/utils/incoming-images.d.ts +39 -0
- package/dist/utils/incoming-images.d.ts.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +81 -3
- package/dist/utils/index.js.map +3 -3
- package/dist/worktree/worktree-manager.d.ts +13 -4
- package/dist/worktree/worktree-manager.d.ts.map +1 -1
- package/instructions/agents/browser.md +25 -16
- package/instructions/coordination/subagent-baseline.md +16 -2
- package/instructions/leader-after-task.md +3 -1
- package/instructions/sections/tool/common-patterns.md +7 -1
- package/instructions/system.md +47 -0
- package/package.json +2 -2
- package/skills/docker-deploy/SKILL.md +1 -1
- package/skills/wrongstack-mailbox/SKILL.md +51 -12
- package/dist/plugins/git-plugin.d.ts +0 -15
- package/dist/plugins/git-plugin.d.ts.map +0 -1
- package/dist/plugins/observability-plugin.d.ts +0 -19
- package/dist/plugins/observability-plugin.d.ts.map +0 -1
- package/dist/plugins/plan-plugin.d.ts +0 -16
- package/dist/plugins/plan-plugin.d.ts.map +0 -1
package/dist/defaults/index.js
CHANGED
|
@@ -528,9 +528,17 @@ function globToRegex(pat) {
|
|
|
528
528
|
return new RegExp(re + "$");
|
|
529
529
|
}
|
|
530
530
|
function baseDir(pat) {
|
|
531
|
-
let
|
|
532
|
-
|
|
533
|
-
|
|
531
|
+
let firstGlob = pat.length;
|
|
532
|
+
for (let i = 0; i < pat.length; i++) {
|
|
533
|
+
if (GLOB_CHARS.has(expectDefined(pat[i]))) {
|
|
534
|
+
firstGlob = i;
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
const cut = Math.max(
|
|
539
|
+
pat.lastIndexOf(SEP, firstGlob - 1),
|
|
540
|
+
pat.lastIndexOf("/", firstGlob - 1)
|
|
541
|
+
);
|
|
534
542
|
return cut < 0 ? "." : pat.slice(0, cut);
|
|
535
543
|
}
|
|
536
544
|
async function expandGlob(pattern) {
|
|
@@ -3187,9 +3195,11 @@ function normalizeSubagentStructuredReport(value) {
|
|
|
3187
3195
|
const filesExamined = stringArray(value["files_examined"]);
|
|
3188
3196
|
const nextSteps = stringArray(value["suggested_next_steps"]);
|
|
3189
3197
|
const confidence = value["confidence"];
|
|
3198
|
+
const completion = value["completion"];
|
|
3199
|
+
const remainingWork = optionalString(value["remaining_work"]);
|
|
3190
3200
|
if (!summary || summary.length > MAX_SUMMARY_CHARS || !findings || findings.length > MAX_FINDINGS || !filesExamined || filesExamined.length > MAX_FILES || !nextSteps || nextSteps.length > MAX_NEXT_STEPS || [...findings, ...filesExamined, ...nextSteps].some(
|
|
3191
3201
|
(item) => item.trim().length > MAX_ITEM_CHARS
|
|
3192
|
-
) || typeof confidence !== "number" || !Number.isFinite(confidence) || confidence < 0 || confidence > 1) {
|
|
3202
|
+
) || typeof confidence !== "number" || !Number.isFinite(confidence) || confidence < 0 || confidence > 1 || completion !== void 0 && completion !== "complete" && completion !== "partial" || completion === "partial" && !remainingWork || remainingWork !== void 0 && remainingWork.length > MAX_SUMMARY_CHARS) {
|
|
3193
3203
|
return void 0;
|
|
3194
3204
|
}
|
|
3195
3205
|
const report = {
|
|
@@ -3197,7 +3207,9 @@ function normalizeSubagentStructuredReport(value) {
|
|
|
3197
3207
|
findings: findings.map((item) => item.trim()).filter(Boolean),
|
|
3198
3208
|
files_examined: filesExamined.map((item) => item.trim()).filter(Boolean),
|
|
3199
3209
|
confidence,
|
|
3200
|
-
suggested_next_steps: nextSteps.map((item) => item.trim()).filter(Boolean)
|
|
3210
|
+
suggested_next_steps: nextSteps.map((item) => item.trim()).filter(Boolean),
|
|
3211
|
+
...completion === "complete" || completion === "partial" ? { completion } : {},
|
|
3212
|
+
...remainingWork ? { remaining_work: remainingWork } : {}
|
|
3201
3213
|
};
|
|
3202
3214
|
return JSON.stringify(report).length <= MAX_SUBAGENT_STRUCTURED_REPORT_CHARS ? report : void 0;
|
|
3203
3215
|
}
|
|
@@ -3210,6 +3222,8 @@ function formatSubagentStructuredReport(report) {
|
|
|
3210
3222
|
const next = report.suggested_next_steps.length ? report.suggested_next_steps.map((item) => `- ${item}`).join("\n") : "- (none)";
|
|
3211
3223
|
return [
|
|
3212
3224
|
report.summary,
|
|
3225
|
+
report.completion ? `Completion: ${report.completion}` : void 0,
|
|
3226
|
+
report.remaining_work ? `Remaining work: ${report.remaining_work}` : void 0,
|
|
3213
3227
|
`
|
|
3214
3228
|
Findings:
|
|
3215
3229
|
${findings}`,
|
|
@@ -3219,7 +3233,7 @@ Files examined: ${files}`,
|
|
|
3219
3233
|
`
|
|
3220
3234
|
Suggested next steps:
|
|
3221
3235
|
${next}`
|
|
3222
|
-
].join("\n");
|
|
3236
|
+
].filter((line) => typeof line === "string").join("\n");
|
|
3223
3237
|
}
|
|
3224
3238
|
function isRecord3(value) {
|
|
3225
3239
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -3227,6 +3241,9 @@ function isRecord3(value) {
|
|
|
3227
3241
|
function nonEmptyString(value) {
|
|
3228
3242
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
3229
3243
|
}
|
|
3244
|
+
function optionalString(value) {
|
|
3245
|
+
return value === void 0 ? void 0 : nonEmptyString(value);
|
|
3246
|
+
}
|
|
3230
3247
|
function stringArray(value) {
|
|
3231
3248
|
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
|
|
3232
3249
|
}
|
|
@@ -5119,58 +5136,27 @@ import * as path5 from "node:path";
|
|
|
5119
5136
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
5120
5137
|
|
|
5121
5138
|
// src/coordination/fleet.ts
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
var
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
prompt: agentPrompt("bug-hunter")
|
|
5138
|
-
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
5139
|
-
};
|
|
5140
|
-
var REFACTOR_PLANNER_AGENT = {
|
|
5141
|
-
id: "refactor-planner",
|
|
5142
|
-
name: "Refactor Planner",
|
|
5143
|
-
role: "refactor-planner",
|
|
5144
|
-
prompt: agentPrompt("refactor-planner")
|
|
5145
|
-
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
5146
|
-
};
|
|
5147
|
-
var SECURITY_SCANNER_AGENT = {
|
|
5148
|
-
id: "security-scanner",
|
|
5149
|
-
name: "Security Scanner",
|
|
5150
|
-
role: "security-scanner",
|
|
5151
|
-
prompt: agentPrompt("security-scanner")
|
|
5152
|
-
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
5153
|
-
};
|
|
5154
|
-
var SHADOW_AGENT = {
|
|
5155
|
-
id: "shadow-agent",
|
|
5156
|
-
name: "Shadow",
|
|
5157
|
-
role: "shadow-agent",
|
|
5158
|
-
prompt: agentPrompt("shadow-agent")
|
|
5159
|
-
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
5160
|
-
};
|
|
5161
|
-
var CRITIC_AGENT = {
|
|
5162
|
-
id: "critic",
|
|
5163
|
-
name: "Critic",
|
|
5164
|
-
role: "critic",
|
|
5165
|
-
prompt: agentPrompt("critic")
|
|
5166
|
-
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
5167
|
-
};
|
|
5139
|
+
function defineAgent(id, name, promptRole = id, provider) {
|
|
5140
|
+
return {
|
|
5141
|
+
id,
|
|
5142
|
+
name,
|
|
5143
|
+
role: id,
|
|
5144
|
+
prompt: agentPrompt(promptRole),
|
|
5145
|
+
...provider === void 0 ? {} : { provider }
|
|
5146
|
+
};
|
|
5147
|
+
}
|
|
5148
|
+
var AUDIT_LOG_AGENT = defineAgent("audit-log", "Audit Log");
|
|
5149
|
+
var BUG_HUNTER_AGENT = defineAgent("bug-hunter", "Bug Hunter");
|
|
5150
|
+
var REFACTOR_PLANNER_AGENT = defineAgent("refactor-planner", "Refactor Planner");
|
|
5151
|
+
var SECURITY_SCANNER_AGENT = defineAgent("security-scanner", "Security Scanner");
|
|
5152
|
+
var SHADOW_AGENT = defineAgent("shadow-agent", "Shadow");
|
|
5153
|
+
var CRITIC_AGENT = defineAgent("critic", "Critic");
|
|
5168
5154
|
var FLEET_ROSTER = {
|
|
5169
5155
|
"audit-log": AUDIT_LOG_AGENT,
|
|
5170
5156
|
"bug-hunter": BUG_HUNTER_AGENT,
|
|
5171
5157
|
"refactor-planner": REFACTOR_PLANNER_AGENT,
|
|
5172
5158
|
"security-scanner": SECURITY_SCANNER_AGENT,
|
|
5173
|
-
|
|
5159
|
+
critic: CRITIC_AGENT,
|
|
5174
5160
|
"shadow-agent": SHADOW_AGENT,
|
|
5175
5161
|
...Object.fromEntries(
|
|
5176
5162
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
|
|
@@ -5182,7 +5168,7 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
5182
5168
|
"bug-hunter": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
|
|
5183
5169
|
"refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
|
|
5184
5170
|
"security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
|
|
5185
|
-
|
|
5171
|
+
critic: { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 12e3 },
|
|
5186
5172
|
"shadow-agent": {
|
|
5187
5173
|
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
5188
5174
|
maxIterations: 2e3,
|
|
@@ -5219,41 +5205,11 @@ function applyRosterBudget(cfg) {
|
|
|
5219
5205
|
};
|
|
5220
5206
|
}
|
|
5221
5207
|
var ALL_FLEET_AGENTS = Object.values(FLEET_ROSTER);
|
|
5222
|
-
var CLINE_AGENT =
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
provider: "acp"
|
|
5228
|
-
};
|
|
5229
|
-
var GEMINI_CLI_AGENT = {
|
|
5230
|
-
id: "gemini-cli",
|
|
5231
|
-
name: "Gemini CLI",
|
|
5232
|
-
role: "gemini-cli",
|
|
5233
|
-
prompt: agentPrompt("acp-gemini-cli"),
|
|
5234
|
-
provider: "acp"
|
|
5235
|
-
};
|
|
5236
|
-
var COPILOT_AGENT = {
|
|
5237
|
-
id: "copilot",
|
|
5238
|
-
name: "GitHub Copilot",
|
|
5239
|
-
role: "copilot",
|
|
5240
|
-
prompt: agentPrompt("acp-copilot"),
|
|
5241
|
-
provider: "acp"
|
|
5242
|
-
};
|
|
5243
|
-
var OPENHANDS_AGENT = {
|
|
5244
|
-
id: "openhands",
|
|
5245
|
-
name: "OpenHands",
|
|
5246
|
-
role: "openhands",
|
|
5247
|
-
prompt: agentPrompt("acp-openhands"),
|
|
5248
|
-
provider: "acp"
|
|
5249
|
-
};
|
|
5250
|
-
var GOOSE_AGENT = {
|
|
5251
|
-
id: "goose",
|
|
5252
|
-
name: "Goose",
|
|
5253
|
-
role: "goose",
|
|
5254
|
-
prompt: agentPrompt("acp-goose"),
|
|
5255
|
-
provider: "acp"
|
|
5256
|
-
};
|
|
5208
|
+
var CLINE_AGENT = defineAgent("cline", "Cline", "acp-cline", "acp");
|
|
5209
|
+
var GEMINI_CLI_AGENT = defineAgent("gemini-cli", "Gemini CLI", "acp-gemini-cli", "acp");
|
|
5210
|
+
var COPILOT_AGENT = defineAgent("copilot", "GitHub Copilot", "acp-copilot", "acp");
|
|
5211
|
+
var OPENHANDS_AGENT = defineAgent("openhands", "OpenHands", "acp-openhands", "acp");
|
|
5212
|
+
var GOOSE_AGENT = defineAgent("goose", "Goose", "acp-goose", "acp");
|
|
5257
5213
|
var ACP_AGENTS = [
|
|
5258
5214
|
CLINE_AGENT,
|
|
5259
5215
|
GEMINI_CLI_AGENT,
|
|
@@ -5261,11 +5217,14 @@ var ACP_AGENTS = [
|
|
|
5261
5217
|
OPENHANDS_AGENT,
|
|
5262
5218
|
GOOSE_AGENT
|
|
5263
5219
|
];
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5220
|
+
var ACP_AGENT_BUDGET = {
|
|
5221
|
+
timeoutMs: 10 * 60 * 60 * 1e3,
|
|
5222
|
+
maxIterations: 8e3,
|
|
5223
|
+
maxToolCalls: 2e4
|
|
5224
|
+
};
|
|
5225
|
+
for (const agent of ACP_AGENTS) {
|
|
5226
|
+
FLEET_ROSTER_BUDGETS[agent.role] = { ...ACP_AGENT_BUDGET };
|
|
5227
|
+
}
|
|
5269
5228
|
var FLEET_ROSTER_WITHACP = {
|
|
5270
5229
|
...FLEET_ROSTER,
|
|
5271
5230
|
...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
|
|
@@ -5273,7 +5232,7 @@ var FLEET_ROSTER_WITHACP = {
|
|
|
5273
5232
|
|
|
5274
5233
|
// src/coordination/delegate-tool.ts
|
|
5275
5234
|
function createDelegateTool(opts) {
|
|
5276
|
-
const defaultTimeoutMs = opts.defaultTimeoutMs ??
|
|
5235
|
+
const defaultTimeoutMs = opts.defaultTimeoutMs ?? 4 * 60 * 60 * 1e3;
|
|
5277
5236
|
const rosterIds = opts.roster ? Object.keys(opts.roster) : [];
|
|
5278
5237
|
const inputSchema = {
|
|
5279
5238
|
type: "object",
|
|
@@ -5332,16 +5291,23 @@ function createDelegateTool(opts) {
|
|
|
5332
5291
|
type: "number",
|
|
5333
5292
|
minimum: 0,
|
|
5334
5293
|
description: "Maximum estimated USD cost the subagent may incur. Unset = use the role/coordinator default."
|
|
5294
|
+
},
|
|
5295
|
+
maxHandoffs: {
|
|
5296
|
+
type: "number",
|
|
5297
|
+
minimum: 0,
|
|
5298
|
+
maximum: 8,
|
|
5299
|
+
description: "Maximum fresh-worker continuations after partial completion or budget exhaustion. Default 1; set 0 to disable. Each successor receives the prior structured/partial report and is told to continue only the remaining work."
|
|
5335
5300
|
}
|
|
5336
5301
|
},
|
|
5337
5302
|
required: ["task"]
|
|
5338
5303
|
};
|
|
5339
5304
|
return {
|
|
5340
5305
|
name: "delegate",
|
|
5341
|
-
description: "Hand a discrete piece of work to a dedicated subagent and wait for its result. The subagent has its own context, its own LLM call, and
|
|
5342
|
-
usageHint: "Set `task` to a complete instruction. Either pick `role` from the roster
|
|
5306
|
+
description: "Hand a discrete piece of work to a dedicated subagent and wait for its result. The subagent has its own context, its own LLM call, and a generous, auto-extending budget. If it reports explicit partial completion or still exhausts a recoverable budget, delegate can continue the remaining work with a fresh subagent (bounded by `maxHandoffs`, default 1). Workers may ask the leader to spawn a parallel helper through the mailbox, but cannot recursively spawn agents themselves. Use specialized roster roles when possible; otherwise pass `name` + `task`. Call multiple delegates in parallel through the provider's parallel-tool-call surface for independent work.",
|
|
5307
|
+
usageHint: "Set `task` to a complete instruction. Either pick `role` from the roster or pass `name` for a free-form coding agent. Defaults are intentionally generous; override `timeoutMs`, `maxIterations`, `maxToolCalls`, or `maxHandoffs` only when the task warrants it. Returns the final TaskResult plus a `handoffs` history when fresh workers continued partial work. Requires Director mode in normal CLI sessions.",
|
|
5343
5308
|
permission: "auto",
|
|
5344
5309
|
mutating: false,
|
|
5310
|
+
managesOwnTimeout: true,
|
|
5345
5311
|
capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
|
|
5346
5312
|
inputSchema,
|
|
5347
5313
|
async execute(input, _ctx, execOpts) {
|
|
@@ -5381,7 +5347,7 @@ function createDelegateTool(opts) {
|
|
|
5381
5347
|
error: `Unknown role "${i.role}". Available: ${rosterIds.join(", ") || "(no roster configured)"}.`
|
|
5382
5348
|
};
|
|
5383
5349
|
}
|
|
5384
|
-
cfg = instantiateRosterConfig(i.role, base);
|
|
5350
|
+
cfg = instantiateRosterConfig(i.role, base, i.timeoutMs, defaultTimeoutMs);
|
|
5385
5351
|
if (i.systemPromptOverride) cfg.systemPromptOverride = i.systemPromptOverride;
|
|
5386
5352
|
if (i.provider) cfg.provider = i.provider;
|
|
5387
5353
|
if (i.model) cfg.model = i.model;
|
|
@@ -5420,172 +5386,175 @@ function createDelegateTool(opts) {
|
|
|
5420
5386
|
cfg.timeoutMs = Math.max(3e4, timeoutMs - SUBAGENT_TIMEOUT_BUFFER_MS);
|
|
5421
5387
|
}
|
|
5422
5388
|
opts.events?.emit("delegate.started", { sessionId, target, task: i.task });
|
|
5423
|
-
const subagentId = await director.spawn(cfg);
|
|
5424
|
-
const taskId = await director.assign({
|
|
5425
|
-
id: `${randomUUID2()}`,
|
|
5426
|
-
description: i.task,
|
|
5427
|
-
subagentId
|
|
5428
|
-
});
|
|
5429
5389
|
const dir = director;
|
|
5430
|
-
const
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
const
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
offIter();
|
|
5441
|
-
offProgress();
|
|
5442
|
-
offAbort();
|
|
5443
|
-
resolve12(value);
|
|
5444
|
-
};
|
|
5445
|
-
const arm = () => {
|
|
5446
|
-
if (timer) clearTimeout(timer);
|
|
5447
|
-
timer = setTimeout(() => finish({ __timeout: true }), timeoutMs);
|
|
5448
|
-
};
|
|
5449
|
-
const bump = (e) => {
|
|
5450
|
-
if (e.subagentId === subagentId) arm();
|
|
5451
|
-
};
|
|
5452
|
-
const offTool = dir.fleet.filter("tool.executed", bump);
|
|
5453
|
-
const offIter = dir.fleet.filter("iteration.started", bump);
|
|
5454
|
-
const offProgress = dir.fleet.filter("tool.progress", bump);
|
|
5455
|
-
if (abortSignal) {
|
|
5456
|
-
const onAbortSignal = () => finish({ __aborted: true });
|
|
5457
|
-
abortSignal.addEventListener("abort", onAbortSignal, { once: true });
|
|
5458
|
-
offAbort = () => abortSignal.removeEventListener("abort", onAbortSignal);
|
|
5459
|
-
if (abortSignal.aborted) onAbortSignal();
|
|
5460
|
-
}
|
|
5461
|
-
arm();
|
|
5462
|
-
dir.awaitTasks([taskId]).then((r) => finish(r[0] ?? { __emptyResult: true })).catch(() => finish({ __timeout: true }));
|
|
5463
|
-
});
|
|
5464
|
-
if ("__aborted" in result) {
|
|
5465
|
-
try {
|
|
5466
|
-
await dir.terminate(subagentId);
|
|
5467
|
-
} catch {
|
|
5468
|
-
}
|
|
5469
|
-
const partial2 = await readSubagentPartial(opts, subagentId);
|
|
5470
|
-
opts.events?.emit("delegate.completed", {
|
|
5471
|
-
sessionId,
|
|
5472
|
-
target,
|
|
5473
|
-
task: i.task,
|
|
5474
|
-
ok: false,
|
|
5475
|
-
status: "aborted",
|
|
5476
|
-
summary: `[${target}] aborted \u2014 the run was interrupted`,
|
|
5477
|
-
durationMs: 0,
|
|
5478
|
-
iterations: partial2?.events ?? 0,
|
|
5479
|
-
toolCalls: partial2?.toolUsesObserved ?? 0,
|
|
5390
|
+
const maxHandoffs = Math.min(8, Math.max(0, Math.floor(i.maxHandoffs ?? 1)));
|
|
5391
|
+
const handoffs = [];
|
|
5392
|
+
let delegatedTask = i.task;
|
|
5393
|
+
let handoffCount = 0;
|
|
5394
|
+
for (; ; ) {
|
|
5395
|
+
const attemptConfig = handoffCount === 0 ? cfg : freshHandoffConfig(cfg, i.role, handoffCount);
|
|
5396
|
+
const subagentId = await dir.spawn(attemptConfig);
|
|
5397
|
+
const taskId = await dir.assign({
|
|
5398
|
+
id: randomUUID2(),
|
|
5399
|
+
description: delegatedTask,
|
|
5480
5400
|
subagentId
|
|
5481
5401
|
});
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
stopReason: "aborted",
|
|
5485
|
-
error: "Delegated task aborted \u2014 the run was interrupted.",
|
|
5402
|
+
const result = await awaitDelegateAttempt(
|
|
5403
|
+
dir,
|
|
5486
5404
|
subagentId,
|
|
5487
5405
|
taskId,
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5406
|
+
timeoutMs,
|
|
5407
|
+
abortSignal
|
|
5408
|
+
);
|
|
5409
|
+
if ("__aborted" in result) {
|
|
5410
|
+
try {
|
|
5411
|
+
await dir.terminate(subagentId);
|
|
5412
|
+
} catch {
|
|
5413
|
+
}
|
|
5414
|
+
const partial2 = await readSubagentPartial(opts, subagentId);
|
|
5415
|
+
opts.events?.emit("delegate.completed", {
|
|
5416
|
+
sessionId,
|
|
5417
|
+
target,
|
|
5418
|
+
task: i.task,
|
|
5419
|
+
ok: false,
|
|
5420
|
+
status: "aborted",
|
|
5421
|
+
summary: `[${target}] aborted \u2014 the run was interrupted`,
|
|
5422
|
+
durationMs: 0,
|
|
5423
|
+
iterations: partial2?.events ?? 0,
|
|
5424
|
+
toolCalls: partial2?.toolUsesObserved ?? 0,
|
|
5425
|
+
subagentId
|
|
5426
|
+
});
|
|
5427
|
+
return {
|
|
5428
|
+
ok: false,
|
|
5429
|
+
stopReason: "aborted",
|
|
5430
|
+
error: "Delegated task aborted \u2014 the run was interrupted.",
|
|
5431
|
+
subagentId,
|
|
5432
|
+
taskId,
|
|
5433
|
+
partial: partial2,
|
|
5434
|
+
...handoffs.length > 0 ? { handoffs } : {}
|
|
5435
|
+
};
|
|
5436
|
+
}
|
|
5437
|
+
if ("__timeout" in result) {
|
|
5438
|
+
try {
|
|
5439
|
+
await dir.terminate(subagentId);
|
|
5440
|
+
} catch {
|
|
5441
|
+
}
|
|
5442
|
+
const partial2 = await readSubagentPartial(opts, subagentId);
|
|
5443
|
+
opts.events?.emit("delegate.completed", {
|
|
5444
|
+
sessionId,
|
|
5445
|
+
target,
|
|
5446
|
+
task: i.task,
|
|
5447
|
+
ok: false,
|
|
5448
|
+
status: "host_timeout",
|
|
5449
|
+
summary: `[${target}] timed out \u2014 no progress within ${Math.round(timeoutMs / 1e3)}s`,
|
|
5450
|
+
durationMs: timeoutMs,
|
|
5451
|
+
iterations: partial2?.events ?? 0,
|
|
5452
|
+
toolCalls: partial2?.toolUsesObserved ?? 0,
|
|
5453
|
+
subagentId
|
|
5454
|
+
});
|
|
5455
|
+
return {
|
|
5456
|
+
ok: false,
|
|
5457
|
+
stopReason: "host_timeout",
|
|
5458
|
+
error: `Subagent timed out: it did not finish or report progress within ${timeoutMs}ms.`,
|
|
5459
|
+
hint: "Raise timeoutMs for unusually long single operations, or split the remaining work.",
|
|
5460
|
+
subagentId,
|
|
5461
|
+
taskId,
|
|
5462
|
+
partial: partial2,
|
|
5463
|
+
...handoffs.length > 0 ? { handoffs } : {}
|
|
5464
|
+
};
|
|
5465
|
+
}
|
|
5466
|
+
if ("__emptyResult" in result) {
|
|
5467
|
+
const partial2 = await readSubagentPartial(opts, subagentId);
|
|
5468
|
+
opts.events?.emit("delegate.completed", {
|
|
5469
|
+
sessionId,
|
|
5470
|
+
target,
|
|
5471
|
+
task: i.task,
|
|
5472
|
+
ok: false,
|
|
5473
|
+
status: "empty_result",
|
|
5474
|
+
summary: `[${target}] completed without a task result`,
|
|
5475
|
+
durationMs: 0,
|
|
5476
|
+
iterations: partial2?.events ?? 0,
|
|
5477
|
+
toolCalls: partial2?.toolUsesObserved ?? 0,
|
|
5478
|
+
subagentId
|
|
5479
|
+
});
|
|
5480
|
+
return {
|
|
5481
|
+
ok: false,
|
|
5482
|
+
stopReason: "error",
|
|
5483
|
+
error: "Director returned no task result for the delegated task.",
|
|
5484
|
+
hint: "Check fleet state, then retry or reassign the task.",
|
|
5485
|
+
subagentId,
|
|
5486
|
+
taskId,
|
|
5487
|
+
partial: partial2,
|
|
5488
|
+
...handoffs.length > 0 ? { handoffs } : {}
|
|
5489
|
+
};
|
|
5490
|
+
}
|
|
5491
|
+
const partial = result.status === "success" ? void 0 : result.partial ? {
|
|
5492
|
+
lastAssistantText: result.partial.text,
|
|
5493
|
+
toolUsesObserved: result.toolCalls,
|
|
5494
|
+
events: result.iterations
|
|
5495
|
+
} : await readSubagentPartial(opts, subagentId);
|
|
5496
|
+
const continuation = continuationFor(result, partial, attemptConfig);
|
|
5497
|
+
if (continuation && handoffCount < maxHandoffs) {
|
|
5498
|
+
handoffs.push({
|
|
5499
|
+
fromSubagentId: result.subagentId,
|
|
5500
|
+
fromTaskId: result.taskId,
|
|
5501
|
+
status: result.status,
|
|
5502
|
+
errorKind: result.error?.kind,
|
|
5503
|
+
summary: continuation.summary,
|
|
5504
|
+
remainingWork: continuation.remainingWork
|
|
5505
|
+
});
|
|
5506
|
+
handoffCount += 1;
|
|
5507
|
+
delegatedTask = buildHandoffTask(i.task, continuation, handoffCount, maxHandoffs);
|
|
5508
|
+
continue;
|
|
5509
|
+
}
|
|
5510
|
+
const incomplete = result.report?.completion === "partial";
|
|
5511
|
+
const baseStopReason = incomplete ? "handoff_limit" : result.status === "success" ? "end_turn" : result.status === "timeout" ? "subagent_timeout" : result.status === "stopped" ? "aborted" : "budget_exhausted";
|
|
5512
|
+
const errorKind = result.error?.kind;
|
|
5513
|
+
const retryable = result.error?.retryable;
|
|
5514
|
+
const backoffMs = result.error?.backoffMs;
|
|
5515
|
+
const summary = incomplete ? `[${target}] partial checkpoint \u2014 handoff limit reached after ${handoffCount} continuation(s)` : buildDelegateSummary(i.role, result);
|
|
5516
|
+
let costUsd;
|
|
5517
|
+
try {
|
|
5518
|
+
costUsd = dir.snapshot().perSubagent[result.subagentId]?.cost;
|
|
5519
|
+
} catch {
|
|
5520
|
+
costUsd = void 0;
|
|
5521
|
+
}
|
|
5517
5522
|
opts.events?.emit("delegate.completed", {
|
|
5518
5523
|
sessionId,
|
|
5519
5524
|
target,
|
|
5520
5525
|
task: i.task,
|
|
5521
|
-
ok:
|
|
5522
|
-
status: "
|
|
5523
|
-
summary
|
|
5524
|
-
durationMs:
|
|
5525
|
-
iterations:
|
|
5526
|
-
toolCalls:
|
|
5527
|
-
|
|
5526
|
+
ok: result.status === "success" && !incomplete,
|
|
5527
|
+
status: incomplete ? "partial" : result.status,
|
|
5528
|
+
summary,
|
|
5529
|
+
durationMs: result.durationMs,
|
|
5530
|
+
iterations: result.iterations,
|
|
5531
|
+
toolCalls: result.toolCalls,
|
|
5532
|
+
costUsd,
|
|
5533
|
+
subagentId: result.subagentId
|
|
5528
5534
|
});
|
|
5529
5535
|
return {
|
|
5530
|
-
ok:
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5536
|
+
ok: result.status === "success" && !incomplete,
|
|
5537
|
+
status: incomplete ? "partial" : result.status,
|
|
5538
|
+
stopReason: baseStopReason,
|
|
5539
|
+
errorKind,
|
|
5540
|
+
retryable,
|
|
5541
|
+
backoffMs,
|
|
5542
|
+
subagentId: result.subagentId,
|
|
5543
|
+
taskId: result.taskId,
|
|
5544
|
+
result: result.result,
|
|
5545
|
+
report: result.report,
|
|
5546
|
+
error: result.error,
|
|
5547
|
+
iterations: result.iterations,
|
|
5548
|
+
toolCalls: result.toolCalls,
|
|
5549
|
+
durationMs: result.durationMs,
|
|
5550
|
+
...partial ? { partial } : {},
|
|
5551
|
+
...handoffs.length > 0 ? { handoffs } : {},
|
|
5552
|
+
...incomplete ? {
|
|
5553
|
+
hint: "A clean partial checkpoint remains. Reinvoke delegate with a larger maxHandoffs or assign report.remaining_work explicitly."
|
|
5554
|
+
} : hintForKind(errorKind, retryable, backoffMs, partial) ? { hint: hintForKind(errorKind, retryable, backoffMs, partial) } : {},
|
|
5555
|
+
summary
|
|
5537
5556
|
};
|
|
5538
5557
|
}
|
|
5539
|
-
const baseStopReason = result.status === "success" ? "end_turn" : result.status === "timeout" ? "subagent_timeout" : result.status === "stopped" ? "aborted" : "budget_exhausted";
|
|
5540
|
-
const partial = result.status === "success" ? void 0 : result.partial ? {
|
|
5541
|
-
lastAssistantText: result.partial.text,
|
|
5542
|
-
toolUsesObserved: result.toolCalls,
|
|
5543
|
-
events: result.iterations
|
|
5544
|
-
} : await readSubagentPartial(opts, subagentId);
|
|
5545
|
-
const errorKind = result.error?.kind;
|
|
5546
|
-
const retryable = result.error?.retryable;
|
|
5547
|
-
const backoffMs = result.error?.backoffMs;
|
|
5548
|
-
const summary = buildDelegateSummary(i.role, result);
|
|
5549
|
-
let costUsd;
|
|
5550
|
-
try {
|
|
5551
|
-
costUsd = dir.snapshot().perSubagent[result.subagentId]?.cost;
|
|
5552
|
-
} catch {
|
|
5553
|
-
costUsd = void 0;
|
|
5554
|
-
}
|
|
5555
|
-
opts.events?.emit("delegate.completed", {
|
|
5556
|
-
sessionId,
|
|
5557
|
-
target,
|
|
5558
|
-
task: i.task,
|
|
5559
|
-
ok: result.status === "success",
|
|
5560
|
-
status: result.status,
|
|
5561
|
-
summary,
|
|
5562
|
-
durationMs: result.durationMs,
|
|
5563
|
-
iterations: result.iterations,
|
|
5564
|
-
toolCalls: result.toolCalls,
|
|
5565
|
-
costUsd,
|
|
5566
|
-
subagentId: result.subagentId
|
|
5567
|
-
});
|
|
5568
|
-
return {
|
|
5569
|
-
ok: result.status === "success",
|
|
5570
|
-
status: result.status,
|
|
5571
|
-
stopReason: baseStopReason,
|
|
5572
|
-
errorKind,
|
|
5573
|
-
retryable,
|
|
5574
|
-
backoffMs,
|
|
5575
|
-
subagentId: result.subagentId,
|
|
5576
|
-
taskId: result.taskId,
|
|
5577
|
-
result: result.result,
|
|
5578
|
-
report: result.report,
|
|
5579
|
-
error: result.error,
|
|
5580
|
-
iterations: result.iterations,
|
|
5581
|
-
toolCalls: result.toolCalls,
|
|
5582
|
-
durationMs: result.durationMs,
|
|
5583
|
-
...partial ? { partial } : {},
|
|
5584
|
-
...hintForKind(errorKind, retryable, backoffMs, partial) ? { hint: hintForKind(errorKind, retryable, backoffMs, partial) } : {},
|
|
5585
|
-
// Summary is included so callers (TUI, CLI renderer) can surface
|
|
5586
|
-
// it as a chat history line — the LLM also sees it for continuity.
|
|
5587
|
-
summary
|
|
5588
|
-
};
|
|
5589
5558
|
} catch (err) {
|
|
5590
5559
|
const message = toErrorMessage(err);
|
|
5591
5560
|
opts.events?.emit("delegate.completed", {
|
|
@@ -5608,10 +5577,103 @@ function createDelegateTool(opts) {
|
|
|
5608
5577
|
}
|
|
5609
5578
|
};
|
|
5610
5579
|
}
|
|
5611
|
-
function
|
|
5580
|
+
async function awaitDelegateAttempt(director, subagentId, taskId, timeoutMs, abortSignal) {
|
|
5581
|
+
return new Promise((resolve12) => {
|
|
5582
|
+
let settled = false;
|
|
5583
|
+
let timer;
|
|
5584
|
+
let offAbort = () => {
|
|
5585
|
+
};
|
|
5586
|
+
const finish = (value) => {
|
|
5587
|
+
if (settled) return;
|
|
5588
|
+
settled = true;
|
|
5589
|
+
if (timer) clearTimeout(timer);
|
|
5590
|
+
offTool();
|
|
5591
|
+
offIter();
|
|
5592
|
+
offProgress();
|
|
5593
|
+
offAbort();
|
|
5594
|
+
resolve12(value);
|
|
5595
|
+
};
|
|
5596
|
+
const arm = () => {
|
|
5597
|
+
if (timer) clearTimeout(timer);
|
|
5598
|
+
timer = setTimeout(() => finish({ __timeout: true }), timeoutMs);
|
|
5599
|
+
};
|
|
5600
|
+
const bump = (event) => {
|
|
5601
|
+
if (event.subagentId === subagentId) arm();
|
|
5602
|
+
};
|
|
5603
|
+
const offTool = director.fleet.filter("tool.executed", bump);
|
|
5604
|
+
const offIter = director.fleet.filter("iteration.started", bump);
|
|
5605
|
+
const offProgress = director.fleet.filter("tool.progress", bump);
|
|
5606
|
+
if (abortSignal) {
|
|
5607
|
+
const onAbort = () => finish({ __aborted: true });
|
|
5608
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
5609
|
+
offAbort = () => abortSignal.removeEventListener("abort", onAbort);
|
|
5610
|
+
if (abortSignal.aborted) onAbort();
|
|
5611
|
+
}
|
|
5612
|
+
arm();
|
|
5613
|
+
director.awaitTasks([taskId]).then((results) => finish(results[0] ?? { __emptyResult: true })).catch(() => finish({ __timeout: true }));
|
|
5614
|
+
});
|
|
5615
|
+
}
|
|
5616
|
+
function freshHandoffConfig(cfg, role, handoffCount) {
|
|
5617
|
+
return {
|
|
5618
|
+
...cfg,
|
|
5619
|
+
id: role ? `${role}-${randomUUID2().slice(0, 8)}` : `${cfg.name.toLowerCase().replace(/[^a-z0-9]+/g, "-") || "subagent"}-handoff-${handoffCount}-${randomUUID2().slice(0, 6)}`
|
|
5620
|
+
};
|
|
5621
|
+
}
|
|
5622
|
+
function continuationFor(result, partial, config) {
|
|
5623
|
+
if (result.report?.completion === "partial" && result.report.remaining_work) {
|
|
5624
|
+
return {
|
|
5625
|
+
summary: result.report.summary,
|
|
5626
|
+
remainingWork: result.report.remaining_work,
|
|
5627
|
+
partialText: typeof result.result === "string" ? result.result : partial?.lastAssistantText
|
|
5628
|
+
};
|
|
5629
|
+
}
|
|
5630
|
+
const budgetKinds = /* @__PURE__ */ new Set([
|
|
5631
|
+
"budget_iterations",
|
|
5632
|
+
"budget_tool_calls",
|
|
5633
|
+
"budget_tokens",
|
|
5634
|
+
"budget_cost",
|
|
5635
|
+
"budget_timeout"
|
|
5636
|
+
]);
|
|
5637
|
+
const partialText = result.partial?.text ?? partial?.lastAssistantText;
|
|
5638
|
+
const mayHaveIsolatedWrites = config.worktree !== false && config.worktree !== "off" && (!config.tools || config.tools.some(
|
|
5639
|
+
(tool) => ["write", "edit", "replace", "patch", "bash", "exec", "install", "format"].includes(tool)
|
|
5640
|
+
));
|
|
5641
|
+
if (!mayHaveIsolatedWrites && result.status !== "success" && result.error?.kind && budgetKinds.has(result.error.kind) && partialText) {
|
|
5642
|
+
return {
|
|
5643
|
+
summary: `Prior worker stopped at ${result.error.kind} after ${result.iterations} iterations and ${result.toolCalls} tool calls.`,
|
|
5644
|
+
remainingWork: "Inspect the existing workspace and finish only the work that remains from the original task.",
|
|
5645
|
+
partialText
|
|
5646
|
+
};
|
|
5647
|
+
}
|
|
5648
|
+
return void 0;
|
|
5649
|
+
}
|
|
5650
|
+
function buildHandoffTask(originalTask, continuation, handoffCount, maxHandoffs) {
|
|
5651
|
+
const partial = continuation.partialText?.trim().slice(-6e3);
|
|
5652
|
+
return [
|
|
5653
|
+
`Continue an oversized delegated task as fresh worker ${handoffCount} of ${maxHandoffs}.`,
|
|
5654
|
+
"Do not blindly repeat completed actions. Inspect the current workspace, git diff, tests, and any files named below before changing anything.",
|
|
5655
|
+
'If the remaining work is still too large, stop at a clean checkpoint and call submit_result with completion="partial" plus concrete remaining_work.',
|
|
5656
|
+
"If parallel help would materially improve the outcome and mail_send is available, send the leader an `ask` that names the exact helper task; do not spawn agents yourself.",
|
|
5657
|
+
`Original task:
|
|
5658
|
+
${originalTask}`,
|
|
5659
|
+
`Prior checkpoint summary:
|
|
5660
|
+
${continuation.summary}`,
|
|
5661
|
+
`Remaining work:
|
|
5662
|
+
${continuation.remainingWork}`,
|
|
5663
|
+
partial ? `Prior worker's last useful output:
|
|
5664
|
+
${partial}` : ""
|
|
5665
|
+
].filter(Boolean).join("\n\n");
|
|
5666
|
+
}
|
|
5667
|
+
function instantiateRosterConfig(role, base, requestedTimeoutMs, defaultTimeoutMs) {
|
|
5612
5668
|
const withBudget = applyRosterBudget({ ...base, role });
|
|
5669
|
+
const rosterTimeoutMs = FLEET_ROSTER_BUDGETS[role]?.timeoutMs;
|
|
5613
5670
|
return {
|
|
5614
5671
|
...withBudget,
|
|
5672
|
+
// Without an explicit host wait, apply the role's tuned multi-hour
|
|
5673
|
+
// wall-clock budget. With an explicit wait, leave this unset so the buffer
|
|
5674
|
+
// logic above derives a slightly shorter child budget without clamping the
|
|
5675
|
+
// role defaults used by ordinary calls.
|
|
5676
|
+
timeoutMs: requestedTimeoutMs === void 0 ? rosterTimeoutMs ?? defaultTimeoutMs : void 0,
|
|
5615
5677
|
// Give each spawn a fresh id so parallel or repeated delegates
|
|
5616
5678
|
// can use the same role safely.
|
|
5617
5679
|
id: `${role}-${randomUUID2().slice(0, 8)}`
|
|
@@ -6253,9 +6315,9 @@ var CollabSession = class extends EventEmitter {
|
|
|
6253
6315
|
this._timeoutTimer = void 0;
|
|
6254
6316
|
}
|
|
6255
6317
|
this.cleanup();
|
|
6256
|
-
const
|
|
6257
|
-
this.emit("session.error",
|
|
6258
|
-
throw
|
|
6318
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
6319
|
+
this.emit("session.error", error2);
|
|
6320
|
+
throw error2;
|
|
6259
6321
|
}
|
|
6260
6322
|
for (const result of results?.flat() ?? []) {
|
|
6261
6323
|
await this.parseAndEmit(result);
|
|
@@ -7392,13 +7454,13 @@ ${a.reports.map((r) => `- ${r.role}: ${r.verdict}${r.error ? ` (${r.error})` : "
|
|
|
7392
7454
|
}
|
|
7393
7455
|
function summarizeTaskResult(result) {
|
|
7394
7456
|
const text = typeof result.result === "string" ? result.result : result.result !== void 0 ? JSON.stringify(result.result, null, 2) : "";
|
|
7395
|
-
const
|
|
7457
|
+
const error2 = result.error ? `${result.error.kind}: ${result.error.message}` : void 0;
|
|
7396
7458
|
return {
|
|
7397
7459
|
taskId: result.taskId,
|
|
7398
7460
|
subagentId: result.subagentId,
|
|
7399
7461
|
status: result.status,
|
|
7400
|
-
summary: excerpt(text ||
|
|
7401
|
-
error
|
|
7462
|
+
summary: excerpt(text || error2 || "(no output)", 4e3),
|
|
7463
|
+
error: error2
|
|
7402
7464
|
};
|
|
7403
7465
|
}
|
|
7404
7466
|
function assessRoleResult(role, result) {
|
|
@@ -10146,7 +10208,7 @@ var Director = class _Director {
|
|
|
10146
10208
|
this.subagentBaseline = opts.subagentBaseline ?? DEFAULT_SUBAGENT_BASELINE;
|
|
10147
10209
|
this.taskResultNotifier = opts.taskResultNotifier;
|
|
10148
10210
|
this.subagentIdleTimeoutMs = typeof opts.subagentIdleTimeoutMs === "number" && Number.isFinite(opts.subagentIdleTimeoutMs) && opts.subagentIdleTimeoutMs >= 0 ? opts.subagentIdleTimeoutMs : void 0;
|
|
10149
|
-
this.retireSubagentOnTaskComplete = opts.retireSubagentOnTaskComplete ??
|
|
10211
|
+
this.retireSubagentOnTaskComplete = opts.retireSubagentOnTaskComplete ?? true;
|
|
10150
10212
|
this.sharedScratchpadPath = opts.sharedScratchpadPath ?? null;
|
|
10151
10213
|
this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
|
|
10152
10214
|
this.maxSpawnDepth = opts.fleetManager?.maxSpawnDepth ?? resolveMaxSpawnDepth(opts.maxSpawnDepth);
|
|
@@ -10157,7 +10219,7 @@ var Director = class _Director {
|
|
|
10157
10219
|
this.dispatchClassifier = opts.dispatchClassifier;
|
|
10158
10220
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
10159
10221
|
this.maxFleetTokens = opts.directorBudget?.maxTokens ?? Number.POSITIVE_INFINITY;
|
|
10160
|
-
this.maxBudgetExtensions = opts.maxBudgetExtensions ??
|
|
10222
|
+
this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 12;
|
|
10161
10223
|
this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
|
|
10162
10224
|
this.maxContext = opts.maxContext ?? 128e3;
|
|
10163
10225
|
this.modelMatrix = opts.modelMatrix;
|
|
@@ -10406,7 +10468,7 @@ var Director = class _Director {
|
|
|
10406
10468
|
payload.deny();
|
|
10407
10469
|
return;
|
|
10408
10470
|
}
|
|
10409
|
-
if (decision.optionId === "stop"
|
|
10471
|
+
if (decision.optionId === "stop") {
|
|
10410
10472
|
payload.deny();
|
|
10411
10473
|
return;
|
|
10412
10474
|
}
|
|
@@ -10934,6 +10996,7 @@ var Director = class _Director {
|
|
|
10934
10996
|
await this.stateCheckpoint.flush().catch((err) => this.logShutdownError("state_checkpoint_flush", err));
|
|
10935
10997
|
await this.stateCheckpoint.releaseLock().catch((err) => this.logShutdownError("state_checkpoint_lock_release", err));
|
|
10936
10998
|
}
|
|
10999
|
+
this.largeAnswerStore.clear();
|
|
10937
11000
|
}
|
|
10938
11001
|
/**
|
|
10939
11002
|
* Funnel for shutdown-phase errors. We can't throw — `shutdown()` is
|
|
@@ -11135,9 +11198,18 @@ var Director = class _Director {
|
|
|
11135
11198
|
}
|
|
11136
11199
|
async terminate(subagentId) {
|
|
11137
11200
|
await this.coordinator.stop(subagentId);
|
|
11201
|
+
void this.remove(subagentId).catch(
|
|
11202
|
+
(err) => this.logShutdownError("terminate_remove", err)
|
|
11203
|
+
);
|
|
11138
11204
|
}
|
|
11139
11205
|
async terminateAll() {
|
|
11206
|
+
const ids = this.status().subagents.map((s) => s.id);
|
|
11140
11207
|
await this.coordinator.stopAll();
|
|
11208
|
+
for (const id of ids) {
|
|
11209
|
+
void this.remove(id).catch(
|
|
11210
|
+
(err) => this.logShutdownError("terminate_all_remove", err)
|
|
11211
|
+
);
|
|
11212
|
+
}
|
|
11141
11213
|
}
|
|
11142
11214
|
async remove(subagentId) {
|
|
11143
11215
|
this.clearSubagentIdleRetirement(subagentId);
|
|
@@ -11177,7 +11249,7 @@ var Director = class _Director {
|
|
|
11177
11249
|
const timer = setTimeout(() => {
|
|
11178
11250
|
this.subagentIdleTimers.delete(subagentId);
|
|
11179
11251
|
const entry = this.coordinator.getStatus().subagents.find((a) => a.id === subagentId);
|
|
11180
|
-
if (
|
|
11252
|
+
if (entry?.status !== "idle") return;
|
|
11181
11253
|
if (this.coordinator.listPendingTasks().some((task) => task.subagentId === subagentId)) {
|
|
11182
11254
|
this.armSubagentIdleRetirement(subagentId, this.subagentIdleTimeoutMs);
|
|
11183
11255
|
return;
|
|
@@ -12769,6 +12841,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
12769
12841
|
projectRoot;
|
|
12770
12842
|
checkpointCas;
|
|
12771
12843
|
isSessionInUse;
|
|
12844
|
+
logger;
|
|
12772
12845
|
/**
|
|
12773
12846
|
* In-memory cache for load() results, keyed by session ID. The cache is
|
|
12774
12847
|
* invalidated when the file's mtimeMs or size changes (indicating the
|
|
@@ -12794,6 +12867,18 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
12794
12867
|
this.events = opts.events;
|
|
12795
12868
|
this.secretScrubber = opts.secretScrubber;
|
|
12796
12869
|
this.isSessionInUse = opts.isSessionInUse;
|
|
12870
|
+
this.logger = opts.logger;
|
|
12871
|
+
}
|
|
12872
|
+
/**
|
|
12873
|
+
* Emit a structured warning. Uses the configured Logger when available;
|
|
12874
|
+
* falls back to console.warn(JSON) so warnings are never silently dropped.
|
|
12875
|
+
*/
|
|
12876
|
+
logWarn(msg, ctx) {
|
|
12877
|
+
if (this.logger) {
|
|
12878
|
+
this.logger.warn(msg, ctx);
|
|
12879
|
+
} else {
|
|
12880
|
+
console.warn(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
12881
|
+
}
|
|
12797
12882
|
}
|
|
12798
12883
|
/**
|
|
12799
12884
|
* Clear the load() cache. Useful for testing or when the caller knows
|
|
@@ -12807,7 +12892,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
12807
12892
|
}
|
|
12808
12893
|
}
|
|
12809
12894
|
// ── Storage event helpers ───────────────────────────────────────────────────
|
|
12810
|
-
emitRead(sessionId, filePath, operation, outcome, durationMs,
|
|
12895
|
+
emitRead(sessionId, filePath, operation, outcome, durationMs, error2) {
|
|
12811
12896
|
this.events?.emit("storage.read", {
|
|
12812
12897
|
sessionId,
|
|
12813
12898
|
store: "session",
|
|
@@ -12815,10 +12900,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
12815
12900
|
operation,
|
|
12816
12901
|
outcome,
|
|
12817
12902
|
durationMs,
|
|
12818
|
-
...
|
|
12903
|
+
...error2 !== void 0 ? { error: error2 } : {}
|
|
12819
12904
|
});
|
|
12820
12905
|
}
|
|
12821
|
-
emitWrite(sessionId, filePath, operation, outcome, durationMs, eventCount,
|
|
12906
|
+
emitWrite(sessionId, filePath, operation, outcome, durationMs, eventCount, error2) {
|
|
12822
12907
|
this.events?.emit("storage.write", {
|
|
12823
12908
|
sessionId,
|
|
12824
12909
|
store: "session",
|
|
@@ -12827,16 +12912,16 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
12827
12912
|
outcome,
|
|
12828
12913
|
durationMs,
|
|
12829
12914
|
...eventCount !== void 0 ? { eventCount } : {},
|
|
12830
|
-
...
|
|
12915
|
+
...error2 !== void 0 ? { error: error2 } : {}
|
|
12831
12916
|
});
|
|
12832
12917
|
}
|
|
12833
|
-
emitError(sessionId, filePath, operation,
|
|
12918
|
+
emitError(sessionId, filePath, operation, error2, recoverable) {
|
|
12834
12919
|
this.events?.emit("storage.error", {
|
|
12835
12920
|
sessionId,
|
|
12836
12921
|
store: "session",
|
|
12837
12922
|
filePath,
|
|
12838
12923
|
operation,
|
|
12839
|
-
error,
|
|
12924
|
+
error: error2,
|
|
12840
12925
|
recoverable
|
|
12841
12926
|
});
|
|
12842
12927
|
}
|
|
@@ -12895,12 +12980,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
12895
12980
|
this.emitWrite(id, file, "create", "success", Date.now() - t0);
|
|
12896
12981
|
return writer;
|
|
12897
12982
|
} catch (err) {
|
|
12898
|
-
await handle.close().catch((e) =>
|
|
12899
|
-
|
|
12900
|
-
event: "session_store.handle_close_failed",
|
|
12901
|
-
|
|
12902
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
12903
|
-
})));
|
|
12983
|
+
await handle.close().catch((e) => this.logWarn(
|
|
12984
|
+
"Session handle close failed",
|
|
12985
|
+
{ event: "session_store.handle_close_failed", message: e instanceof Error ? e.message : String(e) }
|
|
12986
|
+
));
|
|
12904
12987
|
this.emitError(id, file, "create", toErrorMessage(err), true);
|
|
12905
12988
|
throw err;
|
|
12906
12989
|
}
|
|
@@ -13031,12 +13114,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13031
13114
|
this.emitWrite(id, file, "resume", "success", Date.now() - t0);
|
|
13032
13115
|
return { writer, data: resumedData };
|
|
13033
13116
|
} catch (err) {
|
|
13034
|
-
await handle.close().catch((e) =>
|
|
13035
|
-
|
|
13036
|
-
event: "session_store.handle_close_failed",
|
|
13037
|
-
|
|
13038
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
13039
|
-
})));
|
|
13117
|
+
await handle.close().catch((e) => this.logWarn(
|
|
13118
|
+
"Session handle close failed",
|
|
13119
|
+
{ event: "session_store.handle_close_failed", message: e instanceof Error ? e.message : String(e) }
|
|
13120
|
+
));
|
|
13040
13121
|
this.emitError(id, file, "resume", toErrorMessage(err), true);
|
|
13041
13122
|
throw err;
|
|
13042
13123
|
}
|
|
@@ -13657,13 +13738,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
13657
13738
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
13658
13739
|
const msg = toErrorMessage(err);
|
|
13659
13740
|
this.emitError(id, manifest, "summary_fallback", msg, true);
|
|
13660
|
-
|
|
13661
|
-
|
|
13662
|
-
event: "session_store.manifest_write_failed",
|
|
13663
|
-
|
|
13664
|
-
message: msg,
|
|
13665
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
13666
|
-
}));
|
|
13741
|
+
this.logWarn(
|
|
13742
|
+
"Session manifest write failed",
|
|
13743
|
+
{ event: "session_store.manifest_write_failed", sessionId: id, message: msg }
|
|
13744
|
+
);
|
|
13667
13745
|
});
|
|
13668
13746
|
outcome = "failure";
|
|
13669
13747
|
errorMsg = "summary fallback \xE2\u20AC\u201D manifest rebuilt";
|
|
@@ -14280,12 +14358,12 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
14280
14358
|
const stillHard = afterLoad >= pressure.hardThreshold;
|
|
14281
14359
|
const fatal = stillHard && (this.failureMode === "throw" || this.failureMode === "throw_on_hard" && pressure.level === "hard");
|
|
14282
14360
|
if (stillHard) {
|
|
14283
|
-
const
|
|
14361
|
+
const error2 = new Error(
|
|
14284
14362
|
`Auto-compaction left context above the hard threshold after ${pressure.level} compaction`
|
|
14285
14363
|
);
|
|
14286
14364
|
this.events?.emit("compaction.failed", {
|
|
14287
14365
|
sessionId: ctx.session.id,
|
|
14288
|
-
err:
|
|
14366
|
+
err: error2,
|
|
14289
14367
|
aggressive,
|
|
14290
14368
|
level: pressure.level,
|
|
14291
14369
|
tokens: afterTokens,
|
|
@@ -14309,11 +14387,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
14309
14387
|
}
|
|
14310
14388
|
}
|
|
14311
14389
|
} catch (err) {
|
|
14312
|
-
const
|
|
14390
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
14313
14391
|
const fatal = this.failureMode === "throw" || this.failureMode === "throw_on_hard" && pressure.level === "hard";
|
|
14314
14392
|
this.events?.emit("compaction.failed", {
|
|
14315
14393
|
sessionId: ctx.session.id,
|
|
14316
|
-
err:
|
|
14394
|
+
err: error2,
|
|
14317
14395
|
aggressive,
|
|
14318
14396
|
level: pressure.level,
|
|
14319
14397
|
tokens: pressure.tokens,
|
|
@@ -14374,19 +14452,27 @@ function adaptThresholdsForSignals(thresholds, signals) {
|
|
|
14374
14452
|
|
|
14375
14453
|
// src/execution/autonomous-runner.ts
|
|
14376
14454
|
var DoneConditionChecker = class {
|
|
14377
|
-
constructor(condition) {
|
|
14455
|
+
constructor(condition, opts) {
|
|
14378
14456
|
this.condition = condition;
|
|
14457
|
+
this.logger = opts?.logger;
|
|
14379
14458
|
if (condition.type === "output_match" && condition.pattern) {
|
|
14380
14459
|
const result = compileUserRegex(condition.pattern, "");
|
|
14381
14460
|
this.compiledRegex = result.ok ? result.regex : null;
|
|
14382
14461
|
if (!result.ok) {
|
|
14383
|
-
|
|
14384
|
-
|
|
14385
|
-
|
|
14386
|
-
|
|
14387
|
-
|
|
14388
|
-
|
|
14389
|
-
|
|
14462
|
+
if (this.logger) {
|
|
14463
|
+
this.logger.warn(
|
|
14464
|
+
"Done condition has invalid regex \u2014 will never match",
|
|
14465
|
+
{ event: "autonomous.done_condition_invalid_regex", pattern: condition.pattern, reason: result.reason }
|
|
14466
|
+
);
|
|
14467
|
+
} else {
|
|
14468
|
+
console.warn(JSON.stringify({
|
|
14469
|
+
level: "warn",
|
|
14470
|
+
event: "autonomous.done_condition_invalid_regex",
|
|
14471
|
+
pattern: condition.pattern,
|
|
14472
|
+
reason: result.reason,
|
|
14473
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
14474
|
+
}));
|
|
14475
|
+
}
|
|
14390
14476
|
}
|
|
14391
14477
|
} else {
|
|
14392
14478
|
this.compiledRegex = null;
|
|
@@ -14394,6 +14480,7 @@ var DoneConditionChecker = class {
|
|
|
14394
14480
|
}
|
|
14395
14481
|
condition;
|
|
14396
14482
|
compiledRegex;
|
|
14483
|
+
logger;
|
|
14397
14484
|
check(state) {
|
|
14398
14485
|
switch (this.condition.type) {
|
|
14399
14486
|
case "iterations":
|
|
@@ -14444,7 +14531,7 @@ var DoneConditionChecker = class {
|
|
|
14444
14531
|
var AutonomousRunner = class {
|
|
14445
14532
|
constructor(opts) {
|
|
14446
14533
|
this.opts = opts;
|
|
14447
|
-
this.doneChecker = new DoneConditionChecker(opts.doneCondition);
|
|
14534
|
+
this.doneChecker = new DoneConditionChecker(opts.doneCondition, { logger: opts.logger });
|
|
14448
14535
|
}
|
|
14449
14536
|
opts;
|
|
14450
14537
|
iterations = 0;
|
|
@@ -14555,7 +14642,7 @@ var MAX_JOURNAL_ENTRIES = 500;
|
|
|
14555
14642
|
function goalFilePath(projectRoot) {
|
|
14556
14643
|
return resolveWstackPaths({ projectRoot }).projectGoal;
|
|
14557
14644
|
}
|
|
14558
|
-
async function loadGoal(filePath, events) {
|
|
14645
|
+
async function loadGoal(filePath, events, warn) {
|
|
14559
14646
|
const t0 = Date.now();
|
|
14560
14647
|
let raw;
|
|
14561
14648
|
try {
|
|
@@ -14586,13 +14673,9 @@ async function loadGoal(filePath, events) {
|
|
|
14586
14673
|
try {
|
|
14587
14674
|
const parsed = JSON.parse(raw);
|
|
14588
14675
|
if (parsed?.version !== 1 || typeof parsed.goal !== "string" || !Array.isArray(parsed.journal)) {
|
|
14589
|
-
console.warn(JSON.stringify({
|
|
14590
|
-
|
|
14591
|
-
|
|
14592
|
-
path: filePath,
|
|
14593
|
-
message: "invalid schema \u2014 consider deleting and re-creating",
|
|
14594
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
14595
|
-
}));
|
|
14676
|
+
(warn ?? ((msg) => console.warn(JSON.stringify({ level: "warn", event: "goal_store.invalid_schema", path: filePath, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }))))(
|
|
14677
|
+
"invalid schema \u2014 consider deleting and re-creating"
|
|
14678
|
+
);
|
|
14596
14679
|
events?.emit("storage.read", {
|
|
14597
14680
|
sessionId: "~boot~",
|
|
14598
14681
|
store: "goal",
|
|
@@ -14614,13 +14697,9 @@ async function loadGoal(filePath, events) {
|
|
|
14614
14697
|
});
|
|
14615
14698
|
return parsed;
|
|
14616
14699
|
} catch {
|
|
14617
|
-
console.warn(JSON.stringify({
|
|
14618
|
-
|
|
14619
|
-
|
|
14620
|
-
path: filePath,
|
|
14621
|
-
message: "JSON parse failed \u2014 consider deleting and re-creating",
|
|
14622
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
14623
|
-
}));
|
|
14700
|
+
(warn ?? ((msg) => console.warn(JSON.stringify({ level: "warn", event: "goal_store.parse_failed", path: filePath, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }))))(
|
|
14701
|
+
"JSON parse failed \u2014 consider deleting and re-creating"
|
|
14702
|
+
);
|
|
14624
14703
|
events?.emit("storage.read", {
|
|
14625
14704
|
sessionId: "~boot~",
|
|
14626
14705
|
store: "goal",
|
|
@@ -14993,8 +15072,8 @@ function summarizeToolResultElision(block, tokens) {
|
|
|
14993
15072
|
if (block.name) parts.push(`tool=${block.name}`);
|
|
14994
15073
|
const files = extractPathHints(block.content).slice(0, 5);
|
|
14995
15074
|
if (files.length > 0) parts.push(`files=${files.join(", ")}`);
|
|
14996
|
-
const
|
|
14997
|
-
if (
|
|
15075
|
+
const error2 = firstErrorLine(block.content);
|
|
15076
|
+
if (error2) parts.push(`error=${error2}`);
|
|
14998
15077
|
return `[${parts.join("; ")}]`;
|
|
14999
15078
|
}
|
|
15000
15079
|
function extractPathHints(content) {
|
|
@@ -16530,22 +16609,40 @@ var EternalAutonomyEngine = class {
|
|
|
16530
16609
|
currentCtrl = null;
|
|
16531
16610
|
iterationsSinceCompact = 0;
|
|
16532
16611
|
goalPath;
|
|
16612
|
+
/**
|
|
16613
|
+
* Emit a structured error. Uses the configured Logger when available;
|
|
16614
|
+
* falls back to console.error(JSON) so events are never silently dropped.
|
|
16615
|
+
*/
|
|
16616
|
+
logError(msg, ctx) {
|
|
16617
|
+
if (this.opts.logger) {
|
|
16618
|
+
this.opts.logger.error(msg, ctx);
|
|
16619
|
+
} else {
|
|
16620
|
+
console.error(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
16621
|
+
}
|
|
16622
|
+
}
|
|
16533
16623
|
/** Current engine state — readable for UIs. */
|
|
16534
16624
|
get currentState() {
|
|
16535
16625
|
return this.state;
|
|
16536
16626
|
}
|
|
16627
|
+
/**
|
|
16628
|
+
* Load the goal file with structured logging for parse/schema warnings.
|
|
16629
|
+
*/
|
|
16630
|
+
loadGoal() {
|
|
16631
|
+
return loadGoal(
|
|
16632
|
+
this.goalPath,
|
|
16633
|
+
this.opts.events,
|
|
16634
|
+
this.opts.logger ? (msg) => this.opts.logger.warn(msg) : void 0
|
|
16635
|
+
);
|
|
16636
|
+
}
|
|
16537
16637
|
/** Synchronously request stop. Resolves once the running iteration aborts. */
|
|
16538
16638
|
stop() {
|
|
16539
16639
|
this.stopRequested = true;
|
|
16540
16640
|
this.currentCtrl?.abort();
|
|
16541
16641
|
void this.persistEngineState("stopped").catch((err) => {
|
|
16542
|
-
|
|
16543
|
-
|
|
16544
|
-
event: "engine.persist_state_failed",
|
|
16545
|
-
|
|
16546
|
-
context: { expectedState: "stopped" },
|
|
16547
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
16548
|
-
}));
|
|
16642
|
+
this.logError(
|
|
16643
|
+
"Engine persist state failed",
|
|
16644
|
+
{ event: "engine.persist_state_failed", message: toErrorMessage(err), context: { expectedState: "stopped" } }
|
|
16645
|
+
);
|
|
16549
16646
|
});
|
|
16550
16647
|
this.state = "stopped";
|
|
16551
16648
|
}
|
|
@@ -16601,7 +16698,7 @@ var EternalAutonomyEngine = class {
|
|
|
16601
16698
|
const emit = (stage) => {
|
|
16602
16699
|
this.opts.onStage?.(stage);
|
|
16603
16700
|
};
|
|
16604
|
-
const goal = await loadGoal(
|
|
16701
|
+
const goal = await this.loadGoal();
|
|
16605
16702
|
if (!goal) {
|
|
16606
16703
|
emit({ phase: "stopped" });
|
|
16607
16704
|
this.stopRequested = true;
|
|
@@ -16705,16 +16802,13 @@ var EternalAutonomyEngine = class {
|
|
|
16705
16802
|
emit({ phase: "reflect", status, note });
|
|
16706
16803
|
let iterationIndex = 0;
|
|
16707
16804
|
try {
|
|
16708
|
-
const reloaded = await loadGoal(
|
|
16805
|
+
const reloaded = await this.loadGoal();
|
|
16709
16806
|
iterationIndex = reloaded?.iterations ?? 0;
|
|
16710
16807
|
} catch (err) {
|
|
16711
|
-
|
|
16712
|
-
|
|
16713
|
-
event: "autonomy.goal_reload_failed",
|
|
16714
|
-
|
|
16715
|
-
context: { goalPath: this.goalPath },
|
|
16716
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
16717
|
-
}));
|
|
16808
|
+
this.logError(
|
|
16809
|
+
"Goal reload failed",
|
|
16810
|
+
{ event: "autonomy.goal_reload_failed", message: toErrorMessage(err), context: { goalPath: this.goalPath } }
|
|
16811
|
+
);
|
|
16718
16812
|
}
|
|
16719
16813
|
this.opts.onIteration?.({
|
|
16720
16814
|
at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -16950,13 +17044,10 @@ ${lastFew}` : "No prior iterations yet.",
|
|
|
16950
17044
|
clearTimeout(timer);
|
|
16951
17045
|
}
|
|
16952
17046
|
} catch (err) {
|
|
16953
|
-
|
|
16954
|
-
|
|
16955
|
-
event: "autonomy.brainstorm_failed",
|
|
16956
|
-
|
|
16957
|
-
context: { goal: goal.goal.slice(0, 100) },
|
|
16958
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
16959
|
-
}));
|
|
17047
|
+
this.logError(
|
|
17048
|
+
"Brainstorm failed",
|
|
17049
|
+
{ event: "autonomy.brainstorm_failed", message: toErrorMessage(err), context: { goal: goal.goal.slice(0, 100) } }
|
|
17050
|
+
);
|
|
16960
17051
|
return null;
|
|
16961
17052
|
}
|
|
16962
17053
|
}
|
|
@@ -17039,7 +17130,7 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
17039
17130
|
}
|
|
17040
17131
|
}
|
|
17041
17132
|
async appendIterationEntry(entry) {
|
|
17042
|
-
const current = await loadGoal(
|
|
17133
|
+
const current = await this.loadGoal();
|
|
17043
17134
|
if (!current) {
|
|
17044
17135
|
return;
|
|
17045
17136
|
}
|
|
@@ -17053,7 +17144,7 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
17053
17144
|
* the counter to rotate past stuck todos once they cross `todoMaxAttempts`.
|
|
17054
17145
|
*/
|
|
17055
17146
|
async bumpTodoAttempt(todoId) {
|
|
17056
|
-
const current = await loadGoal(
|
|
17147
|
+
const current = await this.loadGoal();
|
|
17057
17148
|
if (!current) return;
|
|
17058
17149
|
const attempts = { ...current.todoAttempts ?? {} };
|
|
17059
17150
|
attempts[todoId] = (attempts[todoId] ?? 0) + 1;
|
|
@@ -17067,7 +17158,7 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
17067
17158
|
* goal is already `completed`.
|
|
17068
17159
|
*/
|
|
17069
17160
|
async markGoalCompleted(action, note) {
|
|
17070
|
-
const current = await loadGoal(
|
|
17161
|
+
const current = await this.loadGoal();
|
|
17071
17162
|
if (!current) return;
|
|
17072
17163
|
if (current.goalState === "completed") return;
|
|
17073
17164
|
const withFlag = { ...current, goalState: "completed" };
|
|
@@ -17078,6 +17169,11 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
17078
17169
|
note: note.slice(0, 240)
|
|
17079
17170
|
});
|
|
17080
17171
|
await saveGoal(this.goalPath, withEntry, this.opts.events);
|
|
17172
|
+
try {
|
|
17173
|
+
const { unlink: unlink12 } = await import("node:fs/promises");
|
|
17174
|
+
await unlink12(this.goalPath);
|
|
17175
|
+
} catch {
|
|
17176
|
+
}
|
|
17081
17177
|
this.opts.onEternalStop?.();
|
|
17082
17178
|
}
|
|
17083
17179
|
/**
|
|
@@ -17086,7 +17182,7 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
17086
17182
|
* `onEternalStop` so the REPL returns to normal mode.
|
|
17087
17183
|
*/
|
|
17088
17184
|
async clearGoalManually(note) {
|
|
17089
|
-
const current = await loadGoal(
|
|
17185
|
+
const current = await this.loadGoal();
|
|
17090
17186
|
if (current) {
|
|
17091
17187
|
const abandoned = { ...current, goalState: "abandoned" };
|
|
17092
17188
|
await saveGoal(this.goalPath, abandoned, this.opts.events);
|
|
@@ -17134,7 +17230,23 @@ Recent work:
|
|
|
17134
17230
|
${recentJournal}` : ""
|
|
17135
17231
|
].join("\n"),
|
|
17136
17232
|
risk: "high",
|
|
17137
|
-
fallback: "continue"
|
|
17233
|
+
fallback: "continue",
|
|
17234
|
+
// Structured verdict — completion is control-plane input, decided by
|
|
17235
|
+
// exact option id rather than sniffing prose for "done"/"complete".
|
|
17236
|
+
options: [
|
|
17237
|
+
{
|
|
17238
|
+
id: "goal_complete",
|
|
17239
|
+
label: "The goal is complete \u2014 stop the eternal run",
|
|
17240
|
+
consequence: "The engine stops and the goal file is archived.",
|
|
17241
|
+
risk: "high"
|
|
17242
|
+
},
|
|
17243
|
+
{
|
|
17244
|
+
id: "keep_working",
|
|
17245
|
+
label: "Not complete yet \u2014 keep working",
|
|
17246
|
+
consequence: "The engine continues iterating on the goal.",
|
|
17247
|
+
risk: "low"
|
|
17248
|
+
}
|
|
17249
|
+
]
|
|
17138
17250
|
});
|
|
17139
17251
|
const summary = formatDecisionSummary(decision, {
|
|
17140
17252
|
id: `goal-done-${goal.iterations}`,
|
|
@@ -17154,8 +17266,7 @@ ${recentJournal}` : ""
|
|
|
17154
17266
|
return false;
|
|
17155
17267
|
}
|
|
17156
17268
|
if (decision.type === "answer") {
|
|
17157
|
-
|
|
17158
|
-
return text.includes("complete") || text.includes("done") || text.includes("yes");
|
|
17269
|
+
return decision.optionId === "goal_complete";
|
|
17159
17270
|
}
|
|
17160
17271
|
return true;
|
|
17161
17272
|
} catch {
|
|
@@ -17166,13 +17277,13 @@ ${recentJournal}` : ""
|
|
|
17166
17277
|
* Persist a progress update from the agent's [PROGRESS: N%] output.
|
|
17167
17278
|
*/
|
|
17168
17279
|
async updateProgress(progress, note) {
|
|
17169
|
-
const current = await loadGoal(
|
|
17280
|
+
const current = await this.loadGoal();
|
|
17170
17281
|
if (!current) return;
|
|
17171
17282
|
const updated = recordProgress(current, progress, note);
|
|
17172
17283
|
await saveGoal(this.goalPath, updated, this.opts.events);
|
|
17173
17284
|
}
|
|
17174
17285
|
async persistEngineState(state) {
|
|
17175
|
-
const current = await loadGoal(
|
|
17286
|
+
const current = await this.loadGoal();
|
|
17176
17287
|
if (!current) return;
|
|
17177
17288
|
if (current.engineState === state) return;
|
|
17178
17289
|
await saveGoal(this.goalPath, { ...current, engineState: state }, this.opts.events);
|
|
@@ -17361,6 +17472,27 @@ var ParallelEternalEngine = class {
|
|
|
17361
17472
|
agentFactory;
|
|
17362
17473
|
dispatchEnabled;
|
|
17363
17474
|
dispatchClassifier;
|
|
17475
|
+
/**
|
|
17476
|
+
* Emit a structured error. Uses the configured Logger when available;
|
|
17477
|
+
* falls back to console.error(JSON) so events are never silently dropped.
|
|
17478
|
+
*/
|
|
17479
|
+
logError(msg, ctx) {
|
|
17480
|
+
if (this.opts.logger) {
|
|
17481
|
+
this.opts.logger.error(msg, ctx);
|
|
17482
|
+
} else {
|
|
17483
|
+
console.error(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
17484
|
+
}
|
|
17485
|
+
}
|
|
17486
|
+
/**
|
|
17487
|
+
* Load the goal file with structured logging for parse/schema warnings.
|
|
17488
|
+
*/
|
|
17489
|
+
loadGoal() {
|
|
17490
|
+
return loadGoal(
|
|
17491
|
+
this.goalPath,
|
|
17492
|
+
this.opts.events,
|
|
17493
|
+
this.opts.logger ? (msg) => this.opts.logger.warn(msg) : void 0
|
|
17494
|
+
);
|
|
17495
|
+
}
|
|
17364
17496
|
get currentState() {
|
|
17365
17497
|
return this.state;
|
|
17366
17498
|
}
|
|
@@ -17373,13 +17505,10 @@ var ParallelEternalEngine = class {
|
|
|
17373
17505
|
stop() {
|
|
17374
17506
|
this.stopRequested = true;
|
|
17375
17507
|
void this.persistState("stopped").catch((err) => {
|
|
17376
|
-
|
|
17377
|
-
|
|
17378
|
-
event: "engine.persist_state_failed",
|
|
17379
|
-
|
|
17380
|
-
context: { expectedState: "stopped" },
|
|
17381
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
17382
|
-
}));
|
|
17508
|
+
this.logError(
|
|
17509
|
+
"Engine persist state failed",
|
|
17510
|
+
{ event: "engine.persist_state_failed", message: toErrorMessage(err), context: { expectedState: "stopped" } }
|
|
17511
|
+
);
|
|
17383
17512
|
});
|
|
17384
17513
|
this.state = "stopped";
|
|
17385
17514
|
}
|
|
@@ -17433,7 +17562,7 @@ var ParallelEternalEngine = class {
|
|
|
17433
17562
|
this.opts.onStage?.(stage);
|
|
17434
17563
|
};
|
|
17435
17564
|
this.iterations++;
|
|
17436
|
-
const goal = await loadGoal(
|
|
17565
|
+
const goal = await this.loadGoal();
|
|
17437
17566
|
if (!goal) {
|
|
17438
17567
|
this.stopRequested = true;
|
|
17439
17568
|
emit({ phase: "stopped" });
|
|
@@ -17577,13 +17706,10 @@ ${personaLine}Task: ${task}
|
|
|
17577
17706
|
taskIds.push(taskId);
|
|
17578
17707
|
await coordinator.assign(spec);
|
|
17579
17708
|
} catch (err) {
|
|
17580
|
-
|
|
17581
|
-
|
|
17582
|
-
event: "parallel_engine.spawn_failed",
|
|
17583
|
-
|
|
17584
|
-
context: { slot: i, task, subagentId },
|
|
17585
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
17586
|
-
}));
|
|
17709
|
+
this.logError(
|
|
17710
|
+
"Parallel spawn failed",
|
|
17711
|
+
{ event: "parallel_engine.spawn_failed", message: toErrorMessage(err), context: { slot: i, task, subagentId } }
|
|
17712
|
+
);
|
|
17587
17713
|
}
|
|
17588
17714
|
})()
|
|
17589
17715
|
);
|
|
@@ -17609,13 +17735,10 @@ ${personaLine}Task: ${task}
|
|
|
17609
17735
|
clearTimeout(timer);
|
|
17610
17736
|
}
|
|
17611
17737
|
} catch (err) {
|
|
17612
|
-
|
|
17613
|
-
|
|
17614
|
-
event: "parallel_engine.brainstorm_results_failed",
|
|
17615
|
-
|
|
17616
|
-
context: { slotCount, taskIds },
|
|
17617
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
17618
|
-
}));
|
|
17738
|
+
this.logError(
|
|
17739
|
+
"Brainstorm results failed",
|
|
17740
|
+
{ event: "parallel_engine.brainstorm_results_failed", message: toErrorMessage(err), context: { slotCount, taskIds } }
|
|
17741
|
+
);
|
|
17619
17742
|
results = coordinator.results().slice(-taskIds.length);
|
|
17620
17743
|
}
|
|
17621
17744
|
await Promise.allSettled(subagentIds.map((id) => coordinator.remove(id)));
|
|
@@ -17650,13 +17773,10 @@ ${personaLine}Task: ${task}
|
|
|
17650
17773
|
}
|
|
17651
17774
|
}
|
|
17652
17775
|
} catch (err) {
|
|
17653
|
-
|
|
17654
|
-
|
|
17655
|
-
event: "parallel_engine.git_status_failed",
|
|
17656
|
-
|
|
17657
|
-
context: { projectRoot: this.opts.projectRoot },
|
|
17658
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
17659
|
-
}));
|
|
17776
|
+
this.logError(
|
|
17777
|
+
"Git status failed",
|
|
17778
|
+
{ event: "parallel_engine.git_status_failed", message: toErrorMessage(err), context: { projectRoot: this.opts.projectRoot } }
|
|
17779
|
+
);
|
|
17660
17780
|
}
|
|
17661
17781
|
}
|
|
17662
17782
|
if (tasks.length < this.slots) {
|
|
@@ -17737,7 +17857,7 @@ ${lastFew}` : "No prior iterations.",
|
|
|
17737
17857
|
// Helpers
|
|
17738
17858
|
// -------------------------------------------------------------------------
|
|
17739
17859
|
async appendIterationEntry(entry) {
|
|
17740
|
-
const current = await loadGoal(
|
|
17860
|
+
const current = await this.loadGoal();
|
|
17741
17861
|
if (!current) return;
|
|
17742
17862
|
const updated = appendJournal(current, entry);
|
|
17743
17863
|
await saveGoal(this.goalPath, updated, this.opts.events);
|
|
@@ -17752,7 +17872,7 @@ ${lastFew}` : "No prior iterations.",
|
|
|
17752
17872
|
await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
|
|
17753
17873
|
}
|
|
17754
17874
|
async persistState(state) {
|
|
17755
|
-
const current = await loadGoal(
|
|
17875
|
+
const current = await this.loadGoal();
|
|
17756
17876
|
if (!current) return;
|
|
17757
17877
|
if (current.engineState === state) return;
|
|
17758
17878
|
await saveGoal(this.goalPath, { ...current, engineState: state }, this.opts.events);
|
|
@@ -19991,12 +20111,12 @@ ${post.additionalContext}`;
|
|
|
19991
20111
|
if (parentSignal.reason instanceof Error) throw parentSignal.reason;
|
|
19992
20112
|
throw new Error(typeof parentSignal.reason === "string" ? parentSignal.reason : "aborted");
|
|
19993
20113
|
}
|
|
19994
|
-
const
|
|
19995
|
-
|
|
19996
|
-
|
|
19997
|
-
|
|
19998
|
-
|
|
19999
|
-
|
|
20114
|
+
const combined = tool.managesOwnTimeout ? parentSignal : AbortSignal.any([
|
|
20115
|
+
parentSignal,
|
|
20116
|
+
AbortSignal.timeout(
|
|
20117
|
+
clampTimeoutMs(tool.timeoutMs ?? this.iterationTimeoutMs, this.maxToolTimeoutMs)
|
|
20118
|
+
)
|
|
20119
|
+
]);
|
|
20000
20120
|
let output;
|
|
20001
20121
|
const toolPromise = typeof tool.executeStream === "function" ? this.runStreamedTool(tool, input, ctx, combined, toolUseId) : (async () => tool.execute(input, ctx, { signal: combined }))();
|
|
20002
20122
|
toolPromise.catch(() => {
|
|
@@ -21960,34 +22080,35 @@ function wireMetricsToEvents(events, sink) {
|
|
|
21960
22080
|
retryable: String(e.retryable)
|
|
21961
22081
|
})
|
|
21962
22082
|
),
|
|
21963
|
-
events.on(
|
|
22083
|
+
events.on(
|
|
22084
|
+
"tool.started",
|
|
22085
|
+
(e) => sink.counter("tool.starts.total", 1, { tool: metricToolName(e.name) })
|
|
22086
|
+
),
|
|
21964
22087
|
events.on("tool.executed", (e) => {
|
|
21965
|
-
|
|
21966
|
-
sink.
|
|
22088
|
+
const tool = metricToolName(e.name);
|
|
22089
|
+
sink.counter("tool.executions.total", 1, { tool, ok: String(e.ok) });
|
|
22090
|
+
sink.histogram("tool.duration_ms", e.durationMs, { tool });
|
|
21967
22091
|
}),
|
|
21968
22092
|
events.on("token.threshold", (e) => sink.gauge("agent.tokens.used", e.used)),
|
|
21969
22093
|
events.on("compaction.fired", (e) => {
|
|
21970
22094
|
sink.counter("compaction.fired.total");
|
|
21971
22095
|
sink.histogram("compaction.reduction_tokens", e.report.before - e.report.after);
|
|
21972
22096
|
}),
|
|
21973
|
-
|
|
21974
|
-
|
|
21975
|
-
|
|
21976
|
-
),
|
|
21977
|
-
events.on(
|
|
21978
|
-
|
|
21979
|
-
(e) => sink.counter("mcp.reconnects.total", 1, { server: e.name })
|
|
21980
|
-
),
|
|
21981
|
-
events.on(
|
|
21982
|
-
"mcp.server.disconnected",
|
|
21983
|
-
(e) => sink.counter("mcp.disconnects.total", 1, { server: e.name })
|
|
21984
|
-
),
|
|
22097
|
+
// MCP server names are user-controlled and may contain tenant/project
|
|
22098
|
+
// identifiers. Keep them out of metric labels to bound cardinality and
|
|
22099
|
+
// prevent operational metadata leaks.
|
|
22100
|
+
events.on("mcp.server.connected", () => sink.counter("mcp.connects.total")),
|
|
22101
|
+
events.on("mcp.server.reconnected", () => sink.counter("mcp.reconnects.total")),
|
|
22102
|
+
events.on("mcp.server.disconnected", () => sink.counter("mcp.disconnects.total")),
|
|
21985
22103
|
events.on("error", (e) => sink.counter("agent.errors.total", 1, { phase: e.phase }))
|
|
21986
22104
|
);
|
|
21987
22105
|
return () => {
|
|
21988
22106
|
for (const u of unsubs) u();
|
|
21989
22107
|
};
|
|
21990
22108
|
}
|
|
22109
|
+
function metricToolName(name) {
|
|
22110
|
+
return name.startsWith("mcp__") ? "mcp_proxy" : name;
|
|
22111
|
+
}
|
|
21991
22112
|
|
|
21992
22113
|
// src/observability/prometheus.ts
|
|
21993
22114
|
var NUMBER_FORMAT_INFINITY = "NaN";
|
|
@@ -22398,6 +22519,172 @@ function startOtlpTraceExporter(opts) {
|
|
|
22398
22519
|
// src/security/permission-policy.ts
|
|
22399
22520
|
import * as fs10 from "node:fs/promises";
|
|
22400
22521
|
|
|
22522
|
+
// src/security/permission-policy-schema.ts
|
|
22523
|
+
var TRUST_POLICY_LIMITS = Object.freeze({
|
|
22524
|
+
maxTools: 1e3,
|
|
22525
|
+
maxPatternsPerRule: 1e3,
|
|
22526
|
+
maxToolNameChars: 256,
|
|
22527
|
+
maxPatternChars: 4096
|
|
22528
|
+
});
|
|
22529
|
+
var TRUST_POLICY_JSON_SCHEMA = Object.freeze({
|
|
22530
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
22531
|
+
$id: "https://wrongstack.dev/schemas/trust-policy-v1.json",
|
|
22532
|
+
title: "WrongStack Trust Policy",
|
|
22533
|
+
type: "object",
|
|
22534
|
+
maxProperties: TRUST_POLICY_LIMITS.maxTools,
|
|
22535
|
+
propertyNames: {
|
|
22536
|
+
minLength: 1,
|
|
22537
|
+
maxLength: TRUST_POLICY_LIMITS.maxToolNameChars,
|
|
22538
|
+
not: { enum: ["__proto__", "prototype", "constructor"] }
|
|
22539
|
+
},
|
|
22540
|
+
additionalProperties: {
|
|
22541
|
+
type: "object",
|
|
22542
|
+
additionalProperties: false,
|
|
22543
|
+
properties: {
|
|
22544
|
+
allow: {
|
|
22545
|
+
type: "array",
|
|
22546
|
+
maxItems: TRUST_POLICY_LIMITS.maxPatternsPerRule,
|
|
22547
|
+
items: { type: "string", minLength: 1, maxLength: TRUST_POLICY_LIMITS.maxPatternChars }
|
|
22548
|
+
},
|
|
22549
|
+
deny: {
|
|
22550
|
+
type: "array",
|
|
22551
|
+
maxItems: TRUST_POLICY_LIMITS.maxPatternsPerRule,
|
|
22552
|
+
items: { type: "string", minLength: 1, maxLength: TRUST_POLICY_LIMITS.maxPatternChars }
|
|
22553
|
+
},
|
|
22554
|
+
auto: { type: "boolean" },
|
|
22555
|
+
trustWorkdir: { type: "boolean" },
|
|
22556
|
+
denyPrivate: { type: "boolean" }
|
|
22557
|
+
}
|
|
22558
|
+
}
|
|
22559
|
+
});
|
|
22560
|
+
var RULE_FIELDS = /* @__PURE__ */ new Set(["allow", "deny", "auto", "trustWorkdir", "denyPrivate"]);
|
|
22561
|
+
var UNSAFE_PROPERTY_NAMES = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
22562
|
+
function isRecord5(value) {
|
|
22563
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22564
|
+
}
|
|
22565
|
+
function error(diagnostics, code, path29, message) {
|
|
22566
|
+
diagnostics.push({ severity: "error", code, path: path29, message });
|
|
22567
|
+
}
|
|
22568
|
+
function validatePatterns(value, path29, diagnostics) {
|
|
22569
|
+
if (!Array.isArray(value)) {
|
|
22570
|
+
error(diagnostics, "invalid_pattern_list", path29, "must be an array of strings");
|
|
22571
|
+
return void 0;
|
|
22572
|
+
}
|
|
22573
|
+
if (value.length > TRUST_POLICY_LIMITS.maxPatternsPerRule) {
|
|
22574
|
+
error(
|
|
22575
|
+
diagnostics,
|
|
22576
|
+
"too_many_patterns",
|
|
22577
|
+
path29,
|
|
22578
|
+
`must contain at most ${TRUST_POLICY_LIMITS.maxPatternsPerRule} patterns`
|
|
22579
|
+
);
|
|
22580
|
+
return void 0;
|
|
22581
|
+
}
|
|
22582
|
+
const patterns = [];
|
|
22583
|
+
const seen = /* @__PURE__ */ new Set();
|
|
22584
|
+
for (const [index, pattern] of value.entries()) {
|
|
22585
|
+
const itemPath = `${path29}[${index}]`;
|
|
22586
|
+
if (typeof pattern !== "string" || pattern.length === 0 || pattern.length > TRUST_POLICY_LIMITS.maxPatternChars) {
|
|
22587
|
+
error(
|
|
22588
|
+
diagnostics,
|
|
22589
|
+
"invalid_pattern",
|
|
22590
|
+
itemPath,
|
|
22591
|
+
`must be a non-empty string up to ${TRUST_POLICY_LIMITS.maxPatternChars} characters`
|
|
22592
|
+
);
|
|
22593
|
+
continue;
|
|
22594
|
+
}
|
|
22595
|
+
if (seen.has(pattern)) {
|
|
22596
|
+
diagnostics.push({
|
|
22597
|
+
severity: "warning",
|
|
22598
|
+
code: "duplicate_pattern",
|
|
22599
|
+
path: itemPath,
|
|
22600
|
+
message: "duplicates an earlier pattern and was normalized away"
|
|
22601
|
+
});
|
|
22602
|
+
continue;
|
|
22603
|
+
}
|
|
22604
|
+
seen.add(pattern);
|
|
22605
|
+
patterns.push(pattern);
|
|
22606
|
+
}
|
|
22607
|
+
return patterns;
|
|
22608
|
+
}
|
|
22609
|
+
function validateTrustPolicy(value) {
|
|
22610
|
+
const diagnostics = [];
|
|
22611
|
+
if (!isRecord5(value)) {
|
|
22612
|
+
return {
|
|
22613
|
+
ok: false,
|
|
22614
|
+
diagnostics: [
|
|
22615
|
+
{
|
|
22616
|
+
severity: "error",
|
|
22617
|
+
code: "invalid_root",
|
|
22618
|
+
path: "$",
|
|
22619
|
+
message: "trust policy must be an object"
|
|
22620
|
+
}
|
|
22621
|
+
]
|
|
22622
|
+
};
|
|
22623
|
+
}
|
|
22624
|
+
const entries = Object.entries(value);
|
|
22625
|
+
if (entries.length > TRUST_POLICY_LIMITS.maxTools) {
|
|
22626
|
+
error(
|
|
22627
|
+
diagnostics,
|
|
22628
|
+
"too_many_tools",
|
|
22629
|
+
"$",
|
|
22630
|
+
`must contain at most ${TRUST_POLICY_LIMITS.maxTools} tool rules`
|
|
22631
|
+
);
|
|
22632
|
+
}
|
|
22633
|
+
const policy = {};
|
|
22634
|
+
for (const [toolName, rawRule] of entries.slice(0, TRUST_POLICY_LIMITS.maxTools)) {
|
|
22635
|
+
const toolPath = `$[${JSON.stringify(toolName)}]`;
|
|
22636
|
+
if (toolName.length === 0 || toolName.length > TRUST_POLICY_LIMITS.maxToolNameChars) {
|
|
22637
|
+
error(
|
|
22638
|
+
diagnostics,
|
|
22639
|
+
"invalid_tool_name",
|
|
22640
|
+
toolPath,
|
|
22641
|
+
`tool name must be 1-${TRUST_POLICY_LIMITS.maxToolNameChars} characters`
|
|
22642
|
+
);
|
|
22643
|
+
continue;
|
|
22644
|
+
}
|
|
22645
|
+
if (UNSAFE_PROPERTY_NAMES.has(toolName)) {
|
|
22646
|
+
error(
|
|
22647
|
+
diagnostics,
|
|
22648
|
+
"unsafe_tool_name",
|
|
22649
|
+
toolPath,
|
|
22650
|
+
"reserved object property names are not valid tool rules"
|
|
22651
|
+
);
|
|
22652
|
+
continue;
|
|
22653
|
+
}
|
|
22654
|
+
if (!isRecord5(rawRule)) {
|
|
22655
|
+
error(diagnostics, "invalid_rule", toolPath, "tool rule must be an object");
|
|
22656
|
+
continue;
|
|
22657
|
+
}
|
|
22658
|
+
for (const field of Object.keys(rawRule)) {
|
|
22659
|
+
if (!RULE_FIELDS.has(field)) {
|
|
22660
|
+
error(
|
|
22661
|
+
diagnostics,
|
|
22662
|
+
"unknown_field",
|
|
22663
|
+
`${toolPath}.${field}`,
|
|
22664
|
+
`unknown policy field "${field}"`
|
|
22665
|
+
);
|
|
22666
|
+
}
|
|
22667
|
+
}
|
|
22668
|
+
const rule = {};
|
|
22669
|
+
for (const field of ["allow", "deny"]) {
|
|
22670
|
+
if (rawRule[field] === void 0) continue;
|
|
22671
|
+
const patterns = validatePatterns(rawRule[field], `${toolPath}.${field}`, diagnostics);
|
|
22672
|
+
if (patterns) rule[field] = patterns;
|
|
22673
|
+
}
|
|
22674
|
+
for (const field of ["auto", "trustWorkdir", "denyPrivate"]) {
|
|
22675
|
+
const setting = rawRule[field];
|
|
22676
|
+
if (setting === void 0) continue;
|
|
22677
|
+
if (typeof setting !== "boolean") {
|
|
22678
|
+
error(diagnostics, "invalid_boolean", `${toolPath}.${field}`, "must be a boolean");
|
|
22679
|
+
} else {
|
|
22680
|
+
rule[field] = setting;
|
|
22681
|
+
}
|
|
22682
|
+
}
|
|
22683
|
+
policy[toolName] = rule;
|
|
22684
|
+
}
|
|
22685
|
+
return diagnostics.some((diagnostic) => diagnostic.severity === "error") ? { ok: false, diagnostics } : { ok: true, policy, diagnostics };
|
|
22686
|
+
}
|
|
22687
|
+
|
|
22401
22688
|
// src/utils/lru-cache.ts
|
|
22402
22689
|
var LruCache = class {
|
|
22403
22690
|
store = /* @__PURE__ */ new Map();
|
|
@@ -22566,6 +22853,8 @@ var DefaultPermissionPolicy = class {
|
|
|
22566
22853
|
* the LRU evicts the least-recently-used entries first.
|
|
22567
22854
|
*/
|
|
22568
22855
|
_evalCache = new LruCache(500);
|
|
22856
|
+
policyDiagnostics = [];
|
|
22857
|
+
policyInvalid = false;
|
|
22569
22858
|
constructor(opts) {
|
|
22570
22859
|
this.trustFile = opts.trustFile;
|
|
22571
22860
|
this.yolo = opts.yolo ?? false;
|
|
@@ -22609,13 +22898,50 @@ var DefaultPermissionPolicy = class {
|
|
|
22609
22898
|
getConfirmDestructive() {
|
|
22610
22899
|
return this.confirmDestructive;
|
|
22611
22900
|
}
|
|
22901
|
+
/** Read-only diagnostics for policy inspector/editor surfaces. */
|
|
22902
|
+
getPolicyDiagnostics() {
|
|
22903
|
+
return this.policyDiagnostics.map((diagnostic) => ({ ...diagnostic }));
|
|
22904
|
+
}
|
|
22612
22905
|
async reload() {
|
|
22906
|
+
this.policyDiagnostics = [];
|
|
22907
|
+
this.policyInvalid = false;
|
|
22613
22908
|
try {
|
|
22614
22909
|
const raw = await fs10.readFile(this.trustFile, "utf8");
|
|
22615
22910
|
const parsed = safeParse(raw);
|
|
22616
|
-
if (parsed.ok
|
|
22617
|
-
|
|
22911
|
+
if (!parsed.ok) {
|
|
22912
|
+
this.policy = {};
|
|
22913
|
+
this.policyInvalid = true;
|
|
22914
|
+
this.policyDiagnostics = [
|
|
22915
|
+
{
|
|
22916
|
+
severity: "error",
|
|
22917
|
+
code: "invalid_json",
|
|
22918
|
+
path: "$",
|
|
22919
|
+
message: parsed.error ?? "trust policy is not valid JSON"
|
|
22920
|
+
}
|
|
22921
|
+
];
|
|
22922
|
+
} else {
|
|
22923
|
+
const validation = validateTrustPolicy(parsed.value);
|
|
22924
|
+
this.policyDiagnostics = validation.diagnostics;
|
|
22925
|
+
if (validation.ok) {
|
|
22926
|
+
this.policy = validation.policy;
|
|
22927
|
+
} else {
|
|
22928
|
+
this.policy = {};
|
|
22929
|
+
this.policyInvalid = true;
|
|
22930
|
+
}
|
|
22931
|
+
}
|
|
22932
|
+
} catch (err) {
|
|
22618
22933
|
this.policy = {};
|
|
22934
|
+
if (err.code !== "ENOENT") {
|
|
22935
|
+
this.policyInvalid = true;
|
|
22936
|
+
this.policyDiagnostics = [
|
|
22937
|
+
{
|
|
22938
|
+
severity: "error",
|
|
22939
|
+
code: "read_error",
|
|
22940
|
+
path: "$",
|
|
22941
|
+
message: err instanceof Error ? err.message : String(err)
|
|
22942
|
+
}
|
|
22943
|
+
];
|
|
22944
|
+
}
|
|
22619
22945
|
}
|
|
22620
22946
|
this.wildcardEntries = [];
|
|
22621
22947
|
for (const [key, val] of Object.entries(this.policy)) {
|
|
@@ -22628,6 +22954,13 @@ var DefaultPermissionPolicy = class {
|
|
|
22628
22954
|
}
|
|
22629
22955
|
async evaluate(tool, input, ctx) {
|
|
22630
22956
|
if (!this.loaded) await this.reload();
|
|
22957
|
+
if (this.policyInvalid) {
|
|
22958
|
+
return {
|
|
22959
|
+
permission: "deny",
|
|
22960
|
+
source: "deny",
|
|
22961
|
+
reason: "trust policy is invalid; refusing tool execution until it is repaired"
|
|
22962
|
+
};
|
|
22963
|
+
}
|
|
22631
22964
|
const namespaceEntry = this.findNamespaceEntry(tool.name);
|
|
22632
22965
|
const entry = this.policy[tool.name] ?? namespaceEntry;
|
|
22633
22966
|
const subject = subjectForToolInput(tool.name, input, tool.subjectKey);
|
|
@@ -22757,6 +23090,9 @@ var DefaultPermissionPolicy = class {
|
|
|
22757
23090
|
}
|
|
22758
23091
|
async trust(rule) {
|
|
22759
23092
|
if (!this.loaded) await this.reload();
|
|
23093
|
+
if (this.policyInvalid) {
|
|
23094
|
+
throw new Error("Cannot update trust rules while trust.json is invalid; repair it first.");
|
|
23095
|
+
}
|
|
22760
23096
|
const entry = this.policy[rule.tool] ?? {};
|
|
22761
23097
|
entry.allow = Array.from(/* @__PURE__ */ new Set([...entry.allow ?? [], rule.pattern]));
|
|
22762
23098
|
this.policy[rule.tool] = entry;
|
|
@@ -22775,6 +23111,9 @@ var DefaultPermissionPolicy = class {
|
|
|
22775
23111
|
/** Persist a deny rule — this tool+pattern pair is permanently blocked. */
|
|
22776
23112
|
async deny(rule) {
|
|
22777
23113
|
if (!this.loaded) await this.reload();
|
|
23114
|
+
if (this.policyInvalid) {
|
|
23115
|
+
throw new Error("Cannot update deny rules while trust.json is invalid; repair it first.");
|
|
23116
|
+
}
|
|
22778
23117
|
const entry = this.policy[rule.tool] ?? {};
|
|
22779
23118
|
entry.deny = Array.from(/* @__PURE__ */ new Set([...entry.deny ?? [], rule.pattern]));
|
|
22780
23119
|
this.policy[rule.tool] = entry;
|
|
@@ -23178,31 +23517,40 @@ function unwrapDataKey(buf, keyFile) {
|
|
|
23178
23517
|
});
|
|
23179
23518
|
}
|
|
23180
23519
|
}
|
|
23181
|
-
function checkKeyFilePermissions(keyFile) {
|
|
23520
|
+
function checkKeyFilePermissions(keyFile, opts) {
|
|
23182
23521
|
if (process.platform === "win32") return;
|
|
23522
|
+
const warn = opts?.warn ?? ((msg) => console.warn(msg));
|
|
23183
23523
|
try {
|
|
23184
23524
|
const stat13 = fs11.statSync(keyFile);
|
|
23185
23525
|
const actualMode = stat13.mode & 511;
|
|
23186
23526
|
if (actualMode !== KEY_FILE_MODE) {
|
|
23187
|
-
|
|
23188
|
-
|
|
23189
|
-
|
|
23190
|
-
message: `Key file ${keyFile} has mode ${actualMode.toString(8)} \u2014 expected ${KEY_FILE_MODE.toString(8)}. Run: chmod ${KEY_FILE_MODE.toString(8)} ${keyFile}`,
|
|
23191
|
-
keyFile,
|
|
23192
|
-
expectedMode: KEY_FILE_MODE,
|
|
23193
|
-
actualMode,
|
|
23194
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
23195
|
-
}));
|
|
23527
|
+
warn(
|
|
23528
|
+
`Key file ${keyFile} has mode ${actualMode.toString(8)} \u2014 expected ${KEY_FILE_MODE.toString(8)}. Run: chmod ${KEY_FILE_MODE.toString(8)} ${keyFile}`
|
|
23529
|
+
);
|
|
23196
23530
|
}
|
|
23197
23531
|
} catch {
|
|
23198
23532
|
}
|
|
23199
23533
|
}
|
|
23200
23534
|
var DefaultSecretVault = class {
|
|
23201
23535
|
keyFile;
|
|
23536
|
+
logger;
|
|
23202
23537
|
key;
|
|
23203
23538
|
_keyVersion = 1;
|
|
23204
23539
|
constructor(opts) {
|
|
23205
23540
|
this.keyFile = opts.keyFile;
|
|
23541
|
+
this.logger = opts.logger;
|
|
23542
|
+
}
|
|
23543
|
+
/**
|
|
23544
|
+
* Emit a structured warning. Uses the configured Logger when available;
|
|
23545
|
+
* falls back to console.warn(JSON) so warnings are never silently dropped
|
|
23546
|
+
* during early boot.
|
|
23547
|
+
*/
|
|
23548
|
+
logWarn(msg, ctx) {
|
|
23549
|
+
if (this.logger) {
|
|
23550
|
+
this.logger.warn(msg, ctx);
|
|
23551
|
+
} else {
|
|
23552
|
+
console.warn(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
23553
|
+
}
|
|
23206
23554
|
}
|
|
23207
23555
|
/** Current key version. Starts at 1; incremented by rotateKey(). */
|
|
23208
23556
|
get keyVersion() {
|
|
@@ -23281,7 +23629,7 @@ var DefaultSecretVault = class {
|
|
|
23281
23629
|
newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
|
|
23282
23630
|
fs11.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
|
|
23283
23631
|
}
|
|
23284
|
-
checkKeyFilePermissions(this.keyFile);
|
|
23632
|
+
checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
|
|
23285
23633
|
this.key = newKey;
|
|
23286
23634
|
this._keyVersion = newVersion;
|
|
23287
23635
|
return { oldVersion, newVersion };
|
|
@@ -23300,7 +23648,7 @@ var DefaultSecretVault = class {
|
|
|
23300
23648
|
fs11.writeFileSync(this.keyFile, wrapDataKey(this.key, this._keyVersion, passphrase), {
|
|
23301
23649
|
mode: 384
|
|
23302
23650
|
});
|
|
23303
|
-
checkKeyFilePermissions(this.keyFile);
|
|
23651
|
+
checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
|
|
23304
23652
|
} catch {
|
|
23305
23653
|
}
|
|
23306
23654
|
}
|
|
@@ -23312,13 +23660,13 @@ var DefaultSecretVault = class {
|
|
|
23312
23660
|
const { key: key2, version } = unwrapDataKey(buf, this.keyFile);
|
|
23313
23661
|
this.key = key2;
|
|
23314
23662
|
this._keyVersion = version;
|
|
23315
|
-
checkKeyFilePermissions(this.keyFile);
|
|
23663
|
+
checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
|
|
23316
23664
|
return this.key;
|
|
23317
23665
|
}
|
|
23318
23666
|
if (buf.length === KEY_BYTES) {
|
|
23319
23667
|
this.key = buf;
|
|
23320
23668
|
this._keyVersion = 1;
|
|
23321
|
-
checkKeyFilePermissions(this.keyFile);
|
|
23669
|
+
checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
|
|
23322
23670
|
this.migrateToWrappedIfPassphrase();
|
|
23323
23671
|
return this.key;
|
|
23324
23672
|
}
|
|
@@ -23342,7 +23690,7 @@ var DefaultSecretVault = class {
|
|
|
23342
23690
|
}
|
|
23343
23691
|
this.key = Buffer.from(key2);
|
|
23344
23692
|
this._keyVersion = version;
|
|
23345
|
-
checkKeyFilePermissions(this.keyFile);
|
|
23693
|
+
checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
|
|
23346
23694
|
this.migrateToWrappedIfPassphrase();
|
|
23347
23695
|
return this.key;
|
|
23348
23696
|
}
|
|
@@ -23367,13 +23715,13 @@ var DefaultSecretVault = class {
|
|
|
23367
23715
|
const { key: winnerKey, version } = unwrapDataKey(buf, this.keyFile);
|
|
23368
23716
|
this.key = winnerKey;
|
|
23369
23717
|
this._keyVersion = version;
|
|
23370
|
-
checkKeyFilePermissions(this.keyFile);
|
|
23718
|
+
checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
|
|
23371
23719
|
return this.key;
|
|
23372
23720
|
}
|
|
23373
23721
|
if (buf.length === KEY_BYTES) {
|
|
23374
23722
|
this.key = buf;
|
|
23375
23723
|
this._keyVersion = 1;
|
|
23376
|
-
checkKeyFilePermissions(this.keyFile);
|
|
23724
|
+
checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
|
|
23377
23725
|
return this.key;
|
|
23378
23726
|
}
|
|
23379
23727
|
if (buf.length === VERSIONED_KEY_FILE_SIZE) {
|
|
@@ -23389,7 +23737,7 @@ var DefaultSecretVault = class {
|
|
|
23389
23737
|
const winnerKey = buf.subarray(KEY_FILE_MAGIC.length + 1);
|
|
23390
23738
|
this.key = Buffer.from(winnerKey);
|
|
23391
23739
|
this._keyVersion = version;
|
|
23392
|
-
checkKeyFilePermissions(this.keyFile);
|
|
23740
|
+
checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
|
|
23393
23741
|
return this.key;
|
|
23394
23742
|
}
|
|
23395
23743
|
throw new ConfigError({
|
|
@@ -23944,6 +24292,10 @@ var KNOWN_DENIED_IN_PROJECT = [
|
|
|
23944
24292
|
key: "fleet",
|
|
23945
24293
|
reason: "Fleet supervision knobs: a repo-committed config could enable autonomous subagent spawning/steering/termination and mailbox traffic on the victim machine."
|
|
23946
24294
|
},
|
|
24295
|
+
{
|
|
24296
|
+
key: "brain",
|
|
24297
|
+
reason: "Brain decision-layer knobs: a repo-committed config could raise the autonomy risk ceiling, switch the Brain to headless (removing the human tier), or reroute Brain decisions to an attacker-chosen provider/model."
|
|
24298
|
+
},
|
|
23947
24299
|
{
|
|
23948
24300
|
key: "git",
|
|
23949
24301
|
reason: "Carries git.identity (GIT_AUTHOR_*/GIT_COMMITTER_* injection): a repo-committed config could spoof the author identity written into the victim's commit history (impersonation)."
|
|
@@ -23992,6 +24344,7 @@ var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
23992
24344
|
"extensions",
|
|
23993
24345
|
"acp",
|
|
23994
24346
|
"fleet",
|
|
24347
|
+
"brain",
|
|
23995
24348
|
"git"
|
|
23996
24349
|
]);
|
|
23997
24350
|
function assertInProjectAllowListComplete() {
|
|
@@ -24143,6 +24496,7 @@ var DefaultConfigLoader = class {
|
|
|
24143
24496
|
extraSources;
|
|
24144
24497
|
events;
|
|
24145
24498
|
traceId;
|
|
24499
|
+
logger;
|
|
24146
24500
|
jsonCache = /* @__PURE__ */ new Map();
|
|
24147
24501
|
constructor(opts) {
|
|
24148
24502
|
this.paths = opts.paths;
|
|
@@ -24151,6 +24505,19 @@ var DefaultConfigLoader = class {
|
|
|
24151
24505
|
this.extraSources = opts.sources ?? [];
|
|
24152
24506
|
this.events = opts.events;
|
|
24153
24507
|
this.traceId = opts.traceId;
|
|
24508
|
+
this.logger = opts.logger;
|
|
24509
|
+
}
|
|
24510
|
+
/**
|
|
24511
|
+
* Emit a structured warning. Uses the configured Logger when available;
|
|
24512
|
+
* falls back to console.warn(JSON) so warnings are never silently dropped
|
|
24513
|
+
* during early boot (before a Logger is constructed).
|
|
24514
|
+
*/
|
|
24515
|
+
logWarn(msg, ctx) {
|
|
24516
|
+
if (this.logger) {
|
|
24517
|
+
this.logger.warn(msg, ctx);
|
|
24518
|
+
} else {
|
|
24519
|
+
console.warn(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
24520
|
+
}
|
|
24154
24521
|
}
|
|
24155
24522
|
async load(opts = {}) {
|
|
24156
24523
|
let cfg = { ...BEHAVIOR_DEFAULTS };
|
|
@@ -24163,7 +24530,14 @@ var DefaultConfigLoader = class {
|
|
|
24163
24530
|
]);
|
|
24164
24531
|
cfg = deepMerge2(cfg, global);
|
|
24165
24532
|
cfg = deepMerge2(cfg, local);
|
|
24166
|
-
cfg = deepMerge2(
|
|
24533
|
+
cfg = deepMerge2(
|
|
24534
|
+
cfg,
|
|
24535
|
+
stripUnsafeInProjectFields(
|
|
24536
|
+
inProject,
|
|
24537
|
+
this.paths.inProjectConfig,
|
|
24538
|
+
this.logger ? (msg) => this.logger.warn(msg) : void 0
|
|
24539
|
+
)
|
|
24540
|
+
);
|
|
24167
24541
|
for (const [key, fn] of Object.entries(ENV_MAP)) {
|
|
24168
24542
|
const v = process.env[key];
|
|
24169
24543
|
if (v) fn(cfg, v);
|
|
@@ -24180,14 +24554,9 @@ var DefaultConfigLoader = class {
|
|
|
24180
24554
|
cfg = deepMerge2(cfg, patch);
|
|
24181
24555
|
}
|
|
24182
24556
|
} catch (err) {
|
|
24183
|
-
|
|
24184
|
-
|
|
24185
|
-
|
|
24186
|
-
event: "config.source_load_failed",
|
|
24187
|
-
source: src.name,
|
|
24188
|
-
message: toErrorMessage(err),
|
|
24189
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
24190
|
-
})
|
|
24557
|
+
this.logWarn(
|
|
24558
|
+
"Config source load failed",
|
|
24559
|
+
{ event: "config.source_load_failed", source: src.name, message: toErrorMessage(err) }
|
|
24191
24560
|
);
|
|
24192
24561
|
}
|
|
24193
24562
|
}
|
|
@@ -24247,14 +24616,9 @@ var DefaultConfigLoader = class {
|
|
|
24247
24616
|
durationMs: Date.now() - t0,
|
|
24248
24617
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
24249
24618
|
});
|
|
24250
|
-
|
|
24251
|
-
|
|
24252
|
-
|
|
24253
|
-
event: "config.defaults_read_failed",
|
|
24254
|
-
path: fp,
|
|
24255
|
-
message: toErrorMessage(err),
|
|
24256
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
24257
|
-
})
|
|
24619
|
+
this.logWarn(
|
|
24620
|
+
"Config defaults read failed",
|
|
24621
|
+
{ event: "config.defaults_read_failed", path: fp, message: toErrorMessage(err) }
|
|
24258
24622
|
);
|
|
24259
24623
|
return;
|
|
24260
24624
|
}
|
|
@@ -24288,14 +24652,9 @@ var DefaultConfigLoader = class {
|
|
|
24288
24652
|
durationMs: Date.now() - t0,
|
|
24289
24653
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
24290
24654
|
});
|
|
24291
|
-
|
|
24292
|
-
|
|
24293
|
-
|
|
24294
|
-
event: "config.defaults_write_failed",
|
|
24295
|
-
path: fp,
|
|
24296
|
-
message: toErrorMessage(err),
|
|
24297
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
24298
|
-
})
|
|
24655
|
+
this.logWarn(
|
|
24656
|
+
"Config defaults write failed",
|
|
24657
|
+
{ event: "config.defaults_write_failed", path: fp, message: toErrorMessage(err) }
|
|
24299
24658
|
);
|
|
24300
24659
|
}
|
|
24301
24660
|
}
|
|
@@ -24398,13 +24757,9 @@ var DefaultConfigLoader = class {
|
|
|
24398
24757
|
error: storageErrorString(err),
|
|
24399
24758
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
24400
24759
|
});
|
|
24401
|
-
|
|
24402
|
-
|
|
24403
|
-
|
|
24404
|
-
event: "config.sync_load_failed",
|
|
24405
|
-
message: toErrorMessage(err),
|
|
24406
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
24407
|
-
})
|
|
24760
|
+
this.logWarn(
|
|
24761
|
+
"Config sync load failed",
|
|
24762
|
+
{ event: "config.sync_load_failed", message: toErrorMessage(err) }
|
|
24408
24763
|
);
|
|
24409
24764
|
return null;
|
|
24410
24765
|
}
|
|
@@ -24434,14 +24789,9 @@ var DefaultConfigLoader = class {
|
|
|
24434
24789
|
error: storageErrorString(err),
|
|
24435
24790
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
24436
24791
|
});
|
|
24437
|
-
|
|
24438
|
-
|
|
24439
|
-
|
|
24440
|
-
event: "config.read_failed",
|
|
24441
|
-
path: file,
|
|
24442
|
-
message: toErrorMessage(err),
|
|
24443
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
24444
|
-
})
|
|
24792
|
+
this.logWarn(
|
|
24793
|
+
"Config read failed (stat)",
|
|
24794
|
+
{ event: "config.read_failed", path: file, message: toErrorMessage(err) }
|
|
24445
24795
|
);
|
|
24446
24796
|
return {};
|
|
24447
24797
|
}
|
|
@@ -24460,14 +24810,9 @@ var DefaultConfigLoader = class {
|
|
|
24460
24810
|
error: storageErrorString(err),
|
|
24461
24811
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
24462
24812
|
});
|
|
24463
|
-
|
|
24464
|
-
|
|
24465
|
-
|
|
24466
|
-
event: "config.read_failed",
|
|
24467
|
-
path: file,
|
|
24468
|
-
message: toErrorMessage(err),
|
|
24469
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
24470
|
-
})
|
|
24813
|
+
this.logWarn(
|
|
24814
|
+
"Config read failed (read)",
|
|
24815
|
+
{ event: "config.read_failed", path: file, message: toErrorMessage(err) }
|
|
24471
24816
|
);
|
|
24472
24817
|
}
|
|
24473
24818
|
this.jsonCache.set(file, { mtimeMs: null, value: {} });
|
|
@@ -24485,14 +24830,9 @@ var DefaultConfigLoader = class {
|
|
|
24485
24830
|
error: "parse error or empty file",
|
|
24486
24831
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
24487
24832
|
});
|
|
24488
|
-
|
|
24489
|
-
|
|
24490
|
-
|
|
24491
|
-
event: "config.parse_failed",
|
|
24492
|
-
path: file,
|
|
24493
|
-
message: "invalid JSON \u2014 falling back to defaults for this layer",
|
|
24494
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
24495
|
-
})
|
|
24833
|
+
this.logWarn(
|
|
24834
|
+
"Config parse failed \u2014 falling back to defaults",
|
|
24835
|
+
{ event: "config.parse_failed", path: file }
|
|
24496
24836
|
);
|
|
24497
24837
|
return {};
|
|
24498
24838
|
}
|
|
@@ -24539,8 +24879,9 @@ var DefaultConfigLoader = class {
|
|
|
24539
24879
|
}
|
|
24540
24880
|
if (c.mode !== void 0 && !isContextWindowModeId(c.mode)) {
|
|
24541
24881
|
const known = listContextWindowModes().map((m) => m.id).join(", ");
|
|
24542
|
-
|
|
24543
|
-
`
|
|
24882
|
+
this.logWarn(
|
|
24883
|
+
`Ignoring unknown context.mode "${c.mode}" \u2014 falling back to "${DEFAULT_CONTEXT_WINDOW_MODE_ID}"`,
|
|
24884
|
+
{ event: "config.unknown_context_mode", mode: c.mode, known }
|
|
24544
24885
|
);
|
|
24545
24886
|
c.mode = DEFAULT_CONTEXT_WINDOW_MODE_ID;
|
|
24546
24887
|
}
|
|
@@ -25670,7 +26011,7 @@ async function loadPlan(filePath, events) {
|
|
|
25670
26011
|
return null;
|
|
25671
26012
|
}
|
|
25672
26013
|
}
|
|
25673
|
-
async function savePlan(filePath, plan, events) {
|
|
26014
|
+
async function savePlan(filePath, plan, events, warn) {
|
|
25674
26015
|
const t0 = Date.now();
|
|
25675
26016
|
try {
|
|
25676
26017
|
await atomicWrite(filePath, JSON.stringify(plan, null, 2), { mode: 384 });
|
|
@@ -25692,10 +26033,14 @@ async function savePlan(filePath, plan, events) {
|
|
|
25692
26033
|
error: toErrorMessage(err),
|
|
25693
26034
|
recoverable: false
|
|
25694
26035
|
});
|
|
25695
|
-
console.warn(
|
|
25696
|
-
|
|
25697
|
-
|
|
25698
|
-
|
|
26036
|
+
(warn ?? ((m) => console.warn(
|
|
26037
|
+
JSON.stringify({
|
|
26038
|
+
level: "warn",
|
|
26039
|
+
event: "plan_store.save_failed",
|
|
26040
|
+
message: m,
|
|
26041
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
26042
|
+
})
|
|
26043
|
+
)))(toErrorMessage(err));
|
|
25699
26044
|
return false;
|
|
25700
26045
|
}
|
|
25701
26046
|
}
|
|
@@ -25736,9 +26081,7 @@ function setPlanItemStatus(plan, idOrIndex, status) {
|
|
|
25736
26081
|
const idx = matchIndex(plan, idOrIndex);
|
|
25737
26082
|
if (idx === -1) return plan;
|
|
25738
26083
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
25739
|
-
const items = plan.items.map(
|
|
25740
|
-
(it, i) => i === idx ? { ...it, status, updatedAt: now } : it
|
|
25741
|
-
);
|
|
26084
|
+
const items = plan.items.map((it, i) => i === idx ? { ...it, status, updatedAt: now } : it);
|
|
25742
26085
|
return { ...plan, items, updatedAt: now };
|
|
25743
26086
|
}
|
|
25744
26087
|
function clearPlan(plan) {
|
|
@@ -25920,10 +26263,19 @@ var QueueStore = class {
|
|
|
25920
26263
|
// reject assigning an optional constructor parameter to these fields.
|
|
25921
26264
|
events;
|
|
25922
26265
|
traceId;
|
|
26266
|
+
logger;
|
|
25923
26267
|
constructor(opts) {
|
|
25924
26268
|
this.file = path27.join(opts.dir, "queue.json");
|
|
25925
26269
|
this.events = opts.events;
|
|
25926
26270
|
this.traceId = opts.traceId;
|
|
26271
|
+
this.logger = opts.logger;
|
|
26272
|
+
}
|
|
26273
|
+
logWarn(msg, ctx) {
|
|
26274
|
+
if (this.logger) {
|
|
26275
|
+
this.logger.warn(msg, ctx);
|
|
26276
|
+
} else {
|
|
26277
|
+
console.warn(JSON.stringify({ ...ctx, message: msg, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
26278
|
+
}
|
|
25927
26279
|
}
|
|
25928
26280
|
async write(items) {
|
|
25929
26281
|
const t0 = Date.now();
|
|
@@ -25953,13 +26305,7 @@ var QueueStore = class {
|
|
|
25953
26305
|
recoverable: false,
|
|
25954
26306
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
25955
26307
|
});
|
|
25956
|
-
|
|
25957
|
-
level: "warn",
|
|
25958
|
-
event: "queue_store.write_failed",
|
|
25959
|
-
path: this.file,
|
|
25960
|
-
message: toErrorMessage(err),
|
|
25961
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
25962
|
-
}));
|
|
26308
|
+
this.logWarn("Queue store write failed", { event: "queue_store.write_failed", path: this.file, message: toErrorMessage(err) });
|
|
25963
26309
|
}
|
|
25964
26310
|
}
|
|
25965
26311
|
async read() {
|
|
@@ -25991,13 +26337,7 @@ var QueueStore = class {
|
|
|
25991
26337
|
recoverable: true,
|
|
25992
26338
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
25993
26339
|
});
|
|
25994
|
-
|
|
25995
|
-
level: "warn",
|
|
25996
|
-
event: "queue_store.read_failed",
|
|
25997
|
-
path: this.file,
|
|
25998
|
-
message: toErrorMessage(err),
|
|
25999
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
26000
|
-
}));
|
|
26340
|
+
this.logWarn("Queue store read failed", { event: "queue_store.read_failed", path: this.file, message: toErrorMessage(err) });
|
|
26001
26341
|
return [];
|
|
26002
26342
|
}
|
|
26003
26343
|
let parsed;
|
|
@@ -26070,13 +26410,7 @@ var QueueStore = class {
|
|
|
26070
26410
|
recoverable: true,
|
|
26071
26411
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
26072
26412
|
});
|
|
26073
|
-
|
|
26074
|
-
level: "warn",
|
|
26075
|
-
event: "queue_store.clear_failed",
|
|
26076
|
-
path: this.file,
|
|
26077
|
-
message: err.message,
|
|
26078
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
26079
|
-
}));
|
|
26413
|
+
this.logWarn("Queue store clear failed", { event: "queue_store.clear_failed", path: this.file, message: err.message });
|
|
26080
26414
|
}
|
|
26081
26415
|
}
|
|
26082
26416
|
};
|
|
@@ -26888,7 +27222,7 @@ async function loadTodosCheckpoint(filePath, events, traceId) {
|
|
|
26888
27222
|
return null;
|
|
26889
27223
|
}
|
|
26890
27224
|
}
|
|
26891
|
-
async function saveTodosCheckpoint(filePath, sessionId, todos, events, traceId) {
|
|
27225
|
+
async function saveTodosCheckpoint(filePath, sessionId, todos, events, traceId, warn) {
|
|
26892
27226
|
const t0 = Date.now();
|
|
26893
27227
|
const payload = {
|
|
26894
27228
|
version: 1,
|
|
@@ -26917,28 +27251,17 @@ async function saveTodosCheckpoint(filePath, sessionId, todos, events, traceId)
|
|
|
26917
27251
|
error: toErrorMessage(err),
|
|
26918
27252
|
recoverable: false
|
|
26919
27253
|
});
|
|
26920
|
-
console.warn(JSON.stringify({
|
|
26921
|
-
level: "warn",
|
|
26922
|
-
event: "todos_checkpoint.save_failed",
|
|
26923
|
-
message: toErrorMessage(err),
|
|
26924
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
26925
|
-
}));
|
|
27254
|
+
(warn ?? ((m) => console.warn(JSON.stringify({ level: "warn", event: "todos_checkpoint.save_failed", message: m, timestamp: (/* @__PURE__ */ new Date()).toISOString() }))))(toErrorMessage(err));
|
|
26926
27255
|
}
|
|
26927
27256
|
}
|
|
26928
|
-
function attachTodosCheckpoint(state, filePath, sessionId, events, traceId) {
|
|
27257
|
+
function attachTodosCheckpoint(state, filePath, sessionId, events, traceId, warn) {
|
|
26929
27258
|
let timer = null;
|
|
26930
27259
|
let pending = null;
|
|
26931
27260
|
let writeChain = Promise.resolve();
|
|
26932
27261
|
const enqueueWrite = (todos) => {
|
|
26933
27262
|
writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos, events, traceId)).catch((err) => {
|
|
26934
27263
|
const msg = toErrorMessage(err);
|
|
26935
|
-
console.error(JSON.stringify({
|
|
26936
|
-
level: "error",
|
|
26937
|
-
event: "todos_checkpoint.write_chain_failed",
|
|
26938
|
-
sessionId,
|
|
26939
|
-
message: msg,
|
|
26940
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
26941
|
-
}));
|
|
27264
|
+
(warn ?? ((m) => console.error(JSON.stringify({ level: "error", event: "todos_checkpoint.write_chain_failed", sessionId, message: m, timestamp: (/* @__PURE__ */ new Date()).toISOString() }))))(msg);
|
|
26942
27265
|
});
|
|
26943
27266
|
return writeChain;
|
|
26944
27267
|
};
|