@odla-ai/harness 0.1.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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +178 -0
  3. package/dist/chunk-ATKV6VTU.js +168 -0
  4. package/dist/chunk-ATKV6VTU.js.map +1 -0
  5. package/dist/chunk-GE6CCN7W.js +93 -0
  6. package/dist/chunk-GE6CCN7W.js.map +1 -0
  7. package/dist/chunk-GMVZ4LZH.js +1769 -0
  8. package/dist/chunk-GMVZ4LZH.js.map +1 -0
  9. package/dist/chunk-PHXQH4YM.js +550 -0
  10. package/dist/chunk-PHXQH4YM.js.map +1 -0
  11. package/dist/chunk-PTXZVYD4.js +81 -0
  12. package/dist/chunk-PTXZVYD4.js.map +1 -0
  13. package/dist/chunk-QTUEF2HZ.js +9 -0
  14. package/dist/chunk-QTUEF2HZ.js.map +1 -0
  15. package/dist/cli.cjs +795 -0
  16. package/dist/cli.cjs.map +1 -0
  17. package/dist/cli.d.cts +1 -0
  18. package/dist/cli.d.ts +1 -0
  19. package/dist/cli.js +98 -0
  20. package/dist/cli.js.map +1 -0
  21. package/dist/code-runtime-cli.cjs +2341 -0
  22. package/dist/code-runtime-cli.cjs.map +1 -0
  23. package/dist/code-runtime-cli.d.cts +1 -0
  24. package/dist/code-runtime-cli.d.ts +1 -0
  25. package/dist/code-runtime-cli.js +133 -0
  26. package/dist/code-runtime-cli.js.map +1 -0
  27. package/dist/index.cjs +228 -0
  28. package/dist/index.cjs.map +1 -0
  29. package/dist/index.d.cts +44 -0
  30. package/dist/index.d.ts +44 -0
  31. package/dist/index.js +46 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/node.cjs +2580 -0
  34. package/dist/node.cjs.map +1 -0
  35. package/dist/node.d.cts +544 -0
  36. package/dist/node.d.ts +544 -0
  37. package/dist/node.js +71 -0
  38. package/dist/node.js.map +1 -0
  39. package/dist/testing.cjs +106 -0
  40. package/dist/testing.cjs.map +1 -0
  41. package/dist/testing.d.cts +25 -0
  42. package/dist/testing.d.ts +25 -0
  43. package/dist/testing.js +79 -0
  44. package/dist/testing.js.map +1 -0
  45. package/dist/types-D12vK3K9.d.cts +249 -0
  46. package/dist/types-D12vK3K9.d.ts +249 -0
  47. package/package.json +84 -0
@@ -0,0 +1,1769 @@
1
+ import {
2
+ assertPinnedImage,
3
+ runContainerAttempt,
4
+ stageWorkspace,
5
+ stageWorkspacePair,
6
+ verifyContainerEngineBoundary
7
+ } from "./chunk-PHXQH4YM.js";
8
+ import {
9
+ HARNESS_PROTOCOL_VERSION
10
+ } from "./chunk-QTUEF2HZ.js";
11
+
12
+ // src/workspace-digest.ts
13
+ import { createHash } from "crypto";
14
+ import { readFile, readdir } from "fs/promises";
15
+ import { relative, resolve } from "path";
16
+ async function digestStagedWorkspace(root, limits) {
17
+ const files = [];
18
+ const walk = async (directory) => {
19
+ const entries = await readdir(directory, { withFileTypes: true });
20
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
21
+ if (entry.isSymbolicLink()) throw new TypeError("workspace digest refuses symbolic links");
22
+ const target = resolve(directory, entry.name);
23
+ if (entry.isDirectory()) await walk(target);
24
+ else if (entry.isFile()) {
25
+ files.push({ path: relative(root, target).split("\\").join("/"), target });
26
+ if (files.length > limits.maxFiles) throw new TypeError("workspace digest exceeds its file bound");
27
+ }
28
+ }
29
+ };
30
+ await walk(resolve(root));
31
+ const hash = createHash("sha256");
32
+ let bytes = 0;
33
+ for (const file of files.sort((left, right) => left.path.localeCompare(right.path))) {
34
+ const content = await readFile(file.target);
35
+ bytes += Buffer.byteLength(file.path) + content.byteLength;
36
+ if (bytes > limits.maxBytes) throw new TypeError("workspace digest exceeds its byte bound");
37
+ hash.update(`${Buffer.byteLength(file.path)}:${file.path}:${content.byteLength}:`);
38
+ hash.update(content);
39
+ }
40
+ return `sha256:${hash.digest("hex")}`;
41
+ }
42
+
43
+ // src/code-runtime-client.ts
44
+ import { digestCodeRepositorySnapshot } from "@odla-ai/camel/code";
45
+
46
+ // src/code-runtime.ts
47
+ var CODE_RUNTIME_PROTOCOL_VERSION = 1;
48
+ async function runCodeRuntimeHeartbeatLoop(options) {
49
+ const heartbeatMs = options.heartbeatMs ?? 15e3;
50
+ if (!Number.isSafeInteger(heartbeatMs) || heartbeatMs < 1e3 || heartbeatMs > 3e5) {
51
+ throw new TypeError("heartbeatMs must be an integer from 1000 to 300000");
52
+ }
53
+ let retryMs = 1e3;
54
+ do {
55
+ if (options.signal?.aborted) return;
56
+ try {
57
+ const snapshot = await options.control.heartbeat(options.runtimeVersion, options.capabilities);
58
+ await options.onSnapshot?.(snapshot);
59
+ retryMs = 1e3;
60
+ if (options.once) return;
61
+ await wait(heartbeatMs, options.signal);
62
+ } catch (error) {
63
+ if (options.signal?.aborted) return;
64
+ if (options.once || !retryableControlFailure(error)) throw error;
65
+ await options.onRetry?.(error, retryMs);
66
+ await wait(retryMs, options.signal);
67
+ retryMs = Math.min(retryMs * 2, 3e4);
68
+ }
69
+ } while (!options.signal?.aborted);
70
+ }
71
+ var CodeRuntimeReconciler = class {
72
+ constructor(control, engine) {
73
+ this.control = control;
74
+ this.engine = engine;
75
+ }
76
+ control;
77
+ engine;
78
+ results = /* @__PURE__ */ new Map();
79
+ async reconcile(snapshot) {
80
+ for (const command of snapshot.commands) {
81
+ let completed = this.results.get(command.commandId);
82
+ if (!completed) {
83
+ let result;
84
+ try {
85
+ result = await this.engine.execute(command);
86
+ } catch (error) {
87
+ result = { status: "failed", message: (error instanceof Error ? error.message : String(error)).slice(0, 2e3) };
88
+ }
89
+ completed = { result, notified: false };
90
+ this.results.set(command.commandId, completed);
91
+ if (this.results.size > 1024) this.results.delete(this.results.keys().next().value);
92
+ }
93
+ await this.control.acknowledge(command.commandId, completed.result);
94
+ if (!completed.notified) {
95
+ await this.engine.acknowledged?.(command, completed.result);
96
+ completed.notified = true;
97
+ }
98
+ }
99
+ }
100
+ };
101
+ function retryableControlFailure(value) {
102
+ if (!value || typeof value !== "object") return false;
103
+ const failure = value;
104
+ if (failure.code === "invalid_response" || typeof failure.status !== "number") return false;
105
+ return failure.status === 408 || failure.status === 425 || failure.status === 429 || failure.status >= 500;
106
+ }
107
+ function wait(ms, signal) {
108
+ return new Promise((resolve5) => {
109
+ if (signal?.aborted) return resolve5();
110
+ const timer = setTimeout(resolve5, ms);
111
+ signal?.addEventListener("abort", () => {
112
+ clearTimeout(timer);
113
+ resolve5();
114
+ }, { once: true });
115
+ });
116
+ }
117
+
118
+ // src/code-runtime-client.ts
119
+ var CodeRuntimeControlError = class extends Error {
120
+ constructor(message2, status, code = "control_error") {
121
+ super(message2);
122
+ this.status = status;
123
+ this.code = code;
124
+ }
125
+ status;
126
+ code;
127
+ name = "CodeRuntimeControlError";
128
+ };
129
+ function createCodeRuntimeControlClient(options) {
130
+ const endpoint = validatedEndpoint(options.endpoint);
131
+ if (!/^odla_code_host_[0-9a-f]{64}$/.test(options.token)) throw new TypeError("invalid Code host credential");
132
+ const requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
133
+ if (!Number.isSafeInteger(requestTimeoutMs) || requestTimeoutMs < 1e3 || requestTimeoutMs > 12e4) {
134
+ throw new TypeError("requestTimeoutMs must be an integer from 1000 to 120000");
135
+ }
136
+ const modelRequestTimeoutMs = options.modelRequestTimeoutMs ?? 15 * 6e4;
137
+ if (!Number.isSafeInteger(modelRequestTimeoutMs) || modelRequestTimeoutMs < 3e4 || modelRequestTimeoutMs > 30 * 6e4) {
138
+ throw new TypeError("modelRequestTimeoutMs must be an integer from 30000 to 1800000");
139
+ }
140
+ const request = options.fetch ?? fetch;
141
+ const call = async (path, body, timeoutMs = requestTimeoutMs) => {
142
+ const timeout = AbortSignal.timeout(timeoutMs);
143
+ const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
144
+ let response2;
145
+ try {
146
+ response2 = await request(`${endpoint}${path}`, {
147
+ method: "POST",
148
+ headers: { authorization: `Bearer ${options.token}`, "content-type": "application/json" },
149
+ body: JSON.stringify(body),
150
+ redirect: "error",
151
+ signal
152
+ });
153
+ } catch (cause) {
154
+ if (options.signal?.aborted) throw cause;
155
+ throw new CodeRuntimeControlError("Code runtime control plane is unavailable", 503, "transport_unavailable");
156
+ }
157
+ const value = await response2.json().catch(() => null);
158
+ if (!response2.ok) {
159
+ const problem = record(record(value)?.error);
160
+ throw new CodeRuntimeControlError(
161
+ typeof problem?.message === "string" ? problem.message : `Code runtime request failed (${response2.status})`,
162
+ response2.status,
163
+ typeof problem?.code === "string" ? problem.code : void 0
164
+ );
165
+ }
166
+ return value;
167
+ };
168
+ return {
169
+ heartbeat: async (version, capabilities) => {
170
+ validateHeartbeat(version, capabilities);
171
+ return parseSnapshot(await call("/registry/code/runtime/heartbeat", { runtimeVersion: version, capabilities }));
172
+ },
173
+ acknowledge: async (commandId, result) => {
174
+ if (!/^ccmd_[0-9a-f]{32}$/.test(commandId)) throw new TypeError("invalid Code runtime command id");
175
+ await call(`/registry/code/runtime/commands/${commandId}/ack`, result);
176
+ },
177
+ source: async (sessionId) => parseSource(
178
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
179
+ ),
180
+ infer: async (sessionId, inference) => {
181
+ const value = record(await call(
182
+ `/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
183
+ inference,
184
+ modelRequestTimeoutMs
185
+ ));
186
+ if (!value || value.requestId !== inference.requestId || !record(value.response) || !record(value.receipt)) {
187
+ throw new CodeRuntimeControlError("invalid Code inference response", 502, "invalid_response");
188
+ }
189
+ return value;
190
+ },
191
+ review: async (sessionId, review) => parseReview(
192
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review, modelRequestTimeoutMs)
193
+ ),
194
+ submitCandidate: async (sessionId, checkpointId, verification) => {
195
+ if (!/^cpoint_[0-9a-f]{32}$/.test(checkpointId)) throw new TypeError("invalid Code checkpoint id");
196
+ return parseCandidate(await call(
197
+ `/registry/code/runtime/sessions/${validSessionId(sessionId)}/candidates`,
198
+ { checkpointId, verification }
199
+ ));
200
+ },
201
+ appendSessionEvent: async (sessionId, eventId, event) => {
202
+ const serialized = JSON.stringify(event);
203
+ if (!/^[A-Za-z0-9._:-]{1,120}$/.test(eventId) || !event || typeof event !== "object" || new TextEncoder().encode(serialized).byteLength > 24e3) {
204
+ throw new TypeError("invalid Code session event");
205
+ }
206
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
207
+ },
208
+ reportSessionFailure: async (sessionId, message2) => {
209
+ if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
210
+ await call(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
211
+ }
212
+ };
213
+ }
214
+ function validatedEndpoint(value) {
215
+ const endpoint = value.replace(/\/+$/, "");
216
+ let url;
217
+ try {
218
+ url = new URL(endpoint);
219
+ } catch {
220
+ throw new TypeError("endpoint must be an HTTPS URL");
221
+ }
222
+ const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
223
+ if (url.username || url.password || url.protocol !== "https:" && !(loopback && url.protocol === "http:")) {
224
+ throw new TypeError("endpoint must use HTTPS (HTTP is allowed only for loopback testing)");
225
+ }
226
+ return endpoint;
227
+ }
228
+ function validSessionId(value) {
229
+ if (!/^csess_[0-9a-f]{32}$/.test(value)) throw new TypeError("invalid Code session id");
230
+ return value;
231
+ }
232
+ function validateHeartbeat(version, capabilities) {
233
+ if (!version.trim() || version.length > 80) throw new TypeError("runtimeVersion is required and at most 80 characters");
234
+ if (capabilities.protocolVersion !== CODE_RUNTIME_PROTOCOL_VERSION) throw new TypeError("unsupported Code runtime protocol version");
235
+ if (capabilities.platform !== "macos" && capabilities.platform !== "linux") throw new TypeError("invalid runtime platform");
236
+ if (!capabilities.arch || !capabilities.engines.length || !capabilities.engines.every((engine) => ["container", "podman", "docker"].includes(engine))) {
237
+ throw new TypeError("runtime arch and supported engine are required");
238
+ }
239
+ if (!Number.isSafeInteger(capabilities.cpuCount) || capabilities.cpuCount < 1 || !Number.isSafeInteger(capabilities.memoryBytes) || capabilities.memoryBytes < 1) {
240
+ throw new TypeError("runtime resources must be positive integers");
241
+ }
242
+ }
243
+ function parseSnapshot(value) {
244
+ const root = record(value);
245
+ const host = record(root?.host);
246
+ if (!host || typeof host.hostId !== "string" || typeof host.runtimeVersion !== "string" || !Number.isSafeInteger(host.lastSeenAt) || host.revokedAt !== null || !Array.isArray(root?.bindings) || root.bindings.length > 1024 || !Array.isArray(root?.commands) || root.commands.length > 64) throw invalid("heartbeat");
247
+ const bindingIds = /* @__PURE__ */ new Set();
248
+ const bindings = root.bindings.map((item) => {
249
+ const binding = record(item);
250
+ if (!binding || typeof binding.bindingId !== "string" || typeof binding.appId !== "string" || binding.env !== "dev" && binding.env !== "prod" || typeof binding.offerId !== "string" || binding.hostId !== host.hostId || !Number.isSafeInteger(binding.generation) || Number(binding.generation) < 1 || binding.revokedAt !== null || bindingIds.has(binding.bindingId)) {
251
+ throw invalid("binding");
252
+ }
253
+ bindingIds.add(binding.bindingId);
254
+ return binding;
255
+ });
256
+ const commandIds = /* @__PURE__ */ new Set();
257
+ const commandSequences = /* @__PURE__ */ new Set();
258
+ const commands = root.commands.map((item) => {
259
+ const command = record(item);
260
+ const binding = bindings.find((candidate) => candidate.bindingId === command?.bindingId);
261
+ const sequenceKey = `${String(command?.instanceId)}:${String(command?.sequence)}`;
262
+ if (!command || typeof command.commandId !== "string" || !/^ccmd_[0-9a-f]{32}$/.test(command.commandId) || typeof command.instanceId !== "string" || typeof command.sessionId !== "string" || !/^csess_[0-9a-f]{32}$/.test(command.sessionId) || typeof command.appId !== "string" || command.env !== "dev" && command.env !== "prod" || command.hostId !== host.hostId || !binding || binding.appId !== command.appId || binding.generation !== command.bindingGeneration || !Number.isSafeInteger(command.sequence) || Number(command.sequence) < 1 || commandIds.has(command.commandId) || commandSequences.has(sequenceKey) || !["start", "prompt", "checkpoint_stop", "resume"].includes(String(command.kind)) || !record(command.payload) || !Number.isSafeInteger(command.createdAt)) throw invalid("command");
263
+ commandIds.add(command.commandId);
264
+ commandSequences.add(sequenceKey);
265
+ return command;
266
+ });
267
+ return { host, bindings, commands };
268
+ }
269
+ async function parseSource(value) {
270
+ const snapshot = record(record(value)?.snapshot);
271
+ if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
272
+ const files = snapshot.files.map((value2) => {
273
+ const file = record(value2);
274
+ if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("source file");
275
+ return { path: file.path, content: file.content };
276
+ });
277
+ const referencesValue = snapshot.references === void 0 ? [] : snapshot.references;
278
+ if (!Array.isArray(referencesValue) || referencesValue.length > 5) throw invalid("reference sources");
279
+ const aliases = /* @__PURE__ */ new Set();
280
+ const references = [];
281
+ for (const item of referencesValue) {
282
+ const reference = record(item);
283
+ if (!reference || typeof reference.alias !== "string" || !/^[a-z][a-z0-9-]{0,39}$/.test(reference.alias) || aliases.has(reference.alias) || reference.alias === "primary" || typeof reference.repository !== "string" || typeof reference.commitSha !== "string" || typeof reference.treeDigest !== "string" || !Array.isArray(reference.files)) throw invalid("reference source");
284
+ aliases.add(reference.alias);
285
+ const referenceFiles = reference.files.map((entry) => {
286
+ const file = record(entry);
287
+ if (!file || typeof file.path !== "string" || typeof file.content !== "string") throw invalid("reference source file");
288
+ return { path: file.path, content: file.content };
289
+ });
290
+ const source2 = { repository: reference.repository, commitSha: reference.commitSha, files: referenceFiles };
291
+ const referenceDigest = await digestCodeRepositorySnapshot(source2, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
292
+ if (referenceDigest !== reference.treeDigest) throw invalid("reference source digest");
293
+ references.push({ alias: reference.alias, ...source2, treeDigest: referenceDigest });
294
+ }
295
+ const source = { repository: snapshot.repository, commitSha: snapshot.commitSha, files };
296
+ const digest = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
297
+ if (digest !== snapshot.treeDigest) throw invalid("source digest");
298
+ return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
299
+ }
300
+ function parseReview(value) {
301
+ const review = record(record(value)?.review);
302
+ if (!review || !["approved", "rejected"].includes(String(review.verdict)) || typeof review.reviewDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(review.reviewDigest) || typeof review.provider !== "string" || !review.provider || typeof review.model !== "string" || !review.model || !Number.isSafeInteger(review.policyVersion) || Number(review.policyVersion) < 1) throw invalid("review");
303
+ return review;
304
+ }
305
+ function parseCandidate(value) {
306
+ const candidate = record(record(value)?.candidate);
307
+ if (!candidate || typeof candidate.candidateId !== "string" || !/^ccand_[0-9a-f]{32}$/.test(candidate.candidateId) || !["submitted", "approved", "published", "failed"].includes(String(candidate.status))) {
308
+ throw invalid("candidate");
309
+ }
310
+ return { candidateId: candidate.candidateId, status: candidate.status };
311
+ }
312
+ var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
313
+ var invalid = (part) => new CodeRuntimeControlError(`invalid Code runtime ${part} response`, 502, "invalid_response");
314
+
315
+ // src/code-checkpoint.ts
316
+ import {
317
+ createCodePortableCheckpoint,
318
+ verifyCodePortableCheckpoint
319
+ } from "@odla-ai/camel/code";
320
+
321
+ // src/code-patch.ts
322
+ import { spawn } from "child_process";
323
+ import { lstat } from "fs/promises";
324
+ import { resolve as resolve2, sep } from "path";
325
+ var RESERVED = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
326
+ var SECRET = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
327
+ var PATH = /^[A-Za-z0-9_@+.,-]+(?:\/[A-Za-z0-9_@+.,-]+)*$/;
328
+ var FORBIDDEN = /^(?:GIT binary patch|Binary files |rename (?:from|to) |copy (?:from|to) |similarity index |old mode |new mode |deleted file mode 160000|new file mode 160000)/m;
329
+ function validateCodePatch(patch2, maxBytes) {
330
+ if (!patch2 || Buffer.byteLength(patch2) > maxBytes || patch2.includes("\0") || patch2.includes("\r")) {
331
+ throw new TypeError("patch is empty, malformed, or exceeds its byte limit");
332
+ }
333
+ if (FORBIDDEN.test(patch2) || /(?:old|new)(?: file)? mode 120000/.test(patch2)) {
334
+ throw new TypeError("patch uses a forbidden binary, link, mode, rename, or copy operation");
335
+ }
336
+ const paths = [];
337
+ const lines = patch2.split("\n");
338
+ for (let index = 0; index < lines.length; index += 1) {
339
+ const line = lines[index];
340
+ if (!line.startsWith("diff --git ")) continue;
341
+ const match = /^diff --git a\/(\S+) b\/(\S+)$/.exec(line);
342
+ const path = match?.[1];
343
+ if (!path || !match?.[2] || path !== match[2]) throw new TypeError("patch must use one unquoted relative path per diff");
344
+ validateRelativePath(path);
345
+ const header = lines.slice(index + 1).findIndex((candidate) => candidate.startsWith("diff --git "));
346
+ const section = lines.slice(index + 1, header < 0 ? lines.length : index + 1 + header);
347
+ const oldPath = section.find((candidate) => candidate.startsWith("--- "))?.slice(4);
348
+ const newPath = section.find((candidate) => candidate.startsWith("+++ "))?.slice(4);
349
+ if (!validHeaderPath(oldPath, path, "a") || !validHeaderPath(newPath, path, "b")) {
350
+ throw new TypeError("patch file headers do not match the declared path");
351
+ }
352
+ paths.push(path);
353
+ }
354
+ if (!paths.length || new Set(paths).size !== paths.length) throw new TypeError("patch has no diffs or repeats a path");
355
+ return paths;
356
+ }
357
+ function validHeaderPath(value, path, prefix) {
358
+ return value === "/dev/null" || value === `${prefix}/${path}`;
359
+ }
360
+ function validateRelativePath(path) {
361
+ const parts = path.split("/");
362
+ if (!PATH.test(path) || parts.some((part) => part === "." || part === ".." || RESERVED.has(part)) || parts.some((part) => SECRET.test(part))) {
363
+ throw new TypeError("path is outside the allowed staged source tree");
364
+ }
365
+ }
366
+ function resolveCodePath(workspaceDir, path) {
367
+ validateRelativePath(path);
368
+ const root = resolve2(workspaceDir);
369
+ const target = resolve2(root, path);
370
+ if (target !== root && !target.startsWith(`${root}${sep}`)) throw new TypeError("path escapes the staged workspace");
371
+ return target;
372
+ }
373
+ async function applyCodePatch(workspaceDir, patch2, paths) {
374
+ await gitApply(workspaceDir, patch2, true);
375
+ await gitApply(workspaceDir, patch2, false);
376
+ for (const path of paths) {
377
+ try {
378
+ const info = await lstat(resolveCodePath(workspaceDir, path));
379
+ if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
380
+ throw new TypeError("patch created a non-regular workspace entry");
381
+ }
382
+ } catch (reason) {
383
+ if (reason.code !== "ENOENT") throw reason;
384
+ }
385
+ }
386
+ }
387
+ function gitApply(cwd, patch2, check) {
388
+ return new Promise((accept, reject) => {
389
+ const args = ["apply", "--recount", "--whitespace=nowarn", ...check ? ["--check"] : [], "-"];
390
+ const child = spawn("git", args, {
391
+ cwd,
392
+ shell: false,
393
+ stdio: ["pipe", "ignore", "pipe"],
394
+ env: { PATH: process.env.PATH ?? "", GIT_CONFIG_NOSYSTEM: "1", GIT_CONFIG_GLOBAL: "/dev/null" }
395
+ });
396
+ let stderr = "";
397
+ child.stderr.setEncoding("utf8");
398
+ child.stderr.on("data", (text) => {
399
+ if (stderr.length < 4e3) stderr += text.slice(0, 4e3);
400
+ });
401
+ child.once("error", reject);
402
+ child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(`patch did not apply: ${stderr.trim().slice(0, 500)}`)));
403
+ child.stdin.end(patch2);
404
+ });
405
+ }
406
+
407
+ // src/code-checkpoint.ts
408
+ async function createCodeWorkspaceCheckpoint(input) {
409
+ const maximum = input.maximumPatchBytes ?? 256 * 1024;
410
+ if (!Number.isSafeInteger(maximum) || maximum < 1 || maximum > 256 * 1024) {
411
+ throw new TypeError("checkpoint patch bound must be from 1 to 262144 bytes");
412
+ }
413
+ const patch2 = await input.workspace.patch(maximum);
414
+ if (patch2) validateCodePatch(patch2, maximum);
415
+ return createCodePortableCheckpoint({ baseCommitSha: input.baseCommitSha, patch: patch2, state: input.state });
416
+ }
417
+ async function restoreCodeWorkspaceCheckpoint(input) {
418
+ const checkpoint = await verifyCodePortableCheckpoint(input.checkpoint);
419
+ if (checkpoint.baseCommitSha !== input.trustedBaseCommitSha) {
420
+ throw new TypeError("checkpoint trusted base does not match the fetched commit");
421
+ }
422
+ const workspace = await stageWorkspace(input.trustedBaseDir, input.stage);
423
+ try {
424
+ if (checkpoint.patch) {
425
+ const paths = validateCodePatch(checkpoint.patch, 256 * 1024);
426
+ await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths);
427
+ }
428
+ return { workspace, checkpoint };
429
+ } catch (error) {
430
+ await workspace.cleanup();
431
+ throw error;
432
+ }
433
+ }
434
+ function isCheckpointEffectCompleted(checkpoint, effectId, actionDigest) {
435
+ const completed = checkpoint.state.completedEffects.find((effect) => effect.effectId === effectId);
436
+ if (!completed) return false;
437
+ if (completed.actionDigest !== actionDigest) throw new TypeError("completed checkpoint effect id has another action digest");
438
+ return true;
439
+ }
440
+
441
+ // src/recipe-container.ts
442
+ import { spawn as spawn2 } from "child_process";
443
+ import { getgid, getuid } from "process";
444
+ import { randomUUID } from "crypto";
445
+ var ARTIFACT_PATH = /^[A-Za-z0-9_@+.,-]+(?:\/[A-Za-z0-9_@+.,-]+)*$/;
446
+ var PRIVATE_ARTIFACT_PART = /^(?:\.git|\.odla|\.wrangler|\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json)$/i;
447
+ function buildRecipeContainerArgs(engine, workspaceDir, recipe2, name = `odla-recipe-${randomUUID().slice(0, 12)}`) {
448
+ assertCodeBuildRecipe(recipe2);
449
+ if (/[,\r\n]/.test(workspaceDir)) throw new TypeError("workspace path contains unsupported mount characters");
450
+ const uid = typeof getuid === "function" ? getuid() : 1e3;
451
+ const gid = typeof getgid === "function" ? getgid() : 1e3;
452
+ const limits = {
453
+ cpus: recipe2.cpus ?? 1,
454
+ memory: recipe2.memory ?? "1g",
455
+ pids: recipe2.pids ?? 256,
456
+ tmpfs: recipe2.tmpfsBytes ?? 64 * 1024 * 1024
457
+ };
458
+ if (engine === "container") {
459
+ return [
460
+ "run",
461
+ "--rm",
462
+ `--name=${name}`,
463
+ "--network=none",
464
+ "--read-only",
465
+ "--cap-drop=ALL",
466
+ `--memory=${limits.memory}`,
467
+ `--cpus=${limits.cpus}`,
468
+ `--user=${uid}:${gid}`,
469
+ "--tmpfs=/tmp",
470
+ `--mount=type=bind,source=${workspaceDir},target=/workspace`,
471
+ "--workdir=/workspace",
472
+ "--env=CI=1",
473
+ recipe2.image,
474
+ ...recipe2.command
475
+ ];
476
+ }
477
+ return [
478
+ "run",
479
+ "--rm",
480
+ `--name=${name}`,
481
+ "--pull=never",
482
+ "--network=none",
483
+ "--read-only",
484
+ "--cap-drop=ALL",
485
+ "--security-opt=no-new-privileges",
486
+ `--pids-limit=${limits.pids}`,
487
+ `--memory=${limits.memory}`,
488
+ `--cpus=${limits.cpus}`,
489
+ `--user=${uid}:${gid}`,
490
+ `--tmpfs=/tmp:rw,noexec,nosuid,nodev,size=${limits.tmpfs}`,
491
+ `--mount=type=bind,src=${workspaceDir},dst=/workspace`,
492
+ "--workdir=/workspace",
493
+ "--env=CI=1",
494
+ recipe2.image,
495
+ ...recipe2.command
496
+ ];
497
+ }
498
+ function createContainerRecipeExecutor(engine) {
499
+ return {
500
+ async run(input) {
501
+ await verifyContainerEngineBoundary(engine);
502
+ const name = `odla-recipe-${randomUUID().slice(0, 12)}`;
503
+ const args = buildRecipeContainerArgs(engine, input.workspaceDir, input.recipe, name);
504
+ return execute(engine, args, name, input.recipe, input.signal);
505
+ }
506
+ };
507
+ }
508
+ function assertCodeBuildRecipe(recipe2) {
509
+ const memoryBytes = parseMemory(recipe2.memory ?? "1g");
510
+ const tmpfsBytes = recipe2.tmpfsBytes ?? 64 * 1024 * 1024;
511
+ const artifacts = recipe2.expectedArtifacts ?? [];
512
+ assertPinnedImage(recipe2.image);
513
+ if (!/^[A-Za-z0-9._:-]{1,120}$/.test(recipe2.id) || recipe2.command.length < 1 || recipe2.command.length > 64 || recipe2.command.some((part) => !part || part.length > 4096 || /[\0\r\n]/.test(part)) || !Number.isSafeInteger(recipe2.timeoutMs) || recipe2.timeoutMs < 1 || recipe2.timeoutMs > 30 * 6e4 || !Number.isSafeInteger(recipe2.maxOutputBytes) || recipe2.maxOutputBytes < 1 || recipe2.maxOutputBytes > 16 * 1024 * 1024 || !Number.isFinite(recipe2.cpus ?? 1) || (recipe2.cpus ?? 1) < 0.1 || (recipe2.cpus ?? 1) > 32 || memoryBytes < 64 * 1024 * 1024 || memoryBytes > 32 * 1024 * 1024 * 1024 || !Number.isSafeInteger(recipe2.pids ?? 256) || (recipe2.pids ?? 256) < 16 || (recipe2.pids ?? 256) > 4096 || !Number.isSafeInteger(tmpfsBytes) || tmpfsBytes < 1024 * 1024 || tmpfsBytes > 1024 * 1024 * 1024 || artifacts.length > 64 || new Set(artifacts.map((item) => item.id)).size !== artifacts.length || artifacts.some((item) => !/^[A-Za-z0-9._:-]{1,120}$/.test(item.id) || !ARTIFACT_PATH.test(item.path) || item.path.split("/").some((part) => PRIVATE_ARTIFACT_PART.test(part)) || !Number.isSafeInteger(item.maximumBytes) || item.maximumBytes < 1 || item.maximumBytes > 512 * 1024 * 1024)) {
514
+ throw new TypeError("build recipe is malformed or exceeds its control bounds");
515
+ }
516
+ }
517
+ function parseMemory(value) {
518
+ const match = /^([1-9][0-9]{0,4})([kmg])$/.exec(value.toLowerCase());
519
+ if (!match?.[1] || !match[2]) return 0;
520
+ const scale = match[2] === "k" ? 1024 : match[2] === "m" ? 1024 ** 2 : 1024 ** 3;
521
+ return Number(match[1]) * scale;
522
+ }
523
+ function execute(engine, args, name, recipe2, signal) {
524
+ return new Promise((accept, reject) => {
525
+ const started = Date.now();
526
+ const child = spawn2(engine, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
527
+ const stdout = [];
528
+ const stderr = [];
529
+ let bytes = 0;
530
+ let outputLimitExceeded = false;
531
+ let timedOut = false;
532
+ let stopping = false;
533
+ const stop = (reason) => {
534
+ if (stopping) return;
535
+ stopping = true;
536
+ timedOut = reason === "timeout";
537
+ outputLimitExceeded = reason === "output";
538
+ const remove = engine === "container" ? ["delete", "--force", name] : ["rm", "-f", name];
539
+ const killer = spawn2(engine, remove, { shell: false, stdio: "ignore" });
540
+ killer.unref();
541
+ child.kill("SIGTERM");
542
+ };
543
+ const collect = (target) => (chunk) => {
544
+ bytes += chunk.byteLength;
545
+ if (bytes > recipe2.maxOutputBytes) stop("output");
546
+ else target.push(chunk);
547
+ };
548
+ child.stdout.on("data", collect(stdout));
549
+ child.stderr.on("data", collect(stderr));
550
+ const abort = () => stop("abort");
551
+ signal?.addEventListener("abort", abort, { once: true });
552
+ if (signal?.aborted) abort();
553
+ const timer = setTimeout(() => stop("timeout"), recipe2.timeoutMs);
554
+ child.once("error", (error) => {
555
+ clearTimeout(timer);
556
+ signal?.removeEventListener("abort", abort);
557
+ reject(error);
558
+ });
559
+ child.once("exit", (code) => {
560
+ clearTimeout(timer);
561
+ signal?.removeEventListener("abort", abort);
562
+ accept({
563
+ exitCode: code ?? 1,
564
+ stdout: Buffer.concat(stdout).toString("utf8"),
565
+ stderr: Buffer.concat(stderr).toString("utf8"),
566
+ durationMs: Date.now() - started,
567
+ outputLimitExceeded,
568
+ timedOut
569
+ });
570
+ });
571
+ });
572
+ }
573
+
574
+ // src/code-verifier.ts
575
+ import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
576
+ import { createReadStream } from "fs";
577
+ import { lstat as lstat2 } from "fs/promises";
578
+ import { join } from "path";
579
+ import {
580
+ digestCodeVerificationReceipt
581
+ } from "@odla-ai/camel/code";
582
+ var SHA = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;
583
+ var DIGEST = /^sha256:[0-9a-f]{64}$/;
584
+ var ID = /^[A-Za-z0-9._:-]{1,160}$/;
585
+ var RULE = /^[A-Za-z0-9_@+.,/-]{1,160}$/;
586
+ var DEFAULT_PREFIXES = ["test/", "tests/", "__tests__/"];
587
+ var DEFAULT_SUFFIXES = [".test.js", ".test.ts", ".test.tsx", ".spec.js", ".spec.ts", ".spec.tsx"];
588
+ async function verifyCodeCandidate(input) {
589
+ const policy = validate(input);
590
+ const limits = { maxFiles: policy.maximumFiles, maxBytes: policy.maximumBytes };
591
+ const staged = await stageWorkspace(input.trustedBaseDir, limits);
592
+ try {
593
+ const baseDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
594
+ if (baseDigest !== input.trustedBaseDigest) throw new TypeError("trusted base does not match its registered digest");
595
+ const paths = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
596
+ await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths);
597
+ const sourceDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
598
+ const policyDigest = digestPolicy(policy);
599
+ const patchDigest = digestBytes(input.candidatePatch);
600
+ const candidateDigest = digestJson({
601
+ trustedBaseCommitSha: input.trustedBaseCommitSha,
602
+ trustedBaseDigest: input.trustedBaseDigest,
603
+ patchDigest
604
+ });
605
+ const changedTests = changedTestPaths(paths, policy);
606
+ if (changedTests.length > policy.maximumChangedTests) throw new TypeError("candidate changes too many test files");
607
+ const recipes = [];
608
+ const logs = [];
609
+ for (const recipe2 of policy.recipes) {
610
+ const clean = await stageWorkspace(staged.workspaceDir, limits);
611
+ try {
612
+ if (await digestStagedWorkspace(clean.workspaceDir, limits) !== sourceDigest) {
613
+ throw new TypeError("clean verifier source changed before execution");
614
+ }
615
+ const result = checkedResult(await input.recipeExecutor.run({
616
+ workspaceDir: clean.workspaceDir,
617
+ recipe: recipe2,
618
+ signal: input.signal
619
+ }), recipe2.maxOutputBytes);
620
+ const artifacts = await inspectArtifacts(clean.workspaceDir, recipe2);
621
+ recipes.push(recipeReceipt(recipe2, result, artifacts));
622
+ logs.push({ recipeId: recipe2.id, ...boundedLogs(result, recipe2.maxOutputBytes) });
623
+ } finally {
624
+ await clean.cleanup();
625
+ }
626
+ }
627
+ const fields = {
628
+ schemaVersion: 1,
629
+ verificationId: input.verificationId ?? `verify-${randomUUID2()}`,
630
+ trustedBaseCommitSha: input.trustedBaseCommitSha,
631
+ trustedBaseDigest: input.trustedBaseDigest,
632
+ patchDigest,
633
+ candidateDigest,
634
+ sourceDigest,
635
+ policyDigest,
636
+ recipes,
637
+ changedTestCount: changedTests.length,
638
+ changedTestSetDigest: digestJson(changedTests),
639
+ changedTestsRequireReview: changedTests.length > 0,
640
+ outcome: recipes.every((recipe2) => recipe2.status === "passed") ? "passed" : "failed"
641
+ };
642
+ return {
643
+ receipt: { ...fields, receiptDigest: await digestCodeVerificationReceipt(fields) },
644
+ changedTests: Object.freeze(changedTests),
645
+ logs: Object.freeze(logs)
646
+ };
647
+ } finally {
648
+ await staged.cleanup();
649
+ }
650
+ }
651
+ function validate(input) {
652
+ const policy = input.policy;
653
+ if (!SHA.test(input.trustedBaseCommitSha) || !DIGEST.test(input.trustedBaseDigest) || !ID.test(input.verificationId ?? "verify-generated") || !ID.test(policy.policyId) || policy.recipes.length < 1 || policy.recipes.length > 64 || new Set(policy.recipes.map((recipe2) => recipe2.id)).size !== policy.recipes.length) {
654
+ throw new TypeError("clean verification input is malformed");
655
+ }
656
+ for (const recipe2 of policy.recipes) assertCodeBuildRecipe(recipe2);
657
+ const result = {
658
+ policyId: policy.policyId,
659
+ recipes: policy.recipes,
660
+ testPathPrefixes: policy.testPathPrefixes ?? DEFAULT_PREFIXES,
661
+ testPathSuffixes: policy.testPathSuffixes ?? DEFAULT_SUFFIXES,
662
+ maximumChangedTests: policy.maximumChangedTests ?? 1e3,
663
+ maximumPatchBytes: policy.maximumPatchBytes ?? 256 * 1024,
664
+ maximumFiles: policy.maximumFiles ?? 2e4,
665
+ maximumBytes: policy.maximumBytes ?? 512 * 1024 * 1024
666
+ };
667
+ if ([...result.testPathPrefixes, ...result.testPathSuffixes].some((rule) => !RULE.test(rule)) || !integer(result.maximumChangedTests, 0, 1e4) || !integer(result.maximumPatchBytes, 1, 4 * 1024 * 1024) || !integer(result.maximumFiles, 1, 1e5) || !integer(result.maximumBytes, 1, 2 * 1024 * 1024 * 1024)) {
668
+ throw new TypeError("clean verification policy exceeds its bounds");
669
+ }
670
+ return result;
671
+ }
672
+ function changedTestPaths(paths, policy) {
673
+ return paths.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
674
+ }
675
+ function recipeReceipt(recipe2, result, artifacts) {
676
+ const status = result.timedOut ? "timed_out" : result.outputLimitExceeded ? "output_limited" : result.exitCode === 0 && artifacts.every((item) => item.status === "verified") ? "passed" : "failed";
677
+ return {
678
+ recipeId: recipe2.id,
679
+ recipeDigest: digestRecipe(recipe2),
680
+ status,
681
+ exitCode: result.exitCode,
682
+ durationMs: result.durationMs,
683
+ artifacts
684
+ };
685
+ }
686
+ async function inspectArtifacts(workspaceDir, recipe2) {
687
+ const receipts = [];
688
+ for (const artifact of recipe2.expectedArtifacts ?? []) {
689
+ try {
690
+ const path = join(workspaceDir, artifact.path);
691
+ const info = await lstat2(path);
692
+ if (!info.isFile() || info.isSymbolicLink()) {
693
+ receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
694
+ } else if (info.size > artifact.maximumBytes) {
695
+ receipts.push({ artifactId: artifact.id, status: "too_large", bytes: info.size, digest: null });
696
+ } else {
697
+ receipts.push({ artifactId: artifact.id, status: "verified", bytes: info.size, digest: await hashFile(path) });
698
+ }
699
+ } catch (reason) {
700
+ if (reason.code !== "ENOENT") throw reason;
701
+ receipts.push({ artifactId: artifact.id, status: "missing", bytes: null, digest: null });
702
+ }
703
+ }
704
+ return receipts;
705
+ }
706
+ function hashFile(path) {
707
+ return new Promise((accept, reject) => {
708
+ const hash = createHash2("sha256");
709
+ const stream = createReadStream(path);
710
+ stream.on("data", (chunk) => {
711
+ hash.update(chunk);
712
+ });
713
+ stream.once("error", reject);
714
+ stream.once("end", () => accept(`sha256:${hash.digest("hex")}`));
715
+ });
716
+ }
717
+ function checkedResult(result, maximumOutputBytes) {
718
+ if (!integer(result.exitCode, 0, 255) || !integer(result.durationMs, 0, 30 * 6e4) || typeof result.stdout !== "string" || typeof result.stderr !== "string" || typeof result.outputLimitExceeded !== "boolean" || typeof result.timedOut !== "boolean") {
719
+ throw new TypeError("recipe executor returned an invalid result");
720
+ }
721
+ const bytes = Buffer.byteLength(result.stdout) + Buffer.byteLength(result.stderr);
722
+ return bytes > maximumOutputBytes ? { ...result, outputLimitExceeded: true } : result;
723
+ }
724
+ function boundedLogs(result, maximum) {
725
+ const stdout = Buffer.from(result.stdout);
726
+ const stderr = Buffer.from(result.stderr);
727
+ const first = stdout.subarray(0, maximum);
728
+ return {
729
+ stdout: first.toString("utf8"),
730
+ stderr: stderr.subarray(0, Math.max(0, maximum - first.byteLength)).toString("utf8")
731
+ };
732
+ }
733
+ function digestPolicy(policy) {
734
+ return digestJson({
735
+ policyId: policy.policyId,
736
+ recipes: policy.recipes.map((recipe2) => normalizedRecipe(recipe2)),
737
+ testPathPrefixes: [...policy.testPathPrefixes].sort(),
738
+ testPathSuffixes: [...policy.testPathSuffixes].sort(),
739
+ maximumChangedTests: policy.maximumChangedTests,
740
+ maximumPatchBytes: policy.maximumPatchBytes,
741
+ maximumFiles: policy.maximumFiles,
742
+ maximumBytes: policy.maximumBytes
743
+ });
744
+ }
745
+ function digestRecipe(recipe2) {
746
+ return digestJson(normalizedRecipe(recipe2));
747
+ }
748
+ function normalizedRecipe(recipe2) {
749
+ return {
750
+ id: recipe2.id,
751
+ image: recipe2.image,
752
+ command: [...recipe2.command],
753
+ timeoutMs: recipe2.timeoutMs,
754
+ maxOutputBytes: recipe2.maxOutputBytes,
755
+ cpus: recipe2.cpus ?? 1,
756
+ memory: recipe2.memory ?? "1g",
757
+ pids: recipe2.pids ?? 256,
758
+ tmpfsBytes: recipe2.tmpfsBytes ?? 64 * 1024 * 1024,
759
+ expectedArtifacts: [...recipe2.expectedArtifacts ?? []].sort((left, right) => left.id.localeCompare(right.id)).map((artifact) => ({ id: artifact.id, path: artifact.path, maximumBytes: artifact.maximumBytes }))
760
+ };
761
+ }
762
+ function digestJson(value) {
763
+ return digestBytes(JSON.stringify(value));
764
+ }
765
+ function digestBytes(value) {
766
+ return `sha256:${createHash2("sha256").update(value).digest("hex")}`;
767
+ }
768
+ function integer(value, minimum, maximum) {
769
+ return Number.isSafeInteger(value) && value >= minimum && value <= maximum;
770
+ }
771
+
772
+ // src/code-runtime-checkpoint.ts
773
+ async function prepareRuntimeCheckpoint(input) {
774
+ const patch2 = await input.workspace.patch(256 * 1024);
775
+ let verification = null;
776
+ let review = null;
777
+ let note = patch2 ? "Candidate remains untrusted" : "Checkpoint has no source changes";
778
+ if (patch2 && input.role === "coding") {
779
+ try {
780
+ const evidence = await verifyCodeCandidate({
781
+ verificationId: `verify-${input.sessionId.slice("csess_".length)}`,
782
+ trustedBaseDir: input.workspace.baselineDir,
783
+ trustedBaseCommitSha: input.baseCommitSha,
784
+ trustedBaseDigest: input.trustedBaseDigest,
785
+ candidatePatch: patch2,
786
+ policy: {
787
+ policyId: "code.runtime",
788
+ recipes: input.recipes,
789
+ maximumFiles: 2e4,
790
+ maximumBytes: 512 * 1024 * 1024
791
+ },
792
+ recipeExecutor: input.recipeExecutor
793
+ });
794
+ if (evidence.receipt.outcome === "passed") {
795
+ verification = evidence.receipt;
796
+ review = await input.review(patch2, verification);
797
+ note = review.verdict === "approved" ? "Clean verification and independent review passed" : "Clean verification passed; independent review rejected the candidate";
798
+ } else {
799
+ note = `Clean verification failed: ${evidence.receipt.recipes.filter((recipe2) => recipe2.status !== "passed").map((recipe2) => `${recipe2.recipeId}=${recipe2.status}`).join(", ")}`;
800
+ }
801
+ } catch (cause) {
802
+ note = `Candidate verification or review failed closed: ${message(cause)}`;
803
+ }
804
+ }
805
+ const reviewed = verification && review?.verdict === "approved";
806
+ const checkpoint = await createCodeWorkspaceCheckpoint({
807
+ workspace: input.workspace,
808
+ baseCommitSha: input.baseCommitSha,
809
+ state: {
810
+ planCursor: null,
811
+ conversationRefs: input.conversationRefs,
812
+ planningInputDigest: input.planningInputDigest,
813
+ buildPolicyDigest: verification?.policyDigest ?? input.fallbackPolicyDigest,
814
+ dependencyLayerDigest: null,
815
+ verificationDigest: verification?.receiptDigest ?? null,
816
+ reviewDigest: reviewed && review ? review.reviewDigest : null,
817
+ completedEffects: [],
818
+ unresolvedApprovals: [],
819
+ trustStatus: reviewed ? "reviewed" : verification ? "verified" : "candidate_untrusted"
820
+ }
821
+ });
822
+ return { checkpoint, verification, review, note };
823
+ }
824
+ var message = (value) => (value instanceof Error ? value.message : String(value)).slice(0, 500);
825
+
826
+ // src/code-runtime-checkpoint-manager.ts
827
+ var CodeRuntimeCheckpointManager = class {
828
+ constructor(options) {
829
+ this.options = options;
830
+ }
831
+ options;
832
+ #pending = /* @__PURE__ */ new Map();
833
+ async prepare(command, active) {
834
+ active.abort.abort("checkpoint_stop");
835
+ await active.done;
836
+ const prepared = await prepareRuntimeCheckpoint({
837
+ sessionId: command.sessionId,
838
+ role: active.role,
839
+ workspace: active.workspace,
840
+ baseCommitSha: active.baseCommitSha,
841
+ trustedBaseDigest: active.trustedBaseDigest,
842
+ planningInputDigest: active.planningInputDigest,
843
+ conversationRefs: active.conversationRefs,
844
+ fallbackPolicyDigest: this.options.fallbackPolicyDigest,
845
+ recipes: this.options.recipes,
846
+ recipeExecutor: this.options.recipeExecutor,
847
+ review: (patch2, verification) => this.options.control.review(command.sessionId, { patch: patch2, verification })
848
+ });
849
+ if (prepared.review?.verdict === "approved" && prepared.verification) {
850
+ this.#pending.set(command.commandId, { verification: prepared.verification, refs: active.conversationRefs });
851
+ }
852
+ await this.options.event(command, { type: "message", actor: "system", body: prepared.note }, active.conversationRefs).catch(() => void 0);
853
+ await active.workspace.cleanup();
854
+ await this.options.event(command, { type: "status", status: "checkpointed" }, active.conversationRefs).catch(() => void 0);
855
+ return { status: "checkpointed", checkpoint: prepared.checkpoint, message: "Pi stopped at a portable checkpoint" };
856
+ }
857
+ async acknowledged(command, result) {
858
+ if (command.kind !== "checkpoint_stop" || result.status !== "checkpointed") return false;
859
+ const pending = this.#pending.get(command.commandId);
860
+ if (!pending) return true;
861
+ const checkpointId = `cpoint_${command.commandId.slice("ccmd_".length)}`;
862
+ const candidate = await this.options.control.submitCandidate(command.sessionId, checkpointId, pending.verification);
863
+ await this.options.event(command, {
864
+ type: "message",
865
+ actor: "system",
866
+ body: command.payload.sourceSet ? `Candidate ${candidate.candidateId} was verified and delivered to the session PR branch` : `Candidate ${candidate.candidateId} is ready for legacy owner publication approval`
867
+ }, pending.refs);
868
+ this.#pending.delete(command.commandId);
869
+ return true;
870
+ }
871
+ };
872
+
873
+ // src/code-runtime-source.ts
874
+ import { mkdir, mkdtemp, rm, writeFile } from "fs/promises";
875
+ import { tmpdir } from "os";
876
+ import { dirname, join as join2, resolve as resolve3, sep as sep2 } from "path";
877
+ var RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
878
+ var SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
879
+ async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir()) {
880
+ if (!snapshot.files.length || snapshot.files.length > 1e4) throw new TypeError("Code source file count is invalid");
881
+ const root = await mkdtemp(join2(tempRoot, "odla-code-source-"));
882
+ const sourceDir = join2(root, "source");
883
+ await mkdir(sourceDir);
884
+ const seen = /* @__PURE__ */ new Set();
885
+ let bytes = 0;
886
+ try {
887
+ for (const file of snapshot.files) {
888
+ validatePath(file.path);
889
+ if (seen.has(file.path)) throw new TypeError("Code source repeats a path");
890
+ seen.add(file.path);
891
+ bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
892
+ if (bytes > 16 * 1024 * 1024) throw new TypeError("Code source exceeds its byte bound");
893
+ const target = resolve3(sourceDir, file.path);
894
+ if (!target.startsWith(`${resolve3(sourceDir)}${sep2}`)) throw new TypeError("Code source path escapes its root");
895
+ await mkdir(dirname(target), { recursive: true });
896
+ await writeFile(target, file.content, { flag: "wx", mode: 420 });
897
+ }
898
+ for (const reference of snapshot.references ?? []) {
899
+ validateAlias(reference.alias);
900
+ if (!reference.files.length || reference.files.length > 1e4) throw new TypeError("Code reference file count is invalid");
901
+ for (const file of reference.files) {
902
+ validatePath(file.path);
903
+ const path = `.odla-references/${reference.alias}/${file.path}`;
904
+ if (seen.has(path)) throw new TypeError("Code reference repeats a path");
905
+ seen.add(path);
906
+ bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
907
+ if (bytes > 80 * 1024 * 1024) throw new TypeError("Code source set exceeds its byte bound");
908
+ const target = resolve3(sourceDir, path);
909
+ if (!target.startsWith(`${resolve3(sourceDir)}${sep2}`)) throw new TypeError("Code reference path escapes its root");
910
+ await mkdir(dirname(target), { recursive: true });
911
+ await writeFile(target, file.content, { flag: "wx", mode: 292 });
912
+ }
913
+ }
914
+ return { sourceDir, cleanup: () => rm(root, { recursive: true, force: true }) };
915
+ } catch (cause) {
916
+ await rm(root, { recursive: true, force: true });
917
+ throw cause;
918
+ }
919
+ }
920
+ function validateAlias(alias) {
921
+ if (!/^[a-z][a-z0-9-]{0,39}$/.test(alias) || alias === "primary") {
922
+ throw new TypeError("Code reference alias is invalid");
923
+ }
924
+ }
925
+ async function attachCodeRuntimeReferences(workspace, references) {
926
+ let bytes = 0;
927
+ for (const reference of references) {
928
+ validateAlias(reference.alias);
929
+ for (const file of reference.files) {
930
+ validatePath(file.path);
931
+ const path = `.odla-references/${reference.alias}/${file.path}`;
932
+ bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
933
+ if (bytes > 64 * 1024 * 1024) throw new TypeError("Code reference set exceeds its byte bound");
934
+ for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
935
+ const target = resolve3(root, path);
936
+ if (!target.startsWith(`${resolve3(root)}${sep2}`)) throw new TypeError("Code reference path escapes its root");
937
+ await mkdir(dirname(target), { recursive: true });
938
+ await writeFile(target, file.content, { flag: "wx", mode: 292 });
939
+ }
940
+ }
941
+ }
942
+ }
943
+ function validatePath(path) {
944
+ const parts = path.split("/");
945
+ if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || parts.some((part) => !part || part === "." || part === ".." || RESERVED2.has(part) || SECRET2.test(part))) {
946
+ throw new TypeError("Code source contains an unsafe path");
947
+ }
948
+ }
949
+
950
+ // src/code-tool-broker.ts
951
+ import { readFile as readFile2, readdir as readdir2, stat } from "fs/promises";
952
+ import { relative as relative2, resolve as resolve4 } from "path";
953
+
954
+ // src/code-tool-policy.ts
955
+ import {
956
+ conversionPolicyDigest,
957
+ createCamelIngress,
958
+ createConversionRegistry,
959
+ registeredIdRegistryDigest
960
+ } from "@odla-ai/camel";
961
+ import {
962
+ createEffectPolicy,
963
+ destinationRegistryDigest
964
+ } from "@odla-ai/camel/policy";
965
+ var DESTINATIONS = "code-workspaces.v1";
966
+ var READ = descriptor("sandbox.read", "scoped_data_read", {
967
+ workspace: "destination",
968
+ authority: "authority",
969
+ path: "selector",
970
+ startLine: "selector",
971
+ endLine: "selector"
972
+ });
973
+ var PATCH = descriptor("sandbox.apply_patch", "reversible_mutation", {
974
+ workspace: "destination",
975
+ authority: "authority",
976
+ patch: "payload"
977
+ });
978
+ var RECIPE = descriptor("sandbox.run_recipe", "code_execution", {
979
+ workspace: "destination",
980
+ authority: "authority",
981
+ recipeId: "selector",
982
+ sourceDigest: "payload"
983
+ });
984
+ function createCodePolicyGate(options) {
985
+ return {
986
+ read: async (input) => {
987
+ const base = await environment(input, options, "sandbox.read");
988
+ const conversions = await conversionRegistry([
989
+ await registeredPolicy("code.path.v1", "code.paths.v1", input.paths),
990
+ await conversionPolicy("code.line.v1", { kind: "integer", minimum: 1, maximum: 1e6 })
991
+ ], { "code.paths.v1": input.paths });
992
+ const path = await conversions.operations.registeredId(unsafe(base, input.path, "path"), "code.path.v1");
993
+ const start = await conversions.operations.integer(unsafe(base, input.startLine, "start"), "code.line.v1");
994
+ const end = await conversions.operations.integer(unsafe(base, input.endLine, "end"), "code.line.v1");
995
+ if (end.value < start.value) return false;
996
+ return authorize(input, options, base, READ, {
997
+ ...base.fixedArgs,
998
+ path: { role: "selector", value: path },
999
+ startLine: { role: "selector", value: start },
1000
+ endLine: { role: "selector", value: end }
1001
+ }, [path, start, end]);
1002
+ },
1003
+ patch: async (input) => {
1004
+ const base = await environment(input, options, "sandbox.apply_patch");
1005
+ const patch2 = unsafe(base, input.patch, "patch");
1006
+ return authorize(input, options, base, PATCH, {
1007
+ ...base.fixedArgs,
1008
+ patch: { role: "payload", value: patch2 }
1009
+ }, []);
1010
+ },
1011
+ recipe: async (input) => {
1012
+ const base = await environment(input, options, "sandbox.run_recipe");
1013
+ const conversions = await conversionRegistry([
1014
+ await registeredPolicy("code.recipe.v1", "code.recipes.v1", input.recipeIds)
1015
+ ], { "code.recipes.v1": input.recipeIds });
1016
+ const recipe2 = await conversions.operations.registeredId(unsafe(base, input.recipeId, "recipe"), "code.recipe.v1");
1017
+ const source = unsafe(base, input.sourceDigest, "source");
1018
+ return authorize(input, options, base, RECIPE, {
1019
+ ...base.fixedArgs,
1020
+ recipeId: { role: "selector", value: recipe2 },
1021
+ sourceDigest: { role: "payload", value: source }
1022
+ }, [recipe2]);
1023
+ }
1024
+ };
1025
+ }
1026
+ function descriptor(name, effect, argumentRoles) {
1027
+ return { name, version: 1, effect, inputSchema: { type: "object" }, argumentRoles, policyId: `odla.code.${name}.v1` };
1028
+ }
1029
+ async function conversionPolicy(id, output) {
1030
+ const definition = {
1031
+ conversionId: id,
1032
+ version: 1,
1033
+ output,
1034
+ maximumSourceBytes: 1e6,
1035
+ maximumOutputsPerArtifact: 4,
1036
+ presentation: "json_scalar"
1037
+ };
1038
+ return { ...definition, digest: await conversionPolicyDigest(definition) };
1039
+ }
1040
+ async function registeredPolicy(id, registryId, values) {
1041
+ const mapping = Object.fromEntries(values.map((value) => [value, value]));
1042
+ return conversionPolicy(id, {
1043
+ kind: "registered_id",
1044
+ registryId,
1045
+ registryDigest: await registeredIdRegistryDigest(mapping)
1046
+ });
1047
+ }
1048
+ async function conversionRegistry(policies, values) {
1049
+ const registeredIds = Object.fromEntries(await Promise.all(Object.entries(values).map(async ([id, entries]) => {
1050
+ const mapping = Object.fromEntries(entries.map((value) => [value, value]));
1051
+ return [id, { values: mapping, digest: await registeredIdRegistryDigest(mapping) }];
1052
+ })));
1053
+ return createConversionRegistry({ policies, registeredIds });
1054
+ }
1055
+ async function environment(input, options, tool) {
1056
+ const ingress = createCamelIngress([
1057
+ { id: "workspace", value: input.workspaceId, readers: input.readers },
1058
+ { id: "authority", value: `lease:${input.lease.leaseId}`, readers: input.readers },
1059
+ { id: "reader", value: options.readerId, readers: input.readers }
1060
+ ]);
1061
+ const digest = await destinationRegistryDigest([input.workspaceId]);
1062
+ const approvals = ["irreversible_mutation", "external_send", "financial", "secret_read"];
1063
+ if (options.recipeAuthorization === "exact_approval") approvals.push("code_execution");
1064
+ const policy = createEffectPolicy({
1065
+ destinationRegistries: { [DESTINATIONS]: { digest, values: [input.workspaceId] } },
1066
+ approvalEffects: approvals
1067
+ });
1068
+ const fixedArgs = {
1069
+ workspace: { role: "destination", value: ingress.control("workspace"), registryId: DESTINATIONS, registryDigest: digest },
1070
+ authority: { role: "authority", value: ingress.control("authority") }
1071
+ };
1072
+ return { ingress, policy, fixedArgs, reader: ingress.control("reader"), runId: `${input.request.requestId}:${tool}` };
1073
+ }
1074
+ function unsafe(base, value, field) {
1075
+ return base.ingress.quarantinedOutput(value, {
1076
+ readers: base.reader.label.readers,
1077
+ runId: `${base.runId}:${field}`
1078
+ });
1079
+ }
1080
+ async function authorize(input, options, base, tool, args, controlDependencies) {
1081
+ const policy = await base.policy.evaluate({
1082
+ planId: input.lease.task.taskId,
1083
+ tool,
1084
+ args,
1085
+ controlDependencies,
1086
+ intendedReaderIds: [base.reader]
1087
+ });
1088
+ let approvalConsumed = false;
1089
+ if (policy.outcome === "require_approval" && options.consumeApproval) {
1090
+ approvalConsumed = await options.consumeApproval(decision(input, policy, false, tool.name, policy.actionDigest));
1091
+ }
1092
+ await options.onDecision?.(decision(input, policy, approvalConsumed, tool.name));
1093
+ return policy.outcome === "allow" || approvalConsumed;
1094
+ }
1095
+ function decision(input, policy, approvalConsumed, tool, actionDigest) {
1096
+ return {
1097
+ lease: input.lease,
1098
+ request: input.request,
1099
+ tool,
1100
+ policy,
1101
+ approvalConsumed,
1102
+ actionDigest: actionDigest ?? (policy.outcome === "require_approval" ? policy.actionDigest : "")
1103
+ };
1104
+ }
1105
+
1106
+ // src/code-tool-broker.ts
1107
+ function createCodeToolBroker(options) {
1108
+ validateOptions(options);
1109
+ const recipes = new Map(options.recipes.map((recipe2) => [recipe2.id, recipe2]));
1110
+ const policy = createCodePolicyGate(options);
1111
+ let tail = Promise.resolve();
1112
+ return {
1113
+ execute(context, request) {
1114
+ const result = tail.then(() => route(context, request, options, recipes, policy));
1115
+ tail = result.then(() => void 0, () => void 0);
1116
+ return result;
1117
+ }
1118
+ };
1119
+ }
1120
+ async function route(context, request, options, recipes, policy) {
1121
+ try {
1122
+ if (context.signal?.aborted) throw new TypeError("tool request was cancelled");
1123
+ if (request.tool === "sandbox.read") return await read(context, request, options, policy);
1124
+ if (request.tool === "sandbox.apply_patch") return await patch(context, request, options, policy);
1125
+ return await recipe(context, request, options, recipes, policy);
1126
+ } catch (reason) {
1127
+ return response(request, false, reason instanceof TypeError ? reason.message : "tool failed closed");
1128
+ }
1129
+ }
1130
+ async function read(context, request, options, policy) {
1131
+ exactKeys(request.input, ["path", "startLine", "endLine"]);
1132
+ const path = stringField(request.input, "path");
1133
+ const startLine = optionalInteger(request.input.startLine) ?? 1;
1134
+ const endLine = optionalInteger(request.input.endLine) ?? startLine + (options.maxReadLines ?? 2e3) - 1;
1135
+ if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
1136
+ throw new TypeError("requested line range exceeds its bound");
1137
+ }
1138
+ const paths = await registeredFiles(context.workspaceDir, 2e4);
1139
+ const allowed = await policy.read(policyContext(context, request, options, { paths, path, startLine, endLine }));
1140
+ if (!allowed) return response(request, false, "tool denied by CaMeL policy");
1141
+ const target = resolveCodePath(context.workspaceDir, path);
1142
+ const info = await stat(target);
1143
+ if (!info.isFile() || info.size > Math.max(options.maxReadBytes ?? 128 * 1024, 2 * 1024 * 1024)) {
1144
+ throw new TypeError("file is not a bounded regular source file");
1145
+ }
1146
+ const source = await readFile2(target);
1147
+ if (source.includes(0)) throw new TypeError("binary files are not readable through this tool");
1148
+ const lines = source.toString("utf8").split("\n");
1149
+ const content = lines.slice(startLine - 1, endLine).join("\n");
1150
+ if (Buffer.byteLength(content) > (options.maxReadBytes ?? 128 * 1024)) {
1151
+ throw new TypeError("read result exceeds its byte bound");
1152
+ }
1153
+ return response(request, true, content, { path, startLine, endLine: Math.min(endLine, lines.length) });
1154
+ }
1155
+ async function patch(context, request, options, policy) {
1156
+ exactKeys(request.input, ["patch"]);
1157
+ const value = stringField(request.input, "patch");
1158
+ const paths = validateCodePatch(value, options.maxPatchBytes ?? 256 * 1024);
1159
+ if (paths.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
1160
+ throw new TypeError("patch targets a read-only reference source");
1161
+ }
1162
+ const allowed = await policy.patch(policyContext(context, request, options, { patch: value }));
1163
+ if (!allowed) return response(request, false, "tool denied by CaMeL policy");
1164
+ await applyCodePatch(context.workspaceDir, value, paths);
1165
+ return response(request, true, `Applied patch to ${paths.length} file(s).`, { paths });
1166
+ }
1167
+ async function recipe(context, request, options, recipes, policy) {
1168
+ exactKeys(request.input, ["recipeId"]);
1169
+ const recipeId = stringField(request.input, "recipeId");
1170
+ const digestLimits = {
1171
+ maxFiles: options.maxRecipeWorkspaceFiles ?? 2e4,
1172
+ maxBytes: options.maxRecipeWorkspaceBytes ?? 512 * 1024 * 1024
1173
+ };
1174
+ const sourceDigest = await digestStagedWorkspace(context.workspaceDir, digestLimits);
1175
+ const allowed = await policy.recipe(policyContext(context, request, options, {
1176
+ recipeIds: [...recipes.keys()].sort(),
1177
+ recipeId,
1178
+ sourceDigest
1179
+ }));
1180
+ if (!allowed) return response(request, false, "tool denied by CaMeL policy");
1181
+ const selected = recipes.get(recipeId);
1182
+ if (!selected) return response(request, false, "build recipe is not registered");
1183
+ const staged = await stageWorkspace(context.workspaceDir, {
1184
+ maxFiles: digestLimits.maxFiles,
1185
+ maxBytes: digestLimits.maxBytes
1186
+ });
1187
+ try {
1188
+ if (await digestStagedWorkspace(staged.workspaceDir, digestLimits) !== sourceDigest) {
1189
+ throw new TypeError("workspace changed after recipe authorization");
1190
+ }
1191
+ const result = await options.recipeExecutor.run({
1192
+ workspaceDir: staged.workspaceDir,
1193
+ recipe: selected,
1194
+ signal: context.signal
1195
+ });
1196
+ const output = [result.stdout, result.stderr].filter(Boolean).join("\n");
1197
+ const ok = result.exitCode === 0 && !result.outputLimitExceeded && !result.timedOut;
1198
+ const status = result.timedOut ? "timed out" : result.outputLimitExceeded ? "exceeded output limit" : ok ? "passed" : `failed with exit ${result.exitCode}`;
1199
+ return response(request, ok, `Recipe ${recipeId} ${status}.${output ? `
1200
+ ${output}` : ""}`, {
1201
+ recipeId,
1202
+ exitCode: result.exitCode,
1203
+ durationMs: result.durationMs,
1204
+ outputLimitExceeded: result.outputLimitExceeded,
1205
+ timedOut: result.timedOut
1206
+ });
1207
+ } finally {
1208
+ await staged.cleanup();
1209
+ }
1210
+ }
1211
+ function policyContext(context, request, options, extra) {
1212
+ return {
1213
+ lease: context.lease,
1214
+ request,
1215
+ workspaceId: `workspace:${context.lease.task.attemptId}`,
1216
+ readers: { kind: "principals", principalIds: [options.readerId] },
1217
+ ...extra
1218
+ };
1219
+ }
1220
+ async function registeredFiles(root, limit) {
1221
+ const paths = [];
1222
+ const walk = async (directory) => {
1223
+ for (const entry of await readdir2(directory, { withFileTypes: true })) {
1224
+ if (entry.isSymbolicLink()) throw new TypeError("workspace contains a symbolic link");
1225
+ const target = resolve4(directory, entry.name);
1226
+ if (entry.isDirectory()) await walk(target);
1227
+ else if (entry.isFile()) {
1228
+ const path = relative2(root, target).split("\\").join("/");
1229
+ try {
1230
+ validateRelativePath(path);
1231
+ } catch {
1232
+ continue;
1233
+ }
1234
+ paths.push(path);
1235
+ if (paths.length > limit) throw new TypeError("workspace file registry exceeds its bound");
1236
+ }
1237
+ }
1238
+ };
1239
+ await walk(resolve4(root));
1240
+ return paths.sort();
1241
+ }
1242
+ function validateOptions(options) {
1243
+ if (!options.readerId || !options.recipes.length || new Set(options.recipes.map((item) => item.id)).size !== options.recipes.length) {
1244
+ throw new TypeError("Code tool broker requires a reader and unique registered recipes");
1245
+ }
1246
+ for (const recipe2 of options.recipes) assertCodeBuildRecipe(recipe2);
1247
+ if (options.readOnlyPrefixes?.some((prefix) => !/^[A-Za-z0-9_.-]+$/.test(prefix) || prefix === "." || prefix === "..")) {
1248
+ throw new TypeError("Code tool broker read-only prefix is invalid");
1249
+ }
1250
+ }
1251
+ function exactKeys(input, allowed) {
1252
+ if (Object.keys(input).some((key) => !allowed.includes(key))) throw new TypeError("tool input contains an unsupported field");
1253
+ }
1254
+ function stringField(input, name) {
1255
+ const value = input[name];
1256
+ if (typeof value !== "string" || !value) throw new TypeError(`${name} must be a non-empty string`);
1257
+ return value;
1258
+ }
1259
+ function optionalInteger(value) {
1260
+ if (value === void 0) return void 0;
1261
+ if (!Number.isSafeInteger(value) || value < 1) throw new TypeError("line bounds must be positive integers");
1262
+ return value;
1263
+ }
1264
+ function response(request, ok, content, details) {
1265
+ return { requestId: request.requestId, ok, content, ...details ? { details } : {} };
1266
+ }
1267
+
1268
+ // src/code-runtime-task.ts
1269
+ function codeCommandMetadata(payload, resume) {
1270
+ const trusted = record2(payload.trustedBase);
1271
+ const role = payload.role;
1272
+ const title = payload.title;
1273
+ const prompt = payload.prompt;
1274
+ const maxTokensPerInteraction = payload.maxTokensPerInteraction ?? 32e3;
1275
+ if (role !== "coding" && role !== "review" || typeof title !== "string" || typeof prompt !== "string") {
1276
+ throw new TypeError(`invalid Code ${resume ? "resume" : "start"} metadata`);
1277
+ }
1278
+ const planning = trusted?.planningInputDigest;
1279
+ const attestation = trusted?.attestationDigest;
1280
+ const repository = trusted?.repository;
1281
+ const baseCommitSha = trusted?.commitSha;
1282
+ const sourceTreeDigest = trusted?.treeDigest;
1283
+ if (typeof repository !== "string" || !repository.includes("/") || typeof baseCommitSha !== "string" || !/^[0-9a-f]{40}$/.test(baseCommitSha) || typeof sourceTreeDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(sourceTreeDigest)) {
1284
+ throw new TypeError(`invalid Code ${resume ? "resume" : "start"} trusted base`);
1285
+ }
1286
+ if (!Number.isSafeInteger(maxTokensPerInteraction) || Number(maxTokensPerInteraction) < 4e3 || Number(maxTokensPerInteraction) > 2e5) {
1287
+ throw new TypeError(`invalid Code ${resume ? "resume" : "start"} interaction token limit`);
1288
+ }
1289
+ return {
1290
+ role,
1291
+ title,
1292
+ prompt,
1293
+ maxTokensPerInteraction: Number(maxTokensPerInteraction),
1294
+ planningInputDigest: typeof planning === "string" && /^sha256:[0-9a-f]{64}$/.test(planning) ? planning : null,
1295
+ attestationDigest: typeof attestation === "string" ? attestation : "resume",
1296
+ repository,
1297
+ baseCommitSha,
1298
+ sourceTreeDigest
1299
+ };
1300
+ }
1301
+ function codeLocalSource(payload) {
1302
+ const source = record2(payload.source);
1303
+ if (!source) return null;
1304
+ if (source.kind !== "local_checkout" || typeof source.repository !== "string" || typeof source.headCommitSha !== "string" || !/^[0-9a-f]{40}$/.test(source.headCommitSha) || typeof source.trustedBaseDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(source.trustedBaseDigest) || typeof source.developerPatchDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(source.developerPatchDigest) || typeof source.snapshotDigest !== "string" || !/^sha256:[0-9a-f]{64}$/.test(source.snapshotDigest) || typeof source.modified !== "boolean" || !Number.isSafeInteger(source.fileCount) || Number(source.fileCount) < 1 || Number(source.fileCount) > 2e4 || !Number.isSafeInteger(source.byteCount) || Number(source.byteCount) < 1 || Number(source.byteCount) > 512 * 1024 * 1024 || !Number.isSafeInteger(source.capturedAt) || Number(source.capturedAt) < 1) {
1305
+ throw new TypeError("invalid local checkout source descriptor");
1306
+ }
1307
+ return source;
1308
+ }
1309
+ function codeCheckpointPayload(payload) {
1310
+ const value = payload.checkpoint;
1311
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("resume checkpoint is missing");
1312
+ return value;
1313
+ }
1314
+ function fakeCodeLease(command, metadata) {
1315
+ return {
1316
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
1317
+ leaseId: `code:${command.commandId}`,
1318
+ generation: command.bindingGeneration,
1319
+ expiresAt: Date.now() + 24 * 60 * 6e4,
1320
+ task: {
1321
+ taskId: command.sessionId,
1322
+ attemptId: command.instanceId,
1323
+ title: metadata.title,
1324
+ prompt: metadata.prompt,
1325
+ workspace: command.appId,
1326
+ aiRoute: metadata.role,
1327
+ policy: {
1328
+ network: "none",
1329
+ timeoutMs: 30 * 6e4,
1330
+ maxOutputBytes: 4 * 1024 * 1024,
1331
+ maxPatchBytes: 256 * 1024
1332
+ }
1333
+ }
1334
+ };
1335
+ }
1336
+ var record2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
1337
+
1338
+ // src/code-runtime-local-source.ts
1339
+ var SOURCE_LIMITS = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
1340
+ async function prepareRuntimeLocalSource(input) {
1341
+ const { command, descriptor: descriptor2, available, repository, baseCommitSha, resume } = input;
1342
+ if (!available || JSON.stringify(available.descriptor) !== JSON.stringify(descriptor2) || descriptor2.repository.toLowerCase() !== repository.toLowerCase() || descriptor2.headCommitSha !== baseCommitSha) {
1343
+ throw new TypeError("the session's local checkout snapshot is not available on this terminal");
1344
+ }
1345
+ const workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
1346
+ trustedBaseDir: available.trustedBaseDir,
1347
+ trustedBaseCommitSha: baseCommitSha,
1348
+ checkpoint: codeCheckpointPayload(command.payload)
1349
+ })).workspace : await stageWorkspacePair(available.trustedBaseDir, available.sourceDir, SOURCE_LIMITS);
1350
+ const trustedBaseDigest = await digestStagedWorkspace(workspace.baselineDir, SOURCE_LIMITS);
1351
+ if (trustedBaseDigest !== descriptor2.trustedBaseDigest) {
1352
+ await workspace.cleanup();
1353
+ throw new TypeError("trusted Git base digest changed after connection");
1354
+ }
1355
+ if (!resume && await digestStagedWorkspace(workspace.workspaceDir, SOURCE_LIMITS) !== descriptor2.snapshotDigest) {
1356
+ await workspace.cleanup();
1357
+ throw new TypeError("local checkout snapshot digest changed after connection");
1358
+ }
1359
+ return { workspace, sourceDigest: descriptor2.snapshotDigest, trustedBaseDigest };
1360
+ }
1361
+
1362
+ // src/code-runtime-broker.ts
1363
+ function createCodeRuntimeToolBroker(input, lease, role) {
1364
+ const broker = createCodeToolBroker({
1365
+ recipes: input.recipes,
1366
+ recipeExecutor: createContainerRecipeExecutor(input.engine),
1367
+ recipeAuthorization: input.recipeAuthorization ?? "registered_recipe",
1368
+ readerId: `code-session:${lease.task.taskId}`,
1369
+ readOnlyPrefixes: [".odla-references"]
1370
+ });
1371
+ return role === "coding" ? broker : { execute: (context, request) => request.tool === "sandbox.read" ? broker.execute(context, request) : Promise.resolve({ requestId: request.requestId, ok: false, content: "review sessions are read-only" }) };
1372
+ }
1373
+
1374
+ // src/code-runtime-inference.ts
1375
+ async function handleCodeRuntimeInference(input) {
1376
+ const { command, metadata, request, state } = input;
1377
+ if (state.tokens >= metadata.maxTokensPerInteraction) {
1378
+ if (!state.noticeEmitted) {
1379
+ state.noticeEmitted = true;
1380
+ await input.event({
1381
+ type: "message",
1382
+ actor: "system",
1383
+ body: `Pi paused at the ${metadata.maxTokensPerInteraction.toLocaleString("en-US")}-token per-interaction limit. Send a new instruction to continue.`
1384
+ }).catch(() => void 0);
1385
+ }
1386
+ return {
1387
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
1388
+ type: "inference.response",
1389
+ requestId: request.requestId,
1390
+ response: {
1391
+ id: `budget:${command.commandId}`,
1392
+ provider: "openai",
1393
+ model: "interaction-budget",
1394
+ role: "assistant",
1395
+ content: [{ type: "text", text: "Pause now. The owner-set token limit for this interaction has been reached." }],
1396
+ stopReason: "end_turn",
1397
+ usage: { inputTokens: 0, outputTokens: 0 }
1398
+ }
1399
+ };
1400
+ }
1401
+ const startedAt = Date.now();
1402
+ const response2 = await input.control.infer(command.sessionId, {
1403
+ requestId: request.requestId,
1404
+ interactionId: command.commandId,
1405
+ call: request.call
1406
+ });
1407
+ state.tokens += response2.receipt.inputTokens + response2.receipt.outputTokens;
1408
+ await input.event({
1409
+ type: "usage",
1410
+ provider: response2.receipt.provider,
1411
+ model: response2.receipt.model,
1412
+ inputTokens: response2.receipt.inputTokens,
1413
+ outputTokens: response2.receipt.outputTokens,
1414
+ durationMs: Date.now() - startedAt,
1415
+ interactionId: command.commandId,
1416
+ interactionTokens: state.tokens,
1417
+ interactionMaxTokens: metadata.maxTokensPerInteraction
1418
+ }).catch(() => void 0);
1419
+ return {
1420
+ protocolVersion: HARNESS_PROTOCOL_VERSION,
1421
+ type: "inference.response",
1422
+ requestId: request.requestId,
1423
+ response: response2.response
1424
+ };
1425
+ }
1426
+
1427
+ // src/code-runtime-events.ts
1428
+ import { createHash as createHash3 } from "crypto";
1429
+ async function appendCodeRuntimeEvent(control, command, event, refs) {
1430
+ const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
1431
+ refs.push(eventId);
1432
+ const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
1433
+ await control.appendSessionEvent(command.sessionId, eventId, bounded);
1434
+ }
1435
+ var digestRuntimeValue = (value) => `sha256:${createHash3("sha256").update(value).digest("hex")}`;
1436
+ var runtimeErrorMessage = (value) => value instanceof Error ? value.message : String(value);
1437
+ var runtimeRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
1438
+ var safeRuntimeJson = (value) => {
1439
+ try {
1440
+ return JSON.stringify(value).slice(0, 1e4);
1441
+ } catch {
1442
+ return "[event]";
1443
+ }
1444
+ };
1445
+ function runtimeResultText(value) {
1446
+ const record3 = runtimeRecord(value);
1447
+ if (record3 && typeof record3.text === "string") return record3.text.slice(0, 2e4);
1448
+ if (record3 && typeof record3.error === "string") return `Pi failed: ${record3.error.slice(0, 19989)}`;
1449
+ return null;
1450
+ }
1451
+ function runtimeResultError(value) {
1452
+ const record3 = runtimeRecord(value);
1453
+ return record3 && typeof record3.error === "string" && record3.error.trim() ? record3.error.trim().slice(0, 2e3) : null;
1454
+ }
1455
+
1456
+ // src/code-runtime-engine.ts
1457
+ var CodePiRuntimeEngine = class {
1458
+ constructor(options) {
1459
+ this.options = options;
1460
+ if (options.imageAuthorization === "cli_embedded" && !/^odla-ai\/pi-agent:embedded-sha256-[0-9a-f]{64}$/.test(options.image)) throw new TypeError("CLI-embedded Pi image must use its content-addressed local tag");
1461
+ this.#run = options.runAttempt ?? runContainerAttempt;
1462
+ this.#buildPolicyDigest = digestRuntimeValue(JSON.stringify(options.recipes));
1463
+ this.#checkpoints = new CodeRuntimeCheckpointManager({
1464
+ control: options.control,
1465
+ recipes: options.recipes,
1466
+ recipeExecutor: options.recipeExecutor ?? createContainerRecipeExecutor(options.engine),
1467
+ fallbackPolicyDigest: this.#buildPolicyDigest,
1468
+ event: (command, event, refs) => this.#event(command, event, refs)
1469
+ });
1470
+ }
1471
+ options;
1472
+ #active = /* @__PURE__ */ new Map();
1473
+ #run;
1474
+ #buildPolicyDigest;
1475
+ #checkpoints;
1476
+ execute(command) {
1477
+ if (command.kind === "checkpoint_stop") return this.#checkpoint(command);
1478
+ if (command.kind === "prompt") return this.#prompt(command);
1479
+ return this.#start(command, command.kind === "resume");
1480
+ }
1481
+ async acknowledged(command, result) {
1482
+ if (await this.#checkpoints.acknowledged(command, result)) return;
1483
+ const active = this.#active.get(command.sessionId);
1484
+ if (!active || result.status !== "running") return;
1485
+ active.acknowledged = true;
1486
+ if (active.failure) await this.options.control.reportSessionFailure(command.sessionId, active.failure).catch(() => void 0);
1487
+ }
1488
+ async close() {
1489
+ const sessions = [...this.#active.values()];
1490
+ for (const session of sessions) session.abort.abort("runtime_shutdown");
1491
+ await Promise.allSettled(sessions.map((session) => session.done));
1492
+ await Promise.allSettled(sessions.map((session) => session.workspace.cleanup()));
1493
+ this.#active.clear();
1494
+ }
1495
+ async #start(command, resume) {
1496
+ if (this.#active.has(command.sessionId)) throw new TypeError("Code session is already active on this runtime");
1497
+ const metadata = codeCommandMetadata(command.payload, resume);
1498
+ const requestedLocal = codeLocalSource(command.payload);
1499
+ let workspace;
1500
+ let sourceDigest;
1501
+ let localTrustedBaseDigest;
1502
+ if (requestedLocal) {
1503
+ const prepared = await prepareRuntimeLocalSource({
1504
+ command,
1505
+ descriptor: requestedLocal,
1506
+ available: this.options.localSource,
1507
+ repository: metadata.repository,
1508
+ baseCommitSha: metadata.baseCommitSha,
1509
+ resume
1510
+ });
1511
+ ({ workspace, sourceDigest, trustedBaseDigest: localTrustedBaseDigest } = prepared);
1512
+ if (command.payload.sourceSet) {
1513
+ const selected = await this.options.control.source(command.sessionId);
1514
+ if (selected.repository !== metadata.repository || selected.commitSha !== metadata.baseCommitSha || selected.treeDigest !== metadata.sourceTreeDigest) {
1515
+ await workspace.cleanup();
1516
+ throw new TypeError("Code local source does not match the selected GitHub primary source");
1517
+ }
1518
+ await attachCodeRuntimeReferences(workspace, selected.references ?? []);
1519
+ }
1520
+ } else {
1521
+ const source = await this.options.control.source(command.sessionId);
1522
+ const materialized = await materializeCodeRuntimeSource(source);
1523
+ try {
1524
+ workspace = resume ? (await restoreCodeWorkspaceCheckpoint({
1525
+ trustedBaseDir: materialized.sourceDir,
1526
+ trustedBaseCommitSha: source.commitSha,
1527
+ checkpoint: codeCheckpointPayload(command.payload)
1528
+ })).workspace : await stageWorkspace(materialized.sourceDir);
1529
+ } finally {
1530
+ await materialized.cleanup();
1531
+ }
1532
+ sourceDigest = source.treeDigest;
1533
+ }
1534
+ const abort = new AbortController();
1535
+ const conversationRefs = [];
1536
+ const active = {
1537
+ workspace,
1538
+ abort,
1539
+ conversationRefs,
1540
+ acknowledged: false,
1541
+ role: metadata.role,
1542
+ title: metadata.title,
1543
+ maxTokensPerInteraction: metadata.maxTokensPerInteraction,
1544
+ baseCommitSha: metadata.baseCommitSha,
1545
+ repository: metadata.repository,
1546
+ sourceTreeDigest: metadata.sourceTreeDigest,
1547
+ trustedBaseDigest: requestedLocal ? localTrustedBaseDigest : await digestStagedWorkspace(workspace.baselineDir, {
1548
+ maxFiles: 2e4,
1549
+ maxBytes: 512 * 1024 * 1024
1550
+ }),
1551
+ planningInputDigest: metadata.planningInputDigest ?? digestRuntimeValue(
1552
+ JSON.stringify({ attestation: metadata.attestationDigest, prompt: metadata.prompt, tree: sourceDigest })
1553
+ ),
1554
+ done: Promise.resolve(null)
1555
+ };
1556
+ this.#active.set(command.sessionId, active);
1557
+ if (requestedLocal) {
1558
+ await this.#event(command, {
1559
+ type: "message",
1560
+ actor: "system",
1561
+ body: `Source snapshot: local checkout ${requestedLocal.snapshotDigest} \xB7 ${requestedLocal.modified ? "modified" : "clean"} \xB7 Git ${requestedLocal.headCommitSha}`
1562
+ }, conversationRefs);
1563
+ }
1564
+ active.done = this.#runAttempt(command, metadata, active).catch(async (cause) => {
1565
+ const detail = runtimeErrorMessage(cause);
1566
+ await this.#event(command, { type: "message", actor: "system", body: `Pi failed: ${detail}` }, conversationRefs).catch(() => void 0);
1567
+ await this.#diagnostic(command, active, detail);
1568
+ await this.#event(command, { type: "status", status: "failed" }, conversationRefs).catch(() => void 0);
1569
+ await this.#failure(command, active, detail);
1570
+ return null;
1571
+ });
1572
+ return { status: "running", message: resume ? "Pi resumed from a portable checkpoint" : "Pi started" };
1573
+ }
1574
+ async #prompt(command) {
1575
+ const active = this.#active.get(command.sessionId);
1576
+ const prompt = command.payload.prompt;
1577
+ if (!active || typeof prompt !== "string" || !prompt.trim() || prompt.length > 2e4) {
1578
+ throw new TypeError("prompt requires an active Code session and bounded text");
1579
+ }
1580
+ const requestedLimit = command.payload.maxTokensPerInteraction ?? active.maxTokensPerInteraction;
1581
+ if (!Number.isSafeInteger(requestedLimit) || Number(requestedLimit) < 4e3 || Number(requestedLimit) > 2e5) {
1582
+ throw new TypeError("prompt requires a valid interaction token limit");
1583
+ }
1584
+ active.maxTokensPerInteraction = Number(requestedLimit);
1585
+ await active.done;
1586
+ active.abort = new AbortController();
1587
+ active.acknowledged = false;
1588
+ active.failure = void 0;
1589
+ active.done = this.#runAttempt(command, {
1590
+ role: active.role,
1591
+ title: active.title,
1592
+ prompt,
1593
+ maxTokensPerInteraction: active.maxTokensPerInteraction,
1594
+ planningInputDigest: active.planningInputDigest,
1595
+ attestationDigest: "follow-up",
1596
+ repository: active.repository,
1597
+ baseCommitSha: active.baseCommitSha,
1598
+ sourceTreeDigest: active.sourceTreeDigest
1599
+ }, active).catch(async (cause) => {
1600
+ const detail = runtimeErrorMessage(cause);
1601
+ await this.#event(
1602
+ command,
1603
+ { type: "message", actor: "system", body: `Pi failed: ${detail}` },
1604
+ active.conversationRefs
1605
+ ).catch(() => void 0);
1606
+ await this.#diagnostic(command, active, detail);
1607
+ await this.#event(command, { type: "status", status: "failed" }, active.conversationRefs).catch(() => void 0);
1608
+ await this.#failure(command, active, detail);
1609
+ return null;
1610
+ });
1611
+ return { status: "running", message: "Pi accepted the owner prompt" };
1612
+ }
1613
+ async #runAttempt(command, metadata, active) {
1614
+ const lease = fakeCodeLease(command, metadata);
1615
+ const broker = createCodeRuntimeToolBroker({
1616
+ recipes: this.options.recipes,
1617
+ engine: this.options.engine,
1618
+ recipeAuthorization: this.options.recipeAuthorization
1619
+ }, lease, metadata.role);
1620
+ const startedAt = Date.now();
1621
+ let completionSeen = false;
1622
+ const interaction = { tokens: 0, noticeEmitted: false };
1623
+ const result = await this.#run({
1624
+ engine: this.options.engine,
1625
+ image: this.options.image,
1626
+ allowUnpinnedImage: this.options.imageAuthorization === "cli_embedded",
1627
+ workspaceDir: active.workspace.workspaceDir,
1628
+ workspaceAccess: "none",
1629
+ task: lease.task,
1630
+ limits: this.options.limits,
1631
+ signal: active.abort.signal,
1632
+ onStderr: (text) => this.#event(command, {
1633
+ type: "message",
1634
+ actor: "system",
1635
+ body: text.slice(0, 4e3)
1636
+ }, active.conversationRefs),
1637
+ onMessage: async (output) => {
1638
+ if (output.type === "inference.request") {
1639
+ return handleCodeRuntimeInference({
1640
+ command,
1641
+ metadata,
1642
+ request: output,
1643
+ state: interaction,
1644
+ control: this.options.control,
1645
+ event: (event) => this.#event(
1646
+ command,
1647
+ event,
1648
+ active.conversationRefs
1649
+ )
1650
+ });
1651
+ }
1652
+ if (output.type === "tool.request") {
1653
+ const toolStarted = Date.now();
1654
+ await this.#event(
1655
+ command,
1656
+ { type: "tool", phase: "started", tool: output.tool },
1657
+ active.conversationRefs
1658
+ ).catch(() => void 0);
1659
+ const response2 = await broker.execute({
1660
+ lease,
1661
+ workspaceDir: active.workspace.workspaceDir,
1662
+ signal: active.abort.signal
1663
+ }, output);
1664
+ await this.#event(command, {
1665
+ type: "tool",
1666
+ phase: "completed",
1667
+ tool: output.tool,
1668
+ ok: response2.ok,
1669
+ durationMs: Date.now() - toolStarted
1670
+ }, active.conversationRefs).catch(() => void 0);
1671
+ return { protocolVersion: HARNESS_PROTOCOL_VERSION, type: "tool.response", ...response2 };
1672
+ }
1673
+ if (output.type === "event") {
1674
+ const payload = runtimeRecord(output.payload);
1675
+ if (output.kind === "pi.started") {
1676
+ await this.#event(command, { type: "status", status: "running" }, active.conversationRefs);
1677
+ } else if (output.kind === "pi.thinking" && payload?.available === true && Number.isSafeInteger(payload.durationMs) && Number(payload.durationMs) >= 0) {
1678
+ await this.#event(command, {
1679
+ type: "thinking",
1680
+ available: true,
1681
+ durationMs: Math.min(Number(payload.durationMs), 864e5)
1682
+ }, active.conversationRefs);
1683
+ } else {
1684
+ await this.#event(command, {
1685
+ type: "message",
1686
+ actor: "system",
1687
+ body: `${output.kind}${output.payload === void 0 ? "" : ` ${safeRuntimeJson(output.payload)}`}`
1688
+ }, active.conversationRefs);
1689
+ }
1690
+ } else if (output.type === "attempt.complete") {
1691
+ completionSeen = true;
1692
+ const body = runtimeResultText(output.result) ?? `Pi ${output.status}.`;
1693
+ await this.#event(command, {
1694
+ type: "message",
1695
+ actor: output.status === "completed" ? "agent" : "system",
1696
+ body
1697
+ }, active.conversationRefs);
1698
+ await this.#event(command, {
1699
+ type: "status",
1700
+ status: output.status === "completed" ? "idle" : "failed",
1701
+ durationMs: Date.now() - startedAt
1702
+ }, active.conversationRefs);
1703
+ }
1704
+ }
1705
+ });
1706
+ if (result.status === "failed" && result.stderr) {
1707
+ await this.#event(command, { type: "message", actor: "system", body: result.stderr.slice(0, 4e3) }, active.conversationRefs);
1708
+ }
1709
+ if (!completionSeen) await this.#event(command, {
1710
+ type: "status",
1711
+ status: result.status === "completed" ? "idle" : "failed",
1712
+ durationMs: Date.now() - startedAt
1713
+ }, active.conversationRefs).catch(() => void 0);
1714
+ if (result.status === "failed") {
1715
+ const detail = (runtimeResultError(result.result) ?? result.stderr.trim()) || "Pi container failed";
1716
+ await this.#diagnostic(command, active, detail);
1717
+ await this.#failure(command, active, detail);
1718
+ }
1719
+ return result;
1720
+ }
1721
+ async #checkpoint(command) {
1722
+ const active = this.#active.get(command.sessionId);
1723
+ if (!active) throw new TypeError("Code session workspace is not active on this runtime");
1724
+ const result = await this.#checkpoints.prepare(command, active);
1725
+ this.#active.delete(command.sessionId);
1726
+ return result;
1727
+ }
1728
+ async #failure(command, active, value) {
1729
+ active.failure = value.slice(0, 2e3);
1730
+ if (active.acknowledged) {
1731
+ await this.options.control.reportSessionFailure(command.sessionId, active.failure).catch(() => void 0);
1732
+ }
1733
+ }
1734
+ async #diagnostic(command, active, value) {
1735
+ const detail = value.trim().slice(0, 2e3) || "Pi runtime failed";
1736
+ this.options.onDiagnostic?.(detail);
1737
+ await this.#event(
1738
+ command,
1739
+ { type: "diagnostic", level: "error", message: detail },
1740
+ active.conversationRefs
1741
+ ).catch(() => void 0);
1742
+ }
1743
+ async #event(command, event, refs) {
1744
+ await appendCodeRuntimeEvent(this.options.control, command, event, refs);
1745
+ }
1746
+ };
1747
+
1748
+ export {
1749
+ digestStagedWorkspace,
1750
+ CodeRuntimeControlError,
1751
+ createCodeRuntimeControlClient,
1752
+ CODE_RUNTIME_PROTOCOL_VERSION,
1753
+ runCodeRuntimeHeartbeatLoop,
1754
+ CodeRuntimeReconciler,
1755
+ createCodeWorkspaceCheckpoint,
1756
+ restoreCodeWorkspaceCheckpoint,
1757
+ isCheckpointEffectCompleted,
1758
+ buildRecipeContainerArgs,
1759
+ createContainerRecipeExecutor,
1760
+ assertCodeBuildRecipe,
1761
+ verifyCodeCandidate,
1762
+ prepareRuntimeCheckpoint,
1763
+ CodeRuntimeCheckpointManager,
1764
+ materializeCodeRuntimeSource,
1765
+ attachCodeRuntimeReferences,
1766
+ createCodeToolBroker,
1767
+ CodePiRuntimeEngine
1768
+ };
1769
+ //# sourceMappingURL=chunk-GMVZ4LZH.js.map