@wrongstack/core 0.89.1 → 0.104.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.
Files changed (87) hide show
  1. package/dist/{agent-bridge-B8F2ORwg.d.ts → agent-bridge-mOxbpFcg.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-CQaLFNnA.d.ts → agent-subagent-runner-DukQLUcS.d.ts} +433 -433
  3. package/dist/{events-DzyLtja6.d.ts → brain-Dfv4Y82E.d.ts} +179 -92
  4. package/dist/{compactor-CJq7LQev.d.ts → compactor-DXLxLcmU.d.ts} +2 -2
  5. package/dist/{config-_DZ7dN-T.d.ts → config-BSU-6vah.d.ts} +45 -38
  6. package/dist/{context-ToHAp4-U.d.ts → context-CNRYfhUv.d.ts} +1 -1
  7. package/dist/coordination/index.d.ts +16 -17
  8. package/dist/coordination/index.js +128 -37
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +34 -35
  11. package/dist/defaults/index.js +752 -218
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/{director-state-CgIc30qi.d.ts → director-state-BfeCUbmk.d.ts} +1 -1
  14. package/dist/execution/index.d.ts +65 -370
  15. package/dist/execution/index.js +446 -53
  16. package/dist/execution/index.js.map +1 -1
  17. package/dist/extension/index.d.ts +9 -10
  18. package/dist/extension/index.js +8 -6
  19. package/dist/extension/index.js.map +1 -1
  20. package/dist/goal-preamble-CI8lxeY1.d.ts +378 -0
  21. package/dist/{goal-store-DvWLNu52.d.ts → goal-store-ht0VmR1A.d.ts} +63 -21
  22. package/dist/{index-Ca_lOmKl.d.ts → index-BWRN6wOb.d.ts} +13 -15
  23. package/dist/{index-Dx-CnrDN.d.ts → index-DIKEcfgC.d.ts} +9 -10
  24. package/dist/index.d.ts +188 -176
  25. package/dist/index.js +1508 -407
  26. package/dist/index.js.map +1 -1
  27. package/dist/infrastructure/index.d.ts +9 -9
  28. package/dist/infrastructure/index.js +4 -2
  29. package/dist/infrastructure/index.js.map +1 -1
  30. package/dist/kernel/index.d.ts +14 -16
  31. package/dist/kernel/index.js.map +1 -1
  32. package/dist/{logger-B72yyPc6.d.ts → logger-B63L5bTg.d.ts} +1 -1
  33. package/dist/{logger-C_27pj9i.d.ts → logger-B9J5puGM.d.ts} +1 -1
  34. package/dist/{mcp-servers-Dck3T85_.d.ts → mcp-servers-CXCsANdY.d.ts} +3 -3
  35. package/dist/{mode-CHo2XtHs.d.ts → mode-ARA3HrkY.d.ts} +1 -1
  36. package/dist/models/index.d.ts +5 -5
  37. package/dist/models/index.js +8 -6
  38. package/dist/models/index.js.map +1 -1
  39. package/dist/{models-registry-Be3osGt5.d.ts → models-registry-B6_KfS65.d.ts} +1 -1
  40. package/dist/{models-registry-Boz639EI.d.ts → models-registry-DU64QxQa.d.ts} +1 -1
  41. package/dist/{multi-agent-coordinator-Da9nd8Ic.d.ts → multi-agent-coordinator-51LvnXkD.d.ts} +2 -2
  42. package/dist/{null-fleet-bus-CHJOXv3t.d.ts → null-fleet-bus-D09hMzFQ.d.ts} +32 -31
  43. package/dist/observability/index.d.ts +3 -3
  44. package/dist/{observability-CoSNZdhX.d.ts → observability-D-HZN_mF.d.ts} +1 -1
  45. package/dist/{parallel-eternal-engine-DyMCj2Pw.d.ts → parallel-eternal-engine-CUtmM_0V.d.ts} +54 -35
  46. package/dist/{path-resolver-TjBlRKyH.d.ts → path-resolver-DDJiMAtX.d.ts} +3 -3
  47. package/dist/{permission-14CChMmO.d.ts → permission-BDv7z0mk.d.ts} +7 -2
  48. package/dist/{permission-policy-gW5htOo1.d.ts → permission-policy-dF74EpDp.d.ts} +2 -2
  49. package/dist/{system-prompt-C0rLCeyn.d.ts → pipeline-BqiA_UMr.d.ts} +73 -73
  50. package/dist/{plan-templates-D9UhwoGh.d.ts → plan-templates-BdDxl9cI.d.ts} +79 -11
  51. package/dist/{provider-runner-BEPWwkxH.d.ts → provider-runner-BUunikwY.d.ts} +5 -5
  52. package/dist/{retry-policy-DSu6O6rD.d.ts → retry-policy-BcmuT_V0.d.ts} +2 -2
  53. package/dist/sdd/index.d.ts +17 -33
  54. package/dist/sdd/index.js +145 -36
  55. package/dist/sdd/index.js.map +1 -1
  56. package/dist/{secret-scrubber-yGBFQYju.d.ts → secret-vault-DrOhc2i5.d.ts} +7 -7
  57. package/dist/security/index.d.ts +4 -5
  58. package/dist/security/index.js +4 -2
  59. package/dist/security/index.js.map +1 -1
  60. package/dist/{selector-11-fm95U.d.ts → selector-C7wcdqMA.d.ts} +1 -1
  61. package/dist/{session-event-bridge-D0u-x576.d.ts → session-event-bridge-BpJ5trO9.d.ts} +2 -2
  62. package/dist/{session-reader-BQU-toaN.d.ts → session-reader-DDz1Ek4V.d.ts} +2 -2
  63. package/dist/{skill-BJeF2DwY.d.ts → skill-Bj6Ezqb8.d.ts} +1 -1
  64. package/dist/skills/index.d.ts +1 -1
  65. package/dist/skills/index.js +3 -9
  66. package/dist/skills/index.js.map +1 -1
  67. package/dist/spec-TBi3Jr6T.d.ts +78 -0
  68. package/dist/storage/index.d.ts +145 -13
  69. package/dist/storage/index.js +990 -172
  70. package/dist/storage/index.js.map +1 -1
  71. package/dist/task-format-vGOIftmK.d.ts +23 -0
  72. package/dist/task-graph-u1q9Jkyk.d.ts +84 -0
  73. package/dist/types/index.d.ts +27 -28
  74. package/dist/types/index.js +19 -31
  75. package/dist/types/index.js.map +1 -1
  76. package/dist/utils/expect-defined.d.ts +7 -0
  77. package/dist/utils/expect-defined.js +11 -0
  78. package/dist/utils/expect-defined.js.map +1 -0
  79. package/dist/utils/index.d.ts +8 -20
  80. package/dist/utils/index.js +120 -41
  81. package/dist/utils/index.js.map +1 -1
  82. package/dist/{wstack-paths-BQMvEllz.d.ts → wstack-paths-_lqjzErq.d.ts} +1 -1
  83. package/package.json +5 -1
  84. package/skills/tech-stack/SKILL.md +177 -0
  85. package/dist/memory-CEXuo7sz.d.ts +0 -16
  86. package/dist/secret-scrubber-3MHDDAtm.d.ts +0 -6
  87. package/dist/task-graph-CikNdRTG.d.ts +0 -161
@@ -6,13 +6,15 @@ import { randomUUID, createHash, randomBytes } from 'crypto';
6
6
  import * as os from 'os';
7
7
  import { EventEmitter } from 'events';
8
8
 
9
- // src/utils/token-estimate.ts
10
- function expectDefined(value) {
9
+ // src/utils/expect-defined.ts
10
+ function expectDefined(value, label) {
11
11
  if (value === null || value === void 0) {
12
12
  throw new Error("Expected value to be defined");
13
13
  }
14
14
  return value;
15
15
  }
16
+
17
+ // src/utils/token-estimate.ts
16
18
  var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
17
19
  var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
18
20
  var ESTIMATE_CACHE_MAX_SIZE = 1e4;
@@ -100,12 +102,6 @@ function estimateRequestTokensCalibrated(messages, systemPrompt, tools) {
100
102
  }
101
103
 
102
104
  // src/utils/message-invariants.ts
103
- function expectDefined2(value) {
104
- if (value === null || value === void 0) {
105
- throw new Error("Expected value to be defined");
106
- }
107
- return value;
108
- }
109
105
  function repairToolUseAdjacency(messages) {
110
106
  const removedToolUses = [];
111
107
  const removedToolResults = [];
@@ -113,7 +109,7 @@ function repairToolUseAdjacency(messages) {
113
109
  let changed = false;
114
110
  const out = [];
115
111
  for (let i = 0; i < messages.length; i++) {
116
- const original = expectDefined2(messages[i]);
112
+ const original = expectDefined(messages[i]);
117
113
  let msg = original;
118
114
  if (hasToolUse(msg)) {
119
115
  const nextIds = toolResultIds(messages[i + 1]);
@@ -613,12 +609,6 @@ var IntelligentCompactor = class {
613
609
  };
614
610
 
615
611
  // src/models/llm-selector.ts
616
- function expectDefined3(value) {
617
- if (value === null || value === void 0) {
618
- throw new Error("Expected value to be defined");
619
- }
620
- return value;
621
- }
622
612
  var DEFAULT_SYSTEM_PROMPT = `You are a context pruning assistant. Given a conversation history and a token budget, decide which message ranges are worth keeping verbatim and which should be collapsed into summaries.
623
613
 
624
614
  Output a JSON object with this structure:
@@ -659,7 +649,7 @@ function formatMessages(messages, maxChars = 8e3) {
659
649
  const lines = [];
660
650
  let used = 0;
661
651
  for (let i = 0; i < messages.length; i++) {
662
- const m = expectDefined3(messages[i]);
652
+ const m = expectDefined(messages[i]);
663
653
  const role = m.role.padEnd(10, " ");
664
654
  let text;
665
655
  if (typeof m.content === "string") {
@@ -724,7 +714,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
724
714
  let tokenCount = 0;
725
715
  let startIdx = 0;
726
716
  for (let i = messages.length - 1; i >= 0; i--) {
727
- const m = expectDefined3(messages[i]);
717
+ const m = expectDefined(messages[i]);
728
718
  const cost = typeof m.content === "string" ? Math.ceil(m.content.length / 4) : m.content.reduce(
729
719
  (acc, b) => acc + (b.type === "text" ? Math.ceil(b.text.length / 4) : Math.ceil(JSON.stringify(b).length / 4)),
730
720
  0
@@ -1457,12 +1447,6 @@ function createToolOutputSerializer(opts = {}) {
1457
1447
  }
1458
1448
 
1459
1449
  // src/execution/tool-executor.ts
1460
- function expectDefined4(value) {
1461
- if (value === null || value === void 0) {
1462
- throw new Error("Expected value to be defined");
1463
- }
1464
- return value;
1465
- }
1466
1450
  var ToolExecutor = class {
1467
1451
  constructor(registry, opts) {
1468
1452
  this.registry = registry;
@@ -1869,7 +1853,7 @@ function hasMalformedArguments(input) {
1869
1853
  function extractMalformedRaw(input) {
1870
1854
  if (!hasMalformedArguments(input)) return void 0;
1871
1855
  const obj = input;
1872
- const value = obj[expectDefined4(Object.keys(obj)[0])];
1856
+ const value = obj[expectDefined(Object.keys(obj)[0])];
1873
1857
  if (value === void 0 || value === null) return void 0;
1874
1858
  if (typeof value === "string") return value;
1875
1859
  try {
@@ -2189,12 +2173,225 @@ function appendJournal(goal, entry) {
2189
2173
  journal: trimmed
2190
2174
  };
2191
2175
  }
2176
+ function parseProgressFromText(text) {
2177
+ const re = /\[progress:\s*(\d{1,3})%\]\s*(?:[—\-]\s*(.+))?/i;
2178
+ const m = text.match(re);
2179
+ if (!m) return null;
2180
+ const progress = Math.min(100, Math.max(0, Number.parseInt(m[1], 10)));
2181
+ const note = m[2]?.trim() || void 0;
2182
+ return { progress, note };
2183
+ }
2184
+ function recordProgress(goal, progress, note) {
2185
+ const clamped = Math.min(100, Math.max(0, progress));
2186
+ const history = [...goal.progressHistory ?? [], { at: (/* @__PURE__ */ new Date()).toISOString(), progress: clamped, note }];
2187
+ const trimmed = history.length > 200 ? history.slice(-200) : history;
2188
+ return {
2189
+ ...goal,
2190
+ progress: clamped,
2191
+ progressNote: note ?? `${clamped}% complete`,
2192
+ progressHistory: trimmed,
2193
+ progressTrend: computeTrend(trimmed)
2194
+ };
2195
+ }
2196
+ function computeTrend(history) {
2197
+ if (history.length < 3) return void 0;
2198
+ const recent = history.slice(-5);
2199
+ const deltas = [];
2200
+ for (let i = 1; i < recent.length; i++) {
2201
+ deltas.push(recent[i].progress - recent[i - 1].progress);
2202
+ }
2203
+ if (deltas.length < 2) return void 0;
2204
+ const avgDelta = deltas.reduce((a, b) => a + b, 0) / deltas.length;
2205
+ if (avgDelta > 2) return "accelerating";
2206
+ if (avgDelta < -1) return "stalling";
2207
+ return "steady";
2208
+ }
2192
2209
 
2193
2210
  // src/utils/sleep.ts
2194
2211
  function sleep(ms) {
2195
2212
  return new Promise((resolve2) => setTimeout(resolve2, ms));
2196
2213
  }
2197
2214
 
2215
+ // src/execution/autonomy-brain.ts
2216
+ var RISK_LEVELS = {
2217
+ low: 0,
2218
+ medium: 1,
2219
+ high: 2,
2220
+ critical: 3
2221
+ };
2222
+ function createAutonomyBrain(opts) {
2223
+ const maxRisk = opts.maxAutoRisk ?? "high";
2224
+ const maxRiskLevel = RISK_LEVELS[maxRisk] ?? 2;
2225
+ const timeoutMs = opts.decisionTimeoutMs ?? 15e3;
2226
+ return {
2227
+ async decide(request) {
2228
+ const requestLevel = RISK_LEVELS[request.risk] ?? 2;
2229
+ if (requestLevel > maxRiskLevel) {
2230
+ const reason = `Auto-denied: risk "${request.risk}" exceeds max "${maxRisk}"`;
2231
+ const decision = { type: "deny", reason };
2232
+ opts.onDecision?.(
2233
+ `\u{1F9E0} Brain: DENIED \u2014 ${request.question.slice(0, 80)} (risk: ${request.risk} > ${maxRisk})`,
2234
+ decision,
2235
+ request
2236
+ );
2237
+ return decision;
2238
+ }
2239
+ const heuristic = quickDecide(request);
2240
+ if (heuristic) {
2241
+ opts.onDecision?.(formatDecisionSummary(heuristic, request), heuristic, request);
2242
+ return heuristic;
2243
+ }
2244
+ const llmDecision = await llmDecide(request, opts.provider, opts.model, timeoutMs);
2245
+ opts.onDecision?.(formatDecisionSummary(llmDecision, request), llmDecision, request);
2246
+ return llmDecision;
2247
+ }
2248
+ };
2249
+ }
2250
+ function formatDecisionSummary(decision, request) {
2251
+ const question = request.question.length > 80 ? request.question.slice(0, 77) + "\u2026" : request.question;
2252
+ if (decision.type === "deny") {
2253
+ return `\u{1F9E0} Brain: DENIED \u2014 "${question}" \u2192 ${decision.reason}`;
2254
+ }
2255
+ if (decision.type === "answer") {
2256
+ const action = decision.optionId ? `chose [${decision.optionId}]` : decision.text.length > 60 ? decision.text.slice(0, 57) + "\u2026" : decision.text;
2257
+ return `\u{1F9E0} Brain: DECIDED \u2014 "${question}" \u2192 ${action}`;
2258
+ }
2259
+ return `\u{1F9E0} Brain: ASKED HUMAN \u2014 "${question}"`;
2260
+ }
2261
+ function quickDecide(request) {
2262
+ const q = request.question.toLowerCase();
2263
+ const ctx = request.context?.toLowerCase() ?? "";
2264
+ if (q.includes("deadlock") && ctx.includes("failed")) {
2265
+ return {
2266
+ type: "answer",
2267
+ text: "Skip deadlocked tasks and continue with remaining work. Failed tasks will be reported in the final summary.",
2268
+ rationale: "Heuristic: deadlocked tasks blocked by failed dependencies \u2014 skipping unblocks remaining work."
2269
+ };
2270
+ }
2271
+ if ((q.includes("failed") || q.includes("retry")) && (ctx.includes("3") || ctx.includes("exhausted"))) {
2272
+ return {
2273
+ type: "answer",
2274
+ text: "Mark as failed and move on. Note the failure for the final report.",
2275
+ rationale: "Heuristic: retries exhausted \u2014 continuing would waste resources."
2276
+ };
2277
+ }
2278
+ if (q.includes("goal complete") || q.includes("mission complete")) {
2279
+ return null;
2280
+ }
2281
+ if (q.includes("continue") || q.includes("proceed")) {
2282
+ return {
2283
+ type: "answer",
2284
+ text: "Continue execution. Do not stop.",
2285
+ rationale: "Heuristic: autonomy mode \u2014 continue until all work is complete."
2286
+ };
2287
+ }
2288
+ return null;
2289
+ }
2290
+ async function llmDecide(request, provider, model, timeoutMs) {
2291
+ const optionsText = request.options?.length ? "\nOptions:\n" + request.options.map(
2292
+ (o) => ` [${o.id}] ${o.label}${o.consequence ? ` \u2014 ${o.consequence}` : ""}${o.recommended ? " \u2605 recommended" : ""}`
2293
+ ).join("\n") : "";
2294
+ const systemPrompt = [
2295
+ "IDENTITY:",
2296
+ "You are the Autonomy Brain \u2014 a dedicated decision engine inside an",
2297
+ "autonomous AI coding agent called WrongStack. Your SOLE purpose is to",
2298
+ "evaluate situations where the autonomous workflow is blocked, stuck, or",
2299
+ "uncertain, and decide the best course of action to keep the system",
2300
+ "running and making progress toward its goal.",
2301
+ "",
2302
+ "WHAT YOU DO:",
2303
+ "- You receive a question + context from an autonomy subsystem (goal",
2304
+ " engine, phase orchestrator, task decomposer).",
2305
+ "- You evaluate whether the system should continue, pivot, retry, skip,",
2306
+ " or stop.",
2307
+ "- You output exactly ONE decision. No preamble, no markdown, no",
2308
+ " elaboration beyond what is needed to justify the decision.",
2309
+ "",
2310
+ "HOW YOU DECIDE:",
2311
+ '1. PREFER CONTINUATION. The default answer is always "continue" unless',
2312
+ " there is clear evidence that stopping is safer or more productive.",
2313
+ "2. BE SPECIFIC. If options are provided, pick one by its [id]. If not,",
2314
+ " describe the exact action in 1-2 sentences.",
2315
+ "3. VERIFY COMPLETION. If the question is about whether the goal is done,",
2316
+ " check deliverables and progress before saying yes. A progress bar at",
2317
+ " 80% with open deliverables means NOT done.",
2318
+ "4. AVOID WASTE. If a task has failed 3+ times with the same approach,",
2319
+ " recommend a different approach or skipping it \u2014 do not recommend",
2320
+ " retrying the same thing.",
2321
+ "5. CONSIDER COST. If the question mentions spent budget or token counts,",
2322
+ " factor that into your decision. A goal that has already spent $50",
2323
+ " with 90% progress is worth finishing; one at 15% with $100 spent",
2324
+ " may need re-evaluation.",
2325
+ "",
2326
+ "OUTPUT FORMAT:",
2327
+ "- With options: output the option [id] and a 1-sentence justification.",
2328
+ ' Example: "[resolve] \u2014 conflict is in test files only, safe to auto-resolve."',
2329
+ "- Without options: output the decision as a 1-2 sentence action.",
2330
+ ' Example: "Continue execution. Progress is steady at 60% with 3/5',
2331
+ ' deliverables done. No reason to stop."',
2332
+ "",
2333
+ "CRITICAL RULE:",
2334
+ "You are NOT the main agent. Do not suggest code changes, tool calls,",
2335
+ "or implementation details. Your output is a DECISION, not a plan."
2336
+ ].join("\n");
2337
+ const userMessage = [
2338
+ `Question: ${request.question}`,
2339
+ request.context ? `
2340
+ Context:
2341
+ ${request.context}` : "",
2342
+ optionsText
2343
+ ].filter(Boolean).join("\n");
2344
+ try {
2345
+ const signal = AbortSignal.timeout(timeoutMs);
2346
+ const response = await provider.complete(
2347
+ {
2348
+ model,
2349
+ system: [{ type: "text", text: systemPrompt }],
2350
+ messages: [{ role: "user", content: userMessage || "Decide." }],
2351
+ maxTokens: 200
2352
+ },
2353
+ { signal }
2354
+ );
2355
+ const text = extractText(response).trim();
2356
+ if (request.options?.length) {
2357
+ for (const opt of request.options) {
2358
+ if (text.toLowerCase().includes(opt.id.toLowerCase())) {
2359
+ return {
2360
+ type: "answer",
2361
+ optionId: opt.id,
2362
+ text: opt.label,
2363
+ rationale: text
2364
+ };
2365
+ }
2366
+ }
2367
+ }
2368
+ return {
2369
+ type: "answer",
2370
+ text: text || (request.fallback === "continue" ? "Continue execution." : "Denied by autonomy policy."),
2371
+ rationale: text || void 0
2372
+ };
2373
+ } catch {
2374
+ if (request.fallback === "continue") {
2375
+ return {
2376
+ type: "answer",
2377
+ text: "Continue (Autonomy Brain LLM unavailable \u2014 using fallback)."
2378
+ };
2379
+ }
2380
+ return { type: "deny", reason: "Autonomy Brain LLM unavailable for decision." };
2381
+ }
2382
+ }
2383
+ function extractText(result) {
2384
+ if (!result || typeof result !== "object") return "";
2385
+ const r = result;
2386
+ if (Array.isArray(r.content)) {
2387
+ return r.content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("");
2388
+ }
2389
+ if (Array.isArray(r.choices)) {
2390
+ return r.choices[0]?.message?.content ?? "";
2391
+ }
2392
+ return typeof r.text === "string" ? r.text : "";
2393
+ }
2394
+
2198
2395
  // src/execution/eternal-autonomy.ts
2199
2396
  var execFileP = promisify(execFile);
2200
2397
  var BRAINSTORM_DONE = /* @__PURE__ */ Symbol("brainstorm-done");
@@ -2264,7 +2461,7 @@ var EternalAutonomyEngine = class {
2264
2461
  this.consecutiveFailures = 0;
2265
2462
  }
2266
2463
  if (this.stopRequested) break;
2267
- await sleep(this.opts.cycleGapMs ?? 1e3);
2464
+ await sleep(this.opts.cycleGapMs ?? 0);
2268
2465
  }
2269
2466
  } finally {
2270
2467
  this.state = "stopped";
@@ -2434,6 +2631,18 @@ var EternalAutonomyEngine = class {
2434
2631
  this.stopRequested = true;
2435
2632
  return true;
2436
2633
  }
2634
+ const parsed = parseProgressFromText(finalText);
2635
+ if (parsed) {
2636
+ await this.updateProgress(parsed.progress, parsed.note);
2637
+ if (parsed.progress >= 100) {
2638
+ await this.markGoalCompleted(
2639
+ { source: action.source, task: action.task, directive: action.directive },
2640
+ `progress reached 100%: ${parsed.note ?? "goal complete"}`
2641
+ );
2642
+ this.stopRequested = true;
2643
+ return true;
2644
+ }
2645
+ }
2437
2646
  this.iterationsSinceCompact++;
2438
2647
  await this.maybeCompact().catch((err) => {
2439
2648
  this.opts.onError?.(
@@ -2520,11 +2729,16 @@ var EternalAutonomyEngine = class {
2520
2729
  this.consecutiveBrainstormDone++;
2521
2730
  const threshold = this.opts.brainstormDoneStopThreshold ?? 3;
2522
2731
  if (this.consecutiveBrainstormDone >= threshold) {
2523
- await this.markGoalCompleted(
2524
- { source: "brainstorm", task: "no further work", directive: "" },
2525
- `brainstorm returned DONE ${this.consecutiveBrainstormDone}x in a row`
2526
- );
2527
- this.stopRequested = true;
2732
+ const shouldStop = await this.consultBrainForDone(goal);
2733
+ if (shouldStop) {
2734
+ await this.markGoalCompleted(
2735
+ { source: "brainstorm", task: "no further work", directive: "" },
2736
+ `brainstorm returned DONE ${this.consecutiveBrainstormDone}x in a row${this.opts.brain ? ", brain confirmed" : ""}`
2737
+ );
2738
+ this.stopRequested = true;
2739
+ } else {
2740
+ this.consecutiveBrainstormDone = 0;
2741
+ }
2528
2742
  }
2529
2743
  return null;
2530
2744
  }
@@ -2758,6 +2972,69 @@ ${recentJournal}` : "No prior iterations.",
2758
2972
  async appendFailure(task, note) {
2759
2973
  await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
2760
2974
  }
2975
+ /**
2976
+ * Consult the brain on whether the goal is truly complete.
2977
+ * Without a brain, always returns true (use heuristic: DONE threshold met = done).
2978
+ */
2979
+ async consultBrainForDone(goal) {
2980
+ if (!this.opts.brain) return true;
2981
+ const deliverablesStatus = goal.deliverables?.length ? `
2982
+ Deliverables: ${goal.deliverables.length} total, progress ${goal.progress ?? "unknown"}%` : "";
2983
+ const recentJournal = goal.journal.slice(-5).map(
2984
+ (e) => ` #${e.iteration} [${e.status}] ${e.task}`
2985
+ ).join("\n");
2986
+ try {
2987
+ const decision = await this.opts.brain.decide({
2988
+ id: `goal-done-${goal.iterations}`,
2989
+ source: "system",
2990
+ question: `Brainstorm returned DONE ${this.consecutiveBrainstormDone}x. Is the goal truly complete?`,
2991
+ context: [
2992
+ `Goal: ${goal.goal}`,
2993
+ `Iterations: ${goal.iterations}`,
2994
+ `Progress: ${goal.progress ?? "unknown"}%`,
2995
+ deliverablesStatus,
2996
+ recentJournal ? `
2997
+ Recent work:
2998
+ ${recentJournal}` : ""
2999
+ ].join("\n"),
3000
+ risk: "high",
3001
+ fallback: "continue"
3002
+ });
3003
+ const summary = formatDecisionSummary(decision, {
3004
+ id: `goal-done-${goal.iterations}`,
3005
+ source: "system",
3006
+ question: "Is the goal complete?",
3007
+ risk: "high",
3008
+ fallback: "continue"
3009
+ });
3010
+ const rationale = "rationale" in decision ? decision.rationale : void 0;
3011
+ await this.appendIterationEntry({
3012
+ source: "brainstorm",
3013
+ task: summary,
3014
+ status: "success",
3015
+ note: rationale
3016
+ });
3017
+ if (decision.type === "deny") {
3018
+ return false;
3019
+ }
3020
+ if (decision.type === "answer") {
3021
+ const text = decision.text.toLowerCase();
3022
+ return text.includes("complete") || text.includes("done") || text.includes("yes");
3023
+ }
3024
+ return true;
3025
+ } catch {
3026
+ return true;
3027
+ }
3028
+ }
3029
+ /**
3030
+ * Persist a progress update from the agent's [PROGRESS: N%] output.
3031
+ */
3032
+ async updateProgress(progress, note) {
3033
+ const current = await loadGoal(this.goalPath);
3034
+ if (!current) return;
3035
+ const updated = recordProgress(current, progress, note);
3036
+ await saveGoal(this.goalPath, updated);
3037
+ }
2761
3038
  async persistEngineState(state) {
2762
3039
  const current = await loadGoal(this.goalPath);
2763
3040
  if (!current) return;
@@ -3123,15 +3400,7 @@ function makeAgentSubagentRunner(opts) {
3123
3400
  let budgetError = null;
3124
3401
  const onBudgetError = (err) => {
3125
3402
  if (err instanceof BudgetThresholdSignal) {
3126
- err.decision.then((decision) => {
3127
- if (decision === "stop") {
3128
- budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
3129
- aborter.abort();
3130
- }
3131
- }).catch(() => {
3132
- budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
3133
- aborter.abort();
3134
- });
3403
+ budgetError = err;
3135
3404
  return;
3136
3405
  }
3137
3406
  aborter.abort();
@@ -3239,15 +3508,23 @@ function makeAgentSubagentRunner(opts) {
3239
3508
  }
3240
3509
  if (budgetError) {
3241
3510
  if ("decision" in budgetError) {
3242
- const decision = await budgetError.decision;
3243
- if (decision === "stop") {
3511
+ try {
3512
+ const decision = await budgetError.decision;
3513
+ if (decision === "stop") {
3514
+ budgetError = new BudgetExceededError(
3515
+ budgetError.kind,
3516
+ budgetError.limit,
3517
+ budgetError.used
3518
+ );
3519
+ } else {
3520
+ budgetError = null;
3521
+ }
3522
+ } catch {
3244
3523
  budgetError = new BudgetExceededError(
3245
3524
  budgetError.kind,
3246
3525
  budgetError.limit,
3247
3526
  budgetError.used
3248
3527
  );
3249
- } else {
3250
- budgetError = null;
3251
3528
  }
3252
3529
  }
3253
3530
  if (budgetError) throw budgetError;
@@ -5411,6 +5688,109 @@ Working rules:
5411
5688
  "cloud cost"
5412
5689
  ]
5413
5690
  }
5691
+ },
5692
+ {
5693
+ config: {
5694
+ id: "tech-stack",
5695
+ name: "Tech Stack Validator",
5696
+ role: "tech-stack",
5697
+ tools: ["search", "fetch", "read", "grep", "glob", "outdated", "audit", "json"],
5698
+ prompt: `You are the Tech Stack Validator \u2014 a single-shot validation agent that fires
5699
+ before any package, library, or framework choice is committed.
5700
+
5701
+ Your ONLY job: verify that a technology choice is current, real, and not obsolete.
5702
+ You are the "this isn't code, this is 10-year-old technology" agent. Intervene
5703
+ hard when the LLM hallucinates a version number or suggests dead tech.
5704
+
5705
+ ## Critical rules
5706
+
5707
+ 1. **Verify existence.** Search npm registry (fetch https://registry.npmjs.org/<pkg>/latest)
5708
+ or web search. A package that doesn't exist = hallucination.
5709
+
5710
+ 2. **Check latest version.** Never trust any version number from the model. Always
5711
+ fetch the actual latest stable version from npm or the project's release page.
5712
+
5713
+ 3. **Reject dead packages.** No release in >2 years + unresolved critical issues =
5714
+ dead. Suggest a maintained replacement.
5715
+
5716
+ 4. **Reject prehistoric tech.** Any package/pattern superseded \u22655 years ago is
5717
+ REJECTED. Key blocklist:
5718
+ - axios / node-fetch / got / request \u2192 native fetch (Node 18+)
5719
+ - moment \u2192 date-fns / luxon / Temporal
5720
+ - jQuery (new projects) \u2192 vanilla DOM / React
5721
+ - Gulp / Grunt \u2192 tsup / esbuild / vite
5722
+ - CoffeeScript / Flow \u2192 TypeScript
5723
+ - Bluebird \u2192 native Promises
5724
+ - crypto-js \u2192 node:crypto / Web Crypto
5725
+ - Bower \u2192 npm/pnpm
5726
+ - underscore \u2192 lodash or native ES2020+
5727
+
5728
+ 5. **The intervention phrase.** When rejecting on age grounds, you MUST output
5729
+ exactly: "This isn't code, this is X-year-old technology." where X =
5730
+ current year \u2212 the year the technology was made obsolete. Follow with
5731
+ what replaced it and a one-step migration path.
5732
+
5733
+ 6. **Prefer built-in over third-party.** Check Node 22+ native APIs first:
5734
+ node:test, node:sqlite, fetch, WebSocket, Web Crypto \u2014 all built-in.
5735
+
5736
+ ## Workflow (single-shot \u2014 do NOT loop)
5737
+
5738
+ 1. Receive the proposed package + version
5739
+ 2. Search npm registry or web for the latest version
5740
+ 3. Check age, maintenance status, deprecation
5741
+ 4. Output verdict: APPROVED (with exact version) or REJECTED (with replacement)
5742
+
5743
+ ## Output format
5744
+
5745
+ ### Tech Stack Validation \u2014 <package>
5746
+
5747
+ **Status**: APPROVED | REJECTED
5748
+
5749
+ **Package**: <name>@<version>
5750
+ **Source**: <URL you checked \u2014 npm registry, GitHub, web search>
5751
+ **Age**: <first release> \u2014 <last release date>
5752
+ **Verdict**: 1\u20132 sentence explanation.
5753
+
5754
+ When REJECTED on age:
5755
+ **"This isn't code, this is X-year-old technology."**
5756
+ **Replaced by**: <modern alternative>
5757
+ **Migration**: <one concrete step>
5758
+
5759
+ When APPROVED:
5760
+ **Install**: pnpm add <name>@^<major>.<minor>.0`
5761
+ },
5762
+ budget: {
5763
+ timeoutMs: 6e4,
5764
+ maxIterations: 5,
5765
+ maxToolCalls: 20,
5766
+ maxTokens: 4e4,
5767
+ maxCostUsd: 0.1
5768
+ },
5769
+ capability: {
5770
+ phase: "meta",
5771
+ summary: "Single-shot tech stack validator: checks npm for latest versions, rejects dead/obsolete packages, enforces modern alternatives.",
5772
+ keywords: [
5773
+ "tech stack",
5774
+ "version",
5775
+ "package",
5776
+ "library",
5777
+ "framework",
5778
+ "dependency",
5779
+ "install",
5780
+ "upgrade",
5781
+ "latest",
5782
+ "npm",
5783
+ "pnpm add",
5784
+ "outdated",
5785
+ "obsolete",
5786
+ "deprecated",
5787
+ "what version",
5788
+ "which package",
5789
+ "check version",
5790
+ "verify version",
5791
+ "is this current"
5792
+ ]
5793
+ }
5414
5794
  }
5415
5795
  ];
5416
5796
 
@@ -6557,12 +6937,6 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
6557
6937
  };
6558
6938
 
6559
6939
  // src/execution/parallel-eternal-engine.ts
6560
- function expectDefined5(value) {
6561
- if (value === null || value === void 0) {
6562
- throw new Error("Expected value to be defined");
6563
- }
6564
- return value;
6565
- }
6566
6940
  var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
6567
6941
  var ParallelEternalEngine = class {
6568
6942
  constructor(opts) {
@@ -6718,7 +7092,7 @@ var ParallelEternalEngine = class {
6718
7092
  // Fan-out
6719
7093
  // -------------------------------------------------------------------------
6720
7094
  async fanOut(goal, tasks) {
6721
- const coordinator = expectDefined5(this.coordinator);
7095
+ const coordinator = expectDefined(this.coordinator);
6722
7096
  const slotCount = Math.min(this.slots, tasks.length);
6723
7097
  const routes = this.dispatchEnabled ? await Promise.all(
6724
7098
  tasks.slice(0, slotCount).map(
@@ -6750,7 +7124,7 @@ ${recentJournal}` : "No prior iterations.",
6750
7124
  const routeInfo = [];
6751
7125
  const spawnPromises = [];
6752
7126
  for (let i = 0; i < slotCount; i++) {
6753
- const task = expectDefined5(tasks[i]);
7127
+ const task = expectDefined(tasks[i]);
6754
7128
  const route = routes[i] ?? null;
6755
7129
  const subagentId = `parallel-${this.iterations}-${i}`;
6756
7130
  const taskId = randomUUID();
@@ -7023,7 +7397,17 @@ ${journalTail.join("\n")}` : "Recent journal: (none \u2014 this is the first ite
7023
7397
  }
7024
7398
 
7025
7399
  // src/execution/goal-preamble.ts
7026
- function buildGoalPreamble(goal) {
7400
+ function buildGoalPreamble(goal, deliverables) {
7401
+ const deliverableBlock = deliverables && deliverables.length > 0 ? [
7402
+ "",
7403
+ "CONCRETE DELIVERABLES (check these off as you go):",
7404
+ ...deliverables.map((d, i) => ` ${i + 1}. ${d}`),
7405
+ "",
7406
+ "After EACH iteration, estimate your completion percentage (0\u2013100)",
7407
+ "against this deliverable list. Output it as:",
7408
+ " [PROGRESS: N%] \u2014 <1-sentence status>",
7409
+ "The eternal engine reads this to update the progress bar."
7410
+ ].join("\n") : "";
7027
7411
  return [
7028
7412
  "[GOAL \u2014 LOCKED IN. You will work on this until it is verifiably done.",
7029
7413
  "The user granted you full autonomy. Read these constraints once, then act.",
@@ -7032,6 +7416,7 @@ function buildGoalPreamble(goal) {
7032
7416
  "---",
7033
7417
  goal,
7034
7418
  "---",
7419
+ deliverableBlock,
7035
7420
  "",
7036
7421
  "AUTHORITY YOU HAVE:",
7037
7422
  "- Spawn as many subagents as the work needs (delegate / spawn_subagent).",
@@ -7053,6 +7438,7 @@ function buildGoalPreamble(goal) {
7053
7438
  "- You can tell the user HOW to verify it themselves in 10 seconds.",
7054
7439
  '- You have NOT hedged. None of: "looks like it should work", "I',
7055
7440
  ' believe this fixes it", "the changes appear correct".',
7441
+ `- Progress bar shows 100%. All deliverables checked off.`,
7056
7442
  "",
7057
7443
  "WHAT IS NOT DONE \u2014 never report any of these as completion:",
7058
7444
  "- An error message you didn't recover from.",
@@ -7067,6 +7453,13 @@ function buildGoalPreamble(goal) {
7067
7453
  " respond to with a fresh attempt (different role, different model,",
7068
7454
  " tighter prompt).",
7069
7455
  "",
7456
+ "PROGRESS REPORTING \u2014 MANDATORY every iteration:",
7457
+ "- End every response with exactly: [PROGRESS: N%] \u2014 <status note>",
7458
+ " where N is your honest estimate (0-100) of how much of the goal",
7459
+ " is done. The engine tracks this and shows a live progress bar.",
7460
+ "- Example: [PROGRESS: 45%] \u2014 Auth module refactored, 3/5 tests pass,",
7461
+ " remaining: test coverage + docs update.",
7462
+ "",
7070
7463
  "PERSISTENCE PROTOCOL:",
7071
7464
  "- If blocked, try at least 3 different angles before reporting the",
7072
7465
  " problem to the user. Different tool inputs, different subagent",
@@ -7342,6 +7735,6 @@ function parseDescription(raw) {
7342
7735
  return { trigger, scope };
7343
7736
  }
7344
7737
 
7345
- export { AutoCompactionMiddleware, AutonomousRunner, DefaultErrorHandler, DefaultRetryPolicy, DefaultSkillLoader, DoneConditionChecker, EternalAutonomyEngine, HybridCompactor, IntelligentCompactor, ParallelEternalEngine, SelectiveCompactor, ToolExecutor, buildGoalPreamble, makeAutonomyPromptContributor };
7738
+ export { AutoCompactionMiddleware, AutonomousRunner, DefaultErrorHandler, DefaultRetryPolicy, DefaultSkillLoader, DoneConditionChecker, EternalAutonomyEngine, HybridCompactor, IntelligentCompactor, ParallelEternalEngine, SelectiveCompactor, ToolExecutor, buildGoalPreamble, createAutonomyBrain, formatDecisionSummary, makeAutonomyPromptContributor };
7346
7739
  //# sourceMappingURL=index.js.map
7347
7740
  //# sourceMappingURL=index.js.map