@canonmsg/agent-sdk 12.5.1 → 12.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +34 -3
  2. package/dist/chunks/{chunk-TFSURFF6.js → chunk-5J4YPLNW.js} +349 -1684
  3. package/dist/chunks/{chunk-3KFVBOVI.js → chunk-DAJ7L7TN.js} +751 -371
  4. package/dist/chunks/chunk-FVYKLH6I.js +1475 -0
  5. package/dist/chunks/chunk-HH7KR22P.js +406 -0
  6. package/dist/chunks/chunk-QK37HAHC.js +118 -0
  7. package/dist/chunks/{chunk-C2WJT3VX.js → chunk-S7BV74DN.js} +719 -813
  8. package/dist/chunks/{mls-crypto-Y44BOSEM.js → mls-crypto-4DZT2UFH.js} +2 -1
  9. package/dist/cli.js +67 -25
  10. package/dist/drivers/acp.d.ts +56 -0
  11. package/dist/drivers/codex.d.ts +1 -2
  12. package/dist/drivers/subprocess.d.ts +35 -0
  13. package/dist/index.d.ts +3 -1
  14. package/dist/index.js +12 -3
  15. package/dist/internal/backend-contracts/endpointContentPolicy.d.ts +2 -2
  16. package/dist/internal/backend-contracts/index.d.ts +1 -0
  17. package/dist/internal/backend-contracts/loggedOutSessions.d.ts +7 -0
  18. package/dist/internal/backend-contracts/mlsDevCredential.d.ts +3 -0
  19. package/dist/internal/endpoint/engine.d.ts +3 -0
  20. package/dist/internal/endpoint/mls-dev-client.d.ts +2 -0
  21. package/dist/internal/endpoint/mls-dev-text.d.ts +3 -0
  22. package/dist/internal/endpoint/node-mls-custody-protocol.d.ts +5 -0
  23. package/dist/internal/endpoint/node.d.ts +1 -0
  24. package/dist/media.js +4 -2
  25. package/dist/mls/darwin-arm64/canon-mls-custody +0 -0
  26. package/dist/mls/linux-arm64/canon-mls-custody +0 -0
  27. package/dist/mls/linux-x64/canon-mls-custody +0 -0
  28. package/dist/mls/wasm/canon_mls_wasm.js +363 -0
  29. package/dist/mls/wasm/canon_mls_wasm_bg.wasm +0 -0
  30. package/dist/mls-wasm-host.js +258 -0
  31. package/dist/runner/mls-bundled.d.ts +15 -2
  32. package/dist/runner/mls-managed.d.ts +6 -3
  33. package/dist/runner/types.d.ts +6 -1
  34. package/package.json +1 -1
@@ -1,9 +1,19 @@
1
+ import {
2
+ assertPrivateHome,
3
+ assertPrivateWindowsFile,
4
+ privateKey,
5
+ restrictWindowsFile,
6
+ runnerLockName,
7
+ secureWindowsDirectory,
8
+ wasmMlsCustody,
9
+ windowsAclProblems,
10
+ writeDurable
11
+ } from "./chunk-HH7KR22P.js";
1
12
  import {
2
13
  ApprovalManager,
3
14
  BoundedFileReadError,
4
15
  CanonClient,
5
16
  MAX_CANON_MEDIA_BYTES,
6
- NodeMlsDevCustodyError,
7
17
  RUNTIME_NEW_SESSION_ACTION,
8
18
  RUNTIME_STOP_ACTION,
9
19
  RUNTIME_STOP_AND_DROP_ACTION,
@@ -12,8 +22,6 @@ import {
12
22
  buildChunkedMessagePartMetadata,
13
23
  conversationMediaCacheDir,
14
24
  createCompletedOutputJournal,
15
- createNodeMlsDevCustody,
16
- createPortableNodeMlsDevCustody,
17
25
  createRuntimeHeartbeat,
18
26
  createRuntimeStatePublisher,
19
27
  inferUploadMimeType,
@@ -30,7 +38,15 @@ import {
30
38
  sendMessageWithRetryChunked,
31
39
  splitTextByUtf8Bytes,
32
40
  verifyCanonRuntimeConnection
33
- } from "./chunk-TFSURFF6.js";
41
+ } from "./chunk-5J4YPLNW.js";
42
+ import {
43
+ NodeMlsDevCustodyError,
44
+ createNodeMlsDevCustody,
45
+ createPortableNodeMlsDevCustody
46
+ } from "./chunk-FVYKLH6I.js";
47
+ import {
48
+ parseMlsDevClientConfiguration
49
+ } from "./chunk-QK37HAHC.js";
34
50
  import {
35
51
  CONNECTOR_RELAY_ANSWER_WITHIN_MS,
36
52
  CONNECTOR_RELAY_INSTALLATION_PATTERN,
@@ -54,12 +70,11 @@ import {
54
70
  mlsDevProtocolCarriesCards,
55
71
  parseConnectorRelayRequest,
56
72
  parseDirectConversationSelection,
57
- parseMlsDevClientConfiguration,
58
73
  parseMlsDevCredentialToken,
59
74
  parseMlsDevGroupHead,
60
75
  parseMlsDevGroupRoster,
61
76
  resolveAllowedUploadMimeType
62
- } from "./chunk-C2WJT3VX.js";
77
+ } from "./chunk-S7BV74DN.js";
63
78
 
64
79
  // src/runner/types.ts
65
80
  var DriverSessionError = class extends Error {
@@ -106,11 +121,13 @@ async function loadInstructions(path) {
106
121
  if (real !== path) refuse(`must be given by its real path (${real}), with no symbolic links`);
107
122
  const parent = await stat(dirname(path)).catch(() => refuse("cannot be read"));
108
123
  const uid = process.getuid?.();
109
- if (parent.uid !== uid && parent.uid !== 0 || (parent.mode & 18) !== 0) refuse("its directory must not be writable by group or others");
124
+ const windows = process.platform === "win32" ? windowsAclProblems([[dirname(path), "unwritable"], [path, "unwritable"]]) : void 0;
125
+ if (windows?.length) refuse(`must not be writable by others: ${windows[0]}`);
126
+ if (!windows && (parent.uid !== uid && parent.uid !== 0 || (parent.mode & 18) !== 0)) refuse("its directory must not be writable by group or others");
110
127
  const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK).catch(() => refuse("cannot be read"));
111
128
  try {
112
129
  const info = await file.stat();
113
- if (info.uid !== uid || (info.mode & 18) !== 0) refuse("must be owned by you and not writable by group or others");
130
+ if (!windows && (info.uid !== uid || (info.mode & 18) !== 0)) refuse("must be owned by you and not writable by group or others");
114
131
  const bytes = await readBoundedRegularFileHandle(file, MAX_INSTRUCTIONS_BYTES).catch((error) => refuse(error instanceof BoundedFileReadError ? error.failure === "too-large" ? "is larger than 32 KiB" : "is not a regular file" : "cannot be read"));
115
132
  let text3;
116
133
  try {
@@ -126,11 +143,13 @@ async function loadInstructions(path) {
126
143
  }
127
144
  __name(loadInstructions, "loadInstructions");
128
145
 
129
- // src/drivers/claude.ts
130
- import { randomUUID } from "node:crypto";
146
+ // src/drivers/acp.ts
147
+ import { spawn as spawn2 } from "node:child_process";
148
+ import { randomBytes } from "node:crypto";
131
149
  import { realpathSync } from "node:fs";
132
150
  import { tmpdir } from "node:os";
133
- import { basename, dirname as dirname2 } from "node:path";
151
+ import { basename } from "node:path";
152
+ import { pathToFileURL } from "node:url";
134
153
 
135
154
  // src/drivers/call-scope.ts
136
155
  import { createHash } from "node:crypto";
@@ -141,17 +160,464 @@ function callScope(name, call) {
141
160
  }
142
161
  __name(callScope, "callScope");
143
162
 
163
+ // src/drivers/installed-cli.ts
164
+ import { spawn } from "node:child_process";
165
+ import { existsSync, readFileSync } from "node:fs";
166
+ import { dirname as dirname2, extname, join } from "node:path";
167
+ function shimTarget(shim) {
168
+ const last = [...shim.matchAll(/"([^"]+)"\s+%\*/g)].at(-1)?.[1];
169
+ const variable = /^%(\w+)%$/.exec(last ?? "")?.[1];
170
+ const target = variable ? new RegExp(`SET "${variable}=([^"]+)"`, "i").exec(shim)?.[1] : last;
171
+ return /^%~?dp0%?\\(.+)$/.exec(target ?? "")?.[1];
172
+ }
173
+ __name(shimTarget, "shimTarget");
174
+ function installedCli(path) {
175
+ const given = { file: path, args: [] };
176
+ if (process.platform !== "win32") return given;
177
+ const names = extname(path) ? [path] : [`${path}.exe`, `${path}.cmd`];
178
+ const directories = /[\\/]/.test(path) ? [""] : (process.env.PATH ?? "").split(";").filter(Boolean);
179
+ for (const directory of directories) {
180
+ for (const name of names) {
181
+ const file = join(directory, name);
182
+ if (!existsSync(file)) continue;
183
+ if (!/\.(?:cmd|bat)$/i.test(file)) return { file, args: [] };
184
+ const target = shimTarget(readFileSync(file, "utf8"));
185
+ if (!target) throw new Error(`${file} is not an npm shim: give the path of the program it runs.`);
186
+ const script = join(dirname2(file), target);
187
+ return /\.[cm]?js$/i.test(script) ? { file: process.execPath, args: [script] } : { file: script, args: [] };
188
+ }
189
+ }
190
+ return given;
191
+ }
192
+ __name(installedCli, "installedCli");
193
+ function stopCli(child) {
194
+ if (process.platform !== "win32") {
195
+ child.kill("SIGTERM");
196
+ return;
197
+ }
198
+ if (child.pid === void 0 || child.exitCode !== null || child.signalCode !== null) return;
199
+ spawn(
200
+ join(process.env.SystemRoot ?? "C:\\Windows", "System32", "taskkill.exe"),
201
+ ["/PID", String(child.pid), "/T", "/F"],
202
+ { stdio: "ignore", windowsHide: true }
203
+ ).once("error", () => {
204
+ child.kill();
205
+ });
206
+ }
207
+ __name(stopCli, "stopCli");
208
+
209
+ // src/drivers/subprocess.ts
210
+ import { createHash as createHash2 } from "node:crypto";
211
+ import { createInterface } from "node:readline";
212
+ var isRecord = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
213
+ var text = /* @__PURE__ */ __name((value) => typeof value === "string" ? value : "", "text");
214
+ var oneLine = /* @__PURE__ */ __name((value, max) => {
215
+ const line = value.replace(/\s+/g, " ").trim();
216
+ return line.length > max ? `${line.slice(0, max - 1)}\u2026` : line;
217
+ }, "oneLine");
218
+ var RISKY = /\brm\s+-[a-z]*[rf]|\bgit\s+(reset\s+--hard|push\s+(-f|--force))|\bdrop\s+(table|database)\b|\bmkfs\b|\bdd\s+if=/i;
219
+ var guestTurn = /* @__PURE__ */ __name((turn) => !turn.input.messages.every((message2) => message2.isOwner || message2.trustedAgent === true), "guestTurn");
220
+ async function approve(turn, request) {
221
+ const asked = createHash2("sha256").update(JSON.stringify([request.scopeKey, request.title, request.detail])).digest("hex").slice(0, 12);
222
+ const answer = await turn.ask({ kind: "approve", ...request, id: `${request.id}#${asked}` }).catch(() => void 0);
223
+ if (!answer) return "stopped";
224
+ if (answer.status !== "answered" || !answer.allow) return "denied";
225
+ return answer.scope === "session" ? "session" : "once";
226
+ }
227
+ __name(approve, "approve");
228
+ function stdioRpc(child, handlers, { name, envelope }) {
229
+ let next = 0;
230
+ const pending = /* @__PURE__ */ new Map();
231
+ const write = /* @__PURE__ */ __name((message2) => {
232
+ if (child.stdin?.writable) child.stdin.write(`${JSON.stringify({ ...envelope, ...message2 })}
233
+ `);
234
+ }, "write");
235
+ child.stdin?.on("error", () => void 0);
236
+ createInterface({ input: child.stdout }).on("error", () => void 0).on("line", (line) => {
237
+ let message2;
238
+ try {
239
+ message2 = JSON.parse(line);
240
+ } catch {
241
+ return;
242
+ }
243
+ if (!isRecord(message2)) return;
244
+ const params = isRecord(message2.params) ? message2.params : {};
245
+ if (typeof message2.method !== "string") {
246
+ const waiting = pending.get(Number(message2.id));
247
+ pending.delete(Number(message2.id));
248
+ if (isRecord(message2.error)) waiting?.reject(Object.assign(new Error(text(message2.error.message) || `${name} refused the request`), { code: message2.error.code }));
249
+ else waiting?.resolve(message2.result);
250
+ } else if (message2.id !== void 0) {
251
+ handlers.request(message2.method, params, message2.id).then(
252
+ (result) => write({ id: message2.id, result }),
253
+ (error) => write({ id: message2.id, error: {
254
+ code: typeof error?.code === "number" ? error.code : -32e3,
255
+ message: error instanceof Error ? error.message : String(error)
256
+ } })
257
+ );
258
+ } else handlers.notify(message2.method, params);
259
+ });
260
+ child.stderr?.resume();
261
+ return {
262
+ call: /* @__PURE__ */ __name((method, params) => new Promise((resolve3, reject) => {
263
+ next += 1;
264
+ pending.set(next, { resolve: /* @__PURE__ */ __name((value) => resolve3(isRecord(value) ? value : {}), "resolve"), reject });
265
+ write({ id: next, method, params });
266
+ }), "call"),
267
+ notify: /* @__PURE__ */ __name((method, params) => write({ method, ...params ? { params } : {} }), "notify"),
268
+ fail(error) {
269
+ for (const waiting of pending.values()) waiting.reject(error);
270
+ pending.clear();
271
+ }
272
+ };
273
+ }
274
+ __name(stdioRpc, "stdioRpc");
275
+
276
+ // src/drivers/acp.ts
277
+ var PROTOCOL = 1;
278
+ var SESSION = "sessionId";
279
+ var OWNER_MODE = "ownerMode";
280
+ var CANON = "canon";
281
+ var METHOD_NOT_FOUND = -32601;
282
+ var AUTH_REQUIRED = -32e3;
283
+ var SYSTEM = "You are an agent in Canon, a messaging app where people and AI agents talk. Each prompt holds the new messages in one conversation. Your final message in a turn is posted there as your reply, so put your whole answer in it. Canon's tools are on the MCP server whose name starts with canon_. To ask the people in the conversation a question, call its ask_question tool and wait for the answer. When you have nothing to add, call its no_reply tool instead of answering.";
284
+ var GUEST = "Only your owner can let you use tools in this conversation, and this turn holds a message from someone else: do not read files, run commands or use any tool but those of the MCP server whose name starts with canon_. Answer from the conversation and what you already know, and say so when something needs your owner.";
285
+ var OWNER_ONLY = "This agent only takes messages from its owner.";
286
+ var PIPE_GRACE_MS = 1e3;
287
+ var KILL_AFTER_MS = 2e3;
288
+ var MACHINE_KINDS = /* @__PURE__ */ new Set(["read", "edit", "delete", "move", "search", "execute", "fetch"]);
289
+ var BRIDGE = `
290
+ const { request } = require('node:http');
291
+ const url = new URL(process.env.TURN_TOOLS_URL);
292
+ const headers = { 'content-type': 'application/json', accept: 'application/json, text/event-stream' };
293
+ require('node:readline').createInterface({ input: process.stdin }).on('line', (line) => {
294
+ const post = request(url, { method: 'POST', headers }, (response) => {
295
+ let body = '';
296
+ response.setEncoding('utf8').on('data', (chunk) => { body += chunk; });
297
+ response.on('end', () => { if (body.trim()) process.stdout.write(body.trim() + '\\n'); });
298
+ });
299
+ post.on('error', () => undefined);
300
+ post.end(line);
301
+ });`;
302
+ function startAgent(command, args, cwd) {
303
+ const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith("CANON_")));
304
+ const cli = installedCli(command);
305
+ return spawn2(cli.file, [...cli.args, ...args], { cwd, env, stdio: ["pipe", "pipe", "pipe"], detached: process.platform !== "win32", windowsHide: true });
306
+ }
307
+ __name(startAgent, "startAgent");
308
+ function signalAgent(child, signal) {
309
+ if (process.platform === "win32") {
310
+ stopCli(child);
311
+ return;
312
+ }
313
+ try {
314
+ if (child.pid) process.kill(-child.pid, signal);
315
+ else child.kill(signal);
316
+ } catch {
317
+ }
318
+ }
319
+ __name(signalAgent, "signalAgent");
320
+ var refuseMethod = /* @__PURE__ */ __name((method) => Promise.reject(Object.assign(new Error(`Canon does not offer ${method}`), { code: METHOD_NOT_FOUND })), "refuseMethod");
321
+ var restoreMethod = /* @__PURE__ */ __name((capabilities) => {
322
+ if (isRecord(capabilities.sessionCapabilities) && isRecord(capabilities.sessionCapabilities.resume)) return "session/resume";
323
+ return capabilities.loadSession === true ? "session/load" : void 0;
324
+ }, "restoreMethod");
325
+ async function initialize(link, agent) {
326
+ const result = await link.call("initialize", {
327
+ protocolVersion: PROTOCOL,
328
+ clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false },
329
+ clientInfo: { name: "canon-agent", title: "Canon", version: "1" }
330
+ });
331
+ if (result.protocolVersion !== PROTOCOL) {
332
+ throw new Error(`${agent} speaks Agent Client Protocol version ${String(result.protocolVersion)}; the acp driver speaks version ${PROTOCOL}`);
333
+ }
334
+ return { capabilities: isRecord(result.agentCapabilities) ? result.agentCapabilities : {}, info: isRecord(result.agentInfo) ? result.agentInfo : {} };
335
+ }
336
+ __name(initialize, "initialize");
337
+ async function checkAcp({ command, args = [], cwd, timeoutMs = 3e4 }) {
338
+ const child = startAgent(command, args, realpathSync(cwd));
339
+ const link = stdioRpc(child, { notify: /* @__PURE__ */ __name(() => void 0, "notify"), request: /* @__PURE__ */ __name((method) => refuseMethod(method), "request") }, { name: basename(command), envelope: { jsonrpc: "2.0" } });
340
+ let timer;
341
+ try {
342
+ return await new Promise((resolve3, reject) => {
343
+ timer = setTimeout(() => reject(new Error(`${command} did not answer the Agent Client Protocol's initialize within ${timeoutMs / 1e3} s`)), timeoutMs);
344
+ child.once("error", (error) => reject(new Error(`Could not run ${command}: ${error.message}`)));
345
+ child.once("close", () => reject(new Error(`${command} exited before answering the Agent Client Protocol's initialize`)));
346
+ initialize(link, command).then(({ capabilities, info }) => resolve3({
347
+ name: text(info.title) || text(info.name) || basename(command),
348
+ version: text(info.version),
349
+ sessionMemory: restoreMethod(capabilities) ? "native" : "none"
350
+ }), reject);
351
+ });
352
+ } finally {
353
+ clearTimeout(timer);
354
+ child.stdin?.end();
355
+ signalAgent(child, "SIGTERM");
356
+ setTimeout(() => signalAgent(child, "SIGKILL"), KILL_AFTER_MS).unref();
357
+ }
358
+ }
359
+ __name(checkAcp, "checkAcp");
360
+ var present = /* @__PURE__ */ __name((fields) => Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== null && value !== void 0)), "present");
361
+ var toolIdentity = /* @__PURE__ */ __name((call) => text(call.name) || text(call.title), "toolIdentity");
362
+ function canonTool(call, turn, server, guest) {
363
+ if (!["", "other"].includes(text(call.kind)) || guest && !text(call.name)) return false;
364
+ const name = toolIdentity(call).replace(/\W+/g, "_").replace(/^mcp_+/, "");
365
+ return turn.tools.definitions.some((tool) => name === `${server}_${tool.name}` || name === `${server}__${tool.name}`);
366
+ }
367
+ __name(canonTool, "canonTool");
368
+ function acpDriver(options) {
369
+ const { command, args = [] } = options;
370
+ if (options.guestMode !== void 0 && (options.unguardedGuests || !options.guestMode.trim())) {
371
+ throw new Error("guestMode must name a mode, and cannot go with unguardedGuests");
372
+ }
373
+ const cwd = realpathSync(options.cwd);
374
+ const agent = basename(command);
375
+ const interruptGraceMs = options.interruptGraceMs ?? 5e3;
376
+ const framing = withInstructions(SYSTEM, options.instructions);
377
+ const running = /* @__PURE__ */ new Map();
378
+ const end = /* @__PURE__ */ __name(async (session) => {
379
+ const live = running.get(session);
380
+ if (!live) return;
381
+ live.close();
382
+ await live.exited;
383
+ }, "end");
384
+ return {
385
+ name: "acp",
386
+ capabilities: {
387
+ sessionMemory: options.sessionMemory ?? "none",
388
+ ask: ["approve", "choose", "text"],
389
+ streaming: true,
390
+ activity: true,
391
+ tools: true,
392
+ outboundFiles: { roots: [cwd, realpathSync(tmpdir())] },
393
+ ownerHistoryOnly: options.guestMode === void 0 && !options.unguardedGuests
394
+ },
395
+ async runTurn(turn) {
396
+ await running.get(turn.session)?.exited;
397
+ if (turn.signal.aborted) throw turn.signal.reason;
398
+ const guest = guestTurn(turn);
399
+ if (guest && options.guestMode === void 0 && !options.unguardedGuests) return { reply: OWNER_ONLY };
400
+ const child = startAgent(command, args, cwd);
401
+ let exit = /* @__PURE__ */ __name(() => void 0, "exit");
402
+ const exited = new Promise((resolve3) => {
403
+ exit = resolve3;
404
+ });
405
+ let killing;
406
+ let stopping = false;
407
+ const entry = { close: /* @__PURE__ */ __name(() => {
408
+ stopping = true;
409
+ signalAgent(child, "SIGTERM");
410
+ killing ??= setTimeout(() => signalAgent(child, "SIGKILL"), KILL_AFTER_MS);
411
+ }, "close"), exited };
412
+ running.set(turn.session, entry);
413
+ const server = `${CANON}_${randomBytes(4).toString("hex")}`;
414
+ return new Promise((resolve3, reject) => {
415
+ const messages = [];
416
+ const calls = /* @__PURE__ */ new Map();
417
+ let sessionId = "";
418
+ let answering = false;
419
+ let continues = false;
420
+ let closed = false;
421
+ let ended = Promise.resolve();
422
+ let closing;
423
+ const visible = /* @__PURE__ */ __name(() => messages.map((message2) => message2.text).filter((part) => part.trim()).join("\n\n"), "visible");
424
+ const closeLater = /* @__PURE__ */ __name(() => {
425
+ if (!closed) closing ??= setTimeout(entry.close, interruptGraceMs);
426
+ }, "closeLater");
427
+ const finish = /* @__PURE__ */ __name(() => {
428
+ if (!closed) child.stdin?.end();
429
+ closeLater();
430
+ }, "finish");
431
+ const interrupt = /* @__PURE__ */ __name(() => {
432
+ if (sessionId && answering) link.notify("session/cancel", { sessionId });
433
+ closeLater();
434
+ }, "interrupt");
435
+ const settle = /* @__PURE__ */ __name((outcome) => {
436
+ answering = false;
437
+ finish();
438
+ if (turn.signal.aborted) reject(turn.signal.reason);
439
+ else if (outcome instanceof Error) reject(outcome);
440
+ else resolve3(outcome);
441
+ }, "settle");
442
+ const stopGuest = /* @__PURE__ */ __name((what) => {
443
+ link.notify("session/cancel", { sessionId });
444
+ entry.close();
445
+ settle(new DriverSessionError(`${agent} ${what} in a turn holding someone other than the owner's message`));
446
+ }, "stopGuest");
447
+ const guard = /* @__PURE__ */ __name((call) => {
448
+ if (guest && MACHINE_KINDS.has(text(call.kind)) && ["in_progress", "completed"].includes(text(call.status))) stopGuest(`ran a ${text(call.kind)} tool`);
449
+ }, "guard");
450
+ const known = /* @__PURE__ */ __name((update2, session) => {
451
+ const key = JSON.stringify([text(session), text(update2.toolCallId)]);
452
+ const call = { ...calls.get(key), ...present(update2) };
453
+ calls.set(key, call);
454
+ return call;
455
+ }, "known");
456
+ const update = /* @__PURE__ */ __name((params) => {
457
+ const change = isRecord(params.update) ? params.update : {};
458
+ if (!answering) return;
459
+ const tool = change.sessionUpdate === "tool_call" || change.sessionUpdate === "tool_call_update";
460
+ const call = tool ? known(change, params.sessionId) : void 0;
461
+ if (call) guard(call);
462
+ if (guest && options.guestMode !== void 0 && change.sessionUpdate === "current_mode_update" && change.currentModeId !== options.guestMode) stopGuest("left its guest mode");
463
+ if (text(params.sessionId) !== sessionId) return;
464
+ if (change.sessionUpdate === "agent_message_chunk") {
465
+ const chunk = isRecord(change.content) && change.content.type === "text" ? text(change.content.text) : "";
466
+ if (!chunk) return;
467
+ const id = text(change.messageId);
468
+ const message2 = id ? messages.find((known2) => known2.id === id) : continues ? messages.at(-1) : void 0;
469
+ if (message2) message2.text += chunk;
470
+ else messages.push({ id, text: chunk });
471
+ continues = true;
472
+ turn.onText(visible());
473
+ } else if (call) {
474
+ if (change.sessionUpdate === "tool_call") continues = false;
475
+ turn.onActivity({
476
+ id: text(call.toolCallId),
477
+ kind: "tool",
478
+ title: oneLine(text(call.title) || "Use a tool", 120),
479
+ state: call.status === "completed" ? "done" : call.status === "failed" ? "failed" : "running"
480
+ });
481
+ }
482
+ }, "update");
483
+ const permission = /* @__PURE__ */ __name(async (params) => {
484
+ const offered = (Array.isArray(params.options) ? params.options : []).filter(isRecord);
485
+ const pick = /* @__PURE__ */ __name((kind) => offered.find((option) => option.kind === kind), "pick");
486
+ const select = /* @__PURE__ */ __name((option) => ({ outcome: option ? { outcome: "selected", optionId: text(option.optionId) } : { outcome: "cancelled" } }), "select");
487
+ if (turn.signal.aborted) return select(void 0);
488
+ const allow = pick("allow_once");
489
+ const refuse = select(pick("reject_once") ?? pick("reject_always"));
490
+ const call = known(isRecord(params.toolCall) ? params.toolCall : {}, params.sessionId);
491
+ if (allow && canonTool(call, turn, server, guest)) return select(allow);
492
+ if (guest || !allow) return refuse;
493
+ const input = call.rawInput === void 0 || ["{}", "[]", '""', "null"].includes(JSON.stringify(call.rawInput)) ? "" : JSON.stringify(call.rawInput);
494
+ const title = oneLine(text(call.title) || "Use a tool", 200);
495
+ const paths = (Array.isArray(call.locations) ? call.locations : []).filter(isRecord).map((location) => text(location.path));
496
+ const edits = (Array.isArray(call.content) ? call.content : []).filter(isRecord).filter((item) => item.type === "diff").map((item) => `${text(item.path)}
497
+ ${text(item.newText)}`);
498
+ const id = `${text(params.sessionId) === sessionId ? "" : `${text(params.sessionId)}:`}${text(call.toolCallId) || "tool"}`;
499
+ const verdict = await approve(turn, {
500
+ id,
501
+ title,
502
+ detail: [input, ...paths, ...edits].filter(Boolean).join("\n"),
503
+ scopeKey: callScope(text(call.kind) || "tool", [toolIdentity(call) || title, input, call.locations ?? null]),
504
+ allowSession: input !== "" && text(call.name) !== "",
505
+ // JSON punctuation goes first, so a command given as an argument list reads as one.
506
+ ...call.kind === "delete" || RISKY.test(input.replace(/[",[\]{}]+/g, " ")) ? { risk: "destructive" } : {}
507
+ });
508
+ if (verdict === "stopped" || turn.signal.aborted) return select(void 0);
509
+ return verdict === "denied" ? refuse : select(allow);
510
+ }, "permission");
511
+ const link = stdioRpc(child, {
512
+ notify: /* @__PURE__ */ __name((method, params) => {
513
+ if (method === "session/update") update(params);
514
+ }, "notify"),
515
+ request: /* @__PURE__ */ __name((method, params) => method === "session/request_permission" ? permission(params) : refuseMethod(method), "request")
516
+ }, { name: agent, envelope: { jsonrpc: "2.0" } });
517
+ const gone = /* @__PURE__ */ __name((error) => {
518
+ if (closed) return;
519
+ closed = true;
520
+ clearTimeout(closing);
521
+ clearTimeout(killing);
522
+ turn.signal.removeEventListener("abort", interrupt);
523
+ link.fail(error);
524
+ settle(error);
525
+ void ended.then(() => {
526
+ if (running.get(turn.session) === entry) running.delete(turn.session);
527
+ exit();
528
+ });
529
+ }, "gone");
530
+ child.once("exit", () => {
531
+ if (stopping || guest || turn.signal.aborted) {
532
+ signalAgent(child, "SIGTERM");
533
+ ended = new Promise((resolve4) => {
534
+ setTimeout(() => {
535
+ signalAgent(child, "SIGKILL");
536
+ resolve4();
537
+ }, KILL_AFTER_MS);
538
+ });
539
+ }
540
+ setTimeout(() => {
541
+ child.stdout?.destroy();
542
+ child.stderr?.destroy();
543
+ }, PIPE_GRACE_MS).unref();
544
+ });
545
+ child.once("error", (error) => gone(new Error(`Could not run ${command}: ${error.message}`)));
546
+ child.once("close", () => gone(new Error(`${agent} exited before finishing the turn`)));
547
+ turn.signal.addEventListener("abort", interrupt, { once: true });
548
+ const signInError = /* @__PURE__ */ __name((error) => error.code === AUTH_REQUIRED && !turn.signal.aborted ? new Error(`${agent} needs signing in: run it once yourself, then start this agent again`) : void 0, "signInError");
549
+ async function openSession(capabilities) {
550
+ const mcp = isRecord(capabilities.mcpCapabilities) ? capabilities.mcpCapabilities : {};
551
+ const { url } = turn.tools;
552
+ const mcpServers = !url ? [] : mcp.http === true ? [{ type: "http", name: server, url, headers: [] }] : [{ name: server, command: process.execPath, args: ["-e", BRIDGE], env: [{ name: "TURN_TOOLS_URL", value: url }] }];
553
+ const restore = options.sessionMemory === "native" ? restoreMethod(capabilities) : void 0;
554
+ const saved = restore && turn.state.get(SESSION);
555
+ if (saved) {
556
+ const restored = await link.call(restore, { sessionId: saved, cwd, mcpServers }).catch((error) => {
557
+ throw signInError(error) ?? (turn.signal.aborted ? error : new DriverSessionError(`${agent} could not restore session ${saved}`, { cause: error }));
558
+ });
559
+ return { id: saved, fresh: false, keep: false, modes: restored.modes };
560
+ }
561
+ const opened = await link.call("session/new", { cwd, mcpServers }).catch((error) => {
562
+ throw signInError(error) ?? error;
563
+ });
564
+ if (!text(opened.sessionId)) throw new Error(`${agent} opened no session`);
565
+ return { id: text(opened.sessionId), fresh: true, keep: Boolean(restore), modes: opened.modes };
566
+ }
567
+ __name(openSession, "openSession");
568
+ async function applyMode(modes) {
569
+ if (!options.guestMode) return;
570
+ const current = isRecord(modes) ? text(modes.currentModeId) : "";
571
+ if (!turn.state.get(OWNER_MODE) && current) turn.state.set(OWNER_MODE, current);
572
+ const wanted = guest ? options.guestMode : turn.state.get(OWNER_MODE) ?? current;
573
+ if (wanted && wanted !== current) {
574
+ await link.call("session/set_mode", { sessionId, modeId: wanted }).catch((error) => {
575
+ throw new Error(`${agent} could not enter mode "${wanted}": ${oneLine(error.message, 200)}`);
576
+ });
577
+ }
578
+ }
579
+ __name(applyMode, "applyMode");
580
+ void (async () => {
581
+ const { capabilities } = await initialize(link, agent);
582
+ const session = await openSession(capabilities);
583
+ sessionId = session.id;
584
+ await applyMode(session.modes);
585
+ if (turn.signal.aborted) return finish();
586
+ if (session.keep) turn.state.set(SESSION, sessionId);
587
+ answering = true;
588
+ const { stopReason } = await link.call("session/prompt", { sessionId, prompt: [
589
+ { type: "text", text: [...session.fresh ? [framing] : [], ...guest ? [GUEST] : [], turn.prompt].join("\n\n") },
590
+ ...turn.files.map((file) => ({ type: "resource_link", name: basename(file), uri: pathToFileURL(file).href }))
591
+ ] });
592
+ if (stopReason === "end_turn") return settle({ reply: messages.filter((message2) => message2.text.trim()).at(-1)?.text ?? null });
593
+ settle(new Error(`${agent} ended the turn: ${String(stopReason)}`));
594
+ })().catch((error) => settle(error instanceof DriverSessionError ? error : new Error(oneLine(error instanceof Error ? error.message : String(error), 300))));
595
+ });
596
+ },
597
+ // New session (and the runner's reset after a stuck turn): the room's process goes; the runner drops the state.
598
+ reset: end,
599
+ async close() {
600
+ await Promise.all([...running.keys()].map(end));
601
+ }
602
+ };
603
+ }
604
+ __name(acpDriver, "acpDriver");
605
+
144
606
  // src/drivers/claude.ts
607
+ import { randomUUID } from "node:crypto";
608
+ import { realpathSync as realpathSync2 } from "node:fs";
609
+ import { tmpdir as tmpdir2 } from "node:os";
610
+ import { basename as basename2, dirname as dirname3 } from "node:path";
145
611
  var SDK = "@anthropic-ai/claude-agent-sdk";
146
- var SESSION = "sessionId";
612
+ var SESSION2 = "sessionId";
147
613
  var ASK_USER_DIALOG = "permission_ask_user_question";
148
614
  var CANON_TOOLS = "mcp__canon__";
149
615
  var NOT_OWNER = "Only the agent's owner can let it use tools in this conversation.";
150
616
  var OPEN_TOOLS = /* @__PURE__ */ new Set(["AskUserQuestion", "ToolSearch"]);
151
- var SYSTEM = "You are an agent in Canon, a messaging app where people and AI agents talk. Each prompt holds the new messages in one conversation. The text of your last message in a turn is posted there as your reply, so put your whole answer in it. Tool approvals and your questions (AskUserQuestion) are put to the people in the conversation. When you have nothing to add, call the no_reply tool instead of answering.";
152
- var isRecord = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
617
+ var SYSTEM2 = "You are an agent in Canon, a messaging app where people and AI agents talk. Each prompt holds the new messages in one conversation. The text of your last message in a turn is posted there as your reply, so put your whole answer in it. Tool approvals and your questions (AskUserQuestion) are put to the people in the conversation. When you have nothing to add, call the no_reply tool instead of answering.";
618
+ var isRecord2 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
153
619
  var forOwner = /* @__PURE__ */ __name((message2) => message2.isOwner || message2.trustedAgent === true, "forOwner");
154
- var oneLine = /* @__PURE__ */ __name((text3, max) => {
620
+ var oneLine2 = /* @__PURE__ */ __name((text3, max) => {
155
621
  const line = text3.replace(/\s+/g, " ").trim();
156
622
  return line.length > max ? `${line.slice(0, max - 1)}\u2026` : line;
157
623
  }, "oneLine");
@@ -165,10 +631,10 @@ async function loadClaudeQuery() {
165
631
  __name(loadClaudeQuery, "loadClaudeQuery");
166
632
  function describeTool(name, input) {
167
633
  const label = name.startsWith("mcp__") ? name.split("__").slice(2).join("__") || name : name;
168
- const fields = isRecord(input) ? input : {};
634
+ const fields = isRecord2(input) ? input : {};
169
635
  const key = ["description", "command", "file_path", "notebook_path", "pattern", "url", "query", "path"].find((candidate) => typeof fields[candidate] === "string" && fields[candidate].trim() !== "");
170
- const detail = key && (key.endsWith("path") ? basename(fields[key]) : fields[key]);
171
- return oneLine(detail ? `${label}: ${detail}` : label, 120);
636
+ const detail = key && (key.endsWith("path") ? basename2(fields[key]) : fields[key]);
637
+ return oneLine2(detail ? `${label}: ${detail}` : label, 120);
172
638
  }
173
639
  __name(describeTool, "describeTool");
174
640
  function liveReader(turn) {
@@ -194,14 +660,14 @@ function liveReader(turn) {
194
660
  else if (event.type === "content_block_delta" && event.delta?.type === "text_delta") append(event.index ?? 0, String(event.delta.text ?? ""));
195
661
  } else if (message2.type === "assistant") {
196
662
  for (const block of message2.message.content) {
197
- if (!isRecord(block) || block.type !== "tool_use" || typeof block.id !== "string") continue;
663
+ if (!isRecord2(block) || block.type !== "tool_use" || typeof block.id !== "string") continue;
198
664
  const title = describeTool(String(block.name), block.input);
199
665
  tools.set(block.id, title);
200
666
  turn.onActivity({ id: block.id, kind: "tool", title, state: "running" });
201
667
  }
202
668
  } else if (message2.type === "user" && Array.isArray(message2.message.content)) {
203
669
  for (const block of message2.message.content) {
204
- if (!isRecord(block) || block.type !== "tool_result" || !tools.has(String(block.tool_use_id))) continue;
670
+ if (!isRecord2(block) || block.type !== "tool_result" || !tools.has(String(block.tool_use_id))) continue;
205
671
  const id = String(block.tool_use_id);
206
672
  turn.onActivity({ id, kind: "tool", title: tools.get(id), state: block.is_error === true ? "failed" : "done" });
207
673
  }
@@ -219,12 +685,12 @@ function toolScope(toolName, input) {
219
685
  return callScope(toolName, toolName.startsWith("mcp__") ? input : Object.fromEntries(Object.entries(input).filter(([key]) => key !== "description")));
220
686
  }
221
687
  __name(toolScope, "toolScope");
222
- async function approve(turn, toolName, input, prompt) {
688
+ async function approve2(turn, toolName, input, prompt) {
223
689
  const detail = typeof input.command === "string" ? input.command : JSON.stringify(input);
224
690
  const answer = await turn.ask({
225
691
  kind: "approve",
226
692
  id: prompt.toolUseID,
227
- title: oneLine(prompt.title || describeTool(toolName, input), 200),
693
+ title: oneLine2(prompt.title || describeTool(toolName, input), 200),
228
694
  scopeKey: toolScope(toolName, input),
229
695
  allowSession: prompt.suppressAlwaysAllowRule !== true,
230
696
  ...detail && detail !== "{}" ? { detail } : {},
@@ -234,14 +700,14 @@ async function approve(turn, toolName, input, prompt) {
234
700
  if (answer.status !== "answered") return { behavior: "deny", message: DECLINED[answer.status] };
235
701
  return answer.allow ? { behavior: "allow", updatedInput: input, decisionClassification: answer.scope === "session" ? "user_permanent" : "user_temporary" } : { behavior: "deny", message: "This was not allowed.", decisionClassification: "user_reject" };
236
702
  }
237
- __name(approve, "approve");
703
+ __name(approve2, "approve");
238
704
  async function answerQuestions(turn, id, questions, input) {
239
- const list = (Array.isArray(questions) ? questions : []).filter(isRecord).slice(0, 4);
705
+ const list = (Array.isArray(questions) ? questions : []).filter(isRecord2).slice(0, 4);
240
706
  if (!list.length) return { behavior: "deny", message: "There was no question to ask." };
241
707
  const answers = {};
242
708
  for (const [index, question] of list.entries()) {
243
709
  const text3 = typeof question.question === "string" ? question.question : "";
244
- const labels = [...new Set((Array.isArray(question.options) ? question.options : []).filter(isRecord).map((option) => option.label).filter((label) => typeof label === "string" && label.trim() !== ""))];
710
+ const labels = [...new Set((Array.isArray(question.options) ? question.options : []).filter(isRecord2).map((option) => option.label).filter((label) => typeof label === "string" && label.trim() !== ""))];
245
711
  const askId = list.length > 1 ? `${id}#${index + 1}` : id;
246
712
  const request = labels.length ? { kind: "choose", id: askId, question: text3, options: labels.map((label) => ({ id: label, label })), ...question.multiSelect === true ? { multiple: true } : {} } : { kind: "text", id: askId, question: text3, ...question.isSecret === true ? { secret: true } : {} };
247
713
  const answer = await turn.ask(request).catch(() => void 0);
@@ -253,10 +719,10 @@ async function answerQuestions(turn, id, questions, input) {
253
719
  }
254
720
  __name(answerQuestions, "answerQuestions");
255
721
  function claudeDriver(options) {
256
- const cwd = realpathSync(options.cwd);
722
+ const cwd = realpathSync2(options.cwd);
257
723
  const permissionMode = options.permissionMode ?? "default";
258
724
  const interruptGraceMs = options.interruptGraceMs ?? 5e3;
259
- const system = withInstructions(SYSTEM, options.instructions);
725
+ const system = withInstructions(SYSTEM2, options.instructions);
260
726
  let loaded;
261
727
  const running = /* @__PURE__ */ new Map();
262
728
  const end = /* @__PURE__ */ __name(async (session) => {
@@ -274,11 +740,11 @@ function claudeDriver(options) {
274
740
  const canUseTool = /* @__PURE__ */ __name(async (toolName, input, prompt) => {
275
741
  if (toolName.startsWith(CANON_TOOLS)) return { behavior: "allow", updatedInput: input };
276
742
  if (toolName === "AskUserQuestion") return answerQuestions(turn, prompt.toolUseID, input.questions, input);
277
- return approve(turn, toolName, input, prompt);
743
+ return approve2(turn, toolName, input, prompt);
278
744
  }, "canUseTool");
279
745
  const onUserDialog = /* @__PURE__ */ __name(async (request, { signal }) => {
280
746
  if (request.dialogKind !== ASK_USER_DIALOG) return { behavior: "cancelled" };
281
- const input = isRecord(request.payload.input) ? request.payload.input : { questions: request.payload.questions };
747
+ const input = isRecord2(request.payload.input) ? request.payload.input : { questions: request.payload.questions };
282
748
  const id = request.toolUseID || typeof request.payload.requestId === "string" && request.payload.requestId || randomUUID();
283
749
  const result = await answerQuestions(turn, id, input.questions, input);
284
750
  return signal.aborted ? { behavior: "cancelled" } : { behavior: "completed", result };
@@ -296,7 +762,7 @@ function claudeDriver(options) {
296
762
  ...resume ? { resume } : {},
297
763
  includePartialMessages: true,
298
764
  ...turn.tools.url ? { mcpServers: { canon: { type: "http", url: turn.tools.url, alwaysLoad: true } }, allowedTools: ["mcp__canon"] } : {},
299
- additionalDirectories: [...new Set(turn.files.map((file) => dirname2(file)))],
765
+ additionalDirectories: [...new Set(turn.files.map((file) => dirname3(file)))],
300
766
  canUseTool,
301
767
  onUserDialog,
302
768
  supportedDialogKinds: [ASK_USER_DIALOG],
@@ -316,13 +782,13 @@ function claudeDriver(options) {
316
782
  streaming: true,
317
783
  activity: true,
318
784
  tools: true,
319
- outboundFiles: { roots: [cwd, realpathSync(tmpdir())] }
785
+ outboundFiles: { roots: [cwd, realpathSync2(tmpdir2())] }
320
786
  },
321
787
  async runTurn(turn) {
322
788
  await running.get(turn.session)?.exited;
323
789
  if (turn.signal.aborted) throw turn.signal.reason;
324
790
  const query = await (options.query ? Promise.resolve(options.query) : loaded ??= loadClaudeQuery());
325
- const resume = turn.state.get(SESSION);
791
+ const resume = turn.state.get(SESSION2);
326
792
  const native = query({ prompt: turn.prompt, options: queryOptions(turn, resume) });
327
793
  let exit = /* @__PURE__ */ __name(() => void 0, "exit");
328
794
  const entry = { close: /* @__PURE__ */ __name(() => native.close(), "close"), exited: new Promise((resolve3) => {
@@ -349,7 +815,7 @@ function claudeDriver(options) {
349
815
  const message2 = raw;
350
816
  if (message2.type === "system" && message2.subtype === "init") started = true;
351
817
  const id = message2.session_id;
352
- if (typeof id === "string" && id && id !== session) turn.state.set(SESSION, session = id);
818
+ if (typeof id === "string" && id && id !== session) turn.state.set(SESSION2, session = id);
353
819
  reader.read(message2);
354
820
  if (message2.type !== "result") continue;
355
821
  finished = true;
@@ -359,7 +825,7 @@ function claudeDriver(options) {
359
825
  resolve3({ reply: reply.trim() ? reply : null });
360
826
  } else {
361
827
  const detail = message2.subtype === "success" ? message2.result : message2.errors.join("; ");
362
- reject(failed(new Error(`Claude Code ended the turn with ${message2.subtype}${detail ? `: ${oneLine(detail, 300)}` : ""}`)));
828
+ reject(failed(new Error(`Claude Code ended the turn with ${message2.subtype}${detail ? `: ${oneLine2(detail, 300)}` : ""}`)));
363
829
  }
364
830
  }
365
831
  if (!finished) throw new Error("Claude Code exited without finishing the turn");
@@ -384,8 +850,8 @@ function claudeDriver(options) {
384
850
  __name(claudeDriver, "claudeDriver");
385
851
 
386
852
  // src/drivers/code.ts
387
- import { realpathSync as realpathSync2 } from "node:fs";
388
- import { tmpdir as tmpdir2 } from "node:os";
853
+ import { realpathSync as realpathSync3 } from "node:fs";
854
+ import { tmpdir as tmpdir3 } from "node:os";
389
855
  function codeDriver(handler, capabilities = {}) {
390
856
  return {
391
857
  name: "code",
@@ -395,7 +861,7 @@ function codeDriver(handler, capabilities = {}) {
395
861
  streaming: true,
396
862
  activity: true,
397
863
  tools: true,
398
- outboundFiles: { roots: [realpathSync2(tmpdir2())] },
864
+ outboundFiles: { roots: [realpathSync3(tmpdir3())] },
399
865
  ...capabilities
400
866
  },
401
867
  async runTurn(turn) {
@@ -420,16 +886,14 @@ function codeDriver(handler, capabilities = {}) {
420
886
  __name(codeDriver, "codeDriver");
421
887
 
422
888
  // src/drivers/codex.ts
423
- import { spawn, spawnSync } from "node:child_process";
424
- import { createHash as createHash2 } from "node:crypto";
425
- import { realpathSync as realpathSync3 } from "node:fs";
426
- import { tmpdir as tmpdir3 } from "node:os";
427
- import { basename as basename2 } from "node:path";
428
- import { createInterface } from "node:readline";
889
+ import { spawn as spawn3, spawnSync } from "node:child_process";
890
+ import { realpathSync as realpathSync4 } from "node:fs";
891
+ import { tmpdir as tmpdir4 } from "node:os";
892
+ import { basename as basename3 } from "node:path";
429
893
  var THREAD = "threadId";
430
- var CANON = "canon";
894
+ var CANON2 = "canon";
431
895
  var API_KEY_PROVIDER = "canon_openai_api_key";
432
- var SYSTEM2 = "You are an agent in Canon, a messaging app where people and AI agents talk. Each prompt holds the new messages in one conversation. Your final message in a turn is posted there as your reply, so put your whole answer in it. Approval requests are put to the people in the conversation. To ask them a question, call the ask_question tool of the canon MCP server and wait for its answer. When you have nothing to add, call the no_reply tool of the canon MCP server instead of answering.";
896
+ var SYSTEM3 = "You are an agent in Canon, a messaging app where people and AI agents talk. Each prompt holds the new messages in one conversation. Your final message in a turn is posted there as your reply, so put your whole answer in it. Approval requests are put to the people in the conversation. To ask them a question, call the ask_question tool of the canon MCP server and wait for its answer. When you have nothing to add, call the no_reply tool of the canon MCP server instead of answering.";
433
897
  var CANON_TOOL_TIMEOUT_SEC = 2 * 60 * 60;
434
898
  var MACHINE_FEATURES = [
435
899
  "shell_tool",
@@ -466,18 +930,11 @@ var GUEST_ITEMS = /* @__PURE__ */ new Set([
466
930
  "sleep"
467
931
  ]);
468
932
  var OWN_LISTINGS = /* @__PURE__ */ new Set(["list_mcp_resources", "list_mcp_resource_templates"]);
469
- var GUEST = "Only your owner can let you use tools in this conversation, and this turn holds a message from someone else: you cannot read files, run commands or use any tool but the canon MCP server's. Answer from the conversation and what you already know, and say so when something needs your owner.";
470
- var RISKY = /\brm\s+-[a-z]*[rf]|\bgit\s+(reset\s+--hard|push\s+(-f|--force))|\bdrop\s+(table|database)\b|\bmkfs\b|\bdd\s+if=/i;
933
+ var GUEST2 = "Only your owner can let you use tools in this conversation, and this turn holds a message from someone else: you cannot read files, run commands or use any tool but the canon MCP server's. Answer from the conversation and what you already know, and say so when something needs your owner.";
471
934
  var IMAGE = /\.(png|jpe?g|gif|webp)$/i;
472
- var isRecord2 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
473
- var guestTurn = /* @__PURE__ */ __name((turn) => !turn.input.messages.every((message2) => message2.isOwner || message2.trustedAgent === true), "guestTurn");
474
- var text = /* @__PURE__ */ __name((value) => typeof value === "string" ? value : "", "text");
475
- var oneLine2 = /* @__PURE__ */ __name((value, max) => {
476
- const line = value.replace(/\s+/g, " ").trim();
477
- return line.length > max ? `${line.slice(0, max - 1)}\u2026` : line;
478
- }, "oneLine");
479
935
  function checkCodex(codexPath = "codex") {
480
- const result = spawnSync(codexPath, ["--version"], { encoding: "utf8" });
936
+ const cli = installedCli(codexPath);
937
+ const result = spawnSync(cli.file, [...cli.args, "--version"], { encoding: "utf8", windowsHide: true });
481
938
  if (result.status !== 0) {
482
939
  throw new Error(`The codex driver needs the Codex CLI (npm install -g @openai/codex) or --codex-path: ${result.error?.message ?? text(result.stderr).trim()}`);
483
940
  }
@@ -485,20 +942,20 @@ function checkCodex(codexPath = "codex") {
485
942
  }
486
943
  __name(checkCodex, "checkCodex");
487
944
  function describe(item) {
488
- const paths = (Array.isArray(item.changes) ? item.changes : []).filter(isRecord2).map((change) => basename2(text(change.path)));
945
+ const paths = (Array.isArray(item.changes) ? item.changes : []).filter(isRecord).map((change) => basename3(text(change.path)));
489
946
  switch (item.type) {
490
947
  case "commandExecution": {
491
- const actions = (Array.isArray(item.commandActions) ? item.commandActions : []).filter(isRecord2).map((action) => text(action.command));
492
- return oneLine2(actions.filter(Boolean).join(" && ") || text(item.command) || "Run a command", 120);
948
+ const actions = (Array.isArray(item.commandActions) ? item.commandActions : []).filter(isRecord).map((action) => text(action.command));
949
+ return oneLine(actions.filter(Boolean).join(" && ") || text(item.command) || "Run a command", 120);
493
950
  }
494
951
  case "fileChange":
495
- return oneLine2(paths.length ? `Edit ${paths.join(", ")}` : "Edit files", 120);
952
+ return oneLine(paths.length ? `Edit ${paths.join(", ")}` : "Edit files", 120);
496
953
  case "mcpToolCall":
497
954
  return `${text(item.server)}: ${text(item.tool)}`;
498
955
  case "webSearch":
499
- return oneLine2(`Search the web${text(item.query) ? `: ${text(item.query)}` : ""}`, 120);
956
+ return oneLine(`Search the web${text(item.query) ? `: ${text(item.query)}` : ""}`, 120);
500
957
  case "imageView":
501
- return `View ${basename2(text(item.path))}`;
958
+ return `View ${basename3(text(item.path))}`;
502
959
  case "dynamicToolCall":
503
960
  return text(item.tool) || "Tool";
504
961
  default:
@@ -506,27 +963,19 @@ function describe(item) {
506
963
  }
507
964
  }
508
965
  __name(describe, "describe");
509
- async function approve2(turn, request) {
510
- const asked = createHash2("sha256").update(JSON.stringify([request.scopeKey, request.title, request.detail])).digest("hex").slice(0, 12);
511
- const answer = await turn.ask({ kind: "approve", ...request, id: `${request.id}#${asked}` }).catch(() => void 0);
512
- if (!answer) return "stopped";
513
- if (answer.status !== "answered" || !answer.allow) return "denied";
514
- return answer.scope === "session" ? "session" : "once";
515
- }
516
- __name(approve2, "approve");
517
966
  var DECISION = { session: "acceptForSession", once: "accept", denied: "decline", stopped: "cancel" };
518
967
  async function answerCodexRequest(turn, method, params, items, rpcId) {
519
968
  const id = text(params.approvalId) || text(params.itemId) || text(params.elicitationId) || `request-${String(rpcId)}`;
520
969
  if (method === "item/commandExecution/requestApproval") {
521
970
  const command = text(params.command);
522
971
  const stdin = params.kind === "writeStdin";
523
- const network = isRecord2(params.networkApprovalContext) ? params.networkApprovalContext : void 0;
524
- const parsed = (Array.isArray(params.commandActions) ? params.commandActions : []).filter(isRecord2).map((action) => text(action.command)).filter(Boolean);
972
+ const network = isRecord(params.networkApprovalContext) ? params.networkApprovalContext : void 0;
973
+ const parsed = (Array.isArray(params.commandActions) ? params.commandActions : []).filter(isRecord).map((action) => text(action.command)).filter(Boolean);
525
974
  const destination = network && `${text(network.host)}${text(network.protocol) ? ` (${text(network.protocol)})` : ""}`;
526
975
  const scope = callScope("exec", [command, text(params.cwd), network ?? null, params.additionalPermissions ?? null]);
527
- const verdict = await approve2(turn, {
976
+ const verdict = await approve(turn, {
528
977
  id,
529
- title: oneLine2(text(params.reason) || (command ? `${stdin ? "Send input to" : "Run"}: ${parsed.join(" && ") || command}` : `Allow network access to ${destination || "the network"}`), 200),
978
+ title: oneLine(text(params.reason) || (command ? `${stdin ? "Send input to" : "Run"}: ${parsed.join(" && ") || command}` : `Allow network access to ${destination || "the network"}`), 200),
530
979
  detail: [
531
980
  command,
532
981
  text(params.cwd) && `in ${text(params.cwd)}`,
@@ -541,24 +990,24 @@ async function answerCodexRequest(turn, method, params, items, rpcId) {
541
990
  return { decision: DECISION[verdict] };
542
991
  }
543
992
  if (method === "item/fileChange/requestApproval") {
544
- const changes = (Array.isArray(items.get(text(params.itemId))?.changes) ? items.get(text(params.itemId)).changes : []).filter(isRecord2);
993
+ const changes = (Array.isArray(items.get(text(params.itemId))?.changes) ? items.get(text(params.itemId)).changes : []).filter(isRecord);
545
994
  const paths = [...new Set(changes.map((change) => text(change.path)))].sort();
546
- const verdict = await approve2(turn, {
995
+ const verdict = await approve(turn, {
547
996
  id,
548
- title: oneLine2(text(params.reason) || (paths.length ? `Edit ${paths.map((path) => basename2(path)).join(", ")}` : "Edit files"), 200),
997
+ title: oneLine(text(params.reason) || (paths.length ? `Edit ${paths.map((path) => basename3(path)).join(", ")}` : "Edit files"), 200),
549
998
  detail: [...changes.map((change) => `${text(change.path)}
550
999
  ${text(change.diff)}`.trim()), text(params.grantRoot) && `Write access to ${text(params.grantRoot)}`].filter(Boolean).join("\n\n"),
551
1000
  scopeKey: callScope("apply_patch", paths),
552
1001
  allowSession: paths.length > 0 && !params.grantRoot,
553
- ...changes.some((change) => isRecord2(change.kind) && change.kind.type === "delete") ? { risk: "destructive" } : {}
1002
+ ...changes.some((change) => isRecord(change.kind) && change.kind.type === "delete") ? { risk: "destructive" } : {}
554
1003
  });
555
1004
  return { decision: DECISION[verdict] };
556
1005
  }
557
1006
  if (method === "item/permissions/requestApproval") {
558
- const permissions = isRecord2(params.permissions) ? params.permissions : {};
559
- const verdict = await approve2(turn, {
1007
+ const permissions = isRecord(params.permissions) ? params.permissions : {};
1008
+ const verdict = await approve(turn, {
560
1009
  id,
561
- title: oneLine2(text(params.reason) || "Allow Codex more access", 200),
1010
+ title: oneLine(text(params.reason) || "Allow Codex more access", 200),
562
1011
  detail: JSON.stringify(permissions),
563
1012
  scopeKey: callScope("permissions", permissions),
564
1013
  allowSession: true
@@ -566,16 +1015,16 @@ ${text(change.diff)}`.trim()), text(params.grantRoot) && `Write access to ${text
566
1015
  return verdict === "session" || verdict === "once" ? { permissions, scope: verdict === "session" ? "session" : "turn" } : { permissions: {}, scope: "turn" };
567
1016
  }
568
1017
  if (method === "mcpServer/elicitation/request") {
569
- const meta = isRecord2(params._meta) ? params._meta : {};
1018
+ const meta = isRecord(params._meta) ? params._meta : {};
570
1019
  if (meta.codex_approval_kind !== "mcp_tool_call") return { action: "decline", content: null, _meta: null };
571
1020
  const server = text(params.serverName);
572
1021
  const named = text(meta.call_id) || text(meta.callId);
573
1022
  const running = [...items.values()].filter((item) => item.type === "mcpToolCall" && item.server === server && item.status === "inProgress");
574
1023
  const call = named ? items.get(named) : running.length === 1 ? running[0] : void 0;
575
1024
  const tool = call && call.server === server ? text(call.tool) : "";
576
- const verdict = await approve2(turn, {
1025
+ const verdict = await approve(turn, {
577
1026
  id: `mcp-${String(rpcId)}`,
578
- title: oneLine2(text(params.message) || `Use ${server}${tool ? `: ${tool}` : ""}`, 200),
1027
+ title: oneLine(text(params.message) || `Use ${server}${tool ? `: ${tool}` : ""}`, 200),
579
1028
  detail: JSON.stringify(meta.tool_params ?? call?.arguments ?? {}),
580
1029
  scopeKey: `codex:mcp:${server}:${tool}`,
581
1030
  allowSession: tool !== ""
@@ -587,10 +1036,10 @@ ${text(change.diff)}`.trim()), text(params.grantRoot) && `Write access to ${text
587
1036
  };
588
1037
  }
589
1038
  if (method === "item/tool/requestUserInput") {
590
- const questions = (Array.isArray(params.questions) ? params.questions : []).filter(isRecord2);
1039
+ const questions = (Array.isArray(params.questions) ? params.questions : []).filter(isRecord);
591
1040
  const answers = {};
592
1041
  for (const [index, question] of questions.entries()) {
593
- const labels = [...new Set((Array.isArray(question.options) ? question.options : []).filter(isRecord2).map((option) => text(option.label)).filter(Boolean))];
1042
+ const labels = [...new Set((Array.isArray(question.options) ? question.options : []).filter(isRecord).map((option) => text(option.label)).filter(Boolean))];
594
1043
  const askId = questions.length > 1 ? `${id}#${index + 1}` : id;
595
1044
  const ask = labels.length ? { kind: "choose", id: askId, question: text(question.question), options: labels.map((label) => ({ id: label, label })) } : { kind: "text", id: askId, question: text(question.question), ...question.isSecret === true ? { secret: true } : {} };
596
1045
  const answer = await turn.ask(ask).catch(() => void 0);
@@ -602,55 +1051,11 @@ ${text(change.diff)}`.trim()), text(params.grantRoot) && `Write access to ${text
602
1051
  throw new Error(`The codex driver does not handle ${method}`);
603
1052
  }
604
1053
  __name(answerCodexRequest, "answerCodexRequest");
605
- function rpc(child, handlers) {
606
- let next = 0;
607
- const pending = /* @__PURE__ */ new Map();
608
- const write = /* @__PURE__ */ __name((message2) => {
609
- if (child.stdin?.writable) child.stdin.write(`${JSON.stringify(message2)}
610
- `);
611
- }, "write");
612
- child.stdin?.on("error", () => void 0);
613
- createInterface({ input: child.stdout }).on("error", () => void 0).on("line", (line) => {
614
- let message2;
615
- try {
616
- message2 = JSON.parse(line);
617
- } catch {
618
- return;
619
- }
620
- if (!isRecord2(message2)) return;
621
- const params = isRecord2(message2.params) ? message2.params : {};
622
- if (typeof message2.method !== "string") {
623
- const waiting = pending.get(Number(message2.id));
624
- pending.delete(Number(message2.id));
625
- if (isRecord2(message2.error)) waiting?.reject(new Error(text(message2.error.message) || "Codex refused the request"));
626
- else waiting?.resolve(message2.result);
627
- } else if (message2.id !== void 0) {
628
- handlers.request(message2.method, params, message2.id).then(
629
- (result) => write({ id: message2.id, result }),
630
- (error) => write({ id: message2.id, error: { code: -32e3, message: error instanceof Error ? error.message : String(error) } })
631
- );
632
- } else handlers.notify(message2.method, params);
633
- });
634
- child.stderr?.resume();
635
- return {
636
- call: /* @__PURE__ */ __name((method, params) => new Promise((resolve3, reject) => {
637
- next += 1;
638
- pending.set(next, { resolve: /* @__PURE__ */ __name((value) => resolve3(isRecord2(value) ? value : {}), "resolve"), reject });
639
- write({ id: next, method, params });
640
- }), "call"),
641
- notify: /* @__PURE__ */ __name((method) => write({ method }), "notify"),
642
- fail(error) {
643
- for (const waiting of pending.values()) waiting.reject(error);
644
- pending.clear();
645
- }
646
- };
647
- }
648
- __name(rpc, "rpc");
649
1054
  function codexDriver(options) {
650
- const cwd = realpathSync3(options.cwd);
1055
+ const cwd = realpathSync4(options.cwd);
651
1056
  const codexPath = options.codexPath ?? "codex";
652
1057
  const interruptGraceMs = options.interruptGraceMs ?? 5e3;
653
- const developerInstructions = withInstructions(SYSTEM2, options.instructions);
1058
+ const developerInstructions = withInstructions(SYSTEM3, options.instructions);
654
1059
  const running = /* @__PURE__ */ new Map();
655
1060
  const end = /* @__PURE__ */ __name(async (session) => {
656
1061
  const live = running.get(session);
@@ -661,8 +1066,8 @@ function codexDriver(options) {
661
1066
  async function threadSettings(turn, call, env) {
662
1067
  const guest = guestTurn(turn);
663
1068
  const configured = (await call("config/read", { cwd })).config;
664
- const settings = isRecord2(configured) ? configured : {};
665
- const servers = guest && isRecord2(settings.mcp_servers) ? Object.keys(settings.mcp_servers).filter((name) => name !== CANON) : [];
1069
+ const settings = isRecord(configured) ? configured : {};
1070
+ const servers = guest && isRecord(settings.mcp_servers) ? Object.keys(settings.mcp_servers).filter((name) => name !== CANON2) : [];
666
1071
  const apiKey = Boolean(env.OPENAI_API_KEY) && [void 0, null, "openai"].includes(settings.model_provider);
667
1072
  return {
668
1073
  cwd,
@@ -675,7 +1080,7 @@ function codexDriver(options) {
675
1080
  config: {
676
1081
  mcp_servers: {
677
1082
  ...Object.fromEntries(servers.map((name) => [name, { enabled: false }])),
678
- ...turn.tools.url ? { [CANON]: { url: turn.tools.url, default_tools_approval_mode: "approve", tool_timeout_sec: CANON_TOOL_TIMEOUT_SEC } } : {}
1083
+ ...turn.tools.url ? { [CANON2]: { url: turn.tools.url, default_tools_approval_mode: "approve", tool_timeout_sec: CANON_TOOL_TIMEOUT_SEC } } : {}
679
1084
  },
680
1085
  // Questions go through Canon's ask_question alone: Codex's own request_user_input outside plan mode (a feature
681
1086
  // still under development) stays off even when the owner's configuration turns it on, so Codex never has two.
@@ -699,20 +1104,21 @@ function codexDriver(options) {
699
1104
  streaming: true,
700
1105
  activity: true,
701
1106
  tools: true,
702
- outboundFiles: { roots: [cwd, realpathSync3(tmpdir3())] }
1107
+ outboundFiles: { roots: [cwd, realpathSync4(tmpdir4())] }
703
1108
  },
704
1109
  async runTurn(turn) {
705
1110
  await running.get(turn.session)?.exited;
706
1111
  if (turn.signal.aborted) throw turn.signal.reason;
707
1112
  const guest = guestTurn(turn);
708
1113
  const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith("CANON_")));
709
- const child = spawn(codexPath, ["app-server", "--listen", "stdio://"], { cwd, env, stdio: ["pipe", "pipe", "pipe"] });
1114
+ const cli = installedCli(codexPath);
1115
+ const child = spawn3(cli.file, [...cli.args, "app-server", "--listen", "stdio://"], { cwd, env, stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
710
1116
  let exit = /* @__PURE__ */ __name(() => void 0, "exit");
711
1117
  const exited = new Promise((resolve3) => {
712
1118
  exit = resolve3;
713
1119
  });
714
1120
  const entry = { close: /* @__PURE__ */ __name(() => {
715
- child.kill("SIGTERM");
1121
+ stopCli(child);
716
1122
  }, "close"), exited };
717
1123
  running.set(turn.session, entry);
718
1124
  return new Promise((resolve3, reject) => {
@@ -749,19 +1155,19 @@ function codexDriver(options) {
749
1155
  const all = [...messages.values()];
750
1156
  const reply = (all.filter((message2) => message2.done && message2.final).at(-1) ?? all.filter((message2) => message2.done).at(-1) ?? all.at(-1))?.text ?? "";
751
1157
  if (ended.status === "completed") return settle({ reply: reply.trim() ? reply : null });
752
- const detail = isRecord2(ended.error) ? text(ended.error.message) : failure;
753
- settle(new Error(`Codex ended the turn ${ended.status === "interrupted" ? "interrupted" : `with an error${detail ? `: ${oneLine2(detail, 300)}` : ""}`}`));
1158
+ const detail = isRecord(ended.error) ? text(ended.error.message) : failure;
1159
+ settle(new Error(`Codex ended the turn ${ended.status === "interrupted" ? "interrupted" : `with an error${detail ? `: ${oneLine(detail, 300)}` : ""}`}`));
754
1160
  }, "conclude");
755
1161
  const guard = /* @__PURE__ */ __name((item) => {
756
- if (!guest || GUEST_ITEMS.has(text(item.type)) || item.type === "mcpToolCall" && (item.server === CANON || item.server === "codex" && OWN_LISTINGS.has(text(item.tool)))) return;
1162
+ if (!guest || GUEST_ITEMS.has(text(item.type)) || item.type === "mcpToolCall" && (item.server === CANON2 || item.server === "codex" && OWN_LISTINGS.has(text(item.tool)))) return;
757
1163
  interrupt();
758
1164
  entry.close();
759
1165
  const what = item.type === "mcpToolCall" ? `mcpToolCall (${describe(item)})` : text(item.type) || "an unknown item";
760
1166
  settle(new DriverSessionError(`Codex started ${what} in a turn holding someone other than the owner's message`));
761
1167
  }, "guard");
762
- const link = rpc(child, {
1168
+ const link = stdioRpc(child, {
763
1169
  notify(method, params) {
764
- const item = isRecord2(params.item) ? params.item : void 0;
1170
+ const item = isRecord(params.item) ? params.item : void 0;
765
1171
  if (item && method === "item/started") guard(item);
766
1172
  if (item) items.set(text(item.id), item);
767
1173
  if (!ours(params)) return;
@@ -777,15 +1183,15 @@ function codexDriver(options) {
777
1183
  const title = describe(item);
778
1184
  const state = method === "item/started" ? "running" : item.status === "failed" || item.status === "declined" ? "failed" : "done";
779
1185
  if (title) turn.onActivity({ id: text(item.id), kind: "tool", title, state });
780
- } else if (method === "error" && params.willRetry !== true && isRecord2(params.error)) {
1186
+ } else if (method === "error" && params.willRetry !== true && isRecord(params.error)) {
781
1187
  failure = text(params.error.message);
782
- } else if (method === "turn/completed" && isRecord2(params.turn)) {
1188
+ } else if (method === "turn/completed" && isRecord(params.turn)) {
783
1189
  completed.set(text(params.turn.id), params.turn);
784
1190
  conclude();
785
1191
  }
786
1192
  },
787
1193
  request: /* @__PURE__ */ __name((method, params, id) => answerCodexRequest(turn, method, params, items, id), "request")
788
- });
1194
+ }, { name: "Codex" });
789
1195
  const gone = /* @__PURE__ */ __name((error) => {
790
1196
  if (closed) return;
791
1197
  closed = true;
@@ -811,18 +1217,18 @@ function codexDriver(options) {
811
1217
  threadId = resume;
812
1218
  } else {
813
1219
  const started2 = await link.call("thread/start", settings);
814
- threadId = isRecord2(started2.thread) ? text(started2.thread.id) : "";
1220
+ threadId = isRecord(started2.thread) ? text(started2.thread.id) : "";
815
1221
  if (!threadId) throw new Error("Codex started no thread");
816
1222
  turn.state.set(THREAD, threadId);
817
1223
  }
818
1224
  if (turn.signal.aborted) return finish();
819
1225
  const started = await link.call("turn/start", { threadId, input: [
820
- ...guest ? [{ type: "text", text: GUEST, text_elements: [] }] : [],
1226
+ ...guest ? [{ type: "text", text: GUEST2, text_elements: [] }] : [],
821
1227
  { type: "text", text: turn.prompt, text_elements: [] },
822
1228
  // Images go to the model as images; every attachment's path is also in the prompt for Codex to read.
823
1229
  ...turn.files.filter((file) => IMAGE.test(file)).map((path) => ({ type: "localImage", path }))
824
1230
  ] });
825
- nativeTurn = isRecord2(started.turn) ? text(started.turn.id) : "";
1231
+ nativeTurn = isRecord(started.turn) ? text(started.turn.id) : "";
826
1232
  if (!nativeTurn) throw new Error("Codex started no turn");
827
1233
  if (turn.signal.aborted) interrupt();
828
1234
  conclude();
@@ -1010,117 +1416,10 @@ function connectorDriver() {
1010
1416
  }
1011
1417
  __name(connectorDriver, "connectorDriver");
1012
1418
 
1013
- // src/runner/home.ts
1014
- import { randomBytes } from "node:crypto";
1015
- import { closeSync, constants as constants2, fstatSync, fsyncSync, linkSync, mkdirSync, openSync, readSync, renameSync, rmSync, writeFileSync, writeSync } from "node:fs";
1016
- import { lstat, mkdir, realpath as realpath2 } from "node:fs/promises";
1017
- import { dirname as dirname3 } from "node:path";
1018
- var runnerLockName = /* @__PURE__ */ __name((environmentId, agentId) => `runner-${environmentId}-${agentId}`.replace(/[^\w.-]/g, "_"), "runnerLockName");
1019
- function writeDurable(file, text3) {
1020
- const directory = dirname3(file);
1021
- const created = mkdirSync(directory, { recursive: true, mode: 448 });
1022
- const temporary = `${file}.${process.pid}.tmp`;
1023
- const fd = openSync(temporary, "w", 384);
1024
- try {
1025
- writeFileSync(fd, text3);
1026
- fsyncSync(fd);
1027
- } finally {
1028
- closeSync(fd);
1029
- }
1030
- renameSync(temporary, file);
1031
- if (process.platform === "win32") return;
1032
- const last = created ? dirname3(created) : directory;
1033
- for (let path = directory; ; path = dirname3(path)) {
1034
- const dir = openSync(path, "r");
1035
- try {
1036
- fsyncSync(dir);
1037
- } finally {
1038
- closeSync(dir);
1039
- }
1040
- if (path === last) break;
1041
- }
1042
- }
1043
- __name(writeDurable, "writeDurable");
1044
- function privateKey(file, bytes = 32) {
1045
- const refuse = /* @__PURE__ */ __name(() => {
1046
- throw new Error(`Refusing to start: ${file} must be a regular file of ${bytes} bytes, owned by this user and private to it (0600).`);
1047
- }, "refuse");
1048
- const read = /* @__PURE__ */ __name(() => {
1049
- let fd2;
1050
- try {
1051
- fd2 = openSync(file, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
1052
- } catch (error) {
1053
- return error.code === "ENOENT" ? void 0 : refuse();
1054
- }
1055
- try {
1056
- const info = fstatSync(fd2);
1057
- const uid = process.getuid?.();
1058
- if (!info.isFile() || info.size !== bytes || uid !== void 0 && (info.uid !== uid || (info.mode & 63) !== 0)) refuse();
1059
- const key = Buffer.alloc(bytes);
1060
- if (readSync(fd2, key, 0, bytes, 0) !== bytes) refuse();
1061
- return key;
1062
- } finally {
1063
- closeSync(fd2);
1064
- }
1065
- }, "read");
1066
- const existing = read();
1067
- if (existing) return existing;
1068
- const directory = dirname3(file);
1069
- mkdirSync(directory, { recursive: true, mode: 448 });
1070
- const temporary = `${file}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`;
1071
- const fd = openSync(temporary, "wx", 384);
1072
- try {
1073
- writeSync(fd, randomBytes(bytes));
1074
- fsyncSync(fd);
1075
- } finally {
1076
- closeSync(fd);
1077
- }
1078
- try {
1079
- linkSync(temporary, file);
1080
- } catch (error) {
1081
- if (error.code !== "EEXIST") throw error;
1082
- } finally {
1083
- rmSync(temporary, { force: true });
1084
- }
1085
- const dir = openSync(directory, "r");
1086
- try {
1087
- fsyncSync(dir);
1088
- } finally {
1089
- closeSync(dir);
1090
- }
1091
- return read() ?? refuse();
1092
- }
1093
- __name(privateKey, "privateKey");
1094
- async function assertPrivateHome(home) {
1095
- const uid = process.getuid?.();
1096
- if (uid === void 0) return;
1097
- const fail2 = /* @__PURE__ */ __name((path, problem) => {
1098
- throw new Error(`Refusing to start: ${path} ${problem}. CANON_HOME must be a private 0700 directory whose ancestors only its owner or root can modify.`);
1099
- }, "fail");
1100
- await mkdir(home, { recursive: true, mode: 448 }).catch(() => void 0);
1101
- let resolved;
1102
- try {
1103
- resolved = await realpath2(home);
1104
- } catch {
1105
- return fail2(home, "does not exist");
1106
- }
1107
- if (resolved !== home) fail2(home, `is not canonical (it resolves to ${resolved})`);
1108
- const stat3 = await lstat(home);
1109
- if (!stat3.isDirectory() || stat3.uid !== uid) fail2(home, "is not a directory owned by this user");
1110
- if (stat3.mode & 63) fail2(home, `is accessible to other users (mode ${(stat3.mode & 511).toString(8)})`);
1111
- for (let path = dirname3(home); ; path = dirname3(path)) {
1112
- const ancestor = await lstat(path);
1113
- if (ancestor.uid !== uid && ancestor.uid !== 0) fail2(path, "is owned by another user");
1114
- if (ancestor.mode & 18) fail2(path, "is writable by other users");
1115
- if (path === dirname3(path)) return;
1116
- }
1117
- }
1118
- __name(assertPrivateHome, "assertPrivateHome");
1119
-
1120
1419
  // src/runner/errands.ts
1121
1420
  import { randomBytes as randomBytes2 } from "node:crypto";
1122
- import { closeSync as closeSync2, constants as constants3, fstatSync as fstatSync2, lstatSync, mkdirSync as mkdirSync2, openSync as openSync2, readdirSync, readFileSync, unlinkSync, watch } from "node:fs";
1123
- import { join } from "node:path";
1421
+ import { closeSync, constants as constants2, fstatSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync as readFileSync2, unlinkSync, watch } from "node:fs";
1422
+ import { join as join2 } from "node:path";
1124
1423
  var ERRAND_KINDS = ["tell", "post", "errand", "cancel"];
1125
1424
  var ERRAND_TEXT_MAX_BYTES = MLS_DEV_TEXT_MAX_BYTES;
1126
1425
  var ERRAND_WHY_MAX_BYTES = 4 * 1024;
@@ -1138,7 +1437,7 @@ function errandsDirectory(home, environmentId, agentId) {
1138
1437
  for (const segment of [environmentId, agentId]) {
1139
1438
  if (!/^[\w.:-]{1,200}$/.test(segment) || /^\.+$/.test(segment)) throw new Error(`Not an id: ${JSON.stringify(segment)}`);
1140
1439
  }
1141
- return join(home, "runner", environmentId, agentId, "errands");
1440
+ return join2(home, "runner", environmentId, agentId, "errands");
1142
1441
  }
1143
1442
  __name(errandsDirectory, "errandsDirectory");
1144
1443
  function custody(stat3, kind) {
@@ -1154,8 +1453,8 @@ function custody(stat3, kind) {
1154
1453
  }
1155
1454
  __name(custody, "custody");
1156
1455
  function errandInbox(directory) {
1157
- const inbox = join(directory, "inbox");
1158
- mkdirSync2(inbox, { recursive: true, mode: 448 });
1456
+ const inbox = join2(directory, "inbox");
1457
+ mkdirSync(inbox, { recursive: true, mode: 448 });
1159
1458
  const problem = custody(lstatSync(inbox), "directory");
1160
1459
  if (problem) throw new Error(`The errand inbox ${inbox} ${problem}`);
1161
1460
  return inbox;
@@ -1188,16 +1487,16 @@ function queueErrand(directory, input) {
1188
1487
  const command = { version: 1, id: randomBytes2(16).toString("hex"), ...fields, createdAt: now.toISOString() };
1189
1488
  const problem = commandProblem(command, command.id);
1190
1489
  if (problem) throw new Error(`Not a valid ${input.kind}: ${problem}.`);
1191
- writeDurable(join(errandInbox(directory), `${now.getTime()}-${command.id}.json`), JSON.stringify(command));
1490
+ writeDurable(join2(errandInbox(directory), `${now.getTime()}-${command.id}.json`), JSON.stringify(command));
1192
1491
  return command;
1193
1492
  }
1194
1493
  __name(queueErrand, "queueErrand");
1195
1494
  function errandJournal(directory) {
1196
- const folder = join(directory, "journal");
1197
- const file = /* @__PURE__ */ __name((id) => join(folder, `${id}.json`), "file");
1495
+ const folder = join2(directory, "journal");
1496
+ const file = /* @__PURE__ */ __name((id) => join2(folder, `${id}.json`), "file");
1198
1497
  const read = /* @__PURE__ */ __name((path) => {
1199
1498
  try {
1200
- return JSON.parse(readFileSync(path, "utf8"));
1499
+ return JSON.parse(readFileSync2(path, "utf8"));
1201
1500
  } catch {
1202
1501
  return void 0;
1203
1502
  }
@@ -1219,7 +1518,7 @@ function errandJournal(directory) {
1219
1518
  names = readdirSync(folder);
1220
1519
  } catch {
1221
1520
  }
1222
- return names.filter((name) => /^[0-9a-f]{32}\.json$/.test(name)).flatMap((name) => read(join(folder, name)) ?? []);
1521
+ return names.filter((name) => /^[0-9a-f]{32}\.json$/.test(name)).flatMap((name) => read(join2(folder, name)) ?? []);
1223
1522
  }
1224
1523
  };
1225
1524
  }
@@ -1244,24 +1543,24 @@ __name(awaitErrand, "awaitErrand");
1244
1543
  function readCommand(path, id) {
1245
1544
  let fd;
1246
1545
  try {
1247
- fd = openSync2(path, constants3.O_RDONLY | constants3.O_NOFOLLOW | constants3.O_NONBLOCK);
1546
+ fd = openSync(path, constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK);
1248
1547
  } catch (error) {
1249
1548
  const code = error.code;
1250
1549
  return { problem: code === "ELOOP" || code === "EMLINK" ? "is a symbolic link" : `cannot be opened (${code})` };
1251
1550
  }
1252
1551
  try {
1253
- const problem = custody(fstatSync2(fd), "file");
1552
+ const problem = custody(fstatSync(fd), "file");
1254
1553
  if (problem) return { problem };
1255
1554
  let value;
1256
1555
  try {
1257
- value = JSON.parse(readFileSync(fd, "utf8"));
1556
+ value = JSON.parse(readFileSync2(fd, "utf8"));
1258
1557
  } catch {
1259
1558
  return { problem: "is not JSON" };
1260
1559
  }
1261
1560
  const invalid2 = commandProblem(value, id);
1262
1561
  return invalid2 ? { problem: `is not a valid command: ${invalid2}` } : value;
1263
1562
  } finally {
1264
- closeSync2(fd);
1563
+ closeSync(fd);
1265
1564
  }
1266
1565
  }
1267
1566
  __name(readCommand, "readCommand");
@@ -1286,7 +1585,7 @@ function watchErrandInbox(directory, handler, options) {
1286
1585
  ${name}`, `Errands: ${name} in the inbox is not a command file; it is left alone.`);
1287
1586
  continue;
1288
1587
  }
1289
- const path = join(inbox, name);
1588
+ const path = join2(inbox, name);
1290
1589
  try {
1291
1590
  if (handler.taken(id) || await handler.take(id, readCommand(path, id))) unlinkSync(path);
1292
1591
  } catch (error) {
@@ -1336,8 +1635,8 @@ __name(watchErrandInbox, "watchErrandInbox");
1336
1635
 
1337
1636
  // src/runner/mls.ts
1338
1637
  import { createHash as createHash4, createHmac } from "node:crypto";
1339
- import { constants as constants4 } from "node:fs";
1340
- import { open as open2, realpath as realpath3 } from "node:fs/promises";
1638
+ import { constants as constants3 } from "node:fs";
1639
+ import { open as open2, realpath as realpath2 } from "node:fs/promises";
1341
1640
  import { isAbsolute, resolve } from "node:path";
1342
1641
  var fail = /* @__PURE__ */ __name(() => {
1343
1642
  throw new Error("MLS_AUTHORITY_REQUIRED");
@@ -1453,8 +1752,8 @@ function object(value, required, optional = []) {
1453
1752
  __name(object, "object");
1454
1753
  async function loadMlsDevConfiguration(filePath, input) {
1455
1754
  if (!isMlsEnvironmentId(input.environmentId)) invalid();
1456
- if (typeof filePath !== "string" || !isAbsolute(filePath) || filePath.includes("\0") || resolve(filePath) !== filePath || await realpath3(filePath).catch(() => "") !== filePath) invalid();
1457
- const file = await open2(filePath, constants4.O_RDONLY | constants4.O_NOFOLLOW).catch(() => invalid());
1755
+ if (typeof filePath !== "string" || !isAbsolute(filePath) || filePath.includes("\0") || resolve(filePath) !== filePath || await realpath2(filePath).catch(() => "") !== filePath) invalid();
1756
+ const file = await open2(filePath, constants3.O_RDONLY | constants3.O_NOFOLLOW).catch(() => invalid());
1458
1757
  let value;
1459
1758
  try {
1460
1759
  const before = await file.stat();
@@ -1462,7 +1761,7 @@ async function loadMlsDevConfiguration(filePath, input) {
1462
1761
  const bytes = Buffer.alloc(MAX_CONFIGURATION_BYTES + 1);
1463
1762
  const { bytesRead } = await file.read(bytes, 0, bytes.length, 0);
1464
1763
  const after = await file.stat();
1465
- if (bytesRead !== before.size || after.size !== before.size || after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs || await realpath3(filePath) !== filePath) invalid();
1764
+ if (bytesRead !== before.size || after.size !== before.size || after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs || await realpath2(filePath) !== filePath) invalid();
1466
1765
  value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, bytesRead)));
1467
1766
  } catch {
1468
1767
  invalid();
@@ -1582,14 +1881,14 @@ __name(runMlsDevSetup, "runMlsDevSetup");
1582
1881
 
1583
1882
  // src/runner/mls-bundled.ts
1584
1883
  import { createHash as createHash5 } from "node:crypto";
1585
- import { existsSync, readFileSync as readFileSync2 } from "node:fs";
1586
- import { dirname as dirname4, join as join2 } from "node:path";
1884
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
1885
+ import { dirname as dirname4, join as join3 } from "node:path";
1587
1886
  import { fileURLToPath } from "node:url";
1588
1887
 
1589
1888
  // mls-custody.json
1590
1889
  var mls_custody_default = {
1591
1890
  schemaVersion: 1,
1592
- release: "mls-custody-cadb937436ce",
1891
+ release: "mls-custody-5ce777037493",
1593
1892
  protocolVersion: 1,
1594
1893
  rustVersion: "rustc 1.91.0 (f8297e351 2025-10-28)",
1595
1894
  sourceHashes: {
@@ -1601,15 +1900,15 @@ var mls_custody_default = {
1601
1900
  "mls/native/src/portable/json.rs": "d4e68556a051ffe9008686036597b1fd8239ae66fad67bd848c3f0e16bf8ccaf",
1602
1901
  "mls/native/src/portable/vault.rs": "14b9ff1e10ec4058e39a46511a489c7e82a9836e4175a768ac5d4b69c1dba8e5",
1603
1902
  "mls/native/src/portable/tests.rs": "7eadba2a49ed044470bb567d9b93fba44b64dc117b1da50a22187cff3dccb64e",
1604
- "mls/native/portable/build.mjs": "84c422a8a8aa6a7ec112a358bdeef080201aa4ea9a91f6d5726b05f46fa2a10c",
1903
+ "mls/native/portable/build.mjs": "ec81bfe27238b3117c4e855fa58b323e18a935ad7aa8ac18807376fb0bddc99b",
1605
1904
  "mls/core/open.rs": "8cdc03882f3b84dca72ab06afd437f162de01a0fe21327b019f17867dd59f5d0",
1606
1905
  "mls/core/protocol.rs": "7f3e021d6e4f1cc130b5dd3c98e0b0fa23edf813c72802c3fa3f8fb9e431c947",
1607
1906
  "mls/core/fault_storage.rs": "fdd6672a21f6ffee657142755637824541a5090b4f8a5fb4902f482556fb2793",
1608
1907
  "mls/core/mls_environment.rs": "390d11ee82ef8b108a7fffc7bb193ac7f3e470aa14e9643d029007aaaa29c4e4",
1609
- "mls/core/dev_identity.rs": "97df6bdb9daab66f0e3d4ca3b49bf9954fa06ac032c927d9ae6c83509b0166bd",
1610
- "mls/core/dev_key_package.rs": "3e7011fe8ab3044d80d84d04944068dbcf683141fcaba8a7a00bd2cfd103de72",
1908
+ "mls/core/dev_identity.rs": "df7282f0daa8babb3a9b9f397ad65d621784f9f54f5ca7a45fb3c8e82d58bbb1",
1909
+ "mls/core/dev_key_package.rs": "e8956f8dd4e365c22ac2356fb520759cdb613e344e1d44510ceee54b1cf9a418",
1611
1910
  "mls/core/dev_application.rs": "fafe245ace38e384da265bab2c12fef6b77c86b2c20477d00563ab891a54f94b",
1612
- "mls/core/dev_group.rs": "1d519a5d15394bbc163aef00e0ddacb9bb87631beaf8a13e52c76d628749152b",
1911
+ "mls/core/dev_group.rs": "d43f9ecdae79effdb9f177e76108487f81618dfc5b72b63618c41eff5896b2e0",
1613
1912
  "packages/test-fixtures/src/mlsDevEnrollmentProofV1.json": "50d7896e0a3a25b420412e84484e64f1e49480a3f298ee2d125f2f9188382810",
1614
1913
  "packages/test-fixtures/src/mlsDevCredentialV1.json": "24cd23af794b8a6e35caf050995020c6584b806de060969db4536f44b1188780",
1615
1914
  "packages/test-fixtures/src/mlsDevCredentialReconciliationV1.json": "bd4bbb3a2e5dc7921867a2e5936d1da77d536740b4360012c1f741f9caed2826",
@@ -1620,10 +1919,10 @@ var mls_custody_default = {
1620
1919
  platforms: {
1621
1920
  "darwin-arm64": {
1622
1921
  target: "aarch64-apple-darwin",
1623
- sha256: "918ae350f1d7bd339c4eee548f6668943217f5fa66022bb93f41a02992dd296c",
1922
+ sha256: "b09c82209cc7884c4521a310c727a8faaf387e9d0603443b1c1d9a55d87b7103",
1624
1923
  bytes: 2932976,
1625
- unitTestsPassed: 95,
1626
- commit: "cdc6ce8fd9d7a47bc5c796d19a72828e4cc9db06",
1924
+ unitTestsPassed: 96,
1925
+ commit: "b9d1a4008749d1f144fd8db6ca4066de20422184",
1627
1926
  builder: {
1628
1927
  macos: "26.3.1",
1629
1928
  sdk: "26.4",
@@ -1632,10 +1931,10 @@ var mls_custody_default = {
1632
1931
  },
1633
1932
  "linux-x64": {
1634
1933
  target: "x86_64-unknown-linux-gnu",
1635
- sha256: "1c5a12d0213c53bdf59b5f36a7390a4b4c17f8a53c95e760ad8cb4905297d256",
1636
- bytes: 3389816,
1637
- unitTestsPassed: 95,
1638
- commit: "cdc6ce8fd9d7a47bc5c796d19a72828e4cc9db06",
1934
+ sha256: "0b20495694534d9792b6920f1e3f375a2182b237a44cb2d384080dd17e7f83bb",
1935
+ bytes: 3389880,
1936
+ unitTestsPassed: 96,
1937
+ commit: "da679f4c60e2271c20b1f1958d0e5f6bfce6ef2d",
1639
1938
  builder: {
1640
1939
  image: "quay.io/pypa/manylinux_2_28_x86_64@sha256:e0adf18521ad35192df733a6c62d35df7801957d24e0470238aacecfab2188a3",
1641
1940
  dockerfileSha256: "fe8d4895b8bd304954441fd27c6273c8c00baa10a1ad912d3c3851b193b8ad75"
@@ -1643,15 +1942,28 @@ var mls_custody_default = {
1643
1942
  },
1644
1943
  "linux-arm64": {
1645
1944
  target: "aarch64-unknown-linux-gnu",
1646
- sha256: "371c5754cf0c437f561e8c49b3ea654c037311b951e11b1c5a29fb6b91656619",
1945
+ sha256: "649d90ba74a42b34025292bd942068fc08979e35f66c9d6ad358469f5022fac7",
1647
1946
  bytes: 3234232,
1648
- unitTestsPassed: 95,
1649
- commit: "cdc6ce8fd9d7a47bc5c796d19a72828e4cc9db06",
1947
+ unitTestsPassed: 96,
1948
+ commit: "b9d1a4008749d1f144fd8db6ca4066de20422184",
1650
1949
  builder: {
1651
1950
  image: "quay.io/pypa/manylinux_2_28_aarch64@sha256:a3e02308c822fb6ce0d12248b0c941817ba5e1a1bc9e7bb5929f29c50eaa40a9",
1652
1951
  dockerfileSha256: "fe8d4895b8bd304954441fd27c6273c8c00baa10a1ad912d3c3851b193b8ad75"
1653
1952
  }
1654
1953
  }
1954
+ },
1955
+ wasm: {
1956
+ release: "mls-wasm-ddfdcc6039d7",
1957
+ commit: "ba57ec95af11e8b560fd7d71431c0ec14542d71e",
1958
+ rustVersion: "rustc 1.91.0 (f8297e351 2025-10-28)",
1959
+ wasm: {
1960
+ sha256: "137454981d2b6236dffa3dae0a7253e5f6d30578bce2cf50476adc8ca3d9aa02",
1961
+ bytes: 1794989
1962
+ },
1963
+ glue: {
1964
+ sha256: "744eb2267560f22aa54540fe9aa988588a7c27641610aae729b2ed75232e2a24",
1965
+ bytes: 13579
1966
+ }
1655
1967
  }
1656
1968
  };
1657
1969
 
@@ -1687,40 +1999,77 @@ var MLS_ENVIRONMENT_PINS = {
1687
1999
  };
1688
2000
  function packageRoot() {
1689
2001
  for (let directory = dirname4(fileURLToPath(import.meta.url)); ; directory = dirname4(directory)) {
1690
- const manifest = join2(directory, "package.json");
1691
- if (existsSync(manifest) && JSON.parse(readFileSync2(manifest, "utf8")).name === "@canonmsg/agent-sdk") return directory;
2002
+ const manifest = join3(directory, "package.json");
2003
+ if (existsSync2(manifest) && JSON.parse(readFileSync3(manifest, "utf8")).name === "@canonmsg/agent-sdk") return directory;
1692
2004
  if (dirname4(directory) === directory) return void 0;
1693
2005
  }
1694
2006
  }
1695
2007
  __name(packageRoot, "packageRoot");
2008
+ function helperRunsOnLibc() {
2009
+ if (process.platform !== "linux") return true;
2010
+ const report = process.report;
2011
+ const { excludeNetwork } = report;
2012
+ report.excludeNetwork = true;
2013
+ try {
2014
+ const version = report.getReport().header?.glibcVersionRuntime;
2015
+ const [major = 0, minor = 0] = (version ?? "").split(".").map(Number);
2016
+ return major > 2 || major === 2 && minor >= 28;
2017
+ } finally {
2018
+ report.excludeNetwork = excludeNetwork;
2019
+ }
2020
+ }
2021
+ __name(helperRunsOnLibc, "helperRunsOnLibc");
1696
2022
  function bundledMlsCustodyHelper() {
1697
2023
  const platform = `${process.platform}-${process.arch}`;
1698
2024
  const pin = mls_custody_default.platforms[platform];
1699
- const root = pin && packageRoot();
1700
- const path = root && join2(root, "dist", "mls", platform, "canon-mls-custody");
1701
- if (!pin || !path || !existsSync(path)) return void 0;
1702
- if (createHash5("sha256").update(readFileSync2(path)).digest("hex") !== pin.sha256) {
2025
+ const root = pin && helperRunsOnLibc() && packageRoot();
2026
+ const path = root && join3(root, "dist", "mls", platform, "canon-mls-custody");
2027
+ if (!pin || !path || !existsSync2(path)) return void 0;
2028
+ if (createHash5("sha256").update(readFileSync3(path)).digest("hex") !== pin.sha256) {
1703
2029
  throw new Error(`${path} is not the custody helper this package pins; reinstall @canonmsg/agent-sdk.`);
1704
2030
  }
1705
2031
  return { path, sha256: pin.sha256 };
1706
2032
  }
1707
2033
  __name(bundledMlsCustodyHelper, "bundledMlsCustodyHelper");
2034
+ function bundledMlsWasmCore() {
2035
+ const root = packageRoot();
2036
+ if (!root) return void 0;
2037
+ const directory = join3(root, "dist", "mls", "wasm");
2038
+ const hostPath = join3(root, "dist", "mls-wasm-host.js"), wasmPath = join3(directory, "canon_mls_wasm_bg.wasm"), gluePath = join3(directory, "canon_mls_wasm.js");
2039
+ if (![hostPath, wasmPath, gluePath].every((path) => existsSync2(path))) return void 0;
2040
+ for (const [path, pin] of [[wasmPath, mls_custody_default.wasm.wasm.sha256], [gluePath, mls_custody_default.wasm.glue.sha256]]) {
2041
+ if (createHash5("sha256").update(readFileSync3(path)).digest("hex") !== pin) throw new Error(`${path} is not the MLS core this package pins; reinstall @canonmsg/agent-sdk.`);
2042
+ }
2043
+ return { hostPath, wasmPath, wasmSha256: mls_custody_default.wasm.wasm.sha256, gluePath, glueSha256: mls_custody_default.wasm.glue.sha256 };
2044
+ }
2045
+ __name(bundledMlsWasmCore, "bundledMlsWasmCore");
1708
2046
 
1709
2047
  // src/runner/mls-managed.ts
1710
2048
  import { createHash as createHash6 } from "node:crypto";
1711
- import { chmodSync, copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync2 } from "node:fs";
1712
- import { dirname as dirname5, join as join3 } from "node:path";
2049
+ import { chmodSync, copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync4, renameSync } from "node:fs";
2050
+ import { dirname as dirname5, join as join4 } from "node:path";
1713
2051
  var sha256 = /* @__PURE__ */ __name((bytes) => createHash6("sha256").update(bytes).digest("hex"), "sha256");
2052
+ function custodyFor(home, environmentId) {
2053
+ const root = join4(home, "mls", environmentId);
2054
+ const keptOnCore = existsSync3(root) && readdirSync2(root).some((name) => existsSync3(join4(root, name, "wrapping.key")));
2055
+ const kept = keptOnCore ? bundledMlsWasmCore() : void 0;
2056
+ if (kept) return { wasm: kept };
2057
+ const helper = bundledMlsCustodyHelper();
2058
+ if (helper) return { helper };
2059
+ const wasm = bundledMlsWasmCore();
2060
+ return wasm ? { wasm } : {};
2061
+ }
2062
+ __name(custodyFor, "custodyFor");
1714
2063
  function defaultMlsDevice(home, environmentId) {
1715
2064
  if (!isMlsEnvironmentId(environmentId)) return void 0;
1716
- const helper = bundledMlsCustodyHelper();
1717
- return helper ? managedMlsDevice({ home, environmentId, helper }) : void 0;
2065
+ const custody2 = custodyFor(home, environmentId);
2066
+ return custody2.helper || custody2.wasm ? managedMlsDevice({ home, environmentId, ...custody2 }) : void 0;
1718
2067
  }
1719
2068
  __name(defaultMlsDevice, "defaultMlsDevice");
1720
2069
  function managedMlsDevice(options) {
1721
2070
  const { environmentId } = options;
1722
- const helper = options.helper ?? bundledMlsCustodyHelper();
1723
- if (!helper) {
2071
+ const { helper, wasm } = options.helper ? { helper: options.helper, wasm: void 0 } : options.wasm ? options : custodyFor(options.home, environmentId);
2072
+ if (!helper && !wasm) {
1724
2073
  throw new Error(`@canonmsg/agent-sdk ships no MLS custody helper for ${process.platform}-${process.arch}: build one from mls/native in the Canon repository and give the runner --mls-dev-config <path> (docs/design/mls-dev-agent-host.md).`);
1725
2074
  }
1726
2075
  const pins = options.pins ?? MLS_ENVIRONMENT_PINS[environmentId];
@@ -1731,24 +2080,27 @@ function managedMlsDevice(options) {
1731
2080
  issuerKeys: pins.issuerKeys,
1732
2081
  receiptKeys: pins.receiptKeys
1733
2082
  });
1734
- const root = join3(options.home, "mls", environmentId);
1735
- const directory = /* @__PURE__ */ __name((context) => join3(root, sha256(Buffer.from(JSON.stringify([
2083
+ const root = join4(options.home, "mls", environmentId);
2084
+ const directory = /* @__PURE__ */ __name((context) => join4(root, sha256(Buffer.from(JSON.stringify([
1736
2085
  context.installationId,
1737
2086
  context.principal.type,
1738
2087
  context.principal.id,
1739
2088
  "ownershipVersion" in context.principal ? context.principal.ownershipVersion : null
1740
2089
  ]))).slice(0, 32)), "directory");
1741
- const vault = /* @__PURE__ */ __name((context) => join3(directory(context), "vault", "vault.bin"), "vault");
2090
+ const vault = /* @__PURE__ */ __name((context) => join4(directory(context), "vault", "vault.bin"), "vault");
2091
+ const wrappingKey = /* @__PURE__ */ __name((context) => join4(directory(context), "wrapping.key"), "wrappingKey");
2092
+ const made = /* @__PURE__ */ __name((context) => wasm ? wrappingKey(context) : vault(context), "made");
2093
+ const stranded = /* @__PURE__ */ __name((context) => new Error(wasm ? `This agent's MLS device was made with the native custody helper, which does not run on this host (${process.platform}-${process.arch}). It is still in ${directory(context)}: run the agent with this home where the helper runs (macOS on Apple silicon, or Linux with glibc 2.28 or later), with the architecture it was set up on. A device cannot move to the WASM core, and a new home is refused, since Canon keeps an agent to one installation.` : `This agent's MLS device was made with the WASM core, which this install of @canonmsg/agent-sdk does not have. It is still in ${directory(context)}: reinstall the package. A device cannot move to the native helper, and a new home is refused, since Canon keeps an agent to one installation.`), "stranded");
1742
2094
  let executable;
1743
2095
  function install() {
1744
- const target = join3(root, "bin", helper.sha256, "canon-mls-custody");
1745
- if (existsSync2(target) && sha256(readFileSync3(target)) === helper.sha256) return target;
1746
- mkdirSync3(dirname5(target), { recursive: true, mode: 448 });
2096
+ const target = join4(root, "bin", helper.sha256, "canon-mls-custody");
2097
+ if (existsSync3(target) && sha256(readFileSync4(target)) === helper.sha256) return target;
2098
+ mkdirSync2(dirname5(target), { recursive: true, mode: 448 });
1747
2099
  const temporary = `${target}.${process.pid}.tmp`;
1748
2100
  copyFileSync(helper.path, temporary);
1749
2101
  chmodSync(temporary, 448);
1750
- if (sha256(readFileSync3(temporary)) !== helper.sha256) throw new Error(`${helper.path} is not the pinned custody helper`);
1751
- renameSync2(temporary, target);
2102
+ if (sha256(readFileSync4(temporary)) !== helper.sha256) throw new Error(`${helper.path} is not the pinned custody helper`);
2103
+ renameSync(temporary, target);
1752
2104
  return target;
1753
2105
  }
1754
2106
  __name(install, "install");
@@ -1758,9 +2110,24 @@ function managedMlsDevice(options) {
1758
2110
  let found = devices.get(key);
1759
2111
  if (!found) {
1760
2112
  found = (async () => {
1761
- if (!provisioning && !existsSync2(vault(context))) throw new NodeMlsDevCustodyError();
1762
- mkdirSync3(dirname5(vault(context)), { recursive: true, mode: 448 });
1763
- const unlock = join3(key, "unlock");
2113
+ if (!provisioning && !existsSync3(made(context))) throw new NodeMlsDevCustodyError();
2114
+ mkdirSync2(dirname5(made(context)), { recursive: true, mode: 448 });
2115
+ if (wasm) {
2116
+ if (process.platform === "win32") {
2117
+ if (provisioning) secureWindowsDirectory(key);
2118
+ else assertPrivateWindowsFile(wrappingKey(context));
2119
+ }
2120
+ const { revision: _revision, ...identity } = context;
2121
+ return wasmMlsCustody({
2122
+ core: wasm,
2123
+ context: identity,
2124
+ keyPath: wrappingKey(context),
2125
+ issuerKeys: configuration.issuerKeys,
2126
+ receiptKeys: configuration.receiptKeys,
2127
+ ...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
2128
+ });
2129
+ }
2130
+ const unlock = join4(key, "unlock");
1764
2131
  if (provisioning) privateKey(unlock, 32);
1765
2132
  const text3 = `${JSON.stringify({
1766
2133
  version: 1,
@@ -1776,8 +2143,8 @@ function managedMlsDevice(options) {
1776
2143
  receiptKeys: configuration.receiptKeys
1777
2144
  }, null, 2)}
1778
2145
  `;
1779
- const file = join3(key, "native.json");
1780
- if (!existsSync2(file) || readFileSync3(file, "utf8") !== text3) writeDurable(file, text3);
2146
+ const file = join4(key, "native.json");
2147
+ if (!existsSync3(file) || readFileSync4(file, "utf8") !== text3) writeDurable(file, text3);
1781
2148
  return createPortableNodeMlsDevCustody({
1782
2149
  executablePath: executable ??= install(),
1783
2150
  executableSha256: helper.sha256,
@@ -1803,8 +2170,18 @@ function managedMlsDevice(options) {
1803
2170
  return {
1804
2171
  configuration,
1805
2172
  custody: custody2,
1806
- provision: /* @__PURE__ */ __name((context) => device(context, true).then((open4) => open4.provision(context)), "provision"),
1807
- provisioned: /* @__PURE__ */ __name((context) => existsSync2(vault(context)), "provisioned"),
2173
+ async provision(context) {
2174
+ await (await device(context, true)).provision(context);
2175
+ if (wasm && process.platform === "win32") {
2176
+ restrictWindowsFile(wrappingKey(context));
2177
+ assertPrivateWindowsFile(wrappingKey(context));
2178
+ }
2179
+ },
2180
+ provisioned(context) {
2181
+ if (existsSync3(made(context))) return true;
2182
+ if (existsSync3(wasm ? vault(context) : wrappingKey(context))) throw stranded(context);
2183
+ return false;
2184
+ },
1808
2185
  async close() {
1809
2186
  await Promise.allSettled([...devices.values()].map((open4) => open4.then((custody3) => custody3.close())));
1810
2187
  }
@@ -1814,10 +2191,10 @@ __name(managedMlsDevice, "managedMlsDevice");
1814
2191
 
1815
2192
  // src/runner/relay.ts
1816
2193
  import { createHmac as createHmac2, timingSafeEqual } from "node:crypto";
1817
- import { closeSync as closeSync3, constants as constants5, fsyncSync as fsyncSync2, mkdirSync as mkdirSync4, openSync as openSync3, renameSync as renameSync3, writeSync as writeSync2 } from "node:fs";
1818
- import { lstat as lstat2, open as open3, realpath as realpath4 } from "node:fs/promises";
2194
+ import { closeSync as closeSync2, constants as constants4, fsyncSync, mkdirSync as mkdirSync3, openSync as openSync2, renameSync as renameSync2, writeSync } from "node:fs";
2195
+ import { lstat, open as open3, realpath as realpath3 } from "node:fs/promises";
1819
2196
  import { createServer } from "node:http";
1820
- import { isAbsolute as isAbsolute2, join as join4, resolve as resolve2 } from "node:path";
2197
+ import { isAbsolute as isAbsolute2, join as join5, resolve as resolve2 } from "node:path";
1821
2198
  var TOLERANCE_S = 300;
1822
2199
  function relaySecretKey(secret) {
1823
2200
  if (typeof secret !== "string" || !/^whsec_[A-Za-z0-9+/]+={0,2}$/.test(secret)) throw new Error("The relay channel secret must be a whsec_ value");
@@ -1950,8 +2327,8 @@ async function readPrivateFile(path, maximum, what) {
1950
2327
  const fail2 = /* @__PURE__ */ __name(() => {
1951
2328
  throw new Error(`${what} must be a private file (0600, owned by this user, not a link): ${path}`);
1952
2329
  }, "fail");
1953
- if (typeof path !== "string" || !isAbsolute2(path) || path.includes("\0") || resolve2(path) !== path || await realpath4(path).catch(() => "") !== path) fail2();
1954
- const file = await open3(path, constants5.O_RDONLY | constants5.O_NOFOLLOW).catch(() => fail2());
2330
+ if (typeof path !== "string" || !isAbsolute2(path) || path.includes("\0") || resolve2(path) !== path || await realpath3(path).catch(() => "") !== path) fail2();
2331
+ const file = await open3(path, constants4.O_RDONLY | constants4.O_NOFOLLOW).catch(() => fail2());
1955
2332
  try {
1956
2333
  const info = await file.stat();
1957
2334
  if (!info.isFile() || info.nlink !== 1 || info.uid !== process.getuid?.() || (info.mode & 63) !== 0 || info.size < 1 || info.size > maximum) fail2();
@@ -1988,11 +2365,11 @@ async function loadRelayConfiguration(path, input) {
1988
2365
  return { environmentId: value.environmentId, agentId: value.agentId, listen: { host: listen.host, port: listen.port }, secret };
1989
2366
  }
1990
2367
  __name(loadRelayConfiguration, "loadRelayConfiguration");
1991
- var relayDirectory = /* @__PURE__ */ __name((home) => join4(home, "relay"), "relayDirectory");
1992
- var credentialFile = /* @__PURE__ */ __name((home) => join4(relayDirectory(home), "credential.json"), "credentialFile");
2368
+ var relayDirectory = /* @__PURE__ */ __name((home) => join5(home, "relay"), "relayDirectory");
2369
+ var credentialFile = /* @__PURE__ */ __name((home) => join5(relayDirectory(home), "credential.json"), "credentialFile");
1993
2370
  async function readRelayCredential(home, relay) {
1994
2371
  const path = credentialFile(home);
1995
- if (!await lstat2(path).then(() => true, () => false)) return void 0;
2372
+ if (!await lstat(path).then(() => true, () => false)) return void 0;
1996
2373
  let value;
1997
2374
  try {
1998
2375
  value = JSON.parse(await readPrivateFile(path, 4 * 1024, "The relay credential"));
@@ -2015,33 +2392,33 @@ function writeRelayCredential(home, relay, delivery) {
2015
2392
  generation: delivery.generation,
2016
2393
  credential: delivery.credential
2017
2394
  };
2018
- mkdirSync4(relayDirectory(home), { recursive: true, mode: 448 });
2395
+ mkdirSync3(relayDirectory(home), { recursive: true, mode: 448 });
2019
2396
  const file = credentialFile(home);
2020
2397
  const temporary = `${file}.${process.pid}.tmp`;
2021
- const fd = openSync3(temporary, "w", 384);
2398
+ const fd = openSync2(temporary, "w", 384);
2022
2399
  try {
2023
- writeSync2(fd, JSON.stringify(value));
2024
- fsyncSync2(fd);
2400
+ writeSync(fd, JSON.stringify(value));
2401
+ fsyncSync(fd);
2025
2402
  } finally {
2026
- closeSync3(fd);
2403
+ closeSync2(fd);
2027
2404
  }
2028
- renameSync3(temporary, file);
2405
+ renameSync2(temporary, file);
2029
2406
  return value;
2030
2407
  }
2031
2408
  __name(writeRelayCredential, "writeRelayCredential");
2032
2409
  function relayRunnerHome(home, installationId) {
2033
2410
  if (!CONNECTOR_RELAY_INSTALLATION_PATTERN.test(installationId)) throw new Error("Invalid relay installation");
2034
- const directory = join4(relayDirectory(home), installationId.slice(CONNECTOR_RELAY_INSTALLATION_PREFIX.length));
2035
- mkdirSync4(directory, { recursive: true, mode: 448 });
2411
+ const directory = join5(relayDirectory(home), installationId.slice(CONNECTOR_RELAY_INSTALLATION_PREFIX.length));
2412
+ mkdirSync3(directory, { recursive: true, mode: 448 });
2036
2413
  return directory;
2037
2414
  }
2038
2415
  __name(relayRunnerHome, "relayRunnerHome");
2039
2416
 
2040
2417
  // src/runner/runner.ts
2041
2418
  import { createHash as createHash8 } from "node:crypto";
2042
- import { readFileSync as readFileSync5, rmSync as rmSync3 } from "node:fs";
2043
- import { realpath as realpath5, stat as stat2 } from "node:fs/promises";
2044
- import { basename as basename3, isAbsolute as isAbsolute3, join as join6, sep } from "node:path";
2419
+ import { readFileSync as readFileSync6, rmSync as rmSync2 } from "node:fs";
2420
+ import { realpath as realpath4, stat as stat2 } from "node:fs/promises";
2421
+ import { basename as basename4, isAbsolute as isAbsolute3, join as join7, sep } from "node:path";
2045
2422
 
2046
2423
  // ../agent-tools/src/communication-tool.ts
2047
2424
  var CANON_COMMUNICATE_TOOL_NAME = "communicate";
@@ -2536,8 +2913,8 @@ __name(createLivePublisher, "createLivePublisher");
2536
2913
 
2537
2914
  // src/runner/mls-ask.ts
2538
2915
  import { createHash as createHash7 } from "node:crypto";
2539
- import { readdirSync as readdirSync2, readFileSync as readFileSync4, rmSync as rmSync2 } from "node:fs";
2540
- import { join as join5 } from "node:path";
2916
+ import { readdirSync as readdirSync3, readFileSync as readFileSync5, rmSync } from "node:fs";
2917
+ import { join as join6 } from "node:path";
2541
2918
  var sha2562 = /* @__PURE__ */ __name((value) => createHash7("sha256").update(value).digest("hex"), "sha256");
2542
2919
  var pause = /* @__PURE__ */ __name((ms) => new Promise((resolve3) => {
2543
2920
  setTimeout(resolve3, ms).unref?.();
@@ -2592,9 +2969,9 @@ function createMlsAsks(options, approvalScope2) {
2592
2969
  });
2593
2970
  }
2594
2971
  });
2595
- const file = /* @__PURE__ */ __name((conversationId, requestId) => join5(options.directory, `${sha2562(JSON.stringify([conversationId, requestId])).slice(0, 32)}.json`), "file");
2972
+ const file = /* @__PURE__ */ __name((conversationId, requestId) => join6(options.directory, `${sha2562(JSON.stringify([conversationId, requestId])).slice(0, 32)}.json`), "file");
2596
2973
  const save = /* @__PURE__ */ __name((journal) => writeDurable(file(journal.conversationId, journal.requestId), JSON.stringify(journal)), "save");
2597
- const forget = /* @__PURE__ */ __name((journal) => rmSync2(file(journal.conversationId, journal.requestId), { force: true }), "forget");
2974
+ const forget = /* @__PURE__ */ __name((journal) => rmSync(file(journal.conversationId, journal.requestId), { force: true }), "forget");
2598
2975
  const operationOf = /* @__PURE__ */ __name((conversationId, messageId) => `runner-mls-ask:${sha2562(JSON.stringify([conversationId, messageId])).slice(0, 48)}`, "operationOf");
2599
2976
  const withdrawalOf = /* @__PURE__ */ __name((journal) => operationOf(journal.conversationId, `${journal.requestId}:withdraw`), "withdrawalOf");
2600
2977
  const audienceChanged = /* @__PURE__ */ __name((error) => codeOf(error) === "MLS_DEV_AUDIENCE_CHANGED", "audienceChanged");
@@ -2929,18 +3306,18 @@ function createMlsAsks(options, approvalScope2) {
2929
3306
  async recover() {
2930
3307
  let names;
2931
3308
  try {
2932
- names = readdirSync2(options.directory).filter((name) => name.endsWith(".json"));
3309
+ names = readdirSync3(options.directory).filter((name) => name.endsWith(".json"));
2933
3310
  } catch {
2934
3311
  return;
2935
3312
  }
2936
3313
  for (const name of names) {
2937
3314
  let journal;
2938
3315
  try {
2939
- journal = JSON.parse(readFileSync4(join5(options.directory, name), "utf8"));
3316
+ journal = JSON.parse(readFileSync5(join6(options.directory, name), "utf8"));
2940
3317
  if (journal.version !== 1 || typeof journal.conversationId !== "string" || typeof journal.requestId !== "string" || !Array.isArray(journal.sends) || journal.sends.some((sent) => typeof sent?.operationId !== "string" || typeof sent.messageId !== "string")) throw new Error();
2941
3318
  for (const sent of journal.sends) sent.audience = snapshotMlsAudience(sent.audience);
2942
3319
  } catch {
2943
- rmSync2(join5(options.directory, name), { force: true });
3320
+ rmSync(join6(options.directory, name), { force: true });
2944
3321
  continue;
2945
3322
  }
2946
3323
  try {
@@ -3044,7 +3421,7 @@ var STATE_LIMIT_BYTES = 64 * 1024;
3044
3421
  var FAILURE_NOTICE = "Sorry, something went wrong and I could not finish this reply.";
3045
3422
  var STOPS_SHORT_NOTICE = "Sorry, my reply could not be delivered in full.";
3046
3423
  var RESTART_NOTICE = "Sorry, I was restarted before I could finish replying to this. Please send it again if you still need an answer.";
3047
- var CLIENT_TYPES = { code: "generic", claude: "claude-code", codex: "codex", connector: "generic" };
3424
+ var CLIENT_TYPES = { code: "generic", claude: "claude-code", codex: "codex", acp: "generic", connector: "generic" };
3048
3425
  var objectSchema = /* @__PURE__ */ __name((properties, required = []) => ({ type: "object", properties, required, additionalProperties: false }), "objectSchema");
3049
3426
  var MAX_QUESTION = 4e3;
3050
3427
  var MAX_OPTION = 120;
@@ -3344,9 +3721,9 @@ async function createRunner(options) {
3344
3721
  current.lane = current.lane.then(publish2).catch(() => void 0);
3345
3722
  }, "enqueue")
3346
3723
  }), "livePublisher");
3347
- const stateFile = /* @__PURE__ */ __name((session) => join6(home, "runner", options.environmentId, agentId, "state", driver.name, `${sha2563(JSON.stringify([scope, session]))}.json`), "stateFile");
3724
+ const stateFile = /* @__PURE__ */ __name((session) => join7(home, "runner", options.environmentId, agentId, "state", driver.name, `${sha2563(JSON.stringify([scope, session]))}.json`), "stateFile");
3348
3725
  const resetSession = /* @__PURE__ */ __name(async (session) => {
3349
- rmSync3(stateFile(session), { force: true });
3726
+ rmSync2(stateFile(session), { force: true });
3350
3727
  mlsAsks?.endSession(session);
3351
3728
  await driver.reset?.(session);
3352
3729
  }, "resetSession");
@@ -3617,7 +3994,7 @@ ${error instanceof Error ? error.message : String(error)}`;
3617
3994
  } finally {
3618
3995
  turn.ended.abort();
3619
3996
  bound.release();
3620
- rmSync3(conversationMediaCacheDir(inboundMedia(current.id)), { recursive: true, force: true });
3997
+ rmSync2(conversationMediaCacheDir(inboundMedia(current.id)), { recursive: true, force: true });
3621
3998
  }
3622
3999
  }
3623
4000
  __name(execute, "execute");
@@ -3732,7 +4109,7 @@ ${error instanceof Error ? error.message : String(error)}`;
3732
4109
  return check;
3733
4110
  }
3734
4111
  __name(reaudience, "reaudience");
3735
- const inboundMedia = /* @__PURE__ */ __name((conversationId) => ({ agentId: "inbound", conversationId, rootDir: join6(home, "runner", options.environmentId, agentId) }), "inboundMedia");
4112
+ const inboundMedia = /* @__PURE__ */ __name((conversationId) => ({ agentId: "inbound", conversationId, rootDir: join7(home, "runner", options.environmentId, agentId) }), "inboundMedia");
3736
4113
  async function materialize(current, turn) {
3737
4114
  if (driver.capabilities.remote) return;
3738
4115
  for (const { payload: { message: message2 } } of turn.inputs) {
@@ -3775,7 +4152,7 @@ ${error instanceof Error ? error.message : String(error)}`;
3775
4152
  const file = await admitFile(path);
3776
4153
  if (typeof file === "string") return { result: toolText(file, true) };
3777
4154
  turn.outbox.push(file.path);
3778
- return { result: toolText(`${basename3(file.path)} will be sent with your reply.`) };
4155
+ return { result: toolText(`${basename4(file.path)} will be sent with your reply.`) };
3779
4156
  }, "send_file"),
3780
4157
  communicate: /* @__PURE__ */ __name(async (args) => ({ result: await communicate(current, turn, args) }), "communicate"),
3781
4158
  // RUN-25 through a tool: the same `ask`, so the same responder, card, expiry, waiting_input and paused deadline;
@@ -3922,7 +4299,7 @@ ${JSON.stringify(result, null, 2)}`);
3922
4299
  async function admitFile(path) {
3923
4300
  if (typeof path !== "string" || !isAbsolute3(path)) return "send_file needs an absolute file path";
3924
4301
  try {
3925
- const real = await realpath5(path);
4302
+ const real = await realpath4(path);
3926
4303
  if (!roots.some((root) => real.startsWith(root.endsWith(sep) ? root : `${root}${sep}`))) return `${path} is outside the directories this agent may share from`;
3927
4304
  const info = await stat2(real);
3928
4305
  if (!info.isFile() || info.size < 1 || info.size > MAX_CANON_MEDIA_BYTES) return `${path} must be a file from 1 byte to 100 MiB`;
@@ -3940,13 +4317,13 @@ ${JSON.stringify(result, null, 2)}`);
3940
4317
  for (const candidate of candidates) {
3941
4318
  const file = await admitFile(candidate);
3942
4319
  if (typeof file === "string") {
3943
- refused2.push(typeof candidate === "string" ? basename3(candidate) : "a file");
4320
+ refused2.push(typeof candidate === "string" ? basename4(candidate) : "a file");
3944
4321
  continue;
3945
4322
  }
3946
4323
  if (seen.has(file.path)) continue;
3947
4324
  seen.add(file.path);
3948
4325
  if (attachments.length >= MAX_GROUP_PARTS - 1) {
3949
- refused2.push(basename3(file.path));
4326
+ refused2.push(basename4(file.path));
3950
4327
  continue;
3951
4328
  }
3952
4329
  try {
@@ -3954,14 +4331,14 @@ ${JSON.stringify(result, null, 2)}`);
3954
4331
  const attachmentId = `${turn.id}-${seen.size}`;
3955
4332
  const [prepared] = await media.prepare({ conversationId, attachmentId, files: [{
3956
4333
  source: file.path,
3957
- descriptor: { mimeType: inferUploadMimeType(file.path), sizeBytes: file.size, fileName: basename3(file.path) }
4334
+ descriptor: { mimeType: inferUploadMimeType(file.path), sizeBytes: file.size, fileName: basename4(file.path) }
3958
4335
  }] }, { signal: turn.retire.signal });
3959
4336
  attachments.push(prepared);
3960
4337
  flows.push(`${conversationId}:upload:${attachmentId}`);
3961
4338
  } catch (error) {
3962
4339
  if (turn.retire.signal.aborted) break;
3963
4340
  warn("file upload")(error);
3964
- refused2.push(basename3(file.path));
4341
+ refused2.push(basename4(file.path));
3965
4342
  }
3966
4343
  }
3967
4344
  return { attachments, flows, refused: refused2 };
@@ -3993,7 +4370,9 @@ ${JSON.stringify(result, null, 2)}`);
3993
4370
  const skip = new Set([...turn.inputs, ...current.queue].map(({ payload }) => payload.message.id));
3994
4371
  const at = Math.max(...turn.inputs.map(({ payload }) => recent.findIndex((message2) => message2.id === payload.message.id)));
3995
4372
  const before = /* @__PURE__ */ __name((message2, index) => message2.senderId === agentId || (at >= 0 ? index > at : !first.createdAt || String(message2.createdAt ?? "") < first.createdAt), "before");
3996
- const window = recent.filter((message2, index) => before(message2, index) && !message2.deleted && !skip.has(message2.id)).slice(0, HISTORY_WINDOW);
4373
+ const ownerId = driver.capabilities.ownerHistoryOnly ? (await client.getAgentMe()).ownerId : void 0;
4374
+ const allowed = /* @__PURE__ */ __name((message2) => ownerId === void 0 || message2.senderId === agentId || message2.senderId === ownerId, "allowed");
4375
+ const window = recent.filter((message2, index) => before(message2, index) && !message2.deleted && !skip.has(message2.id) && allowed(message2)).slice(0, HISTORY_WINDOW);
3997
4376
  const sameAudience = turn.mls ? await Promise.all(window.map((message2) => endpoint.mlsDevGroups.readVerifiedMessageBinding({ conversationId: current.id, messageId: message2.id }).then((binding) => !!binding && JSON.stringify(binding.head) === JSON.stringify(turn.mls.head), () => false))) : void 0;
3998
4377
  return window.filter((_, index) => !sameAudience || sameAudience[index]).reverse();
3999
4378
  } catch (error) {
@@ -4037,7 +4416,7 @@ ${JSON.stringify(result, null, 2)}`);
4037
4416
  } else if (answered2 && turn.mls && resultFiles.length) {
4038
4417
  text3 = `${text3}
4039
4418
 
4040
- (${ENCRYPTED_FILES}: ${resultFiles.map((file) => typeof file === "string" ? basename3(file) : "a file").join(", ")}.)`;
4419
+ (${ENCRYPTED_FILES}: ${resultFiles.map((file) => typeof file === "string" ? basename4(file) : "a file").join(", ")}.)`;
4041
4420
  } else if (answered2 && !turn.mls) {
4042
4421
  const files = await attach(current.id, turn, [...turn.outbox, ...resultFiles]);
4043
4422
  ({ attachments, flows } = files);
@@ -4203,20 +4582,20 @@ ${JSON.stringify(result, null, 2)}`);
4203
4582
  for (const candidate of candidates) {
4204
4583
  const file = await admitFile(candidate);
4205
4584
  if (typeof file === "string") {
4206
- refused2.push(typeof candidate === "string" ? basename3(candidate) : "a file");
4585
+ refused2.push(typeof candidate === "string" ? basename4(candidate) : "a file");
4207
4586
  continue;
4208
4587
  }
4209
4588
  if (seen.has(file.path)) continue;
4210
4589
  seen.add(file.path);
4211
4590
  const mimeType = inferUploadMimeType(file.path);
4212
- if (items.length >= MAX_GROUP_PARTS - 1 || !resolveAllowedUploadMimeType({ mimeType, fileName: basename3(file.path) })) {
4213
- refused2.push(basename3(file.path));
4591
+ if (items.length >= MAX_GROUP_PARTS - 1 || !resolveAllowedUploadMimeType({ mimeType, fileName: basename4(file.path) })) {
4592
+ refused2.push(basename4(file.path));
4214
4593
  continue;
4215
4594
  }
4216
4595
  items.push({
4217
4596
  path: file.path,
4218
4597
  messageId: keyed().text("item", roomId, turn.id, String(items.length)),
4219
- fileName: basename3(file.path),
4598
+ fileName: basename4(file.path),
4220
4599
  mimeType,
4221
4600
  sizeBytes: file.size
4222
4601
  });
@@ -4401,7 +4780,7 @@ ${JSON.stringify(result, null, 2)}`);
4401
4780
  const file = stateFile(session);
4402
4781
  let data = {};
4403
4782
  try {
4404
- data = JSON.parse(readFileSync5(file, "utf8"));
4783
+ data = JSON.parse(readFileSync6(file, "utf8"));
4405
4784
  } catch {
4406
4785
  }
4407
4786
  const save = /* @__PURE__ */ __name((next) => {
@@ -4854,7 +5233,7 @@ ${JSON.stringify(result, null, 2)}`);
4854
5233
  let text3 = answered2 ? result.reply : failed ? FAILURE_NOTICE : null;
4855
5234
  if (answered2 && result.files?.length) text3 = `${text3}
4856
5235
 
4857
- (Files cannot go with an errand's report: ${result.files.map((file) => basename3(String(file))).join(", ")}.)`;
5236
+ (Files cannot go with an errand's report: ${result.files.map((file) => basename4(String(file))).join(", ")}.)`;
4858
5237
  const known = latest(record.id) ?? record;
4859
5238
  const cancelled = known.status === "cancelled" || stopped();
4860
5239
  const status = cancelled ? "cancelled" : ended(known, failed);
@@ -5023,7 +5402,7 @@ ${JSON.stringify(result, null, 2)}`);
5023
5402
  client = new CanonClient(options.apiKey, connection.apiBaseUrl, {
5024
5403
  environmentId: connection.environmentId,
5025
5404
  streamUrl: connection.streamUrl,
5026
- stateDirectory: join6(home, "endpoint"),
5405
+ stateDirectory: join7(home, "endpoint"),
5027
5406
  ...options.client ? { client: options.client } : {},
5028
5407
  // Below Canon's client floor nothing can work, wherever the refusal surfaces: a query, an operation or the stream.
5029
5408
  ...options.onFatal ? { onClientUpdateRequired: /* @__PURE__ */ __name((error) => options.onFatal(error), "onClientUpdateRequired") } : {},
@@ -5071,17 +5450,17 @@ ${JSON.stringify(result, null, 2)}`);
5071
5450
  }
5072
5451
  scope = JSON.stringify([identity.environmentId, agentId, identity.ownershipVersion ?? 0, identity.installationId]);
5073
5452
  if (mlsOn) {
5074
- mlsIds = createMlsIds(privateKey(join6(home, "runner", options.environmentId, agentId, "mls-ids", `${sha2563(scope).slice(0, 32)}.key`), MLS_ID_KEY_BYTES));
5453
+ mlsIds = createMlsIds(privateKey(join7(home, "runner", options.environmentId, agentId, "mls-ids", `${sha2563(scope).slice(0, 32)}.key`), MLS_ID_KEY_BYTES));
5075
5454
  mlsAsks = createMlsAsks({
5076
5455
  endpoint,
5077
5456
  agentId,
5078
- directory: join6(home, "runner", options.environmentId, agentId, "asks", sha2563(scope).slice(0, 32)),
5457
+ directory: join7(home, "runner", options.environmentId, agentId, "asks", sha2563(scope).slice(0, 32)),
5079
5458
  assertAudience,
5080
5459
  sessionForDestructive: options.sessionApprovalForDestructive === true,
5081
5460
  log
5082
5461
  }, approvalScope);
5083
5462
  }
5084
- journal = createCompletedOutputJournal(join6(home, "runner", options.environmentId, agentId, "finals"), scope, {
5463
+ journal = createCompletedOutputJournal(join7(home, "runner", options.environmentId, agentId, "finals"), scope, {
5085
5464
  label: "agent runner",
5086
5465
  validate: /* @__PURE__ */ __name((record) => typeof record.text === "string" && typeof record.options?.messageId === "string" && Array.isArray(record.inputs) && typeof record.notice === "boolean" && (record.attachments === void 0 || Array.isArray(record.attachments)) && (record.flows === void 0 || Array.isArray(record.flows) && record.flows.length === record.attachments?.length) && (record.mls === void 0 || !!record.mls && typeof record.mls === "object") && (record.errand === void 0 || typeof record.errand === "string"), "validate")
5087
5466
  });
@@ -5106,7 +5485,7 @@ ${JSON.stringify(result, null, 2)}`);
5106
5485
  }
5107
5486
  }
5108
5487
  const outbound = driver.capabilities.outboundFiles;
5109
- roots = outbound ? (await Promise.all(outbound.roots.map((root) => realpath5(root).catch(() => "")))).filter(Boolean) : [];
5488
+ roots = outbound ? (await Promise.all(outbound.roots.map((root) => realpath4(root).catch(() => "")))).filter(Boolean) : [];
5110
5489
  if (driver.capabilities.tools) tools = await startToolServer();
5111
5490
  await recover();
5112
5491
  const publisher = createRuntimeStatePublisher({ agentId, clientType: CLIENT_TYPES[driver.name], hostMode: true, rtdb: initRTDBAuth(client) });
@@ -5283,14 +5662,14 @@ export {
5283
5662
  DriverSessionError,
5284
5663
  ConnectorRelayError,
5285
5664
  loadInstructions,
5665
+ checkAcp,
5666
+ acpDriver,
5286
5667
  loadClaudeQuery,
5287
5668
  claudeDriver,
5288
5669
  codeDriver,
5289
5670
  checkCodex,
5290
5671
  codexDriver,
5291
5672
  connectorDriver,
5292
- runnerLockName,
5293
- assertPrivateHome,
5294
5673
  errandsDirectory,
5295
5674
  queueErrand,
5296
5675
  errandJournal,
@@ -5301,6 +5680,7 @@ export {
5301
5680
  runMlsDevSetup,
5302
5681
  MLS_ENVIRONMENT_PINS,
5303
5682
  bundledMlsCustodyHelper,
5683
+ bundledMlsWasmCore,
5304
5684
  defaultMlsDevice,
5305
5685
  managedMlsDevice,
5306
5686
  startRelayChannel,