@wrongstack/core 0.6.0 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-bridge-BKNiE1VH.d.ts → agent-bridge-eb7qnNrd.d.ts} +1 -1
- package/dist/coordination/index.d.ts +5 -5
- package/dist/coordination/index.js +183 -87
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +10 -10
- package/dist/defaults/index.js +596 -114
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-DPQKFX7W.d.ts → events-BHuIHekD.d.ts} +27 -4
- package/dist/execution/index.d.ts +149 -6
- package/dist/execution/index.js +401 -21
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +2 -2
- package/dist/{goal-store-BQ3YX1h1.d.ts → goal-store-DVCfj7Ff.d.ts} +20 -0
- package/dist/{index-B0qTujQW.d.ts → index-BOn9NK7D.d.ts} +1 -1
- package/dist/{index-DkdRz6yK.d.ts → index-CPcDqvZh.d.ts} +11 -4
- package/dist/index.d.ts +50 -16
- package/dist/index.js +831 -125
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +2 -2
- package/dist/infrastructure/index.js +1 -1
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +2 -2
- package/dist/kernel/index.js.map +1 -1
- package/dist/{multi-agent-Cpp7FXUl.d.ts → multi-agent-CxSb-9dQ.d.ts} +30 -5
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +1 -1
- package/dist/observability/index.js.map +1 -1
- package/dist/{path-resolver--g_hKJ7V.d.ts → path-resolver-CMGNadvq.d.ts} +1 -1
- package/dist/{plan-templates-CKJs_sYh.d.ts → plan-templates-BJflQY2i.d.ts} +1 -1
- package/dist/{provider-runner-hl4Il3xS.d.ts → provider-runner-BFgNXpaP.d.ts} +1 -1
- package/dist/sdd/index.d.ts +2 -2
- package/dist/storage/index.d.ts +3 -3
- package/dist/storage/index.js +3 -0
- package/dist/storage/index.js.map +1 -1
- package/dist/{tool-executor-B03CRwu-.d.ts → tool-executor-FoxBjULX.d.ts} +1 -1
- package/dist/types/index.d.ts +7 -7
- package/dist/types/index.js +30 -4
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +5 -1
package/dist/index.js
CHANGED
|
@@ -1347,7 +1347,7 @@ function round4(n) {
|
|
|
1347
1347
|
}
|
|
1348
1348
|
|
|
1349
1349
|
// src/utils/token-estimate.ts
|
|
1350
|
-
var RoughTokenEstimate = (text) => Math.max(1, Math.ceil(text.length /
|
|
1350
|
+
var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
|
|
1351
1351
|
var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
|
|
1352
1352
|
var ESTIMATE_CACHE_MAX_SIZE = 1e4;
|
|
1353
1353
|
function getCachedEstimate(key, compute) {
|
|
@@ -1526,7 +1526,7 @@ var HybridCompactor = class {
|
|
|
1526
1526
|
eliseThreshold;
|
|
1527
1527
|
estimator;
|
|
1528
1528
|
constructor(opts = {}) {
|
|
1529
|
-
this.preserveK = opts.preserveK ??
|
|
1529
|
+
this.preserveK = opts.preserveK ?? 5;
|
|
1530
1530
|
this.eliseThreshold = opts.eliseThreshold ?? 2e3;
|
|
1531
1531
|
this.estimator = opts.estimator ?? estimateTextTokens;
|
|
1532
1532
|
}
|
|
@@ -1570,6 +1570,17 @@ var HybridCompactor = class {
|
|
|
1570
1570
|
preserveStart = i;
|
|
1571
1571
|
}
|
|
1572
1572
|
}
|
|
1573
|
+
for (let i = preserveStart; i < messages.length; i++) {
|
|
1574
|
+
const m = messages[i];
|
|
1575
|
+
if (!m || typeof m.content === "string" || !Array.isArray(m.content)) continue;
|
|
1576
|
+
const hasToolUse2 = m.content.some((b) => b.type === "tool_use");
|
|
1577
|
+
if (hasToolUse2 && i + 1 < messages.length) {
|
|
1578
|
+
const next = messages[i + 1];
|
|
1579
|
+
if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => b.type === "tool_result")) {
|
|
1580
|
+
preserveStart = i + 1;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1573
1584
|
let saved = 0;
|
|
1574
1585
|
let changed = false;
|
|
1575
1586
|
const nextMessages = new Array(messages.length);
|
|
@@ -1591,7 +1602,7 @@ var HybridCompactor = class {
|
|
|
1591
1602
|
const elided = {
|
|
1592
1603
|
type: "tool_result",
|
|
1593
1604
|
tool_use_id: b.tool_use_id,
|
|
1594
|
-
content: `[elided: ~${tokens} tokens
|
|
1605
|
+
content: `[elided: ~${tokens} tokens]`,
|
|
1595
1606
|
is_error: b.is_error
|
|
1596
1607
|
};
|
|
1597
1608
|
return elided;
|
|
@@ -2934,13 +2945,17 @@ var ToolExecutor = class {
|
|
|
2934
2945
|
const ctrl = new AbortController();
|
|
2935
2946
|
const timer = setTimeout(() => ctrl.abort(new Error("tool timeout")), timeoutMs);
|
|
2936
2947
|
const combined = AbortSignal.any([parentSignal, ctrl.signal]);
|
|
2948
|
+
let cleanupCalled = false;
|
|
2949
|
+
let caught = false;
|
|
2937
2950
|
try {
|
|
2938
2951
|
if (typeof tool.executeStream === "function") {
|
|
2939
2952
|
return await this.runStreamedTool(tool, input, ctx, combined, toolUseId);
|
|
2940
2953
|
}
|
|
2941
2954
|
return await tool.execute(input, ctx, { signal: combined });
|
|
2942
2955
|
} catch (err) {
|
|
2956
|
+
caught = true;
|
|
2943
2957
|
if (combined.aborted && typeof tool.cleanup === "function") {
|
|
2958
|
+
cleanupCalled = true;
|
|
2944
2959
|
try {
|
|
2945
2960
|
await tool.cleanup(input, ctx);
|
|
2946
2961
|
} catch {
|
|
@@ -2949,6 +2964,16 @@ var ToolExecutor = class {
|
|
|
2949
2964
|
throw err;
|
|
2950
2965
|
} finally {
|
|
2951
2966
|
clearTimeout(timer);
|
|
2967
|
+
if (combined.aborted && !caught) {
|
|
2968
|
+
if (!cleanupCalled && typeof tool.cleanup === "function") {
|
|
2969
|
+
try {
|
|
2970
|
+
await tool.cleanup(input, ctx);
|
|
2971
|
+
} catch {
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
const reason = combined.reason instanceof Error ? combined.reason : new Error(typeof combined.reason === "string" ? combined.reason : "aborted");
|
|
2975
|
+
throw reason;
|
|
2976
|
+
}
|
|
2952
2977
|
}
|
|
2953
2978
|
}
|
|
2954
2979
|
async runStreamedTool(tool, input, ctx, signal, toolUseId) {
|
|
@@ -3006,7 +3031,8 @@ var ToolExecutor = class {
|
|
|
3006
3031
|
if (subjectKey) {
|
|
3007
3032
|
const v = obj[subjectKey];
|
|
3008
3033
|
if (typeof v === "string") {
|
|
3009
|
-
|
|
3034
|
+
const isPathKey = subjectKey === "path" || subjectKey === "file" || subjectKey === "files";
|
|
3035
|
+
return isPathKey ? normalizePath(v) : escapeGlob(v);
|
|
3010
3036
|
}
|
|
3011
3037
|
}
|
|
3012
3038
|
if (toolName === "bash" && typeof obj.command === "string") {
|
|
@@ -6277,17 +6303,23 @@ async function streamProviderToResponse(provider, req, signal, ctx, events) {
|
|
|
6277
6303
|
handleTextDelta(state, ev.text);
|
|
6278
6304
|
events.emit("provider.text_delta", { ctx, text: ev.text });
|
|
6279
6305
|
break;
|
|
6280
|
-
case "tool_use_start":
|
|
6281
|
-
|
|
6282
|
-
|
|
6306
|
+
case "tool_use_start": {
|
|
6307
|
+
const idVal = ev.id;
|
|
6308
|
+
const nameVal = ev.name;
|
|
6309
|
+
handleToolUseStart(state, { id: idVal, name: nameVal });
|
|
6310
|
+
const emittedPayload = { ctx, id: idVal ?? "unknown", name: nameVal ?? "unknown" };
|
|
6311
|
+
events.emit("provider.tool_use_start", emittedPayload);
|
|
6283
6312
|
break;
|
|
6313
|
+
}
|
|
6284
6314
|
case "tool_use_input_delta":
|
|
6285
6315
|
handleToolUseInputDelta(state, ev);
|
|
6286
6316
|
break;
|
|
6287
|
-
case "tool_use_stop":
|
|
6317
|
+
case "tool_use_stop": {
|
|
6318
|
+
const stoppedName = state.tools.get(ev.id)?.name ?? "unknown";
|
|
6288
6319
|
handleToolUseStop(state, ev);
|
|
6289
|
-
events.emit("provider.tool_use_stop", { ctx, id: ev.id });
|
|
6320
|
+
events.emit("provider.tool_use_stop", { ctx, id: ev.id, name: stoppedName });
|
|
6290
6321
|
break;
|
|
6322
|
+
}
|
|
6291
6323
|
case "thinking_start":
|
|
6292
6324
|
handleThinkingStart(state, ev);
|
|
6293
6325
|
break;
|
|
@@ -6475,7 +6507,28 @@ var IntelligentCompactor = class {
|
|
|
6475
6507
|
try {
|
|
6476
6508
|
summaryText = await this.callSummarizer(toSummarize, ctx);
|
|
6477
6509
|
} catch {
|
|
6478
|
-
|
|
6510
|
+
const toolNames = /* @__PURE__ */ new Set();
|
|
6511
|
+
const filePaths = /* @__PURE__ */ new Set();
|
|
6512
|
+
let userTurns = 0, assistantTurns = 0;
|
|
6513
|
+
for (const m of toSummarize) {
|
|
6514
|
+
if (m.role === "user") userTurns++;
|
|
6515
|
+
else if (m.role === "assistant") {
|
|
6516
|
+
assistantTurns++;
|
|
6517
|
+
if (Array.isArray(m.content)) {
|
|
6518
|
+
for (const b of m.content) {
|
|
6519
|
+
if (b.type === "tool_use") toolNames.add(b.name ?? "unknown");
|
|
6520
|
+
}
|
|
6521
|
+
}
|
|
6522
|
+
}
|
|
6523
|
+
const text = typeof m.content === "string" ? m.content : "";
|
|
6524
|
+
const matches = text.matchAll(/(?:[\w.,\-/@]+\/)*[\w.,\-/@]+\.\w+/g);
|
|
6525
|
+
for (const m_ of matches) filePaths.add(m_[0]);
|
|
6526
|
+
}
|
|
6527
|
+
const parts = [`${toSummarize.length} turns (${userTurns} user, ${assistantTurns} assistant)`];
|
|
6528
|
+
if (toolNames.size > 0) parts.push(`tools: ${[...toolNames].join(", ")}`);
|
|
6529
|
+
if (filePaths.size > 0) parts.push(`files: ${[...filePaths].slice(0, 10).join(", ")}`);
|
|
6530
|
+
summaryText = parts.join(" | ");
|
|
6531
|
+
if (!summaryText) summaryText = `${toSummarize.length} earlier turns omitted`;
|
|
6479
6532
|
}
|
|
6480
6533
|
const summaryMsg = {
|
|
6481
6534
|
role: "system",
|
|
@@ -6559,6 +6612,17 @@ var IntelligentCompactor = class {
|
|
|
6559
6612
|
preserveStart = i;
|
|
6560
6613
|
}
|
|
6561
6614
|
}
|
|
6615
|
+
for (let i = preserveStart; i < messages.length; i++) {
|
|
6616
|
+
const m = messages[i];
|
|
6617
|
+
if (!m || typeof m.content === "string" || !Array.isArray(m.content)) continue;
|
|
6618
|
+
const hasToolUse2 = m.content.some((b) => b.type === "tool_use");
|
|
6619
|
+
if (hasToolUse2 && i + 1 < messages.length) {
|
|
6620
|
+
const next = messages[i + 1];
|
|
6621
|
+
if (next && next.role === "user" && typeof next.content !== "string" && Array.isArray(next.content) && next.content.some((b) => b.type === "tool_result")) {
|
|
6622
|
+
preserveStart = i + 1;
|
|
6623
|
+
}
|
|
6624
|
+
}
|
|
6625
|
+
}
|
|
6562
6626
|
let saved = 0;
|
|
6563
6627
|
let changed = false;
|
|
6564
6628
|
const nextMessages = new Array(messages.length);
|
|
@@ -7073,7 +7137,15 @@ var AutoCompactionMiddleware = class {
|
|
|
7073
7137
|
}
|
|
7074
7138
|
async compact(ctx, aggressive, pressure) {
|
|
7075
7139
|
try {
|
|
7076
|
-
await this.compactor.compact(ctx, { aggressive });
|
|
7140
|
+
const report = await this.compactor.compact(ctx, { aggressive });
|
|
7141
|
+
this.events?.emit("compaction.fired", {
|
|
7142
|
+
level: pressure.level,
|
|
7143
|
+
tokens: pressure.tokens,
|
|
7144
|
+
load: pressure.load,
|
|
7145
|
+
maxContext: this._maxContext,
|
|
7146
|
+
report,
|
|
7147
|
+
aggressive
|
|
7148
|
+
});
|
|
7077
7149
|
} catch (err) {
|
|
7078
7150
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
7079
7151
|
const fatal = this.failureMode === "throw" || this.failureMode === "throw_on_hard" && pressure.level === "hard";
|
|
@@ -7303,6 +7375,8 @@ function emptyGoal(goal) {
|
|
|
7303
7375
|
lastActivityAt: now,
|
|
7304
7376
|
iterations: 0,
|
|
7305
7377
|
engineState: "idle",
|
|
7378
|
+
goalState: "active",
|
|
7379
|
+
todoAttempts: {},
|
|
7306
7380
|
journal: []
|
|
7307
7381
|
};
|
|
7308
7382
|
}
|
|
@@ -7340,6 +7414,7 @@ function formatGoal(goal, journalLimit = 10) {
|
|
|
7340
7414
|
lines.push(`Set: ${goal.setAt}`);
|
|
7341
7415
|
lines.push(`Last activity: ${goal.lastActivityAt}`);
|
|
7342
7416
|
lines.push(`Iterations: ${goal.iterations}`);
|
|
7417
|
+
lines.push(`Mission: ${goal.goalState ?? "active"}`);
|
|
7343
7418
|
lines.push(`Engine: ${goal.engineState}`);
|
|
7344
7419
|
const usage = summarizeUsage(goal);
|
|
7345
7420
|
if (usage.iterationsWithUsage > 0) {
|
|
@@ -7363,6 +7438,8 @@ function formatGoal(goal, journalLimit = 10) {
|
|
|
7363
7438
|
|
|
7364
7439
|
// src/execution/eternal-autonomy.ts
|
|
7365
7440
|
var execFileP = promisify(execFile);
|
|
7441
|
+
var BRAINSTORM_DONE = /* @__PURE__ */ Symbol("brainstorm-done");
|
|
7442
|
+
var GOAL_COMPLETE_MARKER = /^\s*\[goal[_\s-]?complete\]\s*$/im;
|
|
7366
7443
|
var EternalAutonomyEngine = class {
|
|
7367
7444
|
constructor(opts) {
|
|
7368
7445
|
this.opts = opts;
|
|
@@ -7372,6 +7449,14 @@ var EternalAutonomyEngine = class {
|
|
|
7372
7449
|
state = "idle";
|
|
7373
7450
|
stopRequested = false;
|
|
7374
7451
|
consecutiveFailures = 0;
|
|
7452
|
+
consecutiveBrainstormDone = 0;
|
|
7453
|
+
/**
|
|
7454
|
+
* Count of consecutive transient (recoverable) provider failures. Drives
|
|
7455
|
+
* the exponential backoff between iterations. Reset on the first
|
|
7456
|
+
* successful iteration so a single bad afternoon doesn't permanently
|
|
7457
|
+
* slow the loop down.
|
|
7458
|
+
*/
|
|
7459
|
+
consecutiveTransientRetries = 0;
|
|
7375
7460
|
currentCtrl = null;
|
|
7376
7461
|
iterationsSinceCompact = 0;
|
|
7377
7462
|
goalPath;
|
|
@@ -7441,9 +7526,16 @@ var EternalAutonomyEngine = class {
|
|
|
7441
7526
|
this.stopRequested = true;
|
|
7442
7527
|
return false;
|
|
7443
7528
|
}
|
|
7529
|
+
const missionState = goal.goalState ?? "active";
|
|
7530
|
+
if (missionState !== "active") {
|
|
7531
|
+
this.stopRequested = true;
|
|
7532
|
+
return false;
|
|
7533
|
+
}
|
|
7444
7534
|
const action = await this.decide(goal);
|
|
7445
7535
|
if (!action) {
|
|
7446
|
-
|
|
7536
|
+
if (!this.stopRequested) {
|
|
7537
|
+
await sleep(5e3);
|
|
7538
|
+
}
|
|
7447
7539
|
return false;
|
|
7448
7540
|
}
|
|
7449
7541
|
const ctrl = new AbortController();
|
|
@@ -7454,13 +7546,29 @@ var EternalAutonomyEngine = class {
|
|
|
7454
7546
|
);
|
|
7455
7547
|
let status = "success";
|
|
7456
7548
|
let note;
|
|
7549
|
+
let finalText = "";
|
|
7550
|
+
let isTransientFailure = false;
|
|
7457
7551
|
const tc = this.opts.agent.ctx?.tokenCounter;
|
|
7458
7552
|
const beforeUsage = tc?.total?.();
|
|
7459
7553
|
const beforeCost = tc?.estimateCost?.().total;
|
|
7460
7554
|
try {
|
|
7461
7555
|
const result = await this.opts.agent.run(
|
|
7462
7556
|
[{ type: "text", text: action.directive }],
|
|
7463
|
-
{
|
|
7557
|
+
{
|
|
7558
|
+
signal: ctrl.signal,
|
|
7559
|
+
// Enable per-call autonomous continuation so the agent can chain
|
|
7560
|
+
// multiple internal tool/response cycles end-to-end on one
|
|
7561
|
+
// directive instead of returning to the engine after a single
|
|
7562
|
+
// round-trip. The model uses `[continue]` / `[done]` markers
|
|
7563
|
+
// (or the `continue_to_next_iteration` tool) to control the
|
|
7564
|
+
// inner loop. Without this flag the engine produced shallow
|
|
7565
|
+
// iterations and almost never let a real task finish.
|
|
7566
|
+
autonomousContinue: true,
|
|
7567
|
+
// Cap the inner loop so a runaway agent.run can't burn through
|
|
7568
|
+
// the iteration timeout — the engine's own outer loop is the
|
|
7569
|
+
// long-running thing, each tick should be bounded.
|
|
7570
|
+
maxIterations: this.opts.iterationMaxAgentSteps ?? 50
|
|
7571
|
+
}
|
|
7464
7572
|
);
|
|
7465
7573
|
if (result.status === "aborted") {
|
|
7466
7574
|
status = "aborted";
|
|
@@ -7468,22 +7576,30 @@ var EternalAutonomyEngine = class {
|
|
|
7468
7576
|
} else if (result.status === "failed") {
|
|
7469
7577
|
status = "failure";
|
|
7470
7578
|
note = result.error?.describe?.() ?? "agent run failed";
|
|
7579
|
+
isTransientFailure = result.error?.recoverable === true;
|
|
7471
7580
|
} else if (result.status === "max_iterations") {
|
|
7472
7581
|
status = "failure";
|
|
7473
7582
|
note = `max iterations (${result.iterations})`;
|
|
7474
7583
|
} else {
|
|
7475
7584
|
status = "success";
|
|
7476
|
-
|
|
7585
|
+
finalText = result.finalText ?? "";
|
|
7586
|
+
const tail = finalText.slice(0, 240).replace(/\s+/g, " ").trim();
|
|
7477
7587
|
if (tail) note = tail;
|
|
7478
7588
|
}
|
|
7479
7589
|
} catch (err) {
|
|
7480
7590
|
const isAbort = err instanceof Error && (err.name === "AbortError" || err.message.includes("abort"));
|
|
7481
7591
|
status = isAbort ? "aborted" : "failure";
|
|
7482
7592
|
note = err instanceof Error ? err.message : String(err);
|
|
7593
|
+
if (!isAbort && typeof err?.recoverable === "boolean") {
|
|
7594
|
+
isTransientFailure = err.recoverable;
|
|
7595
|
+
}
|
|
7483
7596
|
} finally {
|
|
7484
7597
|
clearTimeout(timer);
|
|
7485
7598
|
this.currentCtrl = null;
|
|
7486
7599
|
}
|
|
7600
|
+
if (action.source === "todo" && action.todoId && status !== "success") {
|
|
7601
|
+
await this.bumpTodoAttempt(action.todoId);
|
|
7602
|
+
}
|
|
7487
7603
|
const afterUsage = tc?.total?.();
|
|
7488
7604
|
const afterCost = tc?.estimateCost?.().total;
|
|
7489
7605
|
const tokens = beforeUsage && afterUsage ? {
|
|
@@ -7516,6 +7632,14 @@ var EternalAutonomyEngine = class {
|
|
|
7516
7632
|
costUsd
|
|
7517
7633
|
});
|
|
7518
7634
|
if (status === "failure") {
|
|
7635
|
+
if (isTransientFailure) {
|
|
7636
|
+
this.consecutiveTransientRetries++;
|
|
7637
|
+
const delay = this.computeTransientBackoffMs();
|
|
7638
|
+
if (delay > 0) {
|
|
7639
|
+
await this.sleepInterruptible(delay);
|
|
7640
|
+
}
|
|
7641
|
+
return false;
|
|
7642
|
+
}
|
|
7519
7643
|
this.consecutiveFailures++;
|
|
7520
7644
|
return false;
|
|
7521
7645
|
}
|
|
@@ -7524,6 +7648,12 @@ var EternalAutonomyEngine = class {
|
|
|
7524
7648
|
this.consecutiveFailures++;
|
|
7525
7649
|
return false;
|
|
7526
7650
|
}
|
|
7651
|
+
this.consecutiveTransientRetries = 0;
|
|
7652
|
+
if (GOAL_COMPLETE_MARKER.test(finalText)) {
|
|
7653
|
+
await this.markGoalCompleted(action, finalText);
|
|
7654
|
+
this.stopRequested = true;
|
|
7655
|
+
return true;
|
|
7656
|
+
}
|
|
7527
7657
|
this.iterationsSinceCompact++;
|
|
7528
7658
|
await this.maybeCompact().catch((err) => {
|
|
7529
7659
|
this.opts.onError?.(
|
|
@@ -7587,11 +7717,12 @@ var EternalAutonomyEngine = class {
|
|
|
7587
7717
|
async decide(goal) {
|
|
7588
7718
|
const forceBrainstorm = this.consecutiveFailures >= (this.opts.failureBudget ?? 3);
|
|
7589
7719
|
if (!forceBrainstorm) {
|
|
7590
|
-
const todo = this.pickPendingTodo();
|
|
7720
|
+
const todo = this.pickPendingTodo(goal);
|
|
7591
7721
|
if (todo) {
|
|
7592
7722
|
return {
|
|
7593
7723
|
source: "todo",
|
|
7594
7724
|
task: todo.content,
|
|
7725
|
+
todoId: todo.id,
|
|
7595
7726
|
directive: this.buildDirective(goal, "todo", todo.content)
|
|
7596
7727
|
};
|
|
7597
7728
|
}
|
|
@@ -7605,17 +7736,38 @@ var EternalAutonomyEngine = class {
|
|
|
7605
7736
|
}
|
|
7606
7737
|
}
|
|
7607
7738
|
const brainstormed = await this.brainstormTask(goal);
|
|
7739
|
+
if (brainstormed === BRAINSTORM_DONE) {
|
|
7740
|
+
this.consecutiveBrainstormDone++;
|
|
7741
|
+
const threshold = this.opts.brainstormDoneStopThreshold ?? 3;
|
|
7742
|
+
if (this.consecutiveBrainstormDone >= threshold) {
|
|
7743
|
+
await this.markGoalCompleted(
|
|
7744
|
+
{ source: "brainstorm", task: "no further work", directive: "" },
|
|
7745
|
+
`brainstorm returned DONE ${this.consecutiveBrainstormDone}x in a row`
|
|
7746
|
+
);
|
|
7747
|
+
this.stopRequested = true;
|
|
7748
|
+
}
|
|
7749
|
+
return null;
|
|
7750
|
+
}
|
|
7608
7751
|
if (!brainstormed) return null;
|
|
7752
|
+
this.consecutiveBrainstormDone = 0;
|
|
7609
7753
|
return {
|
|
7610
7754
|
source: "brainstorm",
|
|
7611
7755
|
task: brainstormed,
|
|
7612
7756
|
directive: this.buildDirective(goal, "brainstorm", brainstormed)
|
|
7613
7757
|
};
|
|
7614
7758
|
}
|
|
7615
|
-
pickPendingTodo() {
|
|
7759
|
+
pickPendingTodo(goal) {
|
|
7616
7760
|
const todos = this.opts.agent.ctx.todos;
|
|
7617
7761
|
if (!Array.isArray(todos)) return null;
|
|
7618
|
-
|
|
7762
|
+
const attempts = goal.todoAttempts ?? {};
|
|
7763
|
+
const ceiling = this.opts.todoMaxAttempts ?? 3;
|
|
7764
|
+
for (const t2 of todos) {
|
|
7765
|
+
if (t2.status !== "pending") continue;
|
|
7766
|
+
const used = attempts[t2.id] ?? 0;
|
|
7767
|
+
if (used >= ceiling) continue;
|
|
7768
|
+
return t2;
|
|
7769
|
+
}
|
|
7770
|
+
return null;
|
|
7619
7771
|
}
|
|
7620
7772
|
async pickGitTask() {
|
|
7621
7773
|
let out;
|
|
@@ -7652,7 +7804,10 @@ ${lastFew}` : "No prior iterations yet.",
|
|
|
7652
7804
|
"- One sentence, imperative form, under 200 chars.",
|
|
7653
7805
|
"- No preamble, no explanation, no markdown \u2014 just the task line.",
|
|
7654
7806
|
"- If recent iterations show repeated failures on the same target, pivot.",
|
|
7655
|
-
"- If the goal appears fully accomplished
|
|
7807
|
+
"- If the goal appears fully accomplished AND you can name a concrete",
|
|
7808
|
+
" artifact / test / output that proves it, output exactly: DONE",
|
|
7809
|
+
"- Be conservative with DONE: if the recent journal contains failures",
|
|
7810
|
+
" or aborted entries, the goal is almost certainly NOT done."
|
|
7656
7811
|
].join("\n");
|
|
7657
7812
|
try {
|
|
7658
7813
|
const ctrl = new AbortController();
|
|
@@ -7664,9 +7819,11 @@ ${lastFew}` : "No prior iterations yet.",
|
|
|
7664
7819
|
);
|
|
7665
7820
|
if (result.status !== "done") return null;
|
|
7666
7821
|
const text = (result.finalText ?? "").trim();
|
|
7667
|
-
if (!text
|
|
7822
|
+
if (!text) return null;
|
|
7823
|
+
if (/^DONE\.?$/i.test(text)) return BRAINSTORM_DONE;
|
|
7668
7824
|
const firstLine = text.split("\n").find((l) => l.trim().length > 0)?.trim();
|
|
7669
7825
|
if (!firstLine) return null;
|
|
7826
|
+
if (/^DONE\.?$/i.test(firstLine)) return BRAINSTORM_DONE;
|
|
7670
7827
|
return firstLine.slice(0, 240);
|
|
7671
7828
|
} finally {
|
|
7672
7829
|
clearTimeout(timer);
|
|
@@ -7676,19 +7833,82 @@ ${lastFew}` : "No prior iterations yet.",
|
|
|
7676
7833
|
}
|
|
7677
7834
|
}
|
|
7678
7835
|
buildDirective(goal, source, task) {
|
|
7836
|
+
const recentJournal = goal.journal.slice(-5).map((e) => ` #${e.iteration} [${e.status}] ${e.task}${e.note ? ` \u2014 ${e.note.slice(0, 80)}` : ""}`).join("\n");
|
|
7679
7837
|
return [
|
|
7680
|
-
"
|
|
7838
|
+
"\u2550\u2550\u2550 ETERNAL AUTONOMY \u2014 iteration directive \u2550\u2550\u2550",
|
|
7681
7839
|
"",
|
|
7682
|
-
`
|
|
7840
|
+
`Mission: ${goal.goal}`,
|
|
7841
|
+
`Iteration: #${goal.iterations + 1}`,
|
|
7683
7842
|
`Source: ${source}`,
|
|
7684
7843
|
`Task: ${task}`,
|
|
7685
7844
|
"",
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
"
|
|
7689
|
-
"
|
|
7845
|
+
recentJournal ? `Recent journal (last 5):
|
|
7846
|
+
${recentJournal}` : "No prior iterations.",
|
|
7847
|
+
"",
|
|
7848
|
+
"\u2500\u2500 EXECUTION PROTOCOL \u2500\u2500",
|
|
7849
|
+
"You are inside a long-running autonomous loop. Each iteration you",
|
|
7850
|
+
"execute ONE concrete task that advances the Mission. No user is",
|
|
7851
|
+
"available to clarify \u2014 make defensible decisions and move forward.",
|
|
7852
|
+
"",
|
|
7853
|
+
"1. EXECUTE END-TO-END",
|
|
7854
|
+
" \u2022 Use multiple tool calls freely. Emit `[continue]` on its own line",
|
|
7855
|
+
" to chain to the next internal step without returning.",
|
|
7856
|
+
" \u2022 When this iteration's Task is finished (real artifact / passing",
|
|
7857
|
+
" test / applied diff / clean output), emit `[done]` on its own line.",
|
|
7858
|
+
" \u2022 Do not stop on the first obstacle \u2014 try at least 3 distinct",
|
|
7859
|
+
" approaches before giving up. YOLO is active; no confirmations.",
|
|
7860
|
+
"",
|
|
7861
|
+
"2. UPDATE TODO STATE (when Source is `todo`)",
|
|
7862
|
+
" \u2022 Mark this todo `in_progress` via the todos tool before tool work.",
|
|
7863
|
+
" \u2022 Mark it `completed` on success, with a one-line outcome note.",
|
|
7864
|
+
" \u2022 If you cannot make progress after 2 distinct attempts, mark it",
|
|
7865
|
+
" `cancelled` with the obstacle. The loop will skip it next time.",
|
|
7866
|
+
"",
|
|
7867
|
+
"3. MISSION-COMPLETE PROTOCOL",
|
|
7868
|
+
" \u2022 If \u2014 and ONLY if \u2014 the OVERALL Mission (not just this Task) is",
|
|
7869
|
+
" verifiably accomplished, emit on its own line:",
|
|
7870
|
+
" [GOAL_COMPLETE]",
|
|
7871
|
+
" followed by a one-paragraph verification recipe (artifact path,",
|
|
7872
|
+
" test command, or 10-second reproduction). This halts the loop.",
|
|
7873
|
+
" \u2022 NEVER emit [GOAL_COMPLETE] on optimism, partial progress, or",
|
|
7874
|
+
' "looks fine". Required: a concrete artifact that proves it AND',
|
|
7875
|
+
" no recent journal failures contradicting completion.",
|
|
7876
|
+
" \u2022 If unsure, emit `[done]` instead and let the next iteration",
|
|
7877
|
+
" decide. The loop is patient; false completion is not.",
|
|
7878
|
+
"",
|
|
7879
|
+
"4. NO INTERACTIVITY",
|
|
7880
|
+
" \u2022 Do not ask questions, do not request confirmation, do not propose",
|
|
7881
|
+
" options. Pick the best path and execute. The user is asleep."
|
|
7690
7882
|
].join("\n");
|
|
7691
7883
|
}
|
|
7884
|
+
/**
|
|
7885
|
+
* Exponential backoff for transient provider errors. `2^N * base`
|
|
7886
|
+
* capped at `transientBackoffMaxMs`. Zero base disables backoff.
|
|
7887
|
+
* Public-private to keep `runOneIteration` readable; the value is
|
|
7888
|
+
* recomputed each call from the current retry count, so callers
|
|
7889
|
+
* don't have to track state.
|
|
7890
|
+
*/
|
|
7891
|
+
computeTransientBackoffMs() {
|
|
7892
|
+
const base = this.opts.transientBackoffBaseMs ?? 2e3;
|
|
7893
|
+
const cap = this.opts.transientBackoffMaxMs ?? 6e4;
|
|
7894
|
+
if (base <= 0) return 0;
|
|
7895
|
+
const exponent = Math.max(0, this.consecutiveTransientRetries - 1);
|
|
7896
|
+
return Math.min(cap, base * Math.pow(2, exponent));
|
|
7897
|
+
}
|
|
7898
|
+
/**
|
|
7899
|
+
* Sleep that wakes early if `stopRequested` flips. Polls every 250 ms
|
|
7900
|
+
* so SIGINT / `/autonomy stop` can land in the middle of a long
|
|
7901
|
+
* backoff instead of waiting up to a minute for the timer.
|
|
7902
|
+
*/
|
|
7903
|
+
async sleepInterruptible(totalMs) {
|
|
7904
|
+
const step = 250;
|
|
7905
|
+
let remaining = totalMs;
|
|
7906
|
+
while (remaining > 0 && !this.stopRequested) {
|
|
7907
|
+
const chunk = Math.min(step, remaining);
|
|
7908
|
+
await sleep(chunk);
|
|
7909
|
+
remaining -= chunk;
|
|
7910
|
+
}
|
|
7911
|
+
}
|
|
7692
7912
|
async appendIterationEntry(entry) {
|
|
7693
7913
|
const current = await loadGoal(this.goalPath);
|
|
7694
7914
|
if (!current) {
|
|
@@ -7697,6 +7917,39 @@ ${lastFew}` : "No prior iterations yet.",
|
|
|
7697
7917
|
const updated = appendJournal(current, entry);
|
|
7698
7918
|
await saveGoal(this.goalPath, updated);
|
|
7699
7919
|
}
|
|
7920
|
+
/**
|
|
7921
|
+
* Persistent per-todo failure counter. Skipped silently when the goal
|
|
7922
|
+
* file has been removed (graceful clear). Each non-success iteration
|
|
7923
|
+
* against a todo source bumps the counter by 1; `pickPendingTodo` reads
|
|
7924
|
+
* the counter to rotate past stuck todos once they cross `todoMaxAttempts`.
|
|
7925
|
+
*/
|
|
7926
|
+
async bumpTodoAttempt(todoId) {
|
|
7927
|
+
const current = await loadGoal(this.goalPath);
|
|
7928
|
+
if (!current) return;
|
|
7929
|
+
const attempts = { ...current.todoAttempts ?? {} };
|
|
7930
|
+
attempts[todoId] = (attempts[todoId] ?? 0) + 1;
|
|
7931
|
+
await saveGoal(this.goalPath, { ...current, todoAttempts: attempts });
|
|
7932
|
+
}
|
|
7933
|
+
/**
|
|
7934
|
+
* Flip the mission to `completed` and journal it. Called from two
|
|
7935
|
+
* paths: (a) `[GOAL_COMPLETE]` marker in a successful iteration's
|
|
7936
|
+
* finalText, (b) `brainstorm` returning DONE consecutively past the
|
|
7937
|
+
* configured threshold. Idempotent — re-entry is a no-op once the
|
|
7938
|
+
* goal is already `completed`.
|
|
7939
|
+
*/
|
|
7940
|
+
async markGoalCompleted(action, note) {
|
|
7941
|
+
const current = await loadGoal(this.goalPath);
|
|
7942
|
+
if (!current) return;
|
|
7943
|
+
if (current.goalState === "completed") return;
|
|
7944
|
+
const withFlag = { ...current, goalState: "completed" };
|
|
7945
|
+
const withEntry = appendJournal(withFlag, {
|
|
7946
|
+
source: action.source,
|
|
7947
|
+
task: `MISSION COMPLETE \u2014 ${action.task}`.slice(0, 240),
|
|
7948
|
+
status: "success",
|
|
7949
|
+
note: note.slice(0, 240)
|
|
7950
|
+
});
|
|
7951
|
+
await saveGoal(this.goalPath, withEntry);
|
|
7952
|
+
}
|
|
7700
7953
|
async appendFailure(task, note) {
|
|
7701
7954
|
await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
|
|
7702
7955
|
}
|
|
@@ -7711,6 +7964,142 @@ function sleep(ms) {
|
|
|
7711
7964
|
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
7712
7965
|
}
|
|
7713
7966
|
|
|
7967
|
+
// src/execution/autonomy-prompt-contributor.ts
|
|
7968
|
+
function makeAutonomyPromptContributor(opts) {
|
|
7969
|
+
return async (ctx) => {
|
|
7970
|
+
if (ctx.subagent) return [];
|
|
7971
|
+
if (!opts.enabled()) return [];
|
|
7972
|
+
let goal;
|
|
7973
|
+
try {
|
|
7974
|
+
goal = await loadGoal(opts.goalPath);
|
|
7975
|
+
} catch {
|
|
7976
|
+
return [];
|
|
7977
|
+
}
|
|
7978
|
+
if (!goal) return [];
|
|
7979
|
+
const missionState = goal.goalState ?? "active";
|
|
7980
|
+
if (missionState !== "active") return [];
|
|
7981
|
+
const tailSize = opts.journalTailSize ?? 5;
|
|
7982
|
+
const journalTail = goal.journal.slice(-tailSize).map((e) => {
|
|
7983
|
+
const note = e.note ? ` \u2014 ${e.note.slice(0, 80)}` : "";
|
|
7984
|
+
return ` #${e.iteration} [${e.status}] ${e.task}${note}`;
|
|
7985
|
+
});
|
|
7986
|
+
const text = [
|
|
7987
|
+
"## ETERNAL AUTONOMY \u2014 active mission",
|
|
7988
|
+
"",
|
|
7989
|
+
"You are inside a long-running autonomous loop. The user is asleep",
|
|
7990
|
+
"and is not available to confirm decisions. Each turn you receive a",
|
|
7991
|
+
"directive describing one concrete sub-task that advances the mission.",
|
|
7992
|
+
"",
|
|
7993
|
+
`Mission: ${goal.goal}`,
|
|
7994
|
+
`Iteration: #${goal.iterations}`,
|
|
7995
|
+
journalTail.length > 0 ? `Recent journal (last ${journalTail.length}):
|
|
7996
|
+
${journalTail.join("\n")}` : "Recent journal: (none \u2014 this is the first iteration)",
|
|
7997
|
+
"",
|
|
7998
|
+
"### Loop control markers",
|
|
7999
|
+
"Emit these on their own line in your final text \u2014 case-insensitive,",
|
|
8000
|
+
"whitespace-tolerant, but they must occupy the entire line:",
|
|
8001
|
+
"- `[continue]` \u2014 chain to the next internal step without returning.",
|
|
8002
|
+
"- `[done]` \u2014 the current sub-task is finished; return to the engine.",
|
|
8003
|
+
"- `[GOAL_COMPLETE]` \u2014 emit ONLY when the OVERALL mission is",
|
|
8004
|
+
" verifiably done. Must be followed by a one-paragraph verification",
|
|
8005
|
+
" recipe (artifact path, test command, or 10-second reproduction).",
|
|
8006
|
+
" The engine halts on this marker \u2014 false positives waste real",
|
|
8007
|
+
" human time. If unsure, emit `[done]` and let the next iteration",
|
|
8008
|
+
" decide.",
|
|
8009
|
+
"",
|
|
8010
|
+
"### Operating principles",
|
|
8011
|
+
"- YOLO is active. Do NOT ask for confirmation, do NOT propose",
|
|
8012
|
+
" options. Pick the best path and execute it.",
|
|
8013
|
+
"- Use tools freely; multiple calls per turn are normal and expected.",
|
|
8014
|
+
"- When working on a todo, mark it `in_progress` via the todos tool",
|
|
8015
|
+
" before tool work and `completed` (or `cancelled` with a reason)",
|
|
8016
|
+
" when done. The loop reads todo state between iterations.",
|
|
8017
|
+
"- If an approach fails twice in a row, pivot. Don't grind on the",
|
|
8018
|
+
" same wall \u2014 try a different angle, file a cancel on the todo, or",
|
|
8019
|
+
" surface the obstacle via `[done]` and let the next iteration",
|
|
8020
|
+
" re-plan."
|
|
8021
|
+
].join("\n");
|
|
8022
|
+
return [
|
|
8023
|
+
{
|
|
8024
|
+
type: "text",
|
|
8025
|
+
text,
|
|
8026
|
+
cache_control: { type: "ephemeral" }
|
|
8027
|
+
}
|
|
8028
|
+
];
|
|
8029
|
+
};
|
|
8030
|
+
}
|
|
8031
|
+
|
|
8032
|
+
// src/execution/goal-preamble.ts
|
|
8033
|
+
function buildGoalPreamble(goal) {
|
|
8034
|
+
return [
|
|
8035
|
+
"[GOAL \u2014 LOCKED IN. You will work on this until it is verifiably done.",
|
|
8036
|
+
"The user granted you full autonomy. Read these constraints once, then act.",
|
|
8037
|
+
"",
|
|
8038
|
+
"YOUR GOAL:",
|
|
8039
|
+
"---",
|
|
8040
|
+
goal,
|
|
8041
|
+
"---",
|
|
8042
|
+
"",
|
|
8043
|
+
"AUTHORITY YOU HAVE:",
|
|
8044
|
+
"- Spawn as many subagents as the work needs (delegate / spawn_subagent).",
|
|
8045
|
+
" Parallel + recursive fan-out are both fine. There is no spawn budget.",
|
|
8046
|
+
"- Use any provider/model per subagent \u2014 pick the right tool for each",
|
|
8047
|
+
" piece of work. Heavy reasoning model for planning, fast model for",
|
|
8048
|
+
" batch work, specialist model for domain code.",
|
|
8049
|
+
"- Run unlimited tool calls and iterations. There is NO hidden budget.",
|
|
8050
|
+
" The Agent loop auto-extends every 100 iterations forever.",
|
|
8051
|
+
"- Retry failed tools with different inputs, alternative paths, fresh",
|
|
8052
|
+
" subagents. Switch providers mid-run if one is rate-limited.",
|
|
8053
|
+
"- Re-plan freely when an approach hits a dead end. You are not obliged",
|
|
8054
|
+
" to stick with the first plan you proposed.",
|
|
8055
|
+
"",
|
|
8056
|
+
'WHAT "DONE" MEANS \u2014 non-negotiable:',
|
|
8057
|
+
"- You can name a concrete artifact (a passing test, a written file at",
|
|
8058
|
+
" a specific path, a fixed bug verified by re-running the failing case,",
|
|
8059
|
+
" a clean grep that previously had matches).",
|
|
8060
|
+
"- You can tell the user HOW to verify it themselves in 10 seconds.",
|
|
8061
|
+
'- You have NOT hedged. None of: "looks like it should work", "I',
|
|
8062
|
+
' believe this fixes it", "the changes appear correct".',
|
|
8063
|
+
"",
|
|
8064
|
+
"WHAT IS NOT DONE \u2014 never report any of these as completion:",
|
|
8065
|
+
"- An error message you didn't recover from.",
|
|
8066
|
+
'- An empty result, a 0-line file, a "no matches found" you accepted',
|
|
8067
|
+
" without questioning the search.",
|
|
8068
|
+
'- "Should I continue?" / "Want me to also...?" / "Let me know if you',
|
|
8069
|
+
' want X." Those are hedges. The user already told you to finish the',
|
|
8070
|
+
" goal \u2014 just do it.",
|
|
8071
|
+
"- Partial progress dressed up as success. Fixed 3 of 5 bugs = 60%",
|
|
8072
|
+
" done, not done.",
|
|
8073
|
+
"- A subagent's failed/timeout/stopped TaskResult that you didn't",
|
|
8074
|
+
" respond to with a fresh attempt (different role, different model,",
|
|
8075
|
+
" tighter prompt).",
|
|
8076
|
+
"",
|
|
8077
|
+
"PERSISTENCE PROTOCOL:",
|
|
8078
|
+
"- If blocked, try at least 3 different angles before reporting the",
|
|
8079
|
+
" problem to the user. Different tool inputs, different subagent",
|
|
8080
|
+
" roles, different providers, different decomposition of the task.",
|
|
8081
|
+
"- If a tool fails, read its error, alter the input, try again. Do",
|
|
8082
|
+
" not just report the failure back.",
|
|
8083
|
+
"- If a subagent returns useless output, respawn with a tighter prompt",
|
|
8084
|
+
' or a different role. Do not accept "I could not determine\u2026" as the',
|
|
8085
|
+
" final answer.",
|
|
8086
|
+
"- Use `ask_subagent` for one-shot questions when you don't need a",
|
|
8087
|
+
" full delegated task.",
|
|
8088
|
+
"",
|
|
8089
|
+
"REPORTING:",
|
|
8090
|
+
"- Stream short progress notes between major actions so the user can",
|
|
8091
|
+
" monitor. Do not go silent for 50 tool calls then dump a wall of",
|
|
8092
|
+
" text \u2014 but also do not narrate every tool call.",
|
|
8093
|
+
"- Use the shared scratchpad (if available) to leave breadcrumbs",
|
|
8094
|
+
" subagents can read.",
|
|
8095
|
+
"- Final response must include: (a) what was accomplished, (b) how",
|
|
8096
|
+
" to verify, (c) any caveats (residual TODOs, things the user",
|
|
8097
|
+
" should know about).",
|
|
8098
|
+
"",
|
|
8099
|
+
"BEGIN.]"
|
|
8100
|
+
].join("\n");
|
|
8101
|
+
}
|
|
8102
|
+
|
|
7714
8103
|
// src/coordination/director.ts
|
|
7715
8104
|
init_atomic_write();
|
|
7716
8105
|
|
|
@@ -7823,40 +8212,12 @@ var FleetBus = class {
|
|
|
7823
8212
|
* subagent teardown so the listeners don't outlive the run.
|
|
7824
8213
|
*/
|
|
7825
8214
|
attach(subagentId, bus, taskId) {
|
|
7826
|
-
const
|
|
7827
|
-
"
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
"tool.confirm_needed",
|
|
7831
|
-
"iteration.started",
|
|
7832
|
-
"iteration.completed",
|
|
7833
|
-
"provider.text_delta",
|
|
7834
|
-
// Subagent extended-thinking output. Forwarded so the FleetPanel /
|
|
7835
|
-
// /fleet log can surface "the planner is thinking…" instead of a
|
|
7836
|
-
// silent gap between iteration.started and the first text_delta.
|
|
7837
|
-
"provider.thinking_delta",
|
|
7838
|
-
"provider.response",
|
|
7839
|
-
"provider.retry",
|
|
7840
|
-
"provider.error",
|
|
7841
|
-
"session.started",
|
|
7842
|
-
"session.ended",
|
|
7843
|
-
"session.damaged",
|
|
7844
|
-
"compaction.fired",
|
|
7845
|
-
"compaction.failed",
|
|
7846
|
-
"token.threshold",
|
|
7847
|
-
// Subagent hit a soft budget limit — coordinator can extend or stop.
|
|
7848
|
-
"budget.threshold_reached"
|
|
7849
|
-
];
|
|
7850
|
-
const offs = [];
|
|
7851
|
-
for (const t2 of FORWARDED_TYPES) {
|
|
7852
|
-
offs.push(
|
|
7853
|
-
bus.on(t2, (payload) => {
|
|
7854
|
-
this.emit({ subagentId, taskId, ts: Date.now(), type: t2, payload });
|
|
7855
|
-
})
|
|
7856
|
-
);
|
|
7857
|
-
}
|
|
8215
|
+
const off = bus.onPattern("*", (type, payload) => {
|
|
8216
|
+
if (type.startsWith("subagent.")) return;
|
|
8217
|
+
this.emit({ subagentId, taskId, ts: Date.now(), type, payload });
|
|
8218
|
+
});
|
|
7858
8219
|
return () => {
|
|
7859
|
-
|
|
8220
|
+
off();
|
|
7860
8221
|
};
|
|
7861
8222
|
}
|
|
7862
8223
|
/** Subscribe to every event from one subagent. */
|
|
@@ -8021,7 +8382,7 @@ var BudgetThresholdSignal = class extends Error {
|
|
|
8021
8382
|
this.decision = decision;
|
|
8022
8383
|
}
|
|
8023
8384
|
};
|
|
8024
|
-
var SubagentBudget = class {
|
|
8385
|
+
var SubagentBudget = class _SubagentBudget {
|
|
8025
8386
|
limits;
|
|
8026
8387
|
iterations = 0;
|
|
8027
8388
|
toolCalls = 0;
|
|
@@ -8030,6 +8391,26 @@ var SubagentBudget = class {
|
|
|
8030
8391
|
costUsd = 0;
|
|
8031
8392
|
startTime = null;
|
|
8032
8393
|
_onThreshold;
|
|
8394
|
+
/**
|
|
8395
|
+
* Tracks which budget kinds currently have an extension request
|
|
8396
|
+
* in flight. While a kind is here, further `checkLimit` calls for the
|
|
8397
|
+
* same kind are no-ops — without this dedup, every `recordIteration`
|
|
8398
|
+
* after the limit is reached spawns a fresh decision Promise (until
|
|
8399
|
+
* the first one lands and patches limits), flooding the FleetBus
|
|
8400
|
+
* with redundant threshold events. Cleared in `checkLimitAsync`'s
|
|
8401
|
+
* `finally`.
|
|
8402
|
+
*/
|
|
8403
|
+
pendingExtensions = /* @__PURE__ */ new Set();
|
|
8404
|
+
/**
|
|
8405
|
+
* Hard cap on how long `checkLimitAsync` waits for the coordinator to
|
|
8406
|
+
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
8407
|
+
* or hung listener (Director not built / event filter detached mid-run)
|
|
8408
|
+
* leaves the budget over-limit and never enforces anything, since
|
|
8409
|
+
* `checkLimit` returns synchronously via `void this.checkLimitAsync`.
|
|
8410
|
+
* Hardcoded for now — most fleets set their own per-task timeout that
|
|
8411
|
+
* dwarfs this anyway. 30 s matches the existing event-emit timeoutMs.
|
|
8412
|
+
*/
|
|
8413
|
+
static DECISION_TIMEOUT_MS = 3e4;
|
|
8033
8414
|
/**
|
|
8034
8415
|
* Injected by the runner when wiring the budget to its EventBus.
|
|
8035
8416
|
* Used by `checkLimitAsync` to emit `budget.threshold_reached` events.
|
|
@@ -8072,56 +8453,92 @@ var SubagentBudget = class {
|
|
|
8072
8453
|
if (kind === "timeout" || !this._onThreshold) {
|
|
8073
8454
|
throw new BudgetExceededError(kind, limit, used);
|
|
8074
8455
|
}
|
|
8075
|
-
|
|
8456
|
+
const bus = this._events;
|
|
8457
|
+
if (!bus || bus.listenerCount("budget.threshold_reached") === 0) {
|
|
8458
|
+
throw new BudgetExceededError(kind, limit, used);
|
|
8459
|
+
}
|
|
8460
|
+
if (this.pendingExtensions.has(kind)) return;
|
|
8461
|
+
this.pendingExtensions.add(kind);
|
|
8462
|
+
const decision = this.negotiateExtension(kind, used, limit);
|
|
8463
|
+
throw new BudgetThresholdSignal(kind, limit, used, decision);
|
|
8076
8464
|
}
|
|
8077
8465
|
/**
|
|
8078
|
-
*
|
|
8079
|
-
*
|
|
8080
|
-
*
|
|
8466
|
+
* Drive the threshold handler to a decision. Resolves with `'stop'`
|
|
8467
|
+
* (signal the runner to abort) or `{ extend: ... }` (limits already
|
|
8468
|
+
* patched in-place; the runner should not abort). Always releases the
|
|
8469
|
+
* `pendingExtensions` slot in `finally`.
|
|
8470
|
+
*
|
|
8471
|
+
* The 'continue' return from a sync handler is treated as
|
|
8472
|
+
* `{ extend: {} }` — keep going without patching, next overrun will
|
|
8473
|
+
* fire a fresh signal.
|
|
8081
8474
|
*/
|
|
8082
|
-
async
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8475
|
+
async negotiateExtension(kind, used, limit) {
|
|
8476
|
+
try {
|
|
8477
|
+
const result = this._onThreshold({
|
|
8478
|
+
kind,
|
|
8479
|
+
used,
|
|
8480
|
+
limit,
|
|
8481
|
+
// Inject a requestDecision helper the handler can call to emit the
|
|
8482
|
+
// budget.threshold_reached event and wait for the coordinator's verdict.
|
|
8483
|
+
// A hard fallback timer guarantees the promise eventually resolves
|
|
8484
|
+
// even if no listener responds — without it, an absent/detached
|
|
8485
|
+
// Director would leave the budget permanently in "asking" state.
|
|
8486
|
+
requestDecision: () => {
|
|
8487
|
+
const bus = this._events;
|
|
8488
|
+
if (!bus || bus.listenerCount("budget.threshold_reached") === 0) {
|
|
8489
|
+
return Promise.resolve("stop");
|
|
8490
|
+
}
|
|
8491
|
+
return new Promise((resolve5) => {
|
|
8492
|
+
let resolved = false;
|
|
8493
|
+
const respond = (d) => {
|
|
8494
|
+
if (resolved) return;
|
|
8495
|
+
resolved = true;
|
|
8496
|
+
resolve5(d);
|
|
8497
|
+
};
|
|
8498
|
+
const fallback = setTimeout(
|
|
8499
|
+
() => respond("stop"),
|
|
8500
|
+
_SubagentBudget.DECISION_TIMEOUT_MS
|
|
8501
|
+
);
|
|
8502
|
+
bus.emit("budget.threshold_reached", {
|
|
8503
|
+
kind,
|
|
8504
|
+
used,
|
|
8505
|
+
limit,
|
|
8506
|
+
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
8507
|
+
extend: (extra) => {
|
|
8508
|
+
clearTimeout(fallback);
|
|
8509
|
+
respond({ extend: extra });
|
|
8510
|
+
},
|
|
8511
|
+
deny: () => {
|
|
8512
|
+
clearTimeout(fallback);
|
|
8513
|
+
respond("stop");
|
|
8514
|
+
}
|
|
8515
|
+
});
|
|
8099
8516
|
});
|
|
8100
|
-
}
|
|
8517
|
+
}
|
|
8518
|
+
});
|
|
8519
|
+
if (result === "throw") return "stop";
|
|
8520
|
+
if (result === "continue") return { extend: {} };
|
|
8521
|
+
const decision = await result;
|
|
8522
|
+
if (decision === "stop") return "stop";
|
|
8523
|
+
const ext = decision.extend;
|
|
8524
|
+
if (ext.maxIterations !== void 0) {
|
|
8525
|
+
this.limits.maxIterations = ext.maxIterations;
|
|
8101
8526
|
}
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
if (ext.maxToolCalls !== void 0) {
|
|
8118
|
-
this.limits.maxToolCalls = ext.maxToolCalls;
|
|
8119
|
-
}
|
|
8120
|
-
if (ext.maxTokens !== void 0) {
|
|
8121
|
-
this.limits.maxTokens = ext.maxTokens;
|
|
8122
|
-
}
|
|
8123
|
-
if (ext.maxCostUsd !== void 0) {
|
|
8124
|
-
this.limits.maxCostUsd = ext.maxCostUsd;
|
|
8527
|
+
if (ext.maxToolCalls !== void 0) {
|
|
8528
|
+
this.limits.maxToolCalls = ext.maxToolCalls;
|
|
8529
|
+
}
|
|
8530
|
+
if (ext.maxTokens !== void 0) {
|
|
8531
|
+
this.limits.maxTokens = ext.maxTokens;
|
|
8532
|
+
}
|
|
8533
|
+
if (ext.maxCostUsd !== void 0) {
|
|
8534
|
+
this.limits.maxCostUsd = ext.maxCostUsd;
|
|
8535
|
+
}
|
|
8536
|
+
if (ext.timeoutMs !== void 0) {
|
|
8537
|
+
this.limits.timeoutMs = ext.timeoutMs;
|
|
8538
|
+
}
|
|
8539
|
+
return decision;
|
|
8540
|
+
} finally {
|
|
8541
|
+
this.pendingExtensions.delete(kind);
|
|
8125
8542
|
}
|
|
8126
8543
|
}
|
|
8127
8544
|
recordIteration() {
|
|
@@ -8404,6 +8821,19 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
8404
8821
|
setRunner(runner) {
|
|
8405
8822
|
this.runner = runner;
|
|
8406
8823
|
}
|
|
8824
|
+
/**
|
|
8825
|
+
* Change the in-flight dispatch ceiling at runtime. Lowering does NOT
|
|
8826
|
+
* preempt running tasks — already-dispatched subagents finish their
|
|
8827
|
+
* current task; only future dispatches respect the new cap. Raising
|
|
8828
|
+
* immediately tries to fill the freed slots from the pending queue.
|
|
8829
|
+
*/
|
|
8830
|
+
setMaxConcurrent(n) {
|
|
8831
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
8832
|
+
throw new Error(`maxConcurrent must be a finite integer >= 1, got ${n}`);
|
|
8833
|
+
}
|
|
8834
|
+
this.config.maxConcurrent = Math.floor(n);
|
|
8835
|
+
this.tryDispatchNext();
|
|
8836
|
+
}
|
|
8407
8837
|
async spawn(subagent) {
|
|
8408
8838
|
const id = subagent.id || randomUUID();
|
|
8409
8839
|
if (this.subagents.has(id)) {
|
|
@@ -8663,14 +9093,65 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
8663
9093
|
this.recordCompletion(result);
|
|
8664
9094
|
}
|
|
8665
9095
|
async executeWithTimeout(runner, task, ctx, budget) {
|
|
8666
|
-
const
|
|
8667
|
-
if (
|
|
9096
|
+
const initialTimeoutMs = budget.limits.timeoutMs;
|
|
9097
|
+
if (initialTimeoutMs === void 0) return runner(task, ctx);
|
|
9098
|
+
const start = Date.now();
|
|
8668
9099
|
let timer = null;
|
|
8669
9100
|
const timeoutPromise = new Promise((_, reject) => {
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
9101
|
+
const armFor = (ms) => {
|
|
9102
|
+
if (timer) clearTimeout(timer);
|
|
9103
|
+
timer = setTimeout(async () => {
|
|
9104
|
+
const elapsed = Date.now() - start;
|
|
9105
|
+
const limit = budget.limits.timeoutMs ?? initialTimeoutMs;
|
|
9106
|
+
if (!budget.onThreshold) {
|
|
9107
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
9108
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
9109
|
+
return;
|
|
9110
|
+
}
|
|
9111
|
+
try {
|
|
9112
|
+
const result = budget.onThreshold({
|
|
9113
|
+
kind: "timeout",
|
|
9114
|
+
used: elapsed,
|
|
9115
|
+
limit,
|
|
9116
|
+
requestDecision: () => new Promise((resolveDecision) => {
|
|
9117
|
+
budget._events?.emit("budget.threshold_reached", {
|
|
9118
|
+
kind: "timeout",
|
|
9119
|
+
used: elapsed,
|
|
9120
|
+
limit,
|
|
9121
|
+
timeoutMs: 3e4,
|
|
9122
|
+
extend: (extra) => resolveDecision({ extend: extra }),
|
|
9123
|
+
deny: () => resolveDecision("stop")
|
|
9124
|
+
});
|
|
9125
|
+
})
|
|
9126
|
+
});
|
|
9127
|
+
const decision = typeof result === "string" ? result : await result;
|
|
9128
|
+
if (decision === "continue") {
|
|
9129
|
+
armFor(Math.max(1e3, limit));
|
|
9130
|
+
return;
|
|
9131
|
+
}
|
|
9132
|
+
if (decision === "throw" || decision === "stop") {
|
|
9133
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
9134
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
9135
|
+
return;
|
|
9136
|
+
}
|
|
9137
|
+
if (decision.extend.timeoutMs !== void 0) {
|
|
9138
|
+
budget.limits.timeoutMs = decision.extend.timeoutMs;
|
|
9139
|
+
const newLimit = decision.extend.timeoutMs;
|
|
9140
|
+
const remaining = Math.max(1e3, newLimit - elapsed);
|
|
9141
|
+
armFor(remaining);
|
|
9142
|
+
return;
|
|
9143
|
+
}
|
|
9144
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
9145
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
9146
|
+
} catch (err) {
|
|
9147
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
9148
|
+
reject(
|
|
9149
|
+
err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
|
|
9150
|
+
);
|
|
9151
|
+
}
|
|
9152
|
+
}, ms);
|
|
9153
|
+
};
|
|
9154
|
+
armFor(initialTimeoutMs);
|
|
8674
9155
|
});
|
|
8675
9156
|
try {
|
|
8676
9157
|
return await Promise.race([runner(task, ctx), timeoutPromise]);
|
|
@@ -9281,7 +9762,7 @@ var Director = class {
|
|
|
9281
9762
|
return;
|
|
9282
9763
|
}
|
|
9283
9764
|
extendCounts.set(guardKey, prior + 1);
|
|
9284
|
-
|
|
9765
|
+
setImmediate(() => {
|
|
9285
9766
|
const extra = {};
|
|
9286
9767
|
switch (payload.kind) {
|
|
9287
9768
|
case "iterations":
|
|
@@ -9296,9 +9777,12 @@ var Director = class {
|
|
|
9296
9777
|
case "cost":
|
|
9297
9778
|
extra.maxCostUsd = Math.min(payload.limit * 1.5, 10);
|
|
9298
9779
|
break;
|
|
9780
|
+
case "timeout":
|
|
9781
|
+
extra.timeoutMs = Math.min(Math.ceil(payload.limit * 1.5), 60 * 6e4);
|
|
9782
|
+
break;
|
|
9299
9783
|
}
|
|
9300
9784
|
payload.extend(extra);
|
|
9301
|
-
}
|
|
9785
|
+
});
|
|
9302
9786
|
});
|
|
9303
9787
|
}
|
|
9304
9788
|
/** Best-effort session-writer append. Swallows failures — the director
|
|
@@ -10093,6 +10577,7 @@ function makeAgentSubagentRunner(opts) {
|
|
|
10093
10577
|
const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
|
|
10094
10578
|
const aborter = new AbortController();
|
|
10095
10579
|
ctx.budget._events = events;
|
|
10580
|
+
ctx.budget.onThreshold = ({ requestDecision }) => requestDecision();
|
|
10096
10581
|
let budgetError = null;
|
|
10097
10582
|
const onBudgetError = (err) => {
|
|
10098
10583
|
if (err instanceof BudgetThresholdSignal) {
|
|
@@ -12923,7 +13408,7 @@ function wireMetricsToEvents(events, sink) {
|
|
|
12923
13408
|
events.on("token.threshold", (e) => sink.gauge("agent.tokens.used", e.used)),
|
|
12924
13409
|
events.on("compaction.fired", (e) => {
|
|
12925
13410
|
sink.counter("compaction.fired.total");
|
|
12926
|
-
sink.histogram("compaction.reduction_tokens", e.before - e.after);
|
|
13411
|
+
sink.histogram("compaction.reduction_tokens", e.report.before - e.report.after);
|
|
12927
13412
|
}),
|
|
12928
13413
|
events.on(
|
|
12929
13414
|
"mcp.server.connected",
|
|
@@ -16118,16 +16603,16 @@ Use \`/security report <number>\` to view a specific report.` };
|
|
|
16118
16603
|
}
|
|
16119
16604
|
const index = parseInt(reportId, 10) - 1;
|
|
16120
16605
|
if (!isNaN(index) && reports[index]) {
|
|
16121
|
-
const { readFile:
|
|
16122
|
-
const content = await
|
|
16606
|
+
const { readFile: readFile30 } = await import('fs/promises');
|
|
16607
|
+
const content = await readFile30(join(reportsDir, reports[index]), "utf-8");
|
|
16123
16608
|
return { message: `# Security Report
|
|
16124
16609
|
|
|
16125
16610
|
${content}` };
|
|
16126
16611
|
}
|
|
16127
16612
|
const match = reports.find((r) => r.includes(reportId));
|
|
16128
16613
|
if (match) {
|
|
16129
|
-
const { readFile:
|
|
16130
|
-
const content = await
|
|
16614
|
+
const { readFile: readFile30 } = await import('fs/promises');
|
|
16615
|
+
const content = await readFile30(join(reportsDir, match), "utf-8");
|
|
16131
16616
|
return { message: `# Security Report
|
|
16132
16617
|
|
|
16133
16618
|
${content}` };
|
|
@@ -16382,6 +16867,226 @@ var FleetManager = class {
|
|
|
16382
16867
|
return { pending, live: [] };
|
|
16383
16868
|
}
|
|
16384
16869
|
};
|
|
16870
|
+
function createMcpControlTool(opts) {
|
|
16871
|
+
const { getConfig, configPath, registry } = opts;
|
|
16872
|
+
const inputSchema = {
|
|
16873
|
+
type: "object",
|
|
16874
|
+
properties: {
|
|
16875
|
+
action: {
|
|
16876
|
+
type: "string",
|
|
16877
|
+
enum: ["list", "search", "enable", "disable", "restart"],
|
|
16878
|
+
description: "The management action to perform."
|
|
16879
|
+
},
|
|
16880
|
+
/** Filter for `search`. Matches server name or description case-insensitively. */
|
|
16881
|
+
query: {
|
|
16882
|
+
type: "string",
|
|
16883
|
+
description: "Search term for `search` action. Matches server name or description."
|
|
16884
|
+
},
|
|
16885
|
+
/** Target server name for `enable`, `disable`, `restart`. */
|
|
16886
|
+
server: {
|
|
16887
|
+
type: "string",
|
|
16888
|
+
description: 'Server name (e.g. "github", "filesystem", "brave-search").'
|
|
16889
|
+
}
|
|
16890
|
+
},
|
|
16891
|
+
required: ["action"]
|
|
16892
|
+
};
|
|
16893
|
+
return {
|
|
16894
|
+
name: "mcp_control",
|
|
16895
|
+
description: "Manage MCP server lifecycle: list available servers, search by name or capability, enable or disable servers at runtime, restart running servers.",
|
|
16896
|
+
category: "mcp",
|
|
16897
|
+
permission: "auto",
|
|
16898
|
+
mutating: false,
|
|
16899
|
+
riskTier: "standard",
|
|
16900
|
+
inputSchema,
|
|
16901
|
+
async execute(raw, ctx) {
|
|
16902
|
+
const input = raw;
|
|
16903
|
+
return mcpControlDispatch(input, { getConfig, configPath, registry });
|
|
16904
|
+
}
|
|
16905
|
+
};
|
|
16906
|
+
}
|
|
16907
|
+
async function mcpControlDispatch(input, deps) {
|
|
16908
|
+
const { action, query, server } = input;
|
|
16909
|
+
switch (action) {
|
|
16910
|
+
case "list":
|
|
16911
|
+
return renderList(deps);
|
|
16912
|
+
case "search":
|
|
16913
|
+
return renderSearch(query ?? "", deps);
|
|
16914
|
+
case "enable":
|
|
16915
|
+
return runEnable(server, deps);
|
|
16916
|
+
case "disable":
|
|
16917
|
+
return runDisable(server, deps);
|
|
16918
|
+
case "restart":
|
|
16919
|
+
return runRestart(server, deps);
|
|
16920
|
+
default:
|
|
16921
|
+
return `Unknown action "${action}". Use one of: list, search, enable, disable, restart.`;
|
|
16922
|
+
}
|
|
16923
|
+
}
|
|
16924
|
+
function renderList(deps) {
|
|
16925
|
+
const configured = deps.getConfig().mcpServers ?? {};
|
|
16926
|
+
const live = deps.registry.describe();
|
|
16927
|
+
if (Object.keys(configured).length === 0) {
|
|
16928
|
+
return [
|
|
16929
|
+
"No MCP servers configured.",
|
|
16930
|
+
' Use `mcp_control({ action: "search" })` to see available presets,',
|
|
16931
|
+
' then `mcp_control({ action: "enable", server: "<name>" })` to add one.'
|
|
16932
|
+
].join("\n");
|
|
16933
|
+
}
|
|
16934
|
+
const lines = [];
|
|
16935
|
+
const liveMap = new Map(live.map((s) => [s.name, s]));
|
|
16936
|
+
for (const [name, cfg] of Object.entries(configured)) {
|
|
16937
|
+
const liveInfo = liveMap.get(name);
|
|
16938
|
+
const toolCount = liveInfo ? ` (${liveInfo.toolCount} tools)` : "";
|
|
16939
|
+
const stateStr = liveInfo ? badge(liveInfo.state) : dim("\u25CB not loaded");
|
|
16940
|
+
const enabled = cfg.enabled === false ? `${dim("disabled")} ` : `${green("\u25CF enabled")} `;
|
|
16941
|
+
lines.push(` ${bold(name)} ${enabled}${stateStr}${toolCount}`);
|
|
16942
|
+
if (cfg.description) lines.push(` ${dim(cfg.description)}`);
|
|
16943
|
+
}
|
|
16944
|
+
lines.push("");
|
|
16945
|
+
lines.push(dim(' Use `mcp_control({ action: "search", query: "<keyword>" })` to find servers.'));
|
|
16946
|
+
lines.push(dim(' Use `mcp_control({ action: "enable", server: "<name>" })` to start a server.'));
|
|
16947
|
+
return lines.join("\n");
|
|
16948
|
+
}
|
|
16949
|
+
function renderSearch(query, deps) {
|
|
16950
|
+
const configured = deps.getConfig().mcpServers ?? {};
|
|
16951
|
+
const all = allServers();
|
|
16952
|
+
const q = query.toLowerCase();
|
|
16953
|
+
const configuredNames = new Set(Object.keys(configured));
|
|
16954
|
+
const configuredEntries = Object.entries(configured).filter(
|
|
16955
|
+
([name, cfg]) => name.toLowerCase().includes(q) || (cfg.description ?? "").toLowerCase().includes(q)
|
|
16956
|
+
);
|
|
16957
|
+
const unconfiguredEntries = Object.entries(all).filter(([name]) => !configuredNames.has(name)).filter(
|
|
16958
|
+
([name, cfg]) => name.toLowerCase().includes(q) || (cfg.description ?? "").toLowerCase().includes(q)
|
|
16959
|
+
);
|
|
16960
|
+
const lines = [];
|
|
16961
|
+
if (configuredEntries.length > 0) {
|
|
16962
|
+
lines.push(bold('Configured servers matching "') + query + '":');
|
|
16963
|
+
for (const [name, cfg] of configuredEntries) {
|
|
16964
|
+
lines.push(` ${bold(name)} ${cfg.description ?? cfg.transport}`);
|
|
16965
|
+
}
|
|
16966
|
+
lines.push("");
|
|
16967
|
+
}
|
|
16968
|
+
if (unconfiguredEntries.length > 0) {
|
|
16969
|
+
lines.push(bold('Available presets matching "') + query + '":');
|
|
16970
|
+
for (const [name, cfg] of unconfiguredEntries) {
|
|
16971
|
+
const warn = cfg.permission === "deny" ? red(" \u26A0 confirm required") : "";
|
|
16972
|
+
lines.push(` ${bold(name)} ${cfg.description ?? cfg.transport}${warn}`);
|
|
16973
|
+
}
|
|
16974
|
+
lines.push("");
|
|
16975
|
+
}
|
|
16976
|
+
if (configuredEntries.length === 0 && unconfiguredEntries.length === 0) {
|
|
16977
|
+
return `No servers match "${query}". Try a shorter keyword or \`mcp_control({ action: "list" })\`.`;
|
|
16978
|
+
}
|
|
16979
|
+
const total = configuredEntries.length + unconfiguredEntries.length;
|
|
16980
|
+
lines.push(dim(` ${total} server${total !== 1 ? "s" : ""} shown. Run \`enable\` on one to activate it.`));
|
|
16981
|
+
return lines.join("\n");
|
|
16982
|
+
}
|
|
16983
|
+
async function runEnable(name, deps) {
|
|
16984
|
+
if (!name) return '`server` is required for enable. Example: { action: "enable", server: "github" }';
|
|
16985
|
+
const all = allServers();
|
|
16986
|
+
const configured = deps.getConfig().mcpServers ?? {};
|
|
16987
|
+
const cfg = configured[name] ?? all[name];
|
|
16988
|
+
if (!cfg) {
|
|
16989
|
+
const known = Object.keys(all).join(", ");
|
|
16990
|
+
return `Unknown server "${name}". Available presets: ${known}`;
|
|
16991
|
+
}
|
|
16992
|
+
const full = await readConfig(deps.configPath);
|
|
16993
|
+
const mcpServers = {
|
|
16994
|
+
...full.mcpServers ?? {}
|
|
16995
|
+
};
|
|
16996
|
+
mcpServers[name] = { ...cfg, enabled: true };
|
|
16997
|
+
full.mcpServers = mcpServers;
|
|
16998
|
+
await writeConfig(deps.configPath, full);
|
|
16999
|
+
try {
|
|
17000
|
+
const live = deps.registry.describe().find((s) => s.name === name);
|
|
17001
|
+
if (live && live.state === "connected") {
|
|
17002
|
+
return `${green("\u25CF")} Server "${name}" is already running (${live.toolCount} tools registered).`;
|
|
17003
|
+
}
|
|
17004
|
+
await deps.registry.start({ ...cfg, enabled: true });
|
|
17005
|
+
const updated = deps.registry.describe().find((s) => s.name === name);
|
|
17006
|
+
return `${green("\u2713 Enabled and started")} "${name}"${updated ? ` (${updated.toolCount} tools registered).` : "."}`;
|
|
17007
|
+
} catch (err) {
|
|
17008
|
+
return `${red("\u2717 Failed to start")} "${name}": ${err instanceof Error ? err.message : String(err)}`;
|
|
17009
|
+
}
|
|
17010
|
+
}
|
|
17011
|
+
async function runDisable(name, deps) {
|
|
17012
|
+
if (!name) return '`server` is required for disable. Example: { action: "disable", server: "github" }';
|
|
17013
|
+
const configured = deps.getConfig().mcpServers ?? {};
|
|
17014
|
+
if (!configured[name]) {
|
|
17015
|
+
return `Server "${name}" is not in config. Add it with \`mcp_control({ action: "enable", server: "${name}" })\`.`;
|
|
17016
|
+
}
|
|
17017
|
+
const full = await readConfig(deps.configPath);
|
|
17018
|
+
const mcpServers = {
|
|
17019
|
+
...full.mcpServers ?? {}
|
|
17020
|
+
};
|
|
17021
|
+
const existing = mcpServers[name];
|
|
17022
|
+
mcpServers[name] = { ...existing, enabled: false };
|
|
17023
|
+
full.mcpServers = mcpServers;
|
|
17024
|
+
await writeConfig(deps.configPath, full);
|
|
17025
|
+
try {
|
|
17026
|
+
await deps.registry.stop(name);
|
|
17027
|
+
return `${yellow("\u25CB Disabled")} "${name}". It will not be started on next boot.`;
|
|
17028
|
+
} catch {
|
|
17029
|
+
return `${yellow("\u25CB Disabled")} "${name}" (it was not running). Config updated.`;
|
|
17030
|
+
}
|
|
17031
|
+
}
|
|
17032
|
+
async function runRestart(name, deps) {
|
|
17033
|
+
if (!name) return '`server` is required for restart. Example: { action: "restart", server: "github" }';
|
|
17034
|
+
const configured = deps.getConfig().mcpServers ?? {};
|
|
17035
|
+
if (!configured[name]) {
|
|
17036
|
+
return `Server "${name}" is not configured. Use \`mcp_control({ action: "enable", server: "${name}" })\` first.`;
|
|
17037
|
+
}
|
|
17038
|
+
try {
|
|
17039
|
+
await deps.registry.restart(name);
|
|
17040
|
+
const updated = deps.registry.describe().find((s) => s.name === name);
|
|
17041
|
+
return `${green("\u2713 Restarted")} "${name}"${updated ? ` (${updated.toolCount} tools registered).` : "."}`;
|
|
17042
|
+
} catch (err) {
|
|
17043
|
+
return `${red("\u2717 Restart failed")} for "${name}": ${err instanceof Error ? err.message : String(err)}`;
|
|
17044
|
+
}
|
|
17045
|
+
}
|
|
17046
|
+
async function readConfig(p) {
|
|
17047
|
+
try {
|
|
17048
|
+
return JSON.parse(await fsp2.readFile(p, "utf8"));
|
|
17049
|
+
} catch {
|
|
17050
|
+
return {};
|
|
17051
|
+
}
|
|
17052
|
+
}
|
|
17053
|
+
async function writeConfig(p, cfg) {
|
|
17054
|
+
const raw = JSON.stringify(cfg, null, 2);
|
|
17055
|
+
const tmp = p + ".tmp";
|
|
17056
|
+
await fsp2.writeFile(tmp, raw, "utf8");
|
|
17057
|
+
await fsp2.rename(tmp, p);
|
|
17058
|
+
}
|
|
17059
|
+
function bold(s) {
|
|
17060
|
+
return `\x1B[1m${s}\x1B[0m`;
|
|
17061
|
+
}
|
|
17062
|
+
function dim(s) {
|
|
17063
|
+
return `\x1B[2m${s}\x1B[0m`;
|
|
17064
|
+
}
|
|
17065
|
+
function green(s) {
|
|
17066
|
+
return `\x1B[32m${s}\x1B[0m`;
|
|
17067
|
+
}
|
|
17068
|
+
function yellow(s) {
|
|
17069
|
+
return `\x1B[33m${s}\x1B[0m`;
|
|
17070
|
+
}
|
|
17071
|
+
function red(s) {
|
|
17072
|
+
return `\x1B[31m${s}\x1B[0m`;
|
|
17073
|
+
}
|
|
17074
|
+
function badge(state) {
|
|
17075
|
+
switch (state) {
|
|
17076
|
+
case "connected":
|
|
17077
|
+
return green("\u25CF connected");
|
|
17078
|
+
case "connecting":
|
|
17079
|
+
return `\x1B[36m\u25D0 connecting\x1B[0m`;
|
|
17080
|
+
case "reconnecting":
|
|
17081
|
+
return `\x1B[36m\u25D1 reconnecting\x1B[0m`;
|
|
17082
|
+
case "disconnected":
|
|
17083
|
+
return dim("\u25CB disconnected");
|
|
17084
|
+
case "failed":
|
|
17085
|
+
return red("\u2717 failed");
|
|
17086
|
+
default:
|
|
17087
|
+
return dim(state);
|
|
17088
|
+
}
|
|
17089
|
+
}
|
|
16385
17090
|
|
|
16386
17091
|
// src/extension/registry.ts
|
|
16387
17092
|
var ExtensionRegistry = class {
|
|
@@ -16844,6 +17549,7 @@ var Agent = class {
|
|
|
16844
17549
|
let effectiveLimit = opts.maxIterations ?? this.maxIterations;
|
|
16845
17550
|
const hasHardLimit = effectiveLimit > 0 && Number.isFinite(effectiveLimit);
|
|
16846
17551
|
let recoveryRetries = 0;
|
|
17552
|
+
const autonomousContinue = opts.autonomousContinue ?? this.autonomousContinue;
|
|
16847
17553
|
const diRunner = this.container.has(TOKENS.ProviderRunner) ? this.container.resolve(TOKENS.ProviderRunner) : null;
|
|
16848
17554
|
const baseRunner = diRunner ? (ctx, req) => diRunner.run({
|
|
16849
17555
|
provider: ctx.provider,
|
|
@@ -16870,7 +17576,7 @@ var Agent = class {
|
|
|
16870
17576
|
if (controller.signal.aborted) {
|
|
16871
17577
|
return { status: "aborted", iterations };
|
|
16872
17578
|
}
|
|
16873
|
-
if (
|
|
17579
|
+
if (autonomousContinue) {
|
|
16874
17580
|
consumeAutonomousContinue(this.ctx);
|
|
16875
17581
|
}
|
|
16876
17582
|
const limitCheck = await this.checkIterationLimit(
|
|
@@ -16949,18 +17655,18 @@ var Agent = class {
|
|
|
16949
17655
|
const toolUses = res.content.filter(isToolUseBlock);
|
|
16950
17656
|
if (toolUses.length === 0) {
|
|
16951
17657
|
this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
|
|
16952
|
-
if (
|
|
17658
|
+
if (autonomousContinue && responseResult.directive === "continue") {
|
|
16953
17659
|
await this.compactContextIfNeeded();
|
|
16954
17660
|
await this.extensions.runAfterIteration(this.ctx, i);
|
|
16955
17661
|
continue;
|
|
16956
17662
|
}
|
|
16957
|
-
if (
|
|
17663
|
+
if (autonomousContinue && responseResult.directive === "stop") {
|
|
16958
17664
|
return { status: "done", iterations, finalText };
|
|
16959
17665
|
}
|
|
16960
17666
|
return { status: "done", iterations, finalText };
|
|
16961
17667
|
}
|
|
16962
17668
|
await this.executeTools(toolUses);
|
|
16963
|
-
if (
|
|
17669
|
+
if (autonomousContinue && consumeAutonomousContinue(this.ctx)) {
|
|
16964
17670
|
this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
|
|
16965
17671
|
await this.compactContextIfNeeded();
|
|
16966
17672
|
await this.extensions.runAfterIteration(this.ctx, i);
|
|
@@ -16969,10 +17675,10 @@ var Agent = class {
|
|
|
16969
17675
|
this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
|
|
16970
17676
|
await this.compactContextIfNeeded();
|
|
16971
17677
|
await this.extensions.runAfterIteration(this.ctx, i);
|
|
16972
|
-
if (
|
|
17678
|
+
if (autonomousContinue && responseResult.directive === "continue") {
|
|
16973
17679
|
continue;
|
|
16974
17680
|
}
|
|
16975
|
-
if (
|
|
17681
|
+
if (autonomousContinue && responseResult.directive === "stop") {
|
|
16976
17682
|
return { status: "done", iterations, finalText };
|
|
16977
17683
|
}
|
|
16978
17684
|
}
|
|
@@ -17062,7 +17768,7 @@ var Agent = class {
|
|
|
17062
17768
|
}
|
|
17063
17769
|
}
|
|
17064
17770
|
let directive = "none";
|
|
17065
|
-
if (
|
|
17771
|
+
if (finalText) {
|
|
17066
17772
|
directive = parseContinueDirective(finalText);
|
|
17067
17773
|
}
|
|
17068
17774
|
return { finalText, aborted: false, done: false, directive };
|
|
@@ -18658,6 +19364,6 @@ function wrapApiForCapabilityCheck(plugin, api, log, enforce = false) {
|
|
|
18658
19364
|
});
|
|
18659
19365
|
}
|
|
18660
19366
|
|
|
18661
|
-
export { AISpecBuilder, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, Agent, AgentError, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, CONTEXT_WINDOW_MODES, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorStateCheckpoint, DoneConditionChecker, EternalAutonomyEngine, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, GitignoreUpdater, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, MAX_JOURNAL_ENTRIES, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, ScopedEventBus, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolError, ToolExecutor, ToolRegistry, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, appendJournal, applyRosterBudget, asBlocks, asText, atomicWrite, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildChildEnv, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, color, compileGlob, compileUserRegex, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDefaultPipelines, createDelegateTool, createMessage, createSecuritySlashCommand, createToolOutputSerializer, decryptConfigSecrets, defaultGitignoreUpdater, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, deriveTodosFromPlanItem, detectNewlineStyle, downloadGitHubTarball, emptyGoal, emptyPlan, encryptConfigSecrets, ensureDir, estimateRequestTokens, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, formatContextWindowModeList, formatGoal, formatPlan, formatPlanTemplates, formatTodosList, getContextWindowMode, getPlanTemplate, getTemplate, githubServer, goalFilePath, googleMapsServer, isAgentError, isConfigError, isContextWindowModeId, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadGoal, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeContinueToNextIterationTool, makeDirectorSessionFactory, matchAny, matchGlob, migratePlaintextSecrets, miniMaxVisionServer, normalizeToLf, parseContinueDirective, parseSkillRef, projectHash, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, resolveContextWindowPolicy, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, saveGoal, savePlan, saveTodosCheckpoint, securitySlashCommand, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, summarizeUsage, templateToMarkdown, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState, zaiVisionServer };
|
|
19367
|
+
export { AISpecBuilder, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, Agent, AgentError, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, CONTEXT_WINDOW_MODES, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorStateCheckpoint, DoneConditionChecker, EternalAutonomyEngine, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, GitignoreUpdater, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, MAX_JOURNAL_ENTRIES, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, ScopedEventBus, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolError, ToolExecutor, ToolRegistry, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, appendJournal, applyRosterBudget, asBlocks, asText, atomicWrite, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildChildEnv, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, color, compileGlob, compileUserRegex, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDefaultPipelines, createDelegateTool, createMcpControlTool, createMessage, createSecuritySlashCommand, createToolOutputSerializer, decryptConfigSecrets, defaultGitignoreUpdater, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, deriveTodosFromPlanItem, detectNewlineStyle, downloadGitHubTarball, emptyGoal, emptyPlan, encryptConfigSecrets, ensureDir, estimateRequestTokens, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, formatContextWindowModeList, formatGoal, formatPlan, formatPlanTemplates, formatTodosList, getContextWindowMode, getPlanTemplate, getTemplate, githubServer, goalFilePath, googleMapsServer, isAgentError, isConfigError, isContextWindowModeId, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadGoal, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAutonomyPromptContributor, makeContinueToNextIterationTool, makeDirectorSessionFactory, matchAny, matchGlob, migratePlaintextSecrets, miniMaxVisionServer, normalizeToLf, parseContinueDirective, parseSkillRef, projectHash, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, resolveContextWindowPolicy, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, saveGoal, savePlan, saveTodosCheckpoint, securitySlashCommand, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, summarizeUsage, templateToMarkdown, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState, zaiVisionServer };
|
|
18662
19368
|
//# sourceMappingURL=index.js.map
|
|
18663
19369
|
//# sourceMappingURL=index.js.map
|