@lumenflow/cli 3.14.0 → 3.16.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 (143) hide show
  1. package/dist/chunk-2D2VOCA4.js +37 -0
  2. package/dist/chunk-2D5KFYGX.js +284 -0
  3. package/dist/chunk-2GXVIN57.js +14072 -0
  4. package/dist/chunk-2MQ7HZWZ.js +26 -0
  5. package/dist/chunk-2UFQ3A3C.js +643 -0
  6. package/dist/chunk-3RG5ZIWI.js +10 -0
  7. package/dist/chunk-4N74J3UT.js +15 -0
  8. package/dist/chunk-5GTOXFYR.js +392 -0
  9. package/dist/chunk-5VY6MQMC.js +240 -0
  10. package/dist/chunk-67XVPMRY.js +1297 -0
  11. package/dist/chunk-6HO4GWJE.js +164 -0
  12. package/dist/chunk-6W5XHWYV.js +1890 -0
  13. package/dist/chunk-6X4EMYJQ.js +64 -0
  14. package/dist/chunk-6XYXI2NQ.js +772 -0
  15. package/dist/chunk-7ANSOV6Q.js +285 -0
  16. package/dist/chunk-A624LFLB.js +1380 -0
  17. package/dist/chunk-ADN5NHG4.js +126 -0
  18. package/dist/chunk-B7YJYJKG.js +33 -0
  19. package/dist/chunk-CCLHCPKG.js +210 -0
  20. package/dist/chunk-CK36VROC.js +1584 -0
  21. package/dist/chunk-D3UOFRSB.js +81 -0
  22. package/dist/chunk-DFR4DJBM.js +230 -0
  23. package/dist/chunk-DSYBDHYH.js +79 -0
  24. package/dist/chunk-DWMLTXKQ.js +1176 -0
  25. package/dist/chunk-E3REJTAJ.js +28 -0
  26. package/dist/chunk-EA3IVO64.js +633 -0
  27. package/dist/chunk-EK2AKZKD.js +55 -0
  28. package/dist/chunk-ELD7JTTT.js +343 -0
  29. package/dist/chunk-EX6TT2XI.js +195 -0
  30. package/dist/chunk-EXINSFZE.js +82 -0
  31. package/dist/chunk-EZ6ZBYBM.js +510 -0
  32. package/dist/chunk-FBKAPTJ2.js +16 -0
  33. package/dist/chunk-FVLV5RYH.js +1118 -0
  34. package/dist/chunk-GDNSBQVK.js +2485 -0
  35. package/dist/chunk-GPQHMBNN.js +278 -0
  36. package/dist/chunk-GTFJB67L.js +68 -0
  37. package/dist/chunk-HANJXVKW.js +1127 -0
  38. package/dist/chunk-HEVS5YLD.js +269 -0
  39. package/dist/chunk-HMEVZKPQ.js +9 -0
  40. package/dist/chunk-HRGSYNLM.js +3511 -0
  41. package/dist/chunk-ISZR5N4K.js +60 -0
  42. package/dist/chunk-J6SUPR2C.js +226 -0
  43. package/dist/chunk-JERYVEIZ.js +244 -0
  44. package/dist/chunk-JHHWGL2N.js +87 -0
  45. package/dist/chunk-JONWQUB5.js +775 -0
  46. package/dist/chunk-K2DIWWDM.js +1766 -0
  47. package/dist/chunk-KY4PGL5V.js +969 -0
  48. package/dist/chunk-L737LQ4C.js +1285 -0
  49. package/dist/chunk-LFTWYIB2.js +497 -0
  50. package/dist/chunk-LV47RFNJ.js +41 -0
  51. package/dist/chunk-MKSAITI7.js +15 -0
  52. package/dist/chunk-MZ7RKIX4.js +212 -0
  53. package/dist/chunk-NAP6CFSO.js +84 -0
  54. package/dist/chunk-ND6MY37M.js +16 -0
  55. package/dist/chunk-NMG736UR.js +683 -0
  56. package/dist/chunk-NRAXROED.js +32 -0
  57. package/dist/chunk-NRIZR3A7.js +690 -0
  58. package/dist/chunk-NX43BG3M.js +233 -0
  59. package/dist/chunk-O645XLSI.js +297 -0
  60. package/dist/chunk-OMJD6A3S.js +235 -0
  61. package/dist/chunk-QB6SJD4T.js +430 -0
  62. package/dist/chunk-QFSTL4J3.js +276 -0
  63. package/dist/chunk-QLGDFMFX.js +212 -0
  64. package/dist/chunk-RIAAGL2E.js +13 -0
  65. package/dist/chunk-RWO5XMZ6.js +86 -0
  66. package/dist/chunk-RXRKBBSM.js +149 -0
  67. package/dist/chunk-RZOZMML6.js +363 -0
  68. package/dist/chunk-U7I7FS7T.js +113 -0
  69. package/dist/chunk-UI42RODY.js +717 -0
  70. package/dist/chunk-UTVMVSCO.js +519 -0
  71. package/dist/chunk-V6OJGLBA.js +1746 -0
  72. package/dist/chunk-W2JHVH7D.js +152 -0
  73. package/dist/chunk-WD3Y7VQN.js +280 -0
  74. package/dist/chunk-WOCTQ5MS.js +303 -0
  75. package/dist/chunk-WZR3ZUNN.js +696 -0
  76. package/dist/chunk-XGI665H7.js +150 -0
  77. package/dist/chunk-XKY65P2T.js +304 -0
  78. package/dist/chunk-Y4CQZY65.js +57 -0
  79. package/dist/chunk-YFEXKLVE.js +194 -0
  80. package/dist/chunk-YHO3HS5X.js +287 -0
  81. package/dist/chunk-YLS7AZSX.js +738 -0
  82. package/dist/chunk-ZE473AO6.js +49 -0
  83. package/dist/chunk-ZF747T3O.js +644 -0
  84. package/dist/chunk-ZHCZHZH3.js +43 -0
  85. package/dist/chunk-ZZNZX2XY.js +87 -0
  86. package/dist/constants-7QAP3VQ4.js +23 -0
  87. package/dist/dist-IY3UUMWK.js +33 -0
  88. package/dist/docs-sync.js +60 -25
  89. package/dist/docs-sync.js.map +1 -1
  90. package/dist/gates-runners.js +43 -2
  91. package/dist/gates-runners.js.map +1 -1
  92. package/dist/init-templates.js +26 -219
  93. package/dist/init-templates.js.map +1 -1
  94. package/dist/invariants-runner-W5RGHCSU.js +27 -0
  95. package/dist/lane-lock-6J36HD5O.js +35 -0
  96. package/dist/lumenflow-upgrade.js +60 -0
  97. package/dist/lumenflow-upgrade.js.map +1 -1
  98. package/dist/mem-checkpoint-core-EANG2GVN.js +14 -0
  99. package/dist/mem-signal-core-2LZ2WYHW.js +19 -0
  100. package/dist/memory-store-OLB5FO7K.js +18 -0
  101. package/dist/plan-edit.js +19 -24
  102. package/dist/plan-edit.js.map +1 -1
  103. package/dist/plan-promote.js +15 -23
  104. package/dist/plan-promote.js.map +1 -1
  105. package/dist/plan-resolve.js +111 -0
  106. package/dist/plan-resolve.js.map +1 -0
  107. package/dist/public-manifest.js +2 -2
  108. package/dist/public-manifest.js.map +1 -1
  109. package/dist/service-6BYCOCO5.js +13 -0
  110. package/dist/spawn-policy-resolver-NTSZYQ6R.js +17 -0
  111. package/dist/spawn-task-builder-R4E2BHSW.js +22 -0
  112. package/dist/sync-templates.js +12 -0
  113. package/dist/sync-templates.js.map +1 -1
  114. package/dist/wu-claim-validation.js +9 -1
  115. package/dist/wu-claim-validation.js.map +1 -1
  116. package/dist/wu-done-pr-WLFFFEPJ.js +25 -0
  117. package/dist/wu-done-validation-3J5E36FE.js +30 -0
  118. package/dist/wu-done.js +42 -1
  119. package/dist/wu-done.js.map +1 -1
  120. package/dist/wu-duplicate-id-detector-5S7JHELK.js +232 -0
  121. package/dist/wu-edit-operations.js +7 -6
  122. package/dist/wu-edit-operations.js.map +1 -1
  123. package/dist/wu-edit.js +23 -3
  124. package/dist/wu-edit.js.map +1 -1
  125. package/dist/wu-spawn-prompt-builders.js +38 -1
  126. package/dist/wu-spawn-prompt-builders.js.map +1 -1
  127. package/package.json +8 -8
  128. package/packs/sidekick/.turbo/turbo-build.log +1 -1
  129. package/packs/sidekick/.turbo/turbo-typecheck.log +4 -0
  130. package/packs/sidekick/package.json +1 -1
  131. package/packs/software-delivery/.turbo/turbo-build.log +1 -1
  132. package/packs/software-delivery/.turbo/turbo-typecheck.log +4 -0
  133. package/packs/software-delivery/package.json +1 -1
  134. package/templates/core/AGENTS.md.template +19 -0
  135. package/templates/core/LUMENFLOW.md.template +13 -2
  136. package/templates/core/UPGRADING.md.template +6 -6
  137. package/templates/core/ai/onboarding/first-15-mins.md.template +1 -1
  138. package/templates/core/ai/onboarding/first-wu-mistakes.md.template +10 -0
  139. package/templates/core/ai/onboarding/quick-ref-commands.md.template +11 -8
  140. package/templates/core/ai/onboarding/starting-prompt.md.template +1 -1
  141. package/templates/core/ai/onboarding/wu-sizing-guide.md.template +11 -2
  142. package/templates/vendors/cursor/.cursor/rules/lumenflow.md.template +9 -1
  143. package/templates/vendors/windsurf/.windsurf/rules/lumenflow.md.template +9 -1
@@ -0,0 +1,3511 @@
1
+ import {
2
+ BASE64_ENCODING,
3
+ DEFAULT_KERNEL_RUNTIME_VERSION,
4
+ DEFAULT_WORKSPACE_CONFIG_HASH,
5
+ EXECUTION_METADATA_KEYS,
6
+ ExecutionContextSchema,
7
+ KERNEL_EVENT_KINDS,
8
+ KERNEL_POLICY_IDS,
9
+ KERNEL_RUNTIME_EVENTS_DIR_NAME,
10
+ KERNEL_RUNTIME_EVENTS_FILE_NAME,
11
+ KERNEL_RUNTIME_EVENTS_LOCK_FILE_NAME,
12
+ KERNEL_RUNTIME_EVIDENCE_DIR_NAME,
13
+ KERNEL_RUNTIME_ROOT_DIR_NAME,
14
+ KERNEL_RUNTIME_TASKS_DIR_NAME,
15
+ KernelEventSchema,
16
+ LUMENFLOW_DIR_NAME,
17
+ LUMENFLOW_SCOPE_NAME,
18
+ PACKAGES_DIR_NAME,
19
+ PACKS_DIR_NAME,
20
+ PACK_MANIFEST_FILE_NAME,
21
+ POLICY_TRIGGERS,
22
+ PackLoader,
23
+ PolicyEngine,
24
+ RESERVED_FRAMEWORK_SCOPE_GLOB,
25
+ RESERVED_FRAMEWORK_SCOPE_PREFIX,
26
+ RESERVED_FRAMEWORK_SCOPE_ROOT,
27
+ RUN_STATUSES,
28
+ RunSchema,
29
+ SHA256_ALGORITHM,
30
+ SHA256_HEX_REGEX,
31
+ TOOL_ERROR_CODES,
32
+ TOOL_HANDLER_KINDS,
33
+ TOOL_TRACE_KINDS,
34
+ TaskSpecSchema,
35
+ TaskStateSchema,
36
+ ToolCapabilitySchema,
37
+ ToolOutputSchema,
38
+ ToolScopeSchema,
39
+ ToolTraceEntrySchema,
40
+ UTF8_ENCODING,
41
+ WORKSPACE_CONFIG_HASH_CONTEXT_KEYS,
42
+ WORKSPACE_FILE_NAME,
43
+ WorkspaceSpecSchema,
44
+ isRunLifecycleEventKind,
45
+ isTaskEventKind,
46
+ resolvePackToolEntryPath,
47
+ validateWorkspaceRootKeys
48
+ } from "./chunk-HANJXVKW.js";
49
+
50
+ // ../kernel/dist/runtime/kernel-runtime.js
51
+ import { randomBytes } from "crypto";
52
+ import { access, mkdir as mkdir3, open as open3, readFile as readFile4, readdir as readdir2, rm as rm3 } from "fs/promises";
53
+ import path4 from "path";
54
+ import { fileURLToPath as fileURLToPath2 } from "url";
55
+ import YAML from "yaml";
56
+ import { z as z4 } from "zod";
57
+
58
+ // ../kernel/dist/canonical-json.js
59
+ import { createHash } from "crypto";
60
+ import { parse as parseYaml } from "yaml";
61
+ function toCanonicalValue(value) {
62
+ let normalized;
63
+ if (value === null) {
64
+ normalized = null;
65
+ } else if (typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
66
+ normalized = value;
67
+ } else if (value instanceof Date) {
68
+ normalized = value.toISOString();
69
+ } else if (Array.isArray(value)) {
70
+ normalized = value.map((item) => toCanonicalValue(item));
71
+ } else if (typeof value === "object") {
72
+ const objectValue = value;
73
+ const entries = Object.keys(objectValue).sort((left, right) => left.localeCompare(right)).map((key) => [key, toCanonicalValue(objectValue[key])]);
74
+ const sorted = {};
75
+ for (const [key, entryValue] of entries) {
76
+ sorted[key] = entryValue;
77
+ }
78
+ normalized = sorted;
79
+ } else {
80
+ throw new TypeError(`Unsupported value in canonical_json input: ${String(value)}`);
81
+ }
82
+ return normalized;
83
+ }
84
+ function canonicalStringify(source) {
85
+ const parsed = typeof source === "string" ? parseYaml(source) : source;
86
+ const canonical = toCanonicalValue(parsed);
87
+ return JSON.stringify(canonical);
88
+ }
89
+ function canonical_json(source) {
90
+ const canonical = canonicalStringify(source);
91
+ return createHash(SHA256_ALGORITHM).update(canonical, UTF8_ENCODING).digest("hex");
92
+ }
93
+
94
+ // ../kernel/dist/event-store/index.js
95
+ import { mkdirSync, watch } from "fs";
96
+ import { appendFile, mkdir, open, readFile, rm, stat } from "fs/promises";
97
+ import { basename, dirname } from "path";
98
+ var DEFAULT_LOCK_RETRY_DELAY_MS = 20;
99
+ var DEFAULT_LOCK_MAX_RETRIES = 250;
100
+ var PROCESS_EXISTS_SIGNAL = 0;
101
+ var SUBSCRIPTION_DEBOUNCE_MS = 25;
102
+ var SUBSCRIPTION_MAX_DRAIN_PASSES = 8;
103
+ var DEFAULT_REPLAY_LIMIT = 100;
104
+ var MAX_REPLAY_LIMIT = 500;
105
+ function sleep(ms) {
106
+ return new Promise((resolve2) => {
107
+ setTimeout(resolve2, ms);
108
+ });
109
+ }
110
+ function toTimestampMillis(value) {
111
+ const parsed = Date.parse(value);
112
+ return Number.isNaN(parsed) ? 0 : parsed;
113
+ }
114
+ function hasTaskId(event) {
115
+ return "task_id" in event;
116
+ }
117
+ function isRunLifecycleEvent(event) {
118
+ return isRunLifecycleEventKind(event.kind);
119
+ }
120
+ function parseReplayFilter(filter) {
121
+ const kinds = Array.isArray(filter.kind) ? new Set(filter.kind) : filter.kind ? /* @__PURE__ */ new Set([filter.kind]) : null;
122
+ const since = filter.sinceTimestamp ? toTimestampMillis(filter.sinceTimestamp) : null;
123
+ const until = filter.untilTimestamp ? toTimestampMillis(filter.untilTimestamp) : null;
124
+ return { kinds, since, until };
125
+ }
126
+ function eventMatchesFilter(event, filter, parsedFilter) {
127
+ if (filter.taskId) {
128
+ if (!hasTaskId(event)) {
129
+ return false;
130
+ }
131
+ if (event.task_id !== filter.taskId) {
132
+ return false;
133
+ }
134
+ }
135
+ if (parsedFilter.kinds && !parsedFilter.kinds.has(event.kind)) {
136
+ return false;
137
+ }
138
+ const ts = toTimestampMillis(event.timestamp);
139
+ if (parsedFilter.since !== null && ts < parsedFilter.since) {
140
+ return false;
141
+ }
142
+ if (parsedFilter.until !== null && ts > parsedFilter.until) {
143
+ return false;
144
+ }
145
+ return true;
146
+ }
147
+ function createSyntheticTaskSpec(taskId) {
148
+ return {
149
+ id: taskId,
150
+ workspace_id: "workspace-default",
151
+ lane_id: "lane-default",
152
+ domain: "kernel",
153
+ title: `Task ${taskId}`,
154
+ description: `Synthetic projection seed for ${taskId}`,
155
+ acceptance: ["Synthetic projection"],
156
+ declared_scopes: [],
157
+ risk: "low",
158
+ type: "runtime",
159
+ priority: "P3",
160
+ created: "1970-01-01"
161
+ };
162
+ }
163
+ function verifyTaskSpecHash(taskSpec, events) {
164
+ const created = events.find((event) => event.kind === KERNEL_EVENT_KINDS.TASK_CREATED);
165
+ if (!created) {
166
+ return;
167
+ }
168
+ const expectedHash = created.spec_hash;
169
+ const actualHash = canonical_json(taskSpec);
170
+ if (expectedHash !== actualHash) {
171
+ throw new Error(`Spec hash mismatch for ${taskSpec.id}: expected ${expectedHash}, actual ${actualHash}`);
172
+ }
173
+ }
174
+ function reduceRunEvent(event, runs) {
175
+ const runId = event.run_id;
176
+ const existing = runs.get(runId) ?? {
177
+ run_id: runId,
178
+ task_id: event.task_id,
179
+ status: RUN_STATUSES.PLANNED,
180
+ started_at: event.timestamp,
181
+ by: "unknown",
182
+ session_id: "unknown"
183
+ };
184
+ let nextRun = { ...existing };
185
+ if (event.kind === KERNEL_EVENT_KINDS.RUN_STARTED) {
186
+ nextRun = RunSchema.parse({
187
+ run_id: runId,
188
+ task_id: event.task_id,
189
+ status: RUN_STATUSES.EXECUTING,
190
+ started_at: event.timestamp,
191
+ by: event.by,
192
+ session_id: event.session_id
193
+ });
194
+ } else if (event.kind === KERNEL_EVENT_KINDS.RUN_PAUSED) {
195
+ nextRun = RunSchema.parse({
196
+ ...existing,
197
+ status: RUN_STATUSES.PAUSED
198
+ });
199
+ } else if (event.kind === KERNEL_EVENT_KINDS.RUN_FAILED) {
200
+ nextRun = RunSchema.parse({
201
+ ...existing,
202
+ status: RUN_STATUSES.FAILED,
203
+ completed_at: event.timestamp
204
+ });
205
+ } else if (event.kind === KERNEL_EVENT_KINDS.RUN_SUCCEEDED) {
206
+ nextRun = RunSchema.parse({
207
+ ...existing,
208
+ status: RUN_STATUSES.SUCCEEDED,
209
+ completed_at: event.timestamp
210
+ });
211
+ }
212
+ runs.set(runId, nextRun);
213
+ return runId;
214
+ }
215
+ function projectTaskState(taskSpec, events) {
216
+ const sorted = [...events].sort((left, right) => toTimestampMillis(left.timestamp) - toTimestampMillis(right.timestamp));
217
+ const state = {
218
+ task_id: taskSpec.id,
219
+ status: "ready",
220
+ run_count: 0
221
+ };
222
+ const runs = /* @__PURE__ */ new Map();
223
+ let currentRunId;
224
+ for (const event of sorted) {
225
+ if (isTaskEventKind(event.kind)) {
226
+ if (event.kind === KERNEL_EVENT_KINDS.TASK_CREATED) {
227
+ state.status = "ready";
228
+ } else if (event.kind === KERNEL_EVENT_KINDS.TASK_CLAIMED) {
229
+ state.status = "active";
230
+ state.claimed_at = event.timestamp;
231
+ state.claimed_by = event.by;
232
+ state.session_id = event.session_id;
233
+ state.blocked_reason = void 0;
234
+ } else if (event.kind === KERNEL_EVENT_KINDS.TASK_BLOCKED) {
235
+ state.status = "blocked";
236
+ state.blocked_reason = event.reason;
237
+ } else if (event.kind === KERNEL_EVENT_KINDS.TASK_UNBLOCKED) {
238
+ state.status = "active";
239
+ state.blocked_reason = void 0;
240
+ } else if (event.kind === KERNEL_EVENT_KINDS.TASK_WAITING) {
241
+ state.status = "waiting";
242
+ state.blocked_reason = event.reason;
243
+ } else if (event.kind === KERNEL_EVENT_KINDS.TASK_RESUMED) {
244
+ state.status = "active";
245
+ state.blocked_reason = void 0;
246
+ } else if (event.kind === KERNEL_EVENT_KINDS.TASK_COMPLETED) {
247
+ state.status = "done";
248
+ state.completed_at = event.timestamp;
249
+ } else if (event.kind === KERNEL_EVENT_KINDS.TASK_RELEASED) {
250
+ state.status = "ready";
251
+ state.session_id = void 0;
252
+ }
253
+ }
254
+ if (isRunLifecycleEvent(event)) {
255
+ currentRunId = reduceRunEvent(event, runs);
256
+ }
257
+ }
258
+ state.run_count = runs.size;
259
+ state.current_run = currentRunId ? runs.get(currentRunId) : void 0;
260
+ return TaskStateSchema.parse(state);
261
+ }
262
+ var EventStore = class {
263
+ eventsFilePath;
264
+ lockFilePath;
265
+ lockRetryDelayMs;
266
+ lockMaxRetries;
267
+ taskSpecLoader;
268
+ indexesHydrated = false;
269
+ orderedEvents = [];
270
+ byTask = /* @__PURE__ */ new Map();
271
+ byKind = /* @__PURE__ */ new Map();
272
+ byTimestamp = /* @__PURE__ */ new Map();
273
+ constructor(options) {
274
+ this.eventsFilePath = options.eventsFilePath;
275
+ this.lockFilePath = options.lockFilePath;
276
+ this.lockRetryDelayMs = options.lockRetryDelayMs ?? DEFAULT_LOCK_RETRY_DELAY_MS;
277
+ this.lockMaxRetries = options.lockMaxRetries ?? DEFAULT_LOCK_MAX_RETRIES;
278
+ this.taskSpecLoader = options.taskSpecLoader;
279
+ }
280
+ async append(event) {
281
+ await this.appendAll([event]);
282
+ }
283
+ async appendAll(events) {
284
+ if (events.length === 0) {
285
+ return;
286
+ }
287
+ const validatedEvents = events.map((event) => {
288
+ const parsed = KernelEventSchema.safeParse(event);
289
+ if (!parsed.success) {
290
+ throw new Error(`KernelEvent validation failed: ${parsed.error.message}`);
291
+ }
292
+ return parsed.data;
293
+ });
294
+ const payload = validatedEvents.map((event) => `${JSON.stringify(event)}
295
+ `).join("");
296
+ await this.withFileLock(async () => {
297
+ await mkdir(dirname(this.eventsFilePath), { recursive: true });
298
+ await appendFile(this.eventsFilePath, payload, UTF8_ENCODING);
299
+ });
300
+ if (!this.indexesHydrated) {
301
+ await this.reloadFromDisk();
302
+ return;
303
+ }
304
+ for (const event of validatedEvents) {
305
+ this.applyEventToIndexes(event);
306
+ }
307
+ }
308
+ async replay(filter = {}) {
309
+ await this.reloadFromDisk();
310
+ const parsedFilter = parseReplayFilter(filter);
311
+ const cursorMs = filter.cursor ? toTimestampMillis(filter.cursor) : null;
312
+ const effectiveLimit = Math.min(Math.max(1, filter.limit ?? DEFAULT_REPLAY_LIMIT), MAX_REPLAY_LIMIT);
313
+ const matched = [];
314
+ let totalMatchingAfterPage = 0;
315
+ for (const event of this.orderedEvents) {
316
+ if (!eventMatchesFilter(event, filter, parsedFilter)) {
317
+ continue;
318
+ }
319
+ if (cursorMs !== null && toTimestampMillis(event.timestamp) <= cursorMs) {
320
+ continue;
321
+ }
322
+ if (matched.length < effectiveLimit) {
323
+ matched.push(event);
324
+ } else {
325
+ totalMatchingAfterPage += 1;
326
+ break;
327
+ }
328
+ }
329
+ const hasMore = totalMatchingAfterPage > 0;
330
+ const lastEvent = matched[matched.length - 1];
331
+ const nextCursor = hasMore && lastEvent ? lastEvent.timestamp : null;
332
+ return { events: matched, nextCursor };
333
+ }
334
+ subscribe(filter = {}, callback) {
335
+ const parsedFilter = parseReplayFilter(filter);
336
+ const watchedDirectory = dirname(this.eventsFilePath);
337
+ const watchedFileName = basename(this.eventsFilePath);
338
+ let isDisposed = false;
339
+ let isDraining = false;
340
+ let shouldDrainAgain = false;
341
+ let drainTimer = null;
342
+ let initializedOffset = false;
343
+ let offset = 0;
344
+ let trailingPartialLine = "";
345
+ const initialOffsetPromise = this.getCurrentFileSize();
346
+ const safeInvoke = async (event) => {
347
+ try {
348
+ await callback(event);
349
+ } catch {
350
+ }
351
+ };
352
+ const processTextChunk = async (text, didTruncate) => {
353
+ if (didTruncate) {
354
+ trailingPartialLine = "";
355
+ }
356
+ const combinedText = trailingPartialLine + text;
357
+ const lines = combinedText.split("\n");
358
+ trailingPartialLine = lines.pop() ?? "";
359
+ for (const rawLine of lines) {
360
+ const line = rawLine.trim();
361
+ if (!line) {
362
+ continue;
363
+ }
364
+ let parsed;
365
+ try {
366
+ parsed = JSON.parse(line);
367
+ } catch {
368
+ continue;
369
+ }
370
+ const eventResult = KernelEventSchema.safeParse(parsed);
371
+ if (!eventResult.success) {
372
+ continue;
373
+ }
374
+ if (eventMatchesFilter(eventResult.data, filter, parsedFilter)) {
375
+ await safeInvoke(eventResult.data);
376
+ }
377
+ }
378
+ };
379
+ const drain = async () => {
380
+ if (isDisposed || isDraining) {
381
+ return;
382
+ }
383
+ isDraining = true;
384
+ try {
385
+ if (!initializedOffset) {
386
+ offset = await initialOffsetPromise;
387
+ initializedOffset = true;
388
+ }
389
+ let passes = 0;
390
+ do {
391
+ shouldDrainAgain = false;
392
+ const readResult = await this.readTailFromOffset(offset);
393
+ offset = readResult.nextOffset;
394
+ if (readResult.text.length > 0 || readResult.didTruncate) {
395
+ await processTextChunk(readResult.text, readResult.didTruncate);
396
+ }
397
+ passes += 1;
398
+ if (readResult.text.length === 0 && !readResult.didTruncate) {
399
+ break;
400
+ }
401
+ } while (!isDisposed && shouldDrainAgain && passes < SUBSCRIPTION_MAX_DRAIN_PASSES);
402
+ } finally {
403
+ isDraining = false;
404
+ }
405
+ };
406
+ const scheduleDrain = () => {
407
+ if (isDisposed) {
408
+ return;
409
+ }
410
+ shouldDrainAgain = true;
411
+ if (drainTimer) {
412
+ return;
413
+ }
414
+ drainTimer = setTimeout(() => {
415
+ drainTimer = null;
416
+ void drain();
417
+ }, SUBSCRIPTION_DEBOUNCE_MS);
418
+ };
419
+ mkdirSync(watchedDirectory, { recursive: true });
420
+ const watcher = watch(watchedDirectory, { persistent: false }, (eventType, filename) => {
421
+ if (isDisposed) {
422
+ return;
423
+ }
424
+ if (eventType !== "change" && eventType !== "rename") {
425
+ return;
426
+ }
427
+ if (filename && filename.toString() !== watchedFileName) {
428
+ return;
429
+ }
430
+ scheduleDrain();
431
+ });
432
+ watcher.on("error", () => {
433
+ });
434
+ return {
435
+ dispose: () => {
436
+ if (isDisposed) {
437
+ return;
438
+ }
439
+ isDisposed = true;
440
+ if (drainTimer) {
441
+ clearTimeout(drainTimer);
442
+ drainTimer = null;
443
+ }
444
+ watcher.close();
445
+ }
446
+ };
447
+ }
448
+ async project(taskId) {
449
+ await this.reloadFromDisk();
450
+ const taskEvents = this.byTask.get(taskId) ?? [];
451
+ let taskSpec = null;
452
+ if (this.taskSpecLoader) {
453
+ taskSpec = await this.taskSpecLoader(taskId);
454
+ }
455
+ const spec = taskSpec ?? createSyntheticTaskSpec(taskId);
456
+ verifyTaskSpecHash(spec, taskEvents);
457
+ return projectTaskState(spec, taskEvents);
458
+ }
459
+ getByTask(taskId) {
460
+ return [...this.byTask.get(taskId) ?? []];
461
+ }
462
+ getByKind(kind) {
463
+ return [...this.byKind.get(kind) ?? []];
464
+ }
465
+ getByTimestamp(timestamp) {
466
+ return [...this.byTimestamp.get(timestamp) ?? []];
467
+ }
468
+ buildLockMetadata() {
469
+ return {
470
+ pid: process.pid,
471
+ acquired_at: (/* @__PURE__ */ new Date()).toISOString()
472
+ };
473
+ }
474
+ parseLockMetadata(raw) {
475
+ if (!raw.trim()) {
476
+ return null;
477
+ }
478
+ try {
479
+ const parsed = JSON.parse(raw);
480
+ if (typeof parsed.pid === "number" && Number.isInteger(parsed.pid) && parsed.pid > 0 && typeof parsed.acquired_at === "string" && parsed.acquired_at.length > 0) {
481
+ return {
482
+ pid: parsed.pid,
483
+ acquired_at: parsed.acquired_at
484
+ };
485
+ }
486
+ return null;
487
+ } catch {
488
+ return null;
489
+ }
490
+ }
491
+ isProcessAlive(pid) {
492
+ try {
493
+ process.kill(pid, PROCESS_EXISTS_SIGNAL);
494
+ return true;
495
+ } catch (error) {
496
+ const nodeError = error;
497
+ if (nodeError.code === "ESRCH") {
498
+ return false;
499
+ }
500
+ if (nodeError.code === "EPERM") {
501
+ return true;
502
+ }
503
+ return true;
504
+ }
505
+ }
506
+ async recoverStaleLockIfNeeded() {
507
+ let metadataRaw;
508
+ try {
509
+ metadataRaw = await readFile(this.lockFilePath, UTF8_ENCODING);
510
+ } catch (error) {
511
+ const nodeError = error;
512
+ if (nodeError.code === "ENOENT") {
513
+ return true;
514
+ }
515
+ throw error;
516
+ }
517
+ const metadata = this.parseLockMetadata(metadataRaw);
518
+ if (!metadata) {
519
+ return false;
520
+ }
521
+ if (this.isProcessAlive(metadata.pid)) {
522
+ return false;
523
+ }
524
+ await rm(this.lockFilePath, { force: true });
525
+ return true;
526
+ }
527
+ async cleanupLockHandle(handle) {
528
+ if (!handle) {
529
+ return;
530
+ }
531
+ try {
532
+ await handle.close();
533
+ } catch {
534
+ }
535
+ await rm(this.lockFilePath, { force: true });
536
+ }
537
+ async reloadFromDisk() {
538
+ let content;
539
+ try {
540
+ content = await readFile(this.eventsFilePath, UTF8_ENCODING);
541
+ } catch (error) {
542
+ const nodeError = error;
543
+ if (nodeError.code === "ENOENT") {
544
+ this.resetIndexes();
545
+ this.indexesHydrated = true;
546
+ return;
547
+ }
548
+ throw error;
549
+ }
550
+ this.resetIndexes();
551
+ const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
552
+ for (let index = 0; index < lines.length; index += 1) {
553
+ const line = lines[index];
554
+ if (line === void 0) {
555
+ continue;
556
+ }
557
+ const parsed = (() => {
558
+ try {
559
+ return JSON.parse(line);
560
+ } catch (error) {
561
+ throw new Error(`Malformed JSON at line ${index + 1}: ${error.message}`, {
562
+ cause: error
563
+ });
564
+ }
565
+ })();
566
+ try {
567
+ const event = KernelEventSchema.parse(parsed);
568
+ this.applyEventToIndexes(event);
569
+ } catch (error) {
570
+ throw new Error(`KernelEvent parse failed at line ${index + 1}`, { cause: error });
571
+ }
572
+ }
573
+ this.indexesHydrated = true;
574
+ }
575
+ async getCurrentFileSize() {
576
+ try {
577
+ const fileStats = await stat(this.eventsFilePath);
578
+ return fileStats.size;
579
+ } catch (error) {
580
+ const nodeError = error;
581
+ if (nodeError.code === "ENOENT") {
582
+ return 0;
583
+ }
584
+ throw error;
585
+ }
586
+ }
587
+ async readTailFromOffset(offset) {
588
+ let handle = null;
589
+ try {
590
+ handle = await open(this.eventsFilePath, "r");
591
+ const fileStats = await handle.stat();
592
+ const didTruncate = fileStats.size < offset;
593
+ const startOffset = didTruncate ? 0 : offset;
594
+ const bytesToRead = Math.max(0, fileStats.size - startOffset);
595
+ if (bytesToRead === 0) {
596
+ return {
597
+ text: "",
598
+ nextOffset: fileStats.size,
599
+ didTruncate
600
+ };
601
+ }
602
+ const buffer = Buffer.allocUnsafe(bytesToRead);
603
+ let totalBytesRead = 0;
604
+ while (totalBytesRead < bytesToRead) {
605
+ const { bytesRead } = await handle.read(buffer, totalBytesRead, bytesToRead - totalBytesRead, startOffset + totalBytesRead);
606
+ if (bytesRead === 0) {
607
+ break;
608
+ }
609
+ totalBytesRead += bytesRead;
610
+ }
611
+ return {
612
+ text: buffer.subarray(0, totalBytesRead).toString(UTF8_ENCODING),
613
+ nextOffset: startOffset + totalBytesRead,
614
+ didTruncate
615
+ };
616
+ } catch (error) {
617
+ const nodeError = error;
618
+ if (nodeError.code === "ENOENT") {
619
+ return {
620
+ text: "",
621
+ nextOffset: 0,
622
+ didTruncate: offset > 0
623
+ };
624
+ }
625
+ throw error;
626
+ } finally {
627
+ if (handle) {
628
+ await handle.close().catch(() => {
629
+ });
630
+ }
631
+ }
632
+ }
633
+ resetIndexes() {
634
+ this.orderedEvents = [];
635
+ this.byTask = /* @__PURE__ */ new Map();
636
+ this.byKind = /* @__PURE__ */ new Map();
637
+ this.byTimestamp = /* @__PURE__ */ new Map();
638
+ }
639
+ applyEventToIndexes(event) {
640
+ this.orderedEvents.push(event);
641
+ if (hasTaskId(event)) {
642
+ const taskBucket = this.byTask.get(event.task_id) ?? [];
643
+ taskBucket.push(event);
644
+ this.byTask.set(event.task_id, taskBucket);
645
+ }
646
+ const kindBucket = this.byKind.get(event.kind) ?? [];
647
+ kindBucket.push(event);
648
+ this.byKind.set(event.kind, kindBucket);
649
+ const tsBucket = this.byTimestamp.get(event.timestamp) ?? [];
650
+ tsBucket.push(event);
651
+ this.byTimestamp.set(event.timestamp, tsBucket);
652
+ }
653
+ async withFileLock(operation) {
654
+ await mkdir(dirname(this.lockFilePath), { recursive: true });
655
+ for (let attempt = 0; attempt <= this.lockMaxRetries; attempt += 1) {
656
+ let handle = null;
657
+ try {
658
+ handle = await open(this.lockFilePath, "wx");
659
+ await handle.writeFile(JSON.stringify(this.buildLockMetadata()), UTF8_ENCODING);
660
+ const result = await operation();
661
+ await this.cleanupLockHandle(handle);
662
+ return result;
663
+ } catch (error) {
664
+ const nodeError = error;
665
+ await this.cleanupLockHandle(handle);
666
+ if (nodeError.code === "EEXIST" && attempt < this.lockMaxRetries) {
667
+ const recovered = await this.recoverStaleLockIfNeeded();
668
+ if (recovered) {
669
+ continue;
670
+ }
671
+ await sleep(this.lockRetryDelayMs);
672
+ continue;
673
+ }
674
+ throw error;
675
+ }
676
+ }
677
+ throw new Error(`Failed to acquire event-store lock at ${this.lockFilePath}`);
678
+ }
679
+ };
680
+
681
+ // ../kernel/dist/evidence/evidence-store.js
682
+ import { appendFile as appendFile2, mkdir as mkdir2, open as open2, readdir, rename, rm as rm2 } from "fs/promises";
683
+ import { createHash as createHash2 } from "crypto";
684
+ import { join } from "path";
685
+
686
+ // ../kernel/dist/evidence/fs-helpers.js
687
+ import { readFile as readFile2, stat as stat2 } from "fs/promises";
688
+ async function readFileOrEmpty(filePath) {
689
+ try {
690
+ return await readFile2(filePath, UTF8_ENCODING);
691
+ } catch (error) {
692
+ const nodeError = error;
693
+ if (nodeError.code === "ENOENT") {
694
+ return "";
695
+ }
696
+ throw error;
697
+ }
698
+ }
699
+ async function statOrNull(filePath) {
700
+ try {
701
+ return await stat2(filePath);
702
+ } catch (error) {
703
+ const nodeError = error;
704
+ if (nodeError.code === "ENOENT") {
705
+ return null;
706
+ }
707
+ throw error;
708
+ }
709
+ }
710
+
711
+ // ../kernel/dist/evidence/evidence-store.js
712
+ var DEFAULT_LOCK_RETRY_DELAY_MS2 = 20;
713
+ var DEFAULT_LOCK_MAX_RETRIES2 = 250;
714
+ var DEFAULT_COMPACTION_THRESHOLD_BYTES = 10 * 1024 * 1024;
715
+ var ACTIVE_TRACE_FILE_NAME = "tool-traces.jsonl";
716
+ var SEGMENT_FILE_PATTERN = /^tool-traces\.(\d+)\.jsonl$/;
717
+ function sha256Hex(content) {
718
+ return createHash2(SHA256_ALGORITHM).update(content).digest("hex");
719
+ }
720
+ function sleep2(ms) {
721
+ return new Promise((resolve2) => {
722
+ setTimeout(resolve2, ms);
723
+ });
724
+ }
725
+ var EvidenceStore = class {
726
+ tracesDir;
727
+ tracesFilePath;
728
+ tracesLockFilePath;
729
+ inputsDir;
730
+ lockRetryDelayMs;
731
+ lockMaxRetries;
732
+ compactionThresholdBytes;
733
+ tracesHydrated = false;
734
+ orderedTraces = [];
735
+ tracesByTaskId = /* @__PURE__ */ new Map();
736
+ taskIdByReceiptId = /* @__PURE__ */ new Map();
737
+ /** Dedup guard: tracks `receipt_id:kind` keys already indexed (WU-1881). */
738
+ seenTraceKeys = /* @__PURE__ */ new Set();
739
+ /** Byte offset cursor into the active trace file for incremental reads. */
740
+ activeFileCursor = 0;
741
+ /** Number of compacted segment files at last hydration. */
742
+ hydratedSegmentCount = 0;
743
+ constructor(options) {
744
+ this.tracesDir = join(options.evidenceRoot, "traces");
745
+ this.tracesFilePath = join(this.tracesDir, ACTIVE_TRACE_FILE_NAME);
746
+ this.tracesLockFilePath = join(this.tracesDir, "tool-traces.lock");
747
+ this.inputsDir = join(options.evidenceRoot, "inputs");
748
+ this.lockRetryDelayMs = options.lockRetryDelayMs ?? DEFAULT_LOCK_RETRY_DELAY_MS2;
749
+ this.lockMaxRetries = options.lockMaxRetries ?? DEFAULT_LOCK_MAX_RETRIES2;
750
+ this.compactionThresholdBytes = options.compactionThresholdBytes ?? DEFAULT_COMPACTION_THRESHOLD_BYTES;
751
+ }
752
+ async appendTrace(entry) {
753
+ const validated = ToolTraceEntrySchema.parse(entry);
754
+ let compacted = false;
755
+ await this.withFileLock(async () => {
756
+ await mkdir2(this.tracesDir, { recursive: true });
757
+ const serialized = `${JSON.stringify(validated)}
758
+ `;
759
+ await appendFile2(this.tracesFilePath, serialized, UTF8_ENCODING);
760
+ compacted = await this.compactIfNeeded();
761
+ });
762
+ if (this.tracesHydrated) {
763
+ this.applyTraceToIndexes(validated);
764
+ if (compacted) {
765
+ this.hydratedSegmentCount += 1;
766
+ this.activeFileCursor = 0;
767
+ } else {
768
+ const fileStat = await statOrNull(this.tracesFilePath);
769
+ this.activeFileCursor = fileStat?.size ?? 0;
770
+ }
771
+ }
772
+ }
773
+ async readTraces() {
774
+ await this.hydrateIndexesIfNeeded();
775
+ return [...this.orderedTraces];
776
+ }
777
+ async readTracesByTaskId(taskId) {
778
+ await this.hydrateIndexesIfNeeded();
779
+ return [...this.tracesByTaskId.get(taskId) ?? []];
780
+ }
781
+ async pruneTask(taskId) {
782
+ await this.hydrateIndexesIfNeeded();
783
+ const receiptIdsToDelete = [];
784
+ for (const [receiptId, indexedTaskId] of this.taskIdByReceiptId.entries()) {
785
+ if (indexedTaskId === taskId) {
786
+ receiptIdsToDelete.push(receiptId);
787
+ }
788
+ }
789
+ for (const receiptId of receiptIdsToDelete) {
790
+ this.taskIdByReceiptId.delete(receiptId);
791
+ }
792
+ this.tracesByTaskId.delete(taskId);
793
+ return receiptIdsToDelete.length;
794
+ }
795
+ async getReceiptIndexSize() {
796
+ await this.hydrateIndexesIfNeeded();
797
+ return this.taskIdByReceiptId.size;
798
+ }
799
+ async hydrateIndexesIfNeeded() {
800
+ if (!this.tracesHydrated) {
801
+ return this.fullHydrate();
802
+ }
803
+ await this.incrementalHydrate();
804
+ }
805
+ /**
806
+ * Full hydration: reads all segment files (sorted ascending) then the active file.
807
+ * Sets cursor to end of active file for subsequent incremental reads.
808
+ */
809
+ async fullHydrate() {
810
+ this.resetIndexes();
811
+ const dirStat = await statOrNull(this.tracesDir);
812
+ if (!dirStat) {
813
+ this.tracesHydrated = true;
814
+ this.activeFileCursor = 0;
815
+ this.hydratedSegmentCount = 0;
816
+ return;
817
+ }
818
+ const segments = await this.listSegmentFiles();
819
+ for (const segmentFile of segments) {
820
+ const segmentPath = join(this.tracesDir, segmentFile);
821
+ await this.hydrateFromFile(segmentPath);
822
+ }
823
+ this.hydratedSegmentCount = segments.length;
824
+ const activeContent = await readFileOrEmpty(this.tracesFilePath);
825
+ if (activeContent.length > 0) {
826
+ this.parseAndApplyLines(activeContent);
827
+ }
828
+ this.activeFileCursor = Buffer.byteLength(activeContent, UTF8_ENCODING);
829
+ this.tracesHydrated = true;
830
+ }
831
+ /**
832
+ * Incremental hydration: only reads new segments (from compaction since last
833
+ * hydration) and new bytes appended to the active file since the cursor.
834
+ * This provides O(1) amortized reads for the hot path.
835
+ */
836
+ async incrementalHydrate() {
837
+ const currentSegments = await this.listSegmentFiles();
838
+ if (currentSegments.length > this.hydratedSegmentCount) {
839
+ const unseenSegments = currentSegments.slice(this.hydratedSegmentCount);
840
+ for (const segmentFile of unseenSegments) {
841
+ const segmentPath = join(this.tracesDir, segmentFile);
842
+ await this.hydrateFromFile(segmentPath);
843
+ }
844
+ this.hydratedSegmentCount = currentSegments.length;
845
+ this.activeFileCursor = 0;
846
+ }
847
+ const activeFileStat = await statOrNull(this.tracesFilePath);
848
+ const activeSize = activeFileStat?.size ?? 0;
849
+ if (activeSize <= this.activeFileCursor) {
850
+ return;
851
+ }
852
+ const handle = await open2(this.tracesFilePath, "r");
853
+ try {
854
+ const deltaSize = activeSize - this.activeFileCursor;
855
+ const buffer = Buffer.alloc(deltaSize);
856
+ await handle.read(buffer, 0, deltaSize, this.activeFileCursor);
857
+ const deltaContent = buffer.toString(UTF8_ENCODING);
858
+ if (deltaContent.trim().length > 0) {
859
+ this.parseAndApplyLines(deltaContent);
860
+ }
861
+ this.activeFileCursor = activeSize;
862
+ } finally {
863
+ await handle.close();
864
+ }
865
+ }
866
+ /**
867
+ * Lists compacted segment files sorted in ascending order.
868
+ * Segment files follow the pattern: tool-traces.NNNN.jsonl
869
+ */
870
+ async listSegmentFiles() {
871
+ let files;
872
+ try {
873
+ files = await readdir(this.tracesDir);
874
+ } catch (error) {
875
+ const nodeError = error;
876
+ if (nodeError.code === "ENOENT") {
877
+ return [];
878
+ }
879
+ throw error;
880
+ }
881
+ return files.filter((f) => SEGMENT_FILE_PATTERN.test(f)).sort((a, b) => {
882
+ const matchA = a.match(SEGMENT_FILE_PATTERN);
883
+ const matchB = b.match(SEGMENT_FILE_PATTERN);
884
+ const numA = matchA ? parseInt(matchA[1] ?? "0", 10) : 0;
885
+ const numB = matchB ? parseInt(matchB[1] ?? "0", 10) : 0;
886
+ return numA - numB;
887
+ });
888
+ }
889
+ /**
890
+ * Reads an entire file and applies all trace lines to the indexes.
891
+ */
892
+ async hydrateFromFile(filePath) {
893
+ const content = await readFileOrEmpty(filePath);
894
+ if (content.trim().length > 0) {
895
+ this.parseAndApplyLines(content);
896
+ }
897
+ }
898
+ /**
899
+ * Parses newline-delimited JSON lines and applies each to the indexes.
900
+ */
901
+ parseAndApplyLines(content) {
902
+ const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
903
+ for (let index = 0; index < lines.length; index += 1) {
904
+ const line = lines[index];
905
+ if (!line) {
906
+ continue;
907
+ }
908
+ const parsed = (() => {
909
+ try {
910
+ return JSON.parse(line);
911
+ } catch (error) {
912
+ throw new Error(`Malformed evidence trace JSON at line ${index + 1}`, { cause: error });
913
+ }
914
+ })();
915
+ this.applyTraceToIndexes(ToolTraceEntrySchema.parse(parsed));
916
+ }
917
+ }
918
+ /**
919
+ * Rotates the active JSONL file to a numbered segment if it exceeds the
920
+ * compaction threshold. Called within the file lock after each append.
921
+ * @returns true if compaction (rotation) occurred.
922
+ */
923
+ async compactIfNeeded() {
924
+ const fileStat = await statOrNull(this.tracesFilePath);
925
+ if (!fileStat) {
926
+ return false;
927
+ }
928
+ if (fileStat.size < this.compactionThresholdBytes) {
929
+ return false;
930
+ }
931
+ const segments = await this.listSegmentFiles();
932
+ let nextNumber = 1;
933
+ if (segments.length > 0) {
934
+ const lastSegment = segments[segments.length - 1];
935
+ if (lastSegment) {
936
+ const match = lastSegment.match(SEGMENT_FILE_PATTERN);
937
+ if (match) {
938
+ nextNumber = parseInt(match[1] ?? "0", 10) + 1;
939
+ }
940
+ }
941
+ }
942
+ const segmentName = `tool-traces.${String(nextNumber).padStart(8, "0")}.jsonl`;
943
+ const segmentPath = join(this.tracesDir, segmentName);
944
+ await rename(this.tracesFilePath, segmentPath);
945
+ return true;
946
+ }
947
+ resetIndexes() {
948
+ this.orderedTraces = [];
949
+ this.tracesByTaskId = /* @__PURE__ */ new Map();
950
+ this.taskIdByReceiptId = /* @__PURE__ */ new Map();
951
+ this.seenTraceKeys = /* @__PURE__ */ new Set();
952
+ }
953
+ applyTraceToIndexes(entry) {
954
+ const traceKey = `${entry.receipt_id}:${entry.kind}`;
955
+ if (this.seenTraceKeys.has(traceKey)) {
956
+ return;
957
+ }
958
+ this.seenTraceKeys.add(traceKey);
959
+ this.orderedTraces.push(entry);
960
+ if (entry.kind === TOOL_TRACE_KINDS.TOOL_CALL_STARTED) {
961
+ this.taskIdByReceiptId.set(entry.receipt_id, entry.task_id);
962
+ const bucket2 = this.tracesByTaskId.get(entry.task_id) ?? [];
963
+ bucket2.push(entry);
964
+ this.tracesByTaskId.set(entry.task_id, bucket2);
965
+ return;
966
+ }
967
+ const taskId = this.taskIdByReceiptId.get(entry.receipt_id);
968
+ if (!taskId) {
969
+ return;
970
+ }
971
+ const bucket = this.tracesByTaskId.get(taskId) ?? [];
972
+ bucket.push(entry);
973
+ this.tracesByTaskId.set(taskId, bucket);
974
+ }
975
+ async persistData(data) {
976
+ const serialized = canonicalStringify(data);
977
+ const dataHash = sha256Hex(serialized);
978
+ const dataRef = join(this.inputsDir, dataHash);
979
+ await mkdir2(this.inputsDir, { recursive: true });
980
+ try {
981
+ const handle = await open2(dataRef, "wx");
982
+ try {
983
+ await handle.writeFile(serialized, UTF8_ENCODING);
984
+ } finally {
985
+ await handle.close();
986
+ }
987
+ } catch (error) {
988
+ const nodeError = error;
989
+ if (nodeError.code !== "EEXIST") {
990
+ throw error;
991
+ }
992
+ }
993
+ return {
994
+ dataHash,
995
+ dataRef
996
+ };
997
+ }
998
+ /** @deprecated Use persistData instead */
999
+ async persistInput(input) {
1000
+ return this.persistData(input);
1001
+ }
1002
+ async reconcileOrphanedStarts() {
1003
+ const traces = await this.readTraces();
1004
+ const started = /* @__PURE__ */ new Map();
1005
+ const finished = /* @__PURE__ */ new Set();
1006
+ for (const trace of traces) {
1007
+ if (trace.kind === TOOL_TRACE_KINDS.TOOL_CALL_STARTED) {
1008
+ started.set(trace.receipt_id, trace);
1009
+ } else {
1010
+ finished.add(trace.receipt_id);
1011
+ }
1012
+ }
1013
+ let reconciled = 0;
1014
+ for (const [receiptId] of started) {
1015
+ if (finished.has(receiptId)) {
1016
+ continue;
1017
+ }
1018
+ const policyDecisions = [
1019
+ {
1020
+ policy_id: KERNEL_POLICY_IDS.RECONCILIATION,
1021
+ decision: "deny",
1022
+ reason: "Orphaned started trace without matching finished trace"
1023
+ }
1024
+ ];
1025
+ await this.appendTrace({
1026
+ schema_version: 1,
1027
+ kind: TOOL_TRACE_KINDS.TOOL_CALL_FINISHED,
1028
+ receipt_id: receiptId,
1029
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1030
+ result: "crashed",
1031
+ duration_ms: 0,
1032
+ scope_enforcement_note: "Synthetic crashed finish generated by orphan reconciliation.",
1033
+ policy_decisions: policyDecisions,
1034
+ artifacts_written: []
1035
+ });
1036
+ reconciled += 1;
1037
+ }
1038
+ return reconciled;
1039
+ }
1040
+ async withFileLock(operation) {
1041
+ await mkdir2(this.tracesDir, { recursive: true });
1042
+ for (let attempt = 0; attempt <= this.lockMaxRetries; attempt += 1) {
1043
+ let handle = null;
1044
+ try {
1045
+ handle = await open2(this.tracesLockFilePath, "wx");
1046
+ const result = await operation();
1047
+ await handle.close();
1048
+ await rm2(this.tracesLockFilePath, { force: true });
1049
+ return result;
1050
+ } catch (error) {
1051
+ const nodeError = error;
1052
+ if (handle) {
1053
+ try {
1054
+ await handle.close();
1055
+ } catch {
1056
+ }
1057
+ try {
1058
+ await rm2(this.tracesLockFilePath, { force: true });
1059
+ } catch {
1060
+ }
1061
+ }
1062
+ if (nodeError.code === "EEXIST") {
1063
+ if (attempt < this.lockMaxRetries) {
1064
+ await sleep2(this.lockRetryDelayMs);
1065
+ continue;
1066
+ }
1067
+ break;
1068
+ }
1069
+ throw error;
1070
+ }
1071
+ }
1072
+ throw new Error(`Failed to acquire evidence-store lock at ${this.tracesLockFilePath}`);
1073
+ }
1074
+ };
1075
+
1076
+ // ../kernel/dist/policy/approval-event.js
1077
+ import { z } from "zod";
1078
+ var ApprovalScopeSchema = z.object({
1079
+ level: z.enum(["workspace", "lane", "pack", "task"]),
1080
+ id: z.string().min(1)
1081
+ });
1082
+ var ApprovalEventSchema = z.object({
1083
+ schema_version: z.literal(1),
1084
+ kind: z.literal("approval_event"),
1085
+ run_id: z.string().min(1),
1086
+ scope: ApprovalScopeSchema,
1087
+ approved_by: z.string().min(1),
1088
+ expires_at: z.string().datetime(),
1089
+ reason: z.string().min(1).optional()
1090
+ });
1091
+
1092
+ // ../kernel/dist/sandbox/bwrap-invocation.js
1093
+ import path from "path";
1094
+ var SYSTEM_READONLY_ALLOWLIST = ["/usr", "/bin", "/sbin", "/lib", "/lib64", "/etc"];
1095
+ function assertCommand(command) {
1096
+ if (command.length === 0) {
1097
+ throw new Error("Sandbox command is required");
1098
+ }
1099
+ }
1100
+ function dedupeMounts(mounts) {
1101
+ const unique = /* @__PURE__ */ new Map();
1102
+ for (const mount of mounts) {
1103
+ const key = `${mount.source}=>${mount.target}`;
1104
+ if (!unique.has(key)) {
1105
+ unique.set(key, mount);
1106
+ }
1107
+ }
1108
+ return [...unique.values()];
1109
+ }
1110
+ function normalizePrefix(prefix) {
1111
+ const resolved = path.resolve(prefix);
1112
+ if (resolved === path.sep) {
1113
+ return resolved;
1114
+ }
1115
+ return resolved.replace(/[/\\]+$/, "");
1116
+ }
1117
+ function isWithinPrefix(candidate, prefix) {
1118
+ const normalizedCandidate = normalizePrefix(candidate);
1119
+ const normalizedPrefix = normalizePrefix(prefix);
1120
+ if (normalizedPrefix === path.sep) {
1121
+ return true;
1122
+ }
1123
+ return normalizedCandidate === normalizedPrefix || normalizedCandidate.startsWith(`${normalizedPrefix}${path.sep}`);
1124
+ }
1125
+ function collectCommandMountPrefixes(profile) {
1126
+ const prefixes = [
1127
+ ...profile.readonly_bind_mounts.map((mount) => mount.target),
1128
+ ...profile.writable_bind_mounts.map((mount) => mount.target)
1129
+ ];
1130
+ return [...new Set(prefixes.map(normalizePrefix))];
1131
+ }
1132
+ function collectCommandReadonlyMounts(profile, command) {
1133
+ const mountPrefixes = collectCommandMountPrefixes(profile);
1134
+ const mounts = [];
1135
+ for (const segment of command) {
1136
+ if (!path.isAbsolute(segment)) {
1137
+ continue;
1138
+ }
1139
+ const absolute = path.resolve(segment);
1140
+ const parent = path.dirname(absolute);
1141
+ const grandparent = path.dirname(parent);
1142
+ if (parent !== "/" && mountPrefixes.some((prefix) => isWithinPrefix(parent, prefix))) {
1143
+ mounts.push({ source: parent, target: parent });
1144
+ }
1145
+ if (grandparent !== "/" && mountPrefixes.some((prefix) => isWithinPrefix(grandparent, prefix))) {
1146
+ mounts.push({ source: grandparent, target: grandparent });
1147
+ }
1148
+ }
1149
+ return dedupeMounts(mounts);
1150
+ }
1151
+ function collectReadonlyAllowlistMounts(profile, command) {
1152
+ const writableTargets = new Set(profile.writable_bind_mounts.map((mount) => mount.target));
1153
+ const readonlyMounts = [
1154
+ ...SYSTEM_READONLY_ALLOWLIST.map((mountPath) => ({
1155
+ source: mountPath,
1156
+ target: mountPath
1157
+ })),
1158
+ ...collectCommandReadonlyMounts(profile, command),
1159
+ ...profile.readonly_bind_mounts
1160
+ ];
1161
+ return dedupeMounts(readonlyMounts).filter((mount) => !writableTargets.has(mount.target));
1162
+ }
1163
+ function escapeShellArg(arg) {
1164
+ return `'${arg.replace(/'/g, "'\\''")}'`;
1165
+ }
1166
+ function parseHostPort(entry) {
1167
+ if (entry.includes("/")) {
1168
+ return { host: entry, port: null };
1169
+ }
1170
+ const lastColon = entry.lastIndexOf(":");
1171
+ if (lastColon > 0) {
1172
+ return {
1173
+ host: entry.slice(0, lastColon),
1174
+ port: entry.slice(lastColon + 1)
1175
+ };
1176
+ }
1177
+ return { host: entry, port: null };
1178
+ }
1179
+ function buildIptablesAllowlistScript(allowlist, command) {
1180
+ const lines = [];
1181
+ lines.push("iptables -A OUTPUT -o lo -j ACCEPT");
1182
+ lines.push("iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT");
1183
+ for (const entry of allowlist) {
1184
+ const { host, port } = parseHostPort(entry);
1185
+ if (port) {
1186
+ lines.push(`iptables -A OUTPUT -d ${host} -p tcp --dport ${port} -j ACCEPT`);
1187
+ } else {
1188
+ lines.push(`iptables -A OUTPUT -d ${host} -j ACCEPT`);
1189
+ }
1190
+ }
1191
+ lines.push("iptables -A OUTPUT -j REJECT --reject-with icmp-port-unreachable");
1192
+ const escapedCommand = command.map(escapeShellArg).join(" ");
1193
+ lines.push(`exec ${escapedCommand}`);
1194
+ return lines.join(" && ");
1195
+ }
1196
+ function buildBwrapInvocation(input) {
1197
+ assertCommand(input.command);
1198
+ const args = ["--die-with-parent", "--new-session", "--tmpfs", "/"];
1199
+ for (const mount of collectReadonlyAllowlistMounts(input.profile, input.command)) {
1200
+ args.push("--ro-bind", mount.source, mount.target);
1201
+ }
1202
+ for (const mount of input.profile.writable_bind_mounts) {
1203
+ args.push("--bind", mount.source, mount.target);
1204
+ }
1205
+ for (const overlay of input.profile.deny_overlays) {
1206
+ if (overlay.kind === "file") {
1207
+ args.push("--bind", "/dev/null", overlay.path);
1208
+ } else {
1209
+ args.push("--tmpfs", overlay.path);
1210
+ }
1211
+ }
1212
+ if (input.profile.network_posture === "off") {
1213
+ args.push("--unshare-net");
1214
+ } else if (input.profile.network_posture === "allowlist") {
1215
+ args.push("--unshare-net");
1216
+ }
1217
+ for (const [key, value] of Object.entries(input.profile.env)) {
1218
+ args.push("--setenv", key, value);
1219
+ }
1220
+ args.push("--proc", "/proc", "--dev", "/dev");
1221
+ if (input.profile.network_posture === "allowlist" && input.profile.network_allowlist.length > 0) {
1222
+ const iptablesScript = buildIptablesAllowlistScript(input.profile.network_allowlist, input.command);
1223
+ args.push("--", "sh", "-c", iptablesScript);
1224
+ } else {
1225
+ args.push("--", ...input.command);
1226
+ }
1227
+ return {
1228
+ command: input.sandboxBinary || "bwrap",
1229
+ args,
1230
+ env: input.profile.env
1231
+ };
1232
+ }
1233
+
1234
+ // ../kernel/dist/sandbox/profile.js
1235
+ import os from "os";
1236
+ import path2 from "path";
1237
+ var READ_CONFINEMENT_NOTE = "read confinement best-effort (v1)";
1238
+ function isGlobSegment(segment) {
1239
+ return /[*?[{(]/.test(segment);
1240
+ }
1241
+ function resolveScopePatternToPath(pattern, workspaceRoot) {
1242
+ const normalized = pattern.replaceAll("\\", "/");
1243
+ const rawSegments = normalized.split("/").filter((segment) => segment.length > 0);
1244
+ const staticSegments = [];
1245
+ for (const segment of rawSegments) {
1246
+ if (isGlobSegment(segment)) {
1247
+ break;
1248
+ }
1249
+ staticSegments.push(segment);
1250
+ }
1251
+ if (normalized.startsWith("/")) {
1252
+ const absolutePrefix = staticSegments.length === 0 ? "/" : `/${staticSegments.join("/")}`;
1253
+ return path2.resolve(absolutePrefix);
1254
+ }
1255
+ if (staticSegments.length === 0) {
1256
+ return path2.resolve(workspaceRoot);
1257
+ }
1258
+ return path2.resolve(workspaceRoot, staticSegments.join("/"));
1259
+ }
1260
+ function dedupeMounts2(mounts) {
1261
+ const seen = /* @__PURE__ */ new Set();
1262
+ return mounts.filter((mount) => {
1263
+ const key = `${mount.source}=>${mount.target}`;
1264
+ if (seen.has(key)) {
1265
+ return false;
1266
+ }
1267
+ seen.add(key);
1268
+ return true;
1269
+ });
1270
+ }
1271
+ function normalizeEnvironment(env) {
1272
+ if (!env) {
1273
+ return {};
1274
+ }
1275
+ const normalized = {};
1276
+ for (const [key, value] of Object.entries(env)) {
1277
+ if (typeof value === "string") {
1278
+ normalized[key] = value;
1279
+ }
1280
+ }
1281
+ return normalized;
1282
+ }
1283
+ function createDefaultDenyOverlays(input) {
1284
+ const workspaceRoot = path2.resolve(input.workspaceRoot);
1285
+ const homeDir = path2.resolve(input.homeDir || os.homedir());
1286
+ return [
1287
+ { path: path2.join(homeDir, ".ssh"), kind: "directory" },
1288
+ { path: path2.join(homeDir, ".aws"), kind: "directory" },
1289
+ { path: path2.join(homeDir, ".gnupg"), kind: "directory" },
1290
+ { path: path2.join(workspaceRoot, ".env"), kind: "file" }
1291
+ ];
1292
+ }
1293
+ function resolveScopeEnforcementNote(scopes) {
1294
+ const hasReadPathScope = scopes.some((scope) => scope.type === "path" && scope.access === "read");
1295
+ return hasReadPathScope ? READ_CONFINEMENT_NOTE : void 0;
1296
+ }
1297
+ function buildSandboxProfileFromScopes(scopeEnforced, options = {}) {
1298
+ const workspaceRoot = path2.resolve(options.workspaceRoot || process.cwd());
1299
+ const writable_bind_mounts = [];
1300
+ const readonly_bind_mounts = [];
1301
+ let network_posture = "off";
1302
+ const allowlistEntries = /* @__PURE__ */ new Set();
1303
+ for (const scope of scopeEnforced) {
1304
+ if (scope.type === "network") {
1305
+ if (scope.posture === "full") {
1306
+ network_posture = "full";
1307
+ } else if (scope.posture === "allowlist" && network_posture !== "full") {
1308
+ network_posture = "allowlist";
1309
+ const entries = "allowlist_entries" in scope ? scope.allowlist_entries : [];
1310
+ for (const entry of entries) {
1311
+ allowlistEntries.add(entry);
1312
+ }
1313
+ }
1314
+ continue;
1315
+ }
1316
+ const resolvedPath = resolveScopePatternToPath(scope.pattern, workspaceRoot);
1317
+ const mount = {
1318
+ source: resolvedPath,
1319
+ target: resolvedPath
1320
+ };
1321
+ if (scope.access === "write") {
1322
+ writable_bind_mounts.push(mount);
1323
+ } else {
1324
+ readonly_bind_mounts.push(mount);
1325
+ }
1326
+ }
1327
+ return {
1328
+ readonly_bind_mounts: dedupeMounts2(readonly_bind_mounts),
1329
+ writable_bind_mounts: dedupeMounts2(writable_bind_mounts),
1330
+ network_posture,
1331
+ network_allowlist: network_posture === "allowlist" ? [...allowlistEntries].sort() : [],
1332
+ deny_overlays: options.denyOverlays || createDefaultDenyOverlays({
1333
+ workspaceRoot,
1334
+ homeDir: options.homeDir
1335
+ }),
1336
+ env: normalizeEnvironment(options.env)
1337
+ };
1338
+ }
1339
+
1340
+ // ../kernel/dist/sandbox/subprocess-dispatcher.js
1341
+ import { spawn, spawnSync } from "child_process";
1342
+ import { randomUUID } from "crypto";
1343
+ import fs from "fs";
1344
+ import { fileURLToPath } from "url";
1345
+
1346
+ // ../kernel/dist/sandbox/tool-runner-worker.js
1347
+ import path3 from "path";
1348
+ import { pathToFileURL } from "url";
1349
+ import { z as z2 } from "zod";
1350
+ var ToolRunnerWorkerInvocationSchema = z2.object({
1351
+ tool_name: z2.string().min(1),
1352
+ handler_entry: z2.string().min(1),
1353
+ input: z2.unknown(),
1354
+ scope_enforced: z2.array(ToolScopeSchema),
1355
+ receipt_id: z2.string().min(1)
1356
+ });
1357
+ var ToolRunnerWorkerResponseSchema = z2.object({
1358
+ output: ToolOutputSchema
1359
+ });
1360
+ function buildFailureOutput(code, message, details) {
1361
+ return {
1362
+ success: false,
1363
+ error: {
1364
+ code,
1365
+ message,
1366
+ ...details ? { details } : {}
1367
+ }
1368
+ };
1369
+ }
1370
+ function withScopeNote(output, scopeEnforced) {
1371
+ const note = resolveScopeEnforcementNote(scopeEnforced);
1372
+ if (!note) {
1373
+ return output;
1374
+ }
1375
+ return {
1376
+ ...output,
1377
+ metadata: {
1378
+ ...output.metadata || {},
1379
+ scope_enforcement_note: note
1380
+ }
1381
+ };
1382
+ }
1383
+ async function defaultImportModule(specifier) {
1384
+ return import(specifier);
1385
+ }
1386
+ function resolveEntrySpecifier(entry) {
1387
+ if (entry.startsWith("file:")) {
1388
+ return entry;
1389
+ }
1390
+ if (path3.isAbsolute(entry)) {
1391
+ return pathToFileURL(path3.resolve(entry)).href;
1392
+ }
1393
+ if (entry.startsWith(".")) {
1394
+ return pathToFileURL(path3.resolve(process.cwd(), entry)).href;
1395
+ }
1396
+ return entry;
1397
+ }
1398
+ function isToolAdapter(value) {
1399
+ return typeof value === "function";
1400
+ }
1401
+ function selectAdapterExport(candidate) {
1402
+ if (isToolAdapter(candidate)) {
1403
+ return candidate;
1404
+ }
1405
+ if (!candidate || typeof candidate !== "object") {
1406
+ return null;
1407
+ }
1408
+ const moduleRecord = candidate;
1409
+ const defaultExport = moduleRecord.default;
1410
+ if (isToolAdapter(defaultExport)) {
1411
+ return defaultExport;
1412
+ }
1413
+ const runExport = moduleRecord.run;
1414
+ if (isToolAdapter(runExport)) {
1415
+ return runExport;
1416
+ }
1417
+ const handlerExport = moduleRecord.handler;
1418
+ if (isToolAdapter(handlerExport)) {
1419
+ return handlerExport;
1420
+ }
1421
+ return null;
1422
+ }
1423
+ async function readStreamAsString(stream) {
1424
+ let output = "";
1425
+ for await (const chunk of stream) {
1426
+ output += typeof chunk === "string" ? chunk : chunk.toString(UTF8_ENCODING);
1427
+ }
1428
+ return output;
1429
+ }
1430
+ function parseToolRunnerWorkerResponse(raw) {
1431
+ const parsedPayload = JSON.parse(raw);
1432
+ return ToolRunnerWorkerResponseSchema.parse(parsedPayload);
1433
+ }
1434
+ async function executeToolRunnerInvocation(invocationInput, options = {}) {
1435
+ const parsedInvocation = ToolRunnerWorkerInvocationSchema.safeParse(invocationInput);
1436
+ if (!parsedInvocation.success) {
1437
+ return buildFailureOutput("INVALID_INVOCATION_PAYLOAD", parsedInvocation.error.message);
1438
+ }
1439
+ const invocation = parsedInvocation.data;
1440
+ const importModule = options.importModule || defaultImportModule;
1441
+ let loadedModule;
1442
+ try {
1443
+ loadedModule = await importModule(resolveEntrySpecifier(invocation.handler_entry));
1444
+ } catch (error) {
1445
+ return withScopeNote(buildFailureOutput("ADAPTER_LOAD_FAILED", `Failed to load adapter "${invocation.handler_entry}": ${error.message}`), invocation.scope_enforced);
1446
+ }
1447
+ const adapter = selectAdapterExport(loadedModule);
1448
+ if (!adapter) {
1449
+ return withScopeNote(buildFailureOutput("ADAPTER_LOAD_FAILED", `Adapter "${invocation.handler_entry}" does not export a function`), invocation.scope_enforced);
1450
+ }
1451
+ let rawOutput;
1452
+ try {
1453
+ rawOutput = await adapter(invocation.input, {
1454
+ tool_name: invocation.tool_name,
1455
+ receipt_id: invocation.receipt_id,
1456
+ scope_enforced: invocation.scope_enforced
1457
+ });
1458
+ } catch (error) {
1459
+ return withScopeNote(buildFailureOutput("TOOL_EXECUTION_FAILED", `Adapter "${invocation.handler_entry}" failed: ${error.message}`), invocation.scope_enforced);
1460
+ }
1461
+ const parsedOutput = ToolOutputSchema.safeParse(rawOutput);
1462
+ if (!parsedOutput.success) {
1463
+ return withScopeNote(buildFailureOutput("INVALID_OUTPUT", parsedOutput.error.message), invocation.scope_enforced);
1464
+ }
1465
+ return withScopeNote(parsedOutput.data, invocation.scope_enforced);
1466
+ }
1467
+ async function runToolRunnerWorkerFromStreams(streams, options = {}) {
1468
+ const rawPayload = await readStreamAsString(streams.stdin);
1469
+ let payload;
1470
+ try {
1471
+ payload = JSON.parse(rawPayload);
1472
+ } catch {
1473
+ const invalidPayload = buildFailureOutput("INVALID_INVOCATION_PAYLOAD", "Worker stdin payload is not valid JSON");
1474
+ streams.stdout.write(JSON.stringify({ output: invalidPayload }));
1475
+ return;
1476
+ }
1477
+ const output = await executeToolRunnerInvocation(payload, options);
1478
+ streams.stdout.write(JSON.stringify({ output }));
1479
+ }
1480
+ async function runToolRunnerWorkerProcess() {
1481
+ await runToolRunnerWorkerFromStreams({
1482
+ stdin: process.stdin,
1483
+ stdout: process.stdout,
1484
+ stderr: process.stderr
1485
+ });
1486
+ }
1487
+ function shouldRunAsMain() {
1488
+ const entryPath = process.argv[1];
1489
+ if (!entryPath) {
1490
+ return false;
1491
+ }
1492
+ return pathToFileURL(path3.resolve(entryPath)).href === import.meta.url;
1493
+ }
1494
+ if (shouldRunAsMain()) {
1495
+ void runToolRunnerWorkerProcess();
1496
+ }
1497
+
1498
+ // ../kernel/dist/sandbox/subprocess-dispatcher.js
1499
+ function buildFailureOutput2(code, message, details) {
1500
+ return {
1501
+ success: false,
1502
+ error: {
1503
+ code,
1504
+ message,
1505
+ ...details ? { details } : {}
1506
+ }
1507
+ };
1508
+ }
1509
+ function withScopeNote2(output, scopes) {
1510
+ const scopeEnforcementNote = resolveScopeEnforcementNote(scopes);
1511
+ if (!scopeEnforcementNote) {
1512
+ return output;
1513
+ }
1514
+ return {
1515
+ ...output,
1516
+ metadata: {
1517
+ ...output.metadata || {},
1518
+ scope_enforcement_note: scopeEnforcementNote
1519
+ }
1520
+ };
1521
+ }
1522
+ function defaultCommandExists(binary) {
1523
+ const probe = spawnSync(binary, ["--help"], { stdio: "ignore" });
1524
+ return !probe.error;
1525
+ }
1526
+ function resolveDefaultWorkerEntry() {
1527
+ const jsPath = fileURLToPath(new URL("./tool-runner-worker.js", import.meta.url));
1528
+ if (fs.existsSync(jsPath)) {
1529
+ return jsPath;
1530
+ }
1531
+ return fileURLToPath(new URL("./tool-runner-worker.ts", import.meta.url));
1532
+ }
1533
+ var NodeSubprocessTransport = class {
1534
+ async execute(request) {
1535
+ return new Promise((resolve2, reject) => {
1536
+ const child = spawn(request.command, request.args, {
1537
+ stdio: "pipe",
1538
+ env: {
1539
+ ...process.env,
1540
+ ...request.env
1541
+ }
1542
+ });
1543
+ let stdout = "";
1544
+ let stderr = "";
1545
+ child.stdout.setEncoding(UTF8_ENCODING);
1546
+ child.stderr.setEncoding(UTF8_ENCODING);
1547
+ child.stdout.on("data", (chunk) => {
1548
+ stdout += chunk;
1549
+ });
1550
+ child.stderr.on("data", (chunk) => {
1551
+ stderr += chunk;
1552
+ });
1553
+ child.on("error", (error) => {
1554
+ reject(error);
1555
+ });
1556
+ child.on("close", (code) => {
1557
+ resolve2({
1558
+ code: code ?? 1,
1559
+ stdout,
1560
+ stderr
1561
+ });
1562
+ });
1563
+ child.stdin.end(request.stdin);
1564
+ });
1565
+ }
1566
+ };
1567
+ var SandboxSubprocessDispatcher = class {
1568
+ commandExists;
1569
+ transport;
1570
+ profileOptions;
1571
+ workerEntry;
1572
+ nodeBinary;
1573
+ constructor(options = {}) {
1574
+ this.commandExists = options.commandExists || defaultCommandExists;
1575
+ this.transport = options.transport || new NodeSubprocessTransport();
1576
+ this.profileOptions = {
1577
+ workspaceRoot: options.workspaceRoot,
1578
+ homeDir: options.homeDir,
1579
+ env: options.env
1580
+ };
1581
+ this.workerEntry = options.workerEntry || resolveDefaultWorkerEntry();
1582
+ this.nodeBinary = options.nodeBinary || process.execPath;
1583
+ }
1584
+ async dispatch(request) {
1585
+ if (request.capability.handler.kind !== "subprocess") {
1586
+ return buildFailureOutput2("INVALID_HANDLER_KIND", "Subprocess dispatcher requires subprocess handlers.");
1587
+ }
1588
+ if (!this.commandExists("bwrap")) {
1589
+ return withScopeNote2(buildFailureOutput2("SUBPROCESS_SANDBOX_UNAVAILABLE", 'Subprocess execution unavailable: required binary "bwrap" was not found.'), request.scopeEnforced);
1590
+ }
1591
+ const invocationPayload = {
1592
+ tool_name: request.capability.name,
1593
+ handler_entry: request.capability.handler.entry,
1594
+ input: request.input,
1595
+ scope_enforced: request.scopeEnforced,
1596
+ receipt_id: randomUUID()
1597
+ };
1598
+ const profile = buildSandboxProfileFromScopes(request.scopeEnforced, this.profileOptions);
1599
+ const sandboxInvocation = buildBwrapInvocation({
1600
+ profile,
1601
+ command: [this.nodeBinary, this.workerEntry]
1602
+ });
1603
+ let transportResult;
1604
+ try {
1605
+ transportResult = await this.transport.execute({
1606
+ command: sandboxInvocation.command,
1607
+ args: sandboxInvocation.args,
1608
+ stdin: JSON.stringify(invocationPayload),
1609
+ env: sandboxInvocation.env
1610
+ });
1611
+ } catch (error) {
1612
+ return withScopeNote2(buildFailureOutput2("SUBPROCESS_TRANSPORT_FAILED", error.message), request.scopeEnforced);
1613
+ }
1614
+ if (transportResult.code !== 0) {
1615
+ return withScopeNote2(buildFailureOutput2("SUBPROCESS_EXIT_NONZERO", "Subprocess worker exited with non-zero code.", {
1616
+ exit_code: transportResult.code,
1617
+ stderr: transportResult.stderr
1618
+ }), request.scopeEnforced);
1619
+ }
1620
+ try {
1621
+ const response = parseToolRunnerWorkerResponse(transportResult.stdout);
1622
+ return withScopeNote2(response.output, request.scopeEnforced);
1623
+ } catch (error) {
1624
+ return withScopeNote2(buildFailureOutput2("SUBPROCESS_PROTOCOL_ERROR", error.message, {
1625
+ stdout: transportResult.stdout,
1626
+ stderr: transportResult.stderr
1627
+ }), request.scopeEnforced);
1628
+ }
1629
+ }
1630
+ };
1631
+
1632
+ // ../kernel/dist/state-machine/index.js
1633
+ var TASK_LIFECYCLE_STATES = {
1634
+ READY: "ready",
1635
+ ACTIVE: "active",
1636
+ BLOCKED: "blocked",
1637
+ WAITING: "waiting",
1638
+ DONE: "done"
1639
+ };
1640
+ var CANONICAL_STATES = [
1641
+ TASK_LIFECYCLE_STATES.READY,
1642
+ TASK_LIFECYCLE_STATES.ACTIVE,
1643
+ TASK_LIFECYCLE_STATES.BLOCKED,
1644
+ TASK_LIFECYCLE_STATES.WAITING,
1645
+ TASK_LIFECYCLE_STATES.DONE
1646
+ ];
1647
+ var ALLOWED_TRANSITIONS = {
1648
+ ready: ["active"],
1649
+ active: ["blocked", "waiting", "done", "ready"],
1650
+ blocked: ["active", "done"],
1651
+ waiting: ["active", "done"],
1652
+ done: []
1653
+ };
1654
+ function buildAliasLookup(aliases) {
1655
+ const lookup = /* @__PURE__ */ new Map();
1656
+ for (const state of CANONICAL_STATES) {
1657
+ lookup.set(state, state);
1658
+ }
1659
+ for (const [state, alias] of Object.entries(aliases)) {
1660
+ if (!alias) {
1661
+ continue;
1662
+ }
1663
+ const normalizedAlias = alias.trim();
1664
+ if (!normalizedAlias) {
1665
+ throw new Error(`Invalid alias for state "${state}": alias must be non-empty`);
1666
+ }
1667
+ const existing = lookup.get(normalizedAlias);
1668
+ if (existing && existing !== state) {
1669
+ throw new Error(`Ambiguous alias "${normalizedAlias}" resolves to both "${existing}" and "${state}"`);
1670
+ }
1671
+ lookup.set(normalizedAlias, state);
1672
+ }
1673
+ return lookup;
1674
+ }
1675
+ function resolveTaskState(state, aliases = {}) {
1676
+ if (state === null || state === void 0) {
1677
+ throw new Error(`Invalid state: ${state}`);
1678
+ }
1679
+ const normalized = state.trim();
1680
+ if (!normalized) {
1681
+ throw new Error(`Invalid state: ${state}`);
1682
+ }
1683
+ const lookup = buildAliasLookup(aliases);
1684
+ const resolved = lookup.get(normalized);
1685
+ if (!resolved) {
1686
+ throw new Error(`Invalid state: ${state}. Expected one of: ${Array.from(lookup.keys()).join(", ")}`);
1687
+ }
1688
+ return resolved;
1689
+ }
1690
+ function assertTransition(from, to, taskId, aliases = {}) {
1691
+ const fromState = resolveTaskState(from, aliases);
1692
+ const toState = resolveTaskState(to, aliases);
1693
+ const allowed = ALLOWED_TRANSITIONS[fromState];
1694
+ if (!allowed.includes(toState)) {
1695
+ const terminalHint = fromState === TASK_LIFECYCLE_STATES.DONE ? " (done is a terminal state)" : "";
1696
+ const allowedNext = allowed.length > 0 ? allowed.join(", ") : "(none)";
1697
+ throw new Error(`Illegal state transition for ${taskId}: ${fromState} -> ${toState}${terminalHint}. Allowed next states: ${allowedNext}`);
1698
+ }
1699
+ }
1700
+
1701
+ // ../kernel/dist/tool-host/builtins/capabilities.js
1702
+ import { readFile as readFile3 } from "fs/promises";
1703
+ import { resolve } from "path";
1704
+ import micromatch from "micromatch";
1705
+ import { z as z3 } from "zod";
1706
+
1707
+ // ../kernel/dist/tool-host/tool-registry.js
1708
+ var ToolRegistry = class {
1709
+ capabilities = /* @__PURE__ */ new Map();
1710
+ register(capability) {
1711
+ const validated = this.validate(capability);
1712
+ if (this.capabilities.has(validated.name)) {
1713
+ throw new Error(`Tool "${validated.name}" is already registered`);
1714
+ }
1715
+ this.capabilities.set(validated.name, validated);
1716
+ return validated;
1717
+ }
1718
+ lookup(name) {
1719
+ return this.capabilities.get(name) ?? null;
1720
+ }
1721
+ list() {
1722
+ return [...this.capabilities.values()];
1723
+ }
1724
+ validate(capability) {
1725
+ const parsed = ToolCapabilitySchema.safeParse(capability);
1726
+ if (!parsed.success) {
1727
+ throw new Error(`ToolCapability validation failed: ${parsed.error.message}`);
1728
+ }
1729
+ return parsed.data;
1730
+ }
1731
+ };
1732
+
1733
+ // ../kernel/dist/tool-host/builtins/capabilities.js
1734
+ var BUILTIN_POLICY_IDS = {
1735
+ DEFAULT_ALLOW: KERNEL_POLICY_IDS.BUILTIN_DEFAULT,
1736
+ PROC_EXEC_DEFAULT_DENY: KERNEL_POLICY_IDS.PROC_EXEC_DEFAULT_DENY
1737
+ };
1738
+ var BUILTIN_TOOL_NAMES = {
1739
+ FS_READ: "fs:read",
1740
+ FS_WRITE: "fs:write",
1741
+ PROC_EXEC: "proc:exec"
1742
+ };
1743
+ var BUILTIN_SUBPROCESS_ENTRIES = {
1744
+ FS_WRITE: "kernel/tool-impl/fs-write.js",
1745
+ PROC_EXEC: "kernel/tool-impl/proc-exec.js"
1746
+ };
1747
+ var BUILTIN_PACK_ID = "kernel-builtins";
1748
+ var BUILTIN_TOOL_VERSION = "1.0.0";
1749
+ var TEXT_ENCODINGS = [UTF8_ENCODING, BASE64_ENCODING];
1750
+ var FsReadInputSchema = z3.object({
1751
+ path: z3.string().min(1),
1752
+ encoding: z3.enum(TEXT_ENCODINGS).optional()
1753
+ });
1754
+ var FsReadOutputSchema = z3.object({
1755
+ path: z3.string().min(1),
1756
+ content: z3.string(),
1757
+ bytes: z3.number().int().nonnegative()
1758
+ });
1759
+ var FsWriteInputSchema = z3.object({
1760
+ path: z3.string().min(1),
1761
+ content: z3.string(),
1762
+ encoding: z3.enum(TEXT_ENCODINGS).optional()
1763
+ });
1764
+ var FsWriteOutputSchema = z3.object({
1765
+ queued: z3.boolean(),
1766
+ target: z3.string().min(1)
1767
+ });
1768
+ var ProcExecInputSchema = z3.object({
1769
+ command: z3.string().min(1),
1770
+ args: z3.array(z3.string()).default([])
1771
+ });
1772
+ var ProcExecOutputSchema = z3.object({
1773
+ exit_code: z3.number().int(),
1774
+ stdout: z3.string(),
1775
+ stderr: z3.string()
1776
+ });
1777
+ function buildScopeKey(scope) {
1778
+ if (scope.type === "path") {
1779
+ return `${scope.type}:${scope.access}:${scope.pattern}`;
1780
+ }
1781
+ return `${scope.type}:${scope.posture}`;
1782
+ }
1783
+ function dedupeScopes(scopes) {
1784
+ const seen = /* @__PURE__ */ new Set();
1785
+ const deduped = [];
1786
+ for (const scope of scopes) {
1787
+ const key = buildScopeKey(scope);
1788
+ if (seen.has(key)) {
1789
+ continue;
1790
+ }
1791
+ seen.add(key);
1792
+ deduped.push(scope);
1793
+ }
1794
+ return deduped;
1795
+ }
1796
+ function buildFailureOutput3(code, message) {
1797
+ return {
1798
+ success: false,
1799
+ error: {
1800
+ code,
1801
+ message
1802
+ }
1803
+ };
1804
+ }
1805
+ async function runFsRead(input, scopes) {
1806
+ const parsedInput = FsReadInputSchema.safeParse(input);
1807
+ if (!parsedInput.success) {
1808
+ return buildFailureOutput3("INVALID_INPUT", parsedInput.error.message);
1809
+ }
1810
+ const resolvedPath = resolve(parsedInput.data.path);
1811
+ const canReadPath = scopesAllowReadPath(scopes, resolvedPath);
1812
+ if (!canReadPath) {
1813
+ return buildFailureOutput3("SCOPE_VIOLATION", `Path ${resolvedPath} is outside the enforced read scopes for fs:read.`);
1814
+ }
1815
+ const encoding = parsedInput.data.encoding || UTF8_ENCODING;
1816
+ try {
1817
+ const content = await readFile3(resolvedPath, { encoding });
1818
+ return {
1819
+ success: true,
1820
+ data: {
1821
+ path: resolvedPath,
1822
+ content,
1823
+ bytes: Buffer.byteLength(content)
1824
+ }
1825
+ };
1826
+ } catch (error) {
1827
+ return buildFailureOutput3("FS_READ_FAILED", error.message);
1828
+ }
1829
+ }
1830
+ function scopesAllowReadPath(scopes, filePath) {
1831
+ const readScopes = scopes.filter((scope) => scope.type === "path" && scope.access === "read");
1832
+ if (readScopes.length === 0) {
1833
+ return false;
1834
+ }
1835
+ const normalizedPath = filePath.replace(/\\/g, "/");
1836
+ return readScopes.some((scope) => micromatch.isMatch(normalizedPath, resolve(scope.pattern).replace(/\\/g, "/")));
1837
+ }
1838
+ function createFsReadCapability(scopes) {
1839
+ return {
1840
+ name: BUILTIN_TOOL_NAMES.FS_READ,
1841
+ domain: "filesystem",
1842
+ version: BUILTIN_TOOL_VERSION,
1843
+ input_schema: FsReadInputSchema,
1844
+ output_schema: FsReadOutputSchema,
1845
+ permission: "read",
1846
+ required_scopes: scopes,
1847
+ handler: {
1848
+ kind: "in-process",
1849
+ fn: (input) => runFsRead(input, scopes)
1850
+ },
1851
+ description: "Read file content from an allowed scope",
1852
+ pack: BUILTIN_PACK_ID
1853
+ };
1854
+ }
1855
+ function createFsWriteCapability(scopes, entry) {
1856
+ return {
1857
+ name: BUILTIN_TOOL_NAMES.FS_WRITE,
1858
+ domain: "filesystem",
1859
+ version: BUILTIN_TOOL_VERSION,
1860
+ input_schema: FsWriteInputSchema,
1861
+ output_schema: FsWriteOutputSchema,
1862
+ permission: "write",
1863
+ required_scopes: scopes,
1864
+ handler: {
1865
+ kind: "subprocess",
1866
+ entry
1867
+ },
1868
+ description: "Write file content through sandboxed subprocess execution",
1869
+ pack: BUILTIN_PACK_ID
1870
+ };
1871
+ }
1872
+ function createProcExecCapability(scopes, entry) {
1873
+ return {
1874
+ name: BUILTIN_TOOL_NAMES.PROC_EXEC,
1875
+ domain: "process",
1876
+ version: BUILTIN_TOOL_VERSION,
1877
+ input_schema: ProcExecInputSchema,
1878
+ output_schema: ProcExecOutputSchema,
1879
+ permission: "admin",
1880
+ required_scopes: scopes,
1881
+ handler: {
1882
+ kind: "subprocess",
1883
+ entry
1884
+ },
1885
+ description: "Execute internal subprocess actions for pack tool implementations",
1886
+ pack: BUILTIN_PACK_ID
1887
+ };
1888
+ }
1889
+ function createBuiltinToolCapabilities(options) {
1890
+ const dedupedScopes = dedupeScopes(options.declaredScopes);
1891
+ const subprocessEntries = {
1892
+ fsWrite: options.subprocessEntries?.fsWrite || BUILTIN_SUBPROCESS_ENTRIES.FS_WRITE,
1893
+ procExec: options.subprocessEntries?.procExec || BUILTIN_SUBPROCESS_ENTRIES.PROC_EXEC
1894
+ };
1895
+ const capabilities = [
1896
+ createFsReadCapability(dedupedScopes),
1897
+ createFsWriteCapability(dedupedScopes, subprocessEntries.fsWrite)
1898
+ ];
1899
+ if (options.includeInternalTools) {
1900
+ capabilities.push(createProcExecCapability(dedupedScopes, subprocessEntries.procExec));
1901
+ }
1902
+ return capabilities;
1903
+ }
1904
+ function registerBuiltinToolCapabilities(registry, options) {
1905
+ const capabilities = createBuiltinToolCapabilities(options);
1906
+ for (const capability of capabilities) {
1907
+ registry.register(capability);
1908
+ }
1909
+ return capabilities;
1910
+ }
1911
+ function createBuiltinPolicyHook(options = {}) {
1912
+ const allowInternalProcExec = options.allowInternalProcExec || false;
1913
+ return async (input) => {
1914
+ if (input.capability.name === BUILTIN_TOOL_NAMES.PROC_EXEC && !allowInternalProcExec) {
1915
+ return [
1916
+ {
1917
+ policy_id: BUILTIN_POLICY_IDS.PROC_EXEC_DEFAULT_DENY,
1918
+ decision: "deny",
1919
+ reason: "proc:exec is internal-only and default-denied by workspace policy."
1920
+ }
1921
+ ];
1922
+ }
1923
+ return [
1924
+ {
1925
+ policy_id: BUILTIN_POLICY_IDS.DEFAULT_ALLOW,
1926
+ decision: "allow",
1927
+ reason: "Builtin tool allowed by default policy."
1928
+ }
1929
+ ];
1930
+ };
1931
+ }
1932
+
1933
+ // ../kernel/dist/tool-host/tool-host.js
1934
+ import { randomUUID as randomUUID2 } from "crypto";
1935
+
1936
+ // ../kernel/dist/tool-host/scope-intersection.js
1937
+ import micromatch2 from "micromatch";
1938
+ function toPathScopes(scopes, access2) {
1939
+ return scopes.filter((scope) => scope.type === "path" && scope.access === access2);
1940
+ }
1941
+ function toNetworkScopes(scopes) {
1942
+ return scopes.filter((scope) => scope.type === "network");
1943
+ }
1944
+ function patternContains(containerPattern, nestedPattern) {
1945
+ if (containerPattern.startsWith("!") || nestedPattern.startsWith("!")) {
1946
+ return false;
1947
+ }
1948
+ const hasGlobstar = nestedPattern.includes("**");
1949
+ if (hasGlobstar) {
1950
+ const rawExpansions = [
1951
+ // 0 segments: double-star matches empty (strip the globstar segment)
1952
+ nestedPattern.replace(/\*\*\/?/g, "").replace(/\/+/g, "/").replace(/^\/|\/$/g, ""),
1953
+ // 1 segment depth
1954
+ nestedPattern.replace(/\*\*/g, "__depth1__"),
1955
+ // 2 segment depth
1956
+ nestedPattern.replace(/\*\*/g, "__depth2a__/__depth2b__"),
1957
+ // 3 segment depth
1958
+ nestedPattern.replace(/\*\*/g, "__depth3a__/__depth3b__/__depth3c__")
1959
+ ];
1960
+ const testPaths = rawExpansions.map((p) => p.replace(/\*/g, "__segment__")).filter((p) => p.length > 0);
1961
+ const validTestPaths = testPaths.filter((tp) => micromatch2.isMatch(tp, nestedPattern));
1962
+ return validTestPaths.length > 0 && validTestPaths.every((tp) => micromatch2.isMatch(tp, containerPattern));
1963
+ }
1964
+ const testPath = nestedPattern.replace(/\*/g, "__segment__");
1965
+ return micromatch2.isMatch(testPath, containerPattern);
1966
+ }
1967
+ function patternsOverlap(left, right) {
1968
+ return left === right || patternContains(left, right) || patternContains(right, left) || micromatch2.isMatch(right, left) || micromatch2.isMatch(left, right);
1969
+ }
1970
+ function allPatternsOverlap(patterns) {
1971
+ for (let i = 0; i < patterns.length; i += 1) {
1972
+ const left = patterns[i];
1973
+ if (left === void 0) {
1974
+ continue;
1975
+ }
1976
+ for (let j = i + 1; j < patterns.length; j += 1) {
1977
+ const right = patterns[j];
1978
+ if (right === void 0) {
1979
+ continue;
1980
+ }
1981
+ if (!patternsOverlap(left, right)) {
1982
+ return false;
1983
+ }
1984
+ }
1985
+ }
1986
+ return true;
1987
+ }
1988
+ function specificityScore(pattern) {
1989
+ const literalLength = pattern.replace(/[*[\]{}()!?+@]/g, "").length;
1990
+ const wildcardCount = (pattern.match(/\*/g) ?? []).length;
1991
+ return literalLength - wildcardCount * 5;
1992
+ }
1993
+ function selectNarrowestPattern(patterns) {
1994
+ return [...patterns].sort((left, right) => specificityScore(right) - specificityScore(left))[0] ?? patterns[0] ?? "";
1995
+ }
1996
+ function intersectPathScopes(workspaceAllowed, laneAllowed, taskDeclared, toolRequired, access2) {
1997
+ const workspace = toPathScopes(workspaceAllowed, access2);
1998
+ const lane = toPathScopes(laneAllowed, access2);
1999
+ const task = toPathScopes(taskDeclared, access2);
2000
+ const tool = toPathScopes(toolRequired, access2);
2001
+ if (workspace.length === 0 || lane.length === 0 || task.length === 0 || tool.length === 0) {
2002
+ return [];
2003
+ }
2004
+ const scopes = /* @__PURE__ */ new Map();
2005
+ for (const toolScope of tool) {
2006
+ for (const workspaceScope of workspace) {
2007
+ if (!patternsOverlap(toolScope.pattern, workspaceScope.pattern)) {
2008
+ continue;
2009
+ }
2010
+ for (const laneScope of lane) {
2011
+ if (!patternsOverlap(toolScope.pattern, laneScope.pattern)) {
2012
+ continue;
2013
+ }
2014
+ for (const taskScope of task) {
2015
+ if (!patternsOverlap(toolScope.pattern, taskScope.pattern)) {
2016
+ continue;
2017
+ }
2018
+ const candidates = [
2019
+ workspaceScope.pattern,
2020
+ laneScope.pattern,
2021
+ taskScope.pattern,
2022
+ toolScope.pattern
2023
+ ];
2024
+ if (!allPatternsOverlap(candidates)) {
2025
+ continue;
2026
+ }
2027
+ const pattern = selectNarrowestPattern(candidates);
2028
+ const dedupeKey = `${access2}:${pattern}`;
2029
+ scopes.set(dedupeKey, {
2030
+ type: "path",
2031
+ access: access2,
2032
+ pattern
2033
+ });
2034
+ }
2035
+ }
2036
+ }
2037
+ }
2038
+ return [...scopes.values()];
2039
+ }
2040
+ function intersectNetworkScopes(workspaceAllowed, laneAllowed, taskDeclared, toolRequired) {
2041
+ const workspace = toNetworkScopes(workspaceAllowed);
2042
+ const lane = toNetworkScopes(laneAllowed);
2043
+ const task = toNetworkScopes(taskDeclared);
2044
+ const tool = toNetworkScopes(toolRequired);
2045
+ if (workspace.length === 0 || lane.length === 0 || task.length === 0 || tool.length === 0) {
2046
+ return [];
2047
+ }
2048
+ const allLayers = [workspace, lane, task, tool];
2049
+ for (const layer of allLayers) {
2050
+ const hasOnlyOff = layer.every((s) => s.posture === "off");
2051
+ if (hasOnlyOff) {
2052
+ const toolWantsNetwork = tool.some((s) => s.posture !== "off");
2053
+ if (toolWantsNetwork) {
2054
+ return [];
2055
+ }
2056
+ }
2057
+ }
2058
+ const hasAllowlist = allLayers.some((layer) => layer.some((s) => s.posture === "allowlist"));
2059
+ if (hasAllowlist) {
2060
+ const layerEntries = [];
2061
+ for (const layer of allLayers) {
2062
+ const allowlistScopes = layer.filter((s) => s.posture === "allowlist");
2063
+ const hasFullScope = layer.some((s) => s.posture === "full");
2064
+ if (allowlistScopes.length > 0) {
2065
+ const entries = /* @__PURE__ */ new Set();
2066
+ for (const scope of allowlistScopes) {
2067
+ const scopeEntries = "allowlist_entries" in scope ? scope.allowlist_entries : [];
2068
+ for (const entry of scopeEntries) {
2069
+ entries.add(entry);
2070
+ }
2071
+ }
2072
+ layerEntries.push(entries);
2073
+ } else if (hasFullScope) {
2074
+ continue;
2075
+ } else {
2076
+ return [];
2077
+ }
2078
+ }
2079
+ if (layerEntries.length === 0) {
2080
+ return [];
2081
+ }
2082
+ let intersected = layerEntries[0];
2083
+ for (let i = 1; i < layerEntries.length; i++) {
2084
+ const next = layerEntries[i];
2085
+ if (!intersected || !next) {
2086
+ return [];
2087
+ }
2088
+ intersected = new Set([...intersected].filter((entry) => next.has(entry)));
2089
+ }
2090
+ if (!intersected || intersected.size === 0) {
2091
+ return [];
2092
+ }
2093
+ return [
2094
+ {
2095
+ type: "network",
2096
+ posture: "allowlist",
2097
+ allowlist_entries: [...intersected].sort()
2098
+ }
2099
+ ];
2100
+ }
2101
+ const scopes = /* @__PURE__ */ new Map();
2102
+ for (const toolScope of tool) {
2103
+ const posture = toolScope.posture;
2104
+ if (workspace.some((scope) => scope.posture === posture) && lane.some((scope) => scope.posture === posture) && task.some((scope) => scope.posture === posture)) {
2105
+ scopes.set(posture, {
2106
+ type: "network",
2107
+ posture
2108
+ });
2109
+ }
2110
+ }
2111
+ return [...scopes.values()];
2112
+ }
2113
+ function intersectToolScopes(input) {
2114
+ const readPaths = intersectPathScopes(input.workspaceAllowed, input.laneAllowed, input.taskDeclared, input.toolRequired, "read");
2115
+ const writePaths = intersectPathScopes(input.workspaceAllowed, input.laneAllowed, input.taskDeclared, input.toolRequired, "write");
2116
+ const network = intersectNetworkScopes(input.workspaceAllowed, input.laneAllowed, input.taskDeclared, input.toolRequired);
2117
+ return [...readPaths, ...writePaths, ...network];
2118
+ }
2119
+
2120
+ // ../kernel/dist/tool-host/subprocess-dispatcher.js
2121
+ var DefaultSubprocessDispatcher = class {
2122
+ async dispatch() {
2123
+ return {
2124
+ success: false,
2125
+ error: {
2126
+ code: "SUBPROCESS_NOT_AVAILABLE",
2127
+ message: "Subprocess execution unavailable: no subprocess dispatcher was configured."
2128
+ }
2129
+ };
2130
+ }
2131
+ };
2132
+
2133
+ // ../kernel/dist/tool-host/tool-host.js
2134
+ function resolveMetadata(context) {
2135
+ if (!context.metadata || typeof context.metadata !== "object") {
2136
+ return {};
2137
+ }
2138
+ return context.metadata;
2139
+ }
2140
+ function parseScopeList(candidate, fallback) {
2141
+ const parsed = ToolScopeSchema.array().safeParse(candidate);
2142
+ if (parsed.success) {
2143
+ return parsed.data;
2144
+ }
2145
+ return fallback;
2146
+ }
2147
+ function parseOptionalString(candidate) {
2148
+ return typeof candidate === "string" && candidate.trim().length > 0 ? candidate : void 0;
2149
+ }
2150
+ function normalizeScopePattern(pattern) {
2151
+ return pattern.replaceAll("\\", "/").replace(/^\.\//, "");
2152
+ }
2153
+ function isReservedFrameworkWriteScope(scope) {
2154
+ if (scope.type !== "path" || scope.access !== "write") {
2155
+ return false;
2156
+ }
2157
+ const normalized = normalizeScopePattern(scope.pattern);
2158
+ return normalized === RESERVED_FRAMEWORK_SCOPE_ROOT || normalized.startsWith(RESERVED_FRAMEWORK_SCOPE_PREFIX);
2159
+ }
2160
+ function collectReservedFrameworkWriteScopes(scopes) {
2161
+ const blocked = scopes.filter((scope) => isReservedFrameworkWriteScope(scope)).map((scope) => normalizeScopePattern(scope.pattern));
2162
+ return [...new Set(blocked)];
2163
+ }
2164
+ var ToolHost = class {
2165
+ registry;
2166
+ evidenceStore;
2167
+ subprocessDispatcher;
2168
+ policyHook;
2169
+ runtimeVersion;
2170
+ now;
2171
+ onTraceError;
2172
+ onTraceAppended;
2173
+ constructor(options) {
2174
+ if (!options.policyHook) {
2175
+ throw new Error("ToolHost requires an explicit policyHook. Use allowAllPolicyHook for development or provide a production policy.");
2176
+ }
2177
+ this.registry = options.registry;
2178
+ this.evidenceStore = options.evidenceStore;
2179
+ this.subprocessDispatcher = options.subprocessDispatcher ?? new DefaultSubprocessDispatcher();
2180
+ this.policyHook = options.policyHook;
2181
+ this.runtimeVersion = options.runtimeVersion ?? DEFAULT_KERNEL_RUNTIME_VERSION;
2182
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
2183
+ this.onTraceError = options.onTraceError;
2184
+ this.onTraceAppended = options.onTraceAppended;
2185
+ }
2186
+ async onStartup() {
2187
+ return this.evidenceStore.reconcileOrphanedStarts();
2188
+ }
2189
+ async onShutdown() {
2190
+ return this.evidenceStore.reconcileOrphanedStarts();
2191
+ }
2192
+ async execute(name, input, ctx) {
2193
+ const context = ExecutionContextSchema.parse(ctx);
2194
+ const capability = this.registry.lookup(name);
2195
+ if (!capability) {
2196
+ return {
2197
+ success: false,
2198
+ error: {
2199
+ code: TOOL_ERROR_CODES.TOOL_NOT_FOUND,
2200
+ message: `Tool "${name}" is not registered`
2201
+ }
2202
+ };
2203
+ }
2204
+ const metadata = resolveMetadata(context);
2205
+ const { scopeRequested, scopeAllowed, scopeEnforced, reservedFrameworkWriteScopes } = this.resolveScope(capability, context, metadata);
2206
+ const { dataHash: inputHash, dataRef: inputRef } = await this.evidenceStore.persistData(input);
2207
+ const receiptId = randomUUID2();
2208
+ const startedAt = this.now().getTime();
2209
+ const timestamp = new Date(startedAt).toISOString();
2210
+ const workspaceConfigHashCandidate = parseOptionalString(metadata[EXECUTION_METADATA_KEYS.WORKSPACE_CONFIG_HASH]);
2211
+ const workspaceConfigHash = workspaceConfigHashCandidate && SHA256_HEX_REGEX.test(workspaceConfigHashCandidate) ? workspaceConfigHashCandidate : DEFAULT_WORKSPACE_CONFIG_HASH;
2212
+ const runtimeVersion = parseOptionalString(metadata[EXECUTION_METADATA_KEYS.RUNTIME_VERSION]) ?? this.runtimeVersion;
2213
+ const packVersion = parseOptionalString(metadata[EXECUTION_METADATA_KEYS.PACK_VERSION]);
2214
+ const packIntegrity = parseOptionalString(metadata[EXECUTION_METADATA_KEYS.PACK_INTEGRITY]);
2215
+ const packId = capability.pack ?? parseOptionalString(metadata[EXECUTION_METADATA_KEYS.PACK_ID]);
2216
+ const startedEntry = {
2217
+ schema_version: 1,
2218
+ kind: TOOL_TRACE_KINDS.TOOL_CALL_STARTED,
2219
+ receipt_id: receiptId,
2220
+ run_id: context.run_id,
2221
+ task_id: context.task_id,
2222
+ session_id: context.session_id,
2223
+ timestamp,
2224
+ tool_name: capability.name,
2225
+ execution_mode: capability.handler.kind,
2226
+ scope_requested: scopeRequested,
2227
+ scope_allowed: scopeAllowed,
2228
+ scope_enforced: scopeEnforced,
2229
+ input_hash: inputHash,
2230
+ input_ref: inputRef,
2231
+ tool_version: capability.version,
2232
+ pack_id: packId,
2233
+ pack_version: packVersion,
2234
+ pack_integrity: packIntegrity,
2235
+ workspace_config_hash: workspaceConfigHash,
2236
+ runtime_version: runtimeVersion
2237
+ };
2238
+ try {
2239
+ await this.evidenceStore.appendTrace(startedEntry);
2240
+ this.onTraceAppended?.(startedEntry);
2241
+ } catch (error) {
2242
+ this.onTraceError?.(error);
2243
+ }
2244
+ const authResult = await this.authorize({
2245
+ receiptId,
2246
+ startedAt,
2247
+ capability,
2248
+ input,
2249
+ context,
2250
+ scopeRequested,
2251
+ scopeAllowed,
2252
+ scopeEnforced,
2253
+ reservedFrameworkWriteScopes
2254
+ });
2255
+ if (authResult.denied) {
2256
+ return authResult.output;
2257
+ }
2258
+ const parsedInput = capability.input_schema.safeParse(input);
2259
+ if (!parsedInput.success) {
2260
+ const invalidInputOutput = {
2261
+ success: false,
2262
+ error: {
2263
+ code: TOOL_ERROR_CODES.INVALID_INPUT,
2264
+ message: parsedInput.error.message
2265
+ }
2266
+ };
2267
+ try {
2268
+ await this.recordDeniedTrace({
2269
+ receiptId,
2270
+ startedAt,
2271
+ result: "failure",
2272
+ scopeEnforcementNote: "Input validation failed before dispatch.",
2273
+ policyDecisions: authResult.policyDecisions
2274
+ });
2275
+ } catch (error) {
2276
+ this.onTraceError?.(error);
2277
+ }
2278
+ return invalidInputOutput;
2279
+ }
2280
+ let output = await this.dispatch(capability, parsedInput.data, context, scopeEnforced);
2281
+ output = this.normalizeOutput(output, capability);
2282
+ try {
2283
+ await this.recordTrace({
2284
+ receiptId,
2285
+ startedAt,
2286
+ output,
2287
+ policyDecisions: authResult.policyDecisions
2288
+ });
2289
+ } catch (error) {
2290
+ this.onTraceError?.(error);
2291
+ }
2292
+ return output;
2293
+ }
2294
+ resolveScope(capability, context, metadata) {
2295
+ const workspaceAllowed = parseScopeList(metadata[EXECUTION_METADATA_KEYS.WORKSPACE_ALLOWED_SCOPES], context.allowed_scopes);
2296
+ const laneAllowed = parseScopeList(metadata[EXECUTION_METADATA_KEYS.LANE_ALLOWED_SCOPES], context.allowed_scopes);
2297
+ const taskDeclared = parseScopeList(metadata[EXECUTION_METADATA_KEYS.TASK_DECLARED_SCOPES], context.allowed_scopes);
2298
+ const scopeRequested = capability.required_scopes;
2299
+ const reservedFrameworkWriteScopes = collectReservedFrameworkWriteScopes(scopeRequested);
2300
+ const scopeAllowed = intersectToolScopes({
2301
+ workspaceAllowed,
2302
+ laneAllowed,
2303
+ taskDeclared,
2304
+ toolRequired: scopeRequested
2305
+ });
2306
+ const scopeEnforced = scopeAllowed;
2307
+ return { scopeRequested, scopeAllowed, scopeEnforced, reservedFrameworkWriteScopes };
2308
+ }
2309
+ async authorize(params) {
2310
+ const { receiptId, startedAt, capability, input, context, scopeRequested, scopeAllowed, scopeEnforced, reservedFrameworkWriteScopes } = params;
2311
+ if (reservedFrameworkWriteScopes.length > 0) {
2312
+ const output = {
2313
+ success: false,
2314
+ error: {
2315
+ code: TOOL_ERROR_CODES.SCOPE_DENIED,
2316
+ message: `Reserved scope violation: pack/tool write scopes under ${RESERVED_FRAMEWORK_SCOPE_GLOB} are not allowed.`,
2317
+ details: {
2318
+ reserved_scopes: reservedFrameworkWriteScopes
2319
+ }
2320
+ }
2321
+ };
2322
+ try {
2323
+ await this.recordDeniedTrace({
2324
+ receiptId,
2325
+ startedAt,
2326
+ result: "denied",
2327
+ scopeEnforcementNote: `Denied by reserved framework boundary: ${RESERVED_FRAMEWORK_SCOPE_GLOB} is framework-owned.`,
2328
+ policyDecisions: [
2329
+ {
2330
+ policy_id: KERNEL_POLICY_IDS.SCOPE_RESERVED_PATH,
2331
+ decision: "deny",
2332
+ reason: `Pack/tool declared write scope targets reserved ${RESERVED_FRAMEWORK_SCOPE_GLOB} namespace`
2333
+ }
2334
+ ]
2335
+ });
2336
+ } catch (error) {
2337
+ this.onTraceError?.(error);
2338
+ }
2339
+ return { denied: true, output };
2340
+ }
2341
+ if (scopeEnforced.length === 0) {
2342
+ const output = {
2343
+ success: false,
2344
+ error: {
2345
+ code: TOOL_ERROR_CODES.SCOPE_DENIED,
2346
+ message: "Scope intersection denied: no allowed scopes remain after intersection.",
2347
+ details: {
2348
+ scope_requested: scopeRequested,
2349
+ scope_allowed: scopeAllowed
2350
+ }
2351
+ }
2352
+ };
2353
+ try {
2354
+ await this.recordDeniedTrace({
2355
+ receiptId,
2356
+ startedAt,
2357
+ result: "denied",
2358
+ scopeEnforcementNote: "Denied by hard boundary: empty scope intersection.",
2359
+ policyDecisions: [
2360
+ {
2361
+ policy_id: KERNEL_POLICY_IDS.SCOPE_BOUNDARY,
2362
+ decision: "deny",
2363
+ reason: "No intersecting scopes after scope resolution"
2364
+ }
2365
+ ]
2366
+ });
2367
+ } catch (error) {
2368
+ this.onTraceError?.(error);
2369
+ }
2370
+ return { denied: true, output };
2371
+ }
2372
+ const tool_arguments = input !== null && typeof input === "object" && !Array.isArray(input) ? input : void 0;
2373
+ const policyDecisions = await this.policyHook({
2374
+ capability,
2375
+ input,
2376
+ context,
2377
+ scopeEnforced,
2378
+ tool_arguments
2379
+ });
2380
+ if (policyDecisions.some((decision) => decision.decision === "deny")) {
2381
+ const output = {
2382
+ success: false,
2383
+ error: {
2384
+ code: TOOL_ERROR_CODES.POLICY_DENIED,
2385
+ message: "Policy hook denied tool execution."
2386
+ }
2387
+ };
2388
+ try {
2389
+ await this.recordDeniedTrace({
2390
+ receiptId,
2391
+ startedAt,
2392
+ result: "denied",
2393
+ scopeEnforcementNote: "Denied by policy hook decision.",
2394
+ policyDecisions
2395
+ });
2396
+ } catch (error) {
2397
+ this.onTraceError?.(error);
2398
+ }
2399
+ return { denied: true, output };
2400
+ }
2401
+ if (policyDecisions.some((decision) => decision.decision === "approval_required")) {
2402
+ const approvalRequestId = randomUUID2();
2403
+ const approvalReasons = policyDecisions.filter((decision) => decision.decision === "approval_required").map((decision) => decision.reason ?? decision.policy_id);
2404
+ const output = {
2405
+ success: false,
2406
+ error: {
2407
+ code: TOOL_ERROR_CODES.APPROVAL_REQUIRED,
2408
+ message: `Tool execution requires human approval: ${approvalReasons.join("; ")}`,
2409
+ details: {
2410
+ request_id: approvalRequestId,
2411
+ tool_name: capability.name,
2412
+ task_id: context.task_id,
2413
+ run_id: context.run_id,
2414
+ policy_decisions: policyDecisions.filter((decision) => decision.decision === "approval_required")
2415
+ }
2416
+ }
2417
+ };
2418
+ try {
2419
+ await this.recordDeniedTrace({
2420
+ receiptId,
2421
+ startedAt,
2422
+ result: "denied",
2423
+ scopeEnforcementNote: "Blocked pending human approval.",
2424
+ policyDecisions
2425
+ });
2426
+ } catch (error) {
2427
+ this.onTraceError?.(error);
2428
+ }
2429
+ return { denied: true, output };
2430
+ }
2431
+ return { denied: false, policyDecisions };
2432
+ }
2433
+ async dispatch(capability, input, context, scopeEnforced) {
2434
+ try {
2435
+ if (capability.handler.kind === TOOL_HANDLER_KINDS.IN_PROCESS) {
2436
+ return await capability.handler.fn(input, context);
2437
+ }
2438
+ return await this.subprocessDispatcher.dispatch({
2439
+ capability,
2440
+ input,
2441
+ context,
2442
+ scopeEnforced
2443
+ });
2444
+ } catch (error) {
2445
+ return {
2446
+ success: false,
2447
+ error: {
2448
+ code: TOOL_ERROR_CODES.TOOL_EXECUTION_FAILED,
2449
+ message: error.message
2450
+ }
2451
+ };
2452
+ }
2453
+ }
2454
+ normalizeOutput(output, capability) {
2455
+ const normalizedOutputResult = ToolOutputSchema.safeParse(output);
2456
+ if (!normalizedOutputResult.success) {
2457
+ return {
2458
+ success: false,
2459
+ error: {
2460
+ code: TOOL_ERROR_CODES.INVALID_OUTPUT,
2461
+ message: normalizedOutputResult.error.message
2462
+ }
2463
+ };
2464
+ }
2465
+ let normalized = normalizedOutputResult.data;
2466
+ if (capability.output_schema && normalized.success) {
2467
+ const parsedData = capability.output_schema.safeParse(normalized.data);
2468
+ if (!parsedData.success) {
2469
+ normalized = {
2470
+ success: false,
2471
+ error: {
2472
+ code: TOOL_ERROR_CODES.INVALID_OUTPUT,
2473
+ message: parsedData.error.message
2474
+ }
2475
+ };
2476
+ }
2477
+ }
2478
+ return normalized;
2479
+ }
2480
+ async recordDeniedTrace(params) {
2481
+ const finishedAt = this.now();
2482
+ const entry = {
2483
+ schema_version: 1,
2484
+ kind: TOOL_TRACE_KINDS.TOOL_CALL_FINISHED,
2485
+ receipt_id: params.receiptId,
2486
+ timestamp: finishedAt.toISOString(),
2487
+ result: params.result,
2488
+ duration_ms: finishedAt.getTime() - params.startedAt,
2489
+ scope_enforcement_note: params.scopeEnforcementNote,
2490
+ policy_decisions: params.policyDecisions,
2491
+ artifacts_written: []
2492
+ };
2493
+ await this.evidenceStore.appendTrace(entry);
2494
+ this.onTraceAppended?.(entry);
2495
+ }
2496
+ async recordTrace(params) {
2497
+ const { receiptId, startedAt, output, policyDecisions } = params;
2498
+ const outputRef = output.data === void 0 ? void 0 : await this.evidenceStore.persistData(output.data);
2499
+ const outputHash = outputRef?.dataHash;
2500
+ const outputReference = outputRef?.dataRef;
2501
+ const result = output.success ? "success" : output.error?.code === TOOL_ERROR_CODES.SCOPE_DENIED ? "denied" : "failure";
2502
+ const finishedAt = this.now();
2503
+ const entry = {
2504
+ schema_version: 1,
2505
+ kind: TOOL_TRACE_KINDS.TOOL_CALL_FINISHED,
2506
+ receipt_id: receiptId,
2507
+ timestamp: finishedAt.toISOString(),
2508
+ result,
2509
+ duration_ms: finishedAt.getTime() - startedAt,
2510
+ output_hash: outputHash,
2511
+ output_ref: outputReference,
2512
+ scope_enforcement_note: result === "success" ? "Allowed by scope intersection and policy." : "Denied or failed during execution.",
2513
+ policy_decisions: policyDecisions,
2514
+ artifacts_written: Array.isArray(output.metadata?.artifacts_written) && output.metadata?.artifacts_written.every((artifact) => typeof artifact === "string") ? output.metadata.artifacts_written : []
2515
+ };
2516
+ await this.evidenceStore.appendTrace(entry);
2517
+ this.onTraceAppended?.(entry);
2518
+ }
2519
+ };
2520
+
2521
+ // ../kernel/dist/runtime/kernel-runtime.js
2522
+ var DEFAULT_RUNTIME_ROOT = path4.join(LUMENFLOW_DIR_NAME, KERNEL_RUNTIME_ROOT_DIR_NAME);
2523
+ var DEFAULT_PACKS_ROOT_CANDIDATES = [
2524
+ PACKS_DIR_NAME,
2525
+ path4.join(PACKAGES_DIR_NAME, LUMENFLOW_SCOPE_NAME, PACKS_DIR_NAME)
2526
+ ];
2527
+ var CLI_PACKAGE_DIRECTORY_NAME = "cli";
2528
+ var CLI_PACKS_ROOT_PATH_SEGMENTS = [
2529
+ "..",
2530
+ "..",
2531
+ "..",
2532
+ CLI_PACKAGE_DIRECTORY_NAME,
2533
+ PACKS_DIR_NAME
2534
+ ];
2535
+ var KERNEL_RUNTIME_MODULE_DIR = path4.dirname(fileURLToPath2(import.meta.url));
2536
+ var CLI_PACKS_ROOT_CANDIDATE = path4.resolve(KERNEL_RUNTIME_MODULE_DIR, ...CLI_PACKS_ROOT_PATH_SEGMENTS);
2537
+ var DEFAULT_PACK_TOOL_INPUT_SCHEMA = z4.record(z4.string(), z4.unknown());
2538
+ var DEFAULT_PACK_TOOL_OUTPUT_SCHEMA = z4.record(z4.string(), z4.unknown());
2539
+ var JSON_SCHEMA_MAX_DEPTH = 12;
2540
+ var RUNTIME_LOAD_STAGE_ERROR_PREFIX = "Runtime load stage failed for pack";
2541
+ var RUNTIME_REGISTRATION_STAGE_ERROR_PREFIX = "Runtime registration stage failed for tool";
2542
+ var WORKSPACE_UPDATED_INIT_SUMMARY = "Workspace config hash initialized during runtime startup.";
2543
+ var SPEC_TAMPERED_ERROR_CODE = "SPEC_TAMPERED";
2544
+ var SPEC_TAMPERED_WORKSPACE_MESSAGE = "Workspace configuration hash mismatch detected; execution blocked.";
2545
+ var SPEC_TAMPERED_WORKSPACE_MISSING_MESSAGE = "Workspace configuration file is missing; execution blocked.";
2546
+ function normalizeTimestamp(now, inputTimestamp) {
2547
+ return inputTimestamp ?? now().toISOString();
2548
+ }
2549
+ function defaultRunIdFactory(taskId, nextRunNumber) {
2550
+ const suffix = randomBytes(4).toString("hex");
2551
+ return `run-${taskId}-${nextRunNumber}-${suffix}`;
2552
+ }
2553
+ function isRunLifecycleEvent2(event) {
2554
+ return isRunLifecycleEventKind(event.kind);
2555
+ }
2556
+ function buildRunHistory(events) {
2557
+ const sortedEvents = [...events].sort((left, right) => {
2558
+ return Date.parse(left.timestamp) - Date.parse(right.timestamp);
2559
+ });
2560
+ const byRun = /* @__PURE__ */ new Map();
2561
+ for (const event of sortedEvents) {
2562
+ if (!isRunLifecycleEvent2(event)) {
2563
+ continue;
2564
+ }
2565
+ const existing = byRun.get(event.run_id);
2566
+ if (event.kind === KERNEL_EVENT_KINDS.RUN_STARTED) {
2567
+ byRun.set(event.run_id, RunSchema.parse({
2568
+ run_id: event.run_id,
2569
+ task_id: event.task_id,
2570
+ status: RUN_STATUSES.EXECUTING,
2571
+ started_at: event.timestamp,
2572
+ by: event.by,
2573
+ session_id: event.session_id
2574
+ }));
2575
+ continue;
2576
+ }
2577
+ const fallback = existing ?? RunSchema.parse({
2578
+ run_id: event.run_id,
2579
+ task_id: event.task_id,
2580
+ status: RUN_STATUSES.PLANNED,
2581
+ started_at: event.timestamp,
2582
+ by: "unknown",
2583
+ session_id: "unknown"
2584
+ });
2585
+ if (event.kind === KERNEL_EVENT_KINDS.RUN_PAUSED) {
2586
+ byRun.set(event.run_id, RunSchema.parse({
2587
+ ...fallback,
2588
+ status: RUN_STATUSES.PAUSED
2589
+ }));
2590
+ continue;
2591
+ }
2592
+ if (event.kind === KERNEL_EVENT_KINDS.RUN_FAILED) {
2593
+ byRun.set(event.run_id, RunSchema.parse({
2594
+ ...fallback,
2595
+ status: RUN_STATUSES.FAILED,
2596
+ completed_at: event.timestamp
2597
+ }));
2598
+ continue;
2599
+ }
2600
+ byRun.set(event.run_id, RunSchema.parse({
2601
+ ...fallback,
2602
+ status: RUN_STATUSES.SUCCEEDED,
2603
+ completed_at: event.timestamp
2604
+ }));
2605
+ }
2606
+ return [...byRun.values()].sort((left, right) => {
2607
+ return Date.parse(left.started_at) - Date.parse(right.started_at);
2608
+ });
2609
+ }
2610
+ function dedupePolicyDecisions(decisions) {
2611
+ const byKey = /* @__PURE__ */ new Map();
2612
+ for (const decision of decisions) {
2613
+ const key = `${decision.policy_id}|${decision.decision}|${decision.reason ?? ""}`;
2614
+ if (!byKey.has(key)) {
2615
+ byKey.set(key, decision);
2616
+ }
2617
+ }
2618
+ return [...byKey.values()];
2619
+ }
2620
+ function toPolicyHookDecisions(evaluation) {
2621
+ if (evaluation.decisions.length === 0) {
2622
+ return [
2623
+ {
2624
+ policy_id: KERNEL_POLICY_IDS.RUNTIME_FALLBACK,
2625
+ decision: evaluation.decision,
2626
+ reason: "Effective policy decision without explicit matching rules."
2627
+ }
2628
+ ];
2629
+ }
2630
+ if (evaluation.decision === "approval_required") {
2631
+ const hasApprovalRequired = evaluation.decisions.some((decision) => decision.decision === "approval_required");
2632
+ if (!hasApprovalRequired) {
2633
+ return [
2634
+ ...evaluation.decisions,
2635
+ {
2636
+ policy_id: KERNEL_POLICY_IDS.APPROVAL_REQUIRED,
2637
+ decision: "approval_required",
2638
+ reason: "Effective policy decision is approval_required."
2639
+ }
2640
+ ];
2641
+ }
2642
+ }
2643
+ if (evaluation.decision === "deny") {
2644
+ const hasHardDeny = evaluation.decisions.some((decision) => decision.decision === "deny");
2645
+ if (!hasHardDeny) {
2646
+ return [
2647
+ ...evaluation.decisions,
2648
+ {
2649
+ policy_id: KERNEL_POLICY_IDS.RUNTIME_FALLBACK,
2650
+ decision: "deny",
2651
+ reason: "Effective policy decision is deny."
2652
+ }
2653
+ ];
2654
+ }
2655
+ }
2656
+ return evaluation.decisions;
2657
+ }
2658
+ function mergeStateAliases(loadedPacks) {
2659
+ const aliases = {};
2660
+ const validStates = /* @__PURE__ */ new Set(["ready", "active", "blocked", "waiting", "done"]);
2661
+ for (const loadedPack of loadedPacks) {
2662
+ for (const [state, alias] of Object.entries(loadedPack.manifest.state_aliases)) {
2663
+ if (!validStates.has(state)) {
2664
+ continue;
2665
+ }
2666
+ aliases[state] = alias;
2667
+ }
2668
+ }
2669
+ return aliases;
2670
+ }
2671
+ function formatRuntimeLoadStageError(packId) {
2672
+ return `${RUNTIME_LOAD_STAGE_ERROR_PREFIX} "${packId}"`;
2673
+ }
2674
+ function formatRuntimeRegistrationStageError(toolName, packId) {
2675
+ return `${RUNTIME_REGISTRATION_STAGE_ERROR_PREFIX} "${toolName}" in pack "${packId}"`;
2676
+ }
2677
+ function buildPackToolDescription(toolName, packId) {
2678
+ return `Pack tool ${toolName} declared by ${packId}`;
2679
+ }
2680
+ function ensureJsonSchemaObject(value, context) {
2681
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
2682
+ throw new Error(`${context} must be a JSON Schema object`);
2683
+ }
2684
+ return value;
2685
+ }
2686
+ function parseJsonSchemaType(schema, context) {
2687
+ const schemaType = schema.type;
2688
+ if (typeof schemaType !== "string") {
2689
+ throw new Error(`${context}.type must be a string`);
2690
+ }
2691
+ return schemaType;
2692
+ }
2693
+ function isJsonLiteral(value) {
2694
+ return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
2695
+ }
2696
+ function buildEnumSchema(enumValues, context) {
2697
+ if (enumValues.length === 0) {
2698
+ throw new Error(`${context}.enum must not be empty`);
2699
+ }
2700
+ if (enumValues.every((value) => typeof value === "string")) {
2701
+ const [firstValue, ...restValues] = enumValues;
2702
+ if (typeof firstValue !== "string") {
2703
+ throw new Error(`${context}.enum must include at least one string value`);
2704
+ }
2705
+ return z4.enum([firstValue, ...restValues]);
2706
+ }
2707
+ if (enumValues.length === 1) {
2708
+ const singleValue = enumValues[0];
2709
+ if (!isJsonLiteral(singleValue)) {
2710
+ throw new Error(`${context}.enum values must be JSON literal values`);
2711
+ }
2712
+ return z4.literal(singleValue);
2713
+ }
2714
+ if (!enumValues.every((value) => isJsonLiteral(value))) {
2715
+ throw new Error(`${context}.enum values must be JSON literal values`);
2716
+ }
2717
+ const literals = enumValues.map((value) => z4.literal(value));
2718
+ const [firstLiteral, ...restLiterals] = literals;
2719
+ if (!firstLiteral) {
2720
+ throw new Error(`${context}.enum must include at least one value`);
2721
+ }
2722
+ return z4.union([firstLiteral, ...restLiterals]);
2723
+ }
2724
+ function buildStringSchema(schema, context) {
2725
+ let resolved = z4.string();
2726
+ if (typeof schema.minLength === "number") {
2727
+ resolved = resolved.min(schema.minLength);
2728
+ }
2729
+ if (typeof schema.maxLength === "number") {
2730
+ resolved = resolved.max(schema.maxLength);
2731
+ }
2732
+ if (typeof schema.pattern === "string") {
2733
+ try {
2734
+ resolved = resolved.regex(new RegExp(schema.pattern));
2735
+ } catch {
2736
+ throw new Error(`${context}.pattern must be a valid regular expression`);
2737
+ }
2738
+ }
2739
+ return resolved;
2740
+ }
2741
+ function buildNumberSchema(schema, _context, integerOnly) {
2742
+ let resolved = integerOnly ? z4.number().int() : z4.number();
2743
+ if (typeof schema.minimum === "number") {
2744
+ resolved = resolved.gte(schema.minimum);
2745
+ }
2746
+ if (typeof schema.maximum === "number") {
2747
+ resolved = resolved.lte(schema.maximum);
2748
+ }
2749
+ if (typeof schema.exclusiveMinimum === "number") {
2750
+ resolved = resolved.gt(schema.exclusiveMinimum);
2751
+ }
2752
+ if (typeof schema.exclusiveMaximum === "number") {
2753
+ resolved = resolved.lt(schema.exclusiveMaximum);
2754
+ }
2755
+ return resolved;
2756
+ }
2757
+ function buildObjectSchema(schema, context, depth) {
2758
+ const propertiesValue = schema.properties ?? {};
2759
+ if (!propertiesValue || typeof propertiesValue !== "object" || Array.isArray(propertiesValue)) {
2760
+ throw new Error(`${context}.properties must be an object when provided`);
2761
+ }
2762
+ const properties = propertiesValue;
2763
+ const requiredValue = schema.required ?? [];
2764
+ if (!Array.isArray(requiredValue)) {
2765
+ throw new Error(`${context}.required must be an array when provided`);
2766
+ }
2767
+ const requiredKeys = new Set(requiredValue.filter((entry) => typeof entry === "string"));
2768
+ const shape = {};
2769
+ for (const [key, childSchemaValue] of Object.entries(properties)) {
2770
+ const childContext = `${context}.properties.${key}`;
2771
+ const childSchema = buildZodSchemaFromJsonSchema(childSchemaValue, childContext, depth + 1);
2772
+ shape[key] = requiredKeys.has(key) ? childSchema : childSchema.optional();
2773
+ }
2774
+ const additionalProperties = schema.additionalProperties;
2775
+ if (additionalProperties === true) {
2776
+ return z4.object(shape).passthrough();
2777
+ }
2778
+ return z4.object(shape).strict();
2779
+ }
2780
+ function buildArraySchema(schema, context, depth) {
2781
+ if (!("items" in schema)) {
2782
+ throw new Error(`${context}.items is required for array schemas`);
2783
+ }
2784
+ const itemSchema = buildZodSchemaFromJsonSchema(schema.items, `${context}.items`, depth + 1);
2785
+ let resolved = z4.array(itemSchema);
2786
+ if (typeof schema.minItems === "number") {
2787
+ resolved = resolved.min(schema.minItems);
2788
+ }
2789
+ if (typeof schema.maxItems === "number") {
2790
+ resolved = resolved.max(schema.maxItems);
2791
+ }
2792
+ return resolved;
2793
+ }
2794
+ function buildZodSchemaFromJsonSchema(schemaValue, context, depth) {
2795
+ if (depth > JSON_SCHEMA_MAX_DEPTH) {
2796
+ throw new Error(`${context} exceeded maximum schema depth (${JSON_SCHEMA_MAX_DEPTH})`);
2797
+ }
2798
+ const schema = ensureJsonSchemaObject(schemaValue, context);
2799
+ if ("const" in schema) {
2800
+ if (!isJsonLiteral(schema.const)) {
2801
+ throw new Error(`${context}.const must be a JSON literal value`);
2802
+ }
2803
+ return z4.literal(schema.const);
2804
+ }
2805
+ if (Array.isArray(schema.enum)) {
2806
+ return buildEnumSchema(schema.enum, context);
2807
+ }
2808
+ const schemaType = parseJsonSchemaType(schema, context);
2809
+ if (schemaType === "object") {
2810
+ return buildObjectSchema(schema, context, depth);
2811
+ }
2812
+ if (schemaType === "array") {
2813
+ return buildArraySchema(schema, context, depth);
2814
+ }
2815
+ if (schemaType === "string") {
2816
+ return buildStringSchema(schema, context);
2817
+ }
2818
+ if (schemaType === "number") {
2819
+ return buildNumberSchema(schema, context, false);
2820
+ }
2821
+ if (schemaType === "integer") {
2822
+ return buildNumberSchema(schema, context, true);
2823
+ }
2824
+ if (schemaType === "boolean") {
2825
+ return z4.boolean();
2826
+ }
2827
+ throw new Error(`${context}.type "${schemaType}" is not supported`);
2828
+ }
2829
+ function parsePackToolJsonSchema(schemaValue, toolName, schemaField) {
2830
+ const context = `${schemaField} for tool "${toolName}"`;
2831
+ try {
2832
+ return buildZodSchemaFromJsonSchema(schemaValue, context, 0);
2833
+ } catch (error) {
2834
+ const message = error instanceof Error ? error.message : "unknown schema parsing error";
2835
+ throw new Error(`Invalid ${context}: ${message}`, {
2836
+ cause: error
2837
+ });
2838
+ }
2839
+ }
2840
+ async function defaultRuntimeToolCapabilityResolver(input) {
2841
+ const resolvedEntry = resolvePackToolEntryPath(input.loadedPack.packRoot, input.tool.entry);
2842
+ const resolvedInputSchema = input.tool.input_schema ? parsePackToolJsonSchema(input.tool.input_schema, input.tool.name, "input_schema") : DEFAULT_PACK_TOOL_INPUT_SCHEMA;
2843
+ const resolvedOutputSchema = input.tool.output_schema ? parsePackToolJsonSchema(input.tool.output_schema, input.tool.name, "output_schema") : DEFAULT_PACK_TOOL_OUTPUT_SCHEMA;
2844
+ return {
2845
+ name: input.tool.name,
2846
+ domain: input.loadedPack.manifest.id,
2847
+ version: input.loadedPack.manifest.version,
2848
+ input_schema: resolvedInputSchema,
2849
+ output_schema: resolvedOutputSchema,
2850
+ permission: input.tool.permission,
2851
+ required_scopes: input.tool.required_scopes,
2852
+ handler: {
2853
+ kind: TOOL_HANDLER_KINDS.SUBPROCESS,
2854
+ entry: resolvedEntry
2855
+ },
2856
+ description: buildPackToolDescription(input.tool.name, input.loadedPack.manifest.id),
2857
+ pack: input.loadedPack.pin.id
2858
+ };
2859
+ }
2860
+ async function fileExists(targetPath) {
2861
+ try {
2862
+ await access(targetPath);
2863
+ return true;
2864
+ } catch (error) {
2865
+ const nodeError = error;
2866
+ if (nodeError.code === "ENOENT") {
2867
+ return false;
2868
+ }
2869
+ throw error;
2870
+ }
2871
+ }
2872
+ async function resolvePacksRoot(options) {
2873
+ if (options.packsRoot) {
2874
+ return path4.resolve(options.packsRoot);
2875
+ }
2876
+ const workspaceRoot = path4.resolve(options.workspaceRoot);
2877
+ for (const candidate of DEFAULT_PACKS_ROOT_CANDIDATES) {
2878
+ const absoluteCandidate = path4.resolve(workspaceRoot, candidate);
2879
+ if (await fileExists(absoluteCandidate)) {
2880
+ return absoluteCandidate;
2881
+ }
2882
+ }
2883
+ if (await fileExists(CLI_PACKS_ROOT_CANDIDATE)) {
2884
+ return CLI_PACKS_ROOT_CANDIDATE;
2885
+ }
2886
+ const fallbackCandidate = DEFAULT_PACKS_ROOT_CANDIDATES[0];
2887
+ if (!fallbackCandidate) {
2888
+ throw new Error("No default packs root candidates are configured.");
2889
+ }
2890
+ return path4.resolve(workspaceRoot, fallbackCandidate);
2891
+ }
2892
+ async function resolveAvailablePackManifests(packsRoot) {
2893
+ let entries;
2894
+ try {
2895
+ entries = await readdir2(packsRoot, { withFileTypes: true });
2896
+ } catch (error) {
2897
+ const nodeError = error;
2898
+ if (nodeError.code === "ENOENT") {
2899
+ return [];
2900
+ }
2901
+ throw error;
2902
+ }
2903
+ const manifests = /* @__PURE__ */ new Map();
2904
+ for (const entry of entries) {
2905
+ if (!entry.isDirectory()) {
2906
+ continue;
2907
+ }
2908
+ const manifestPath = path4.join(packsRoot, entry.name, PACK_MANIFEST_FILE_NAME);
2909
+ if (!await fileExists(manifestPath)) {
2910
+ continue;
2911
+ }
2912
+ try {
2913
+ const rawManifest = await readFile4(manifestPath, UTF8_ENCODING);
2914
+ const parsedManifest = YAML.parse(rawManifest);
2915
+ if (!parsedManifest || typeof parsedManifest !== "object") {
2916
+ continue;
2917
+ }
2918
+ const id = parsedManifest.id;
2919
+ const version = parsedManifest.version;
2920
+ if (typeof id === "string" && id.length > 0 && typeof version === "string" && version.length > 0) {
2921
+ manifests.set(id, { id, version });
2922
+ }
2923
+ } catch {
2924
+ continue;
2925
+ }
2926
+ }
2927
+ return [...manifests.values()];
2928
+ }
2929
+ function resolveTaskSpecPath(taskSpecRoot, taskId) {
2930
+ return path4.join(taskSpecRoot, `${taskId}.yaml`);
2931
+ }
2932
+ async function readTaskSpecFromDisk(taskSpecRoot, taskId) {
2933
+ const taskSpecPath = resolveTaskSpecPath(taskSpecRoot, taskId);
2934
+ try {
2935
+ const yamlText = await readFile4(taskSpecPath, UTF8_ENCODING);
2936
+ return TaskSpecSchema.parse(YAML.parse(yamlText));
2937
+ } catch (error) {
2938
+ const nodeError = error;
2939
+ if (nodeError.code === "ENOENT") {
2940
+ return null;
2941
+ }
2942
+ throw error;
2943
+ }
2944
+ }
2945
+ async function writeTaskSpecImmutable(taskSpecRoot, task) {
2946
+ await mkdir3(taskSpecRoot, { recursive: true });
2947
+ const taskSpecPath = resolveTaskSpecPath(taskSpecRoot, task.id);
2948
+ const fileHandle = await open3(taskSpecPath, "wx");
2949
+ try {
2950
+ await fileHandle.writeFile(YAML.stringify(task), UTF8_ENCODING);
2951
+ } catch (writeError) {
2952
+ await fileHandle.close();
2953
+ await rm3(taskSpecPath, { force: true });
2954
+ throw writeError;
2955
+ }
2956
+ await fileHandle.close();
2957
+ return taskSpecPath;
2958
+ }
2959
+ async function resolveWorkspaceSpec(options) {
2960
+ const workspaceRoot = path4.resolve(options.workspaceRoot);
2961
+ const workspaceFilePath = path4.resolve(options.workspaceFilePath ?? path4.join(workspaceRoot, options.workspaceFileName ?? WORKSPACE_FILE_NAME));
2962
+ const raw = await readFile4(workspaceFilePath, UTF8_ENCODING);
2963
+ const rawWorkspaceData = YAML.parse(raw);
2964
+ const workspaceSpec = WorkspaceSpecSchema.parse(rawWorkspaceData);
2965
+ return {
2966
+ workspace_file_path: workspaceFilePath,
2967
+ workspace_spec: workspaceSpec,
2968
+ workspace_config_hash: canonical_json(raw),
2969
+ raw_workspace_data: rawWorkspaceData
2970
+ };
2971
+ }
2972
+ function buildDefaultPolicyLayers(loadedPacks) {
2973
+ const packRules = loadedPacks.flatMap((loadedPack) => {
2974
+ return loadedPack.manifest.policies.map((policy) => ({
2975
+ id: policy.id,
2976
+ trigger: policy.trigger,
2977
+ decision: policy.decision,
2978
+ reason: policy.reason
2979
+ }));
2980
+ });
2981
+ return [
2982
+ {
2983
+ level: "workspace",
2984
+ default_decision: "allow",
2985
+ allow_loosening: true,
2986
+ rules: []
2987
+ },
2988
+ {
2989
+ level: "lane",
2990
+ rules: []
2991
+ },
2992
+ {
2993
+ level: "pack",
2994
+ rules: packRules
2995
+ },
2996
+ {
2997
+ level: "task",
2998
+ rules: []
2999
+ }
3000
+ ];
3001
+ }
3002
+ function createRuntimePolicyHook(policyEngine) {
3003
+ const builtinPolicyHook = createBuiltinPolicyHook();
3004
+ return async (input) => {
3005
+ const builtinDecisions = await builtinPolicyHook(input);
3006
+ if (builtinDecisions.some((decision) => decision.decision === "deny")) {
3007
+ return builtinDecisions;
3008
+ }
3009
+ const context = {
3010
+ trigger: POLICY_TRIGGERS.ON_TOOL_REQUEST,
3011
+ run_id: input.context.run_id,
3012
+ task_id: input.context.task_id,
3013
+ tool_name: input.capability.name,
3014
+ pack_id: input.capability.pack,
3015
+ tool_arguments: input.tool_arguments
3016
+ };
3017
+ const evaluation = await policyEngine.evaluate(context);
3018
+ return [...builtinDecisions, ...toPolicyHookDecisions(evaluation)];
3019
+ };
3020
+ }
3021
+ function resolveReplayTaskFilter(taskId) {
3022
+ return {
3023
+ taskId
3024
+ };
3025
+ }
3026
+ function resolveExecutionMetadata(context) {
3027
+ if (!context.metadata || typeof context.metadata !== "object") {
3028
+ return {};
3029
+ }
3030
+ return context.metadata;
3031
+ }
3032
+ var DefaultKernelRuntime = class {
3033
+ workspaceSpec;
3034
+ workspaceFilePath;
3035
+ workspaceConfigHash;
3036
+ loadedPacks;
3037
+ taskSpecRoot;
3038
+ eventStore;
3039
+ evidenceStore;
3040
+ toolHost;
3041
+ policyEngine;
3042
+ stateAliases;
3043
+ now;
3044
+ runIdFactory;
3045
+ pendingApprovals = /* @__PURE__ */ new Map();
3046
+ constructor(options) {
3047
+ this.workspaceSpec = options.workspace_spec;
3048
+ this.workspaceFilePath = options.workspace_file_path;
3049
+ this.workspaceConfigHash = options.workspace_config_hash;
3050
+ this.loadedPacks = options.loaded_packs;
3051
+ this.taskSpecRoot = options.task_spec_root;
3052
+ this.eventStore = options.event_store;
3053
+ this.evidenceStore = options.evidence_store;
3054
+ this.toolHost = options.tool_host;
3055
+ this.policyEngine = options.policy_engine;
3056
+ this.stateAliases = options.state_aliases ?? {};
3057
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
3058
+ this.runIdFactory = options.run_id_factory ?? defaultRunIdFactory;
3059
+ }
3060
+ getToolHost() {
3061
+ return this.toolHost;
3062
+ }
3063
+ getPolicyEngine() {
3064
+ return this.policyEngine;
3065
+ }
3066
+ async executeTool(name, input, ctx) {
3067
+ const context = ExecutionContextSchema.parse(ctx);
3068
+ const metadata = resolveExecutionMetadata(context);
3069
+ const expectedHash = this.workspaceConfigHash;
3070
+ let actualHash;
3071
+ let missingWorkspaceFile = false;
3072
+ try {
3073
+ actualHash = await this.computeWorkspaceConfigHash();
3074
+ } catch (error) {
3075
+ const nodeError = error;
3076
+ if (nodeError.code !== "ENOENT") {
3077
+ throw error;
3078
+ }
3079
+ missingWorkspaceFile = true;
3080
+ actualHash = canonical_json({
3081
+ [WORKSPACE_CONFIG_HASH_CONTEXT_KEYS.WORKSPACE_FILE_MISSING]: this.workspaceFilePath
3082
+ });
3083
+ }
3084
+ if (actualHash !== expectedHash) {
3085
+ const tamperedEvent = {
3086
+ schema_version: 1,
3087
+ kind: KERNEL_EVENT_KINDS.SPEC_TAMPERED,
3088
+ spec: "workspace",
3089
+ id: this.workspaceSpec.id,
3090
+ expected_hash: expectedHash,
3091
+ actual_hash: actualHash,
3092
+ timestamp: normalizeTimestamp(this.now)
3093
+ };
3094
+ await this.eventStore.append(tamperedEvent);
3095
+ return {
3096
+ success: false,
3097
+ error: {
3098
+ code: SPEC_TAMPERED_ERROR_CODE,
3099
+ message: missingWorkspaceFile ? SPEC_TAMPERED_WORKSPACE_MISSING_MESSAGE : SPEC_TAMPERED_WORKSPACE_MESSAGE,
3100
+ details: {
3101
+ workspace_id: this.workspaceSpec.id,
3102
+ workspace_file_path: this.workspaceFilePath,
3103
+ [WORKSPACE_CONFIG_HASH_CONTEXT_KEYS.WORKSPACE_FILE_MISSING]: missingWorkspaceFile,
3104
+ expected_hash: expectedHash,
3105
+ actual_hash: actualHash
3106
+ }
3107
+ }
3108
+ };
3109
+ }
3110
+ const runtimeContext = ExecutionContextSchema.parse({
3111
+ ...context,
3112
+ metadata: {
3113
+ ...metadata,
3114
+ [EXECUTION_METADATA_KEYS.WORKSPACE_CONFIG_HASH]: actualHash
3115
+ }
3116
+ });
3117
+ const output = await this.toolHost.execute(name, input, runtimeContext);
3118
+ if (output.error?.code === TOOL_ERROR_CODES.APPROVAL_REQUIRED) {
3119
+ const details = output.error.details;
3120
+ const requestId = typeof details?.request_id === "string" ? details.request_id : void 0;
3121
+ if (requestId) {
3122
+ this.pendingApprovals.set(requestId, {
3123
+ task_id: context.task_id,
3124
+ run_id: context.run_id,
3125
+ tool_name: name,
3126
+ requested_at: normalizeTimestamp(this.now)
3127
+ });
3128
+ const waitingEvent = {
3129
+ schema_version: 1,
3130
+ kind: KERNEL_EVENT_KINDS.TASK_WAITING,
3131
+ task_id: context.task_id,
3132
+ timestamp: normalizeTimestamp(this.now),
3133
+ reason: `Approval required for tool "${name}"`,
3134
+ wait_for: requestId
3135
+ };
3136
+ await this.eventStore.append(waitingEvent);
3137
+ }
3138
+ }
3139
+ return output;
3140
+ }
3141
+ async createTask(taskSpec) {
3142
+ const parsedTask = TaskSpecSchema.parse(taskSpec);
3143
+ if (parsedTask.workspace_id !== this.workspaceSpec.id) {
3144
+ throw new Error(`Task workspace mismatch: expected ${this.workspaceSpec.id}, got ${parsedTask.workspace_id}`);
3145
+ }
3146
+ const laneExists = this.workspaceSpec.lanes.some((lane) => lane.id === parsedTask.lane_id);
3147
+ if (!laneExists) {
3148
+ throw new Error(`Task lane "${parsedTask.lane_id}" is not declared in workspace lanes.`);
3149
+ }
3150
+ let taskSpecPath;
3151
+ try {
3152
+ taskSpecPath = await writeTaskSpecImmutable(this.taskSpecRoot, parsedTask);
3153
+ } catch (error) {
3154
+ const nodeError = error;
3155
+ if (nodeError.code === "EEXIST") {
3156
+ throw new Error(`Task spec already exists for ${parsedTask.id} and is immutable.`, {
3157
+ cause: error
3158
+ });
3159
+ }
3160
+ throw error;
3161
+ }
3162
+ const createdEvent = {
3163
+ schema_version: 1,
3164
+ kind: KERNEL_EVENT_KINDS.TASK_CREATED,
3165
+ task_id: parsedTask.id,
3166
+ timestamp: normalizeTimestamp(this.now),
3167
+ spec_hash: canonical_json(parsedTask)
3168
+ };
3169
+ try {
3170
+ await this.eventStore.append(createdEvent);
3171
+ } catch (eventError) {
3172
+ await rm3(taskSpecPath, { force: true });
3173
+ throw eventError;
3174
+ }
3175
+ return {
3176
+ task: parsedTask,
3177
+ task_spec_path: taskSpecPath,
3178
+ event: createdEvent
3179
+ };
3180
+ }
3181
+ async claimTask(input) {
3182
+ const task = await this.requireTaskSpec(input.task_id);
3183
+ const projected = await this.projectTaskState(task.id);
3184
+ assertTransition(projected.status, "active", task.id, this.stateAliases);
3185
+ const runId = this.runIdFactory(task.id, projected.run_count + 1);
3186
+ const policy = await this.policyEngine.evaluate({
3187
+ trigger: POLICY_TRIGGERS.ON_CLAIM,
3188
+ run_id: runId,
3189
+ task_id: task.id,
3190
+ lane_id: task.lane_id,
3191
+ pack_id: task.domain
3192
+ });
3193
+ if (policy.decision === "deny") {
3194
+ throw new Error(`Policy denied claim for ${task.id}.`);
3195
+ }
3196
+ const claimedTimestamp = normalizeTimestamp(this.now, input.timestamp);
3197
+ const claimedEvent = {
3198
+ schema_version: 1,
3199
+ kind: KERNEL_EVENT_KINDS.TASK_CLAIMED,
3200
+ task_id: task.id,
3201
+ timestamp: claimedTimestamp,
3202
+ by: input.by,
3203
+ session_id: input.session_id,
3204
+ domain_data: input.domain_data
3205
+ };
3206
+ const runStartedEvent = {
3207
+ schema_version: 1,
3208
+ kind: KERNEL_EVENT_KINDS.RUN_STARTED,
3209
+ task_id: task.id,
3210
+ run_id: runId,
3211
+ timestamp: claimedTimestamp,
3212
+ by: input.by,
3213
+ session_id: input.session_id
3214
+ };
3215
+ await this.eventStore.appendAll([claimedEvent, runStartedEvent]);
3216
+ return {
3217
+ task_id: task.id,
3218
+ run: RunSchema.parse({
3219
+ run_id: runId,
3220
+ task_id: task.id,
3221
+ status: RUN_STATUSES.EXECUTING,
3222
+ started_at: runStartedEvent.timestamp,
3223
+ by: input.by,
3224
+ session_id: input.session_id
3225
+ }),
3226
+ events: [claimedEvent, runStartedEvent],
3227
+ policy
3228
+ };
3229
+ }
3230
+ async blockTask(input) {
3231
+ const task = await this.requireTaskSpec(input.task_id);
3232
+ const projected = await this.projectTaskState(task.id);
3233
+ assertTransition(projected.status, "blocked", task.id, this.stateAliases);
3234
+ const reason = input.reason.trim();
3235
+ if (reason.length === 0) {
3236
+ throw new Error(`Cannot block ${task.id}: reason is required.`);
3237
+ }
3238
+ const blockedEvent = {
3239
+ schema_version: 1,
3240
+ kind: KERNEL_EVENT_KINDS.TASK_BLOCKED,
3241
+ task_id: task.id,
3242
+ timestamp: normalizeTimestamp(this.now, input.timestamp),
3243
+ reason
3244
+ };
3245
+ await this.eventStore.append(blockedEvent);
3246
+ return {
3247
+ task_id: task.id,
3248
+ event: blockedEvent
3249
+ };
3250
+ }
3251
+ async unblockTask(input) {
3252
+ const task = await this.requireTaskSpec(input.task_id);
3253
+ const projected = await this.projectTaskState(task.id);
3254
+ assertTransition(projected.status, "active", task.id, this.stateAliases);
3255
+ const unblockedEvent = {
3256
+ schema_version: 1,
3257
+ kind: KERNEL_EVENT_KINDS.TASK_UNBLOCKED,
3258
+ task_id: task.id,
3259
+ timestamp: normalizeTimestamp(this.now, input.timestamp)
3260
+ };
3261
+ await this.eventStore.append(unblockedEvent);
3262
+ return {
3263
+ task_id: task.id,
3264
+ event: unblockedEvent
3265
+ };
3266
+ }
3267
+ async completeTask(input) {
3268
+ const task = await this.requireTaskSpec(input.task_id);
3269
+ const projected = await this.projectTaskState(task.id);
3270
+ assertTransition(projected.status, "done", task.id, this.stateAliases);
3271
+ const runId = input.run_id ?? projected.current_run?.run_id;
3272
+ if (!runId) {
3273
+ throw new Error(`Cannot complete ${task.id}: no active run found.`);
3274
+ }
3275
+ const policy = await this.policyEngine.evaluate({
3276
+ trigger: POLICY_TRIGGERS.ON_COMPLETION,
3277
+ run_id: runId,
3278
+ task_id: task.id,
3279
+ lane_id: task.lane_id,
3280
+ pack_id: task.domain
3281
+ });
3282
+ if (policy.decision === "deny") {
3283
+ throw new Error(`Policy denied completion for ${task.id}.`);
3284
+ }
3285
+ const completedTimestamp = normalizeTimestamp(this.now, input.timestamp);
3286
+ const runSucceededEvent = {
3287
+ schema_version: 1,
3288
+ kind: KERNEL_EVENT_KINDS.RUN_SUCCEEDED,
3289
+ task_id: task.id,
3290
+ run_id: runId,
3291
+ timestamp: completedTimestamp,
3292
+ evidence_refs: input.evidence_refs
3293
+ };
3294
+ const taskCompletedEvent = {
3295
+ schema_version: 1,
3296
+ kind: KERNEL_EVENT_KINDS.TASK_COMPLETED,
3297
+ task_id: task.id,
3298
+ timestamp: completedTimestamp,
3299
+ evidence_refs: input.evidence_refs
3300
+ };
3301
+ await this.eventStore.appendAll([runSucceededEvent, taskCompletedEvent]);
3302
+ await this.evidenceStore.pruneTask(task.id);
3303
+ return {
3304
+ task_id: task.id,
3305
+ run_id: runId,
3306
+ events: [runSucceededEvent, taskCompletedEvent],
3307
+ policy
3308
+ };
3309
+ }
3310
+ async inspectTask(taskId) {
3311
+ const task = await this.requireTaskSpec(taskId);
3312
+ const replayResult = await this.eventStore.replay(resolveReplayTaskFilter(taskId));
3313
+ const events = replayResult.events;
3314
+ const state = projectTaskState(task, events);
3315
+ const runHistory = buildRunHistory(events);
3316
+ const receipts = await this.readReceiptsForTask(taskId);
3317
+ const receiptDecisions = receipts.flatMap((receipt) => {
3318
+ if (receipt.kind === TOOL_TRACE_KINDS.TOOL_CALL_FINISHED) {
3319
+ return receipt.policy_decisions;
3320
+ }
3321
+ return [];
3322
+ });
3323
+ const completionDecisions = state.status === "done" ? await this.evaluateCompletionPolicy(task, state) : [];
3324
+ return {
3325
+ task_id: taskId,
3326
+ task,
3327
+ state,
3328
+ run_history: runHistory,
3329
+ receipts,
3330
+ policy_decisions: dedupePolicyDecisions([...receiptDecisions, ...completionDecisions]),
3331
+ events
3332
+ };
3333
+ }
3334
+ async resolveApproval(input) {
3335
+ const pending = this.pendingApprovals.get(input.request_id);
3336
+ if (!pending) {
3337
+ throw new Error(`No pending approval found for request_id "${input.request_id}"`);
3338
+ }
3339
+ this.pendingApprovals.delete(input.request_id);
3340
+ const resumedEvent = {
3341
+ schema_version: 1,
3342
+ kind: KERNEL_EVENT_KINDS.TASK_RESUMED,
3343
+ task_id: pending.task_id,
3344
+ timestamp: normalizeTimestamp(this.now)
3345
+ };
3346
+ await this.eventStore.append(resumedEvent);
3347
+ return {
3348
+ request_id: input.request_id,
3349
+ approved: input.approved,
3350
+ task_id: pending.task_id,
3351
+ run_id: pending.run_id
3352
+ };
3353
+ }
3354
+ async requireTaskSpec(taskId) {
3355
+ const loaded = await readTaskSpecFromDisk(this.taskSpecRoot, taskId);
3356
+ if (!loaded) {
3357
+ throw new Error(`Task spec not found for ${taskId}`);
3358
+ }
3359
+ return loaded;
3360
+ }
3361
+ async projectTaskState(taskId) {
3362
+ const task = await this.requireTaskSpec(taskId);
3363
+ const { events } = await this.eventStore.replay(resolveReplayTaskFilter(taskId));
3364
+ return projectTaskState(task, events);
3365
+ }
3366
+ async evaluateCompletionPolicy(task, state) {
3367
+ const runId = state.current_run?.run_id;
3368
+ if (!runId) {
3369
+ return [];
3370
+ }
3371
+ const evaluation = await this.policyEngine.evaluate({
3372
+ trigger: POLICY_TRIGGERS.ON_COMPLETION,
3373
+ run_id: runId,
3374
+ task_id: task.id,
3375
+ lane_id: task.lane_id,
3376
+ pack_id: task.domain
3377
+ });
3378
+ return evaluation.decisions;
3379
+ }
3380
+ async computeWorkspaceConfigHash() {
3381
+ const raw = await readFile4(this.workspaceFilePath, UTF8_ENCODING);
3382
+ return canonical_json(raw);
3383
+ }
3384
+ async readReceiptsForTask(taskId) {
3385
+ const indexed = await this.evidenceStore.readTracesByTaskId(taskId);
3386
+ if (indexed.length > 0) {
3387
+ return indexed;
3388
+ }
3389
+ const traces = await this.evidenceStore.readTraces();
3390
+ const receiptIds = /* @__PURE__ */ new Set();
3391
+ for (const trace of traces) {
3392
+ if (trace.kind !== TOOL_TRACE_KINDS.TOOL_CALL_STARTED) {
3393
+ continue;
3394
+ }
3395
+ if (trace.task_id === taskId) {
3396
+ receiptIds.add(trace.receipt_id);
3397
+ }
3398
+ }
3399
+ return traces.filter((trace) => {
3400
+ if (trace.kind === TOOL_TRACE_KINDS.TOOL_CALL_STARTED) {
3401
+ return trace.task_id === taskId;
3402
+ }
3403
+ return receiptIds.has(trace.receipt_id);
3404
+ });
3405
+ }
3406
+ };
3407
+ async function initializeKernelRuntime(options) {
3408
+ const workspaceRoot = path4.resolve(options.workspaceRoot);
3409
+ const resolvedWorkspace = await resolveWorkspaceSpec(options);
3410
+ const workspaceSpec = resolvedWorkspace.workspace_spec;
3411
+ const packsRoot = await resolvePacksRoot(options);
3412
+ const availableManifests = await resolveAvailablePackManifests(packsRoot);
3413
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
3414
+ const taskSpecRoot = path4.resolve(options.taskSpecRoot ?? path4.join(workspaceRoot, DEFAULT_RUNTIME_ROOT, KERNEL_RUNTIME_TASKS_DIR_NAME));
3415
+ const eventsFilePath = path4.resolve(options.eventsFilePath ?? path4.join(workspaceRoot, DEFAULT_RUNTIME_ROOT, KERNEL_RUNTIME_EVENTS_DIR_NAME, KERNEL_RUNTIME_EVENTS_FILE_NAME));
3416
+ const eventLockFilePath = path4.resolve(options.eventLockFilePath ?? path4.join(workspaceRoot, DEFAULT_RUNTIME_ROOT, KERNEL_RUNTIME_EVENTS_DIR_NAME, KERNEL_RUNTIME_EVENTS_LOCK_FILE_NAME));
3417
+ const evidenceRoot = path4.resolve(options.evidenceRoot ?? path4.join(workspaceRoot, DEFAULT_RUNTIME_ROOT, KERNEL_RUNTIME_EVIDENCE_DIR_NAME));
3418
+ const packLoader = new PackLoader({ packsRoot });
3419
+ const loadedPacks = [];
3420
+ for (const pin of workspaceSpec.packs) {
3421
+ let loadedPack;
3422
+ try {
3423
+ loadedPack = await packLoader.load({
3424
+ workspaceSpec,
3425
+ packId: pin.id
3426
+ });
3427
+ } catch (error) {
3428
+ throw new Error(formatRuntimeLoadStageError(pin.id), { cause: error });
3429
+ }
3430
+ loadedPacks.push(loadedPack);
3431
+ }
3432
+ const rootKeyValidation = validateWorkspaceRootKeys(resolvedWorkspace.raw_workspace_data, loadedPacks.map((lp) => lp.manifest), availableManifests);
3433
+ if (!rootKeyValidation.valid) {
3434
+ const keyList = rootKeyValidation.errors.join("\n - ");
3435
+ throw new Error(`Workspace root-key validation failed:
3436
+ - ${keyList}`);
3437
+ }
3438
+ const registry = new ToolRegistry();
3439
+ if (options.includeBuiltinTools !== false) {
3440
+ registerBuiltinToolCapabilities(registry, {
3441
+ declaredScopes: workspaceSpec.security.allowed_scopes
3442
+ });
3443
+ }
3444
+ const toolCapabilityResolver = options.toolCapabilityResolver ?? defaultRuntimeToolCapabilityResolver;
3445
+ for (const loadedPack of loadedPacks) {
3446
+ for (const tool of loadedPack.manifest.tools) {
3447
+ let capability;
3448
+ try {
3449
+ capability = await toolCapabilityResolver({
3450
+ workspaceSpec,
3451
+ loadedPack,
3452
+ tool
3453
+ });
3454
+ } catch (error) {
3455
+ throw new Error(formatRuntimeRegistrationStageError(tool.name, loadedPack.pin.id), {
3456
+ cause: error
3457
+ });
3458
+ }
3459
+ if (capability) {
3460
+ registry.register(capability);
3461
+ }
3462
+ }
3463
+ }
3464
+ const policyEngine = new PolicyEngine({
3465
+ layers: options.policyLayers ?? buildDefaultPolicyLayers(loadedPacks)
3466
+ });
3467
+ const evidenceStore = new EvidenceStore({ evidenceRoot });
3468
+ const eventStoreOptions = {
3469
+ eventsFilePath,
3470
+ lockFilePath: eventLockFilePath,
3471
+ taskSpecLoader: async (taskId) => readTaskSpecFromDisk(taskSpecRoot, taskId)
3472
+ };
3473
+ const eventStore = new EventStore(eventStoreOptions);
3474
+ const workspaceUpdatedEvent = {
3475
+ schema_version: 1,
3476
+ kind: KERNEL_EVENT_KINDS.WORKSPACE_UPDATED,
3477
+ timestamp: normalizeTimestamp(now),
3478
+ config_hash: resolvedWorkspace.workspace_config_hash,
3479
+ changes_summary: WORKSPACE_UPDATED_INIT_SUMMARY
3480
+ };
3481
+ await eventStore.append(workspaceUpdatedEvent);
3482
+ const toolHost = new ToolHost({
3483
+ registry,
3484
+ evidenceStore,
3485
+ subprocessDispatcher: options.subprocessDispatcher || new SandboxSubprocessDispatcher({
3486
+ workspaceRoot,
3487
+ ...options.sandboxSubprocessDispatcherOptions
3488
+ }),
3489
+ policyHook: createRuntimePolicyHook(policyEngine),
3490
+ runtimeVersion: options.runtimeVersion
3491
+ });
3492
+ await toolHost.onStartup();
3493
+ return new DefaultKernelRuntime({
3494
+ workspace_spec: workspaceSpec,
3495
+ workspace_file_path: resolvedWorkspace.workspace_file_path,
3496
+ workspace_config_hash: resolvedWorkspace.workspace_config_hash,
3497
+ loaded_packs: loadedPacks,
3498
+ task_spec_root: taskSpecRoot,
3499
+ event_store: eventStore,
3500
+ evidence_store: evidenceStore,
3501
+ tool_host: toolHost,
3502
+ policy_engine: policyEngine,
3503
+ state_aliases: mergeStateAliases(loadedPacks),
3504
+ now,
3505
+ run_id_factory: options.runIdFactory
3506
+ });
3507
+ }
3508
+
3509
+ export {
3510
+ initializeKernelRuntime
3511
+ };