@slock-ai/daemon 0.50.0-play.20260518104853 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,11 +7,11 @@ import {
7
7
  } from "./chunk-KNMCE6WB.js";
8
8
 
9
9
  // src/core.ts
10
- import path17 from "path";
10
+ import path16 from "path";
11
11
  import os8 from "os";
12
12
  import { createRequire } from "module";
13
13
  import { accessSync } from "fs";
14
- import { fileURLToPath as fileURLToPath2 } from "url";
14
+ import { fileURLToPath } from "url";
15
15
 
16
16
  // ../shared/src/tracing/index.ts
17
17
  var DEFAULT_TRACE_FLAGS = "00";
@@ -723,7 +723,6 @@ var SERVER_CAPABILITY_MATRIX = {
723
723
  var RUNTIMES = [
724
724
  { id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
725
725
  { id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
726
- { id: "pi", displayName: "Pi", binary: "pi", supported: true },
727
726
  { id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true },
728
727
  { id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
729
728
  { id: "cursor", displayName: "Cursor CLI", binary: "cursor-agent", supported: true },
@@ -765,10 +764,10 @@ var DISPLAY_PLAN_CONFIG = {
765
764
  };
766
765
 
767
766
  // src/agentProcessManager.ts
768
- import { mkdirSync as mkdirSync5, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
767
+ import { mkdirSync as mkdirSync4, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync7 } from "fs";
769
768
  import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
770
769
  import { createHash as createHash2 } from "crypto";
771
- import path13 from "path";
770
+ import path12 from "path";
772
771
  import os6 from "os";
773
772
 
774
773
  // src/drivers/claude.ts
@@ -778,7 +777,7 @@ import os2 from "os";
778
777
  import path4 from "path";
779
778
 
780
779
  // src/drivers/cliTransport.ts
781
- import { mkdirSync, writeFileSync } from "fs";
780
+ import { mkdirSync, rmSync, writeFileSync } from "fs";
782
781
  import path2 from "path";
783
782
 
784
783
  // src/drivers/systemPrompt.ts
@@ -871,16 +870,14 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
871
870
  16. **\`slock attachment upload\`** \u2014 Upload a file to attach to a message. Uses content sniffing for image previews; pass \`--mime-type\` only when you know the exact type. Returns an attachment ID to pass to \`slock message send\`.
872
871
  17. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
873
872
  18. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
874
- 19. **\`slock profile update\`** \u2014 Update your own profile. Supports \`--avatar-file <path>\`, \`--display-name <name>\`, and \`--description <text>\`. Values must be non-empty. Provide at least one flag per call; multiple flags can be combined.
875
- 20. **\`slock integration list\`** \u2014 List registered third-party services and this agent's active Slock Agent Logins.
876
- 21. **\`slock integration login\`** \u2014 Provision or reuse this agent's login for a registered third-party service.
877
- 22. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
878
- 23. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
879
- 24. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
880
- 25. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
881
- 26. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
882
- 27. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
883
- 28. **\`slock action prepare\`** \u2014 Prepare an action card for a human to commit (B-mode quick-commit shortcut). Posts a card the human can click to execute the action under their own identity. Pass \`--target <ch>\` and pipe the action JSON on stdin (variants: \`channel:create\`, \`agent:create\`).
873
+ 19. **\`slock profile update\`** \u2014 Update your own profile. Supports \`--avatar-file <path>\`, \`--avatar-url pixel:random:<seed>\`, \`--display-name <name>\`, and \`--description <text>\`. Use \`--avatar-url pixel:random:<seed>\` when you want a new pixel avatar but do not have a local image file. Values must be non-empty. Provide at least one flag per call; multiple flags can be combined.
874
+ 20. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
875
+ 21. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
876
+ 22. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
877
+ 23. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
878
+ 24. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
879
+ 25. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
880
+ 26. **\`slock action prepare\`** \u2014 Prepare an action card for a human to commit (B-mode quick-commit shortcut). Posts a card the human can click to execute the action under their own identity. Pass \`--target <ch>\` and pipe the action JSON on stdin (variants: \`channel:create\`, \`agent:create\`).
884
881
 
885
882
  The CLI prints human-readable canonical text on success (matching the format you see in received messages and history). On failure it prints JSON to stderr:
886
883
  - failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
@@ -981,11 +978,6 @@ Each channel has a **name** and optionally a **description** that define its pur
981
978
  - **Reply in context** \u2014 always respond in the channel/thread the message came from.
982
979
  - **Stay on topic** \u2014 when proactively sharing results or updates, post in the channel most relevant to the work. Don't scatter messages across unrelated channels.
983
980
  - If unsure where something belongs, call ${serverInfoCmd} to review channel descriptions.`;
984
- const thirdPartyIntegrationsSection = isCli ? `### Third-party integrations
985
-
986
- If a registered third-party service requires login, use Slock Agent Login through the CLI instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app, first run \`slock integration list\` and match the app to a registered service before browsing the app. Use \`slock integration login --service <service>\` to provision or reuse your agent login for that service. When the command returns \`Agent login ready\` or \`Already logged in\`, the agent-side login is ready. If the output includes an app URL, open that URL as the service-provided third-party app surface; it should look like the service's normal Login with Slock callback and not require you to understand Slock's internal grant/request protocol. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow, use internal request IDs as OAuth callback codes, or call third-party exchange endpoints unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use \`slock profile show\`. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the registered service / Slock Agent Login path.` : `### Third-party integrations
987
-
988
- If a registered third-party service requires login, use Slock Agent Login through the available registered-service interface instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app, first inspect the registered-service interface and match the app to a registered service before browsing the app. Once the registered-service interface reports the agent login is ready, the agent-side login is ready. If that interface provides an app URL, use it as the service-provided third-party app surface; it should look like the service's normal Login with Slock callback and not require you to understand Slock's internal grant/request protocol. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow or treat internal request IDs as OAuth callback codes unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use your Slock profile view. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the registered service / Slock Agent Login path.`;
989
981
  const readingHistorySection = isCli ? `### Reading history
990
982
 
991
983
  \`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
@@ -1157,8 +1149,6 @@ ${discoverySection}
1157
1149
 
1158
1150
  ${channelAwarenessSection}
1159
1151
 
1160
- ${thirdPartyIntegrationsSection}
1161
-
1162
1152
  ${readingHistorySection}
1163
1153
 
1164
1154
  ${historicalReferenceSection}
@@ -1190,17 +1180,6 @@ Keep the user informed. They cannot see your internal reasoning, so:
1190
1180
  - For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
1191
1181
  - When done, summarize the result.
1192
1182
  - Keep updates concise \u2014 one or two sentences. Don't flood the chat.
1193
- - For long answers where users need the conclusion first but details still matter, put the conclusion and next action outside any collapse, then use sanitized HTML details blocks for optional depth:
1194
-
1195
- \`\`\`html
1196
- <details>
1197
- <summary>Evidence, logs, or edge cases</summary>
1198
-
1199
- Detailed notes go here.
1200
- </details>
1201
- \`\`\`
1202
-
1203
- Do not hide the main recommendation, blocker, or required action inside \`<details>\`; only fold supporting evidence, logs, alternatives, or extended rationale.
1204
1183
 
1205
1184
  ### Conversation etiquette
1206
1185
 
@@ -1370,21 +1349,304 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
1370
1349
  return candidates.filter((candidate) => existsSync(candidate.path));
1371
1350
  }
1372
1351
 
1373
- // src/authEnv.ts
1374
- var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
1375
- var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
1376
- function scrubDaemonAuthEnv(env) {
1377
- delete env[DAEMON_API_KEY_ENV];
1378
- return env;
1352
+ // src/agentCredentialProxy.ts
1353
+ import { randomBytes } from "crypto";
1354
+ import http from "http";
1355
+ import { URL as URL2 } from "url";
1356
+ var registrations = /* @__PURE__ */ new Map();
1357
+ var servers = /* @__PURE__ */ new Map();
1358
+ function allocatePort() {
1359
+ return 43e3 + randomBytes(2).readUInt16BE(0) % 1e4;
1360
+ }
1361
+ function ensureServer(port) {
1362
+ if (servers.has(port)) return;
1363
+ const server = http.createServer((req, res) => {
1364
+ void handleProxyRequest(req, res);
1365
+ });
1366
+ server.on("error", (err) => {
1367
+ logger.warn(`[Agent Credential Proxy] local proxy on port ${port} failed: ${err.message}`);
1368
+ });
1369
+ server.listen(port, "127.0.0.1");
1370
+ server.unref();
1371
+ servers.set(port, server);
1372
+ }
1373
+ async function handleProxyRequest(req, res) {
1374
+ const auth = req.headers.authorization;
1375
+ const token = typeof auth === "string" && auth.startsWith("Bearer ") ? auth.slice("Bearer ".length) : "";
1376
+ const registration = registrations.get(token);
1377
+ if (!registration) {
1378
+ res.writeHead(401, { "content-type": "application/json" });
1379
+ res.end(JSON.stringify({ error: "invalid local agent proxy token", code: "invalid_agent_proxy_token" }));
1380
+ return;
1381
+ }
1382
+ try {
1383
+ const target = new URL2(req.url ?? "/", registration.serverUrl);
1384
+ const headers = new Headers();
1385
+ for (const [name, value] of Object.entries(req.headers)) {
1386
+ if (value === void 0) continue;
1387
+ if (name.toLowerCase() === "host") continue;
1388
+ if (name.toLowerCase() === "authorization") continue;
1389
+ if (Array.isArray(value)) {
1390
+ for (const item of value) headers.append(name, item);
1391
+ } else {
1392
+ headers.set(name, value);
1393
+ }
1394
+ }
1395
+ headers.set("Authorization", `Bearer ${registration.apiKey}`);
1396
+ headers.set("X-Agent-Id", registration.agentId);
1397
+ headers.set("X-Slock-Client", "cli");
1398
+ headers.set("X-Slock-Agent-Active-Capabilities", registration.activeCapabilities);
1399
+ const method = req.method ?? "GET";
1400
+ let body = method === "GET" || method === "HEAD" ? void 0 : req;
1401
+ let sendTarget;
1402
+ if (method === "POST" && target.pathname === "/internal/agent-api/send") {
1403
+ const rawBody = await readRequestBody(req);
1404
+ const prepared = await prepareAgentApiSendForward(registration, headers, rawBody);
1405
+ if (prepared.localResponse) {
1406
+ const responseText = JSON.stringify(prepared.localResponse);
1407
+ res.writeHead(200, { "content-type": "application/json" });
1408
+ res.end(responseText);
1409
+ return;
1410
+ }
1411
+ body = prepared.bodyText;
1412
+ sendTarget = prepared.target;
1413
+ headers.set("content-type", "application/json");
1414
+ headers.set("content-length", String(Buffer.byteLength(prepared.bodyText)));
1415
+ }
1416
+ const upstream = await fetch(target, {
1417
+ method,
1418
+ headers,
1419
+ body,
1420
+ // Required by undici when forwarding a Node stream.
1421
+ duplex: body ? "half" : void 0
1422
+ });
1423
+ if (shouldBufferJsonResponse(upstream, target.pathname, registration)) {
1424
+ const responseText = await upstream.text();
1425
+ consumeVisibleResponse(registration, target, sendTarget, responseText);
1426
+ res.writeHead(upstream.status, Object.fromEntries(upstream.headers.entries()));
1427
+ res.end(responseText);
1428
+ return;
1429
+ }
1430
+ res.writeHead(upstream.status, Object.fromEntries(upstream.headers.entries()));
1431
+ if (upstream.body) {
1432
+ const reader = upstream.body.getReader();
1433
+ try {
1434
+ while (true) {
1435
+ const { done, value } = await reader.read();
1436
+ if (done) break;
1437
+ res.write(Buffer.from(value));
1438
+ }
1439
+ } finally {
1440
+ res.end();
1441
+ }
1442
+ } else {
1443
+ res.end();
1444
+ }
1445
+ } catch (err) {
1446
+ res.writeHead(502, { "content-type": "application/json" });
1447
+ res.end(JSON.stringify({
1448
+ error: "failed to proxy local agent request",
1449
+ code: "agent_proxy_failed",
1450
+ detail: err instanceof Error ? err.message : String(err)
1451
+ }));
1452
+ }
1453
+ }
1454
+ async function readRequestBody(req) {
1455
+ let body = "";
1456
+ req.setEncoding("utf8");
1457
+ for await (const chunk of req) {
1458
+ body += String(chunk);
1459
+ }
1460
+ return body;
1461
+ }
1462
+ function messageSeq(message) {
1463
+ return Number(message.seq ?? 0);
1464
+ }
1465
+ function boundaryBefore(messages) {
1466
+ let minSeq = Number.POSITIVE_INFINITY;
1467
+ for (const message of messages) {
1468
+ const seq = Math.floor(messageSeq(message));
1469
+ if (Number.isFinite(seq) && seq > 0) minSeq = Math.min(minSeq, seq);
1470
+ }
1471
+ return Number.isFinite(minSeq) ? Math.max(0, minSeq - 1) : void 0;
1472
+ }
1473
+ function maxMessageSeq(messages) {
1474
+ let maxSeq = 0;
1475
+ for (const message of messages) {
1476
+ const seq = Math.floor(messageSeq(message));
1477
+ if (Number.isFinite(seq) && seq > 0) maxSeq = Math.max(maxSeq, seq);
1478
+ }
1479
+ return maxSeq > 0 ? maxSeq : void 0;
1480
+ }
1481
+ function parseTargetFields(target) {
1482
+ if (target.startsWith("dm:@")) {
1483
+ const rest = target.slice("dm:@".length);
1484
+ const [peer, threadId] = rest.split(":", 2);
1485
+ if (threadId) {
1486
+ return {
1487
+ channel_type: "thread",
1488
+ channel_name: threadId,
1489
+ parent_channel_type: "dm",
1490
+ parent_channel_name: peer
1491
+ };
1492
+ }
1493
+ return { channel_type: "dm", channel_name: peer };
1494
+ }
1495
+ if (target.startsWith("#")) {
1496
+ const rest = target.slice(1);
1497
+ const [channel, threadId] = rest.split(":", 2);
1498
+ if (threadId) {
1499
+ return {
1500
+ channel_type: "thread",
1501
+ channel_name: threadId,
1502
+ parent_channel_type: "channel",
1503
+ parent_channel_name: channel
1504
+ };
1505
+ }
1506
+ return { channel_type: "channel", channel_name: channel };
1507
+ }
1508
+ return {};
1509
+ }
1510
+ function normalizeVisibleMessage(message, target) {
1511
+ const targetFields = target ? parseTargetFields(target) : {};
1512
+ return {
1513
+ ...targetFields,
1514
+ ...message,
1515
+ message_id: message.message_id ?? message.id,
1516
+ timestamp: message.timestamp ?? message.createdAt,
1517
+ sender_type: message.sender_type ?? message.senderType,
1518
+ sender_name: message.sender_name ?? message.senderName,
1519
+ sender_description: message.sender_description ?? message.senderDescription ?? null
1520
+ };
1521
+ }
1522
+ function normalizeVisibleMessages(messages, target) {
1523
+ return messages.map((message) => normalizeVisibleMessage(message, target));
1524
+ }
1525
+ async function loadRecentTargetMessages(registration, headers, target) {
1526
+ const historyUrl = new URL2("/internal/agent-api/history", registration.serverUrl);
1527
+ historyUrl.searchParams.set("channel", target);
1528
+ historyUrl.searchParams.set("limit", "3");
1529
+ const historyHeaders = new Headers(headers);
1530
+ historyHeaders.delete("content-length");
1531
+ historyHeaders.delete("content-type");
1532
+ const res = await fetch(historyUrl, { method: "GET", headers: historyHeaders });
1533
+ if (!res.ok) return [];
1534
+ const parsed = await res.json().catch(() => null);
1535
+ return Array.isArray(parsed?.messages) ? normalizeVisibleMessages(parsed.messages, target) : [];
1536
+ }
1537
+ async function prepareAgentApiSendForward(registration, headers, rawBody) {
1538
+ let body;
1539
+ try {
1540
+ body = rawBody ? JSON.parse(rawBody) : {};
1541
+ } catch {
1542
+ return { bodyText: rawBody };
1543
+ }
1544
+ const target = typeof body.target === "string" ? body.target : void 0;
1545
+ const coordinator = registration.inboxCoordinator;
1546
+ if (!target || !coordinator || body.continueAnyway === true) {
1547
+ return { bodyText: JSON.stringify(body), target };
1548
+ }
1549
+ const pending = coordinator.getPendingMessages(target);
1550
+ if (pending.length > 0) {
1551
+ const staleBoundary = boundaryBefore(pending);
1552
+ if (staleBoundary !== void 0) {
1553
+ body.seenUpToSeq = staleBoundary;
1554
+ }
1555
+ return { bodyText: JSON.stringify(body), target };
1556
+ }
1557
+ const existingBoundary = typeof body.seenUpToSeq === "number" && Number.isFinite(body.seenUpToSeq) ? Math.max(0, Math.floor(body.seenUpToSeq)) : void 0;
1558
+ const loadedBoundary = coordinator.getBoundary(target);
1559
+ const boundary = Math.max(existingBoundary ?? 0, loadedBoundary ?? 0);
1560
+ if (boundary > 0) {
1561
+ body.seenUpToSeq = boundary;
1562
+ return { bodyText: JSON.stringify(body), target };
1563
+ }
1564
+ const recent = await loadRecentTargetMessages(registration, headers, target);
1565
+ if (recent.length > 0) {
1566
+ const seenUpToSeq = maxMessageSeq(recent);
1567
+ coordinator.consumeVisibleMessages({ target, messages: recent, boundarySeq: seenUpToSeq, source: "send_preflight_context" });
1568
+ return {
1569
+ bodyText: JSON.stringify(body),
1570
+ target,
1571
+ localResponse: {
1572
+ state: "held",
1573
+ seenUpToSeq,
1574
+ heldMessages: recent,
1575
+ newMessageCount: recent.length,
1576
+ shownMessageCount: recent.length,
1577
+ omittedMessageCount: 0
1578
+ }
1579
+ };
1580
+ }
1581
+ return { bodyText: JSON.stringify(body), target };
1582
+ }
1583
+ function shouldBufferJsonResponse(upstream, pathname, registration) {
1584
+ if (!registration.inboxCoordinator) return false;
1585
+ const contentType = upstream.headers.get("content-type") ?? "";
1586
+ if (!contentType.includes("application/json")) return false;
1587
+ return pathname === "/internal/agent-api/send" || pathname === "/internal/agent-api/events" || pathname === "/internal/agent-api/history";
1588
+ }
1589
+ function consumeVisibleResponse(registration, targetUrl, sendTarget, responseText) {
1590
+ const coordinator = registration.inboxCoordinator;
1591
+ if (!coordinator) return;
1592
+ let parsed;
1593
+ try {
1594
+ parsed = JSON.parse(responseText);
1595
+ } catch {
1596
+ return;
1597
+ }
1598
+ if (targetUrl.pathname === "/internal/agent-api/send" && parsed.state === "held" && Array.isArray(parsed.heldMessages)) {
1599
+ coordinator.consumeVisibleMessages({
1600
+ target: sendTarget,
1601
+ messages: normalizeVisibleMessages(parsed.heldMessages, sendTarget),
1602
+ boundarySeq: typeof parsed.seenUpToSeq === "number" ? parsed.seenUpToSeq : void 0,
1603
+ source: "server_held_context"
1604
+ });
1605
+ return;
1606
+ }
1607
+ if (targetUrl.pathname === "/internal/agent-api/events" && Array.isArray(parsed.events)) {
1608
+ coordinator.consumeVisibleMessages({ messages: normalizeVisibleMessages(parsed.events), source: "agent_api_events" });
1609
+ return;
1610
+ }
1611
+ if (targetUrl.pathname === "/internal/agent-api/history" && Array.isArray(parsed.messages)) {
1612
+ const target = targetUrl.searchParams.get("channel") ?? void 0;
1613
+ const messages = normalizeVisibleMessages(parsed.messages, target);
1614
+ coordinator.consumeVisibleMessages({ target, messages, boundarySeq: maxMessageSeq(messages), source: "agent_api_history" });
1615
+ }
1616
+ }
1617
+ function registerAgentCredentialProxy(input) {
1618
+ const port = allocatePort();
1619
+ ensureServer(port);
1620
+ const proxyToken = `sap_${randomBytes(32).toString("base64url")}`;
1621
+ registrations.set(proxyToken, {
1622
+ serverUrl: input.serverUrl,
1623
+ apiKey: input.apiKey,
1624
+ agentId: input.agentId,
1625
+ launchId: input.launchId ?? null,
1626
+ activeCapabilities: input.activeCapabilities,
1627
+ inboxCoordinator: input.inboxCoordinator
1628
+ });
1629
+ return {
1630
+ proxyUrl: `http://127.0.0.1:${port}`,
1631
+ proxyToken
1632
+ };
1379
1633
  }
1380
- function scrubDaemonChildEnv(env) {
1381
- delete env[DAEMON_API_KEY_ENV];
1382
- delete env[SLOCK_AGENT_TOKEN_ENV];
1383
- return env;
1634
+ function unregisterAgentCredentialProxyForLaunch(input) {
1635
+ let removed = 0;
1636
+ const launchId = input.launchId ?? null;
1637
+ for (const [token, registration] of registrations) {
1638
+ if (registration.agentId === input.agentId && registration.launchId === launchId) {
1639
+ registrations.delete(token);
1640
+ removed += 1;
1641
+ }
1642
+ }
1643
+ return removed;
1384
1644
  }
1385
1645
 
1386
1646
  // src/drivers/cliTransport.ts
1387
1647
  var shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
1648
+ var DEFAULT_ACTIVE_CAPABILITIES = "send,read,mentions,tasks,reactions,server,channels";
1649
+ var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
1388
1650
  function runtimeContextEnv(config) {
1389
1651
  const ctx = config.runtimeContext;
1390
1652
  if (!ctx) return {};
@@ -1408,17 +1670,43 @@ function prepareCliTransport(ctx, extraEnv = {}, platform = process.platform) {
1408
1670
  }
1409
1671
  const slockDir = path2.join(ctx.workingDirectory, ".slock");
1410
1672
  mkdirSync(slockDir, { recursive: true });
1673
+ const slockHome = resolveSlockHome();
1411
1674
  const tokenFile = path2.join(slockDir, "agent-token");
1412
- writeFileSync(tokenFile, ctx.config.authToken || ctx.daemonApiKey, { mode: 384 });
1675
+ const agentCredentialKey = ctx.config.agentCredentialKey;
1676
+ let agentCredentialProxy = null;
1677
+ let agentCredentialProxyTokenFile = null;
1678
+ if (typeof agentCredentialKey === "string" && agentCredentialKey.length > 0) {
1679
+ rmSync(tokenFile, { force: true });
1680
+ agentCredentialProxy = registerAgentCredentialProxy({
1681
+ agentId: ctx.agentId,
1682
+ launchId: ctx.launchId,
1683
+ serverUrl: ctx.config.serverUrl,
1684
+ apiKey: agentCredentialKey,
1685
+ activeCapabilities: DEFAULT_ACTIVE_CAPABILITIES,
1686
+ inboxCoordinator: ctx.agentCredentialProxyInboxCoordinator
1687
+ });
1688
+ const launchPart = safePathPart(ctx.launchId || `pid-${process.pid}`);
1689
+ const proxyTokenDir = path2.join(slockHome, "agent-proxy-tokens", safePathPart(ctx.agentId));
1690
+ mkdirSync(proxyTokenDir, { recursive: true, mode: 448 });
1691
+ agentCredentialProxyTokenFile = path2.join(proxyTokenDir, `${launchPart}.token`);
1692
+ writeFileSync(agentCredentialProxyTokenFile, agentCredentialProxy.proxyToken, { mode: 384 });
1693
+ } else {
1694
+ writeFileSync(tokenFile, ctx.config.authToken || ctx.daemonApiKey, { mode: 384 });
1695
+ }
1413
1696
  const posixWrapper = path2.join(slockDir, "slock");
1697
+ const posixCredentialPrefix = agentCredentialProxy ? `SLOCK_AGENT_PROXY_URL=${shellSingleQuote(agentCredentialProxy.proxyUrl)} SLOCK_AGENT_PROXY_TOKEN_FILE=${shellSingleQuote(agentCredentialProxyTokenFile)} SLOCK_AGENT_ACTIVE_CAPABILITIES=${shellSingleQuote(DEFAULT_ACTIVE_CAPABILITIES)} ` : "";
1414
1698
  const posixBody = `#!/usr/bin/env bash
1415
- exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)} "$@"
1699
+ ${posixCredentialPrefix}exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)} "$@"
1416
1700
  `;
1417
1701
  writeFileSync(posixWrapper, posixBody, { mode: 493 });
1418
1702
  if (platform === "win32") {
1419
1703
  const cmdWrapper = path2.join(slockDir, "slock.cmd");
1704
+ const cmdCredentialLine = agentCredentialProxy ? `set "SLOCK_AGENT_PROXY_URL=${agentCredentialProxy.proxyUrl}"\r
1705
+ set "SLOCK_AGENT_PROXY_TOKEN_FILE=${agentCredentialProxyTokenFile}"\r
1706
+ set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
1707
+ ` : "";
1420
1708
  const cmdBody = `@echo off\r
1421
- "${process.execPath}" "${ctx.slockCliPath}" %*\r
1709
+ ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
1422
1710
  `;
1423
1711
  writeFileSync(cmdWrapper, cmdBody);
1424
1712
  }
@@ -1429,17 +1717,27 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)}
1429
1717
  ...ctx.config.envVars || {},
1430
1718
  ...extraEnv,
1431
1719
  ...runtimeContextEnv(ctx.config),
1432
- [SLOCK_HOME_ENV]: resolveSlockHome(),
1720
+ [SLOCK_HOME_ENV]: slockHome,
1433
1721
  SLOCK_AGENT_ID: ctx.agentId,
1434
1722
  ...ctx.launchId ? { SLOCK_AGENT_LAUNCH_ID: ctx.launchId } : {},
1435
1723
  SLOCK_SERVER_URL: ctx.config.serverUrl,
1436
- SLOCK_AGENT_TOKEN_FILE: tokenFile,
1724
+ ...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
1437
1725
  PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
1438
1726
  };
1439
- scrubDaemonChildEnv(spawnEnv);
1727
+ delete spawnEnv.SLOCK_AGENT_TOKEN;
1728
+ delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY;
1729
+ delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY_FILE;
1730
+ delete spawnEnv.SLOCK_AGENT_PROXY_URL;
1731
+ delete spawnEnv.SLOCK_AGENT_PROXY_TOKEN;
1732
+ delete spawnEnv.SLOCK_AGENT_PROXY_TOKEN_FILE;
1733
+ delete spawnEnv.SLOCK_AGENT_ACTIVE_CAPABILITIES;
1734
+ if (agentCredentialProxy) {
1735
+ delete spawnEnv.SLOCK_AGENT_TOKEN_FILE;
1736
+ }
1440
1737
  return {
1441
1738
  slockDir,
1442
1739
  tokenFile,
1740
+ agentCredentialProxyUrl: agentCredentialProxy?.proxyUrl ?? null,
1443
1741
  wrapperPath,
1444
1742
  spawnEnv
1445
1743
  };
@@ -1473,7 +1771,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn) {
1473
1771
  }
1474
1772
  function resolveCommandOnPath(command, deps = {}) {
1475
1773
  const platform = deps.platform ?? process.platform;
1476
- const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
1774
+ const env = deps.env ?? process.env;
1477
1775
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
1478
1776
  if (platform === "win32") {
1479
1777
  return resolveCommandOnWindows(command, env, execFileSyncFn);
@@ -1498,7 +1796,7 @@ function firstExistingPath(candidates, deps = {}) {
1498
1796
  return null;
1499
1797
  }
1500
1798
  function readCommandVersion(command, args = [], deps = {}) {
1501
- const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
1799
+ const env = deps.env ?? process.env;
1502
1800
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
1503
1801
  try {
1504
1802
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -1607,7 +1905,7 @@ function collectClaudeMcpConfigFiles(ctx, home) {
1607
1905
  return files;
1608
1906
  }
1609
1907
  function buildClaudeUserMcpServers(ctx, home) {
1610
- const servers = /* @__PURE__ */ Object.create(null);
1908
+ const servers2 = /* @__PURE__ */ Object.create(null);
1611
1909
  for (const configFile of collectClaudeMcpConfigFiles(ctx, home)) {
1612
1910
  const mcpServers = readClaudeMcpServers(configFile.path, configFile.vars);
1613
1911
  if (!mcpServers) continue;
@@ -1616,10 +1914,10 @@ function buildClaudeUserMcpServers(ctx, home) {
1616
1914
  logger.warn(`[Claude] ignoring invalid MCP server "${name}" in ${configFile.path}`);
1617
1915
  continue;
1618
1916
  }
1619
- servers[name] = server;
1917
+ servers2[name] = server;
1620
1918
  }
1621
1919
  }
1622
- return servers;
1920
+ return servers2;
1623
1921
  }
1624
1922
  var ClaudeDriver = class {
1625
1923
  id = "claude";
@@ -1667,9 +1965,9 @@ var ClaudeDriver = class {
1667
1965
  CLAUDE_DISALLOWED_TOOLS
1668
1966
  ];
1669
1967
  if (opts.standingPromptFilePath) {
1670
- args.push("--system-prompt-file", opts.standingPromptFilePath);
1968
+ args.push("--append-system-prompt-file", opts.standingPromptFilePath);
1671
1969
  } else {
1672
- args.push("--system-prompt", standingPrompt);
1970
+ args.push("--append-system-prompt", standingPrompt);
1673
1971
  }
1674
1972
  if (config.sessionId) {
1675
1973
  args.push("--resume", config.sessionId);
@@ -1917,13 +2215,29 @@ function resolveWindowsNpmCodexEntry(deps = {}) {
1917
2215
  }
1918
2216
  return null;
1919
2217
  }
2218
+ function resolveWindowsCodexDesktopEntry(deps = {}) {
2219
+ const existsSyncFn = deps.existsSyncFn ?? existsSync4;
2220
+ const env = deps.env ?? process.env;
2221
+ const homeDir = deps.homeDir;
2222
+ const winPath = path5.win32;
2223
+ const candidates = [
2224
+ env.LOCALAPPDATA ? winPath.join(env.LOCALAPPDATA, "OpenAI", "Codex", "bin", "codex.exe") : null,
2225
+ env.USERPROFILE ? winPath.join(env.USERPROFILE, "AppData", "Local", "OpenAI", "Codex", "bin", "codex.exe") : null,
2226
+ homeDir ? winPath.join(homeDir, "AppData", "Local", "OpenAI", "Codex", "bin", "codex.exe") : null
2227
+ ].filter((candidate) => Boolean(candidate));
2228
+ for (const candidate of candidates) {
2229
+ if (existsSyncFn(candidate)) return candidate;
2230
+ }
2231
+ return null;
2232
+ }
1920
2233
  function resolveCodexCommand(deps = {}) {
1921
2234
  const platform = deps.platform ?? process.platform;
1922
2235
  if (platform === "win32") {
1923
2236
  const npmEntry = resolveWindowsNpmCodexEntry(deps);
1924
2237
  if (npmEntry) return npmEntry;
1925
2238
  const command = resolveCommandOnPath("codex", deps);
1926
- return command && !isWindowsSandboxRunner(command) ? command : null;
2239
+ if (command && !isWindowsSandboxRunner(command)) return command;
2240
+ return resolveWindowsCodexDesktopEntry(deps);
1927
2241
  }
1928
2242
  const pathCommand = resolveCommandOnPath("codex", deps);
1929
2243
  if (pathCommand) return pathCommand;
@@ -1966,6 +2280,10 @@ function resolveCodexSpawn(commandArgs, deps = {}) {
1966
2280
  if (command && !isWindowsSandboxRunner(command)) {
1967
2281
  return { command, args: commandArgs };
1968
2282
  }
2283
+ const desktopEntry = resolveWindowsCodexDesktopEntry(deps);
2284
+ if (desktopEntry) {
2285
+ return { command: desktopEntry, args: commandArgs };
2286
+ }
1969
2287
  throw new Error(
1970
2288
  "Cannot resolve Codex CLI entry point on Windows. Install Codex Desktop or install @openai/codex globally via npm (npm i -g @openai/codex). Ignoring .codex/.sandbox-bin/codex-command-runner because it is a sandbox helper, not the Codex CLI."
1971
2289
  );
@@ -2775,7 +3093,7 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
2775
3093
  }
2776
3094
  function runCursorModelsCommand() {
2777
3095
  return spawnSync("cursor-agent", ["models"], {
2778
- env: scrubDaemonChildEnv({ ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" }),
3096
+ env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
2779
3097
  encoding: "utf8",
2780
3098
  timeout: 5e3
2781
3099
  });
@@ -2822,7 +3140,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
2822
3140
  }
2823
3141
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
2824
3142
  const existsSyncFn = deps.existsSyncFn ?? existsSync6;
2825
- const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
3143
+ const env = deps.env ?? process.env;
2826
3144
  const winPath = path8.win32;
2827
3145
  let geminiEntry = null;
2828
3146
  try {
@@ -2996,16 +3314,13 @@ var GeminiDriver = class {
2996
3314
  // src/drivers/kimi.ts
2997
3315
  import { randomUUID } from "crypto";
2998
3316
  import { spawn as spawn6 } from "child_process";
2999
- import { chmodSync, existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
3317
+ import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
3000
3318
  import os4 from "os";
3001
3319
  import path9 from "path";
3002
3320
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
3003
3321
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
3004
3322
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
3005
3323
  var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
3006
- var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
3007
- var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
3008
- var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
3009
3324
  function parseToolArguments(raw) {
3010
3325
  if (typeof raw !== "string") return raw;
3011
3326
  try {
@@ -3014,73 +3329,6 @@ function parseToolArguments(raw) {
3014
3329
  return raw;
3015
3330
  }
3016
3331
  }
3017
- function readKimiConfigSource(home = os4.homedir(), env = process.env) {
3018
- const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
3019
- if (inlineConfig && inlineConfig.trim()) {
3020
- return {
3021
- raw: inlineConfig,
3022
- explicitPath: null,
3023
- sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
3024
- };
3025
- }
3026
- const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
3027
- const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
3028
- try {
3029
- return {
3030
- raw: readFileSync3(configPath, "utf8"),
3031
- explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
3032
- sourcePath: configPath
3033
- };
3034
- } catch {
3035
- return {
3036
- raw: null,
3037
- explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
3038
- sourcePath: configPath
3039
- };
3040
- }
3041
- }
3042
- function buildKimiSpawnEnv(env = process.env) {
3043
- const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
3044
- delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
3045
- delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
3046
- return scrubDaemonChildEnv(spawnEnv);
3047
- }
3048
- function buildKimiEffectiveEnv(ctx, overrideEnv) {
3049
- return {
3050
- ...process.env,
3051
- ...ctx.config.envVars || {},
3052
- ...overrideEnv || {}
3053
- };
3054
- }
3055
- function buildKimiLaunchOptions(ctx, opts = {}) {
3056
- const env = buildKimiEffectiveEnv(ctx, opts.env);
3057
- const source = readKimiConfigSource(opts.home ?? os4.homedir(), env);
3058
- const args = [];
3059
- let configFilePath = null;
3060
- let configContent = null;
3061
- if (source.explicitPath) {
3062
- configFilePath = source.explicitPath;
3063
- } else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
3064
- configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
3065
- configContent = source.raw;
3066
- if (opts.writeGeneratedConfig !== false) {
3067
- writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
3068
- chmodSync(configFilePath, 384);
3069
- }
3070
- }
3071
- if (configFilePath) {
3072
- args.push("--config-file", configFilePath);
3073
- }
3074
- if (ctx.config.model && ctx.config.model !== "default") {
3075
- args.push("--model", ctx.config.model);
3076
- }
3077
- return {
3078
- args,
3079
- env: buildKimiSpawnEnv(env),
3080
- configFilePath,
3081
- configContent
3082
- };
3083
- }
3084
3332
  function resolveKimiSpawn(commandArgs, deps = {}) {
3085
3333
  return {
3086
3334
  command: resolveCommandOnPath("kimi", deps) ?? "kimi",
@@ -3104,25 +3352,7 @@ var KimiDriver = class {
3104
3352
  };
3105
3353
  model = {
3106
3354
  detectedModelsVerifiedAs: "launchable",
3107
- toLaunchSpec: (modelId, ctx, opts) => {
3108
- if (!ctx) return { args: ["--model", modelId] };
3109
- const launchCtx = {
3110
- ...ctx,
3111
- config: {
3112
- ...ctx.config,
3113
- model: modelId
3114
- }
3115
- };
3116
- const launch = buildKimiLaunchOptions(launchCtx, {
3117
- home: opts?.home,
3118
- writeGeneratedConfig: false
3119
- });
3120
- return {
3121
- args: launch.args,
3122
- env: launch.env,
3123
- configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
3124
- };
3125
- }
3355
+ toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
3126
3356
  };
3127
3357
  supportsStdinNotification = true;
3128
3358
  mcpToolPrefix = "";
@@ -3176,7 +3406,6 @@ var KimiDriver = class {
3176
3406
  }
3177
3407
  }
3178
3408
  }), "utf8");
3179
- const launch = buildKimiLaunchOptions(ctx);
3180
3409
  const args = [
3181
3410
  "--wire",
3182
3411
  "--yolo",
@@ -3185,15 +3414,14 @@ var KimiDriver = class {
3185
3414
  "--mcp-config-file",
3186
3415
  mcpConfigPath,
3187
3416
  "--session",
3188
- this.sessionId,
3189
- ...launch.args
3417
+ this.sessionId
3190
3418
  ];
3191
3419
  if (ctx.config.model && ctx.config.model !== "default") {
3192
3420
  args.push("--model", ctx.config.model);
3193
3421
  }
3194
3422
  const spawnEnv = prepareCliTransport(ctx, { NO_COLOR: "1" }).spawnEnv;
3195
- const spawnTarget = resolveKimiSpawn(args);
3196
- const proc = spawn6(spawnTarget.command, spawnTarget.args, {
3423
+ const launch = resolveKimiSpawn(args);
3424
+ const proc = spawn6(launch.command, launch.args, {
3197
3425
  cwd: ctx.workingDirectory,
3198
3426
  stdio: ["pipe", "pipe", "pipe"],
3199
3427
  env: spawnEnv,
@@ -3201,7 +3429,7 @@ var KimiDriver = class {
3201
3429
  // and has an 8191-character command-line limit. Kimi's official
3202
3430
  // installer/uv entrypoint is an executable, so launch it directly and
3203
3431
  // keep prompts on stdin / files instead of routing through cmd.exe.
3204
- shell: spawnTarget.shell
3432
+ shell: launch.shell
3205
3433
  });
3206
3434
  proc.stdin?.write(JSON.stringify({
3207
3435
  jsonrpc: "2.0",
@@ -3317,9 +3545,14 @@ var KimiDriver = class {
3317
3545
  return detectKimiModels();
3318
3546
  }
3319
3547
  };
3320
- function detectKimiModels(home = os4.homedir(), opts = {}) {
3321
- const raw = readKimiConfigSource(home, opts.env).raw;
3322
- if (raw === null) return null;
3548
+ function detectKimiModels(home = os4.homedir()) {
3549
+ const configPath = path9.join(home, ".kimi", "config.toml");
3550
+ let raw;
3551
+ try {
3552
+ raw = readFileSync3(configPath, "utf8");
3553
+ } catch {
3554
+ return null;
3555
+ }
3323
3556
  const models = [];
3324
3557
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
3325
3558
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -3580,7 +3813,7 @@ function detectOpenCodeModels(home = os5.homedir(), runCommand = runOpenCodeMode
3580
3813
  }
3581
3814
  function runOpenCodeModelsCommand(home) {
3582
3815
  const result = spawnSync2("opencode", ["models"], {
3583
- env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
3816
+ env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
3584
3817
  encoding: "utf8",
3585
3818
  timeout: 5e3
3586
3819
  });
@@ -3738,297 +3971,6 @@ var OpenCodeDriver = class {
3738
3971
  }
3739
3972
  };
3740
3973
 
3741
- // src/drivers/pi.ts
3742
- import { spawn as spawn8 } from "child_process";
3743
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
3744
- import path11 from "path";
3745
- import { fileURLToPath } from "url";
3746
- import { getAgentDir, VERSION as PI_SDK_VERSION } from "@earendil-works/pi-coding-agent";
3747
- var CHAT_MCP_TOOL_PREFIX2 = "chat_";
3748
- var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
3749
- var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
3750
- var MIN_SUPPORTED_PI_VERSION = "0.74.0";
3751
- function parseSemver2(version) {
3752
- const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
3753
- if (!match) return null;
3754
- return [Number(match[1]), Number(match[2]), Number(match[3])];
3755
- }
3756
- function isSupportedPiVersion(version) {
3757
- if (!version) return true;
3758
- const actual = parseSemver2(version);
3759
- const minimum = parseSemver2(MIN_SUPPORTED_PI_VERSION);
3760
- if (!actual || !minimum) return true;
3761
- for (let i = 0; i < 3; i += 1) {
3762
- if (actual[i] > minimum[i]) return true;
3763
- if (actual[i] < minimum[i]) return false;
3764
- }
3765
- return true;
3766
- }
3767
- function unsupportedPiVersionMessage(version) {
3768
- if (!version || isSupportedPiVersion(version)) return null;
3769
- return `Pi SDK ${version} is unsupported; requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION}. Upgrade the daemon Pi dependency before starting this runtime.`;
3770
- }
3771
- function probePi(version = PI_SDK_VERSION) {
3772
- const unsupportedMessage = unsupportedPiVersionMessage(version);
3773
- if (unsupportedMessage) {
3774
- return {
3775
- available: false,
3776
- version: `${version} (requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION})`
3777
- };
3778
- }
3779
- return { available: true, version };
3780
- }
3781
- function resolvePiSdkRunnerPath(moduleUrl = import.meta.url) {
3782
- const moduleDir = path11.dirname(fileURLToPath(moduleUrl));
3783
- const sourceSibling = path11.join(moduleDir, "piSdkRunner.ts");
3784
- if (existsSync8(sourceSibling)) return sourceSibling;
3785
- const bundledEntry = path11.join(moduleDir, "drivers", "piSdkRunner.js");
3786
- if (existsSync8(bundledEntry)) return bundledEntry;
3787
- return path11.join(moduleDir, "piSdkRunner.js");
3788
- }
3789
- function buildPiSdkNodeArgs(runnerPath = resolvePiSdkRunnerPath()) {
3790
- if (runnerPath.endsWith(".ts")) {
3791
- return [...process.execArgv, runnerPath];
3792
- }
3793
- return [runnerPath];
3794
- }
3795
- function buildPiLaunchOptions(ctx, opts = {}) {
3796
- const command = opts.command ?? process.execPath;
3797
- const piDir = path11.join(ctx.workingDirectory, ".slock", "pi");
3798
- const sessionDir = path11.join(piDir, "sessions");
3799
- mkdirSync4(sessionDir, { recursive: true });
3800
- const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
3801
- const runnerPath = opts.runnerPath ?? resolvePiSdkRunnerPath();
3802
- const agentDir = opts.agentDir ?? getAgentDir();
3803
- const runnerConfigPath = path11.join(
3804
- piDir,
3805
- `sdk-run-${(ctx.launchId || "launch").replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`
3806
- );
3807
- const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
3808
- const runnerConfig = {
3809
- cwd: ctx.workingDirectory,
3810
- agentDir,
3811
- sessionDir,
3812
- sessionId: ctx.config.sessionId || null,
3813
- standingPrompt: ctx.standingPrompt,
3814
- prompt: turnPrompt,
3815
- model: ctx.config.model && ctx.config.model !== "default" ? ctx.config.model : null
3816
- };
3817
- writeFileSync7(runnerConfigPath, `${JSON.stringify(runnerConfig)}
3818
- `, { encoding: "utf8", mode: 384 });
3819
- const args = [
3820
- ...buildPiSdkNodeArgs(runnerPath),
3821
- "--config",
3822
- runnerConfigPath
3823
- ];
3824
- return {
3825
- command,
3826
- args,
3827
- env: slock.spawnEnv,
3828
- sessionDir,
3829
- agentDir,
3830
- runnerConfigPath,
3831
- sdkVersion: PI_SDK_VERSION
3832
- };
3833
- }
3834
- function isSystemFirstMessageTask2(message) {
3835
- return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
3836
- }
3837
- function buildPiSystemPrompt(config) {
3838
- return buildCliTransportSystemPrompt(config, {
3839
- toolPrefix: CHAT_MCP_TOOL_PREFIX2,
3840
- extraCriticalRules: [
3841
- "- Runtime Profile migration controls are not available in the Pi runtime yet. If asked to acknowledge a runtime migration, explain the blocker instead of inventing a command."
3842
- ],
3843
- postStartupNotes: [
3844
- "**Pi runtime note:** Slock launches you as a per-turn process. Complete the current wake using `slock` CLI commands, then stop; the daemon will restart you when new messages arrive."
3845
- ],
3846
- includeStdinNotificationSection: false,
3847
- messageNotificationStyle: "poll"
3848
- });
3849
- }
3850
- function contentText(content) {
3851
- if (!content) return "";
3852
- const chunks = [];
3853
- for (const item of content) {
3854
- if (item.type === "text" && typeof item.text === "string") {
3855
- chunks.push(item.text);
3856
- }
3857
- }
3858
- return chunks.join("");
3859
- }
3860
- function apiKeyErrorMessage(line) {
3861
- const trimmed = line.trim();
3862
- if (!trimmed) return null;
3863
- if (/no api key found/i.test(trimmed)) return trimmed;
3864
- if (/api key.+required/i.test(trimmed)) return trimmed;
3865
- if (/no models available/i.test(trimmed)) return trimmed;
3866
- return null;
3867
- }
3868
- var PiDriver = class {
3869
- id = "pi";
3870
- lifecycle = {
3871
- kind: "per_turn",
3872
- start: "defer_until_concrete_message",
3873
- exit: "terminate_on_turn_end",
3874
- inFlightWake: "coalesce_into_pending"
3875
- };
3876
- communication = {
3877
- chat: "slock_cli",
3878
- runtimeControl: "none"
3879
- };
3880
- session = {
3881
- recovery: "resume_or_fresh"
3882
- };
3883
- model = {
3884
- detectedModelsVerifiedAs: "launchable",
3885
- toLaunchSpec: (modelId, ctx) => {
3886
- if (!ctx) return modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] };
3887
- const launchCtx = {
3888
- ...ctx,
3889
- config: {
3890
- ...ctx.config,
3891
- model: modelId
3892
- }
3893
- };
3894
- const launch = buildPiLaunchOptions(launchCtx);
3895
- return {
3896
- args: launch.args,
3897
- env: launch.env,
3898
- configFiles: [launch.runnerConfigPath],
3899
- params: {
3900
- agentDir: launch.agentDir,
3901
- sessionDir: launch.sessionDir,
3902
- sdkVersion: launch.sdkVersion,
3903
- resources: "extensions/skills/prompt-templates/themes/context-files disabled by Slock policy"
3904
- }
3905
- };
3906
- }
3907
- };
3908
- supportsStdinNotification = false;
3909
- mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
3910
- busyDeliveryMode = "none";
3911
- terminateProcessOnTurnEnd = true;
3912
- deferSpawnUntilMessage = true;
3913
- usesSlockCliForCommunication = true;
3914
- sessionId = null;
3915
- sessionAnnounced = false;
3916
- apiKeyErrorAnnounced = false;
3917
- turnEnded = false;
3918
- assistantTextByMessageId = /* @__PURE__ */ new Map();
3919
- shouldDeferWakeMessage(message) {
3920
- return isSystemFirstMessageTask2(message);
3921
- }
3922
- probe() {
3923
- return probePi();
3924
- }
3925
- async detectModels() {
3926
- return null;
3927
- }
3928
- spawn(ctx) {
3929
- this.sessionId = ctx.config.sessionId || null;
3930
- this.sessionAnnounced = false;
3931
- this.apiKeyErrorAnnounced = false;
3932
- this.turnEnded = false;
3933
- this.assistantTextByMessageId.clear();
3934
- const unsupportedMessage = unsupportedPiVersionMessage(PI_SDK_VERSION);
3935
- if (unsupportedMessage) throw new Error(unsupportedMessage);
3936
- const launch = buildPiLaunchOptions(ctx);
3937
- const proc = spawn8(launch.command, launch.args, {
3938
- cwd: ctx.workingDirectory,
3939
- stdio: ["pipe", "pipe", "pipe"],
3940
- env: launch.env,
3941
- shell: false
3942
- });
3943
- proc.stdin?.end();
3944
- return { process: proc };
3945
- }
3946
- parseLine(line) {
3947
- let event;
3948
- try {
3949
- event = JSON.parse(line);
3950
- } catch {
3951
- if (this.apiKeyErrorAnnounced) return [];
3952
- const message = apiKeyErrorMessage(line);
3953
- if (!message) return [];
3954
- this.apiKeyErrorAnnounced = true;
3955
- this.turnEnded = true;
3956
- return [
3957
- { kind: "error", message },
3958
- { kind: "turn_end", sessionId: this.sessionId || void 0 }
3959
- ];
3960
- }
3961
- const events = [];
3962
- if (event.type === "session" && event.id) {
3963
- this.sessionId = event.id;
3964
- }
3965
- if (!this.sessionAnnounced && this.sessionId) {
3966
- events.push({ kind: "session_init", sessionId: this.sessionId });
3967
- this.sessionAnnounced = true;
3968
- }
3969
- switch (event.type) {
3970
- case "agent_start":
3971
- case "turn_start":
3972
- events.push({ kind: "thinking", text: "" });
3973
- break;
3974
- case "message_update":
3975
- case "message_end":
3976
- if (event.message?.role === "assistant") {
3977
- const key = event.message.id || "current";
3978
- const currentText = contentText(event.message.content);
3979
- const previousText = this.assistantTextByMessageId.get(key) ?? "";
3980
- if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
3981
- events.push({ kind: "text", text: currentText.slice(previousText.length) });
3982
- } else if (currentText && currentText !== previousText) {
3983
- events.push({ kind: "text", text: currentText });
3984
- }
3985
- this.assistantTextByMessageId.set(key, currentText);
3986
- if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
3987
- events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
3988
- }
3989
- }
3990
- break;
3991
- case "tool_execution_start":
3992
- events.push({
3993
- kind: "tool_call",
3994
- name: event.toolName || "unknown_tool",
3995
- input: event.args
3996
- });
3997
- break;
3998
- case "tool_execution_end":
3999
- events.push({
4000
- kind: "tool_output",
4001
- name: event.toolName || "unknown_tool"
4002
- });
4003
- if (event.isError) {
4004
- events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
4005
- }
4006
- break;
4007
- case "compaction_start":
4008
- events.push({ kind: "compaction_started" });
4009
- break;
4010
- case "compaction_end":
4011
- events.push({ kind: "compaction_finished" });
4012
- if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
4013
- break;
4014
- case "turn_end":
4015
- case "agent_end":
4016
- if (!this.turnEnded) {
4017
- events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
4018
- this.turnEnded = true;
4019
- }
4020
- break;
4021
- }
4022
- return events;
4023
- }
4024
- encodeStdinMessage(_text, _sessionId, _opts) {
4025
- return null;
4026
- }
4027
- buildSystemPrompt(config, _agentId) {
4028
- return buildPiSystemPrompt(config);
4029
- }
4030
- };
4031
-
4032
3974
  // src/drivers/index.ts
4033
3975
  var driverFactories = {
4034
3976
  claude: () => new ClaudeDriver(),
@@ -4037,8 +3979,7 @@ var driverFactories = {
4037
3979
  cursor: () => new CursorDriver(),
4038
3980
  gemini: () => new GeminiDriver(),
4039
3981
  kimi: () => new KimiDriver(),
4040
- opencode: () => new OpenCodeDriver(),
4041
- pi: () => new PiDriver()
3982
+ opencode: () => new OpenCodeDriver()
4042
3983
  };
4043
3984
  function getDriver(runtimeId) {
4044
3985
  const createDriver = driverFactories[runtimeId];
@@ -4051,7 +3992,7 @@ function getDriver(runtimeId) {
4051
3992
 
4052
3993
  // src/workspaces.ts
4053
3994
  import { readdir, rm, stat } from "fs/promises";
4054
- import path12 from "path";
3995
+ import path11 from "path";
4055
3996
  function isValidWorkspaceDirectoryName(directoryName) {
4056
3997
  return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
4057
3998
  }
@@ -4059,7 +4000,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
4059
4000
  if (!isValidWorkspaceDirectoryName(directoryName)) {
4060
4001
  return null;
4061
4002
  }
4062
- return path12.join(dataDir, directoryName);
4003
+ return path11.join(dataDir, directoryName);
4063
4004
  }
4064
4005
  function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
4065
4006
  return {
@@ -4108,7 +4049,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
4108
4049
  return summary;
4109
4050
  }
4110
4051
  const childSummaries = await Promise.all(
4111
- entries.map((entry) => summarizeWorkspaceEntry(path12.join(dirPath, entry.name), entry))
4052
+ entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
4112
4053
  );
4113
4054
  for (const childSummary of childSummaries) {
4114
4055
  summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
@@ -4127,7 +4068,7 @@ async function scanWorkspaceDirectories(dataDir) {
4127
4068
  if (!entry.isDirectory()) {
4128
4069
  return null;
4129
4070
  }
4130
- const dirPath = path12.join(dataDir, entry.name);
4071
+ const dirPath = path11.join(dataDir, entry.name);
4131
4072
  try {
4132
4073
  const summary = await summarizeWorkspaceDirectory(dirPath);
4133
4074
  return {
@@ -4240,6 +4181,8 @@ function redactUrlQuery(value) {
4240
4181
  // src/agentProcessManager.ts
4241
4182
  var DEFAULT_MAX_CONCURRENT_AGENT_STARTS = 5;
4242
4183
  var DEFAULT_AGENT_START_INTERVAL_MS = 500;
4184
+ var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS = 3;
4185
+ var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS = 250;
4243
4186
  var WORKSPACE_TEXT_FILE_MAX_BYTES = 1048576;
4244
4187
  var WORKSPACE_IMAGE_PREVIEW_MAX_BYTES = 5 * 1024 * 1024;
4245
4188
  var WORKSPACE_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -4284,6 +4227,41 @@ function readNonNegativeIntegerEnv(name, fallback) {
4284
4227
  if (!Number.isFinite(parsed) || parsed < 0) return fallback;
4285
4228
  return Math.floor(parsed);
4286
4229
  }
4230
+ var RunnerCredentialMintError = class extends Error {
4231
+ code;
4232
+ retryable;
4233
+ status;
4234
+ constructor(message, opts) {
4235
+ super(message);
4236
+ this.name = "RunnerCredentialMintError";
4237
+ this.code = opts.code;
4238
+ this.retryable = opts.retryable ?? false;
4239
+ this.status = opts.status;
4240
+ }
4241
+ };
4242
+ function isRetryableMintHttpFailure(status, code) {
4243
+ if (code === "experimental_surface_disabled") return false;
4244
+ return status === 408 || status === 425 || status === 429 || status >= 500;
4245
+ }
4246
+ function runnerCredentialErrorDetail(error) {
4247
+ if (error instanceof RunnerCredentialMintError) {
4248
+ return {
4249
+ message: error.message,
4250
+ code: error.code,
4251
+ retryable: error.retryable,
4252
+ status: error.status
4253
+ };
4254
+ }
4255
+ const message = error instanceof Error ? error.message : String(error);
4256
+ return {
4257
+ message,
4258
+ code: "runner_credential_mint_network_error",
4259
+ retryable: true
4260
+ };
4261
+ }
4262
+ async function waitForRunnerCredentialRetry() {
4263
+ await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS));
4264
+ }
4287
4265
  function stalledRecoverySigtermTimeoutMs() {
4288
4266
  return readNonNegativeIntegerEnv(
4289
4267
  "SLOCK_DAEMON_STALLED_RECOVERY_SIGTERM_TIMEOUT_MS",
@@ -4312,6 +4290,22 @@ function formatMessageTarget(message) {
4312
4290
  }
4313
4291
  return `#${message.channel_name}`;
4314
4292
  }
4293
+ function formatVisibleMessageTarget(message) {
4294
+ if (message.channel_type === "thread" && message.parent_channel_name && message.channel_name) {
4295
+ const shortId = getMessageShortId(String(message.channel_name));
4296
+ if (message.parent_channel_type === "dm") {
4297
+ return `dm:@${message.parent_channel_name}:${shortId}`;
4298
+ }
4299
+ return `#${message.parent_channel_name}:${shortId}`;
4300
+ }
4301
+ if (message.channel_type === "dm" && message.channel_name) {
4302
+ return `dm:@${message.channel_name}`;
4303
+ }
4304
+ if (message.channel_name) {
4305
+ return `#${message.channel_name}`;
4306
+ }
4307
+ return null;
4308
+ }
4315
4309
  function getMessageShortId(messageId) {
4316
4310
  return messageId.startsWith("thread-") ? messageId.slice(7) : messageId.slice(0, 8);
4317
4311
  }
@@ -4331,12 +4325,12 @@ function findSessionJsonl(root, predicate) {
4331
4325
  for (const entry of entries) {
4332
4326
  if (++visited > maxEntries) return null;
4333
4327
  if (!entry.isFile() || !predicate(entry.name)) continue;
4334
- return path13.join(dir, entry.name);
4328
+ return path12.join(dir, entry.name);
4335
4329
  }
4336
4330
  for (const entry of entries) {
4337
4331
  if (++visited > maxEntries) return null;
4338
4332
  if (!entry.isDirectory()) continue;
4339
- const found = visit(path13.join(dir, entry.name), depth - 1);
4333
+ const found = visit(path12.join(dir, entry.name), depth - 1);
4340
4334
  if (found) return found;
4341
4335
  }
4342
4336
  return null;
@@ -4349,10 +4343,10 @@ function safeSessionFilename(value) {
4349
4343
  }
4350
4344
  function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
4351
4345
  try {
4352
- const dir = path13.join(fallbackDir, ".slock", "runtime-sessions");
4353
- mkdirSync5(dir, { recursive: true });
4354
- const filePath = path13.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
4355
- writeFileSync8(filePath, JSON.stringify({
4346
+ const dir = path12.join(fallbackDir, ".slock", "runtime-sessions");
4347
+ mkdirSync4(dir, { recursive: true });
4348
+ const filePath = path12.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
4349
+ writeFileSync7(filePath, JSON.stringify({
4356
4350
  type: "runtime_session_handoff",
4357
4351
  runtime,
4358
4352
  sessionId,
@@ -4371,7 +4365,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
4371
4365
  }
4372
4366
  }
4373
4367
  function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir) {
4374
- const directPath = path13.isAbsolute(sessionId) ? sessionId : null;
4368
+ const directPath = path12.isAbsolute(sessionId) ? sessionId : null;
4375
4369
  if (directPath) {
4376
4370
  try {
4377
4371
  if (statSync2(directPath).isFile()) {
@@ -4380,7 +4374,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
4380
4374
  } catch {
4381
4375
  }
4382
4376
  }
4383
- const resolvedPath = runtime === "claude" ? findSessionJsonl(path13.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`) : runtime === "codex" ? findSessionJsonl(path13.join(homeDir, ".codex", "sessions"), (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId)) : null;
4377
+ const resolvedPath = runtime === "claude" ? findSessionJsonl(path12.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`) : runtime === "codex" ? findSessionJsonl(path12.join(homeDir, ".codex", "sessions"), (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId)) : null;
4384
4378
  if (!resolvedPath && fallbackDir) {
4385
4379
  const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
4386
4380
  if (fallback) return fallback;
@@ -4932,6 +4926,14 @@ function createRuntimeTraceCounters() {
4932
4926
  thinkingEvents: 0
4933
4927
  };
4934
4928
  }
4929
+ function cleanupAgentCredentialProxy(agentId, launchId) {
4930
+ unregisterAgentCredentialProxyForLaunch({ agentId, launchId });
4931
+ }
4932
+ function stripManagedRunnerCredential(config) {
4933
+ if (!config.agentCredentialKey && !config.agentCredentialId) return config;
4934
+ const { agentCredentialKey: _agentCredentialKey, agentCredentialId: _agentCredentialId, ...rest } = config;
4935
+ return rest;
4936
+ }
4935
4937
  function createGatedSteeringState() {
4936
4938
  return {
4937
4939
  phase: "idle",
@@ -5263,6 +5265,7 @@ var AgentProcessManager = class _AgentProcessManager {
5263
5265
  tracer;
5264
5266
  deliveryTraceContexts = /* @__PURE__ */ new WeakMap();
5265
5267
  processExitTraceAttrs = /* @__PURE__ */ new WeakMap();
5268
+ agentVisibleBoundaries = /* @__PURE__ */ new Map();
5266
5269
  constructor(chatBridgePath, sendToServer, daemonApiKey, opts) {
5267
5270
  this.chatBridgePath = chatBridgePath;
5268
5271
  this.slockCliPath = opts.slockCliPath ?? "";
@@ -5290,6 +5293,97 @@ var AgentProcessManager = class _AgentProcessManager {
5290
5293
  setTracer(tracer) {
5291
5294
  this.tracer = tracer;
5292
5295
  }
5296
+ visibleBoundaryMap(agentId) {
5297
+ let map = this.agentVisibleBoundaries.get(agentId);
5298
+ if (!map) {
5299
+ map = /* @__PURE__ */ new Map();
5300
+ this.agentVisibleBoundaries.set(agentId, map);
5301
+ }
5302
+ return map;
5303
+ }
5304
+ getVisibleBoundary(agentId, target) {
5305
+ return this.agentVisibleBoundaries.get(agentId)?.get(target);
5306
+ }
5307
+ pendingVisibleMessages(agentId, target) {
5308
+ const collect = (messages) => (messages ?? []).filter((message) => formatMessageTarget(message) === target && typeof message.seq === "number" && message.seq > 0);
5309
+ return [
5310
+ ...collect(this.agents.get(agentId)?.inbox),
5311
+ ...collect(this.startingInboxes.get(agentId))
5312
+ ];
5313
+ }
5314
+ /**
5315
+ * Single-inbox consume point for messages that have been rendered into the
5316
+ * agent-visible input surface. Daemon pending inbox is only a runner cache:
5317
+ * once a message is visible through wake/stdin/check/read/held/preflight, the
5318
+ * same seq must not be injected later through the local pending queue.
5319
+ */
5320
+ consumeVisibleMessages(agentId, input) {
5321
+ if (input.messages.length === 0 && (!input.target || typeof input.boundarySeq !== "number")) return;
5322
+ const byTarget = /* @__PURE__ */ new Map();
5323
+ const ensureBucket = (target) => {
5324
+ let bucket = byTarget.get(target);
5325
+ if (!bucket) {
5326
+ bucket = { maxSeq: 0, boundarySeq: 0, seqs: /* @__PURE__ */ new Set(), ids: /* @__PURE__ */ new Set() };
5327
+ byTarget.set(target, bucket);
5328
+ }
5329
+ return bucket;
5330
+ };
5331
+ for (const message of input.messages) {
5332
+ const seq = Number(message.seq ?? 0);
5333
+ if (!Number.isFinite(seq) || seq <= 0) continue;
5334
+ const target = input.target ?? formatVisibleMessageTarget(message);
5335
+ if (!target) continue;
5336
+ const bucket = ensureBucket(target);
5337
+ bucket.maxSeq = Math.max(bucket.maxSeq, Math.floor(seq));
5338
+ bucket.seqs.add(Math.floor(seq));
5339
+ const id = typeof message.id === "string" ? message.id : typeof message.message_id === "string" ? message.message_id : null;
5340
+ if (id) bucket.ids.add(id);
5341
+ }
5342
+ if (input.target && typeof input.boundarySeq === "number" && Number.isFinite(input.boundarySeq) && input.boundarySeq > 0) {
5343
+ const bucket = ensureBucket(input.target);
5344
+ bucket.boundarySeq = Math.max(bucket.boundarySeq, Math.floor(input.boundarySeq));
5345
+ }
5346
+ if (byTarget.size === 0) return;
5347
+ const boundaryMap = this.visibleBoundaryMap(agentId);
5348
+ for (const [target, bucket] of byTarget) {
5349
+ const highWaterSeq = Math.max(bucket.maxSeq, bucket.boundarySeq);
5350
+ const previous = boundaryMap.get(target) ?? 0;
5351
+ boundaryMap.set(target, Math.max(previous, highWaterSeq));
5352
+ }
5353
+ const suppress = (messages) => {
5354
+ if (!messages || messages.length === 0) return 0;
5355
+ let removed = 0;
5356
+ const retained = messages.filter((message) => {
5357
+ const target = formatMessageTarget(message);
5358
+ const bucket = byTarget.get(target);
5359
+ if (!bucket) return true;
5360
+ const seq = typeof message.seq === "number" ? Math.floor(message.seq) : 0;
5361
+ const id = message.message_id ?? "";
5362
+ const matched = seq > 0 && bucket.boundarySeq > 0 && seq <= bucket.boundarySeq || seq > 0 && bucket.seqs.has(seq) || id.length > 0 && bucket.ids.has(id);
5363
+ if (matched) removed += 1;
5364
+ return !matched;
5365
+ });
5366
+ messages.splice(0, messages.length, ...retained);
5367
+ return removed;
5368
+ };
5369
+ const active = this.agents.get(agentId);
5370
+ const removedActive = suppress(active?.inbox);
5371
+ const removedStarting = suppress(this.startingInboxes.get(agentId));
5372
+ this.recordDaemonTrace("daemon.agent.inbox.visible_consumed", {
5373
+ agentId,
5374
+ source: input.source,
5375
+ targets: [...byTarget.keys()],
5376
+ messages_count: input.messages.length,
5377
+ suppressed_pending_count: removedActive + removedStarting
5378
+ });
5379
+ }
5380
+ createAgentProxyInboxCoordinator(agentId) {
5381
+ return {
5382
+ getBoundary: (target) => this.getVisibleBoundary(agentId, target),
5383
+ getPendingMessages: (target) => this.pendingVisibleMessages(agentId, target),
5384
+ consumeVisibleMessages: (input) => this.consumeVisibleMessages(agentId, input)
5385
+ };
5386
+ }
5293
5387
  recordDaemonTrace(name, attrs, status = "ok", parentTraceparent) {
5294
5388
  const span = this.tracer.startSpan(name, {
5295
5389
  parent: parseTraceparent(parentTraceparent),
@@ -5514,26 +5608,26 @@ var AgentProcessManager = class _AgentProcessManager {
5514
5608
  this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
5515
5609
  try {
5516
5610
  const driver = this.driverResolver(config.runtime || "claude");
5517
- const agentDataDir = path13.join(this.dataDir, agentId);
5611
+ const agentDataDir = path12.join(this.dataDir, agentId);
5518
5612
  await mkdir(agentDataDir, { recursive: true });
5519
5613
  const runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
5520
- const memoryMdPath = path13.join(agentDataDir, "MEMORY.md");
5614
+ const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
5521
5615
  try {
5522
5616
  await access(memoryMdPath);
5523
5617
  } catch {
5524
5618
  const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
5525
5619
  await writeFile(memoryMdPath, initialMemoryMd);
5526
5620
  }
5527
- const notesDir = path13.join(agentDataDir, "notes");
5621
+ const notesDir = path12.join(agentDataDir, "notes");
5528
5622
  await mkdir(notesDir, { recursive: true });
5529
5623
  if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
5530
5624
  const seedFiles = buildOnboardingSeedFiles();
5531
5625
  for (const { relativePath, content } of seedFiles) {
5532
- const fullPath = path13.join(agentDataDir, relativePath);
5626
+ const fullPath = path12.join(agentDataDir, relativePath);
5533
5627
  try {
5534
5628
  await access(fullPath);
5535
5629
  } catch {
5536
- await mkdir(path13.dirname(fullPath), { recursive: true });
5630
+ await mkdir(path12.dirname(fullPath), { recursive: true });
5537
5631
  await writeFile(fullPath, content);
5538
5632
  }
5539
5633
  }
@@ -5641,7 +5735,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5641
5735
  chatBridgePath: this.chatBridgePath,
5642
5736
  slockCliPath: this.slockCliPath,
5643
5737
  daemonApiKey: this.daemonApiKey,
5644
- launchId: launchId || null
5738
+ launchId: launchId || null,
5739
+ agentCredentialProxyInboxCoordinator: this.createAgentProxyInboxCoordinator(agentId)
5645
5740
  });
5646
5741
  this.recordDaemonTrace("daemon.agent.spawn.created", {
5647
5742
  ...this.startQueueTraceAttrs(agentId, effectiveConfig, wakeMessage, unreadSummary, resumePrompt, launchId),
@@ -5697,6 +5792,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5697
5792
  this.ackInjectedRuntimeProfileControl(agentId, config.runtimeProfileControl, agentProcess.launchId);
5698
5793
  }
5699
5794
  if (wakeMessage) {
5795
+ this.consumeVisibleMessages(agentId, { messages: [wakeMessage], source: "spawn_wake_message" });
5700
5796
  this.ackInjectedRuntimeProfileMessages(agentId, [wakeMessage], agentProcess.launchId);
5701
5797
  }
5702
5798
  let buffer = "";
@@ -5790,11 +5886,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5790
5886
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "process_exit")
5791
5887
  });
5792
5888
  this.interruptCompactionIfActive(agentId);
5889
+ cleanupAgentCredentialProxy(agentId, ap.launchId);
5890
+ this.revokeManagedRunnerCredential(agentId, ap.config, ap.launchId);
5793
5891
  this.agents.delete(agentId);
5794
5892
  if (missingResumeSession) {
5795
5893
  const staleSessionId = ap.sessionId;
5796
5894
  const runtimeLabel = ap.driver.id === "opencode" ? "OpenCode" : "Claude";
5797
- const restartConfig = { ...ap.config, sessionId: null };
5895
+ const restartConfig = { ...stripManagedRunnerCredential(ap.config), sessionId: null };
5798
5896
  logger.warn(
5799
5897
  `[Agent ${agentId}] Stored ${runtimeLabel} session ${staleSessionId} is unavailable locally; falling back to cold start`
5800
5898
  );
@@ -5831,7 +5929,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5831
5929
  const unreadSummary2 = queuedWakeMessage ? buildUnreadSummary(ap.inbox, formatChannelLabel(queuedWakeMessage)) : void 0;
5832
5930
  if (queuedWakeMessage) {
5833
5931
  logger.info(`[Agent ${agentId}] Turn completed; restarting immediately for queued message`);
5834
- const nextConfig = { ...ap.config, sessionId: ap.sessionId };
5932
+ const nextConfig = { ...stripManagedRunnerCredential(ap.config), sessionId: ap.sessionId };
5835
5933
  this.idleAgentConfigs.set(agentId, {
5836
5934
  config: nextConfig,
5837
5935
  sessionId: ap.sessionId,
@@ -5841,6 +5939,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5841
5939
  this.idleAgentConfigs.delete(agentId);
5842
5940
  this.startAgent(agentId, nextConfig, queuedWakeMessage, unreadSummary2, void 0, ap.launchId || void 0).catch((err) => {
5843
5941
  logger.error(`[Agent ${agentId}] Failed to continue with queued message`, err);
5942
+ if (this.reportRunnerCredentialMintFailure(agentId, err, ap.launchId, "queued_continuation")) {
5943
+ return;
5944
+ }
5844
5945
  this.idleAgentConfigs.set(agentId, {
5845
5946
  config: nextConfig,
5846
5947
  sessionId: ap.sessionId,
@@ -5851,7 +5952,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5851
5952
  return;
5852
5953
  }
5853
5954
  this.idleAgentConfigs.set(agentId, {
5854
- config: { ...ap.config, sessionId: ap.sessionId },
5955
+ config: { ...stripManagedRunnerCredential(ap.config), sessionId: ap.sessionId },
5855
5956
  sessionId: ap.sessionId,
5856
5957
  launchId: ap.launchId
5857
5958
  });
@@ -5887,27 +5988,28 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5887
5988
  }
5888
5989
  async buildSpawnConfig(agentId, config) {
5889
5990
  const baseConfig = config.serverUrl === this.serverUrl ? config : { ...config, serverUrl: this.serverUrl };
5991
+ const runnerConfig = await this.ensureManagedRunnerCredential(agentId, baseConfig);
5890
5992
  if (!this.defaultAgentEnvVarsProvider) {
5891
- return baseConfig;
5993
+ return runnerConfig;
5892
5994
  }
5893
5995
  try {
5894
5996
  const defaultEnvVars = await this.defaultAgentEnvVarsProvider({
5895
- runtime: baseConfig.runtime,
5896
- model: baseConfig.model,
5897
- envVars: baseConfig.envVars
5997
+ runtime: runnerConfig.runtime,
5998
+ model: runnerConfig.model,
5999
+ envVars: runnerConfig.envVars
5898
6000
  });
5899
6001
  if (!defaultEnvVars || Object.keys(defaultEnvVars).length === 0) {
5900
- return baseConfig;
6002
+ return runnerConfig;
5901
6003
  }
5902
6004
  const mergedEnvVars = {
5903
6005
  ...defaultEnvVars,
5904
- ...baseConfig.envVars ?? {}
6006
+ ...runnerConfig.envVars ?? {}
5905
6007
  };
5906
- if (this.sameEnvVars(mergedEnvVars, baseConfig.envVars)) {
5907
- return baseConfig;
6008
+ if (this.sameEnvVars(mergedEnvVars, runnerConfig.envVars)) {
6009
+ return runnerConfig;
5908
6010
  }
5909
6011
  return {
5910
- ...baseConfig,
6012
+ ...runnerConfig,
5911
6013
  envVars: mergedEnvVars
5912
6014
  };
5913
6015
  } catch (error) {
@@ -5915,8 +6017,130 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5915
6017
  logger.warn(
5916
6018
  `[Agent ${agentId}] Failed to resolve default runtime env vars \u2014 continuing without machine-level defaults (${reason})`
5917
6019
  );
5918
- return baseConfig;
6020
+ return runnerConfig;
6021
+ }
6022
+ }
6023
+ async requestManagedRunnerCredentialOnce(agentId, config) {
6024
+ const url = new URL(`/internal/computer/runners/${encodeURIComponent(agentId)}/credentials`, this.serverUrl);
6025
+ const res = await fetch(url, {
6026
+ method: "POST",
6027
+ headers: {
6028
+ Authorization: `Bearer ${this.daemonApiKey}`,
6029
+ "Content-Type": "application/json",
6030
+ "X-Slock-Client": "daemon-server-session-worker"
6031
+ },
6032
+ body: JSON.stringify({
6033
+ scopes: ["send", "read", "mentions", "tasks", "reactions", "server", "channels"],
6034
+ name: `runner:${config.runtime}:${agentId.slice(0, 8)}`
6035
+ })
6036
+ });
6037
+ if (!res.ok) {
6038
+ const contentType = res.headers.get("content-type") ?? "";
6039
+ let detail = `HTTP ${res.status}`;
6040
+ let code = null;
6041
+ if (contentType.includes("application/json")) {
6042
+ const body2 = await res.json().catch(() => null);
6043
+ const error = typeof body2?.error === "string" ? body2.error : null;
6044
+ code = typeof body2?.code === "string" ? body2.code : null;
6045
+ detail = [detail, code, error].filter(Boolean).join(" ");
6046
+ }
6047
+ throw new RunnerCredentialMintError(detail, {
6048
+ code: code ?? "runner_credential_mint_http_error",
6049
+ retryable: isRetryableMintHttpFailure(res.status, code),
6050
+ status: res.status
6051
+ });
6052
+ }
6053
+ const body = await res.json().catch(() => null);
6054
+ if (typeof body?.apiKey !== "string" || !body.apiKey.startsWith("sk_agent_")) {
6055
+ throw new RunnerCredentialMintError("invalid_agent_credential_payload", {
6056
+ code: "invalid_agent_credential_payload"
6057
+ });
6058
+ }
6059
+ return {
6060
+ apiKey: body.apiKey,
6061
+ credentialId: typeof body.credentialId === "string" ? body.credentialId : null
6062
+ };
6063
+ }
6064
+ async ensureManagedRunnerCredential(agentId, config) {
6065
+ if (config.agentCredentialKey) return config;
6066
+ if (process.env.SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED === "1") {
6067
+ throw new RunnerCredentialMintError("runner credential mint is disabled by SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED", {
6068
+ code: "runner_credentials_disabled"
6069
+ });
6070
+ }
6071
+ let lastError = null;
6072
+ for (let attempt = 1; attempt <= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS; attempt += 1) {
6073
+ try {
6074
+ const credential = await this.requestManagedRunnerCredentialOnce(agentId, config);
6075
+ return {
6076
+ ...config,
6077
+ agentCredentialKey: credential.apiKey,
6078
+ agentCredentialId: credential.credentialId
6079
+ };
6080
+ } catch (err) {
6081
+ lastError = err;
6082
+ const detail2 = runnerCredentialErrorDetail(err);
6083
+ this.recordDaemonTrace("daemon.runner_credential_mint.retry", {
6084
+ agentId,
6085
+ runtime: config.runtime,
6086
+ attempt,
6087
+ max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS,
6088
+ status: detail2.status,
6089
+ code: detail2.code,
6090
+ reason: detail2.message,
6091
+ retryable: detail2.retryable
6092
+ }, detail2.retryable && attempt < RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS ? "ok" : "error");
6093
+ if (!detail2.retryable || attempt >= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS) break;
6094
+ await waitForRunnerCredentialRetry();
6095
+ }
5919
6096
  }
6097
+ const detail = runnerCredentialErrorDetail(lastError);
6098
+ this.recordDaemonTrace("daemon.runner_credential_mint.failed", {
6099
+ agentId,
6100
+ runtime: config.runtime,
6101
+ status: detail.status,
6102
+ code: detail.code,
6103
+ reason: detail.message,
6104
+ retryable: detail.retryable,
6105
+ max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS
6106
+ }, "error");
6107
+ throw new RunnerCredentialMintError(
6108
+ `runner_credential_mint_failed: ${detail.message}. Managed runner startup requires /internal/computer credential mint; deploy server first or roll back the daemon binary.`,
6109
+ {
6110
+ code: detail.code,
6111
+ retryable: detail.retryable,
6112
+ status: detail.status
6113
+ }
6114
+ );
6115
+ }
6116
+ revokeManagedRunnerCredential(agentId, config, launchId) {
6117
+ const credentialId = config.agentCredentialId;
6118
+ if (!credentialId) return;
6119
+ const url = new URL(
6120
+ `/internal/computer/runners/${encodeURIComponent(agentId)}/credentials/${encodeURIComponent(credentialId)}`,
6121
+ this.serverUrl
6122
+ );
6123
+ void fetch(url, {
6124
+ method: "DELETE",
6125
+ headers: {
6126
+ Authorization: `Bearer ${this.daemonApiKey}`,
6127
+ "X-Slock-Client": "daemon-server-session-worker"
6128
+ }
6129
+ }).then((res) => {
6130
+ this.recordDaemonTrace("daemon.runner_credential.revoke", {
6131
+ agentId,
6132
+ launchId: launchId || void 0,
6133
+ credentialId,
6134
+ status: res.status
6135
+ }, res.ok ? "ok" : "error");
6136
+ }).catch((err) => {
6137
+ this.recordDaemonTrace("daemon.runner_credential.revoke", {
6138
+ agentId,
6139
+ launchId: launchId || void 0,
6140
+ credentialId,
6141
+ reason: err instanceof Error ? err.message : String(err)
6142
+ }, "error");
6143
+ });
5920
6144
  }
5921
6145
  sameEnvVars(left, right) {
5922
6146
  const leftKeys = Object.keys(left ?? {});
@@ -6006,6 +6230,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6006
6230
  }
6007
6231
  this.clearCompactionWatchdog(ap);
6008
6232
  this.clearStalledRecoverySigtermWatchdog(ap);
6233
+ cleanupAgentCredentialProxy(agentId, ap.launchId);
6234
+ this.revokeManagedRunnerCredential(agentId, ap.config, ap.launchId);
6009
6235
  this.agents.delete(agentId);
6010
6236
  this.processExitTraceAttrs.set(ap.process, {
6011
6237
  stop_source: silent ? "daemon_internal" : "explicit_request",
@@ -6089,10 +6315,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6089
6315
  session_id_present: Boolean(cached.sessionId),
6090
6316
  launchId: cached.launchId || void 0
6091
6317
  }));
6092
- this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).catch((err) => {
6318
+ return this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).then(() => true, (err) => {
6093
6319
  logger.error(`[Agent ${agentId}] Failed to auto-restart`, err);
6320
+ if (this.reportRunnerCredentialMintFailure(agentId, err, cached.launchId, "idle_auto_restart")) {
6321
+ return false;
6322
+ }
6323
+ this.idleAgentConfigs.set(agentId, cached);
6324
+ return false;
6094
6325
  });
6095
- return true;
6096
6326
  }
6097
6327
  logger.warn(`[Agent ${agentId}] Delivery received but no running process or cached idle config exists`);
6098
6328
  this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
@@ -6242,7 +6472,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6242
6472
  return true;
6243
6473
  }
6244
6474
  async resetWorkspace(agentId) {
6245
- const agentDataDir = path13.join(this.dataDir, agentId);
6475
+ const agentDataDir = path12.join(this.dataDir, agentId);
6246
6476
  try {
6247
6477
  await rm2(agentDataDir, { recursive: true, force: true });
6248
6478
  logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
@@ -6279,7 +6509,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6279
6509
  return result;
6280
6510
  }
6281
6511
  buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
6282
- const workspacePath = path13.join(this.dataDir, agentId);
6512
+ const workspacePath = path12.join(this.dataDir, agentId);
6283
6513
  return {
6284
6514
  agentId,
6285
6515
  launchId,
@@ -6411,19 +6641,28 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6411
6641
  if (cached) {
6412
6642
  logger.info(`[Agent ${agentId}] Starting from idle state for runtime profile ${kind} ${key}`);
6413
6643
  this.idleAgentConfigs.delete(agentId);
6414
- this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).catch((err) => {
6644
+ return this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).then(() => true, (err) => {
6415
6645
  logger.error(`[Agent ${agentId}] Failed to auto-restart for runtime profile notification`, err);
6416
- this.idleAgentConfigs.set(agentId, cached);
6417
- });
6418
- span.end("ok", {
6419
- attrs: {
6420
- outcome: "restart_queued",
6421
- runtime: cached.config.runtime,
6422
- launchId: cached.launchId || void 0,
6423
- session_id_present: Boolean(cached.sessionId)
6646
+ if (this.reportRunnerCredentialMintFailure(agentId, err, cached.launchId, "runtime_profile_auto_restart")) {
6647
+ span.end("error", {
6648
+ attrs: {
6649
+ outcome: "runner_credential_mint_failed",
6650
+ runtime: cached.config.runtime,
6651
+ launchId: cached.launchId || void 0
6652
+ }
6653
+ });
6654
+ return false;
6424
6655
  }
6656
+ this.idleAgentConfigs.set(agentId, cached);
6657
+ span.end("error", {
6658
+ attrs: {
6659
+ outcome: "restart_failed",
6660
+ runtime: cached.config.runtime,
6661
+ launchId: cached.launchId || void 0
6662
+ }
6663
+ });
6664
+ return false;
6425
6665
  });
6426
- return true;
6427
6666
  }
6428
6667
  logger.warn(`[Agent ${agentId}] Runtime profile ${kind} ${key} has no runtime injection path yet; leaving unacked for retry`);
6429
6668
  span.end("ok", { attrs: { outcome: "no_path" } });
@@ -6527,7 +6766,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6527
6766
  }
6528
6767
  // Workspace file browsing
6529
6768
  async getFileTree(agentId, dirPath) {
6530
- const agentDir = path13.join(this.dataDir, agentId);
6769
+ const agentDir = path12.join(this.dataDir, agentId);
6531
6770
  try {
6532
6771
  await stat2(agentDir);
6533
6772
  } catch {
@@ -6535,8 +6774,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6535
6774
  }
6536
6775
  let targetDir = agentDir;
6537
6776
  if (dirPath) {
6538
- const resolved = path13.resolve(agentDir, dirPath);
6539
- if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
6777
+ const resolved = path12.resolve(agentDir, dirPath);
6778
+ if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
6540
6779
  return [];
6541
6780
  }
6542
6781
  targetDir = resolved;
@@ -6544,14 +6783,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6544
6783
  return this.listDirectoryChildren(targetDir, agentDir);
6545
6784
  }
6546
6785
  async readFile(agentId, filePath) {
6547
- const agentDir = path13.join(this.dataDir, agentId);
6548
- const resolved = path13.resolve(agentDir, filePath);
6549
- if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
6786
+ const agentDir = path12.join(this.dataDir, agentId);
6787
+ const resolved = path12.resolve(agentDir, filePath);
6788
+ if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
6550
6789
  throw new Error("Access denied");
6551
6790
  }
6552
6791
  const info = await stat2(resolved);
6553
6792
  if (info.isDirectory()) throw new Error("Cannot read a directory");
6554
- const ext = path13.extname(resolved).toLowerCase();
6793
+ const ext = path12.extname(resolved).toLowerCase();
6555
6794
  if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
6556
6795
  if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
6557
6796
  const content = await readFile(resolved, "utf-8");
@@ -6586,13 +6825,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6586
6825
  const agent = this.agents.get(agentId);
6587
6826
  const runtime = runtimeHint || agent?.config.runtime || "claude";
6588
6827
  const home = os6.homedir();
6589
- const workspaceDir = path13.join(this.dataDir, agentId);
6828
+ const workspaceDir = path12.join(this.dataDir, agentId);
6590
6829
  const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
6591
6830
  const globalResults = await Promise.all(
6592
- paths.global.map((p) => this.scanSkillsDir(path13.join(home, p)))
6831
+ paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
6593
6832
  );
6594
6833
  const workspaceResults = await Promise.all(
6595
- paths.workspace.map((p) => this.scanSkillsDir(path13.join(workspaceDir, p)))
6834
+ paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
6596
6835
  );
6597
6836
  const dedup = (skills) => {
6598
6837
  const seen = /* @__PURE__ */ new Set();
@@ -6621,7 +6860,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6621
6860
  const skills = [];
6622
6861
  for (const entry of entries) {
6623
6862
  if (entry.isDirectory() || entry.isSymbolicLink()) {
6624
- const skillMd = path13.join(dir, entry.name, "SKILL.md");
6863
+ const skillMd = path12.join(dir, entry.name, "SKILL.md");
6625
6864
  try {
6626
6865
  const content = await readFile(skillMd, "utf-8");
6627
6866
  const skill = this.parseSkillMd(entry.name, content);
@@ -6632,7 +6871,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6632
6871
  } else if (entry.name.endsWith(".md")) {
6633
6872
  const cmdName = entry.name.replace(/\.md$/, "");
6634
6873
  try {
6635
- const content = await readFile(path13.join(dir, entry.name), "utf-8");
6874
+ const content = await readFile(path12.join(dir, entry.name), "utf-8");
6636
6875
  const skill = this.parseSkillMd(cmdName, content);
6637
6876
  skill.sourcePath = dir;
6638
6877
  skills.push(skill);
@@ -7401,6 +7640,29 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7401
7640
  sendAgentStatus(agentId, status, launchId) {
7402
7641
  this.sendToServer({ type: "agent:status", agentId, status, launchId: launchId || void 0 });
7403
7642
  }
7643
+ reportRunnerCredentialMintFailure(agentId, err, launchId, source) {
7644
+ if (!(err instanceof RunnerCredentialMintError)) return false;
7645
+ const detail = runnerCredentialErrorDetail(err);
7646
+ this.recordDaemonTrace("daemon.runner_credential_mint.hard_fail", {
7647
+ agentId,
7648
+ launchId: launchId || void 0,
7649
+ source,
7650
+ status: detail.status,
7651
+ code: detail.code,
7652
+ reason: detail.message,
7653
+ retryable: detail.retryable
7654
+ }, "error");
7655
+ const reason = err.message;
7656
+ this.sendAgentStatus(agentId, "inactive", launchId);
7657
+ this.broadcastActivity(
7658
+ agentId,
7659
+ "error",
7660
+ `Start failed: ${reason}`,
7661
+ [{ kind: "text", text: `Error: ${reason}` }],
7662
+ launchId
7663
+ );
7664
+ return true;
7665
+ }
7404
7666
  noteRuntimeTraceCounter(ap, event) {
7405
7667
  ap.runtimeTraceCounters.events++;
7406
7668
  switch (event.kind) {
@@ -7563,6 +7825,7 @@ ${RESPONSE_TARGET_HINT}`);
7563
7825
  }, "error");
7564
7826
  return false;
7565
7827
  }
7828
+ this.consumeVisibleMessages(agentId, { messages, source: `stdin_${mode}_delivery` });
7566
7829
  const senders = [...new Set(messages.map((message) => `@${message.sender_name}`))].join(", ");
7567
7830
  logger.info(
7568
7831
  `[Agent ${agentId}] Delivering ${mode} ${messages.length === 1 ? "message" : `${messages.length} messages`} via stdin from ${senders}`
@@ -7596,8 +7859,8 @@ ${RESPONSE_TARGET_HINT}`);
7596
7859
  const nodes = [];
7597
7860
  for (const entry of entries) {
7598
7861
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
7599
- const fullPath = path13.join(dir, entry.name);
7600
- const relativePath = path13.relative(rootDir, fullPath);
7862
+ const fullPath = path12.join(dir, entry.name);
7863
+ const relativePath = path12.relative(rootDir, fullPath);
7601
7864
  let info;
7602
7865
  try {
7603
7866
  info = await stat2(fullPath);
@@ -7900,9 +8163,9 @@ var ReminderCache = class {
7900
8163
 
7901
8164
  // src/machineLock.ts
7902
8165
  import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
7903
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
8166
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
7904
8167
  import os7 from "os";
7905
- import path14 from "path";
8168
+ import path13 from "path";
7906
8169
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
7907
8170
  var DaemonMachineLockConflictError = class extends Error {
7908
8171
  code = "DAEMON_MACHINE_LOCK_HELD";
@@ -7924,7 +8187,7 @@ function resolveDefaultMachineStateRoot() {
7924
8187
  return resolveSlockHomePath("machines");
7925
8188
  }
7926
8189
  function ownerPath(lockDir) {
7927
- return path14.join(lockDir, "owner.json");
8190
+ return path13.join(lockDir, "owner.json");
7928
8191
  }
7929
8192
  function readOwner(lockDir) {
7930
8193
  try {
@@ -7954,13 +8217,13 @@ function acquireDaemonMachineLock(options) {
7954
8217
  const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
7955
8218
  const fingerprint = apiKeyFingerprint(options.apiKey);
7956
8219
  const lockId = getDaemonMachineLockId(options.apiKey);
7957
- const machineDir = path14.join(rootDir, lockId);
7958
- const lockDir = path14.join(machineDir, "daemon.lock");
8220
+ const machineDir = path13.join(rootDir, lockId);
8221
+ const lockDir = path13.join(machineDir, "daemon.lock");
7959
8222
  const token = randomUUID2();
7960
- mkdirSync6(machineDir, { recursive: true });
8223
+ mkdirSync5(machineDir, { recursive: true });
7961
8224
  for (let attempt = 0; attempt < 2; attempt += 1) {
7962
8225
  try {
7963
- mkdirSync6(lockDir);
8226
+ mkdirSync5(lockDir);
7964
8227
  const owner = {
7965
8228
  pid: process.pid,
7966
8229
  token,
@@ -7970,10 +8233,10 @@ function acquireDaemonMachineLock(options) {
7970
8233
  apiKeyFingerprint: fingerprint.slice(0, 16)
7971
8234
  };
7972
8235
  try {
7973
- writeFileSync9(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
8236
+ writeFileSync8(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
7974
8237
  `, { mode: 384 });
7975
8238
  } catch (err) {
7976
- rmSync2(lockDir, { recursive: true, force: true });
8239
+ rmSync3(lockDir, { recursive: true, force: true });
7977
8240
  throw err;
7978
8241
  }
7979
8242
  return {
@@ -7983,7 +8246,7 @@ function acquireDaemonMachineLock(options) {
7983
8246
  release: () => {
7984
8247
  const currentOwner = readOwner(lockDir);
7985
8248
  if (currentOwner?.pid === process.pid && currentOwner.token === token) {
7986
- rmSync2(lockDir, { recursive: true, force: true });
8249
+ rmSync3(lockDir, { recursive: true, force: true });
7987
8250
  }
7988
8251
  }
7989
8252
  };
@@ -8000,15 +8263,15 @@ function acquireDaemonMachineLock(options) {
8000
8263
  throw new DaemonMachineLockConflictError(lockDir, null);
8001
8264
  }
8002
8265
  }
8003
- rmSync2(lockDir, { recursive: true, force: true });
8266
+ rmSync3(lockDir, { recursive: true, force: true });
8004
8267
  }
8005
8268
  }
8006
8269
  throw new DaemonMachineLockConflictError(lockDir, readOwner(lockDir));
8007
8270
  }
8008
8271
 
8009
8272
  // src/localTraceSink.ts
8010
- import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync10 } from "fs";
8011
- import path15 from "path";
8273
+ import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
8274
+ import path14 from "path";
8012
8275
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
8013
8276
  var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
8014
8277
  var DEFAULT_MAX_FILES = 8;
@@ -8044,7 +8307,7 @@ var LocalRotatingTraceSink = class {
8044
8307
  currentSize = 0;
8045
8308
  sequence = 0;
8046
8309
  constructor(options) {
8047
- this.traceDir = path15.join(options.machineDir, "traces");
8310
+ this.traceDir = path14.join(options.machineDir, "traces");
8048
8311
  this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
8049
8312
  const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
8050
8313
  const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
@@ -8070,15 +8333,15 @@ var LocalRotatingTraceSink = class {
8070
8333
  return this.currentFile;
8071
8334
  }
8072
8335
  ensureFile(nextBytes) {
8073
- mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
8336
+ mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
8074
8337
  const nowMs = this.nowMsProvider();
8075
8338
  const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
8076
8339
  if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
8077
- this.currentFile = path15.join(
8340
+ this.currentFile = path14.join(
8078
8341
  this.traceDir,
8079
8342
  `daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
8080
8343
  );
8081
- writeFileSync10(this.currentFile, "", { flag: "a", mode: 384 });
8344
+ writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
8082
8345
  this.currentSize = statSync4(this.currentFile).size;
8083
8346
  this.currentFileOpenedAtMs = nowMs;
8084
8347
  this.pruneOldFiles();
@@ -8089,7 +8352,7 @@ var LocalRotatingTraceSink = class {
8089
8352
  const excess = files.length - this.maxFiles;
8090
8353
  if (excess <= 0) return;
8091
8354
  for (const file of files.slice(0, excess)) {
8092
- rmSync3(path15.join(this.traceDir, file), { force: true });
8355
+ rmSync4(path14.join(this.traceDir, file), { force: true });
8093
8356
  }
8094
8357
  }
8095
8358
  };
@@ -8176,11 +8439,11 @@ function isDiagnosticErrorAttr(key) {
8176
8439
  import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
8177
8440
  import { gzipSync } from "zlib";
8178
8441
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
8179
- import path16 from "path";
8442
+ import path15 from "path";
8180
8443
 
8181
8444
  // src/directUploadCapability.ts
8182
- function joinUrl(base, path18) {
8183
- return `${base.replace(/\/+$/, "")}${path18}`;
8445
+ function joinUrl(base, path17) {
8446
+ return `${base.replace(/\/+$/, "")}${path17}`;
8184
8447
  }
8185
8448
  function jsonHeaders(apiKey) {
8186
8449
  return {
@@ -8399,7 +8662,7 @@ var DaemonTraceBundleUploader = class {
8399
8662
  }, nextMs);
8400
8663
  }
8401
8664
  async findUploadCandidates() {
8402
- const traceDir = path16.join(this.options.machineDir, "traces");
8665
+ const traceDir = path15.join(this.options.machineDir, "traces");
8403
8666
  let names;
8404
8667
  try {
8405
8668
  names = await readdir3(traceDir);
@@ -8411,8 +8674,8 @@ var DaemonTraceBundleUploader = class {
8411
8674
  const currentFile = this.options.currentFileProvider?.();
8412
8675
  const candidates = [];
8413
8676
  for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
8414
- const file = path16.join(traceDir, name);
8415
- if (currentFile && path16.resolve(file) === path16.resolve(currentFile)) continue;
8677
+ const file = path15.join(traceDir, name);
8678
+ if (currentFile && path15.resolve(file) === path15.resolve(currentFile)) continue;
8416
8679
  if (await this.isUploaded(file)) continue;
8417
8680
  try {
8418
8681
  const info = await stat3(file);
@@ -8486,8 +8749,8 @@ var DaemonTraceBundleUploader = class {
8486
8749
  }
8487
8750
  }
8488
8751
  uploadStatePath(file) {
8489
- const stateDir = path16.join(this.options.machineDir, "trace-uploads");
8490
- return path16.join(stateDir, `${path16.basename(file)}.uploaded.json`);
8752
+ const stateDir = path15.join(this.options.machineDir, "trace-uploads");
8753
+ return path15.join(stateDir, `${path15.basename(file)}.uploaded.json`);
8491
8754
  }
8492
8755
  async isUploaded(file) {
8493
8756
  try {
@@ -8499,9 +8762,9 @@ var DaemonTraceBundleUploader = class {
8499
8762
  }
8500
8763
  async markUploaded(file, metadata) {
8501
8764
  const stateFile = this.uploadStatePath(file);
8502
- await mkdir2(path16.dirname(stateFile), { recursive: true, mode: 448 });
8765
+ await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
8503
8766
  await writeFile2(stateFile, `${JSON.stringify({
8504
- file: path16.basename(file),
8767
+ file: path15.basename(file),
8505
8768
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
8506
8769
  ...metadata
8507
8770
  }, null, 2)}
@@ -8520,10 +8783,48 @@ function readPositiveIntegerEnv2(name, fallback) {
8520
8783
 
8521
8784
  // src/core.ts
8522
8785
  var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
8523
- var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
8524
- function parseDaemonCliArgs(args, env = {}) {
8786
+ var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels"];
8787
+ var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
8788
+ var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
8789
+ var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
8790
+ var RunnerCredentialMintError2 = class extends Error {
8791
+ code;
8792
+ retryable;
8793
+ status;
8794
+ constructor(message, opts) {
8795
+ super(message);
8796
+ this.name = "RunnerCredentialMintError";
8797
+ this.code = opts.code;
8798
+ this.retryable = opts.retryable ?? false;
8799
+ this.status = opts.status;
8800
+ }
8801
+ };
8802
+ function isRetryableMintHttpFailure2(status, code) {
8803
+ if (code === "experimental_surface_disabled") return false;
8804
+ return status === 408 || status === 425 || status === 429 || status >= 500;
8805
+ }
8806
+ function runnerCredentialErrorDetail2(error) {
8807
+ if (error instanceof RunnerCredentialMintError2) {
8808
+ return {
8809
+ message: error.message,
8810
+ code: error.code,
8811
+ retryable: error.retryable,
8812
+ status: error.status
8813
+ };
8814
+ }
8815
+ const message = error instanceof Error ? error.message : String(error);
8816
+ return {
8817
+ message,
8818
+ code: "runner_credential_mint_network_error",
8819
+ retryable: true
8820
+ };
8821
+ }
8822
+ async function waitForRunnerCredentialRetry2() {
8823
+ await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
8824
+ }
8825
+ function parseDaemonCliArgs(args) {
8525
8826
  let serverUrl = "";
8526
- let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
8827
+ let apiKey = "";
8527
8828
  for (let i = 0; i < args.length; i++) {
8528
8829
  if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
8529
8830
  if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
@@ -8540,23 +8841,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
8540
8841
  }
8541
8842
  }
8542
8843
  function resolveChatBridgePath(moduleUrl = import.meta.url) {
8543
- const dirname = path17.dirname(fileURLToPath2(moduleUrl));
8544
- const jsPath = path17.resolve(dirname, "chat-bridge.js");
8844
+ const dirname = path16.dirname(fileURLToPath(moduleUrl));
8845
+ const jsPath = path16.resolve(dirname, "chat-bridge.js");
8545
8846
  try {
8546
8847
  accessSync(jsPath);
8547
8848
  return jsPath;
8548
8849
  } catch {
8549
- return path17.resolve(dirname, "chat-bridge.ts");
8850
+ return path16.resolve(dirname, "chat-bridge.ts");
8550
8851
  }
8551
8852
  }
8552
8853
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
8553
- const thisDir = path17.dirname(fileURLToPath2(moduleUrl));
8554
- const bundledDistPath = path17.resolve(thisDir, "cli", "index.js");
8854
+ const thisDir = path16.dirname(fileURLToPath(moduleUrl));
8855
+ const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
8555
8856
  try {
8556
8857
  accessSync(bundledDistPath);
8557
8858
  return bundledDistPath;
8558
8859
  } catch {
8559
- const workspaceDistPath = path17.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
8860
+ const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
8560
8861
  accessSync(workspaceDistPath);
8561
8862
  return workspaceDistPath;
8562
8863
  }
@@ -8735,7 +9036,7 @@ var DaemonCore = class {
8735
9036
  }
8736
9037
  resolveMachineStateRoot() {
8737
9038
  if (this.options.machineStateDir) return this.options.machineStateDir;
8738
- if (this.options.dataDir) return path17.join(path17.dirname(this.options.dataDir), "machines");
9039
+ if (this.options.dataDir) return path16.join(path16.dirname(this.options.dataDir), "machines");
8739
9040
  return resolveDefaultMachineStateRoot();
8740
9041
  }
8741
9042
  shouldEnableLocalTrace() {
@@ -8852,13 +9153,115 @@ var DaemonCore = class {
8852
9153
  });
8853
9154
  span.end(status);
8854
9155
  }
9156
+ async requestRunnerCredentialOnce(agentId, config) {
9157
+ const url = new URL(`/internal/computer/runners/${encodeURIComponent(agentId)}/credentials`, this.options.serverUrl);
9158
+ const res = await fetch(url, {
9159
+ method: "POST",
9160
+ headers: {
9161
+ "Authorization": `Bearer ${this.options.apiKey}`,
9162
+ "Content-Type": "application/json",
9163
+ "X-Slock-Client": "daemon-server-session-worker"
9164
+ },
9165
+ body: JSON.stringify({
9166
+ scopes: RUNNER_CREDENTIAL_SCOPES,
9167
+ name: `runner:${config.runtime}:${agentId.slice(0, 8)}`
9168
+ })
9169
+ });
9170
+ if (!res.ok) {
9171
+ const contentType = res.headers.get("content-type") ?? "";
9172
+ let detail = `HTTP ${res.status}`;
9173
+ let code = null;
9174
+ if (contentType.includes("application/json")) {
9175
+ const body2 = await res.json().catch(() => null);
9176
+ const error = typeof body2?.error === "string" ? body2.error : null;
9177
+ code = typeof body2?.code === "string" ? body2.code : null;
9178
+ detail = [detail, code, error].filter(Boolean).join(" ");
9179
+ }
9180
+ throw new RunnerCredentialMintError2(detail, {
9181
+ code: code ?? "runner_credential_mint_http_error",
9182
+ retryable: isRetryableMintHttpFailure2(res.status, code),
9183
+ status: res.status
9184
+ });
9185
+ }
9186
+ const body = await res.json().catch(() => null);
9187
+ if (typeof body?.apiKey !== "string" || !body.apiKey.startsWith("sk_agent_")) {
9188
+ throw new RunnerCredentialMintError2("invalid_agent_credential_payload", {
9189
+ code: "invalid_agent_credential_payload"
9190
+ });
9191
+ }
9192
+ return {
9193
+ apiKey: body.apiKey,
9194
+ credentialId: typeof body.credentialId === "string" ? body.credentialId : null
9195
+ };
9196
+ }
9197
+ async mintRunnerCredential(agentId, config) {
9198
+ if (config.agentCredentialKey) {
9199
+ return { apiKey: config.agentCredentialKey, credentialId: config.agentCredentialId ?? null };
9200
+ }
9201
+ if (process.env.SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED === "1") {
9202
+ throw new RunnerCredentialMintError2("runner credential mint is disabled by SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED", {
9203
+ code: "runner_credentials_disabled"
9204
+ });
9205
+ }
9206
+ let lastError = null;
9207
+ for (let attempt = 1; attempt <= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2; attempt += 1) {
9208
+ try {
9209
+ return await this.requestRunnerCredentialOnce(agentId, config);
9210
+ } catch (err) {
9211
+ lastError = err;
9212
+ const detail2 = runnerCredentialErrorDetail2(err);
9213
+ this.recordDaemonTrace("daemon.runner_credential_mint.retry", {
9214
+ agentId,
9215
+ runtime: config.runtime,
9216
+ attempt,
9217
+ max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2,
9218
+ status: detail2.status,
9219
+ code: detail2.code,
9220
+ reason: detail2.message,
9221
+ retryable: detail2.retryable
9222
+ }, detail2.retryable && attempt < RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 ? "ok" : "error");
9223
+ if (!detail2.retryable || attempt >= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2) break;
9224
+ await waitForRunnerCredentialRetry2();
9225
+ }
9226
+ }
9227
+ const detail = runnerCredentialErrorDetail2(lastError);
9228
+ this.recordDaemonTrace("daemon.runner_credential_mint.failed", {
9229
+ agentId,
9230
+ runtime: config.runtime,
9231
+ status: detail.status,
9232
+ code: detail.code,
9233
+ reason: detail.message,
9234
+ retryable: detail.retryable,
9235
+ max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2
9236
+ }, "error");
9237
+ throw new RunnerCredentialMintError2(
9238
+ `runner_credential_mint_failed: ${detail.message}. Managed runner startup requires /internal/computer credential mint; deploy server first or roll back the daemon binary.`,
9239
+ {
9240
+ code: detail.code,
9241
+ retryable: detail.retryable,
9242
+ status: detail.status
9243
+ }
9244
+ );
9245
+ }
9246
+ async startAgentFromMessage(msg) {
9247
+ const agentCredential = await this.mintRunnerCredential(msg.agentId, msg.config);
9248
+ const config = { ...msg.config, agentCredentialKey: agentCredential.apiKey, agentCredentialId: agentCredential.credentialId };
9249
+ await this.agentManager.startAgent(
9250
+ msg.agentId,
9251
+ config,
9252
+ msg.wakeMessage,
9253
+ msg.unreadSummary,
9254
+ msg.resumePrompt,
9255
+ msg.launchId
9256
+ );
9257
+ }
8855
9258
  handleMessage(msg) {
8856
9259
  const summary = summarizeIncomingMessage(msg);
8857
9260
  logger.info(`[Daemon] Received ${msg.type}${summary ? ` ${summary}` : ""}`);
8858
9261
  switch (msg.type) {
8859
9262
  case "agent:start":
8860
9263
  logger.info(`[Agent ${msg.agentId}] Start requested (runtime=${msg.config.runtime}, model=${msg.config.model}, session=${msg.config.sessionId || "new"}${msg.wakeMessage ? ", wake=true" : ""})`);
8861
- this.agentManager.startAgent(msg.agentId, msg.config, msg.wakeMessage, msg.unreadSummary, msg.resumePrompt, msg.launchId).catch((err) => {
9264
+ this.startAgentFromMessage(msg).catch((err) => {
8862
9265
  const reason = err instanceof Error ? err.message : String(err);
8863
9266
  logger.error(`[Agent ${msg.agentId}] Start failed (${reason})`);
8864
9267
  this.connection.send({ type: "agent:status", agentId: msg.agentId, status: "inactive", launchId: msg.launchId });
@@ -8891,22 +9294,27 @@ var DaemonCore = class {
8891
9294
  logger.info(`[Agent ${msg.agentId}] Delivery received (seq=${msg.seq}, from=@${msg.message.sender_name}, target=${formatChannelTarget(msg)})`);
8892
9295
  try {
8893
9296
  span.addEvent("daemon.receive", { seq: msg.seq, deliveryId: msg.deliveryId });
8894
- const accepted = this.agentManager.deliverMessage(msg.agentId, msg.message, { deliveryId: msg.deliveryId });
8895
- span.addEvent("daemon.deliver_to_agent_manager", { accepted });
8896
- if (!accepted) {
8897
- span.end("ok", { attrs: { outcome: "not-accepted" } });
8898
- break;
8899
- }
8900
- const ackSeq = msg.seq > 0 ? msg.seq : msg.message.seq ?? 0;
8901
- span.addEvent("daemon.ack.sent", { seq: ackSeq });
8902
- this.connection.send({
8903
- type: "agent:deliver:ack",
8904
- agentId: msg.agentId,
8905
- seq: ackSeq,
8906
- traceparent: formatTraceparent(span.context),
8907
- deliveryId: msg.deliveryId
9297
+ const acceptedOrPromise = this.agentManager.deliverMessage(msg.agentId, msg.message, { deliveryId: msg.deliveryId });
9298
+ Promise.resolve(acceptedOrPromise).then((accepted) => {
9299
+ span.addEvent("daemon.deliver_to_agent_manager", { accepted });
9300
+ if (!accepted) {
9301
+ span.end("ok", { attrs: { outcome: "not-accepted" } });
9302
+ return;
9303
+ }
9304
+ const ackSeq = msg.seq > 0 ? msg.seq : msg.message.seq ?? 0;
9305
+ span.addEvent("daemon.ack.sent", { seq: ackSeq });
9306
+ this.connection.send({
9307
+ type: "agent:deliver:ack",
9308
+ agentId: msg.agentId,
9309
+ seq: ackSeq,
9310
+ traceparent: formatTraceparent(span.context),
9311
+ deliveryId: msg.deliveryId
9312
+ });
9313
+ span.end("ok", { attrs: { outcome: "ack-sent", ackSeq, deliveryId: msg.deliveryId } });
9314
+ }, (err) => {
9315
+ logger.error(`[Agent ${msg.agentId}] Delivery handling failed`, err);
9316
+ span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
8908
9317
  });
8909
- span.end("ok", { attrs: { outcome: "ack-sent", ackSeq, deliveryId: msg.deliveryId } });
8910
9318
  } catch (err) {
8911
9319
  span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
8912
9320
  throw err;
@@ -8926,8 +9334,14 @@ var DaemonCore = class {
8926
9334
  }
8927
9335
  });
8928
9336
  logger.info(`[Agent ${msg.agentId}] Runtime profile migration received (${msg.migrationKey})`);
8929
- const accepted = this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.migrationKey, "migration", msg.message, formatTraceparent(span.context));
8930
- span.end("ok", { attrs: { outcome: accepted ? "accepted" : "no_injection_path" } });
9337
+ Promise.resolve(
9338
+ this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.migrationKey, "migration", msg.message, formatTraceparent(span.context))
9339
+ ).then((accepted) => {
9340
+ span.end("ok", { attrs: { outcome: accepted ? "accepted" : "no_injection_path" } });
9341
+ }, (err) => {
9342
+ logger.error(`[Agent ${msg.agentId}] Runtime profile migration handling failed`, err);
9343
+ span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
9344
+ });
8931
9345
  break;
8932
9346
  }
8933
9347
  case "agent:runtime_profile:daemon_release_notice": {
@@ -8943,8 +9357,14 @@ var DaemonCore = class {
8943
9357
  }
8944
9358
  });
8945
9359
  logger.info(`[Agent ${msg.agentId}] Runtime profile daemon release notice received (${msg.noticeKey})`);
8946
- const accepted = this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.noticeKey, "daemon_release_notice", msg.message, formatTraceparent(span.context));
8947
- span.end("ok", { attrs: { outcome: accepted ? "accepted" : "no_injection_path" } });
9360
+ Promise.resolve(
9361
+ this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.noticeKey, "daemon_release_notice", msg.message, formatTraceparent(span.context))
9362
+ ).then((accepted) => {
9363
+ span.end("ok", { attrs: { outcome: accepted ? "accepted" : "no_injection_path" } });
9364
+ }, (err) => {
9365
+ logger.error(`[Agent ${msg.agentId}] Runtime profile daemon release notice handling failed`, err);
9366
+ span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
9367
+ });
8948
9368
  break;
8949
9369
  }
8950
9370
  case "agent:workspace:list":
@@ -9118,8 +9538,6 @@ var DaemonCore = class {
9118
9538
  };
9119
9539
 
9120
9540
  export {
9121
- DAEMON_API_KEY_ENV,
9122
- scrubDaemonAuthEnv,
9123
9541
  resolveWorkspaceDirectoryPath,
9124
9542
  scanWorkspaceDirectories,
9125
9543
  deleteWorkspaceDirectory,