@wrongstack/core 0.300.0 → 0.301.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 (144) hide show
  1. package/dist/chronicle/index.d.ts +2 -0
  2. package/dist/chronicle/index.js +297 -10
  3. package/dist/chronicle/project-server.js +253 -15
  4. package/dist/chronicle/sqlite-compaction.d.ts +20 -0
  5. package/dist/chronicle/sqlite-journal.d.ts +57 -0
  6. package/dist/coordination/agents/capability-manifest.d.ts +7 -0
  7. package/dist/coordination/agents/index.d.ts +3 -2
  8. package/dist/coordination/agents/index.js +818 -570
  9. package/dist/coordination/agents/project-agent-identity.d.ts +6 -6
  10. package/dist/coordination/agents/role-skills.d.ts +1 -1
  11. package/dist/coordination/agents/types.d.ts +6 -0
  12. package/dist/coordination/director.d.ts +2 -2
  13. package/dist/coordination/index.js +6778 -6438
  14. package/dist/coordination/mail-tools.d.ts +3 -3
  15. package/dist/coordination/mailbox-project-server.js +3 -4
  16. package/dist/coordination/mailbox-types.d.ts +6 -0
  17. package/dist/core/agent-response.d.ts +5 -1
  18. package/dist/core/agent-tools.d.ts +3 -0
  19. package/dist/core/context.d.ts +20 -0
  20. package/dist/core/index.d.ts +8 -7
  21. package/dist/core/index.js +893 -284
  22. package/dist/core/instruction-template.d.ts +6 -3
  23. package/dist/core/next-steps-slot.d.ts +88 -0
  24. package/dist/core/system-prompt-builder.d.ts +5 -3
  25. package/dist/core/system-prompt-memory-skills.d.ts +1 -0
  26. package/dist/core/system-prompt-skill-bodies.d.ts +3 -3
  27. package/dist/defaults/index.js +9796 -8897
  28. package/dist/design/index.js +11 -3
  29. package/dist/execution/auto-compaction-middleware.d.ts +17 -0
  30. package/dist/execution/autonomy-brain.d.ts +8 -2
  31. package/dist/execution/brain-runtime.d.ts +3 -1
  32. package/dist/execution/compaction-core.d.ts +41 -2
  33. package/dist/execution/council-brain.d.ts +37 -2
  34. package/dist/execution/council-orchestrator.d.ts +2 -2
  35. package/dist/execution/index.d.ts +11 -10
  36. package/dist/execution/index.js +5178 -4077
  37. package/dist/execution/tool-executor.d.ts +4 -1
  38. package/dist/execution/topic-shift-advisor.d.ts +53 -0
  39. package/dist/extension/index.js +8 -2
  40. package/dist/extension/registry.d.ts +8 -1
  41. package/dist/goal/index.js +11 -3
  42. package/dist/hooks/index.js +42 -9
  43. package/dist/hooks/runner.d.ts +12 -1
  44. package/dist/hq/index.js +41 -17
  45. package/dist/hq/publisher.d.ts +21 -3
  46. package/dist/index.d.ts +21 -20
  47. package/dist/index.js +10887 -8053
  48. package/dist/infrastructure/index.js +108 -21
  49. package/dist/kernel/events/session-events.d.ts +13 -0
  50. package/dist/kernel/events/tool-events.d.ts +3 -3
  51. package/dist/models/index.d.ts +1 -0
  52. package/dist/models/index.js +17 -0
  53. package/dist/models/provider-credentials.d.ts +54 -0
  54. package/dist/plugin/index.d.ts +1 -0
  55. package/dist/plugin/index.js +2024 -502
  56. package/dist/plugins/auto-review-plugin.d.ts +3 -0
  57. package/dist/plugins/cloud-config-sync-plugin.d.ts +22 -0
  58. package/dist/plugins/review-claim-registry.d.ts +23 -8
  59. package/dist/plugins/review-types.d.ts +2 -0
  60. package/dist/registry/index.js +109 -74
  61. package/dist/registry/tool-registry.d.ts +15 -0
  62. package/dist/security/capabilities.d.ts +2 -0
  63. package/dist/security/index.d.ts +1 -1
  64. package/dist/security/index.js +9 -2
  65. package/dist/security/readonly-permission-policy.d.ts +20 -0
  66. package/dist/session-registry-atomic-file.d.ts +32 -5
  67. package/dist/skills/frontmatter.d.ts +6 -0
  68. package/dist/skills/index.js +22 -5
  69. package/dist/storage/cloud-config-sync/sanitize.d.ts +54 -0
  70. package/dist/storage/cloud-config-sync.d.ts +87 -0
  71. package/dist/storage/index.d.ts +1 -0
  72. package/dist/storage/index.js +854 -66
  73. package/dist/tools/index.d.ts +1 -1
  74. package/dist/tools/index.js +511 -234
  75. package/dist/tools/one-shot-llm-tool.d.ts +1 -0
  76. package/dist/tools/plugin-manager.d.ts +27 -0
  77. package/dist/types/config/mcp-features.d.ts +18 -11
  78. package/dist/types/config/root.d.ts +8 -1
  79. package/dist/types/config/runtime.d.ts +20 -6
  80. package/dist/types/config/skills-fleet-brain.d.ts +12 -4
  81. package/dist/types/config/tools.d.ts +16 -0
  82. package/dist/types/context-window.d.ts +1 -0
  83. package/dist/types/hooks.d.ts +14 -0
  84. package/dist/types/index.d.ts +2 -2
  85. package/dist/types/index.js +1 -0
  86. package/dist/types/multi-agent.d.ts +2 -0
  87. package/dist/types/one-shot-llm.d.ts +16 -0
  88. package/dist/types/provider.d.ts +16 -0
  89. package/dist/types/runtime-capability-manifest.d.ts +168 -0
  90. package/dist/types/skill.d.ts +5 -0
  91. package/dist/types/system-prompt.d.ts +3 -1
  92. package/dist/types/tool-executor.d.ts +8 -1
  93. package/dist/utils/context-breakdown.d.ts +8 -2
  94. package/dist/utils/index.d.ts +1 -1
  95. package/dist/utils/index.js +141 -31
  96. package/dist/utils/regex-guard.d.ts +16 -2
  97. package/dist/utils/sage-output-block.d.ts +7 -10
  98. package/dist/utils/wstack-paths.d.ts +11 -3
  99. package/instructions/agents/browser.md +1 -4
  100. package/instructions/agents/e2e.md +17 -15
  101. package/instructions/agents/ios.md +3 -3
  102. package/instructions/agents/search.md +1 -1
  103. package/instructions/autonomy/goal-preamble.md +4 -4
  104. package/instructions/coordination/director-preamble.md +2 -3
  105. package/instructions/coordination/subagent-baseline.md +3 -1
  106. package/instructions/leader-after-task.md +12 -0
  107. package/instructions/llm/chimera-review.md +1 -1
  108. package/instructions/modes/audit-lite.md +1 -1
  109. package/instructions/sections/tool/common-patterns.md +18 -2
  110. package/instructions/sections/tool/mailbox-compact.md +1 -1
  111. package/instructions/sections/tool/mailbox-full.md +1 -1
  112. package/instructions/system-lite.md +10 -0
  113. package/instructions/system-pro.md +23 -14
  114. package/instructions/system.md +22 -13
  115. package/package.json +3 -3
  116. package/skills/api-design/SKILL.md +3 -0
  117. package/skills/audit-log/SKILL.md +2 -0
  118. package/skills/auto-review/SKILL.md +6 -1
  119. package/skills/bug-hunter/SKILL.md +4 -1
  120. package/skills/chimera/SKILL.md +3 -0
  121. package/skills/data-governance/SKILL.md +3 -0
  122. package/skills/design-system/SKILL.md +5 -2
  123. package/skills/docker-deploy/SKILL.md +2 -0
  124. package/skills/git-flow/SKILL.md +2 -0
  125. package/skills/mailbox-bridge/SKILL.md +3 -0
  126. package/skills/mnemosyne/SKILL.md +2 -0
  127. package/skills/multi-agent/SKILL.md +4 -1
  128. package/skills/node-modern/SKILL.md +4 -1
  129. package/skills/observability/SKILL.md +3 -0
  130. package/skills/output-standards/SKILL.md +2 -0
  131. package/skills/plugin-author/SKILL.md +4 -1
  132. package/skills/prompt-engineering/SKILL.md +3 -1
  133. package/skills/react-modern/SKILL.md +6 -3
  134. package/skills/refactor-planner/SKILL.md +2 -0
  135. package/skills/research-web/SKILL.md +3 -0
  136. package/skills/sdd/SKILL.md +3 -1
  137. package/skills/security-scanner/SKILL.md +3 -0
  138. package/skills/skill-creator/SKILL.md +2 -0
  139. package/skills/tech-stack/SKILL.md +3 -0
  140. package/skills/testing/SKILL.md +4 -1
  141. package/skills/typescript-strict/SKILL.md +4 -1
  142. package/skills/wrongstack-kanban/SKILL.md +6 -3
  143. package/skills/wrongstack-mailbox/SKILL.md +4 -1
  144. package/skills/wrongstack-mailbox-mcp/SKILL.md +10 -8
@@ -305,9 +305,15 @@ var ExtensionRegistry = class {
305
305
  * Build a composed provider runner. Extensions with `wrapProviderRunner`
306
306
  * form a middleware-style chain: the innermost extension wraps the
307
307
  * default runner, each subsequent wrapper wraps the previous.
308
+ *
309
+ * Utility calls may exclude agent-loop-only middleware (notably
310
+ * `fallback-model`) while retaining request-safety wrappers such as the
311
+ * prompt firewall. One-shot orchestration owns its provider and fallback
312
+ * route, so an agent-loop fallback wrapper cannot safely run against it.
308
313
  */
309
- wrapProviderRunner(inner) {
310
- const wrappers = this.extensions.filter((e) => e.wrapProviderRunner).map((e) => ({ name: e.name, wrap: expectDefined(e.wrapProviderRunner) }));
314
+ wrapProviderRunner(inner, options) {
315
+ const excluded = new Set(options?.exclude);
316
+ const wrappers = this.extensions.filter((e) => e.wrapProviderRunner && !excluded.has(e.name)).map((e) => ({ name: e.name, wrap: expectDefined(e.wrapProviderRunner) }));
311
317
  if (wrappers.length === 0) return inner;
312
318
  let composed = inner;
313
319
  for (let i = wrappers.length - 1; i >= 0; i--) {
@@ -540,6 +546,9 @@ function canonicalProjectRoot(absRoot) {
540
546
  const commonDirFile = path.join(gitDir, "commondir");
541
547
  if (!fs.statSync(commonDirFile).isFile()) return checkoutRoot;
542
548
  const commonDir = path.resolve(gitDir, fs.readFileSync(commonDirFile, "utf8").trim());
549
+ const worktreesDir = path.dirname(gitDir);
550
+ if (path.basename(worktreesDir).toLowerCase() !== "worktrees") return checkoutRoot;
551
+ if (path.resolve(worktreesDir, "..") !== commonDir) return checkoutRoot;
543
552
  if (path.basename(commonDir).toLowerCase() !== ".git") return checkoutRoot;
544
553
  return path.dirname(commonDir);
545
554
  } catch {
@@ -1647,6 +1656,20 @@ import * as path7 from "node:path";
1647
1656
  // src/security/file-permissions.ts
1648
1657
  var SECRET_FILE_MODE = 384;
1649
1658
 
1659
+ // src/utils/pid.ts
1660
+ function isPidAlive(pid) {
1661
+ if (!Number.isInteger(pid) || pid <= 0) return false;
1662
+ if (pid === process.pid) return true;
1663
+ try {
1664
+ process.kill(pid, 0);
1665
+ return true;
1666
+ } catch (err) {
1667
+ const code = err.code;
1668
+ if (code === "EPERM") return true;
1669
+ return false;
1670
+ }
1671
+ }
1672
+
1650
1673
  // src/hq/auth-store.ts
1651
1674
  function defaultHqDataDir() {
1652
1675
  return path7.join(wstackGlobalRoot(), "hq");
@@ -1662,22 +1685,13 @@ function hqAuthFilePath(dataDir) {
1662
1685
  function hqRuntimeFilePath(dataDir) {
1663
1686
  return path7.join(dataDir, "runtime.json");
1664
1687
  }
1665
- function isProcessAlive(pid) {
1666
- if (!Number.isInteger(pid) || pid <= 0) return false;
1667
- try {
1668
- process.kill(pid, 0);
1669
- return true;
1670
- } catch {
1671
- return false;
1672
- }
1673
- }
1674
1688
  function readHqRuntimeFileSync(dataDir) {
1675
1689
  try {
1676
1690
  const parsed = JSON.parse(
1677
1691
  syncFs.readFileSync(hqRuntimeFilePath(dataDir), "utf8")
1678
1692
  );
1679
1693
  if (typeof parsed.url !== "string" || parsed.url.trim().length === 0) return void 0;
1680
- if (typeof parsed.pid === "number" && !isProcessAlive(parsed.pid)) return void 0;
1694
+ if (typeof parsed.pid === "number" && !isPidAlive(parsed.pid)) return void 0;
1681
1695
  return {
1682
1696
  url: parsed.url,
1683
1697
  updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : "",
@@ -2303,6 +2317,7 @@ function createMailboxEventPayload(input) {
2303
2317
  var TRANSCRIPT_EVENT_TYPES = /* @__PURE__ */ new Set(["session.transcript", "agent.message"]);
2304
2318
  var OPEN_STATE = 1;
2305
2319
  var CONNECT_WARN_AFTER_FAILURES = 5;
2320
+ var DEFAULT_CONNECT_WARN_COOLDOWN_MS = 5 * 6e4;
2306
2321
  var DEFAULT_RECONNECT_BASE_MS = 1e3;
2307
2322
  var DEFAULT_RECONNECT_MAX_MS = 3e4;
2308
2323
  var DEFAULT_DISCOVERY_POLL_MS = 5e3;
@@ -2314,6 +2329,7 @@ var DEFAULT_MAX_QUEUED_BYTES = Math.min(
2314
2329
  var DEFAULT_COMMAND_POLL_INTERVAL_MS = 500;
2315
2330
  var DEFAULT_COMMAND_POLL_LIMIT = 25;
2316
2331
  var DEFAULT_HEARTBEAT_INTERVAL_MS2 = 25e3;
2332
+ var lastConnectWarningAtMs = 0;
2317
2333
  function queuedFrameCoalesceKey(frame) {
2318
2334
  if (frame.type !== "client.event" || !frame.event.type.endsWith(".snapshot")) {
2319
2335
  return void 0;
@@ -2365,6 +2381,8 @@ var HqPublisher = class {
2365
2381
  this.reconnect = options.reconnect ?? true;
2366
2382
  this.reconnectBaseMs = options.reconnectBaseMs ?? DEFAULT_RECONNECT_BASE_MS;
2367
2383
  this.reconnectMaxMs = options.reconnectMaxMs ?? DEFAULT_RECONNECT_MAX_MS;
2384
+ this.connectWarnAfterFailures = options.connectWarnAfterFailures ?? CONNECT_WARN_AFTER_FAILURES;
2385
+ this.connectWarnCooldownMs = options.connectWarnCooldownMs ?? DEFAULT_CONNECT_WARN_COOLDOWN_MS;
2368
2386
  this.maxQueuedMessages = options.maxQueuedMessages ?? DEFAULT_MAX_QUEUED_MESSAGES;
2369
2387
  this.maxQueuedBytes = options.maxQueuedBytes ?? DEFAULT_MAX_QUEUED_BYTES;
2370
2388
  this.resolvedRedactionPolicy = resolveHqRedactionPolicy(options.redactionPolicy);
@@ -2378,6 +2396,8 @@ var HqPublisher = class {
2378
2396
  reconnect;
2379
2397
  reconnectBaseMs;
2380
2398
  reconnectMaxMs;
2399
+ connectWarnAfterFailures;
2400
+ connectWarnCooldownMs;
2381
2401
  maxQueuedMessages;
2382
2402
  maxQueuedBytes;
2383
2403
  resolvedRedactionPolicy;
@@ -2789,9 +2809,8 @@ var HqPublisher = class {
2789
2809
  if (this.stopped || !this.reconnect || this.reconnectTimer !== null) return;
2790
2810
  const delay2 = Math.min(this.reconnectMaxMs, this.reconnectBaseMs * 2 ** this.reconnectAttempt);
2791
2811
  this.reconnectAttempt += 1;
2792
- if (!this.connectWarningEmitted && this.reconnectAttempt >= CONNECT_WARN_AFTER_FAILURES) {
2793
- this.connectWarningEmitted = true;
2794
- this.emitConnectWarning();
2812
+ if (!this.connectWarningEmitted && this.reconnectAttempt >= this.connectWarnAfterFailures) {
2813
+ if (this.emitConnectWarning()) this.connectWarningEmitted = true;
2795
2814
  }
2796
2815
  this.reconnectTimer = setTimeout(() => {
2797
2816
  this.reconnectTimer = null;
@@ -2800,11 +2819,16 @@ var HqPublisher = class {
2800
2819
  this.reconnectTimer.unref?.();
2801
2820
  }
2802
2821
  emitConnectWarning() {
2822
+ const nowMs = Date.now();
2823
+ if (nowMs - lastConnectWarningAtMs < this.connectWarnCooldownMs) {
2824
+ return false;
2825
+ }
2826
+ lastConnectWarningAtMs = nowMs;
2803
2827
  const attempt = this.lastAttempt;
2804
2828
  const message = `WrongStack HQ publisher: ${this.reconnectAttempt} consecutive connection failures${attempt !== null ? ` to ${attempt.url} (client token ${attempt.hadToken ? "present" : "absent"})` : ""}. Either the HQ server is unreachable or it rejected the token (401). If HQ runs in client-token mode, verify WRONGSTACK_HQ_TOKEN / auth.json. Retries continue with backoff.`;
2805
2829
  if (this.logger) {
2806
2830
  this.logger.warn(message, { event: "hq.publisher.connect_failed" });
2807
- return;
2831
+ return true;
2808
2832
  }
2809
2833
  const warn = this.options.warn ?? ((msg) => console.warn(
2810
2834
  JSON.stringify({
@@ -2818,6 +2842,7 @@ var HqPublisher = class {
2818
2842
  warn(message);
2819
2843
  } catch {
2820
2844
  }
2845
+ return true;
2821
2846
  }
2822
2847
  /**
2823
2848
  * Dormant re-check while no HQ endpoint is discoverable. Uses a FIXED
@@ -4826,6 +4851,8 @@ var Context = class _Context {
4826
4851
  allowOutsideProjectRoot;
4827
4852
  model;
4828
4853
  tools = [];
4854
+ /** Complete enabled catalog; provider token accounting continues to use `tools`. */
4855
+ catalogTools = [];
4829
4856
  meta = {};
4830
4857
  /** Agent id performing this run (e.g. 'leader', 'executor'). */
4831
4858
  agentId;
@@ -4906,6 +4933,32 @@ var Context = class _Context {
4906
4933
  * Cleared after being consumed by agent-tools.
4907
4934
  */
4908
4935
  pendingPostToolContext = void 0;
4936
+ /**
4937
+ * Bounded, provider-bound memory evidence kept outside conversation and
4938
+ * tool-result history. Owners replace their own slot instead of appending a
4939
+ * fresh message on every retrieval, so long sessions do not accumulate the
4940
+ * same SAGE hints. Request construction emits these as ephemeral system
4941
+ * suffixes, preserving the stable prompt-cache prefix.
4942
+ */
4943
+ memoryEvidence = [];
4944
+ setMemoryEvidence(source, text, maxChars = 6e3) {
4945
+ const key = source.trim();
4946
+ if (!key) return;
4947
+ const clean = text?.trim();
4948
+ const retained = this.memoryEvidence.filter((entry) => entry.source !== key);
4949
+ if (clean) {
4950
+ const boundedMax = Number.isFinite(maxChars) ? Math.max(0, Math.floor(maxChars)) : 0;
4951
+ if (boundedMax > 0) retained.push({ source: key, text: clean.slice(0, boundedMax) });
4952
+ }
4953
+ this.memoryEvidence = retained.slice(-8);
4954
+ }
4955
+ clearMemoryEvidence(source) {
4956
+ if (source === void 0) {
4957
+ this.memoryEvidence = [];
4958
+ return;
4959
+ }
4960
+ this.memoryEvidence = this.memoryEvidence.filter((entry) => entry.source !== source);
4961
+ }
4909
4962
  /**
4910
4963
  * H1: pre-computed total-request token estimate from the most recent
4911
4964
  * `estimateRequestTokens()` call in the agent loop's pre-flight step.
@@ -4941,6 +4994,7 @@ var Context = class _Context {
4941
4994
  this.allowOutsideProjectRoot = init.allowOutsideProjectRoot ?? false;
4942
4995
  this.model = init.model;
4943
4996
  this.tools = init.tools ?? [];
4997
+ this.catalogTools = init.catalogTools ?? this.tools;
4944
4998
  this.agentId = init.agentId ?? "unknown";
4945
4999
  this.agentName = init.agentName ?? "Unknown Agent";
4946
5000
  this.traceId = init.traceId;
@@ -5446,6 +5500,87 @@ function requestLimitExtension(opts) {
5446
5500
  });
5447
5501
  }
5448
5502
 
5503
+ // src/utils/todos-format.ts
5504
+ function hasOpenTodos(todos) {
5505
+ if (!Array.isArray(todos) || todos.length === 0) return false;
5506
+ return todos.some((t2) => t2.status === "pending" || t2.status === "in_progress");
5507
+ }
5508
+
5509
+ // src/core/next-steps-slot.ts
5510
+ var SLOT_KEY = "nextsteps.pending";
5511
+ var MAX_PENDING_NEXT_STEPS = 4;
5512
+ var NEXT_STEPS_TAG_RE = /<nextsteps\b[^>]*>/i;
5513
+ function writePendingNextSteps(ctx, steps) {
5514
+ const cleaned = [];
5515
+ for (const step of steps) {
5516
+ const text = typeof step?.text === "string" ? step.text.replace(/\s+/g, " ").trim() : "";
5517
+ if (!text) continue;
5518
+ cleaned.push(step.auto === true && cleaned.length === 0 ? { text, auto: true } : { text });
5519
+ if (cleaned.length >= MAX_PENDING_NEXT_STEPS) break;
5520
+ }
5521
+ if (cleaned.length === 0) {
5522
+ clearPendingNextSteps(ctx);
5523
+ return;
5524
+ }
5525
+ ctx.meta[SLOT_KEY] = cleaned;
5526
+ }
5527
+ function readPendingNextSteps(ctx) {
5528
+ const raw = ctx.meta[SLOT_KEY];
5529
+ if (!Array.isArray(raw) || raw.length === 0) return void 0;
5530
+ const steps = raw.filter(
5531
+ (s) => typeof s?.text === "string" && s.text.length > 0
5532
+ );
5533
+ return steps.length > 0 ? steps : void 0;
5534
+ }
5535
+ function clearPendingNextSteps(ctx) {
5536
+ delete ctx.meta[SLOT_KEY];
5537
+ }
5538
+ function hasNextStepsTag(text) {
5539
+ return NEXT_STEPS_TAG_RE.test(text);
5540
+ }
5541
+ function renderNextStepsBlock(steps) {
5542
+ const lines = steps.map((step, i) => {
5543
+ const auto = i === 0 && step.auto === true ? ' auto="true"' : "";
5544
+ return `${i + 1}. ${step.text}${auto}`;
5545
+ });
5546
+ return `<nextsteps>
5547
+ ${lines.join("\n")}
5548
+ </nextsteps>`;
5549
+ }
5550
+ function endsTurn(stopReason) {
5551
+ return stopReason !== "tool_use" && stopReason !== "tool_call";
5552
+ }
5553
+ function maybeAppendPendingNextSteps(ctx, res) {
5554
+ if (ctx.agentId !== "leader") return res;
5555
+ if (!endsTurn(res.stopReason)) return res;
5556
+ if (ctx.signal.aborted) return res;
5557
+ const steps = readPendingNextSteps(ctx);
5558
+ if (!steps) return res;
5559
+ if (hasOpenTodos(ctx.todos)) {
5560
+ clearPendingNextSteps(ctx);
5561
+ return res;
5562
+ }
5563
+ const lastTextIndex = res.content.findLastIndex(isTextBlock);
5564
+ if (lastTextIndex >= 0) {
5565
+ const block = res.content[lastTextIndex];
5566
+ if (block && isTextBlock(block) && hasNextStepsTag(block.text)) {
5567
+ clearPendingNextSteps(ctx);
5568
+ return res;
5569
+ }
5570
+ }
5571
+ const rendered = renderNextStepsBlock(steps);
5572
+ const content = [...res.content];
5573
+ const target = lastTextIndex >= 0 ? content[lastTextIndex] : void 0;
5574
+ if (target && isTextBlock(target)) {
5575
+ const separator = target.text.trim() ? "\n\n" : "";
5576
+ content[lastTextIndex] = { ...target, text: `${target.text}${separator}${rendered}` };
5577
+ } else {
5578
+ content.push({ type: "text", text: rendered });
5579
+ }
5580
+ clearPendingNextSteps(ctx);
5581
+ return { ...res, content };
5582
+ }
5583
+
5449
5584
  // src/core/streaming-response-builder.ts
5450
5585
  import { randomUUID as randomUUID4 } from "node:crypto";
5451
5586
 
@@ -6279,6 +6414,91 @@ function createAgentLoopHandler(a, handlers) {
6279
6414
  const getFleetPulse = attachFleetPulse(a, fleetPulseCfg);
6280
6415
  const pulseEveryN = Math.max(1, fleetPulseCfg?.everyNIterations ?? 5);
6281
6416
  const backgroundCoordination = () => a.ctx.meta["coordinationContextMode"] === "background";
6417
+ async function refreshProviderContextLimit(provider, model, opts = {}) {
6418
+ a.ctx.meta ??= {};
6419
+ const routeKey = `${provider.id}/${model}`;
6420
+ const previousRouteKey = a.ctx.meta["contextLimitRouteKey"];
6421
+ const routeChanged = previousRouteKey !== void 0 && previousRouteKey !== routeKey;
6422
+ const previousMaxContext = currentMaxContext();
6423
+ if (routeChanged) {
6424
+ delete a.ctx.meta["providerOverflowMaxContext"];
6425
+ delete a.ctx.meta["contextLimitBaseline"];
6426
+ delete a.ctx.meta["providerLimitEffectiveMaxContext"];
6427
+ delete a.ctx.meta["effectiveMaxContext"];
6428
+ delete a.ctx.meta["effectiveMaxContextSource"];
6429
+ delete a.ctx.meta["contextLimitLastEmittedMaxContext"];
6430
+ delete a.ctx.meta["contextLimitLastEmittedSource"];
6431
+ }
6432
+ a.ctx.meta["contextLimitRouteKey"] = routeKey;
6433
+ const routeMeta = a.ctx.meta["effectiveMaxContext"];
6434
+ const routeProviderCap = provider.capabilities.maxContext;
6435
+ const currentEffective = typeof routeMeta === "number" && routeMeta > 0 ? routeMeta : typeof routeProviderCap === "number" && routeProviderCap > 0 ? routeProviderCap : 2e5;
6436
+ const emitEffectiveLimit = (effective2, source) => {
6437
+ const lastEmitted = a.ctx.meta["contextLimitLastEmittedMaxContext"];
6438
+ const lastEmittedSource = a.ctx.meta["contextLimitLastEmittedSource"];
6439
+ if (lastEmitted === effective2 && lastEmittedSource === source) return;
6440
+ const eventPrevious = typeof lastEmitted === "number" && Number.isFinite(lastEmitted) && lastEmitted > 0 ? Math.floor(lastEmitted) : previousMaxContext;
6441
+ a.ctx.meta["contextLimitLastEmittedMaxContext"] = effective2;
6442
+ a.ctx.meta["contextLimitLastEmittedSource"] = source;
6443
+ a.events.emit("ctx.max_context", {
6444
+ sessionId: resolveEventSessionId(a.ctx),
6445
+ providerId: provider.id,
6446
+ modelId: model,
6447
+ maxContext: effective2,
6448
+ ...eventPrevious > 0 ? { previousMaxContext: eventPrevious } : {},
6449
+ source,
6450
+ decreased: eventPrevious > 0 && effective2 < eventPrevious
6451
+ });
6452
+ };
6453
+ const emitNativeAfterRouteChange = () => {
6454
+ if (routeChanged && currentEffective > 0) {
6455
+ a.ctx.meta["effectiveMaxContext"] = currentEffective;
6456
+ a.ctx.meta["effectiveMaxContextSource"] = "configured";
6457
+ emitEffectiveLimit(currentEffective, "configured");
6458
+ }
6459
+ };
6460
+ const refresh = provider.refreshContextLimit;
6461
+ if (opts.probe === false || !refresh) {
6462
+ emitNativeAfterRouteChange();
6463
+ return;
6464
+ }
6465
+ let discovered;
6466
+ try {
6467
+ discovered = await refresh.call(provider, model, { signal: a.ctx.signal });
6468
+ } catch {
6469
+ emitNativeAfterRouteChange();
6470
+ return;
6471
+ }
6472
+ const providerLimit = discovered?.maxContext;
6473
+ if (typeof providerLimit !== "number" || !Number.isFinite(providerLimit) || providerLimit <= 0) {
6474
+ emitNativeAfterRouteChange();
6475
+ return;
6476
+ }
6477
+ const providerLimitFloored = Math.floor(providerLimit);
6478
+ const existingBaseline = a.ctx.meta["contextLimitBaseline"];
6479
+ const lastApplied = a.ctx.meta["providerLimitEffectiveMaxContext"];
6480
+ const existingOverflow = a.ctx.meta["providerOverflowMaxContext"];
6481
+ const effectiveChangedOutsideProbe = typeof lastApplied === "number" && Number.isFinite(lastApplied) && currentEffective !== Math.floor(lastApplied) && currentEffective !== existingOverflow;
6482
+ const baseline = effectiveChangedOutsideProbe || typeof existingBaseline !== "number" || !Number.isFinite(existingBaseline) || existingBaseline <= 0 ? currentEffective : Math.floor(existingBaseline);
6483
+ a.ctx.meta["contextLimitBaseline"] = baseline;
6484
+ const overflowLimit = a.ctx.meta["providerOverflowMaxContext"];
6485
+ const overflowFloored = typeof overflowLimit === "number" && Number.isFinite(overflowLimit) && overflowLimit > 0 ? Math.floor(overflowLimit) : void 0;
6486
+ const ceilings = [baseline, providerLimitFloored];
6487
+ if (overflowFloored !== void 0) ceilings.push(overflowFloored);
6488
+ const effective = Math.min(...ceilings.filter((limit) => limit > 0));
6489
+ let effectiveSource;
6490
+ if (overflowFloored !== void 0 && effective === overflowFloored) {
6491
+ effectiveSource = "provider_overflow";
6492
+ } else if (effective === providerLimitFloored) {
6493
+ effectiveSource = "provider";
6494
+ } else {
6495
+ effectiveSource = "configured";
6496
+ }
6497
+ a.ctx.meta["effectiveMaxContext"] = effective;
6498
+ a.ctx.meta["providerLimitEffectiveMaxContext"] = effective;
6499
+ a.ctx.meta["effectiveMaxContextSource"] = effectiveSource;
6500
+ emitEffectiveLimit(effective, effectiveSource);
6501
+ }
6282
6502
  async function compactContextIfNeeded() {
6283
6503
  const msgCount = a.ctx.messages.length;
6284
6504
  const maxContext = currentMaxContext();
@@ -6291,13 +6511,14 @@ function createAgentLoopHandler(a, handlers) {
6291
6511
  }
6292
6512
  const beforeMessages = a.ctx.messages;
6293
6513
  const beforeMsgCount = a.ctx.messages.length;
6514
+ const beforeRevision = a.ctx.state.revision;
6294
6515
  await a.pipelines.contextWindow.run(a.ctx);
6295
6516
  _lastCompactionMsgCount = a.ctx.messages.length;
6296
6517
  _lastCompactionRevision = a.ctx.state.revision;
6297
6518
  _lastCompactionToolsRef = a.ctx.tools;
6298
6519
  _lastCompactionSystemRef = a.ctx.systemPrompt;
6299
6520
  _lastCompactionMaxContext = maxContext;
6300
- const changed = a.ctx.messages !== beforeMessages || a.ctx.messages.length !== beforeMsgCount;
6521
+ const changed = a.ctx.state.revision !== beforeRevision || a.ctx.messages !== beforeMessages || a.ctx.messages.length !== beforeMsgCount;
6301
6522
  const tokens = refreshContextRequestTokenStash({ force: changed });
6302
6523
  _lastCompactionRequestTokens = tokens;
6303
6524
  const load = maxContext > 0 ? tokens / maxContext : 0;
@@ -6334,40 +6555,58 @@ function createAgentLoopHandler(a, handlers) {
6334
6555
  );
6335
6556
  a.ctx.lastRequestTokens = preFlight.total;
6336
6557
  _lastPreFlightMsgCount = req.messages.length;
6558
+ _lastPreFlightToolCount = (req.tools ?? []).length;
6559
+ _lastPreFlightRevision = a.ctx.state.revision;
6337
6560
  _cachedSysRef = req.system;
6338
6561
  _cachedToolsRef = req.tools ?? [];
6339
6562
  _cachedOverheadTokens = preFlight.systemPrompt + preFlight.tools;
6340
6563
  a.ctx.meta["lastRequestTokensAt"] = {
6341
6564
  msgCount: req.messages.length,
6342
- toolCount: (req.tools ?? []).length
6565
+ toolCount: (req.tools ?? []).length,
6566
+ revision: a.ctx.state.revision
6343
6567
  };
6344
6568
  return preFlight;
6345
6569
  }
6346
6570
  function refreshContextRequestTokenStash(opts = {}) {
6347
6571
  const msgCount = a.ctx.messages.length;
6348
6572
  const toolCount = (a.ctx.tools ?? []).length;
6573
+ const revision = a.ctx.state.revision;
6349
6574
  const stashed = a.ctx.lastRequestTokens;
6350
6575
  const stashedAt = a.ctx.meta?.["lastRequestTokensAt"];
6351
6576
  if (!opts.force && typeof stashed === "number" && stashed > 0 && typeof stashedAt === "object" && stashedAt !== null) {
6352
6577
  const meta = stashedAt;
6353
- if (meta.msgCount === msgCount && (typeof meta.toolCount !== "number" || meta.toolCount === toolCount)) {
6578
+ if (meta.msgCount === msgCount && meta.toolCount === toolCount && meta.revision === revision && _lastPreFlightRevision === revision) {
6354
6579
  return stashed;
6355
6580
  }
6356
6581
  }
6357
6582
  const refreshed = estimateMessageTokens(a.ctx.messages) + systemAndToolsOverhead();
6358
6583
  a.ctx.lastRequestTokens = refreshed;
6359
6584
  _lastPreFlightMsgCount = msgCount;
6360
- a.ctx.meta["lastRequestTokensAt"] = { msgCount, toolCount };
6585
+ _lastPreFlightToolCount = toolCount;
6586
+ _lastPreFlightRevision = revision;
6587
+ a.ctx.meta["lastRequestTokensAt"] = { msgCount, toolCount, revision };
6361
6588
  return refreshed;
6362
6589
  }
6363
6590
  async function buildRequestWithPreflightCompaction(opts) {
6364
6591
  let prepared = await handlers.response.buildAndRunRequestPipeline(opts);
6365
6592
  let req = prepared.request;
6366
6593
  let preFlight = stashRequestTokens(req);
6594
+ let probedRouteKey = `${prepared.provider.id}/${req.model}`;
6595
+ await refreshProviderContextLimit(prepared.provider, req.model);
6367
6596
  if (await compactContextIfNeeded()) {
6368
6597
  prepared = await handlers.response.buildAndRunRequestPipeline(opts);
6369
6598
  req = prepared.request;
6370
6599
  preFlight = stashRequestTokens(req);
6600
+ const rebuiltRouteKey = `${prepared.provider.id}/${req.model}`;
6601
+ if (rebuiltRouteKey !== probedRouteKey) {
6602
+ probedRouteKey = rebuiltRouteKey;
6603
+ await refreshProviderContextLimit(prepared.provider, req.model);
6604
+ if (await compactContextIfNeeded()) {
6605
+ prepared = await handlers.response.buildAndRunRequestPipeline(opts);
6606
+ req = prepared.request;
6607
+ preFlight = stashRequestTokens(req);
6608
+ }
6609
+ }
6371
6610
  }
6372
6611
  return { req, provider: prepared.provider, preFlight };
6373
6612
  }
@@ -6383,16 +6622,8 @@ function createAgentLoopHandler(a, handlers) {
6383
6622
  _lastEmittedToolCount = toolCount;
6384
6623
  _lastEmittedRevision = revision;
6385
6624
  _lastEmittedMaxContext = maxContext;
6386
- if (msgCount !== _lastPreFlightMsgCount) {
6387
- const stashed2 = a.ctx.lastRequestTokens;
6388
- if (typeof stashed2 === "number" && stashed2 > 0 && _lastPreFlightMsgCount >= 0 && _lastPreFlightMsgCount < msgCount) {
6389
- const delta = estimateMessageTokens(a.ctx.messages.slice(_lastPreFlightMsgCount));
6390
- a.ctx.lastRequestTokens = stashed2 + delta;
6391
- } else {
6392
- a.ctx.lastRequestTokens = estimateMessageTokens(a.ctx.messages) + systemAndToolsOverhead();
6393
- }
6394
- _lastPreFlightMsgCount = msgCount;
6395
- a.ctx.meta["lastRequestTokensAt"] = { msgCount, toolCount };
6625
+ if (msgCount !== _lastPreFlightMsgCount || toolCount !== _lastPreFlightToolCount || revision !== _lastPreFlightRevision) {
6626
+ refreshContextRequestTokenStash({ force: true });
6396
6627
  }
6397
6628
  let total;
6398
6629
  const anchored = realAnchoredInputTokens(
@@ -6407,9 +6638,7 @@ function createAgentLoopHandler(a, handlers) {
6407
6638
  const cal = getCalibrationState(calibrationKey());
6408
6639
  total = cal.calibrated ? Math.round(stashed * Math.min(1.5, Math.max(0.5, cal.ratio))) : stashed;
6409
6640
  } else {
6410
- const raw = estimateMessageTokens(a.ctx.messages) + systemAndToolsOverhead();
6411
- a.ctx.lastRequestTokens = raw;
6412
- _lastPreFlightMsgCount = msgCount;
6641
+ const raw = refreshContextRequestTokenStash({ force: true });
6413
6642
  const cal = getCalibrationState(calibrationKey());
6414
6643
  total = cal.calibrated ? Math.round(raw * Math.min(1.5, Math.max(0.5, cal.ratio))) : raw;
6415
6644
  }
@@ -6433,6 +6662,8 @@ function createAgentLoopHandler(a, handlers) {
6433
6662
  let _lastEmittedRevision = -1;
6434
6663
  let _lastEmittedMaxContext = -1;
6435
6664
  let _lastPreFlightMsgCount = -1;
6665
+ let _lastPreFlightToolCount = -1;
6666
+ let _lastPreFlightRevision = -1;
6436
6667
  let _lastCompactionMsgCount = -1;
6437
6668
  let _lastCompactionRevision = -1;
6438
6669
  let _lastCompactionToolsRef = null;
@@ -6538,6 +6769,7 @@ function createAgentLoopHandler(a, handlers) {
6538
6769
  } catch (err) {
6539
6770
  (a.logger.debug ?? a.logger.warn)?.(`session boundary flush failed: ${toErrorMessage(err)}`);
6540
6771
  }
6772
+ clearPendingNextSteps(a.ctx);
6541
6773
  let finalText = "";
6542
6774
  let iterations = 0;
6543
6775
  const delegateSummaries = [];
@@ -6625,7 +6857,11 @@ ${text}` : text;
6625
6857
  return { ...limitCheck.exit, finalText };
6626
6858
  }
6627
6859
  await a.extensions.runBeforeIteration(a.ctx, i);
6628
- a.events.emit("iteration.started", { sessionId: resolveEventSessionId(a.ctx), ctx: a.ctx, index: i });
6860
+ a.events.emit("iteration.started", {
6861
+ sessionId: resolveEventSessionId(a.ctx),
6862
+ ctx: a.ctx,
6863
+ index: i
6864
+ });
6629
6865
  injectPendingBtwNotes((block) => pendingMailboxBlocks.push(block));
6630
6866
  injectQueueAwareness();
6631
6867
  if (pendingLoopSteer) {
@@ -6775,11 +7011,9 @@ ${text}` : text;
6775
7011
  res = recovered.response;
6776
7012
  }
6777
7013
  clearEvaluatedMailboxBlocks();
6778
- const responseResult = await handlers.response.processResponse(
6779
- res,
6780
- req,
6781
- providerBoundToRequest(req) ?? requestProvider
6782
- );
7014
+ const responseProvider = providerBoundToRequest(req) ?? requestProvider;
7015
+ const responseResult = await handlers.response.processResponse(res, req, responseProvider);
7016
+ await refreshProviderContextLimit(responseProvider, req.model, { probe: false });
6783
7017
  if (responseResult.aborted) {
6784
7018
  return {
6785
7019
  status: "aborted",
@@ -6937,7 +7171,11 @@ ${text}` : text;
6937
7171
  }
6938
7172
  await compactContextIfNeeded();
6939
7173
  emitContextPct();
6940
- a.events.emit("iteration.completed", { sessionId: resolveEventSessionId(a.ctx), ctx: a.ctx, index: i });
7174
+ a.events.emit("iteration.completed", {
7175
+ sessionId: resolveEventSessionId(a.ctx),
7176
+ ctx: a.ctx,
7177
+ index: i
7178
+ });
6941
7179
  await a.extensions.runAfterIteration(a.ctx, i);
6942
7180
  if (autonomousContinue && responseResult.directive === "continue") {
6943
7181
  continue;
@@ -7091,6 +7329,9 @@ function buildLiveNextStepsGateBlock(ctx) {
7091
7329
  (todo) => todo.status === "pending" || todo.status === "in_progress"
7092
7330
  );
7093
7331
  if (openTodos.length === 0) {
7332
+ const toolRoute = ctx.tools?.some((t2) => t2.name === "nextsteps") ? [
7333
+ "Calling the `nextsteps` tool with the same items satisfies branch 1 as well; if you both call it and write the block, the block wins."
7334
+ ] : [];
7094
7335
  return {
7095
7336
  type: "text",
7096
7337
  text: [
@@ -7099,6 +7340,7 @@ function buildLiveNextStepsGateBlock(ctx) {
7099
7340
  "On the final response, you MUST take exactly one branch:",
7100
7341
  "1. If at least one genuinely useful follow-on action exists, include a balanced <nextsteps> block containing 1-4 exact prompt messages that can be submitted back to you through the current TUI or WebUI input.",
7101
7342
  "Every item must ask the agent to perform work. Never put a human-only chore or an instruction addressed to the user inside <nextsteps>; natural-language agent-directed imperatives are valid and need not be shell commands.",
7343
+ ...toolRoute,
7102
7344
  "2. If no useful follow-on action truly exists, omit <nextsteps> and explicitly tell the user in normal prose that no further steps are needed for this task.",
7103
7345
  "Silently omitting both is invalid. Do not decide by chance, tone, or response length, and do not invent filler suggestions.",
7104
7346
  "[/nextsteps_gate]"
@@ -7126,6 +7368,27 @@ function buildLiveNextStepsGateBlock(ctx) {
7126
7368
  cache_control: { type: "ephemeral" }
7127
7369
  };
7128
7370
  }
7371
+ var MAX_MEMORY_EVIDENCE_CHARS = 12e3;
7372
+ function buildMemoryEvidenceBlocks(ctx) {
7373
+ const blocks = [];
7374
+ let remaining = MAX_MEMORY_EVIDENCE_CHARS;
7375
+ for (const entry of ctx.memoryEvidence) {
7376
+ if (remaining <= 0) break;
7377
+ const text = entry.text.trim();
7378
+ if (!text) continue;
7379
+ const source = entry.source.replace(/[^a-z0-9_.-]+/gi, "-").slice(0, 80) || "memory";
7380
+ const bounded = text.slice(0, remaining);
7381
+ remaining -= bounded.length;
7382
+ blocks.push({
7383
+ type: "text",
7384
+ text: `[memory_evidence source="${source}"]
7385
+ ${bounded}
7386
+ [/memory_evidence]`,
7387
+ cache_control: { type: "ephemeral" }
7388
+ });
7389
+ }
7390
+ return blocks;
7391
+ }
7129
7392
  function createAgentResponseHandler(a) {
7130
7393
  const stabilizedPromptEpochs = /* @__PURE__ */ new WeakSet();
7131
7394
  function stabilizePromptEpoch() {
@@ -7157,7 +7420,8 @@ function createAgentResponseHandler(a) {
7157
7420
  stabilizePromptEpoch();
7158
7421
  const volatileLedger = buildCompletedWorkLedgerBlock(a.ctx);
7159
7422
  const liveNextStepsGate = buildLiveNextStepsGateBlock(a.ctx);
7160
- const volatileBlocks = [volatileLedger, liveNextStepsGate].filter(
7423
+ const memoryEvidence = buildMemoryEvidenceBlocks(a.ctx);
7424
+ const volatileBlocks = [volatileLedger, liveNextStepsGate, ...memoryEvidence].filter(
7161
7425
  (block) => block !== void 0
7162
7426
  );
7163
7427
  const system = volatileBlocks.length > 0 ? [...a.ctx.systemPrompt, ...volatileBlocks] : a.ctx.systemPrompt;
@@ -7167,7 +7431,7 @@ function createAgentResponseHandler(a) {
7167
7431
  model: opts.model ?? a.ctx.model,
7168
7432
  system,
7169
7433
  messages: a.ctx.messages,
7170
- tools: a.tools.list(),
7434
+ tools: a.tools.listForProvider(),
7171
7435
  // `maxTokens` is deliberately NOT set here. The provider adapter
7172
7436
  // resolves the ceiling from the catalog entry for the model in
7173
7437
  // `req.model`, which is the only source that stays correct across a
@@ -7190,6 +7454,7 @@ function createAgentResponseHandler(a) {
7190
7454
  async function processResponse(raw, req, requestProvider = a.ctx.provider) {
7191
7455
  let res = raw;
7192
7456
  res = await a.pipelines.response.run(res);
7457
+ res = maybeAppendPendingNextSteps(a.ctx, res);
7193
7458
  a.events.emit("provider.response", {
7194
7459
  sessionId: resolveEventSessionId(a.ctx),
7195
7460
  ctx: a.ctx,
@@ -7330,7 +7595,7 @@ var DIFF_TOOL_NAMES = /* @__PURE__ */ new Set(["edit", "write", "replace", "patc
7330
7595
  var DIFF_TOOL_EVENT_PREVIEW_MAX = 16e3;
7331
7596
  var SAGE_EVENT_MAX = 2e3;
7332
7597
  function createAgentToolHandler(a) {
7333
- async function executeSingleWithDecision(tool, use) {
7598
+ async function executeSingleWithDecision(tool, use, preToolContext) {
7334
7599
  const start = Date.now();
7335
7600
  try {
7336
7601
  const result = await a.toolExecutor.executeTool(
@@ -7342,7 +7607,8 @@ function createAgentToolHandler(a) {
7342
7607
  input: use.input
7343
7608
  },
7344
7609
  a.ctx,
7345
- a.perIterationOutputCapBytes
7610
+ a.perIterationOutputCapBytes,
7611
+ preToolContext
7346
7612
  );
7347
7613
  return { result: result.block, durationMs: Date.now() - start };
7348
7614
  } catch (err) {
@@ -7486,11 +7752,15 @@ function createAgentToolHandler(a) {
7486
7752
  const p = a.permission;
7487
7753
  p.denyOnce?.({ tool: tool.name, pattern: result.suggestedPattern });
7488
7754
  }
7489
- const reRunResult = decision === "yes" || decision === "always" ? await executeSingleWithDecision(tool, {
7490
- id: result.toolUseId,
7491
- name: tool.name,
7492
- input: result.input
7493
- }) : {
7755
+ const reRunResult = decision === "yes" || decision === "always" ? await executeSingleWithDecision(
7756
+ tool,
7757
+ {
7758
+ id: result.toolUseId,
7759
+ name: tool.name,
7760
+ input: result.input
7761
+ },
7762
+ result.preToolContext
7763
+ ) : {
7494
7764
  result: {
7495
7765
  type: "tool_result",
7496
7766
  tool_use_id: result.toolUseId,
@@ -7924,7 +8194,8 @@ var Agent = class {
7924
8194
  await sessionWriter.flush().catch(() => {
7925
8195
  });
7926
8196
  });
7927
- this.ctx.tools = this.tools.list();
8197
+ this.ctx.tools = this.tools.listForProvider();
8198
+ this.ctx.catalogTools = this.tools.list();
7928
8199
  const span = this.tracer?.startSpan("agent.run", {
7929
8200
  "agent.model": opts.model ?? this.ctx.model,
7930
8201
  "agent.executionStrategy": opts.executionStrategy ?? this.executionStrategy
@@ -8011,165 +8282,6 @@ var Agent = class {
8011
8282
  // ── Tool + response execution handled by AgentToolHandler / AgentResponseHandler ──
8012
8283
  };
8013
8284
 
8014
- // src/core/input-builder.ts
8015
- var InputBuilder = class {
8016
- store;
8017
- pasteLineThreshold;
8018
- pasteCharThreshold;
8019
- display = "";
8020
- refs = [];
8021
- constructor(opts) {
8022
- this.store = opts.store;
8023
- this.pasteLineThreshold = opts.pasteLineThreshold ?? 8;
8024
- this.pasteCharThreshold = opts.pasteCharThreshold ?? 2e3;
8025
- }
8026
- get text() {
8027
- return this.display;
8028
- }
8029
- get attachments() {
8030
- return [...this.refs];
8031
- }
8032
- get isEmpty() {
8033
- return this.display.trim().length === 0;
8034
- }
8035
- appendText(text) {
8036
- this.display += text;
8037
- }
8038
- /**
8039
- * Decide whether a chunk of pasted text is "big" enough to collapse.
8040
- * If yes, store it and append a placeholder. If no, inline it.
8041
- * Returns the placeholder string actually appended (or `null` if inlined).
8042
- */
8043
- async appendPaste(text) {
8044
- if (this.shouldCollapse(text)) {
8045
- const ref = await this.store.add({
8046
- kind: "text",
8047
- data: text,
8048
- meta: { label: paragraphLabel(text) }
8049
- });
8050
- const placeholder = `[pasted #${ref.seq}]`;
8051
- this.display += placeholder;
8052
- this.refs.push(ref);
8053
- return placeholder;
8054
- }
8055
- this.display += text;
8056
- return null;
8057
- }
8058
- /**
8059
- * Always collapses to `[image #N]` — images are never inlined.
8060
- * `dataBase64` is the raw base64 payload (no data: prefix).
8061
- */
8062
- async appendImage(dataBase64, mediaType) {
8063
- const ref = await this.store.add({
8064
- kind: "image",
8065
- data: dataBase64,
8066
- meta: { mediaType, label: `${mediaType.split("/")[1]?.toUpperCase() ?? "IMG"}` }
8067
- });
8068
- const placeholder = `[image #${ref.seq}]`;
8069
- this.display += placeholder;
8070
- this.refs.push(ref);
8071
- return placeholder;
8072
- }
8073
- async appendFile(input) {
8074
- const ref = await this.store.add({ ...input, kind: "file" });
8075
- const placeholder = `[file #${ref.seq}]`;
8076
- this.display += placeholder;
8077
- this.refs.push(ref);
8078
- return placeholder;
8079
- }
8080
- /**
8081
- * Register-only variant of `appendPaste`. Always stores the paste and
8082
- * returns the inline `[pasted #N, L lines]` token WITHOUT mutating
8083
- * `display` — the caller (TUI) owns its own editable buffer as the single
8084
- * source of truth, inserts the token there, and expands the buffer at
8085
- * submit. The collapse decision is the caller's (it gates this call); use
8086
- * `wouldCollapse()` for that.
8087
- */
8088
- async registerPaste(text) {
8089
- const ref = await this.store.add({
8090
- kind: "text",
8091
- data: text,
8092
- meta: { label: paragraphLabel(text) }
8093
- });
8094
- this.refs.push(ref);
8095
- const lines = text.split(/\r?\n|\r/).length;
8096
- return `[pasted #${ref.seq}, ${lines} lines]`;
8097
- }
8098
- /**
8099
- * Register-only variant of `appendImage` — see `registerPaste`. Returns a
8100
- * seq-keyed `[image #N, LABEL]` token; does not touch `display`.
8101
- */
8102
- async registerImage(dataBase64, mediaType) {
8103
- const label = `${mediaType.split("/")[1]?.toUpperCase() ?? "IMG"}`;
8104
- const ref = await this.store.add({
8105
- kind: "image",
8106
- data: dataBase64,
8107
- meta: { mediaType, label }
8108
- });
8109
- this.refs.push(ref);
8110
- return `[image #${ref.seq}, ${label}]`;
8111
- }
8112
- /**
8113
- * Register-only variant of `appendFile` — see `registerPaste`. Returns a
8114
- * path-keyed `[file:<path>]` token (resolved by path at expand time); does
8115
- * not touch `display`. The path is read from `meta.filename` (falling back
8116
- * to `meta.label`).
8117
- */
8118
- async registerFile(input) {
8119
- const ref = await this.store.add({ ...input, kind: "file" });
8120
- this.refs.push(ref);
8121
- const path15 = ref.meta.filename ?? ref.meta.label ?? String(ref.seq);
8122
- return `[file:${path15}]`;
8123
- }
8124
- /**
8125
- * Whether `appendPaste(text)` would collapse the text to a placeholder
8126
- * (rather than inlining it). Lets a frontend decide where to route a paste
8127
- * — e.g. collapsed pastes become a pill, while inlined ones can be shown
8128
- * in the editable input row — without calling `appendPaste` first (which
8129
- * mutates the display buffer).
8130
- */
8131
- wouldCollapse(text) {
8132
- return this.shouldCollapse(text);
8133
- }
8134
- /** Reset display and ref list. Does NOT clear the store itself. */
8135
- reset() {
8136
- this.display = "";
8137
- this.refs.length = 0;
8138
- }
8139
- /**
8140
- * Resolve the current display string into ContentBlock[]. Empty
8141
- * input returns an empty array — caller decides what to do.
8142
- */
8143
- async submit() {
8144
- const text = this.display;
8145
- this.reset();
8146
- return text ? this.store.expand(text) : [];
8147
- }
8148
- shouldCollapse(text) {
8149
- if (text.length >= this.pasteCharThreshold) return true;
8150
- let lines = 1;
8151
- for (let i = 0; i < text.length; i++) {
8152
- const c = text.charCodeAt(i);
8153
- if (c === 10) lines++;
8154
- else if (c === 13 && text.charCodeAt(i + 1) !== 10) lines++;
8155
- if (lines >= this.pasteLineThreshold) return true;
8156
- }
8157
- return false;
8158
- }
8159
- };
8160
- function paragraphLabel(text) {
8161
- const lines = text.split(/\r?\n|\r/).length;
8162
- const bytes = Buffer.byteLength(text, "utf8");
8163
- if (bytes < 1024) return `${lines} lines, ${bytes} B`;
8164
- return `${lines} lines, ${(bytes / 1024).toFixed(1)} KB`;
8165
- }
8166
-
8167
- // src/utils/todos-format.ts
8168
- function hasOpenTodos(todos) {
8169
- if (!Array.isArray(todos) || todos.length === 0) return false;
8170
- return todos.some((t2) => t2.status === "pending" || t2.status === "in_progress");
8171
- }
8172
-
8173
8285
  // src/core/continue-intent.ts
8174
8286
  var CONTINUE_PHRASES = /* @__PURE__ */ new Set([
8175
8287
  // ── English ──
@@ -9101,34 +9213,157 @@ function createFallbackModelExtension(deps) {
9101
9213
  };
9102
9214
  }
9103
9215
 
9104
- // src/types/config/runtime.ts
9105
- function normalizeTokenSavingTier(val) {
9106
- if (val === void 0) return "off";
9107
- if (typeof val === "boolean") return val ? "medium" : "off";
9108
- const validTiers = /* @__PURE__ */ new Set([
9109
- "off",
9110
- "minimal",
9111
- "light",
9112
- "medium",
9113
- "aggressive"
9114
- ]);
9115
- return validTiers.has(val) ? val : "off";
9116
- }
9117
- function resolveTokenSavingTier(val, maxContext) {
9118
- if (val === "auto") {
9119
- if (typeof maxContext !== "number" || !Number.isFinite(maxContext) || maxContext <= 0) {
9120
- return "off";
9216
+ // src/core/input-builder.ts
9217
+ var InputBuilder = class {
9218
+ store;
9219
+ pasteLineThreshold;
9220
+ pasteCharThreshold;
9221
+ display = "";
9222
+ refs = [];
9223
+ constructor(opts) {
9224
+ this.store = opts.store;
9225
+ this.pasteLineThreshold = opts.pasteLineThreshold ?? 8;
9226
+ this.pasteCharThreshold = opts.pasteCharThreshold ?? 2e3;
9227
+ }
9228
+ get text() {
9229
+ return this.display;
9230
+ }
9231
+ get attachments() {
9232
+ return [...this.refs];
9233
+ }
9234
+ get isEmpty() {
9235
+ return this.display.trim().length === 0;
9236
+ }
9237
+ appendText(text) {
9238
+ this.display += text;
9239
+ }
9240
+ /**
9241
+ * Decide whether a chunk of pasted text is "big" enough to collapse.
9242
+ * If yes, store it and append a placeholder. If no, inline it.
9243
+ * Returns the placeholder string actually appended (or `null` if inlined).
9244
+ */
9245
+ async appendPaste(text) {
9246
+ if (this.shouldCollapse(text)) {
9247
+ const ref = await this.store.add({
9248
+ kind: "text",
9249
+ data: text,
9250
+ meta: { label: paragraphLabel(text) }
9251
+ });
9252
+ const placeholder = `[pasted #${ref.seq}]`;
9253
+ this.display += placeholder;
9254
+ this.refs.push(ref);
9255
+ return placeholder;
9121
9256
  }
9122
- if (maxContext < 32e3) return "medium";
9123
- if (maxContext < 128e3) return "light";
9124
- return "minimal";
9257
+ this.display += text;
9258
+ return null;
9125
9259
  }
9126
- return normalizeTokenSavingTier(val);
9127
- }
9128
-
9129
- // src/types/system-prompt.ts
9130
- function flattenSystemPromptRegions(regions) {
9131
- return [...regions.core, ...regions.session, ...regions.volatile];
9260
+ /**
9261
+ * Always collapses to `[image #N]` — images are never inlined.
9262
+ * `dataBase64` is the raw base64 payload (no data: prefix).
9263
+ */
9264
+ async appendImage(dataBase64, mediaType) {
9265
+ const ref = await this.store.add({
9266
+ kind: "image",
9267
+ data: dataBase64,
9268
+ meta: { mediaType, label: `${mediaType.split("/")[1]?.toUpperCase() ?? "IMG"}` }
9269
+ });
9270
+ const placeholder = `[image #${ref.seq}]`;
9271
+ this.display += placeholder;
9272
+ this.refs.push(ref);
9273
+ return placeholder;
9274
+ }
9275
+ async appendFile(input) {
9276
+ const ref = await this.store.add({ ...input, kind: "file" });
9277
+ const placeholder = `[file #${ref.seq}]`;
9278
+ this.display += placeholder;
9279
+ this.refs.push(ref);
9280
+ return placeholder;
9281
+ }
9282
+ /**
9283
+ * Register-only variant of `appendPaste`. Always stores the paste and
9284
+ * returns the inline `[pasted #N, L lines]` token WITHOUT mutating
9285
+ * `display` — the caller (TUI) owns its own editable buffer as the single
9286
+ * source of truth, inserts the token there, and expands the buffer at
9287
+ * submit. The collapse decision is the caller's (it gates this call); use
9288
+ * `wouldCollapse()` for that.
9289
+ */
9290
+ async registerPaste(text) {
9291
+ const ref = await this.store.add({
9292
+ kind: "text",
9293
+ data: text,
9294
+ meta: { label: paragraphLabel(text) }
9295
+ });
9296
+ this.refs.push(ref);
9297
+ const lines = text.split(/\r?\n|\r/).length;
9298
+ return `[pasted #${ref.seq}, ${lines} lines]`;
9299
+ }
9300
+ /**
9301
+ * Register-only variant of `appendImage` — see `registerPaste`. Returns a
9302
+ * seq-keyed `[image #N, LABEL]` token; does not touch `display`.
9303
+ */
9304
+ async registerImage(dataBase64, mediaType) {
9305
+ const label = `${mediaType.split("/")[1]?.toUpperCase() ?? "IMG"}`;
9306
+ const ref = await this.store.add({
9307
+ kind: "image",
9308
+ data: dataBase64,
9309
+ meta: { mediaType, label }
9310
+ });
9311
+ this.refs.push(ref);
9312
+ return `[image #${ref.seq}, ${label}]`;
9313
+ }
9314
+ /**
9315
+ * Register-only variant of `appendFile` — see `registerPaste`. Returns a
9316
+ * path-keyed `[file:<path>]` token (resolved by path at expand time); does
9317
+ * not touch `display`. The path is read from `meta.filename` (falling back
9318
+ * to `meta.label`).
9319
+ */
9320
+ async registerFile(input) {
9321
+ const ref = await this.store.add({ ...input, kind: "file" });
9322
+ this.refs.push(ref);
9323
+ const path15 = ref.meta.filename ?? ref.meta.label ?? String(ref.seq);
9324
+ return `[file:${path15}]`;
9325
+ }
9326
+ /**
9327
+ * Whether `appendPaste(text)` would collapse the text to a placeholder
9328
+ * (rather than inlining it). Lets a frontend decide where to route a paste
9329
+ * — e.g. collapsed pastes become a pill, while inlined ones can be shown
9330
+ * in the editable input row — without calling `appendPaste` first (which
9331
+ * mutates the display buffer).
9332
+ */
9333
+ wouldCollapse(text) {
9334
+ return this.shouldCollapse(text);
9335
+ }
9336
+ /** Reset display and ref list. Does NOT clear the store itself. */
9337
+ reset() {
9338
+ this.display = "";
9339
+ this.refs.length = 0;
9340
+ }
9341
+ /**
9342
+ * Resolve the current display string into ContentBlock[]. Empty
9343
+ * input returns an empty array — caller decides what to do.
9344
+ */
9345
+ async submit() {
9346
+ const text = this.display;
9347
+ this.reset();
9348
+ return text ? this.store.expand(text) : [];
9349
+ }
9350
+ shouldCollapse(text) {
9351
+ if (text.length >= this.pasteCharThreshold) return true;
9352
+ let lines = 1;
9353
+ for (let i = 0; i < text.length; i++) {
9354
+ const c = text.charCodeAt(i);
9355
+ if (c === 10) lines++;
9356
+ else if (c === 13 && text.charCodeAt(i + 1) !== 10) lines++;
9357
+ if (lines >= this.pasteLineThreshold) return true;
9358
+ }
9359
+ return false;
9360
+ }
9361
+ };
9362
+ function paragraphLabel(text) {
9363
+ const lines = text.split(/\r?\n|\r/).length;
9364
+ const bytes = Buffer.byteLength(text, "utf8");
9365
+ if (bytes < 1024) return `${lines} lines, ${bytes} B`;
9366
+ return `${lines} lines, ${(bytes / 1024).toFixed(1)} KB`;
9132
9367
  }
9133
9368
 
9134
9369
  // src/core/instruction-bundle.ts
@@ -9306,7 +9541,281 @@ function firstExistingDirSync(candidates) {
9306
9541
  return candidates[0] ?? "";
9307
9542
  }
9308
9543
 
9544
+ // src/types/runtime-capability-manifest.ts
9545
+ var PLAYWRIGHT_ALIASES = {
9546
+ playwright_navigate: "browser_navigate",
9547
+ playwright_screenshot: "browser_screenshot",
9548
+ playwright_click: "browser_click",
9549
+ playwright_type: "browser_type",
9550
+ playwright_evaluate: "browser_evaluate",
9551
+ playwright_select_option: "browser_select",
9552
+ playwright_hover: "browser_hover",
9553
+ playwright_fill_form: "browser_type",
9554
+ playwright_wait_for: "browser_wait",
9555
+ playwright_press_key: "browser_press",
9556
+ playwright_drag: "browser_drag"
9557
+ };
9558
+ var RUNTIME_CAPABILITY_MANIFEST = [
9559
+ {
9560
+ id: "filesystem.read",
9561
+ pack: "core",
9562
+ exposure: "direct",
9563
+ tools: ["read", "grep", "glob", "tree"]
9564
+ },
9565
+ {
9566
+ id: "code.inspect",
9567
+ pack: "core",
9568
+ exposure: "direct",
9569
+ tools: [
9570
+ "codebase-stats",
9571
+ "codebase-search",
9572
+ "codebase-incoming-calls",
9573
+ "codebase-outgoing-calls",
9574
+ "codebase-index",
9575
+ "dead-code-scan",
9576
+ "diff",
9577
+ "json",
9578
+ "logs"
9579
+ ]
9580
+ },
9581
+ {
9582
+ id: "filesystem.write",
9583
+ pack: "development",
9584
+ exposure: "direct",
9585
+ tools: ["write", "edit", "replace", "patch"]
9586
+ },
9587
+ {
9588
+ id: "execution.shell",
9589
+ pack: "development",
9590
+ exposure: "direct",
9591
+ tools: ["bash", "exec", "language", "language_info", "language_package"]
9592
+ },
9593
+ {
9594
+ id: "verification.run",
9595
+ pack: "development",
9596
+ exposure: "direct",
9597
+ tools: ["lint", "format", "typecheck", "test", "e2e_plan"]
9598
+ },
9599
+ {
9600
+ id: "version-control.manage",
9601
+ pack: "development",
9602
+ exposure: "direct",
9603
+ tools: ["git"]
9604
+ },
9605
+ {
9606
+ id: "dependencies.manage",
9607
+ pack: "development",
9608
+ exposure: "on-demand",
9609
+ tools: ["install", "outdated", "audit"]
9610
+ },
9611
+ {
9612
+ id: "documentation.author",
9613
+ pack: "development",
9614
+ exposure: "on-demand",
9615
+ tools: ["document", "design", "scaffold"]
9616
+ },
9617
+ {
9618
+ id: "web.research",
9619
+ pack: "core",
9620
+ exposure: "direct",
9621
+ tools: ["search", "fetch"]
9622
+ },
9623
+ {
9624
+ id: "work.plan",
9625
+ pack: "core",
9626
+ exposure: "direct",
9627
+ tools: ["todo", "plan", "task", "kanban", "nextsteps"]
9628
+ },
9629
+ {
9630
+ id: "coordination.mailbox",
9631
+ pack: "fleet",
9632
+ exposure: "direct",
9633
+ tools: ["mailbox", "mail_send", "mail_inbox", "fleet_status"]
9634
+ },
9635
+ {
9636
+ id: "fleet.delegate",
9637
+ pack: "fleet",
9638
+ exposure: "on-demand",
9639
+ tools: [
9640
+ "delegate",
9641
+ "spawn_subagent",
9642
+ "assign_task",
9643
+ "kanban_queue",
9644
+ "await_tasks",
9645
+ "ask_subagent",
9646
+ "ask_result",
9647
+ "roll_up",
9648
+ "quality_gate",
9649
+ "terminate_subagent",
9650
+ "terminate_all",
9651
+ "work_complete",
9652
+ "collab_debug",
9653
+ "fleet_emit",
9654
+ "fleet"
9655
+ ]
9656
+ },
9657
+ {
9658
+ id: "reasoning.oneshot",
9659
+ pack: "core",
9660
+ exposure: "on-demand",
9661
+ tools: ["llm", "council"]
9662
+ },
9663
+ {
9664
+ id: "memory.manage",
9665
+ pack: "memory",
9666
+ exposure: "on-demand",
9667
+ tools: ["remember", "search_memory", "find_related_memories", "forget"]
9668
+ },
9669
+ {
9670
+ id: "memory.curate",
9671
+ pack: "memory",
9672
+ exposure: "on-demand",
9673
+ tools: [
9674
+ "memory_candidates",
9675
+ "memory_update",
9676
+ "memory_delete",
9677
+ "memory_recover",
9678
+ "memory_backfill_recoverable",
9679
+ "memory_for_file",
9680
+ "memory_for_path",
9681
+ "memory_search",
9682
+ "memory_graph",
9683
+ "memory_gather_batch",
9684
+ "memory_verify",
9685
+ "memory_hygiene"
9686
+ ]
9687
+ },
9688
+ {
9689
+ id: "browser.interact",
9690
+ pack: "browser",
9691
+ exposure: "on-demand",
9692
+ tools: [
9693
+ "browser_open",
9694
+ "browser_status",
9695
+ "browser_list",
9696
+ "browser_navigate",
9697
+ "browser_snapshot",
9698
+ "browser_screenshot",
9699
+ "browser_click",
9700
+ "browser_type",
9701
+ "browser_select",
9702
+ "browser_press",
9703
+ "browser_hover",
9704
+ "browser_drag",
9705
+ "browser_wait",
9706
+ "browser_evaluate",
9707
+ "browser_upload",
9708
+ "browser_close"
9709
+ ],
9710
+ aliases: PLAYWRIGHT_ALIASES
9711
+ },
9712
+ {
9713
+ id: "mcp.dynamic",
9714
+ pack: "mcp",
9715
+ exposure: "on-demand",
9716
+ tools: ["mcp_use"]
9717
+ },
9718
+ {
9719
+ id: "automation.manage",
9720
+ pack: "admin",
9721
+ exposure: "on-demand",
9722
+ tools: ["cron_schedule", "cron_list", "cron_cancel", "watch_start", "watch_list", "watch_stop"]
9723
+ },
9724
+ {
9725
+ id: "context.pin",
9726
+ pack: "admin",
9727
+ exposure: "on-demand",
9728
+ tools: ["pin_add", "pin_list", "pin_remove"]
9729
+ },
9730
+ {
9731
+ id: "quality.analyze",
9732
+ pack: "development",
9733
+ exposure: "on-demand",
9734
+ tools: ["dead_code_scan", "detect_duplicate_code", "secret_scanner_test", "error_lens_history"]
9735
+ },
9736
+ {
9737
+ id: "release.manage",
9738
+ pack: "development",
9739
+ exposure: "on-demand",
9740
+ tools: ["git_autocommit", "semver_current", "semver_bump", "semver_changelog"]
9741
+ },
9742
+ {
9743
+ id: "messaging.telegram",
9744
+ pack: "fleet",
9745
+ exposure: "on-demand",
9746
+ tools: ["telegram_send", "telegram_read", "telegram_approve"]
9747
+ },
9748
+ {
9749
+ id: "tools.discover",
9750
+ pack: "admin",
9751
+ exposure: "direct",
9752
+ tools: ["tool_search", "tool_use", "tool_help", "batch_tool_use"]
9753
+ },
9754
+ {
9755
+ id: "runtime.admin",
9756
+ pack: "admin",
9757
+ exposure: "internal",
9758
+ tools: ["set_working_dir", "context_manager", "mode", "skill", "mcp_control"]
9759
+ }
9760
+ ];
9761
+ var CAPABILITY_BY_ID = new Map(
9762
+ RUNTIME_CAPABILITY_MANIFEST.map((definition) => [definition.id, definition])
9763
+ );
9764
+ var CAPABILITY_BY_TOOL = /* @__PURE__ */ new Map();
9765
+ var TOOL_ALIASES = /* @__PURE__ */ new Map();
9766
+ for (const definition of RUNTIME_CAPABILITY_MANIFEST) {
9767
+ for (const tool of definition.tools) {
9768
+ CAPABILITY_BY_TOOL.set(tool, definition.id);
9769
+ }
9770
+ for (const [alias, tool] of Object.entries(definition.aliases ?? {})) {
9771
+ TOOL_ALIASES.set(alias, tool);
9772
+ }
9773
+ }
9774
+ function normalizeRuntimeToolName(name) {
9775
+ return TOOL_ALIASES.get(name) ?? name;
9776
+ }
9777
+ function runtimeCapabilityForTool(name) {
9778
+ return CAPABILITY_BY_TOOL.get(normalizeRuntimeToolName(name));
9779
+ }
9780
+ function inferRuntimeCapabilities(toolNames) {
9781
+ const ids = /* @__PURE__ */ new Set();
9782
+ for (const rawName of toolNames) {
9783
+ const id = runtimeCapabilityForTool(rawName);
9784
+ if (id) ids.add(id);
9785
+ }
9786
+ return [...ids];
9787
+ }
9788
+ function missingRuntimeCapabilities(required, toolNames) {
9789
+ if (!required || required.length === 0) return [];
9790
+ const available = new Set(inferRuntimeCapabilities(toolNames));
9791
+ return required.filter((id) => !CAPABILITY_BY_ID.has(id) || !available.has(id));
9792
+ }
9793
+ function missingRequiredRuntimeTools(required, toolNames) {
9794
+ if (!required || required.length === 0) return [];
9795
+ const available = new Set(toolNames.map(normalizeRuntimeToolName));
9796
+ return required.map(normalizeRuntimeToolName).filter((name) => !CAPABILITY_BY_TOOL.has(name) || !available.has(name));
9797
+ }
9798
+ function runtimeToolReferencesFromText(text) {
9799
+ const references = /* @__PURE__ */ new Set();
9800
+ for (const match of text.matchAll(/`([a-z][a-z0-9_-]+)`/gi)) {
9801
+ const name = normalizeRuntimeToolName(match[1] ?? "");
9802
+ if (CAPABILITY_BY_TOOL.has(name)) references.add(name);
9803
+ }
9804
+ for (const pattern of [
9805
+ /\b(?:call|use|invoke|run)\s+`([a-z][a-z0-9_-]+)`/gi,
9806
+ /`([a-z][a-z0-9_-]+)`\s+tool\b/gi
9807
+ ]) {
9808
+ for (const match of text.matchAll(pattern)) {
9809
+ references.add(normalizeRuntimeToolName(match[1] ?? ""));
9810
+ }
9811
+ }
9812
+ return [...references];
9813
+ }
9814
+
9309
9815
  // src/core/instruction-template.ts
9816
+ var CANONICAL_TOOL_NAMES = new Set(
9817
+ RUNTIME_CAPABILITY_MANIFEST.flatMap((entry) => [...entry.tools])
9818
+ );
9310
9819
  var DIRECTIVE_RE = /[ \t]*<!--\s*ws:(if|else|end)\b([^>]*?)-->[ \t]*(?:\r?\n)?/g;
9311
9820
  var PLACEHOLDER_RE = /\{\{\s*(tools:)?\s*([a-zA-Z0-9_.,\s-]+?)\s*\}\}/g;
9312
9821
  function renderInstructionLayer(text, ctx) {
@@ -9316,7 +9825,41 @@ function renderInstructionLayer(text, ctx) {
9316
9825
  if (!hasDirectives && !hasPlaceholders) return text;
9317
9826
  const rendered = hasDirectives ? emit(parse(text), ctx) : text;
9318
9827
  const substituted = hasPlaceholders ? substitute(rendered, ctx) : rendered;
9319
- return tidy(substituted);
9828
+ const guarded = ctx?.strictToolReferences ? dropLinesWithUnavailableToolReferences(
9829
+ substituted,
9830
+ ctx,
9831
+ /* @__PURE__ */ new Set([...CANONICAL_TOOL_NAMES, ...declaredToolNames(text)])
9832
+ ) : substituted;
9833
+ return tidy(guarded);
9834
+ }
9835
+ function declaredToolNames(text) {
9836
+ const names = /* @__PURE__ */ new Set();
9837
+ for (const marker of text.matchAll(/<!--\s*ws:if\b([^>]*?)-->/g)) {
9838
+ for (const attr of (marker[1] ?? "").matchAll(/!?tool=([A-Za-z0-9_.,-]+)/g)) {
9839
+ for (const name of (attr[1] ?? "").split(",")) if (name.trim()) names.add(name.trim());
9840
+ }
9841
+ }
9842
+ for (const placeholder of text.matchAll(/\{\{\s*tools:\s*([^}]+)}}/g)) {
9843
+ for (const name of (placeholder[1] ?? "").split(",")) if (name.trim()) names.add(name.trim());
9844
+ }
9845
+ return names;
9846
+ }
9847
+ function dropLinesWithUnavailableToolReferences(text, ctx, declared) {
9848
+ const unavailable = [...declared].filter((name) => !ctx.toolNames.has(name));
9849
+ if (unavailable.length === 0) return text;
9850
+ return text.split(/(?<=\n)/).filter((line) => !unavailable.some((name) => formattedToolMention(line, name))).join("");
9851
+ }
9852
+ function formattedToolMention(line, name) {
9853
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9854
+ const token = new RegExp(`(?<![\\w-])${escaped}(?![\\w-])`);
9855
+ if (line.split("`").some((segment, index) => {
9856
+ if (index % 2 !== 1) return false;
9857
+ if (segment.includes(`<${name}`) || segment.includes(`</${name}`)) return false;
9858
+ return token.test(segment);
9859
+ })) {
9860
+ return true;
9861
+ }
9862
+ return line.split("**").some((segment, index) => index % 2 === 1 && segment.trim() === name);
9320
9863
  }
9321
9864
  function parse(text) {
9322
9865
  const root = [];
@@ -9401,6 +9944,37 @@ function tidy(text) {
9401
9944
  return text.replace(/(\r?\n){3,}/g, "$1$1");
9402
9945
  }
9403
9946
 
9947
+ // src/types/config/runtime.ts
9948
+ function normalizeTokenSavingTier(val) {
9949
+ if (val === void 0) return "off";
9950
+ if (typeof val === "boolean") return val ? "medium" : "off";
9951
+ if (val === "auto") return "medium";
9952
+ const validTiers = /* @__PURE__ */ new Set([
9953
+ "off",
9954
+ "minimal",
9955
+ "light",
9956
+ "medium",
9957
+ "aggressive"
9958
+ ]);
9959
+ return validTiers.has(val) ? val : "off";
9960
+ }
9961
+ function resolveTokenSavingTier(val, maxContext) {
9962
+ if (val === "auto") {
9963
+ if (typeof maxContext !== "number" || !Number.isFinite(maxContext) || maxContext <= 0) {
9964
+ return "off";
9965
+ }
9966
+ if (maxContext < 32e3) return "medium";
9967
+ if (maxContext < 128e3) return "light";
9968
+ return "minimal";
9969
+ }
9970
+ return normalizeTokenSavingTier(val);
9971
+ }
9972
+
9973
+ // src/types/system-prompt.ts
9974
+ function flattenSystemPromptRegions(regions) {
9975
+ return [...regions.core, ...regions.session, ...regions.volatile];
9976
+ }
9977
+
9404
9978
  // src/core/modes/default.ts
9405
9979
  import { readFileSync as readFileSync6, statSync as statSync3 } from "node:fs";
9406
9980
  import * as path12 from "node:path";
@@ -9795,7 +10369,8 @@ async function buildEnvironment(ctx, env) {
9795
10369
  modelCapabilities?.maxContextTokens ?? 0,
9796
10370
  modelCapabilities?.supportsTools ? 1 : 0,
9797
10371
  modelCapabilities?.supportsVision ? 1 : 0,
9798
- modelCapabilities?.supportsReasoning ? 1 : 0
10372
+ modelCapabilities?.supportsReasoning ? 1 : 0,
10373
+ env.skillCache ?? ""
9799
10374
  ].join("\0");
9800
10375
  const cached = env.envCacheByRoot.get(cacheKey);
9801
10376
  if (cached) {
@@ -9854,7 +10429,7 @@ async function buildEnvironment(ctx, env) {
9854
10429
  lines.push(`- Mode: ${env.modeId}`);
9855
10430
  }
9856
10431
  }
9857
- if (effShell !== "posix" && tier !== "minimal") {
10432
+ if (ctx.tools.some((tool) => tool.name === "bash") && effShell !== "posix" && tier !== "minimal") {
9858
10433
  const guide = shellGuidanceBlock(effShell, tier === "light" ? "short" : "full");
9859
10434
  if (guide) lines.push("", guide);
9860
10435
  }
@@ -10028,7 +10603,7 @@ function foreignProvenanceTag(source, originTool) {
10028
10603
  const origin = source === "foreign" && originTool ? originTool : source;
10029
10604
  return ` [foreign skill: ${origin}]`;
10030
10605
  }
10031
- async function buildProgressiveSkillManifestText(loader) {
10606
+ async function buildProgressiveSkillManifestText(loader, availableToolNames = []) {
10032
10607
  try {
10033
10608
  const entries = await loader.listEntries();
10034
10609
  if (entries.length === 0) return "";
@@ -10038,7 +10613,12 @@ async function buildProgressiveSkillManifestText(loader) {
10038
10613
  "| Skill | Use when |",
10039
10614
  "|---|---|"
10040
10615
  ];
10616
+ const manifests = new Map((await loader.list()).map((manifest) => [manifest.name, manifest]));
10041
10617
  for (const e of entries) {
10618
+ const manifest = manifests.get(e.name);
10619
+ if (manifest && (missingRuntimeCapabilities(manifest.requiredCapabilities, availableToolNames).length > 0 || missingRequiredRuntimeTools(manifest.requiredTools, availableToolNames).length > 0)) {
10620
+ continue;
10621
+ }
10042
10622
  const trigger = (e.trigger ?? "").replace(/\|/g, "\\|").replace(/\n+/g, " ").trim();
10043
10623
  lines.push(`| \`${e.name}\`${foreignProvenanceTag(e.source, e.originTool)} | ${trigger} |`);
10044
10624
  }
@@ -10047,7 +10627,7 @@ async function buildProgressiveSkillManifestText(loader) {
10047
10627
  return "";
10048
10628
  }
10049
10629
  }
10050
- async function buildFullSkillBodiesText(loader, budget = SKILL_LIMITS.EAGER_DEFAULT_MAX_CHARS) {
10630
+ async function buildFullSkillBodiesText(loader, budget = SKILL_LIMITS.EAGER_DEFAULT_MAX_CHARS, availableToolNames = []) {
10051
10631
  try {
10052
10632
  const skills = await loader.list();
10053
10633
  if (skills.length === 0) return "";
@@ -10055,10 +10635,16 @@ async function buildFullSkillBodiesText(loader, budget = SKILL_LIMITS.EAGER_DEFA
10055
10635
  const overflow = [];
10056
10636
  let used = 0;
10057
10637
  for (const s of skills) {
10638
+ if (missingRuntimeCapabilities(s.requiredCapabilities, availableToolNames).length > 0 || missingRequiredRuntimeTools(s.requiredTools, availableToolNames).length > 0) {
10639
+ continue;
10640
+ }
10058
10641
  try {
10059
10642
  const raw = await loader.readBody(s.name);
10060
10643
  const trimmed = stripFrontmatter(raw).trim();
10061
10644
  if (!trimmed) continue;
10645
+ if (missingRequiredRuntimeTools(runtimeToolReferencesFromText(trimmed), availableToolNames).length > 0) {
10646
+ continue;
10647
+ }
10062
10648
  const entry = `## Skill: ${s.name}${foreignProvenanceTag(s.source, s.originTool)}
10063
10649
 
10064
10650
  ${capSkillBody(trimmed)}`;
@@ -10082,7 +10668,7 @@ ${overflow.join("\n")}`;
10082
10668
  return "";
10083
10669
  }
10084
10670
  }
10085
- async function buildCompactSkillBodiesText(loader, budget = SKILL_LIMITS.COMPACT_DEFAULT_MAX_CHARS) {
10671
+ async function buildCompactSkillBodiesText(loader, budget = SKILL_LIMITS.COMPACT_DEFAULT_MAX_CHARS, availableToolNames = []) {
10086
10672
  try {
10087
10673
  const skills = await loader.list();
10088
10674
  if (skills.length === 0) return "";
@@ -10090,10 +10676,16 @@ async function buildCompactSkillBodiesText(loader, budget = SKILL_LIMITS.COMPACT
10090
10676
  const overflow = [];
10091
10677
  let used = 0;
10092
10678
  for (const s of skills) {
10679
+ if (missingRuntimeCapabilities(s.requiredCapabilities, availableToolNames).length > 0 || missingRequiredRuntimeTools(s.requiredTools, availableToolNames).length > 0) {
10680
+ continue;
10681
+ }
10093
10682
  try {
10094
10683
  const saveBody = await loader.readSaveBody(s.name);
10095
10684
  const clean = stripFrontmatter(saveBody).trim();
10096
10685
  if (!clean) continue;
10686
+ if (missingRequiredRuntimeTools(runtimeToolReferencesFromText(clean), availableToolNames).length > 0) {
10687
+ continue;
10688
+ }
10097
10689
  const entry = `## Skill: ${s.name}${foreignProvenanceTag(s.source, s.originTool)}
10098
10690
 
10099
10691
  ${clean}`;
@@ -10191,11 +10783,22 @@ ${memText}`);
10191
10783
  }
10192
10784
  if (mem.skillLoader) {
10193
10785
  if (mem.skillMode === "progressive") {
10194
- skillBodyCache = await buildProgressiveSkillManifestText(mem.skillLoader);
10786
+ skillBodyCache = await buildProgressiveSkillManifestText(
10787
+ mem.skillLoader,
10788
+ mem.catalogTools?.map((tool) => tool.name) ?? []
10789
+ );
10195
10790
  } else if (mem.isCompact) {
10196
- skillBodyCache = await buildCompactSkillBodiesText(mem.skillLoader);
10791
+ skillBodyCache = await buildCompactSkillBodiesText(
10792
+ mem.skillLoader,
10793
+ void 0,
10794
+ mem.catalogTools?.map((tool) => tool.name) ?? []
10795
+ );
10197
10796
  } else {
10198
- skillBodyCache = await buildFullSkillBodiesText(mem.skillLoader, mem.skillEagerMaxChars);
10797
+ skillBodyCache = await buildFullSkillBodiesText(
10798
+ mem.skillLoader,
10799
+ mem.skillEagerMaxChars,
10800
+ mem.catalogTools?.map((tool) => tool.name) ?? []
10801
+ );
10199
10802
  }
10200
10803
  }
10201
10804
  if (skillBodyCache) {
@@ -10282,6 +10885,8 @@ var DefaultSystemPromptBuilder = class {
10282
10885
  skillBodyCache;
10283
10886
  /** Tools from last build — used for memory relevance scoring. */
10284
10887
  _lastBuildTools;
10888
+ /** Full executable catalog used to capability-gate progressive skills. */
10889
+ _lastCatalogTools;
10285
10890
  /** Cached rendered online agents string, keyed by content fingerprint. */
10286
10891
  _lastOnlineAgents;
10287
10892
  /** Cached full buildToolUsage output — keyed by tools array ref + agents fingerprint + tier. */
@@ -10341,12 +10946,26 @@ var DefaultSystemPromptBuilder = class {
10341
10946
  }
10342
10947
  async buildRegions(ctx) {
10343
10948
  this._lastBuildTools = ctx.tools;
10949
+ this._lastCatalogTools = ctx.catalogTools ?? ctx.tools;
10344
10950
  if (this.opts.skillLoader) {
10345
10951
  try {
10346
10952
  const entries = await this.opts.skillLoader.listEntries();
10953
+ const manifests = new Map(
10954
+ (await this.opts.skillLoader.list()).map((manifest) => [manifest.name, manifest])
10955
+ );
10347
10956
  if (entries.length > 0) {
10348
10957
  const lines = [];
10349
10958
  for (const e of entries) {
10959
+ const manifest = manifests.get(e.name);
10960
+ if (manifest && (missingRuntimeCapabilities(
10961
+ manifest.requiredCapabilities,
10962
+ this._lastCatalogTools?.map((tool) => tool.name) ?? []
10963
+ ).length > 0 || missingRequiredRuntimeTools(
10964
+ manifest.requiredTools,
10965
+ this._lastCatalogTools?.map((tool) => tool.name) ?? []
10966
+ ).length > 0)) {
10967
+ continue;
10968
+ }
10350
10969
  const shortTrigger = compactTrigger(e.trigger);
10351
10970
  lines.push(`- **${e.name}** (${shortTrigger})`);
10352
10971
  }
@@ -10371,14 +10990,8 @@ var DefaultSystemPromptBuilder = class {
10371
10990
  const layer5 = await this.buildMode();
10372
10991
  const layer6 = ctx.subagent ? "" : await this.buildActivePlan();
10373
10992
  const core = [
10374
- tagBlock(
10375
- { type: "text", text: layer1, cache_control: { type: "ephemeral" } },
10376
- "identity"
10377
- ),
10378
- tagBlock(
10379
- { type: "text", text: layer2, cache_control: { type: "ephemeral" } },
10380
- "tool-usage"
10381
- )
10993
+ tagBlock({ type: "text", text: layer1, cache_control: { type: "ephemeral" } }, "identity"),
10994
+ tagBlock({ type: "text", text: layer2, cache_control: { type: "ephemeral" } }, "tool-usage")
10382
10995
  ];
10383
10996
  const session = [
10384
10997
  tagBlock(
@@ -10389,18 +11002,12 @@ var DefaultSystemPromptBuilder = class {
10389
11002
  const volatile = [];
10390
11003
  if (layer4.trim()) {
10391
11004
  session.push(
10392
- tagBlock(
10393
- { type: "text", text: layer4, cache_control: { type: "ephemeral" } },
10394
- "skills"
10395
- )
11005
+ tagBlock({ type: "text", text: layer4, cache_control: { type: "ephemeral" } }, "skills")
10396
11006
  );
10397
11007
  }
10398
11008
  if (layer5.trim()) {
10399
11009
  session.push(
10400
- tagBlock(
10401
- { type: "text", text: layer5, cache_control: { type: "ephemeral" } },
10402
- "mode"
10403
- )
11010
+ tagBlock({ type: "text", text: layer5, cache_control: { type: "ephemeral" } }, "mode")
10404
11011
  );
10405
11012
  }
10406
11013
  if (this.opts.modeStore && this.opts.skillLoader) {
@@ -10428,10 +11035,7 @@ var DefaultSystemPromptBuilder = class {
10428
11035
  }
10429
11036
  if (layer6.trim()) {
10430
11037
  volatile.push(
10431
- tagBlock(
10432
- { type: "text", text: layer6, cache_control: { type: "ephemeral" } },
10433
- "plan"
10434
- )
11038
+ tagBlock({ type: "text", text: layer6, cache_control: { type: "ephemeral" } }, "plan")
10435
11039
  );
10436
11040
  }
10437
11041
  if (this.opts.contributors && this.opts.contributors.length > 0) {
@@ -10469,7 +11073,8 @@ var DefaultSystemPromptBuilder = class {
10469
11073
  return {
10470
11074
  toolNames: new Set(ctx.tools.map((t2) => t2.name)),
10471
11075
  tier: this.tier,
10472
- subagent: ctx.subagent === true
11076
+ subagent: ctx.subagent === true,
11077
+ strictToolReferences: true
10473
11078
  };
10474
11079
  }
10475
11080
  /**
@@ -10582,7 +11187,15 @@ ${hint.trim()}`);
10582
11187
  }
10583
11188
  if (this.tier !== "minimal" && this.tier !== "aggressive") {
10584
11189
  const commonPatterns = section("tool.common.patterns");
10585
- if (commonPatterns) lines.push(commonPatterns);
11190
+ if (commonPatterns) {
11191
+ lines.push(
11192
+ renderInstructionLayer(commonPatterns, {
11193
+ toolNames: new Set(tools.map((tool) => tool.name)),
11194
+ tier: this.tier,
11195
+ subagent: ctx.subagent === true
11196
+ })
11197
+ );
11198
+ }
10586
11199
  }
10587
11200
  const hasDelegate = tools.some((t2) => t2.name === "delegate");
10588
11201
  if (hasDelegate) {
@@ -10621,10 +11234,7 @@ ${hint.trim()}`);
10621
11234
  mailSendCommand
10622
11235
  };
10623
11236
  if (this.tier !== "off") {
10624
- const mailbox = section(
10625
- "tool.mailbox.compact",
10626
- mailboxVars
10627
- );
11237
+ const mailbox = section("tool.mailbox.compact", mailboxVars);
10628
11238
  if (mailbox) lines.push(mailbox);
10629
11239
  } else {
10630
11240
  const mailbox = section("tool.mailbox.full", mailboxVars);
@@ -10640,14 +11250,10 @@ ${hint.trim()}`);
10640
11250
  const hasMcpUse = tools.some((t2) => t2.name === "mcp_use");
10641
11251
  if (hasMcpControl) {
10642
11252
  if (this.tier === "minimal" || this.tier === "light" || this.tier === "aggressive") {
10643
- const mcp = section(
10644
- hasMcpUse ? "tool.mcp.compact.use" : "tool.mcp.compact.control"
10645
- );
11253
+ const mcp = section(hasMcpUse ? "tool.mcp.compact.use" : "tool.mcp.compact.control");
10646
11254
  if (mcp) lines.push(mcp);
10647
11255
  } else {
10648
- const mcp = section(
10649
- hasMcpUse ? "tool.mcp.full.use" : "tool.mcp.full.control"
10650
- );
11256
+ const mcp = section(hasMcpUse ? "tool.mcp.full.use" : "tool.mcp.full.control");
10651
11257
  if (mcp) lines.push(mcp);
10652
11258
  }
10653
11259
  }
@@ -10655,17 +11261,12 @@ ${hint.trim()}`);
10655
11261
  if (hasContextManager) {
10656
11262
  if (this.tier === "minimal" || this.tier === "light") {
10657
11263
  } else if (this.tier === "medium") {
10658
- const contextManagement = section(
10659
- "tool.context.management.compact"
10660
- );
11264
+ const contextManagement = section("tool.context.management.compact");
10661
11265
  if (contextManagement) lines.push(contextManagement);
10662
11266
  } else {
10663
11267
  const maxCtx = this.modelCapabilities()?.maxContextTokens ?? 0;
10664
11268
  const threshold = maxCtx <= 32e3 ? "50" : "70";
10665
- const contextManagement = section(
10666
- "tool.context.management.full",
10667
- { threshold }
10668
- );
11269
+ const contextManagement = section("tool.context.management.full", { threshold });
10669
11270
  if (contextManagement) lines.push(contextManagement);
10670
11271
  }
10671
11272
  }
@@ -10704,6 +11305,7 @@ ${hint.trim()}`);
10704
11305
  skillMode: this.opts.skillMode,
10705
11306
  skillEagerMaxChars: this.opts.skillEagerMaxChars,
10706
11307
  lastBuildTools: this._lastBuildTools,
11308
+ catalogTools: this._lastCatalogTools,
10707
11309
  skillBodyCache: this.skillBodyCache
10708
11310
  });
10709
11311
  this.skillBodyCache = result.skillBodyCache;
@@ -10724,7 +11326,9 @@ export {
10724
11326
  DefaultSystemPromptBuilder,
10725
11327
  FallbackProfileManager,
10726
11328
  InputBuilder,
11329
+ MAX_PENDING_NEXT_STEPS,
10727
11330
  buildBtwBlock,
11331
+ clearPendingNextSteps,
10728
11332
  consumeBtwNotes,
10729
11333
  createDefaultPipelines,
10730
11334
  createFallbackModelExtension,
@@ -10732,16 +11336,21 @@ export {
10732
11336
  effectiveFallbackChain,
10733
11337
  fallbackProfileChain,
10734
11338
  formatModelRef,
11339
+ hasNextStepsTag,
10735
11340
  loadInstructionBundle,
11341
+ maybeAppendPendingNextSteps,
10736
11342
  normalizeModelRef,
10737
11343
  parseModelRef,
10738
11344
  pendingBtwCount,
11345
+ readPendingNextSteps,
10739
11346
  renderInstructionLayer,
11347
+ renderNextStepsBlock,
10740
11348
  resolveContinuation,
10741
11349
  runProviderWithRetry,
10742
11350
  setBtwNote,
10743
11351
  setQueuedMessagesSnapshot,
10744
11352
  smartDefaultFallbackChain,
10745
- wrapAsState
11353
+ wrapAsState,
11354
+ writePendingNextSteps
10746
11355
  };
10747
11356
  //# sourceMappingURL=index.js.map