@tangle-network/agent-runtime 0.300.0 → 0.306.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/{activation-DqWmWTyB.js → activation-C5wesfzd.js} +3 -3
  2. package/dist/{activation-DqWmWTyB.js.map → activation-C5wesfzd.js.map} +1 -1
  3. package/dist/{activation-Bhp8dYV5.d.ts → activation-wZ-8vOjV.d.ts} +88 -13
  4. package/dist/agent.d.ts +2 -2
  5. package/dist/agent.js +2 -2
  6. package/dist/{bundle-C9v5TH_H.js → bundle-C8EO05u3.js} +2 -2
  7. package/dist/{bundle-C9v5TH_H.js.map → bundle-C8EO05u3.js.map} +1 -1
  8. package/dist/candidate-execution/index.d.ts +3 -26
  9. package/dist/candidate-execution/index.js +6 -6
  10. package/dist/{coordination-driver-DzTvehd6.js → coordination-driver-yj9HPXef.js} +322 -223
  11. package/dist/coordination-driver-yj9HPXef.js.map +1 -0
  12. package/dist/{delegate-vJ2g76r6.js → delegate-KtrL9HWq.js} +5 -5
  13. package/dist/delegate-KtrL9HWq.js.map +1 -0
  14. package/dist/durable.d.ts +3 -15
  15. package/dist/durable.js +158 -124
  16. package/dist/durable.js.map +1 -1
  17. package/dist/{graph-BDQh3qry.js → graph-DcdyHbgf.js} +5 -6
  18. package/dist/graph-DcdyHbgf.js.map +1 -0
  19. package/dist/{improvement-cycle-D6cPLi6k.js → improvement-cycle-DsN-TVXR.js} +1129 -10
  20. package/dist/improvement-cycle-DsN-TVXR.js.map +1 -0
  21. package/dist/{index-sC6sl88d.d.ts → index-DflDZq_I.d.ts} +188 -748
  22. package/dist/index.d.ts +378 -12
  23. package/dist/index.js +223 -320
  24. package/dist/index.js.map +1 -1
  25. package/dist/intelligence.d.ts +5 -5
  26. package/dist/intelligence.js +7 -8
  27. package/dist/intelligence.js.map +1 -1
  28. package/dist/kernel.d.ts +4 -4
  29. package/dist/kernel.js +9 -9
  30. package/dist/{loop-runner-bin-DZglq4Cf.d.ts → loop-runner-bin-DZfRUVWy.d.ts} +3 -3
  31. package/dist/{loop-runner-bin-DQIHnCL0.js → loop-runner-bin-rVrSX5l1.js} +3 -3
  32. package/dist/{loop-runner-bin-DQIHnCL0.js.map → loop-runner-bin-rVrSX5l1.js.map} +1 -1
  33. package/dist/loop-runner-bin.d.ts +1 -1
  34. package/dist/loop-runner-bin.js +1 -1
  35. package/dist/mcp/bin.js +3 -3
  36. package/dist/mcp/index.d.ts +6 -35
  37. package/dist/mcp/index.js +311 -364
  38. package/dist/mcp/index.js.map +1 -1
  39. package/dist/model-policy-DydyWTQg.js.map +1 -1
  40. package/dist/{openai-tools-DvR82zEo.d.ts → openai-tools-YO30D2s3.d.ts} +2 -2
  41. package/dist/{prepare-CGVNMb1o.js → prepare--nWcazPr.js} +7 -13
  42. package/dist/prepare--nWcazPr.js.map +1 -0
  43. package/dist/{profile-BetOB4_Q.js → profile-h04LDjV6.js} +2 -2
  44. package/dist/{profile-BetOB4_Q.js.map → profile-h04LDjV6.js.map} +1 -1
  45. package/dist/profiles.d.ts +2 -94
  46. package/dist/profiles.js +2 -451
  47. package/dist/profiles.js.map +1 -1
  48. package/dist/{protected-model-port-CAa0t1MT.d.ts → protected-model-port-C9QtEf3y.d.ts} +2 -2
  49. package/dist/{protected-model-port-CuVHkhti.js → protected-model-port-wwalbyXM.js} +4 -4
  50. package/dist/{protected-model-port-CuVHkhti.js.map → protected-model-port-wwalbyXM.js.map} +1 -1
  51. package/dist/{provision-supervisor-IXBlLHE0.js → provision-supervisor-WxFEFQeJ.js} +14 -24
  52. package/dist/provision-supervisor-WxFEFQeJ.js.map +1 -0
  53. package/dist/{runtime-DpEt5v42.js → runtime-B9eBIyws.js} +86 -496
  54. package/dist/runtime-B9eBIyws.js.map +1 -0
  55. package/dist/{search-method-DvU-fc-f.js → search-executor-2tWr38Qw.js} +239 -1132
  56. package/dist/search-executor-2tWr38Qw.js.map +1 -0
  57. package/dist/{server-CIE8s4AN.js → server-CpKcrOPj.js} +5 -12
  58. package/dist/server-CpKcrOPj.js.map +1 -0
  59. package/dist/{stream-agent-turn-I3bb0eqF.d.ts → stream-agent-turn-B1giFgOg.d.ts} +4 -2
  60. package/dist/{substrate-UjsxukjZ.d.ts → substrate-L_sC7jCD.d.ts} +2 -2
  61. package/dist/{supervise-C5mktzrh.js → supervise-DllOHM4M.js} +206 -80
  62. package/dist/supervise-DllOHM4M.js.map +1 -0
  63. package/dist/{supervisor-CQSu5XGl.js → supervisor-gCfG6K1-.js} +1130 -1667
  64. package/dist/supervisor-gCfG6K1-.js.map +1 -0
  65. package/dist/testing.d.ts +2 -2
  66. package/dist/testing.js +14 -14
  67. package/dist/tui/index.d.ts +1 -1
  68. package/dist/tui/index.js +1 -1
  69. package/dist/{types-C73caP0U.d.ts → types-DeuEkBhI.d.ts} +167 -261
  70. package/dist/{workspace-archive-pz8YJ1fr.d.ts → workspace-streams-CpSmt3zx.d.ts} +67 -3
  71. package/dist/{workspace-streams-D5gHJdTb.js → workspace-streams-ILw0eWZ0.js} +694 -2
  72. package/dist/workspace-streams-ILw0eWZ0.js.map +1 -0
  73. package/package.json +8 -8
  74. package/skills/generate-eval/SKILL.md +1 -1
  75. package/skills/profile-authoring/SKILL.md +5 -5
  76. package/skills/supervise/SKILL.md +2 -2
  77. package/dist/coordination-driver-DzTvehd6.js.map +0 -1
  78. package/dist/delegate-vJ2g76r6.js.map +0 -1
  79. package/dist/graph-BDQh3qry.js.map +0 -1
  80. package/dist/improvement-cycle-D6cPLi6k.js.map +0 -1
  81. package/dist/prepare-CGVNMb1o.js.map +0 -1
  82. package/dist/provision-supervisor-IXBlLHE0.js.map +0 -1
  83. package/dist/runtime-DpEt5v42.js.map +0 -1
  84. package/dist/search-method-DvU-fc-f.js.map +0 -1
  85. package/dist/server-CIE8s4AN.js.map +0 -1
  86. package/dist/supervise-C5mktzrh.js.map +0 -1
  87. package/dist/supervisor-CQSu5XGl.js.map +0 -1
  88. package/dist/workspace-streams-D5gHJdTb.js.map +0 -1
@@ -1,5 +1,6 @@
1
- import { $i as detachedFrozen, $n as parseWorkerToolTraceArtifact, Aa as addResourceSpend, Ai as prepareJsonlAppend, Ar as contentAddress, Bn as InMemoryResultBlobStore, Da as unmeteredSpend, E as failedItems, It as createRouterTranscript, Lr as resolveRedactor, M as withDriverExecutor, Ma as withBudgetResources, Mi as writeAllBytes, Qt as createPeerMailbox, Rn as FileResultBlobStore, Tn as runBrainLoop, Vi as providerAttemptEvidence, Vn as InMemorySpawnJournal, _ as CheckUnavailableError, c as runTree, er as workerTraceAnalysisStore, ft as resolveSpawnResourcePaths, ga as chargedTokens, i as bestDelivered, ir as assertValidBudget, ji as readCommittedJsonLines, jt as createExecutorRegistry, nn as isLiveNodeStatus, o as pickBestDelivered, ot as scopeRetainedOwnerResourceReader, qt as createInbox, rn as isTerminalNodeStatus, s as runFinalizer, ur as errMessage, x as checkVerdictOf, xa as promptCacheTokenClasses, z as meterRuntimeOwnedProviderAttempt, zn as FileSpawnJournal } from "./supervisor-CQSu5XGl.js";
1
+ import { Di as providerAttemptEvidence, Dn as FileSpawnJournal, Dr as resolveRedactor, Dt as createRouterTranscript, En as FileResultBlobStore, Fn as prepareJsonlAppend, Hn as workerTraceAnalysisStore, In as readCommittedJsonLines, Jt as isTerminalNodeStatus, Kn as assertValidBudget, Ln as writeAllBytes, On as InMemoryResultBlobStore, R as meterRuntimeOwnedProviderAttempt, Rt as createInbox, St as createExecutorRegistry, T as failedItems, Ut as createPeerMailbox, Vn as parseWorkerToolTraceArtifact, _ as CheckUnavailableError, br as contentAddress, c as runTree, er as errMessage, et as scopeRetainedOwnerResourceReader, i as bestDelivered, j as withDriverExecutor, kn as InMemorySpawnJournal, o as pickBestDelivered, pn as runBrainLoop, qt as isLiveNodeStatus, s as runFinalizer, st as resolveSpawnResourcePaths, x as checkVerdictOf, zi as detachedFrozen } from "./supervisor-gCfG6K1-.js";
2
2
  import { f as RuntimeRunStateError, m as ValidationError } from "./errors-DodWX-cb.js";
3
+ import { V as chargedTokens, Z as promptCacheTokenClasses, at as unmeteredSpend, ft as withBudgetResources, lt as addResourceSpend } from "./workspace-streams-ILw0eWZ0.js";
3
4
  import { A as writeWorkerCancellation, M as writeWorkerSteerAcknowledgement, b as workerCancellationsDir, c as readWorkerCancelRequests, d as readWorkerSteerRequests, k as writeRunCancellation, l as readWorkerCancellation, o as readRunCancelRequest, r as claimWorkerSteerDelivery, s as readRunCancellation, u as readWorkerSteerAcknowledgement } from "./run-layout-DSCWdbX2.js";
4
5
  import { agentProfileSchema, canonicalAgentProfileDigest, canonicalCandidateDigest } from "@tangle-network/agent-interface";
5
6
  import { argHash, errorStreakDetector, observeAll, repeatedActionDetector } from "@tangle-network/agent-eval";
@@ -7,7 +8,6 @@ import { randomUUID } from "node:crypto";
7
8
  import { mkdirSync, readFileSync, watch } from "node:fs";
8
9
  import { join } from "node:path";
9
10
  import { registry } from "zod";
10
- import { AsyncLocalStorage } from "node:async_hooks";
11
11
  //#region src/runtime/supervise/detector-monitor.ts
12
12
  /**
13
13
  *
@@ -57,6 +57,7 @@ function createEventBus(now = Date.now) {
57
57
  const queue = [];
58
58
  const log = [];
59
59
  const subscribers = [];
60
+ const queuedHandlers = [];
60
61
  const byKind = {};
61
62
  const staged = /* @__PURE__ */ new WeakMap();
62
63
  let seq = 0;
@@ -97,6 +98,7 @@ function createEventBus(now = Date.now) {
97
98
  log.push(record);
98
99
  published += 1;
99
100
  byKind[event.type] = (byKind[event.type] ?? 0) + 1;
101
+ if (opts?.queue !== false) for (const handler of [...queuedHandlers]) handler(record);
100
102
  return record;
101
103
  },
102
104
  pull(kinds) {
@@ -113,6 +115,13 @@ function createEventBus(now = Date.now) {
113
115
  if (i >= 0) subscribers.splice(i, 1);
114
116
  };
115
117
  },
118
+ onQueued(handler) {
119
+ queuedHandlers.push(handler);
120
+ return () => {
121
+ const i = queuedHandlers.indexOf(handler);
122
+ if (i >= 0) queuedHandlers.splice(i, 1);
123
+ };
124
+ },
116
125
  pending(kinds) {
117
126
  return kinds ? queue.filter((r) => matches(r, kinds)).length : queue.length;
118
127
  },
@@ -129,53 +138,6 @@ function createEventBus(now = Date.now) {
129
138
  };
130
139
  }
131
140
  //#endregion
132
- //#region src/mcp/tools/coordination-request-context.ts
133
- /**
134
- * The header a harness sends to a remote MCP server whose tool-call timeout it
135
- * raised, carrying the applied timeout in milliseconds. The Tangle profile
136
- * materializer writes it beside `MCP_TOOL_TIMEOUT` (Claude Code) and
137
- * `tool_timeout_sec` (Codex) when an attachment declares
138
- * `metadata.toolTimeoutMs`. Without it the server must assume the harness
139
- * default, 60 s for Claude Code 2.1.287 and Codex (measured 2026-10-03).
140
- */
141
- const MCP_TOOL_TIMEOUT_HEADER = "x-mcp-tool-timeout-ms";
142
- /** The tool-call timeout Runtime asks harnesses to give its coordination server. */
143
- const COORDINATION_CLIENT_TOOL_TIMEOUT_MS = 3e5;
144
- /**
145
- * The longest `await_event` holds for a caller that declared a raised timeout.
146
- * A held response through the Sandbox edge was measured to 200 s on 2026-10-03.
147
- */
148
- const MAX_DECLARED_AWAIT_FENCE_MS = 18e4;
149
- const MIN_DECLARED_TOOL_TIMEOUT_MS = 1e3;
150
- const MAX_DECLARED_TOOL_TIMEOUT_MS = 36e5;
151
- const requestContext = new AsyncLocalStorage();
152
- /**
153
- * The tool-call timeout a request declared through {@link MCP_TOOL_TIMEOUT_HEADER},
154
- * or `undefined` when it is absent or malformed. A malformed value is treated as
155
- * absent: the caller then gets the default fence, which every harness tolerates.
156
- */
157
- function declaredToolTimeoutMs(header) {
158
- if (typeof header !== "string" || !/^[0-9]{1,8}$/.test(header)) return void 0;
159
- const value = Number(header);
160
- return value >= MIN_DECLARED_TOOL_TIMEOUT_MS && value <= MAX_DECLARED_TOOL_TIMEOUT_MS ? value : void 0;
161
- }
162
- /**
163
- * How long `await_event` may hold for a caller that declared `declaredMs`: 60% of
164
- * it, so the answer leaves well before the client gives up, and at most
165
- * {@link MAX_DECLARED_AWAIT_FENCE_MS}.
166
- */
167
- function declaredAwaitFenceMs(declaredMs) {
168
- return Math.min(MAX_DECLARED_AWAIT_FENCE_MS, Math.floor(declaredMs * .6));
169
- }
170
- /** Run one coordination request with the fence its caller declared, if any. */
171
- function withCoordinationAwaitFence(awaitFenceMs, run) {
172
- return awaitFenceMs === void 0 ? run() : requestContext.run({ awaitFenceMs }, run);
173
- }
174
- /** The fence the current coordination request declared, or `undefined`. */
175
- function coordinationRequestAwaitFenceMs() {
176
- return requestContext.getStore()?.awaitFenceMs;
177
- }
178
- //#endregion
179
141
  //#region src/mcp/tools/worker-output.ts
180
142
  const WORKER_OUTPUT_PAGE_CHARS = 16384;
181
143
  function workerOutputReadOptions(raw) {
@@ -532,13 +494,6 @@ function emptyPreflightCounts() {
532
494
  "unmountable-tool": 0
533
495
  };
534
496
  }
535
- /** Default ceiling for a single `await_event` block (ms). Every `pending` return costs the driver an
536
- * inference turn, so the ceiling is as long as the harness clients allow: Claude Code 2.1.287 and
537
- * Codex both time out an MCP tool call at 60 s by default (measured 2026-10-03: a 150 s HTTP MCP
538
- * call failed at about 60 s and passed with `MCP_TOOL_TIMEOUT=300000`), and the Sandbox edge held
539
- * a 200 s response. 45 s leaves 15 s of that minute for transfer and the driver's own latency. At
540
- * 15 s a Discovery root director spent 303 of its 401 native tool calls (76%) re-polling. */
541
- const DEFAULT_AWAIT_EVENT_TIMEOUT_MS = 45e3;
542
497
  /** Default and maximum bounds for one {@link CoordinationTools} journal read. The defaults are
543
498
  * sized for a driver turn (a page a model can read and still have room to act on); the maxima
544
499
  * bound what a single tool result may cost even when the caller asks for everything. */
@@ -563,7 +518,9 @@ const journalEventKinds = [
563
518
  "escalation",
564
519
  "analyst-defined",
565
520
  "analyst-assignment",
566
- "acknowledgement"
521
+ "acknowledgement",
522
+ "tool-outcome",
523
+ "lead-message"
567
524
  ];
568
525
  /** The reserved coordination verb names — the complete set `createCoordinationTools` can emit
569
526
  * (the analyst pair is conditional but still reserved). A driver's extra WORK tools must not
@@ -582,7 +539,6 @@ const coordinationVerbNames = [
582
539
  "cancel_worker",
583
540
  "observe_agent",
584
541
  "steer_agent",
585
- "await_event",
586
542
  "list_questions",
587
543
  "answer_question",
588
544
  "ask_parent",
@@ -596,22 +552,18 @@ const coordinationVerbNames = [
596
552
  "define_analyst"
597
553
  ];
598
554
  /**
599
- * The `CoordinationEvent` kinds a driver may name in `await_event`. The pull queue carries the
600
- * UP-leg only: `steer` / `answer` / `instruction` / `delivery-attempt` are recorded `queue: false`
601
- * (history and subscribers, never pulled back), and `mail` is delivered to its addressee's inbox.
602
- *
603
- * Declared once because the tool advertises this list in its JSON Schema AND filters on it at
604
- * dispatch. Written twice, the two drift and the schema promises a kind the filter drops — a
605
- * driver then blocks on a queue that already holds its event.
555
+ * The `CoordinationEvent` kinds that wake a waiting manager. The queue carries the UP-leg only:
556
+ * `steer` / `answer` / `instruction` / `delivery-attempt` are recorded `queue: false` (history and
557
+ * subscribers, never delivered back), and `mail` is delivered to its addressee's inbox. A
558
+ * `question` on this bus is the manager's own `ask_parent`, already escalated: it never wakes the
559
+ * asker, whose answer arrives as a `lead-message`.
606
560
  */
607
- const awaitableEventKinds = [
561
+ const wakeEventKinds = [
608
562
  "settled",
609
- "question",
610
- "finding"
563
+ "finding",
564
+ "tool-outcome",
565
+ "lead-message"
611
566
  ];
612
- function isAwaitableEventKind(value) {
613
- return awaitableEventKinds.includes(value);
614
- }
615
567
  const idArg = {
616
568
  type: "string",
617
569
  description: "The workerId returned by spawn_worker."
@@ -1007,6 +959,10 @@ function createCoordinationToolsForManager(opts, lifetime) {
1007
959
  return readyInFlight;
1008
960
  };
1009
961
  const deliveries = [];
962
+ const openReceipts = /* @__PURE__ */ new Map();
963
+ let receiptSeq = 0;
964
+ let leadPublishing = 0;
965
+ let deadlineNoticeDelivered = false;
1010
966
  let driverAttempt;
1011
967
  let journalReadTo = 0;
1012
968
  let lastRejection;
@@ -1338,7 +1294,16 @@ function createCoordinationToolsForManager(opts, lifetime) {
1338
1294
  }, { analyst: route.kind });
1339
1295
  } catch {}
1340
1296
  };
1297
+ let flushing = 0;
1341
1298
  const flushPendingSettlement = async () => {
1299
+ flushing += 1;
1300
+ try {
1301
+ return await flushPendingSettlementOnce();
1302
+ } finally {
1303
+ flushing -= 1;
1304
+ }
1305
+ };
1306
+ const flushPendingSettlementOnce = async () => {
1342
1307
  const pending = pendingSettlement;
1343
1308
  if (!pending) return false;
1344
1309
  await bus.publish(pending.event);
@@ -1604,6 +1569,14 @@ function createCoordinationToolsForManager(opts, lifetime) {
1604
1569
  type: "acknowledgement",
1605
1570
  ...ev.acknowledgement
1606
1571
  };
1572
+ if (ev.type === "tool-outcome") return {
1573
+ type: "tool-outcome",
1574
+ ...ev.outcome
1575
+ };
1576
+ if (ev.type === "lead-message") return {
1577
+ type: "lead-message",
1578
+ ...ev.message
1579
+ };
1607
1580
  return {
1608
1581
  type: ev.type,
1609
1582
  ...ev.down
@@ -1906,21 +1879,26 @@ function createCoordinationToolsForManager(opts, lifetime) {
1906
1879
  const received = new Set(ledger.map((worker) => worker.id));
1907
1880
  const nodes = opts.scope.view?.nodes ?? [];
1908
1881
  return {
1909
- running: nodes.filter((node) => isLiveNodeStatus(node.status) && node.status !== "waiting"),
1882
+ running: nodes.filter((node) => isLiveNodeStatus(node.status)),
1910
1883
  unqueued: nodes.filter((node) => !isLiveNodeStatus(node.status) && !received.has(node.id) && !flushedAnalystRuns.has(node.id)),
1911
- queued: bus.queued(["settled", "finding"])
1884
+ queued: bus.queued([
1885
+ "settled",
1886
+ "finding",
1887
+ "lead-message"
1888
+ ])
1912
1889
  };
1913
1890
  };
1914
1891
  const openWorkRefusal = (verb) => {
1892
+ if (deadlineNoticeDelivered) return void 0;
1915
1893
  const { running, unqueued, queued } = openWork();
1916
1894
  if (running.length === 0 && unqueued.length === 0 && queued.length === 0) return void 0;
1917
1895
  const parts = [];
1918
1896
  if (running.length > 0) parts.push(`${running.length} worker${running.length === 1 ? " is" : "s are"} still running (${running.map((node) => node.id).join(", ")})`);
1919
1897
  const waiting = unqueued.length + queued.length;
1920
- if (waiting > 0) parts.push(`${waiting} event${waiting === 1 ? " is" : "s are"} waiting for you in await_event`);
1898
+ if (waiting > 0) parts.push(`${waiting} event${waiting === 1 ? " is" : "s are"} waiting for you`);
1921
1899
  return {
1922
1900
  error: "open-work",
1923
- reason: `${parts.join(" and ")}. Call await_event until it returns idle, read each settled worker's output, and then call ${verb} again. Nothing was ${verb === "stop" ? "stopped" : "checked"}.`,
1901
+ reason: `${parts.join(" and ")}. End your turn to wait: you will be woken with each result. Read each settled worker's output, and then call ${verb} again. Nothing was ${verb === "stop" ? "stopped" : "checked"}.`,
1924
1902
  running: running.map((node) => ({
1925
1903
  id: node.id,
1926
1904
  label: node.label,
@@ -1994,7 +1972,6 @@ function createCoordinationToolsForManager(opts, lifetime) {
1994
1972
  unsub();
1995
1973
  } catch {}
1996
1974
  };
1997
- const awaitTimeoutMs = opts.awaitTimeoutMs ?? 45e3;
1998
1975
  let inFlightDrain = null;
1999
1976
  const ensureDrain = () => {
2000
1977
  if (!inFlightDrain) inFlightDrain = drainSettlement().finally(() => {
@@ -2045,28 +2022,104 @@ function createCoordinationToolsForManager(opts, lifetime) {
2045
2022
  });
2046
2023
  return managerAnalysis;
2047
2024
  };
2048
- const awaitFenceMs = () => {
2049
- const declared = coordinationRequestAwaitFenceMs();
2050
- return declared !== void 0 && awaitTimeoutMs > 0 ? declared : awaitTimeoutMs;
2025
+ const settledWithoutDrain = async () => {
2026
+ if (inFlightDrain) return;
2027
+ inFlightDrain = (async () => {
2028
+ let drained = false;
2029
+ while (await drainSettlement("nextResolved")) drained = true;
2030
+ return drained;
2031
+ })().finally(() => {
2032
+ inFlightDrain = null;
2033
+ });
2034
+ await inFlightDrain;
2035
+ };
2036
+ const hasOpenWork = () => {
2037
+ const { running, unqueued, queued } = openWork();
2038
+ return running.length > 0 || unqueued.length > 0 || queued.length > 0 || bus.pending(wakeEventKinds) > 0 || openReceipts.size > 0 || leadPublishing > 0 || pendingSettlement !== void 0 || flushing > 0;
2051
2039
  };
2052
- const raceDrainWithTimeout = async (drain) => {
2053
- const fenceMs = awaitFenceMs();
2054
- if (fenceMs <= 0) return { drained: await drain };
2040
+ const awaitWake = async (input) => {
2041
+ const startedAt = Date.now();
2042
+ await ready();
2043
+ await settledWithoutDrain();
2044
+ if (!hasOpenWork() || input.signal.aborted) return void 0;
2045
+ const heartbeatAt = startedAt + Math.max(0, input.heartbeatMs);
2046
+ const deadlineAt = input.deadlineAt ?? Number.POSITIVE_INFINITY;
2047
+ const wakeAt = Math.min(heartbeatAt, deadlineAt);
2048
+ let unsubscribe;
2055
2049
  let timer;
2056
- const timeout = new Promise((resolve) => {
2057
- timer = setTimeout(() => resolve(void 0), fenceMs);
2058
- if (typeof timer?.unref === "function") timer.unref();
2059
- });
2050
+ let onAbort;
2051
+ let outsideWake = false;
2060
2052
  try {
2061
- return await Promise.race([drain.then((drained) => ({ drained })), timeout]);
2053
+ const queuedEvent = new Promise((resolve) => {
2054
+ unsubscribe = bus.onQueued((record) => {
2055
+ if (wakeEventKinds.includes(record.event.type)) resolve();
2056
+ });
2057
+ });
2058
+ const elapsed = new Promise((resolve) => {
2059
+ timer = setTimeout(resolve, Math.max(0, wakeAt - Date.now()));
2060
+ });
2061
+ const aborted = new Promise((resolve) => {
2062
+ onAbort = () => resolve();
2063
+ input.signal.addEventListener("abort", onAbort, { once: true });
2064
+ });
2065
+ const outside = (input.alsoWakeOn ?? new Promise(() => void 0)).then(() => {
2066
+ outsideWake = true;
2067
+ });
2068
+ let cursorIdle = false;
2069
+ while (bus.pending(wakeEventKinds) === 0 && !outsideWake && !input.signal.aborted && Date.now() < wakeAt) {
2070
+ const drained = !cursorIdle && openWork().running.length > 0 ? ensureDrain() : new Promise(() => void 0);
2071
+ if (await Promise.race([
2072
+ drained.then((value) => value ? "drained" : "idle"),
2073
+ queuedEvent.then(() => "queued"),
2074
+ elapsed.then(() => "elapsed"),
2075
+ aborted.then(() => "aborted"),
2076
+ outside.then(() => "outside")
2077
+ ]) === "idle") {
2078
+ cursorIdle = true;
2079
+ if (!hasOpenWork()) break;
2080
+ }
2081
+ }
2082
+ if (input.signal.aborted) return void 0;
2083
+ if (flushing > 0 && inFlightDrain !== null) await inFlightDrain.catch(() => false);
2084
+ if (bus.pending(wakeEventKinds) > 0 && input.debounceMs > 0 && openWork().running.length > 0) {
2085
+ await Promise.race([new Promise((resolve) => {
2086
+ setTimeout(resolve, input.debounceMs);
2087
+ }), aborted]);
2088
+ if (input.signal.aborted) return void 0;
2089
+ await settledWithoutDrain();
2090
+ }
2062
2091
  } finally {
2092
+ unsubscribe?.();
2063
2093
  if (timer) clearTimeout(timer);
2094
+ if (onAbort) input.signal.removeEventListener("abort", onAbort);
2064
2095
  }
2096
+ const events = [];
2097
+ for (let next = bus.pullRecord(wakeEventKinds); next; next = bus.pullRecord(wakeEventKinds)) {
2098
+ deliveries.push({
2099
+ record: next,
2100
+ attempt: input.attempt ?? driverAttempt,
2101
+ acknowledged: false
2102
+ });
2103
+ events.push({
2104
+ ...projectEvent(next.event),
2105
+ eventSeq: next.seq
2106
+ });
2107
+ }
2108
+ if (events.length === 0 && !outsideWake && !hasOpenWork()) return void 0;
2109
+ const reason = events.length > 0 || outsideWake ? "events" : Date.now() >= deadlineAt ? "deadline" : "heartbeat";
2110
+ if (reason === "deadline") deadlineNoticeDelivered = true;
2111
+ return detachedFrozen({
2112
+ reason,
2113
+ events,
2114
+ live: liveSnapshot(),
2115
+ freeSlots: freeWorkerSlots(),
2116
+ idleMs: Date.now() - startedAt
2117
+ });
2065
2118
  };
2066
2119
  const tools = [
2067
2120
  {
2068
2121
  name: "spawn_worker",
2069
- description: "Start a worker the driver will drive. `profile` is the worker or another driver; `task` is what it should do. Reserves budget from the conserved pool and fails closed. Pass an optional `budget` (per-field) to give a sub-task more or less than the default — it merges over the per-worker default; the conserved pool is still the hard fence. Each worker reserves its whole budget when it starts, so the pool divided by the per-worker budget is how many run at once: to run a wide team at once, give each worker less. When every worker slot is busy, or the pool cannot cover the budget until your running workers return what they hold, the worker is admitted with `status: \"queued\"` and starts on its own when a slot and its budget free — it is never refused for concurrency, so spawn all the work you want done and await_event for the results. A queued worker whose budget never frees settles down as budget-exhausted. Pass a `key` naming the assignment to make it run-once ACROSS restarts: a key that already completed returns the finished result (`resumed: \"completed\"` — no work re-runs, nothing is spent), a key whose prior attempt failed (`down`) spawns fresh and says so (`resumed: \"retried\"`), and a key the process died with IN FLIGHT retries under the same key when the executor provably died with the process (an inline worker — the same `resumed: \"retried\"`, naming the interruption). A key with no terminal receipt whose execution may still exist elsewhere (sandbox, CLI bridge, router) is refused (`error: \"in-doubt\"`) until its exact prior execution is recovered. A key still running is refused (`error: \"duplicate-key\"`). Returns `freeSlots`: how many MORE workers start at once (`null` = no slot bound), and `queued`: how many of your workers wait for a slot. Parallel workers finish the run sooner than one at a time." + (profileTable === void 0 ? "" : " This run has a profiles table: pass `profile` as one of the table's names to run that exact profile, or write a new profile under a name the table does not hold."),
2122
+ description: "Start a worker the driver will drive. `profile` is the worker or another driver; `task` is what it should do. Reserves budget from the conserved pool and fails closed. Pass an optional `budget` (per-field) to give a sub-task more or less than the default — it merges over the per-worker default; the conserved pool is still the hard fence. Each worker reserves its whole budget when it starts, so the pool divided by the per-worker budget is how many run at once: to run a wide team at once, give each worker less. When every worker slot is busy, or the pool cannot cover the budget until your running workers return what they hold, the worker is admitted with `status: \"queued\"` and starts on its own when a slot and its budget free — it is never refused for concurrency, so spawn all the work you want done, then end your turn: you are woken with each result. A queued worker whose budget never frees settles down as budget-exhausted. Pass a `key` naming the assignment to make it run-once ACROSS restarts: a key that already completed returns the finished result (`resumed: \"completed\"` — no work re-runs, nothing is spent), a key whose prior attempt failed (`down`) spawns fresh and says so (`resumed: \"retried\"`), and a key the process died with IN FLIGHT retries under the same key when the executor provably died with the process (an inline worker — the same `resumed: \"retried\"`, naming the interruption). A key with no terminal receipt whose execution may still exist elsewhere (sandbox, CLI bridge, router) is refused (`error: \"in-doubt\"`) until its exact prior execution is recovered. A key still running is refused (`error: \"duplicate-key\"`). Returns `freeSlots`: how many MORE workers start at once (`null` = no slot bound), and `queued`: how many of your workers wait for a slot. Parallel workers finish the run sooner than one at a time." + (profileTable === void 0 ? "" : " This run has a profiles table: pass `profile` as one of the table's names to run that exact profile, or write a new profile under a name the table does not hold."),
2070
2123
  inputSchema: {
2071
2124
  type: "object",
2072
2125
  properties: {
@@ -2087,7 +2140,7 @@ function createCoordinationToolsForManager(opts, lifetime) {
2087
2140
  },
2088
2141
  successorOf: {
2089
2142
  type: "string",
2090
- description: "The workerId of a SETTLED worker you spawned that this new worker replaces — for example one that failed, stalled, or finished with a weak result, restarted with a changed profile or task. The run record then names the predecessor on the new worker. Refused for an id you did not spawn (`error: \"successor-unknown\"`) and for a worker still live (`error: \"successor-live\"` — await_event until it settles). Put what the successor needs from its predecessor in `task`."
2143
+ description: "The workerId of a SETTLED worker you spawned that this new worker replaces — for example one that failed, stalled, or finished with a weak result, restarted with a changed profile or task. The run record then names the predecessor on the new worker. Refused for an id you did not spawn (`error: \"successor-unknown\"`) and for a worker still live (`error: \"successor-live\"` — end your turn until it settles). Put what the successor needs from its predecessor in `task`."
2091
2144
  },
2092
2145
  budget: {
2093
2146
  type: "object",
@@ -2187,7 +2240,7 @@ function createCoordinationToolsForManager(opts, lifetime) {
2187
2240
  };
2188
2241
  if (isLiveNodeStatus(predecessor.status)) return {
2189
2242
  error: "successor-live",
2190
- reason: `worker '${successorOf}' has not settled — await_event until it does, then spawn its successor (steer_agent redirects a live worker instead)`,
2243
+ reason: `worker '${successorOf}' has not settled — end your turn until it does, then spawn its successor (steer_agent redirects a live worker instead)`,
2191
2244
  live: liveWorkerCount(),
2192
2245
  freeSlots: freeWorkerSlots()
2193
2246
  };
@@ -2293,7 +2346,7 @@ function createCoordinationToolsForManager(opts, lifetime) {
2293
2346
  },
2294
2347
  {
2295
2348
  name: "observe_agent",
2296
- description: "Inspect a worker WHILE IT RUNS, not only after it finishes: status, spend so far, and `progress` — how long since it last did anything (`idleMs`), whether that counts as stalled, how many turns it has taken, the last tools/files it touched (`recentActivity`), what its executor CHANGED about the profile you gave it (`derived` — an MCP config it materialized, an extension it had to add), whether a steer can even reach it (`steerable`), and how many steers it has not yet read (`pendingMessages`). For a worker that leads its own team, `progress.team` counts the agents below it (working, queued, done, down) and its idle clock reads the newest activity anywhere in that team, so a lead whose workers are busy is not stalled. A queued worker waits for a worker slot and is never stalled. A worker whose executor has FINISHED but whose settlement you have not yet drained reports `settlementPending` with its terminal kind; its `status` still reads running until await_event delivers it, so call await_event, not observe_agent again. The settled output artifact is returned once drained. Select a field with `outputPath`, for example [\"content\"] to read a provider result without its event history. Omit it to read the complete artifact. Large outputs return a bounded JSON `outputPage`; repeat with `outputOffset: outputPage.nextOffset` until it is null, concatenate page text, then parse the JSON. Offsets and limits count UTF-16 characters. The full artifact stays retained; an outRef is a content address, not a workspace path. Use this BEFORE steer_agent: a steer is only worth sending when the progress says the worker is on the wrong path or has stopped making any.",
2349
+ description: "Inspect a worker WHILE IT RUNS, not only after it finishes: status, spend so far, and `progress` — how long since it last did anything (`idleMs`), whether that counts as stalled, how many turns it has taken, the last tools/files it touched (`recentActivity`), what its executor CHANGED about the profile you gave it (`derived` — an MCP config it materialized, an extension it had to add), whether a steer can even reach it (`steerable`), and how many steers it has not yet read (`pendingMessages`). For a worker that leads its own team, `progress.team` counts the agents below it (working, queued, done, down) and its idle clock reads the newest activity anywhere in that team, so a lead whose workers are busy is not stalled. A queued worker waits for a worker slot and is never stalled. A worker whose executor has FINISHED but whose settlement you have not yet drained reports `settlementPending` with its terminal kind; its `status` still reads running until your next wake delivers it, so end your turn rather than observing again. The settled output artifact is returned once drained. Select a field with `outputPath`, for example [\"content\"] to read a provider result without its event history. Omit it to read the complete artifact. Large outputs return a bounded JSON `outputPage`; repeat with `outputOffset: outputPage.nextOffset` until it is null, concatenate page text, then parse the JSON. Offsets and limits count UTF-16 characters. The full artifact stays retained; an outRef is a content address, not a workspace path. Use this BEFORE steer_agent: a steer is only worth sending when the progress says the worker is on the wrong path or has stopped making any.",
2297
2350
  inputSchema: {
2298
2351
  type: "object",
2299
2352
  properties: {
@@ -2358,7 +2411,7 @@ function createCoordinationToolsForManager(opts, lifetime) {
2358
2411
  progress: progress ?? null,
2359
2412
  ...pending ? {
2360
2413
  settlementPending: pending,
2361
- hint: `this worker has finished (${pending.kind}); its settlement is queued — call await_event to receive it, observing again will not change this`
2414
+ hint: `this worker has finished (${pending.kind}); its settlement is queued for your next wake — end your turn to receive it, observing again will not change this`
2362
2415
  } : {}
2363
2416
  };
2364
2417
  }
@@ -2400,7 +2453,7 @@ function createCoordinationToolsForManager(opts, lifetime) {
2400
2453
  },
2401
2454
  {
2402
2455
  name: "cancel_worker",
2403
- description: "Cancel one of YOUR live workers — running, or queued for a worker slot — and every worker it leads. Its unspent budget returns to your pool when it settles, so this is how you take back what a stalled or no-longer-needed worker holds (for example when spawn_worker is refused budget-exhausted while workers you no longer need hold the budget). The cancelled worker still settles `down` through await_event with what it spent. Refused (`error: \"not-live\"`) for a worker that already settled or is not yours.",
2456
+ description: "Cancel one of YOUR live workers — running, or queued for a worker slot — and every worker it leads. Its unspent budget returns to your pool when it settles, so this is how you take back what a stalled or no-longer-needed worker holds (for example when spawn_worker is refused budget-exhausted while workers you no longer need hold the budget). The cancelled worker still settles `down`, delivered in your next wake with what it spent. Refused (`error: \"not-live\"`) for a worker that already settled or is not yours.",
2404
2457
  inputSchema: {
2405
2458
  type: "object",
2406
2459
  properties: {
@@ -2432,117 +2485,6 @@ function createCoordinationToolsForManager(opts, lifetime) {
2432
2485
  };
2433
2486
  }
2434
2487
  },
2435
- {
2436
- name: "await_event",
2437
- description: "Wait for and pull the next message a worker, sub-driver, or analyst sent up — the unified inbox. Read a settled artifact with the returned `outputRead` call; the receipt carries its reference, not its bytes. An event is one of: a settled worker output ('settled'), a question needing your answer ('question', from ask_parent / the worker's ask-user), or a trace-analyst finding ('finding', from analyze-on-settle). Pass kinds:['settled'] for just the next finished worker; omit `kinds` to also receive questions and findings. Returns { idle: true } when nothing is queued and no workers are live. If a worker is still running when the wait elapses, returns { pending: true, live: [...] } (the workers still in flight) instead of blocking indefinitely — call await_event again to keep waiting; the settlement is not lost. Every reply carries `freeSlots`: how many more workers you can start right now (`null` = uncapped). A settled worker frees its slot, so `freeSlots > 0` means capacity is sitting idle — spawn into it before waiting again. Each event carries `eventSeq`; pass the ones you have processed in `acknowledge` on a later call. An event delivered to a turn that ends in failure is named again when you are re-entered, until it is acknowledged. Pass `max` above 1 to read a whole team at once: the reply is `{ events: [...] }` with the first event it waited for plus every other event already waiting, up to `max`, so a lead of hundreds of workers reads their receipts in a few turns instead of one each.",
2438
- inputSchema: {
2439
- type: "object",
2440
- properties: {
2441
- kinds: {
2442
- type: "array",
2443
- items: {
2444
- type: "string",
2445
- enum: [...awaitableEventKinds]
2446
- },
2447
- description: "Restrict to these event kinds (any if omitted)."
2448
- },
2449
- acknowledge: {
2450
- type: "array",
2451
- items: {
2452
- type: "integer",
2453
- minimum: 0
2454
- },
2455
- description: "The eventSeq of each earlier event you have now processed."
2456
- },
2457
- max: {
2458
- type: "integer",
2459
- minimum: 1,
2460
- description: "Most events to return in one reply. Omit or 1 for one event; above 1 returns `{ events }`."
2461
- }
2462
- }
2463
- },
2464
- handler: async (raw) => {
2465
- const args = raw === void 0 ? {} : obj(raw);
2466
- const max = args.max === void 0 ? 1 : args.max;
2467
- if (typeof max !== "number" || !Number.isSafeInteger(max) || max < 1) throw new Error("coordination tools: \"max\" must be a positive integer");
2468
- const k = args.kinds;
2469
- const kinds = Array.isArray(k) ? k.filter(isAwaitableEventKind) : void 0;
2470
- if (Array.isArray(args.acknowledge)) {
2471
- const named = new Set(args.acknowledge.filter((value) => typeof value === "number" && Number.isInteger(value)));
2472
- await acknowledge(deliveries.filter((delivery) => named.has(delivery.record.seq)), "manager");
2473
- }
2474
- const deliver = (record) => {
2475
- deliveries.push({
2476
- record,
2477
- attempt: driverAttempt,
2478
- acknowledged: false
2479
- });
2480
- return {
2481
- ...projectEvent(record.event),
2482
- eventSeq: record.seq
2483
- };
2484
- };
2485
- if (max > 1) {
2486
- const fenceMs = awaitFenceMs();
2487
- const fenceAt = fenceMs > 0 ? Date.now() + fenceMs / 2 : Infinity;
2488
- if (!inFlightDrain) {
2489
- inFlightDrain = (async () => {
2490
- let drained = false;
2491
- while (bus.pending(kinds) < max && Date.now() < fenceAt) {
2492
- if (!await drainSettlement("nextResolved")) break;
2493
- drained = true;
2494
- }
2495
- return drained;
2496
- })().finally(() => {
2497
- inFlightDrain = null;
2498
- });
2499
- await inFlightDrain;
2500
- }
2501
- const events = [];
2502
- const takeQueued = () => {
2503
- for (let next = bus.pullRecord(kinds); next; next = bus.pullRecord(kinds)) {
2504
- events.push(deliver(next));
2505
- if (events.length >= max) return;
2506
- }
2507
- };
2508
- takeQueued();
2509
- if (events.length === 0) {
2510
- const first = await pullOne();
2511
- if (!("eventSeq" in first)) return first;
2512
- const { freeSlots: _slots, ...head } = first;
2513
- events.push(head);
2514
- takeQueued();
2515
- }
2516
- return {
2517
- events,
2518
- freeSlots: freeWorkerSlots()
2519
- };
2520
- }
2521
- return pullOne();
2522
- async function pullOne() {
2523
- let ev = bus.pullRecord(kinds);
2524
- if (ev) return {
2525
- ...deliver(ev),
2526
- freeSlots: freeWorkerSlots()
2527
- };
2528
- const raced = await raceDrainWithTimeout(ensureDrain());
2529
- if (raced === void 0) return {
2530
- pending: true,
2531
- live: liveSnapshot(),
2532
- freeSlots: freeWorkerSlots()
2533
- };
2534
- ev = bus.pullRecord(kinds);
2535
- if (!ev) return {
2536
- idle: !raced.drained,
2537
- freeSlots: freeWorkerSlots()
2538
- };
2539
- return {
2540
- ...deliver(ev),
2541
- freeSlots: freeWorkerSlots()
2542
- };
2543
- }
2544
- }
2545
- },
2546
2488
  {
2547
2489
  name: "list_questions",
2548
2490
  description: "List questions raised by workers, drivers, or analysts. Blocking stop behavior follows questionPolicy.",
@@ -3010,7 +2952,7 @@ function createCoordinationToolsForManager(opts, lifetime) {
3010
2952
  };
3011
2953
  if (isLiveNodeStatus(node.status)) return {
3012
2954
  error: "worker-not-settled",
3013
- reason: `worker ${JSON.stringify(id)} has not settled, so it has no trace to analyze yet — await_event until it settles, then run the lens`
2955
+ reason: `worker ${JSON.stringify(id)} has not settled, so it has no trace to analyze yet — end your turn until it settles, then run the lens`
3014
2956
  };
3015
2957
  const trace = ledger.find((worker) => worker.id === id)?.trace ?? node.trace ?? {
3016
2958
  status: "unavailable",
@@ -3163,6 +3105,69 @@ function createCoordinationToolsForManager(opts, lifetime) {
3163
3105
  const attempt = driverAttempt;
3164
3106
  if (outcome === "completed") await acknowledge(deliveries.filter((delivery) => delivery.attempt === attempt), "turn-completed");
3165
3107
  },
3108
+ hasOpenWork,
3109
+ awaitWake,
3110
+ awaitSettlement: async (workerIds, signal) => {
3111
+ const wanted = new Set(workerIds);
3112
+ const settledIds = () => ledger.filter((worker) => wanted.has(worker.id)).map((w) => w.id);
3113
+ await ready();
3114
+ for (;;) {
3115
+ await settledWithoutDrain();
3116
+ const hit = settledIds();
3117
+ if (hit.length > 0) return hit;
3118
+ signal?.throwIfAborted();
3119
+ if (!(opts.scope.view?.nodes ?? []).some((node) => wanted.has(node.id) && isLiveNodeStatus(node.status))) return [];
3120
+ if (!await (signal === void 0 ? ensureDrain() : Promise.race([ensureDrain(), new Promise((_resolve, reject) => {
3121
+ signal.addEventListener("abort", () => reject(signal.reason), { once: true });
3122
+ })])) && settledIds().length === 0) return [];
3123
+ }
3124
+ },
3125
+ receiveLeadMessage: (message) => {
3126
+ const parsed = leadMessageOf(message);
3127
+ if (parsed === void 0) return false;
3128
+ leadPublishing += 1;
3129
+ bus.publish({
3130
+ type: "lead-message",
3131
+ message: parsed
3132
+ }).catch((error) => {
3133
+ console.error("Runtime could not queue a lead message for its manager", {
3134
+ manager: opts.scope.view.root,
3135
+ error: error instanceof Error ? error.message : String(error)
3136
+ });
3137
+ }).finally(() => {
3138
+ leadPublishing -= 1;
3139
+ });
3140
+ return true;
3141
+ },
3142
+ openToolReceipt: (tool) => {
3143
+ receiptSeq += 1;
3144
+ const receipt = `${tool}#${receiptSeq}`;
3145
+ openReceipts.set(receipt, {
3146
+ tool,
3147
+ startedAt: Date.now()
3148
+ });
3149
+ return receipt;
3150
+ },
3151
+ settleToolReceipt: async (receipt, outcome) => {
3152
+ const open = openReceipts.get(receipt);
3153
+ if (open === void 0) return;
3154
+ openReceipts.delete(receipt);
3155
+ await bus.publish({
3156
+ type: "tool-outcome",
3157
+ outcome: detachedFrozen({
3158
+ receipt,
3159
+ tool: open.tool,
3160
+ ...outcome.ok ? {
3161
+ ok: true,
3162
+ value: outcome.value
3163
+ } : {
3164
+ ok: false,
3165
+ error: outcome.error instanceof Error ? outcome.error.message : String(outcome.error)
3166
+ },
3167
+ elapsedMs: Date.now() - open.startedAt
3168
+ })
3169
+ });
3170
+ },
3166
3171
  reentryState: () => {
3167
3172
  const { unqueued, queued } = openWork();
3168
3173
  const deliveredSettled = new Set(deliveries.flatMap((delivery) => delivery.record.event.type === "settled" ? [delivery.record.event.worker.id] : []));
@@ -3272,6 +3277,22 @@ function safeJsonText(value) {
3272
3277
  return String(value);
3273
3278
  }
3274
3279
  }
3280
+ function leadMessageOf(message) {
3281
+ if (message === null || typeof message !== "object") return void 0;
3282
+ const m = message;
3283
+ const interrupt = m.interrupt === true;
3284
+ if (typeof m.steer === "string") return {
3285
+ kind: "steer",
3286
+ text: m.steer,
3287
+ interrupt
3288
+ };
3289
+ if (typeof m.answer === "string") return {
3290
+ kind: "answer",
3291
+ text: m.answer,
3292
+ ...typeof m.questionId === "string" ? { questionId: m.questionId } : {},
3293
+ interrupt
3294
+ };
3295
+ }
3275
3296
  //#endregion
3276
3297
  //#region src/runtime/supervise/run-cancellation.ts
3277
3298
  /** Apply a durable request through the run's existing abort controller. */
@@ -3700,7 +3721,7 @@ function renderAnytimeTable(report) {
3700
3721
  * A stop rule reads the run's own PROGRESS and decides. Three signals feed it:
3701
3722
  * - the objective curve over settled work (best-so-far, from `anytime.ts` — see below),
3702
3723
  * - the LIVE worker feed (`WorkerProgress`: `idleMs`, `stalled`, `turns`, `tokens`),
3703
- * - tree-level shape (how many are in flight, how many are waiting, when the last settle landed).
3724
+ * - tree-level shape (how many are in flight, when the last settle landed).
3704
3725
  *
3705
3726
  * ── Two boundaries this module holds deliberately ───────────────────────────────────────────────
3706
3727
  *
@@ -3758,7 +3779,6 @@ function createProgressTracker(opts = {}) {
3758
3779
  const workers = [];
3759
3780
  if (scope && treeView) for (const node of treeView.nodes) {
3760
3781
  if (isTerminalNodeStatus(node.status)) continue;
3761
- if (node.status === "waiting") continue;
3762
3782
  const p = scope.progress(node.id, viewOpts?.stallAfterMs !== void 0 ? {
3763
3783
  now: now(),
3764
3784
  stallAfterMs: viewOpts.stallAfterMs
@@ -3776,8 +3796,7 @@ function createProgressTracker(opts = {}) {
3776
3796
  lastImprovementAt,
3777
3797
  settlesSinceImprovement: lastImprovementIdx < 0 ? recorded.length : recorded.length - 1 - lastImprovementIdx,
3778
3798
  workers,
3779
- inFlight: treeView?.inFlight ?? 0,
3780
- waiting: treeView?.waiting ?? 0
3799
+ inFlight: treeView?.inFlight ?? 0
3781
3800
  };
3782
3801
  },
3783
3802
  evaluate(rule, scope, viewOpts) {
@@ -3819,7 +3838,7 @@ function noProgressFor(opts) {
3819
3838
  stop: true,
3820
3839
  reason: `no-progress: ${v.settlesSinceImprovement} settles with no improvement (limit ${opts.settles}), best=${v.best}`
3821
3840
  };
3822
- if (opts.ms !== void 0 && v.waiting === 0 && v.lastSettleAt > 0) {
3841
+ if (opts.ms !== void 0 && v.lastSettleAt > 0) {
3823
3842
  const idle = v.now - v.lastSettleAt;
3824
3843
  if (idle >= opts.ms) return {
3825
3844
  stop: true,
@@ -3861,7 +3880,6 @@ function plateau(opts) {
3861
3880
  function allWorkersStalled(opts = {}) {
3862
3881
  const minWorkers = opts.minWorkers ?? 1;
3863
3882
  return (v) => {
3864
- if (v.waiting > 0) return CONTINUE;
3865
3883
  if (v.workers.length < minWorkers) return CONTINUE;
3866
3884
  if (!v.workers.every((w) => w.stalled)) return CONTINUE;
3867
3885
  const worst = Math.max(...v.workers.map((w) => w.idleMs));
@@ -3919,6 +3937,56 @@ function progressStop(tracker, rule, ledger, scope, now, stallAfterMs) {
3919
3937
  return tracker.evaluate(rule, scope, stallAfterMs !== void 0 ? { stallAfterMs } : void 0);
3920
3938
  }
3921
3939
  //#endregion
3940
+ //#region src/runtime/supervise/wake.ts
3941
+ /** How long a waiting manager hears nothing before Runtime wakes it to reconsider: 15 min. */
3942
+ const DEFAULT_WAKE_HEARTBEAT_MS = 15 * 6e4;
3943
+ /** How long a wake waits for more events after its first, while other workers still run: 2 s. */
3944
+ const DEFAULT_WAKE_DEBOUNCE_MS = 2e3;
3945
+ /** How long before its deadline a manager is woken once to submit its best result: 15 min. */
3946
+ const DEFAULT_DEADLINE_WARNING_MS = 15 * 6e4;
3947
+ /** The instruction every manager receives about waiting. */
3948
+ const WAIT_BY_ENDING_TURN = "When you are waiting for workers, end your turn. You will be woken with what happened: a worker settled, a finding, a message or answer from your lead, or the deadline approaching.";
3949
+ function resolveWakePolicy(policy) {
3950
+ const pick = (value, fallback, name) => {
3951
+ const resolved = value ?? fallback;
3952
+ if (!Number.isSafeInteger(resolved) || resolved < 0) throw new TypeError(`wake.${name} must be a nonnegative safe integer of milliseconds`);
3953
+ return resolved;
3954
+ };
3955
+ return {
3956
+ heartbeatMs: pick(policy?.heartbeatMs, DEFAULT_WAKE_HEARTBEAT_MS, "heartbeatMs"),
3957
+ debounceMs: pick(policy?.debounceMs, DEFAULT_WAKE_DEBOUNCE_MS, "debounceMs"),
3958
+ deadlineWarningMs: pick(policy?.deadlineWarningMs, DEFAULT_DEADLINE_WARNING_MS, "deadlineWarningMs")
3959
+ };
3960
+ }
3961
+ /**
3962
+ * The deadline warning, at most once per manager: the epoch ms to wake at, or undefined. The lead
3963
+ * is at most a quarter of the manager's whole time to its deadline, so a short run is not warned
3964
+ * the moment it starts.
3965
+ */
3966
+ function deadlineWarningAt(deadlineMs, policy, warned, startedAtMs) {
3967
+ if (warned || deadlineMs <= 0 || policy.deadlineWarningMs <= 0) return void 0;
3968
+ return deadlineMs - Math.min(policy.deadlineWarningMs, Math.max(0, deadlineMs - startedAtMs) / 4);
3969
+ }
3970
+ /** The input a woken manager's next turn receives. */
3971
+ function composeWakeInput(wake, context) {
3972
+ const span = (ms) => ms < 12e4 ? `${Math.max(0, Math.round(ms / 1e3))} s` : `${Math.round(ms / 6e4)} min`;
3973
+ const lines = [];
3974
+ const waited = `You waited ${span(wake.idleMs)} without spending a turn.`;
3975
+ if (wake.reason === "deadline") {
3976
+ const left = context.deadlineMs === void 0 ? void 0 : context.deadlineMs - context.now;
3977
+ lines.push(`The run's deadline is ${left === void 0 ? "near" : `in ${span(left)}`}. ${waited}`, context.tools?.includes("submit_result") === true ? "Submit your best result now with submit_result. A run that reaches its deadline without an accepted result delivers nothing." : "Finish now with your best result.");
3978
+ } else if (wake.reason === "heartbeat") lines.push(`Nothing reached your inbox. ${waited} Reconsider whether the running workers still serve the objective.`);
3979
+ else lines.push(`${wake.events.length} event${wake.events.length === 1 ? "" : "s"} arrived. ${waited}`);
3980
+ if (wake.events.length > 0) {
3981
+ lines.push("", "## Events", "");
3982
+ for (const event of wake.events) lines.push(`- ${JSON.stringify(event)}`);
3983
+ }
3984
+ lines.push("", wake.live.length === 0 ? "Workers running: none." : `Workers running: ${wake.live.map((worker) => String(worker.id)).join(", ")}.`);
3985
+ if (wake.freeSlots !== null) lines.push(`Free worker slots: ${wake.freeSlots}.`);
3986
+ lines.push("", WAIT_BY_ENDING_TURN);
3987
+ return lines.join("\n");
3988
+ }
3989
+ //#endregion
3922
3990
  //#region src/runtime/supervise/coordination-driver.ts
3923
3991
  /**
3924
3992
  *
@@ -3977,7 +4045,7 @@ function formatOverspend(violation) {
3977
4045
  * overspend usd up to the turn tripwire). */
3978
4046
  function poolStarved(scope, perWorker, preserveOwnerTurns = false) {
3979
4047
  const b = scope.budget;
3980
- if (scope.view.inFlight > 0 || scope.view.waiting > 0) return false;
4048
+ if (scope.view.inFlight > 0) return false;
3981
4049
  const tokenStarved = preserveOwnerTurns ? b.tokensLeft <= 0 : b.tokensLeft < perWorker.maxTokens;
3982
4050
  const iterationStarved = b.iterationsLeft <= 0;
3983
4051
  const usdStarved = b.usdCapped && (b.usdLeft <= 0 || !preserveOwnerTurns && perWorker.maxUsd !== void 0 && b.usdLeft < perWorker.maxUsd);
@@ -3987,12 +4055,14 @@ function poolStarved(scope, perWorker, preserveOwnerTurns = false) {
3987
4055
  /** The number of driver turns a manager whose spend Runtime cannot measure may take when no
3988
4056
  * deadline or dollar cap bounds it. */
3989
4057
  const UNMETERED_DRIVER_TURNS = 16;
4058
+ /** How often a waiting driver applies operator cancels and steers between its turns. */
4059
+ const WAITING_CONTROL_POLL_MS = 5e3;
3990
4060
  /**
3991
4061
  * Safety guards for a manager's own turns when no deadline bounds them.
3992
4062
  *
3993
4063
  * `poolStarved` lets a manager keep taking turns while its workers run, because those workers hold
3994
4064
  * the pool its turns would otherwise be refused from. With no turn cap and no deadline, a manager
3995
- * waiting on a worker that never settles would take a metered turn per `await_event` fence forever.
4065
+ * waiting on a worker that never settles would take a metered turn per heartbeat wake forever.
3996
4066
  * Money bounds that: a manager that has overdrawn its pool by as much again as the whole pool stops.
3997
4067
  * A brain that reports no usage cannot be bounded by money, so without a deadline or dollar cap it
3998
4068
  * keeps the historical 16-turn bound. A caller's explicit `maxTurns` or a deadline replaces both.
@@ -4416,7 +4486,6 @@ function driverAgent(opts) {
4416
4486
  ...opts.sourceEvidenceRefs ? { sourceEvidenceRefs: opts.sourceEvidenceRefs } : {},
4417
4487
  ...opts.watchWorkers ? { watchWorkers: opts.watchWorkers } : {},
4418
4488
  ...opts.stallAfterMs !== void 0 ? { stallAfterMs: opts.stallAfterMs } : {},
4419
- ...opts.awaitTimeoutMs !== void 0 ? { awaitTimeoutMs: opts.awaitTimeoutMs } : {},
4420
4489
  ...opts.continuityByProfile ? { continuityByProfile: opts.continuityByProfile } : {},
4421
4490
  ...opts.preflightSpawn ? { preflightSpawn: opts.preflightSpawn } : {},
4422
4491
  ...opts.resolveSpawnProfile ? { resolveSpawnProfile: opts.resolveSpawnProfile } : {},
@@ -4453,7 +4522,7 @@ function driverAgent(opts) {
4453
4522
  const modelTools = selectedTools.flatMap((selected) => selected.kind === "descriptor" ? [selected.descriptor] : []);
4454
4523
  probeableByName = new Map(modelTools.map((tool) => [tool.name, tool]));
4455
4524
  const selectedExtraNames = new Set(selectedTools.flatMap((selected) => selected.kind === "extra" ? [selected.extra.name] : []));
4456
- opts.onCoordinationTools?.(modelTools);
4525
+ opts.onCoordinationTools?.(modelTools, coord);
4457
4526
  const acknowledger = opts.controlDir === void 0 ? void 0 : createCancelAcknowledger({
4458
4527
  dir: opts.controlDir,
4459
4528
  coord,
@@ -4471,10 +4540,6 @@ function driverAgent(opts) {
4471
4540
  signal: scope.signal,
4472
4541
  ...opts.controlScope === "subtree" ? {} : { deliverRoot: inbox.deliver }
4473
4542
  });
4474
- for (const w of scope.resume?.waits ?? []) {
4475
- const rearmed = scope.wait(w.spec, { label: w.label });
4476
- if (!rearmed.ok) throw new RuntimeRunStateError(`driverAgent: cannot re-arm resumed wait '${w.label}' (${rearmed.reason})`);
4477
- }
4478
4543
  const byName = new Map(modelTools.map((tool) => [tool.name, tool]));
4479
4544
  const toolSpecs = selectedTools.map((selected) => selected.kind === "descriptor" ? {
4480
4545
  type: "function",
@@ -4496,6 +4561,9 @@ function driverAgent(opts) {
4496
4561
  const poolTokens = scope.budget.tokensLeft + scope.budget.reservedTokens;
4497
4562
  let driverTurns = 0;
4498
4563
  let progressStopReason;
4564
+ const wakePolicy = resolveWakePolicy(opts.wake);
4565
+ let deadlineWarned = false;
4566
+ const startedAt = now();
4499
4567
  let driverTurn = 0;
4500
4568
  let driverCall = 0;
4501
4569
  const meteredBrain = async (messages, tools, detail, conversationTurn) => {
@@ -4645,6 +4713,38 @@ function driverAgent(opts) {
4645
4713
  });
4646
4714
  transcript.observe(messages);
4647
4715
  },
4716
+ afterFinal: async () => {
4717
+ if (coord.isStopped() || coord.submittedResult() !== void 0 || scope.signal.aborted) return;
4718
+ if (inbox.pending() === 0 && !coord.hasOpenWork()) return void 0;
4719
+ const deadlineMs = scope.budget.deadlineMs;
4720
+ const warnAt = deadlineWarningAt(deadlineMs, wakePolicy, deadlineWarned, startedAt);
4721
+ const controlClock = acknowledger === void 0 && steerAcknowledger === void 0 ? void 0 : setInterval(() => {
4722
+ (async () => {
4723
+ await steerAcknowledger?.pass("turn");
4724
+ acknowledger?.pass("turn");
4725
+ })().catch(() => void 0);
4726
+ }, WAITING_CONTROL_POLL_MS);
4727
+ controlClock?.unref?.();
4728
+ let wake;
4729
+ try {
4730
+ wake = await coord.awaitWake({
4731
+ signal: scope.signal,
4732
+ heartbeatMs: wakePolicy.heartbeatMs,
4733
+ debounceMs: wakePolicy.debounceMs,
4734
+ ...warnAt === void 0 ? {} : { deadlineAt: warnAt },
4735
+ alsoWakeOn: inbox.pending() > 0 ? Promise.resolve() : inbox.nextDelivery()
4736
+ });
4737
+ } finally {
4738
+ if (controlClock !== void 0) clearInterval(controlClock);
4739
+ }
4740
+ if (wake === void 0) return void 0;
4741
+ if (wake.reason === "deadline") deadlineWarned = true;
4742
+ return composeWakeInput(wake, {
4743
+ tools: selectedTools.map((selected) => selected.kind === "descriptor" ? selected.descriptor.name : selected.extra.name),
4744
+ deadlineMs,
4745
+ now: now()
4746
+ });
4747
+ },
4648
4748
  stopBefore: () => {
4649
4749
  if (coord.isStopped() || scope.signal.aborted || poolStarved(scope, opts.perWorker, opts.preserveOwnerTurns) || deadlinePassed(scope, now) || ownTurnsUnbounded(scope, poolTokens, driverTurns, opts.maxTurns)) return true;
4650
4750
  if (!opts.stopRule || !tracker) return false;
@@ -4710,10 +4810,9 @@ function resumeBrief(resume, current, prior) {
4710
4810
  const inDoubt = byState("in-doubt").filter(([, value]) => !currentIds.has(value.id));
4711
4811
  const failed = byState("down");
4712
4812
  if (completed.length > 0) lines.push("", "COMPLETED keys — spawn_worker with the same key returns the finished result, spending nothing:", ...completed.map(([k, v]) => `- ${k} → ${v.id} (${v.label})`));
4713
- if (recovered.length > 0) lines.push("", "Recovered keys are attached to this scope. Use await_event to receive their results and coordinate with their original workers:", ...recovered.map(([key, value]) => `- ${key} → ${value.id} (${value.label})`));
4813
+ if (recovered.length > 0) lines.push("", "Recovered keys are attached to this scope. Their results arrive when you are woken; coordinate with their original workers:", ...recovered.map(([key, value]) => `- ${key} → ${value.id} (${value.label})`));
4714
4814
  if (inDoubt.length > 0) lines.push("", "Keys IN DOUBT — a prior process recorded them as started but never recorded a terminal receipt. Do NOT spawn a replacement under these keys; inspect or recover each exact prior execution first:", ...inDoubt.map(([k, v]) => `- ${k} (prior attempt ${v.id}, ${v.label})`));
4715
4815
  if (failed.length > 0) lines.push("", "Keys whose prior attempt FAILED (settled down) — spawn_worker with the same key retries:", ...failed.map(([k, v]) => `- ${k} (prior attempt ${v.id}, ${v.label})`));
4716
- if (resume.waits.length > 0) lines.push("", "Pending waits RE-ARMED on their original deadlines (they settle through await_event):", ...resume.waits.map((w) => `- ${w.label} (${w.spec.kind})`));
4717
4816
  appendPriorCoordination(lines, prior);
4718
4817
  const spent = resume.priorSpend;
4719
4818
  lines.push("", "Budget the run ALREADY spent before this process (it counts toward the run total):", `- child work: tokens charged=${chargedTokens(spent.childWork.tokens)} (in=${spent.childWork.tokens.input} out=${spent.childWork.tokens.output}), usd=${spent.childWork.usd}, iterations=${spent.childWork.iterations}`, `- driver inference: tokens charged=${chargedTokens(spent.driverInference.tokens)} (in=${spent.driverInference.tokens.input} out=${spent.driverInference.tokens.output}), usd=${spent.driverInference.usd}`);
@@ -4784,6 +4883,6 @@ function safeJson(v) {
4784
4883
  }
4785
4884
  }
4786
4885
  //#endregion
4787
- export { createCoordinationTools as A, withCoordinationAwaitFence as B, applyRunCancellation as C, analystToolGroupNames as D, DEFAULT_AWAIT_EVENT_TIMEOUT_MS as E, questionEscalationTargets as F, defaultToolDetectors as H, COORDINATION_CLIENT_TOOL_TIMEOUT_MS as I, MCP_TOOL_TIMEOUT_HEADER as L, downMessageRefusalReasons as M, normalizeAnalyzeOnSettle as N, canonicalFindingEvent as O, parseAuthoredAnalystDefinition as P, declaredAwaitFenceMs as R, foldCoordinationRecords as S, ANALYST_DEFINITION_BOUNDS as T, watchTrace as U, createEventBus as V, renderAnytimeTable as _, allOf as a, withRunContext as b, createProgressTracker as c, progressStop as d, sampleFromSettled as f, plateauLength as g, bestSoFar as h, finalizeBestDelivered as i, createCoordinationToolsForManager as j, coordinationVerbNames as k, noProgressFor as l, areaUnderCurve as m, createSteerAcknowledger as n, allWorkersStalled as o, anytimeReport as p, driverAgent as r, anyOf as s, createCancelAcknowledger as t, plateau as u, createFileRunContext as v, watchRunCancellation as w, FileCoordinationLog as x, createInMemoryRunContext as y, declaredToolTimeoutMs as z };
4886
+ export { analystToolGroupNames as A, defaultToolDetectors as B, createInMemoryRunContext as C, applyRunCancellation as D, foldCoordinationRecords as E, downMessageRefusalReasons as F, normalizeAnalyzeOnSettle as I, parseAuthoredAnalystDefinition as L, coordinationVerbNames as M, createCoordinationTools as N, watchRunCancellation as O, createCoordinationToolsForManager as P, questionEscalationTargets as R, createFileRunContext as S, FileCoordinationLog as T, watchTrace as V, anytimeReport as _, DEFAULT_DEADLINE_WARNING_MS as a, plateauLength as b, resolveWakePolicy as c, anyOf as d, createProgressTracker as f, sampleFromSettled as g, progressStop as h, finalizeBestDelivered as i, canonicalFindingEvent as j, ANALYST_DEFINITION_BOUNDS as k, allOf as l, plateau as m, createSteerAcknowledger as n, composeWakeInput as o, noProgressFor as p, driverAgent as r, deadlineWarningAt as s, createCancelAcknowledger as t, allWorkersStalled as u, areaUnderCurve as v, withRunContext as w, renderAnytimeTable as x, bestSoFar as y, createEventBus as z };
4788
4887
 
4789
- //# sourceMappingURL=coordination-driver-DzTvehd6.js.map
4888
+ //# sourceMappingURL=coordination-driver-yj9HPXef.js.map