@tangle-network/agent-runtime 0.174.1 → 0.177.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 (104) hide show
  1. package/README.md +4 -0
  2. package/dist/{activation-CsdJWRXX.js → activation-XVI_z1g3.js} +3 -3
  3. package/dist/{activation-CsdJWRXX.js.map → activation-XVI_z1g3.js.map} +1 -1
  4. package/dist/agent.d.ts +1 -1
  5. package/dist/agent.js +2 -2
  6. package/dist/{authoring-DsLNInAz.js → authoring-bXmPNfCI.js} +2 -2
  7. package/dist/{authoring-DsLNInAz.js.map → authoring-bXmPNfCI.js.map} +1 -1
  8. package/dist/candidate-execution/index.js +4 -4
  9. package/dist/{candidate-execution-BbMbyhgC.js → candidate-execution-fBNUyWyE.js} +10 -4
  10. package/dist/candidate-execution-fBNUyWyE.js.map +1 -0
  11. package/dist/{conversation-7XGKoDM6.js → conversation-BREx2hi-.js} +3 -3
  12. package/dist/{conversation-7XGKoDM6.js.map → conversation-BREx2hi-.js.map} +1 -1
  13. package/dist/conversation.d.ts +1 -1
  14. package/dist/conversation.js +1 -1
  15. package/dist/coordination-driver-xO1tkxy-.js +3063 -0
  16. package/dist/coordination-driver-xO1tkxy-.js.map +1 -0
  17. package/dist/durable-file-g3YdfEh4.js +131 -0
  18. package/dist/durable-file-g3YdfEh4.js.map +1 -0
  19. package/dist/durable.d.ts +2 -2
  20. package/dist/durable.js +1 -1
  21. package/dist/{environment-provider-DWjbGH8W.d.ts → environment-provider-5E82esWE.d.ts} +6 -3
  22. package/dist/{environment-provider-Bn3652YU.js → environment-provider-DKyMWQJd.js} +121 -17
  23. package/dist/environment-provider-DKyMWQJd.js.map +1 -0
  24. package/dist/environment-provider.d.ts +1 -1
  25. package/dist/environment-provider.js +1 -1
  26. package/dist/{graph-BbeHgLXH.js → graph-DwJXa1Ph.js} +3 -3
  27. package/dist/graph-DwJXa1Ph.js.map +1 -0
  28. package/dist/graph.d.ts +11 -3
  29. package/dist/graph.js +35 -4
  30. package/dist/graph.js.map +1 -1
  31. package/dist/{improvement-cycle-DKGDCukL.js → improvement-cycle-eiSv6aH4.js} +5 -5
  32. package/dist/{improvement-cycle-DKGDCukL.js.map → improvement-cycle-eiSv6aH4.js.map} +1 -1
  33. package/dist/{index-CIhkH7mq.d.ts → index-BnAiQVLD.d.ts} +290 -29
  34. package/dist/{index-CFMB9ux3.d.ts → index-Bs0uhf-y.d.ts} +2 -2
  35. package/dist/{index-Dk5X9eKg.d.ts → index-C9XTLVP1.d.ts} +4 -4
  36. package/dist/index.d.ts +7 -7
  37. package/dist/index.js +13 -13
  38. package/dist/intelligence.d.ts +4 -4
  39. package/dist/intelligence.js +6 -6
  40. package/dist/kernel.d.ts +6 -6
  41. package/dist/kernel.js +13 -11
  42. package/dist/{knowledge-BaAX0HVu.js → knowledge-Bmtj_7u7.js} +5 -5
  43. package/dist/{knowledge-BaAX0HVu.js.map → knowledge-Bmtj_7u7.js.map} +1 -1
  44. package/dist/knowledge.d.ts +1 -1
  45. package/dist/knowledge.js +1 -1
  46. package/dist/{loop-runner-bin-DRYApMiu.d.ts → loop-runner-bin-ChS5deJ0.d.ts} +3 -3
  47. package/dist/{loop-runner-bin-D63Tytdk.js → loop-runner-bin-GTN5wqCt.js} +3 -3
  48. package/dist/{loop-runner-bin-D63Tytdk.js.map → loop-runner-bin-GTN5wqCt.js.map} +1 -1
  49. package/dist/loop-runner-bin.d.ts +1 -1
  50. package/dist/loop-runner-bin.js +1 -1
  51. package/dist/{materialization-Ct3I4wu3.js → materialization-CekWK6OO.js} +5 -3
  52. package/dist/materialization-CekWK6OO.js.map +1 -0
  53. package/dist/mcp/bin.js +3 -3
  54. package/dist/mcp/index.d.ts +3 -3
  55. package/dist/mcp/index.js +5 -4
  56. package/dist/mcp/index.js.map +1 -1
  57. package/dist/{openai-tools-BBgjWYNw.js → openai-tools-BBj1QGJp.js} +2 -2
  58. package/dist/{openai-tools-BBgjWYNw.js.map → openai-tools-BBj1QGJp.js.map} +1 -1
  59. package/dist/{prepare-BAyaNoZu.js → prepare-Tip-2ZoZ.js} +2 -2
  60. package/dist/{prepare-BAyaNoZu.js.map → prepare-Tip-2ZoZ.js.map} +1 -1
  61. package/dist/primeintellect/index.d.ts +1 -1
  62. package/dist/{protected-model-port-T2UWrDNQ.js → protected-model-port-Dd3a1SRm.js} +2 -2
  63. package/dist/{protected-model-port-T2UWrDNQ.js.map → protected-model-port-Dd3a1SRm.js.map} +1 -1
  64. package/dist/provision-supervisor-zA__ayUX.js +1020 -0
  65. package/dist/provision-supervisor-zA__ayUX.js.map +1 -0
  66. package/dist/{redact-DvLf4x1m.d.ts → redact-DkRLcSiE.d.ts} +3 -3
  67. package/dist/run-layout-Cd_l2XWV.js +645 -0
  68. package/dist/run-layout-Cd_l2XWV.js.map +1 -0
  69. package/dist/{runtime-D5u1M6T5.d.ts → runtime-B2eZ76kG.d.ts} +3 -3
  70. package/dist/{runtime-CojgP-hr.js → runtime-CJNlF5wM.js} +16 -1095
  71. package/dist/runtime-CJNlF5wM.js.map +1 -0
  72. package/dist/{spawn-journal-Tgvp5VS5.js → spawn-journal--N8Ten1q.js} +3 -1
  73. package/dist/{spawn-journal-Tgvp5VS5.js.map → spawn-journal--N8Ten1q.js.map} +1 -1
  74. package/dist/{stream-agent-turn-DxQ3GOuR.d.ts → stream-agent-turn-ByY8po9W.d.ts} +2 -2
  75. package/dist/{stream-agent-turn-CKIpCbSq.js → stream-agent-turn-CPj9SqVm.js} +2 -2
  76. package/dist/{stream-agent-turn-CKIpCbSq.js.map → stream-agent-turn-CPj9SqVm.js.map} +1 -1
  77. package/dist/{structural-rollout-BUYA61iV.js → structural-rollout-BeAUK7Ta.js} +4 -4
  78. package/dist/{structural-rollout-BUYA61iV.js.map → structural-rollout-BeAUK7Ta.js.map} +1 -1
  79. package/dist/{supervise-Ci0RfWQF.js → supervise-HsRKOlKY.js} +405 -3374
  80. package/dist/supervise-HsRKOlKY.js.map +1 -0
  81. package/dist/{supervisor-Bt1XLCVq.js → supervisor-ChLgoYhG.js} +1620 -33
  82. package/dist/supervisor-ChLgoYhG.js.map +1 -0
  83. package/dist/testing.d.ts +2 -2
  84. package/dist/testing.js +13 -12
  85. package/dist/testing.js.map +1 -1
  86. package/dist/{top-app-3rYfFPTW.js → top-app-DIBApkt1.js} +25 -9
  87. package/dist/top-app-DIBApkt1.js.map +1 -0
  88. package/dist/tui/bin.js +1 -1
  89. package/dist/tui/index.d.ts +9 -1
  90. package/dist/tui/index.js +3 -2
  91. package/dist/{types-DjVO-p7R.d.ts → types-a2ZZUl80.d.ts} +25 -2
  92. package/dist/{workspace-archive-B0Hqp2eJ.js → workspace-archive-DOZAeeKU.js} +2 -2
  93. package/dist/{workspace-archive-B0Hqp2eJ.js.map → workspace-archive-DOZAeeKU.js.map} +1 -1
  94. package/package.json +5 -4
  95. package/dist/candidate-execution-BbMbyhgC.js.map +0 -1
  96. package/dist/environment-provider-Bn3652YU.js.map +0 -1
  97. package/dist/graph-BbeHgLXH.js.map +0 -1
  98. package/dist/materialization-Ct3I4wu3.js.map +0 -1
  99. package/dist/run-layout-C2jgwsCq.js +0 -393
  100. package/dist/run-layout-C2jgwsCq.js.map +0 -1
  101. package/dist/runtime-CojgP-hr.js.map +0 -1
  102. package/dist/supervise-Ci0RfWQF.js.map +0 -1
  103. package/dist/supervisor-Bt1XLCVq.js.map +0 -1
  104. package/dist/top-app-3rYfFPTW.js.map +0 -1
@@ -0,0 +1,645 @@
1
+ import { i as writeAtomicDurableFile, n as assertNoSymlinkDescendant, r as publishExclusiveDurableFile, t as appendDurableFile } from "./durable-file-g3YdfEh4.js";
2
+ import { canonicalCandidateDigest } from "@tangle-network/agent-interface";
3
+ import { createHash } from "node:crypto";
4
+ import { join, resolve } from "node:path";
5
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
6
+ //#region src/runtime/supervise/run-layout.ts
7
+ /**
8
+ * The on-disk supervisor-run layout: `<root>/.agent/supervisor/<id>`.
9
+ *
10
+ * This is the durable, cross-process face of a supervisor run — the counterpart to the in-process
11
+ * `Inbox` seam in `./inbox`. A run persists its state under one directory so that any OTHER process
12
+ * can find it after the fact: `@tangle-network/traces` reads exactly this layout via
13
+ * `traces analyze --supervisor-run-dir`, a restarted host can rehydrate a run it no longer holds
14
+ * handles to, and a human can steer a live worker by appending one NDJSON line. Until now the
15
+ * layout was defined only in the unpublished `loops` repo (`src/supervisor-control.ts`) — a
16
+ * published reader depending on an unpublished writer's convention — so the contract is promoted
17
+ * here, names preserved.
18
+ *
19
+ * `.agent` is the one dot-dir for ALL agent-owned state (skills already write
20
+ * `.agent/hypotheses/`, `.agent/skill-runs.jsonl`); supervisor runs live beside them rather than
21
+ * under a product-branded dir. Runs written by older writers used `.loops/supervisor/<id>` —
22
+ * readers that must see those keep a legacy fallback; this writer never creates `.loops` again.
23
+ *
24
+ * Layout, relative to `supervisorRunDir(root, id)`:
25
+ *
26
+ * steers/requests/<hash>.json one exact, caller-idempotent {@link WorkerSteerRequest}
27
+ * steers/acks/<hash>.json runtime acknowledgement for that steer operation
28
+ * workers/<id>.inbox.ndjson best-effort readable projection of admitted steer requests
29
+ * workers/<label>.ndjson best-effort per-worker control-event log (delivery bookkeeping)
30
+ * cancellations/requests.ndjson worker-scoped cancel requests (durable inbox); each line is a
31
+ * {@link WorkerCancelRequest}
32
+ * cancellations/<opId>.json the acknowledgement for one cancel operation — a
33
+ * {@link WorkerCancellation} written ONLY by the runtime
34
+ * acknowledger in the OWNING manager's turn loop: exact node
35
+ * ids route to the manager that parents them (any depth);
36
+ * label/profile-name references to the root manager alone
37
+ * cancellations/run.request.json the run-scoped cancel request — one {@link RunCancelRequest},
38
+ * the whole RUN rather than one worker
39
+ * cancellations/run.json the acknowledgement for the run-scoped request — a
40
+ * {@link RunCancellation} written ONLY by the runtime (the root
41
+ * manager issues the abort; the `supervise()` settle path records
42
+ * what the run actually did)
43
+ *
44
+ * Reads are tolerant by contract: a partial trailing line (a writer mid-append) or a corrupt line
45
+ * never poisons the rest of the file — later valid lines still matter. Duplicate operation ids
46
+ * collapse to the first admitted request, while a payload conflict fails closed.
47
+ *
48
+ * Promoted from `loops/src/supervisor-control.ts`. Steer admission now requires the exact worker id
49
+ * and a caller-owned operation id. One atomic request file is the durable inbox; the NDJSON file is
50
+ * a readable projection only and never controls idempotency.
51
+ *
52
+ * @experimental
53
+ */
54
+ /** The root every supervisor run of one workspace lives under. */
55
+ function supervisorRunsRoot(rootDir) {
56
+ return join(resolve(rootDir), ".agent", "supervisor");
57
+ }
58
+ /** The run directory every artifact of one supervisor run lives under. */
59
+ function supervisorRunDir(rootDir, id) {
60
+ return join(supervisorRunsRoot(rootDir), id);
61
+ }
62
+ /**
63
+ * Where a pre-rename writer put the same run (`<root>/.loops/supervisor/<id>`). Readers that must
64
+ * see historical runs check {@link supervisorRunDir} first and fall back to this; nothing writes
65
+ * here anymore.
66
+ */
67
+ function legacySupervisorRunDir(rootDir, id) {
68
+ return join(legacySupervisorRunsRoot(rootDir), id);
69
+ }
70
+ /**
71
+ * The pre-rename runs root (`<root>/.loops/supervisor`). Only readers that ENUMERATE historical
72
+ * runs need this — the per-id form is {@link legacySupervisorRunDir}. Nothing writes here.
73
+ */
74
+ function legacySupervisorRunsRoot(rootDir) {
75
+ return join(resolve(rootDir), ".loops", "supervisor");
76
+ }
77
+ /** A worker label reduced to a safe filename stem. Empty labels get a stable fallback. */
78
+ function safeWorkerFile(label) {
79
+ const safe = label.replace(/[^A-Za-z0-9._-]/g, "_");
80
+ return safe.length > 0 ? safe : "worker";
81
+ }
82
+ /** The directory holding every per-worker file of one run (inboxes and control-event logs). */
83
+ function supervisorWorkersDir(eventDir) {
84
+ return join(eventDir, "workers");
85
+ }
86
+ /** The durable inbox file for one worker of one run. */
87
+ function workerInboxFile(rootDir, supervisorId, worker) {
88
+ return workerInboxFileFromEventDir(supervisorRunDir(rootDir, supervisorId), worker);
89
+ }
90
+ /** Same, addressed from an already-known run directory (the reader's usual entry point). */
91
+ function workerInboxFileFromEventDir(eventDir, worker) {
92
+ return join(supervisorWorkersDir(eventDir), `${safeWorkerFile(worker)}.inbox.ndjson`);
93
+ }
94
+ /**
95
+ * The best-effort control-event log for one worker (`workers/<label>.ndjson`) — delivery
96
+ * bookkeeping for steers, plus whatever lifecycle events a writer chooses to append. Distinct from
97
+ * the inbox: the inbox is the durable down-leg queue, this is the record of what happened to it.
98
+ */
99
+ function workerControlLogFile(eventDir, worker) {
100
+ return join(supervisorWorkersDir(eventDir), `${safeWorkerFile(worker)}.ndjson`);
101
+ }
102
+ /** Directory containing atomically admitted steer requests and runtime acknowledgements. */
103
+ function workerSteersDir(eventDir) {
104
+ return join(eventDir, "steers");
105
+ }
106
+ /** Directory containing one canonical request file per steer operation. */
107
+ function workerSteerRequestsDir(eventDir) {
108
+ return join(workerSteersDir(eventDir), "requests");
109
+ }
110
+ /** Directory containing one runtime acknowledgement per steer operation. */
111
+ function workerSteerAcknowledgementsDir(eventDir) {
112
+ return join(workerSteersDir(eventDir), "acks");
113
+ }
114
+ /** Canonical request file for one caller-owned steer operation id. */
115
+ function workerSteerRequestFile(eventDir, operationId) {
116
+ return join(workerSteerRequestsDir(eventDir), `${operationFileHash(operationId)}.json`);
117
+ }
118
+ /** Runtime acknowledgement file for one caller-owned steer operation id. */
119
+ function workerSteerAcknowledgementFile(eventDir, operationId) {
120
+ return join(workerSteerAcknowledgementsDir(eventDir), `${operationFileHash(operationId)}.json`);
121
+ }
122
+ /**
123
+ * Admit one steer exactly once under a caller-owned operation id.
124
+ *
125
+ * The per-operation request file is linked into place atomically after its bytes reach disk. A
126
+ * same-body retry returns the winner's request. A changed-body retry fails loud. The NDJSON inbox
127
+ * and control log are readable projections written only by the admission winner.
128
+ * @stable
129
+ */
130
+ function writeWorkerSteer(rootDir, supervisorId, worker, options) {
131
+ const workerId = worker.trim();
132
+ if (!workerId) throw new Error("writeWorkerSteer: worker id is empty");
133
+ const operationId = options.operationId.trim();
134
+ if (!operationId) throw new Error("writeWorkerSteer: operationId is empty");
135
+ const trimmed = options.message.trim();
136
+ if (!trimmed) throw new Error("steer message is empty");
137
+ const source = options.source?.trim() || "human";
138
+ const interrupt = options.interrupt === true;
139
+ const dir = supervisorRunDir(rootDir, supervisorId);
140
+ const requestDigest = workerSteerRequestDigest({
141
+ operationId,
142
+ worker: workerId,
143
+ message: trimmed,
144
+ source,
145
+ interrupt
146
+ });
147
+ const request = {
148
+ schemaVersion: 1,
149
+ operationId,
150
+ requestDigest,
151
+ at: (/* @__PURE__ */ new Date()).toISOString(),
152
+ source,
153
+ worker: workerId,
154
+ message: trimmed,
155
+ interrupt
156
+ };
157
+ const file = workerSteerRequestFile(dir, operationId);
158
+ assertNoSymlinkDescendant(dir, file, "steer request");
159
+ mkdirSync(workerSteerRequestsDir(dir), { recursive: true });
160
+ assertNoSymlinkDescendant(dir, file, "steer request");
161
+ const admitted = admitSteerRequest(file, request);
162
+ if (admitted.replayed) return {
163
+ worker: admitted.request.worker,
164
+ file,
165
+ request: admitted.request,
166
+ ...readWorkerSteerAcknowledgement(dir, operationId) === void 0 ? {} : { acknowledgement: readWorkerSteerAcknowledgement(dir, operationId) },
167
+ replayed: true
168
+ };
169
+ mkdirSync(supervisorWorkersDir(dir), { recursive: true });
170
+ const projection = workerInboxFile(rootDir, supervisorId, workerId);
171
+ try {
172
+ appendFileSync(projection, `${JSON.stringify(request)}\n`, "utf8");
173
+ } catch {}
174
+ appendWorkerControlEvent(dir, workerId, {
175
+ kind: "message",
176
+ direction: "down",
177
+ source,
178
+ operationId,
179
+ requestDigest,
180
+ message: trimmed,
181
+ interrupt,
182
+ queued: true,
183
+ delivered: false
184
+ });
185
+ return {
186
+ worker: workerId,
187
+ file,
188
+ request,
189
+ replayed: false
190
+ };
191
+ }
192
+ /** Read every atomically admitted request for one exact worker id, in admission order. @stable */
193
+ function readWorkerSteerRequests(eventDir, worker) {
194
+ const workerId = worker?.trim();
195
+ if (worker !== void 0 && !workerId) return [];
196
+ let names;
197
+ assertNoSymlinkDescendant(eventDir, workerSteerRequestsDir(eventDir), "steer request");
198
+ try {
199
+ names = readdirSync(workerSteerRequestsDir(eventDir));
200
+ } catch {
201
+ return [];
202
+ }
203
+ const out = [];
204
+ for (const name of names) {
205
+ if (!name.endsWith(".json")) continue;
206
+ try {
207
+ const file = join(workerSteerRequestsDir(eventDir), name);
208
+ assertNoSymlinkDescendant(eventDir, file, "steer request");
209
+ const parsed = parseWorkerSteerRequest(JSON.parse(readFileSync(file, "utf8")));
210
+ if (name !== `${operationFileHash(parsed.operationId)}.json`) continue;
211
+ if (workerId === void 0 || parsed.worker === workerId) out.push(parsed);
212
+ } catch {}
213
+ }
214
+ return out.sort((left, right) => left.at === right.at ? left.operationId.localeCompare(right.operationId) : left.at.localeCompare(right.at));
215
+ }
216
+ /** Read one runtime steer acknowledgement, or `undefined` while no manager has answered. @stable */
217
+ function readWorkerSteerAcknowledgement(eventDir, operationId) {
218
+ const id = operationId.trim();
219
+ if (!id) throw new Error("readWorkerSteerAcknowledgement: operationId is empty");
220
+ const file = workerSteerAcknowledgementFile(eventDir, id);
221
+ assertNoSymlinkDescendant(eventDir, file, "steer acknowledgement");
222
+ if (!existsSync(file)) return void 0;
223
+ const record = parseWorkerSteerAcknowledgement(JSON.parse(readFileSync(file, "utf8")));
224
+ if (record.operationId !== id) throw new Error(`steer acknowledgement '${file}' belongs to another operation`);
225
+ return record;
226
+ }
227
+ /**
228
+ * Claim one steer for delivery with an atomic no-clobber acknowledgement.
229
+ * Returns `true` only to the process that created the pre-delivery record.
230
+ * @internal
231
+ */
232
+ function claimWorkerSteerDelivery(eventDir, record) {
233
+ const exact = parseWorkerSteerAcknowledgement(record);
234
+ if (exact.effect !== "unknown") throw new Error("steer delivery claim must use effect unknown");
235
+ const request = readWorkerSteerRequest(eventDir, exact.operationId);
236
+ if (request === void 0 || request.requestDigest !== exact.requestDigest) throw new Error("steer delivery claim does not match an admitted request");
237
+ const dir = workerSteerAcknowledgementsDir(eventDir);
238
+ assertNoSymlinkDescendant(eventDir, dir, "steer acknowledgement");
239
+ mkdirSync(dir, { recursive: true });
240
+ assertNoSymlinkDescendant(eventDir, dir, "steer acknowledgement");
241
+ if (publishExclusiveDurableFile(workerSteerAcknowledgementFile(eventDir, exact.operationId), `${JSON.stringify(exact, null, 2)}\n`)) return true;
242
+ const winner = readWorkerSteerAcknowledgement(eventDir, exact.operationId);
243
+ if (winner === void 0 || winner.operationId !== exact.operationId || winner.requestDigest !== exact.requestDigest) throw new Error("steer delivery claim conflicts with another operation");
244
+ return false;
245
+ }
246
+ /** Atomically replace the runtime acknowledgement for one steer operation. @internal */
247
+ function writeWorkerSteerAcknowledgement(eventDir, record) {
248
+ const exact = parseWorkerSteerAcknowledgement(record);
249
+ const request = readWorkerSteerRequest(eventDir, exact.operationId);
250
+ if (request === void 0 || request.requestDigest !== exact.requestDigest) throw new Error("steer acknowledgement does not match an admitted request");
251
+ const dir = workerSteerAcknowledgementsDir(eventDir);
252
+ assertNoSymlinkDescendant(eventDir, dir, "steer acknowledgement");
253
+ mkdirSync(dir, { recursive: true });
254
+ assertNoSymlinkDescendant(eventDir, dir, "steer acknowledgement");
255
+ const file = workerSteerAcknowledgementFile(eventDir, exact.operationId);
256
+ const prior = readWorkerSteerAcknowledgement(eventDir, exact.operationId);
257
+ if (prior !== void 0 && prior.effect !== "unknown" && canonicalCandidateDigest(prior) !== canonicalCandidateDigest(exact)) throw new Error(`steer operation '${exact.operationId}' already has a terminal acknowledgement`);
258
+ writeAtomicDurableFile(file, `${JSON.stringify(exact, null, 2)}\n`);
259
+ }
260
+ /** The directory holding every cancellation artifact of one run (request inbox + acknowledgements). */
261
+ function workerCancellationsDir(eventDir) {
262
+ return join(eventDir, "cancellations");
263
+ }
264
+ /**
265
+ * The durable cancel-request inbox of one run — one NDJSON line per {@link WorkerCancelRequest}.
266
+ * It stays append-only so independent operations retain admission order and readers can skip a
267
+ * partial trailing line. `appendDurableFile` uses O_APPEND, one write, file fsync, and directory
268
+ * fsync; single-record acknowledgements use atomic replacement instead.
269
+ */
270
+ function workerCancelRequestsFile(eventDir) {
271
+ return join(workerCancellationsDir(eventDir), "requests.ndjson");
272
+ }
273
+ /**
274
+ * The acknowledgement file for one cancel operation. The filename is a sanitized stem of the
275
+ * `operationId`; the record inside carries the exact id, and readers verify it so two distinct
276
+ * ids that sanitize to one stem fail loud instead of answering for each other.
277
+ */
278
+ function workerCancellationFile(eventDir, operationId) {
279
+ return join(workerCancellationsDir(eventDir), `${safeOperationFile(operationId)}.json`);
280
+ }
281
+ function safeOperationFile(operationId) {
282
+ const safe = operationId.replace(/[^A-Za-z0-9._-]/g, "_");
283
+ if (safe.length === 0) throw new Error("cancel operationId is empty");
284
+ return safe;
285
+ }
286
+ /** Read every valid cancel request in the run's cancellation inbox. Corrupt lines are skipped. */
287
+ function readWorkerCancelRequests(eventDir) {
288
+ const dir = workerCancellationsDir(eventDir);
289
+ const file = workerCancelRequestsFile(eventDir);
290
+ assertNoSymlinkDescendant(eventDir, dir, "cancel request");
291
+ assertNoSymlinkDescendant(eventDir, file, "cancel request");
292
+ if (!existsSync(file)) return [];
293
+ const out = [];
294
+ const byOperationId = /* @__PURE__ */ new Map();
295
+ let raw = "";
296
+ try {
297
+ raw = readFileSync(file, "utf8");
298
+ } catch {
299
+ return out;
300
+ }
301
+ for (const line of raw.split("\n")) {
302
+ const trimmed = line.trim();
303
+ if (!trimmed) continue;
304
+ let parsed;
305
+ try {
306
+ parsed = JSON.parse(trimmed);
307
+ } catch {
308
+ continue;
309
+ }
310
+ if (!isWorkerCancelRequest(parsed)) continue;
311
+ const prior = byOperationId.get(parsed.operationId);
312
+ if (prior !== void 0) assertSameWorkerCancelRequest(prior, parsed);
313
+ else {
314
+ byOperationId.set(parsed.operationId, parsed);
315
+ out.push(parsed);
316
+ }
317
+ }
318
+ return out;
319
+ }
320
+ /**
321
+ * Read the acknowledgement for one cancel operation. `undefined` when the runtime has not
322
+ * answered. A record whose stored `operationId` differs from the requested one is a filename
323
+ * collision between two sanitized ids — fail loud rather than return another operation's answer.
324
+ */
325
+ function readWorkerCancellation(eventDir, operationId) {
326
+ const dir = workerCancellationsDir(eventDir);
327
+ const file = workerCancellationFile(eventDir, operationId);
328
+ assertNoSymlinkDescendant(eventDir, dir, "cancel acknowledgement");
329
+ assertNoSymlinkDescendant(eventDir, file, "cancel acknowledgement");
330
+ if (!existsSync(file)) return void 0;
331
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
332
+ if (parsed.operationId !== operationId) throw new Error(`cancel acknowledgement collision: '${file}' holds operation '${parsed.operationId}', not '${operationId}' — use operation ids that stay distinct after filename sanitization`);
333
+ return parsed;
334
+ }
335
+ /**
336
+ * Durably write one acknowledgement record. The shared writer fsyncs the file and containing
337
+ * directory, so a concurrent reader sees the prior complete record or the new complete record.
338
+ *
339
+ * @internal The runtime acknowledger is the only intended writer; clients read via
340
+ * {@link readWorkerCancellation} or {@link cancelWorker}.
341
+ */
342
+ function writeWorkerCancellation(eventDir, record) {
343
+ const dir = workerCancellationsDir(eventDir);
344
+ assertNoSymlinkDescendant(eventDir, dir, "cancel acknowledgement");
345
+ mkdirSync(dir, { recursive: true });
346
+ assertNoSymlinkDescendant(eventDir, dir, "cancel acknowledgement");
347
+ const file = workerCancellationFile(eventDir, record.operationId);
348
+ assertNoSymlinkDescendant(eventDir, file, "cancel acknowledgement");
349
+ writeAtomicDurableFile(file, `${JSON.stringify(record, null, 2)}\n`, { mode: 384 });
350
+ }
351
+ /**
352
+ * Request the cancellation of ONE worker, idempotently, and return the operation's current
353
+ * durable state.
354
+ *
355
+ * The write half of the acknowledged-cancellation contract (`writeWorkerSteer` is the steer
356
+ * analog): append the request to the run's cancellation inbox, where the OWNING manager's
357
+ * acknowledger (its turn loop — the root for label/profile references, the parent manager for an
358
+ * exact node id at any depth) applies it — aborting exactly that worker's subtree and recording
359
+ * what it proved. This function never applies the cancellation itself; writing a
360
+ * request file is not an acknowledgement.
361
+ *
362
+ * Idempotency is a lookup: when an acknowledgement for `operationId` already exists, it is
363
+ * returned AS-IS and nothing is appended — repeating one operation can never apply twice. A
364
+ * retry that changes the worker, source, or reason fails closed. A request the runtime has not
365
+ * answered yet returns `effect: 'unknown'` (never a success); call again with the same
366
+ * `operationId` — or `readWorkerCancellation` — to read the acknowledged result after a
367
+ * reconnect.
368
+ */
369
+ function cancelWorker(eventDir, worker, operationId, options = {}) {
370
+ const ref = worker.trim();
371
+ if (!ref) throw new Error("cancelWorker: worker reference is empty");
372
+ const opId = operationId.trim();
373
+ if (!opId) throw new Error("cancelWorker: operationId is empty");
374
+ const pending = readWorkerCancelRequests(eventDir).find((r) => r.operationId === opId);
375
+ const candidate = {
376
+ operationId: opId,
377
+ worker: ref,
378
+ source: options.source ?? "human",
379
+ ...options.reason === void 0 ? {} : { reason: options.reason }
380
+ };
381
+ if (pending !== void 0) assertSameWorkerCancelRequest(pending, candidate);
382
+ const acknowledged = readWorkerCancellation(eventDir, opId);
383
+ if (acknowledged !== void 0) {
384
+ if (pending === void 0 && options.source !== void 0) throw new Error(`cancelWorker: operation '${opId}' cannot verify source without its admitted request`);
385
+ assertWorkerCancellationMatchesCandidate(acknowledged, candidate);
386
+ return acknowledged;
387
+ }
388
+ const source = options.source ?? "human";
389
+ const request = pending ?? {
390
+ operationId: opId,
391
+ at: (/* @__PURE__ */ new Date()).toISOString(),
392
+ source,
393
+ worker: ref,
394
+ ...options.reason === void 0 ? {} : { reason: options.reason }
395
+ };
396
+ if (!pending) {
397
+ const dir = workerCancellationsDir(eventDir);
398
+ assertNoSymlinkDescendant(eventDir, dir, "cancel request");
399
+ mkdirSync(dir, { recursive: true });
400
+ assertNoSymlinkDescendant(eventDir, dir, "cancel request");
401
+ const file = workerCancelRequestsFile(eventDir);
402
+ assertNoSymlinkDescendant(eventDir, file, "cancel request");
403
+ appendDurableFile(file, `${JSON.stringify(request)}\n`, { mode: 384 });
404
+ appendWorkerControlEvent(eventDir, ref, {
405
+ kind: "cancel-request",
406
+ operationId: opId,
407
+ source,
408
+ queued: true,
409
+ ...options.reason === void 0 ? {} : { reason: options.reason }
410
+ });
411
+ }
412
+ return {
413
+ operationId: opId,
414
+ worker: request.worker,
415
+ effect: "unknown",
416
+ requestedAt: request.at,
417
+ observedAt: request.at,
418
+ ...request.reason === void 0 ? {} : { reason: request.reason },
419
+ detail: "request queued; no runtime acknowledger has answered yet",
420
+ terminated: []
421
+ };
422
+ }
423
+ /** The run-scoped cancel request file of one run — one {@link RunCancelRequest}. */
424
+ function runCancelRequestFile(eventDir) {
425
+ return join(workerCancellationsDir(eventDir), "run.request.json");
426
+ }
427
+ /** The run-scoped acknowledgement file of one run — one {@link RunCancellation}. */
428
+ function runCancellationFile(eventDir) {
429
+ return join(workerCancellationsDir(eventDir), "run.json");
430
+ }
431
+ /** Read the run-scoped cancel request, or `undefined` when none was written. */
432
+ function readRunCancelRequest(eventDir) {
433
+ const dir = workerCancellationsDir(eventDir);
434
+ const file = runCancelRequestFile(eventDir);
435
+ assertNoSymlinkDescendant(eventDir, dir, "run cancel request");
436
+ assertNoSymlinkDescendant(eventDir, file, "run cancel request");
437
+ if (!existsSync(file)) return void 0;
438
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
439
+ if (!isRunCancelRequest(parsed)) throw new Error(`run cancel request '${file}' is not a complete RunCancelRequest`);
440
+ return parsed;
441
+ }
442
+ /**
443
+ * Read the acknowledgement for the run-scoped cancel operation. `undefined` when the runtime has
444
+ * not answered. A record holding a DIFFERENT `operationId` belongs to another operation on the
445
+ * same run — fail loud rather than answer for it.
446
+ */
447
+ function readRunCancellation(eventDir, operationId) {
448
+ const dir = workerCancellationsDir(eventDir);
449
+ const file = runCancellationFile(eventDir);
450
+ assertNoSymlinkDescendant(eventDir, dir, "run cancel acknowledgement");
451
+ assertNoSymlinkDescendant(eventDir, file, "run cancel acknowledgement");
452
+ if (!existsSync(file)) return void 0;
453
+ const parsed = JSON.parse(readFileSync(file, "utf8"));
454
+ if (parsed.operationId !== operationId) throw new Error(`run cancel acknowledgement '${file}' holds operation '${parsed.operationId}', not '${operationId}' — one run carries one run-scoped cancel operation`);
455
+ return parsed;
456
+ }
457
+ /**
458
+ * Durably write the run-scoped acknowledgement. The shared writer fsyncs the file and containing
459
+ * directory, so a concurrent reader sees the prior complete record or the new complete record.
460
+ *
461
+ * @internal The runtime is the only intended writer; clients read via {@link readRunCancellation}
462
+ * or {@link cancelRun}.
463
+ */
464
+ function writeRunCancellation(eventDir, record) {
465
+ const dir = workerCancellationsDir(eventDir);
466
+ assertNoSymlinkDescendant(eventDir, dir, "run cancel acknowledgement");
467
+ mkdirSync(dir, { recursive: true });
468
+ assertNoSymlinkDescendant(eventDir, dir, "run cancel acknowledgement");
469
+ const file = runCancellationFile(eventDir);
470
+ assertNoSymlinkDescendant(eventDir, file, "run cancel acknowledgement");
471
+ writeAtomicDurableFile(file, `${JSON.stringify(record, null, 2)}\n`, { mode: 384 });
472
+ }
473
+ /**
474
+ * Request the cancellation of the WHOLE run, idempotently, and return the operation's current
475
+ * durable state.
476
+ *
477
+ * The run-scoped twin of {@link cancelWorker}: write the request into the run's cancellation
478
+ * directory, where the run's own root manager applies it — aborting the root through the one
479
+ * cascade controller the run already has, so every live worker comes down with it. This function
480
+ * never applies the cancellation itself; writing a request file is not an acknowledgement.
481
+ *
482
+ * Idempotency is a lookup: when an acknowledgement for `operationId` already exists it is returned
483
+ * AS-IS and nothing is written. A retry that changes the source or reason fails closed. A request
484
+ * the runtime has not answered yet returns `effect: 'unknown'` (never a success); call again with
485
+ * the same `operationId` — or {@link readRunCancellation} — to read the acknowledged result
486
+ * after a reconnect.
487
+ *
488
+ * A run carries ONE run-scoped operation: a second request under a different `operationId` throws
489
+ * rather than silently replacing the pending one, because both would claim the same single abort.
490
+ */
491
+ function cancelRun(eventDir, operationId, options = {}) {
492
+ const opId = operationId.trim();
493
+ if (!opId) throw new Error("cancelRun: operationId is empty");
494
+ const pending = readRunCancelRequest(eventDir);
495
+ if (pending !== void 0 && pending.operationId !== opId) throw new Error(`cancelRun: run cancel '${pending.operationId}' is already pending for this run; read it with readRunCancellation instead of issuing '${opId}'`);
496
+ const candidate = {
497
+ operationId: opId,
498
+ source: options.source ?? "human",
499
+ ...options.reason === void 0 ? {} : { reason: options.reason }
500
+ };
501
+ if (pending !== void 0) assertSameRunCancelRequest(pending, candidate);
502
+ const acknowledged = readRunCancellation(eventDir, opId);
503
+ if (acknowledged !== void 0) {
504
+ if (pending === void 0 && options.source !== void 0) throw new Error(`cancelRun: operation '${opId}' cannot verify source without its admitted request`);
505
+ assertRunCancellationMatchesCandidate(acknowledged, candidate);
506
+ return acknowledged;
507
+ }
508
+ const source = options.source ?? "human";
509
+ const request = pending ?? {
510
+ operationId: opId,
511
+ at: (/* @__PURE__ */ new Date()).toISOString(),
512
+ source,
513
+ ...options.reason === void 0 ? {} : { reason: options.reason }
514
+ };
515
+ if (pending === void 0) {
516
+ const dir = workerCancellationsDir(eventDir);
517
+ assertNoSymlinkDescendant(eventDir, dir, "run cancel request");
518
+ mkdirSync(dir, { recursive: true });
519
+ assertNoSymlinkDescendant(eventDir, dir, "run cancel request");
520
+ const file = runCancelRequestFile(eventDir);
521
+ assertNoSymlinkDescendant(eventDir, file, "run cancel request");
522
+ writeAtomicDurableFile(file, `${JSON.stringify(request, null, 2)}\n`, { mode: 384 });
523
+ appendWorkerControlEvent(eventDir, "run", {
524
+ kind: "run-cancel-request",
525
+ operationId: opId,
526
+ source,
527
+ queued: true,
528
+ ...options.reason === void 0 ? {} : { reason: options.reason }
529
+ });
530
+ }
531
+ return {
532
+ operationId: opId,
533
+ effect: "unknown",
534
+ requestedAt: request.at,
535
+ observedAt: request.at,
536
+ ...request.reason === void 0 ? {} : { reason: request.reason },
537
+ detail: "request queued; no runtime acknowledger has answered yet"
538
+ };
539
+ }
540
+ function assertSameWorkerCancelRequest(existing, candidate) {
541
+ if (existing.operationId !== candidate.operationId) throw new Error(`cancelWorker: operation '${candidate.operationId}' has an id collision`);
542
+ if (existing.worker !== candidate.worker) throw new Error(`cancelWorker: operation '${candidate.operationId}' conflicts with its admitted request (worker '${existing.worker}' != '${candidate.worker}')`);
543
+ if (existing.source !== candidate.source) throw new Error(`cancelWorker: operation '${candidate.operationId}' conflicts with its admitted request (source '${existing.source}' != '${candidate.source}')`);
544
+ if (existing.reason !== candidate.reason) throw new Error(`cancelWorker: operation '${candidate.operationId}' conflicts with its admitted request (reason differs)`);
545
+ }
546
+ function assertWorkerCancellationMatchesCandidate(existing, candidate) {
547
+ if (existing.worker !== candidate.worker) throw new Error(`cancelWorker: operation '${candidate.operationId}' conflicts with its acknowledgement (worker '${existing.worker}' != '${candidate.worker}')`);
548
+ if (existing.reason !== candidate.reason) throw new Error(`cancelWorker: operation '${candidate.operationId}' conflicts with its acknowledgement (reason differs)`);
549
+ }
550
+ function assertSameRunCancelRequest(existing, candidate) {
551
+ if (existing.operationId !== candidate.operationId) throw new Error(`cancelRun: operation '${candidate.operationId}' has an id collision`);
552
+ if (existing.source !== candidate.source) throw new Error(`cancelRun: operation '${candidate.operationId}' conflicts with its admitted request (source '${existing.source}' != '${candidate.source}')`);
553
+ if (existing.reason !== candidate.reason) throw new Error(`cancelRun: operation '${candidate.operationId}' conflicts with its admitted request (reason differs)`);
554
+ }
555
+ function assertRunCancellationMatchesCandidate(existing, candidate) {
556
+ if (existing.reason !== candidate.reason) throw new Error(`cancelRun: operation '${candidate.operationId}' conflicts with its acknowledgement (reason differs)`);
557
+ }
558
+ function isRunCancelRequest(value) {
559
+ return typeof value.operationId === "string" && value.operationId.length > 0 && typeof value.at === "string" && typeof value.source === "string" && (value.reason === void 0 || typeof value.reason === "string");
560
+ }
561
+ function isWorkerCancelRequest(value) {
562
+ return typeof value.operationId === "string" && value.operationId.length > 0 && typeof value.at === "string" && typeof value.source === "string" && typeof value.worker === "string" && value.worker.length > 0 && (value.reason === void 0 || typeof value.reason === "string");
563
+ }
564
+ function appendWorkerControlEvent(eventDir, label, event) {
565
+ try {
566
+ mkdirSync(supervisorWorkersDir(eventDir), { recursive: true });
567
+ appendFileSync(workerControlLogFile(eventDir, label), `${JSON.stringify({
568
+ at: (/* @__PURE__ */ new Date()).toISOString(),
569
+ label,
570
+ ...event
571
+ })}\n`, "utf8");
572
+ } catch {}
573
+ }
574
+ function workerSteerRequestDigest(value) {
575
+ return canonicalCandidateDigest({
576
+ kind: "worker-steer-request.v1",
577
+ ...value
578
+ });
579
+ }
580
+ function operationFileHash(operationId) {
581
+ const id = operationId.trim();
582
+ if (!id) throw new Error("steer operationId is empty");
583
+ return createHash("sha256").update(id).digest("hex");
584
+ }
585
+ function admitSteerRequest(file, request) {
586
+ const existing = readWorkerSteerRequestFile(file);
587
+ if (existing !== void 0) {
588
+ assertSameSteerRequest(existing, request);
589
+ return {
590
+ request: existing,
591
+ replayed: true
592
+ };
593
+ }
594
+ if (publishExclusiveDurableFile(file, `${JSON.stringify(request, null, 2)}\n`)) return {
595
+ request,
596
+ replayed: false
597
+ };
598
+ const winner = readWorkerSteerRequestFile(file);
599
+ if (winner === void 0) throw new Error("steer request publication lost its winner");
600
+ assertSameSteerRequest(winner, request);
601
+ return {
602
+ request: winner,
603
+ replayed: true
604
+ };
605
+ }
606
+ function readWorkerSteerRequest(eventDir, operationId) {
607
+ const file = workerSteerRequestFile(eventDir, operationId);
608
+ assertNoSymlinkDescendant(eventDir, file, "steer request");
609
+ const request = readWorkerSteerRequestFile(file);
610
+ if (request !== void 0 && request.operationId !== operationId) throw new Error("steer request file belongs to another operation");
611
+ return request;
612
+ }
613
+ function readWorkerSteerRequestFile(file) {
614
+ if (!existsSync(file)) return void 0;
615
+ return parseWorkerSteerRequest(JSON.parse(readFileSync(file, "utf8")));
616
+ }
617
+ function parseWorkerSteerRequest(value) {
618
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("steer request is malformed");
619
+ const request = value;
620
+ if (request.schemaVersion !== 1 || typeof request.operationId !== "string" || request.operationId.length === 0 || typeof request.requestDigest !== "string" || typeof request.at !== "string" || typeof request.source !== "string" || request.source.length === 0 || typeof request.worker !== "string" || request.worker.length === 0 || typeof request.message !== "string" || request.message.trim().length === 0 || typeof request.interrupt !== "boolean") throw new Error("steer request is malformed");
621
+ const exact = request;
622
+ const expected = workerSteerRequestDigest({
623
+ operationId: exact.operationId,
624
+ worker: exact.worker,
625
+ message: exact.message,
626
+ source: exact.source,
627
+ interrupt: exact.interrupt
628
+ });
629
+ if (exact.requestDigest !== expected) throw new Error("steer request digest does not match");
630
+ return exact;
631
+ }
632
+ function parseWorkerSteerAcknowledgement(value) {
633
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("steer acknowledgement is malformed");
634
+ const record = value;
635
+ if (record.schemaVersion !== 1 || typeof record.operationId !== "string" || record.operationId.length === 0 || typeof record.requestDigest !== "string" || typeof record.worker !== "string" || record.worker.length === 0 || record.effect !== "unknown" && record.effect !== "delivered" && record.effect !== "not_live" && record.effect !== "unsupported" && record.effect !== "refused" || typeof record.requestedAt !== "string" || typeof record.observedAt !== "string" || typeof record.detail !== "string") throw new Error("steer acknowledgement is malformed");
636
+ return record;
637
+ }
638
+ function assertSameSteerRequest(existing, candidate) {
639
+ if (existing.operationId !== candidate.operationId) throw new Error("steer operation file collision");
640
+ if (existing.requestDigest !== candidate.requestDigest) throw new Error(`writeWorkerSteer: operation '${candidate.operationId}' conflicts with its admitted request`);
641
+ }
642
+ //#endregion
643
+ export { writeWorkerCancellation as A, workerInboxFileFromEventDir as C, workerSteerRequestsDir as D, workerSteerRequestFile as E, writeWorkerSteerAcknowledgement as M, workerSteersDir as O, workerInboxFile as S, workerSteerAcknowledgementsDir as T, supervisorWorkersDir as _, legacySupervisorRunsRoot as a, workerCancellationsDir as b, readWorkerCancelRequests as c, readWorkerSteerRequests as d, runCancelRequestFile as f, supervisorRunsRoot as g, supervisorRunDir as h, legacySupervisorRunDir as i, writeWorkerSteer as j, writeRunCancellation as k, readWorkerCancellation as l, safeWorkerFile as m, cancelWorker as n, readRunCancelRequest as o, runCancellationFile as p, claimWorkerSteerDelivery as r, readRunCancellation as s, cancelRun as t, readWorkerSteerAcknowledgement as u, workerCancelRequestsFile as v, workerSteerAcknowledgementFile as w, workerControlLogFile as x, workerCancellationFile as y };
644
+
645
+ //# sourceMappingURL=run-layout-Cd_l2XWV.js.map