@slock-ai/daemon 0.50.0 → 0.51.1-play.20260519174619

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 path16 from "path";
10
+ import path17 from "path";
11
11
  import os8 from "os";
12
12
  import { createRequire } from "module";
13
13
  import { accessSync } from "fs";
14
- import { fileURLToPath } from "url";
14
+ import { fileURLToPath as fileURLToPath2 } from "url";
15
15
 
16
16
  // ../shared/src/tracing/index.ts
17
17
  var DEFAULT_TRACE_FLAGS = "00";
@@ -723,6 +723,7 @@ 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 },
726
727
  { id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true },
727
728
  { id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
728
729
  { id: "cursor", displayName: "Cursor CLI", binary: "cursor-agent", supported: true },
@@ -764,10 +765,10 @@ var DISPLAY_PLAN_CONFIG = {
764
765
  };
765
766
 
766
767
  // src/agentProcessManager.ts
767
- import { mkdirSync as mkdirSync4, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync7 } from "fs";
768
+ import { mkdirSync as mkdirSync5, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
768
769
  import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
769
770
  import { createHash as createHash2 } from "crypto";
770
- import path12 from "path";
771
+ import path13 from "path";
771
772
  import os6 from "os";
772
773
 
773
774
  // src/drivers/claude.ts
@@ -777,7 +778,7 @@ import os2 from "os";
777
778
  import path4 from "path";
778
779
 
779
780
  // src/drivers/cliTransport.ts
780
- import { mkdirSync, writeFileSync } from "fs";
781
+ import { mkdirSync, rmSync, writeFileSync } from "fs";
781
782
  import path2 from "path";
782
783
 
783
784
  // src/drivers/systemPrompt.ts
@@ -870,14 +871,16 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
870
871
  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\`.
871
872
  17. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
872
873
  18. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
873
- 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.
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\`).
874
+ 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.
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\`).
881
884
 
882
885
  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:
883
886
  - failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
@@ -978,6 +981,11 @@ Each channel has a **name** and optionally a **description** that define its pur
978
981
  - **Reply in context** \u2014 always respond in the channel/thread the message came from.
979
982
  - **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.
980
983
  - 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.`;
981
989
  const readingHistorySection = isCli ? `### Reading history
982
990
 
983
991
  \`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
@@ -1149,6 +1157,8 @@ ${discoverySection}
1149
1157
 
1150
1158
  ${channelAwarenessSection}
1151
1159
 
1160
+ ${thirdPartyIntegrationsSection}
1161
+
1152
1162
  ${readingHistorySection}
1153
1163
 
1154
1164
  ${historicalReferenceSection}
@@ -1180,6 +1190,17 @@ Keep the user informed. They cannot see your internal reasoning, so:
1180
1190
  - For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
1181
1191
  - When done, summarize the result.
1182
1192
  - 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.
1183
1204
 
1184
1205
  ### Conversation etiquette
1185
1206
 
@@ -1349,8 +1370,326 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
1349
1370
  return candidates.filter((candidate) => existsSync(candidate.path));
1350
1371
  }
1351
1372
 
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;
1379
+ }
1380
+ function scrubDaemonChildEnv(env) {
1381
+ delete env[DAEMON_API_KEY_ENV];
1382
+ delete env[SLOCK_AGENT_TOKEN_ENV];
1383
+ return env;
1384
+ }
1385
+
1386
+ // src/agentCredentialProxy.ts
1387
+ import { randomBytes } from "crypto";
1388
+ import http from "http";
1389
+ import { URL as URL2 } from "url";
1390
+ var registrations = /* @__PURE__ */ new Map();
1391
+ var servers = /* @__PURE__ */ new Map();
1392
+ var DECODED_RESPONSE_HEADERS = /* @__PURE__ */ new Set(["content-encoding", "content-length", "transfer-encoding"]);
1393
+ function allocatePort() {
1394
+ return 43e3 + randomBytes(2).readUInt16BE(0) % 1e4;
1395
+ }
1396
+ function ensureServer(port) {
1397
+ if (servers.has(port)) return;
1398
+ const server = http.createServer((req, res) => {
1399
+ void handleProxyRequest(req, res);
1400
+ });
1401
+ server.on("error", (err) => {
1402
+ logger.warn(`[Agent Credential Proxy] local proxy on port ${port} failed: ${err.message}`);
1403
+ });
1404
+ server.listen(port, "127.0.0.1");
1405
+ server.unref();
1406
+ servers.set(port, server);
1407
+ }
1408
+ function responseHeadersForLocalProxy(upstream) {
1409
+ const headers = {};
1410
+ for (const [name, value] of upstream.headers.entries()) {
1411
+ if (DECODED_RESPONSE_HEADERS.has(name.toLowerCase())) continue;
1412
+ headers[name] = value;
1413
+ }
1414
+ return headers;
1415
+ }
1416
+ async function handleProxyRequest(req, res) {
1417
+ const auth = req.headers.authorization;
1418
+ const token = typeof auth === "string" && auth.startsWith("Bearer ") ? auth.slice("Bearer ".length) : "";
1419
+ const registration = registrations.get(token);
1420
+ if (!registration) {
1421
+ res.writeHead(401, { "content-type": "application/json" });
1422
+ res.end(JSON.stringify({ error: "invalid local agent proxy token", code: "invalid_agent_proxy_token" }));
1423
+ return;
1424
+ }
1425
+ try {
1426
+ const target = new URL2(req.url ?? "/", registration.serverUrl);
1427
+ const headers = new Headers();
1428
+ for (const [name, value] of Object.entries(req.headers)) {
1429
+ if (value === void 0) continue;
1430
+ if (name.toLowerCase() === "host") continue;
1431
+ if (name.toLowerCase() === "authorization") continue;
1432
+ if (Array.isArray(value)) {
1433
+ for (const item of value) headers.append(name, item);
1434
+ } else {
1435
+ headers.set(name, value);
1436
+ }
1437
+ }
1438
+ headers.set("Authorization", `Bearer ${registration.apiKey}`);
1439
+ headers.set("X-Agent-Id", registration.agentId);
1440
+ headers.set("X-Slock-Client", "cli");
1441
+ headers.set("X-Slock-Agent-Active-Capabilities", registration.activeCapabilities);
1442
+ const method = req.method ?? "GET";
1443
+ let body = method === "GET" || method === "HEAD" ? void 0 : req;
1444
+ let sendTarget;
1445
+ if (method === "POST" && target.pathname === "/internal/agent-api/send") {
1446
+ const rawBody = await readRequestBody(req);
1447
+ const prepared = await prepareAgentApiSendForward(registration, headers, rawBody);
1448
+ if (prepared.localResponse) {
1449
+ const responseText = JSON.stringify(prepared.localResponse);
1450
+ res.writeHead(200, { "content-type": "application/json" });
1451
+ res.end(responseText);
1452
+ return;
1453
+ }
1454
+ body = prepared.bodyText;
1455
+ sendTarget = prepared.target;
1456
+ headers.set("content-type", "application/json");
1457
+ headers.set("content-length", String(Buffer.byteLength(prepared.bodyText)));
1458
+ }
1459
+ const upstream = await fetch(target, {
1460
+ method,
1461
+ headers,
1462
+ body,
1463
+ // Required by undici when forwarding a Node stream.
1464
+ duplex: body ? "half" : void 0
1465
+ });
1466
+ if (shouldBufferJsonResponse(upstream, target.pathname, registration)) {
1467
+ const responseText = await upstream.text();
1468
+ consumeVisibleResponse(registration, target, sendTarget, responseText);
1469
+ res.writeHead(upstream.status, responseHeadersForLocalProxy(upstream));
1470
+ res.end(responseText);
1471
+ return;
1472
+ }
1473
+ res.writeHead(upstream.status, responseHeadersForLocalProxy(upstream));
1474
+ if (upstream.body) {
1475
+ const reader = upstream.body.getReader();
1476
+ try {
1477
+ while (true) {
1478
+ const { done, value } = await reader.read();
1479
+ if (done) break;
1480
+ res.write(Buffer.from(value));
1481
+ }
1482
+ } finally {
1483
+ res.end();
1484
+ }
1485
+ } else {
1486
+ res.end();
1487
+ }
1488
+ } catch (err) {
1489
+ res.writeHead(502, { "content-type": "application/json" });
1490
+ res.end(JSON.stringify({
1491
+ error: "failed to proxy local agent request",
1492
+ code: "agent_proxy_failed",
1493
+ detail: err instanceof Error ? err.message : String(err)
1494
+ }));
1495
+ }
1496
+ }
1497
+ async function readRequestBody(req) {
1498
+ let body = "";
1499
+ req.setEncoding("utf8");
1500
+ for await (const chunk of req) {
1501
+ body += String(chunk);
1502
+ }
1503
+ return body;
1504
+ }
1505
+ function messageSeq(message) {
1506
+ return Number(message.seq ?? 0);
1507
+ }
1508
+ function boundaryBefore(messages) {
1509
+ let minSeq = Number.POSITIVE_INFINITY;
1510
+ for (const message of messages) {
1511
+ const seq = Math.floor(messageSeq(message));
1512
+ if (Number.isFinite(seq) && seq > 0) minSeq = Math.min(minSeq, seq);
1513
+ }
1514
+ return Number.isFinite(minSeq) ? Math.max(0, minSeq - 1) : void 0;
1515
+ }
1516
+ function maxMessageSeq(messages) {
1517
+ let maxSeq = 0;
1518
+ for (const message of messages) {
1519
+ const seq = Math.floor(messageSeq(message));
1520
+ if (Number.isFinite(seq) && seq > 0) maxSeq = Math.max(maxSeq, seq);
1521
+ }
1522
+ return maxSeq > 0 ? maxSeq : void 0;
1523
+ }
1524
+ function parseTargetFields(target) {
1525
+ if (target.startsWith("dm:@")) {
1526
+ const rest = target.slice("dm:@".length);
1527
+ const [peer, threadId] = rest.split(":", 2);
1528
+ if (threadId) {
1529
+ return {
1530
+ channel_type: "thread",
1531
+ channel_name: threadId,
1532
+ parent_channel_type: "dm",
1533
+ parent_channel_name: peer
1534
+ };
1535
+ }
1536
+ return { channel_type: "dm", channel_name: peer };
1537
+ }
1538
+ if (target.startsWith("#")) {
1539
+ const rest = target.slice(1);
1540
+ const [channel, threadId] = rest.split(":", 2);
1541
+ if (threadId) {
1542
+ return {
1543
+ channel_type: "thread",
1544
+ channel_name: threadId,
1545
+ parent_channel_type: "channel",
1546
+ parent_channel_name: channel
1547
+ };
1548
+ }
1549
+ return { channel_type: "channel", channel_name: channel };
1550
+ }
1551
+ return {};
1552
+ }
1553
+ function normalizeVisibleMessage(message, target) {
1554
+ const targetFields = target ? parseTargetFields(target) : {};
1555
+ return {
1556
+ ...targetFields,
1557
+ ...message,
1558
+ message_id: message.message_id ?? message.id,
1559
+ timestamp: message.timestamp ?? message.createdAt,
1560
+ sender_type: message.sender_type ?? message.senderType,
1561
+ sender_name: message.sender_name ?? message.senderName,
1562
+ sender_description: message.sender_description ?? message.senderDescription ?? null
1563
+ };
1564
+ }
1565
+ function normalizeVisibleMessages(messages, target) {
1566
+ return messages.map((message) => normalizeVisibleMessage(message, target));
1567
+ }
1568
+ async function loadRecentTargetMessages(registration, headers, target) {
1569
+ const historyUrl = new URL2("/internal/agent-api/history", registration.serverUrl);
1570
+ historyUrl.searchParams.set("channel", target);
1571
+ historyUrl.searchParams.set("limit", "3");
1572
+ const historyHeaders = new Headers(headers);
1573
+ historyHeaders.delete("content-length");
1574
+ historyHeaders.delete("content-type");
1575
+ const res = await fetch(historyUrl, { method: "GET", headers: historyHeaders });
1576
+ if (!res.ok) return [];
1577
+ const parsed = await res.json().catch(() => null);
1578
+ return Array.isArray(parsed?.messages) ? normalizeVisibleMessages(parsed.messages, target) : [];
1579
+ }
1580
+ async function prepareAgentApiSendForward(registration, headers, rawBody) {
1581
+ let body;
1582
+ try {
1583
+ body = rawBody ? JSON.parse(rawBody) : {};
1584
+ } catch {
1585
+ return { bodyText: rawBody };
1586
+ }
1587
+ const target = typeof body.target === "string" ? body.target : void 0;
1588
+ const coordinator = registration.inboxCoordinator;
1589
+ if (!target || !coordinator || body.continueAnyway === true) {
1590
+ return { bodyText: JSON.stringify(body), target };
1591
+ }
1592
+ const pending = coordinator.getPendingMessages(target);
1593
+ if (pending.length > 0) {
1594
+ const staleBoundary = boundaryBefore(pending);
1595
+ if (staleBoundary !== void 0) {
1596
+ body.seenUpToSeq = staleBoundary;
1597
+ }
1598
+ return { bodyText: JSON.stringify(body), target };
1599
+ }
1600
+ const existingBoundary = typeof body.seenUpToSeq === "number" && Number.isFinite(body.seenUpToSeq) ? Math.max(0, Math.floor(body.seenUpToSeq)) : void 0;
1601
+ const loadedBoundary = coordinator.getBoundary(target);
1602
+ const boundary = Math.max(existingBoundary ?? 0, loadedBoundary ?? 0);
1603
+ if (boundary > 0) {
1604
+ body.seenUpToSeq = boundary;
1605
+ return { bodyText: JSON.stringify(body), target };
1606
+ }
1607
+ const recent = await loadRecentTargetMessages(registration, headers, target);
1608
+ if (recent.length > 0) {
1609
+ const seenUpToSeq = maxMessageSeq(recent);
1610
+ coordinator.consumeVisibleMessages({ target, messages: recent, boundarySeq: seenUpToSeq, source: "send_preflight_context" });
1611
+ return {
1612
+ bodyText: JSON.stringify(body),
1613
+ target,
1614
+ localResponse: {
1615
+ state: "held",
1616
+ seenUpToSeq,
1617
+ heldMessages: recent,
1618
+ newMessageCount: recent.length,
1619
+ shownMessageCount: recent.length,
1620
+ omittedMessageCount: 0
1621
+ }
1622
+ };
1623
+ }
1624
+ return { bodyText: JSON.stringify(body), target };
1625
+ }
1626
+ function shouldBufferJsonResponse(upstream, pathname, registration) {
1627
+ if (!registration.inboxCoordinator) return false;
1628
+ const contentType = upstream.headers.get("content-type") ?? "";
1629
+ if (!contentType.includes("application/json")) return false;
1630
+ return pathname === "/internal/agent-api/send" || pathname === "/internal/agent-api/events" || pathname === "/internal/agent-api/history";
1631
+ }
1632
+ function consumeVisibleResponse(registration, targetUrl, sendTarget, responseText) {
1633
+ const coordinator = registration.inboxCoordinator;
1634
+ if (!coordinator) return;
1635
+ let parsed;
1636
+ try {
1637
+ parsed = JSON.parse(responseText);
1638
+ } catch {
1639
+ return;
1640
+ }
1641
+ if (targetUrl.pathname === "/internal/agent-api/send" && parsed.state === "held" && Array.isArray(parsed.heldMessages)) {
1642
+ coordinator.consumeVisibleMessages({
1643
+ target: sendTarget,
1644
+ messages: normalizeVisibleMessages(parsed.heldMessages, sendTarget),
1645
+ boundarySeq: typeof parsed.seenUpToSeq === "number" ? parsed.seenUpToSeq : void 0,
1646
+ source: "server_held_context"
1647
+ });
1648
+ return;
1649
+ }
1650
+ if (targetUrl.pathname === "/internal/agent-api/events" && Array.isArray(parsed.events)) {
1651
+ coordinator.consumeVisibleMessages({ messages: normalizeVisibleMessages(parsed.events), source: "agent_api_events" });
1652
+ return;
1653
+ }
1654
+ if (targetUrl.pathname === "/internal/agent-api/history" && Array.isArray(parsed.messages)) {
1655
+ const target = targetUrl.searchParams.get("channel") ?? void 0;
1656
+ const messages = normalizeVisibleMessages(parsed.messages, target);
1657
+ coordinator.consumeVisibleMessages({ target, messages, boundarySeq: maxMessageSeq(messages), source: "agent_api_history" });
1658
+ }
1659
+ }
1660
+ function registerAgentCredentialProxy(input) {
1661
+ const port = allocatePort();
1662
+ ensureServer(port);
1663
+ const proxyToken = `sap_${randomBytes(32).toString("base64url")}`;
1664
+ registrations.set(proxyToken, {
1665
+ serverUrl: input.serverUrl,
1666
+ apiKey: input.apiKey,
1667
+ agentId: input.agentId,
1668
+ launchId: input.launchId ?? null,
1669
+ activeCapabilities: input.activeCapabilities,
1670
+ inboxCoordinator: input.inboxCoordinator
1671
+ });
1672
+ return {
1673
+ proxyUrl: `http://127.0.0.1:${port}`,
1674
+ proxyToken
1675
+ };
1676
+ }
1677
+ function unregisterAgentCredentialProxyForLaunch(input) {
1678
+ let removed = 0;
1679
+ const launchId = input.launchId ?? null;
1680
+ for (const [token, registration] of registrations) {
1681
+ if (registration.agentId === input.agentId && registration.launchId === launchId) {
1682
+ registrations.delete(token);
1683
+ removed += 1;
1684
+ }
1685
+ }
1686
+ return removed;
1687
+ }
1688
+
1352
1689
  // src/drivers/cliTransport.ts
1353
1690
  var shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
1691
+ var DEFAULT_ACTIVE_CAPABILITIES = "send,read,mentions,tasks,reactions,server,channels";
1692
+ var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
1354
1693
  function runtimeContextEnv(config) {
1355
1694
  const ctx = config.runtimeContext;
1356
1695
  if (!ctx) return {};
@@ -1374,17 +1713,43 @@ function prepareCliTransport(ctx, extraEnv = {}, platform = process.platform) {
1374
1713
  }
1375
1714
  const slockDir = path2.join(ctx.workingDirectory, ".slock");
1376
1715
  mkdirSync(slockDir, { recursive: true });
1716
+ const slockHome = resolveSlockHome();
1377
1717
  const tokenFile = path2.join(slockDir, "agent-token");
1378
- writeFileSync(tokenFile, ctx.config.authToken || ctx.daemonApiKey, { mode: 384 });
1718
+ const agentCredentialKey = ctx.config.agentCredentialKey;
1719
+ let agentCredentialProxy = null;
1720
+ let agentCredentialProxyTokenFile = null;
1721
+ if (typeof agentCredentialKey === "string" && agentCredentialKey.length > 0) {
1722
+ rmSync(tokenFile, { force: true });
1723
+ agentCredentialProxy = registerAgentCredentialProxy({
1724
+ agentId: ctx.agentId,
1725
+ launchId: ctx.launchId,
1726
+ serverUrl: ctx.config.serverUrl,
1727
+ apiKey: agentCredentialKey,
1728
+ activeCapabilities: DEFAULT_ACTIVE_CAPABILITIES,
1729
+ inboxCoordinator: ctx.agentCredentialProxyInboxCoordinator
1730
+ });
1731
+ const launchPart = safePathPart(ctx.launchId || `pid-${process.pid}`);
1732
+ const proxyTokenDir = path2.join(slockHome, "agent-proxy-tokens", safePathPart(ctx.agentId));
1733
+ mkdirSync(proxyTokenDir, { recursive: true, mode: 448 });
1734
+ agentCredentialProxyTokenFile = path2.join(proxyTokenDir, `${launchPart}.token`);
1735
+ writeFileSync(agentCredentialProxyTokenFile, agentCredentialProxy.proxyToken, { mode: 384 });
1736
+ } else {
1737
+ writeFileSync(tokenFile, ctx.config.authToken || ctx.daemonApiKey, { mode: 384 });
1738
+ }
1379
1739
  const posixWrapper = path2.join(slockDir, "slock");
1740
+ 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)} ` : "";
1380
1741
  const posixBody = `#!/usr/bin/env bash
1381
- exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)} "$@"
1742
+ ${posixCredentialPrefix}exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)} "$@"
1382
1743
  `;
1383
1744
  writeFileSync(posixWrapper, posixBody, { mode: 493 });
1384
1745
  if (platform === "win32") {
1385
1746
  const cmdWrapper = path2.join(slockDir, "slock.cmd");
1747
+ const cmdCredentialLine = agentCredentialProxy ? `set "SLOCK_AGENT_PROXY_URL=${agentCredentialProxy.proxyUrl}"\r
1748
+ set "SLOCK_AGENT_PROXY_TOKEN_FILE=${agentCredentialProxyTokenFile}"\r
1749
+ set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
1750
+ ` : "";
1386
1751
  const cmdBody = `@echo off\r
1387
- "${process.execPath}" "${ctx.slockCliPath}" %*\r
1752
+ ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
1388
1753
  `;
1389
1754
  writeFileSync(cmdWrapper, cmdBody);
1390
1755
  }
@@ -1395,17 +1760,27 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)}
1395
1760
  ...ctx.config.envVars || {},
1396
1761
  ...extraEnv,
1397
1762
  ...runtimeContextEnv(ctx.config),
1398
- [SLOCK_HOME_ENV]: resolveSlockHome(),
1763
+ [SLOCK_HOME_ENV]: slockHome,
1399
1764
  SLOCK_AGENT_ID: ctx.agentId,
1400
1765
  ...ctx.launchId ? { SLOCK_AGENT_LAUNCH_ID: ctx.launchId } : {},
1401
1766
  SLOCK_SERVER_URL: ctx.config.serverUrl,
1402
- SLOCK_AGENT_TOKEN_FILE: tokenFile,
1767
+ ...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
1403
1768
  PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
1404
1769
  };
1405
- delete spawnEnv.SLOCK_AGENT_TOKEN;
1770
+ scrubDaemonChildEnv(spawnEnv);
1771
+ delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY;
1772
+ delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY_FILE;
1773
+ delete spawnEnv.SLOCK_AGENT_PROXY_URL;
1774
+ delete spawnEnv.SLOCK_AGENT_PROXY_TOKEN;
1775
+ delete spawnEnv.SLOCK_AGENT_PROXY_TOKEN_FILE;
1776
+ delete spawnEnv.SLOCK_AGENT_ACTIVE_CAPABILITIES;
1777
+ if (agentCredentialProxy) {
1778
+ delete spawnEnv.SLOCK_AGENT_TOKEN_FILE;
1779
+ }
1406
1780
  return {
1407
1781
  slockDir,
1408
1782
  tokenFile,
1783
+ agentCredentialProxyUrl: agentCredentialProxy?.proxyUrl ?? null,
1409
1784
  wrapperPath,
1410
1785
  spawnEnv
1411
1786
  };
@@ -1439,7 +1814,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn) {
1439
1814
  }
1440
1815
  function resolveCommandOnPath(command, deps = {}) {
1441
1816
  const platform = deps.platform ?? process.platform;
1442
- const env = deps.env ?? process.env;
1817
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
1443
1818
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
1444
1819
  if (platform === "win32") {
1445
1820
  return resolveCommandOnWindows(command, env, execFileSyncFn);
@@ -1464,7 +1839,7 @@ function firstExistingPath(candidates, deps = {}) {
1464
1839
  return null;
1465
1840
  }
1466
1841
  function readCommandVersion(command, args = [], deps = {}) {
1467
- const env = deps.env ?? process.env;
1842
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
1468
1843
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
1469
1844
  try {
1470
1845
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -1573,7 +1948,7 @@ function collectClaudeMcpConfigFiles(ctx, home) {
1573
1948
  return files;
1574
1949
  }
1575
1950
  function buildClaudeUserMcpServers(ctx, home) {
1576
- const servers = /* @__PURE__ */ Object.create(null);
1951
+ const servers2 = /* @__PURE__ */ Object.create(null);
1577
1952
  for (const configFile of collectClaudeMcpConfigFiles(ctx, home)) {
1578
1953
  const mcpServers = readClaudeMcpServers(configFile.path, configFile.vars);
1579
1954
  if (!mcpServers) continue;
@@ -1582,10 +1957,10 @@ function buildClaudeUserMcpServers(ctx, home) {
1582
1957
  logger.warn(`[Claude] ignoring invalid MCP server "${name}" in ${configFile.path}`);
1583
1958
  continue;
1584
1959
  }
1585
- servers[name] = server;
1960
+ servers2[name] = server;
1586
1961
  }
1587
1962
  }
1588
- return servers;
1963
+ return servers2;
1589
1964
  }
1590
1965
  var ClaudeDriver = class {
1591
1966
  id = "claude";
@@ -1633,9 +2008,9 @@ var ClaudeDriver = class {
1633
2008
  CLAUDE_DISALLOWED_TOOLS
1634
2009
  ];
1635
2010
  if (opts.standingPromptFilePath) {
1636
- args.push("--system-prompt-file", opts.standingPromptFilePath);
2011
+ args.push("--append-system-prompt-file", opts.standingPromptFilePath);
1637
2012
  } else {
1638
- args.push("--system-prompt", standingPrompt);
2013
+ args.push("--append-system-prompt", standingPrompt);
1639
2014
  }
1640
2015
  if (config.sessionId) {
1641
2016
  args.push("--resume", config.sessionId);
@@ -1883,13 +2258,29 @@ function resolveWindowsNpmCodexEntry(deps = {}) {
1883
2258
  }
1884
2259
  return null;
1885
2260
  }
2261
+ function resolveWindowsCodexDesktopEntry(deps = {}) {
2262
+ const existsSyncFn = deps.existsSyncFn ?? existsSync4;
2263
+ const env = deps.env ?? process.env;
2264
+ const homeDir = deps.homeDir;
2265
+ const winPath = path5.win32;
2266
+ const candidates = [
2267
+ env.LOCALAPPDATA ? winPath.join(env.LOCALAPPDATA, "OpenAI", "Codex", "bin", "codex.exe") : null,
2268
+ env.USERPROFILE ? winPath.join(env.USERPROFILE, "AppData", "Local", "OpenAI", "Codex", "bin", "codex.exe") : null,
2269
+ homeDir ? winPath.join(homeDir, "AppData", "Local", "OpenAI", "Codex", "bin", "codex.exe") : null
2270
+ ].filter((candidate) => Boolean(candidate));
2271
+ for (const candidate of candidates) {
2272
+ if (existsSyncFn(candidate)) return candidate;
2273
+ }
2274
+ return null;
2275
+ }
1886
2276
  function resolveCodexCommand(deps = {}) {
1887
2277
  const platform = deps.platform ?? process.platform;
1888
2278
  if (platform === "win32") {
1889
2279
  const npmEntry = resolveWindowsNpmCodexEntry(deps);
1890
2280
  if (npmEntry) return npmEntry;
1891
2281
  const command = resolveCommandOnPath("codex", deps);
1892
- return command && !isWindowsSandboxRunner(command) ? command : null;
2282
+ if (command && !isWindowsSandboxRunner(command)) return command;
2283
+ return resolveWindowsCodexDesktopEntry(deps);
1893
2284
  }
1894
2285
  const pathCommand = resolveCommandOnPath("codex", deps);
1895
2286
  if (pathCommand) return pathCommand;
@@ -1932,6 +2323,10 @@ function resolveCodexSpawn(commandArgs, deps = {}) {
1932
2323
  if (command && !isWindowsSandboxRunner(command)) {
1933
2324
  return { command, args: commandArgs };
1934
2325
  }
2326
+ const desktopEntry = resolveWindowsCodexDesktopEntry(deps);
2327
+ if (desktopEntry) {
2328
+ return { command: desktopEntry, args: commandArgs };
2329
+ }
1935
2330
  throw new Error(
1936
2331
  "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."
1937
2332
  );
@@ -2741,7 +3136,7 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
2741
3136
  }
2742
3137
  function runCursorModelsCommand() {
2743
3138
  return spawnSync("cursor-agent", ["models"], {
2744
- env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
3139
+ env: scrubDaemonChildEnv({ ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" }),
2745
3140
  encoding: "utf8",
2746
3141
  timeout: 5e3
2747
3142
  });
@@ -2788,7 +3183,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
2788
3183
  }
2789
3184
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
2790
3185
  const existsSyncFn = deps.existsSyncFn ?? existsSync6;
2791
- const env = deps.env ?? process.env;
3186
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
2792
3187
  const winPath = path8.win32;
2793
3188
  let geminiEntry = null;
2794
3189
  try {
@@ -2962,13 +3357,16 @@ var GeminiDriver = class {
2962
3357
  // src/drivers/kimi.ts
2963
3358
  import { randomUUID } from "crypto";
2964
3359
  import { spawn as spawn6 } from "child_process";
2965
- import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
3360
+ import { chmodSync, existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
2966
3361
  import os4 from "os";
2967
3362
  import path9 from "path";
2968
3363
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
2969
3364
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
2970
3365
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
2971
3366
  var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
3367
+ var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
3368
+ var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
3369
+ var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
2972
3370
  function parseToolArguments(raw) {
2973
3371
  if (typeof raw !== "string") return raw;
2974
3372
  try {
@@ -2977,6 +3375,73 @@ function parseToolArguments(raw) {
2977
3375
  return raw;
2978
3376
  }
2979
3377
  }
3378
+ function readKimiConfigSource(home = os4.homedir(), env = process.env) {
3379
+ const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
3380
+ if (inlineConfig && inlineConfig.trim()) {
3381
+ return {
3382
+ raw: inlineConfig,
3383
+ explicitPath: null,
3384
+ sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
3385
+ };
3386
+ }
3387
+ const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
3388
+ const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
3389
+ try {
3390
+ return {
3391
+ raw: readFileSync3(configPath, "utf8"),
3392
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
3393
+ sourcePath: configPath
3394
+ };
3395
+ } catch {
3396
+ return {
3397
+ raw: null,
3398
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
3399
+ sourcePath: configPath
3400
+ };
3401
+ }
3402
+ }
3403
+ function buildKimiSpawnEnv(env = process.env) {
3404
+ const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
3405
+ delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
3406
+ delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
3407
+ return scrubDaemonChildEnv(spawnEnv);
3408
+ }
3409
+ function buildKimiEffectiveEnv(ctx, overrideEnv) {
3410
+ return {
3411
+ ...process.env,
3412
+ ...ctx.config.envVars || {},
3413
+ ...overrideEnv || {}
3414
+ };
3415
+ }
3416
+ function buildKimiLaunchOptions(ctx, opts = {}) {
3417
+ const env = buildKimiEffectiveEnv(ctx, opts.env);
3418
+ const source = readKimiConfigSource(opts.home ?? os4.homedir(), env);
3419
+ const args = [];
3420
+ let configFilePath = null;
3421
+ let configContent = null;
3422
+ if (source.explicitPath) {
3423
+ configFilePath = source.explicitPath;
3424
+ } else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
3425
+ configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
3426
+ configContent = source.raw;
3427
+ if (opts.writeGeneratedConfig !== false) {
3428
+ writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
3429
+ chmodSync(configFilePath, 384);
3430
+ }
3431
+ }
3432
+ if (configFilePath) {
3433
+ args.push("--config-file", configFilePath);
3434
+ }
3435
+ if (ctx.config.model && ctx.config.model !== "default") {
3436
+ args.push("--model", ctx.config.model);
3437
+ }
3438
+ return {
3439
+ args,
3440
+ env: buildKimiSpawnEnv(env),
3441
+ configFilePath,
3442
+ configContent
3443
+ };
3444
+ }
2980
3445
  function resolveKimiSpawn(commandArgs, deps = {}) {
2981
3446
  return {
2982
3447
  command: resolveCommandOnPath("kimi", deps) ?? "kimi",
@@ -3000,7 +3465,25 @@ var KimiDriver = class {
3000
3465
  };
3001
3466
  model = {
3002
3467
  detectedModelsVerifiedAs: "launchable",
3003
- toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
3468
+ toLaunchSpec: (modelId, ctx, opts) => {
3469
+ if (!ctx) return { args: ["--model", modelId] };
3470
+ const launchCtx = {
3471
+ ...ctx,
3472
+ config: {
3473
+ ...ctx.config,
3474
+ model: modelId
3475
+ }
3476
+ };
3477
+ const launch = buildKimiLaunchOptions(launchCtx, {
3478
+ home: opts?.home,
3479
+ writeGeneratedConfig: false
3480
+ });
3481
+ return {
3482
+ args: launch.args,
3483
+ env: launch.env,
3484
+ configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
3485
+ };
3486
+ }
3004
3487
  };
3005
3488
  supportsStdinNotification = true;
3006
3489
  mcpToolPrefix = "";
@@ -3054,6 +3537,7 @@ var KimiDriver = class {
3054
3537
  }
3055
3538
  }
3056
3539
  }), "utf8");
3540
+ const launch = buildKimiLaunchOptions(ctx);
3057
3541
  const args = [
3058
3542
  "--wire",
3059
3543
  "--yolo",
@@ -3062,14 +3546,15 @@ var KimiDriver = class {
3062
3546
  "--mcp-config-file",
3063
3547
  mcpConfigPath,
3064
3548
  "--session",
3065
- this.sessionId
3549
+ this.sessionId,
3550
+ ...launch.args
3066
3551
  ];
3067
3552
  if (ctx.config.model && ctx.config.model !== "default") {
3068
3553
  args.push("--model", ctx.config.model);
3069
3554
  }
3070
3555
  const spawnEnv = prepareCliTransport(ctx, { NO_COLOR: "1" }).spawnEnv;
3071
- const launch = resolveKimiSpawn(args);
3072
- const proc = spawn6(launch.command, launch.args, {
3556
+ const spawnTarget = resolveKimiSpawn(args);
3557
+ const proc = spawn6(spawnTarget.command, spawnTarget.args, {
3073
3558
  cwd: ctx.workingDirectory,
3074
3559
  stdio: ["pipe", "pipe", "pipe"],
3075
3560
  env: spawnEnv,
@@ -3077,7 +3562,7 @@ var KimiDriver = class {
3077
3562
  // and has an 8191-character command-line limit. Kimi's official
3078
3563
  // installer/uv entrypoint is an executable, so launch it directly and
3079
3564
  // keep prompts on stdin / files instead of routing through cmd.exe.
3080
- shell: launch.shell
3565
+ shell: spawnTarget.shell
3081
3566
  });
3082
3567
  proc.stdin?.write(JSON.stringify({
3083
3568
  jsonrpc: "2.0",
@@ -3193,14 +3678,9 @@ var KimiDriver = class {
3193
3678
  return detectKimiModels();
3194
3679
  }
3195
3680
  };
3196
- function detectKimiModels(home = os4.homedir()) {
3197
- const configPath = path9.join(home, ".kimi", "config.toml");
3198
- let raw;
3199
- try {
3200
- raw = readFileSync3(configPath, "utf8");
3201
- } catch {
3202
- return null;
3203
- }
3681
+ function detectKimiModels(home = os4.homedir(), opts = {}) {
3682
+ const raw = readKimiConfigSource(home, opts.env).raw;
3683
+ if (raw === null) return null;
3204
3684
  const models = [];
3205
3685
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
3206
3686
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -3297,6 +3777,18 @@ function isSupportedOpenCodeVersion(version) {
3297
3777
  }
3298
3778
  return true;
3299
3779
  }
3780
+ var AGENT_FLAG_CUTOFF_VERSION = "1.15.0";
3781
+ function requiresAgentCliFlag(version) {
3782
+ if (!version) return true;
3783
+ const actual = parseSemver(version);
3784
+ const cutoff = parseSemver(AGENT_FLAG_CUTOFF_VERSION);
3785
+ if (!actual || !cutoff) return true;
3786
+ for (let i = 0; i < 3; i += 1) {
3787
+ if (actual[i] > cutoff[i]) return false;
3788
+ if (actual[i] < cutoff[i]) return true;
3789
+ }
3790
+ return false;
3791
+ }
3300
3792
  function unsupportedOpenCodeVersionMessage(version) {
3301
3793
  if (!version || isSupportedOpenCodeVersion(version)) return null;
3302
3794
  return `OpenCode CLI ${version} is unsupported; requires OpenCode >= ${MIN_SUPPORTED_OPENCODE_VERSION}. Upgrade opencode before starting this runtime.`;
@@ -3344,7 +3836,7 @@ function buildOpenCodeConfig(ctx, home = os5.homedir()) {
3344
3836
  }
3345
3837
  };
3346
3838
  }
3347
- function buildOpenCodeLaunchOptions(ctx, home = os5.homedir()) {
3839
+ function buildOpenCodeLaunchOptions(ctx, home = os5.homedir(), version = null) {
3348
3840
  const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
3349
3841
  const config = buildOpenCodeConfig(ctx, home);
3350
3842
  const env = {
@@ -3363,7 +3855,9 @@ function buildOpenCodeLaunchOptions(ctx, home = os5.homedir()) {
3363
3855
  if (ctx.config.model && ctx.config.model !== "default") {
3364
3856
  args.push("--model", ctx.config.model);
3365
3857
  }
3366
- args.push("--agent", SLOCK_AGENT_NAME);
3858
+ if (requiresAgentCliFlag(version)) {
3859
+ args.push("--agent", SLOCK_AGENT_NAME);
3860
+ }
3367
3861
  if (ctx.config.sessionId) {
3368
3862
  args.push("--session", ctx.config.sessionId);
3369
3863
  }
@@ -3447,7 +3941,7 @@ function detectOpenCodeModels(home = os5.homedir(), runCommand = runOpenCodeMode
3447
3941
  }
3448
3942
  function runOpenCodeModelsCommand(home) {
3449
3943
  const result = spawnSync2("opencode", ["models"], {
3450
- env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
3944
+ env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
3451
3945
  encoding: "utf8",
3452
3946
  timeout: 5e3
3453
3947
  });
@@ -3499,7 +3993,8 @@ var OpenCodeDriver = class {
3499
3993
  model: modelId
3500
3994
  }
3501
3995
  };
3502
- const launch = buildOpenCodeLaunchOptions(launchCtx, opts?.home);
3996
+ const version = readCommandVersion("opencode");
3997
+ const launch = buildOpenCodeLaunchOptions(launchCtx, opts?.home, version);
3503
3998
  return {
3504
3999
  args: launch.args,
3505
4000
  env: launch.env,
@@ -3536,11 +4031,12 @@ var OpenCodeDriver = class {
3536
4031
  spawn(ctx) {
3537
4032
  this.sessionId = ctx.config.sessionId || null;
3538
4033
  this.sessionAnnounced = false;
3539
- const unsupportedMessage = unsupportedOpenCodeVersionMessage(readCommandVersion("opencode"));
4034
+ const version = readCommandVersion("opencode");
4035
+ const unsupportedMessage = unsupportedOpenCodeVersionMessage(version);
3540
4036
  if (unsupportedMessage) {
3541
4037
  throw new Error(unsupportedMessage);
3542
4038
  }
3543
- const launch = buildOpenCodeLaunchOptions(ctx);
4039
+ const launch = buildOpenCodeLaunchOptions(ctx, os5.homedir(), version);
3544
4040
  const proc = spawn7("opencode", launch.args, {
3545
4041
  cwd: ctx.workingDirectory,
3546
4042
  stdio: ["pipe", "pipe", "pipe"],
@@ -3603,6 +4099,297 @@ var OpenCodeDriver = class {
3603
4099
  }
3604
4100
  };
3605
4101
 
4102
+ // src/drivers/pi.ts
4103
+ import { spawn as spawn8 } from "child_process";
4104
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
4105
+ import path11 from "path";
4106
+ import { fileURLToPath } from "url";
4107
+ import { getAgentDir, VERSION as PI_SDK_VERSION } from "@earendil-works/pi-coding-agent";
4108
+ var CHAT_MCP_TOOL_PREFIX2 = "chat_";
4109
+ var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
4110
+ var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
4111
+ var MIN_SUPPORTED_PI_VERSION = "0.74.0";
4112
+ function parseSemver2(version) {
4113
+ const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
4114
+ if (!match) return null;
4115
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
4116
+ }
4117
+ function isSupportedPiVersion(version) {
4118
+ if (!version) return true;
4119
+ const actual = parseSemver2(version);
4120
+ const minimum = parseSemver2(MIN_SUPPORTED_PI_VERSION);
4121
+ if (!actual || !minimum) return true;
4122
+ for (let i = 0; i < 3; i += 1) {
4123
+ if (actual[i] > minimum[i]) return true;
4124
+ if (actual[i] < minimum[i]) return false;
4125
+ }
4126
+ return true;
4127
+ }
4128
+ function unsupportedPiVersionMessage(version) {
4129
+ if (!version || isSupportedPiVersion(version)) return null;
4130
+ 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.`;
4131
+ }
4132
+ function probePi(version = PI_SDK_VERSION) {
4133
+ const unsupportedMessage = unsupportedPiVersionMessage(version);
4134
+ if (unsupportedMessage) {
4135
+ return {
4136
+ available: false,
4137
+ version: `${version} (requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION})`
4138
+ };
4139
+ }
4140
+ return { available: true, version };
4141
+ }
4142
+ function resolvePiSdkRunnerPath(moduleUrl = import.meta.url) {
4143
+ const moduleDir = path11.dirname(fileURLToPath(moduleUrl));
4144
+ const sourceSibling = path11.join(moduleDir, "piSdkRunner.ts");
4145
+ if (existsSync8(sourceSibling)) return sourceSibling;
4146
+ const bundledEntry = path11.join(moduleDir, "drivers", "piSdkRunner.js");
4147
+ if (existsSync8(bundledEntry)) return bundledEntry;
4148
+ return path11.join(moduleDir, "piSdkRunner.js");
4149
+ }
4150
+ function buildPiSdkNodeArgs(runnerPath = resolvePiSdkRunnerPath()) {
4151
+ if (runnerPath.endsWith(".ts")) {
4152
+ return [...process.execArgv, runnerPath];
4153
+ }
4154
+ return [runnerPath];
4155
+ }
4156
+ function buildPiLaunchOptions(ctx, opts = {}) {
4157
+ const command = opts.command ?? process.execPath;
4158
+ const piDir = path11.join(ctx.workingDirectory, ".slock", "pi");
4159
+ const sessionDir = path11.join(piDir, "sessions");
4160
+ mkdirSync4(sessionDir, { recursive: true });
4161
+ const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
4162
+ const runnerPath = opts.runnerPath ?? resolvePiSdkRunnerPath();
4163
+ const agentDir = opts.agentDir ?? getAgentDir();
4164
+ const runnerConfigPath = path11.join(
4165
+ piDir,
4166
+ `sdk-run-${(ctx.launchId || "launch").replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`
4167
+ );
4168
+ const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
4169
+ const runnerConfig = {
4170
+ cwd: ctx.workingDirectory,
4171
+ agentDir,
4172
+ sessionDir,
4173
+ sessionId: ctx.config.sessionId || null,
4174
+ standingPrompt: ctx.standingPrompt,
4175
+ prompt: turnPrompt,
4176
+ model: ctx.config.model && ctx.config.model !== "default" ? ctx.config.model : null
4177
+ };
4178
+ writeFileSync7(runnerConfigPath, `${JSON.stringify(runnerConfig)}
4179
+ `, { encoding: "utf8", mode: 384 });
4180
+ const args = [
4181
+ ...buildPiSdkNodeArgs(runnerPath),
4182
+ "--config",
4183
+ runnerConfigPath
4184
+ ];
4185
+ return {
4186
+ command,
4187
+ args,
4188
+ env: slock.spawnEnv,
4189
+ sessionDir,
4190
+ agentDir,
4191
+ runnerConfigPath,
4192
+ sdkVersion: PI_SDK_VERSION
4193
+ };
4194
+ }
4195
+ function isSystemFirstMessageTask2(message) {
4196
+ return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
4197
+ }
4198
+ function buildPiSystemPrompt(config) {
4199
+ return buildCliTransportSystemPrompt(config, {
4200
+ toolPrefix: CHAT_MCP_TOOL_PREFIX2,
4201
+ extraCriticalRules: [
4202
+ "- 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."
4203
+ ],
4204
+ postStartupNotes: [
4205
+ "**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."
4206
+ ],
4207
+ includeStdinNotificationSection: false,
4208
+ messageNotificationStyle: "poll"
4209
+ });
4210
+ }
4211
+ function contentText(content) {
4212
+ if (!content) return "";
4213
+ const chunks = [];
4214
+ for (const item of content) {
4215
+ if (item.type === "text" && typeof item.text === "string") {
4216
+ chunks.push(item.text);
4217
+ }
4218
+ }
4219
+ return chunks.join("");
4220
+ }
4221
+ function apiKeyErrorMessage(line) {
4222
+ const trimmed = line.trim();
4223
+ if (!trimmed) return null;
4224
+ if (/no api key found/i.test(trimmed)) return trimmed;
4225
+ if (/api key.+required/i.test(trimmed)) return trimmed;
4226
+ if (/no models available/i.test(trimmed)) return trimmed;
4227
+ return null;
4228
+ }
4229
+ var PiDriver = class {
4230
+ id = "pi";
4231
+ lifecycle = {
4232
+ kind: "per_turn",
4233
+ start: "defer_until_concrete_message",
4234
+ exit: "terminate_on_turn_end",
4235
+ inFlightWake: "coalesce_into_pending"
4236
+ };
4237
+ communication = {
4238
+ chat: "slock_cli",
4239
+ runtimeControl: "none"
4240
+ };
4241
+ session = {
4242
+ recovery: "resume_or_fresh"
4243
+ };
4244
+ model = {
4245
+ detectedModelsVerifiedAs: "launchable",
4246
+ toLaunchSpec: (modelId, ctx) => {
4247
+ if (!ctx) return modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] };
4248
+ const launchCtx = {
4249
+ ...ctx,
4250
+ config: {
4251
+ ...ctx.config,
4252
+ model: modelId
4253
+ }
4254
+ };
4255
+ const launch = buildPiLaunchOptions(launchCtx);
4256
+ return {
4257
+ args: launch.args,
4258
+ env: launch.env,
4259
+ configFiles: [launch.runnerConfigPath],
4260
+ params: {
4261
+ agentDir: launch.agentDir,
4262
+ sessionDir: launch.sessionDir,
4263
+ sdkVersion: launch.sdkVersion,
4264
+ resources: "extensions/skills/prompt-templates/themes/context-files disabled by Slock policy"
4265
+ }
4266
+ };
4267
+ }
4268
+ };
4269
+ supportsStdinNotification = false;
4270
+ mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
4271
+ busyDeliveryMode = "none";
4272
+ terminateProcessOnTurnEnd = true;
4273
+ deferSpawnUntilMessage = true;
4274
+ usesSlockCliForCommunication = true;
4275
+ sessionId = null;
4276
+ sessionAnnounced = false;
4277
+ apiKeyErrorAnnounced = false;
4278
+ turnEnded = false;
4279
+ assistantTextByMessageId = /* @__PURE__ */ new Map();
4280
+ shouldDeferWakeMessage(message) {
4281
+ return isSystemFirstMessageTask2(message);
4282
+ }
4283
+ probe() {
4284
+ return probePi();
4285
+ }
4286
+ async detectModels() {
4287
+ return null;
4288
+ }
4289
+ spawn(ctx) {
4290
+ this.sessionId = ctx.config.sessionId || null;
4291
+ this.sessionAnnounced = false;
4292
+ this.apiKeyErrorAnnounced = false;
4293
+ this.turnEnded = false;
4294
+ this.assistantTextByMessageId.clear();
4295
+ const unsupportedMessage = unsupportedPiVersionMessage(PI_SDK_VERSION);
4296
+ if (unsupportedMessage) throw new Error(unsupportedMessage);
4297
+ const launch = buildPiLaunchOptions(ctx);
4298
+ const proc = spawn8(launch.command, launch.args, {
4299
+ cwd: ctx.workingDirectory,
4300
+ stdio: ["pipe", "pipe", "pipe"],
4301
+ env: launch.env,
4302
+ shell: false
4303
+ });
4304
+ proc.stdin?.end();
4305
+ return { process: proc };
4306
+ }
4307
+ parseLine(line) {
4308
+ let event;
4309
+ try {
4310
+ event = JSON.parse(line);
4311
+ } catch {
4312
+ if (this.apiKeyErrorAnnounced) return [];
4313
+ const message = apiKeyErrorMessage(line);
4314
+ if (!message) return [];
4315
+ this.apiKeyErrorAnnounced = true;
4316
+ this.turnEnded = true;
4317
+ return [
4318
+ { kind: "error", message },
4319
+ { kind: "turn_end", sessionId: this.sessionId || void 0 }
4320
+ ];
4321
+ }
4322
+ const events = [];
4323
+ if (event.type === "session" && event.id) {
4324
+ this.sessionId = event.id;
4325
+ }
4326
+ if (!this.sessionAnnounced && this.sessionId) {
4327
+ events.push({ kind: "session_init", sessionId: this.sessionId });
4328
+ this.sessionAnnounced = true;
4329
+ }
4330
+ switch (event.type) {
4331
+ case "agent_start":
4332
+ case "turn_start":
4333
+ events.push({ kind: "thinking", text: "" });
4334
+ break;
4335
+ case "message_update":
4336
+ case "message_end":
4337
+ if (event.message?.role === "assistant") {
4338
+ const key = event.message.id || "current";
4339
+ const currentText = contentText(event.message.content);
4340
+ const previousText = this.assistantTextByMessageId.get(key) ?? "";
4341
+ if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
4342
+ events.push({ kind: "text", text: currentText.slice(previousText.length) });
4343
+ } else if (currentText && currentText !== previousText) {
4344
+ events.push({ kind: "text", text: currentText });
4345
+ }
4346
+ this.assistantTextByMessageId.set(key, currentText);
4347
+ if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
4348
+ events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
4349
+ }
4350
+ }
4351
+ break;
4352
+ case "tool_execution_start":
4353
+ events.push({
4354
+ kind: "tool_call",
4355
+ name: event.toolName || "unknown_tool",
4356
+ input: event.args
4357
+ });
4358
+ break;
4359
+ case "tool_execution_end":
4360
+ events.push({
4361
+ kind: "tool_output",
4362
+ name: event.toolName || "unknown_tool"
4363
+ });
4364
+ if (event.isError) {
4365
+ events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
4366
+ }
4367
+ break;
4368
+ case "compaction_start":
4369
+ events.push({ kind: "compaction_started" });
4370
+ break;
4371
+ case "compaction_end":
4372
+ events.push({ kind: "compaction_finished" });
4373
+ if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
4374
+ break;
4375
+ case "turn_end":
4376
+ case "agent_end":
4377
+ if (!this.turnEnded) {
4378
+ events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
4379
+ this.turnEnded = true;
4380
+ }
4381
+ break;
4382
+ }
4383
+ return events;
4384
+ }
4385
+ encodeStdinMessage(_text, _sessionId, _opts) {
4386
+ return null;
4387
+ }
4388
+ buildSystemPrompt(config, _agentId) {
4389
+ return buildPiSystemPrompt(config);
4390
+ }
4391
+ };
4392
+
3606
4393
  // src/drivers/index.ts
3607
4394
  var driverFactories = {
3608
4395
  claude: () => new ClaudeDriver(),
@@ -3611,7 +4398,8 @@ var driverFactories = {
3611
4398
  cursor: () => new CursorDriver(),
3612
4399
  gemini: () => new GeminiDriver(),
3613
4400
  kimi: () => new KimiDriver(),
3614
- opencode: () => new OpenCodeDriver()
4401
+ opencode: () => new OpenCodeDriver(),
4402
+ pi: () => new PiDriver()
3615
4403
  };
3616
4404
  function getDriver(runtimeId) {
3617
4405
  const createDriver = driverFactories[runtimeId];
@@ -3624,7 +4412,7 @@ function getDriver(runtimeId) {
3624
4412
 
3625
4413
  // src/workspaces.ts
3626
4414
  import { readdir, rm, stat } from "fs/promises";
3627
- import path11 from "path";
4415
+ import path12 from "path";
3628
4416
  function isValidWorkspaceDirectoryName(directoryName) {
3629
4417
  return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
3630
4418
  }
@@ -3632,7 +4420,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
3632
4420
  if (!isValidWorkspaceDirectoryName(directoryName)) {
3633
4421
  return null;
3634
4422
  }
3635
- return path11.join(dataDir, directoryName);
4423
+ return path12.join(dataDir, directoryName);
3636
4424
  }
3637
4425
  function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
3638
4426
  return {
@@ -3681,7 +4469,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
3681
4469
  return summary;
3682
4470
  }
3683
4471
  const childSummaries = await Promise.all(
3684
- entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
4472
+ entries.map((entry) => summarizeWorkspaceEntry(path12.join(dirPath, entry.name), entry))
3685
4473
  );
3686
4474
  for (const childSummary of childSummaries) {
3687
4475
  summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
@@ -3700,7 +4488,7 @@ async function scanWorkspaceDirectories(dataDir) {
3700
4488
  if (!entry.isDirectory()) {
3701
4489
  return null;
3702
4490
  }
3703
- const dirPath = path11.join(dataDir, entry.name);
4491
+ const dirPath = path12.join(dataDir, entry.name);
3704
4492
  try {
3705
4493
  const summary = await summarizeWorkspaceDirectory(dirPath);
3706
4494
  return {
@@ -3813,6 +4601,8 @@ function redactUrlQuery(value) {
3813
4601
  // src/agentProcessManager.ts
3814
4602
  var DEFAULT_MAX_CONCURRENT_AGENT_STARTS = 5;
3815
4603
  var DEFAULT_AGENT_START_INTERVAL_MS = 500;
4604
+ var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS = 3;
4605
+ var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS = 250;
3816
4606
  var WORKSPACE_TEXT_FILE_MAX_BYTES = 1048576;
3817
4607
  var WORKSPACE_IMAGE_PREVIEW_MAX_BYTES = 5 * 1024 * 1024;
3818
4608
  var WORKSPACE_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -3857,6 +4647,41 @@ function readNonNegativeIntegerEnv(name, fallback) {
3857
4647
  if (!Number.isFinite(parsed) || parsed < 0) return fallback;
3858
4648
  return Math.floor(parsed);
3859
4649
  }
4650
+ var RunnerCredentialMintError = class extends Error {
4651
+ code;
4652
+ retryable;
4653
+ status;
4654
+ constructor(message, opts) {
4655
+ super(message);
4656
+ this.name = "RunnerCredentialMintError";
4657
+ this.code = opts.code;
4658
+ this.retryable = opts.retryable ?? false;
4659
+ this.status = opts.status;
4660
+ }
4661
+ };
4662
+ function isRetryableMintHttpFailure(status, code) {
4663
+ if (code === "experimental_surface_disabled") return false;
4664
+ return status === 408 || status === 425 || status === 429 || status >= 500;
4665
+ }
4666
+ function runnerCredentialErrorDetail(error) {
4667
+ if (error instanceof RunnerCredentialMintError) {
4668
+ return {
4669
+ message: error.message,
4670
+ code: error.code,
4671
+ retryable: error.retryable,
4672
+ status: error.status
4673
+ };
4674
+ }
4675
+ const message = error instanceof Error ? error.message : String(error);
4676
+ return {
4677
+ message,
4678
+ code: "runner_credential_mint_network_error",
4679
+ retryable: true
4680
+ };
4681
+ }
4682
+ async function waitForRunnerCredentialRetry() {
4683
+ await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS));
4684
+ }
3860
4685
  function stalledRecoverySigtermTimeoutMs() {
3861
4686
  return readNonNegativeIntegerEnv(
3862
4687
  "SLOCK_DAEMON_STALLED_RECOVERY_SIGTERM_TIMEOUT_MS",
@@ -3885,6 +4710,22 @@ function formatMessageTarget(message) {
3885
4710
  }
3886
4711
  return `#${message.channel_name}`;
3887
4712
  }
4713
+ function formatVisibleMessageTarget(message) {
4714
+ if (message.channel_type === "thread" && message.parent_channel_name && message.channel_name) {
4715
+ const shortId = getMessageShortId(String(message.channel_name));
4716
+ if (message.parent_channel_type === "dm") {
4717
+ return `dm:@${message.parent_channel_name}:${shortId}`;
4718
+ }
4719
+ return `#${message.parent_channel_name}:${shortId}`;
4720
+ }
4721
+ if (message.channel_type === "dm" && message.channel_name) {
4722
+ return `dm:@${message.channel_name}`;
4723
+ }
4724
+ if (message.channel_name) {
4725
+ return `#${message.channel_name}`;
4726
+ }
4727
+ return null;
4728
+ }
3888
4729
  function getMessageShortId(messageId) {
3889
4730
  return messageId.startsWith("thread-") ? messageId.slice(7) : messageId.slice(0, 8);
3890
4731
  }
@@ -3904,12 +4745,12 @@ function findSessionJsonl(root, predicate) {
3904
4745
  for (const entry of entries) {
3905
4746
  if (++visited > maxEntries) return null;
3906
4747
  if (!entry.isFile() || !predicate(entry.name)) continue;
3907
- return path12.join(dir, entry.name);
4748
+ return path13.join(dir, entry.name);
3908
4749
  }
3909
4750
  for (const entry of entries) {
3910
4751
  if (++visited > maxEntries) return null;
3911
4752
  if (!entry.isDirectory()) continue;
3912
- const found = visit(path12.join(dir, entry.name), depth - 1);
4753
+ const found = visit(path13.join(dir, entry.name), depth - 1);
3913
4754
  if (found) return found;
3914
4755
  }
3915
4756
  return null;
@@ -3922,10 +4763,10 @@ function safeSessionFilename(value) {
3922
4763
  }
3923
4764
  function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
3924
4765
  try {
3925
- const dir = path12.join(fallbackDir, ".slock", "runtime-sessions");
3926
- mkdirSync4(dir, { recursive: true });
3927
- const filePath = path12.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
3928
- writeFileSync7(filePath, JSON.stringify({
4766
+ const dir = path13.join(fallbackDir, ".slock", "runtime-sessions");
4767
+ mkdirSync5(dir, { recursive: true });
4768
+ const filePath = path13.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
4769
+ writeFileSync8(filePath, JSON.stringify({
3929
4770
  type: "runtime_session_handoff",
3930
4771
  runtime,
3931
4772
  sessionId,
@@ -3944,7 +4785,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
3944
4785
  }
3945
4786
  }
3946
4787
  function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir) {
3947
- const directPath = path12.isAbsolute(sessionId) ? sessionId : null;
4788
+ const directPath = path13.isAbsolute(sessionId) ? sessionId : null;
3948
4789
  if (directPath) {
3949
4790
  try {
3950
4791
  if (statSync2(directPath).isFile()) {
@@ -3953,7 +4794,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
3953
4794
  } catch {
3954
4795
  }
3955
4796
  }
3956
- 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;
4797
+ 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;
3957
4798
  if (!resolvedPath && fallbackDir) {
3958
4799
  const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
3959
4800
  if (fallback) return fallback;
@@ -4505,6 +5346,14 @@ function createRuntimeTraceCounters() {
4505
5346
  thinkingEvents: 0
4506
5347
  };
4507
5348
  }
5349
+ function cleanupAgentCredentialProxy(agentId, launchId) {
5350
+ unregisterAgentCredentialProxyForLaunch({ agentId, launchId });
5351
+ }
5352
+ function stripManagedRunnerCredential(config) {
5353
+ if (!config.agentCredentialKey && !config.agentCredentialId) return config;
5354
+ const { agentCredentialKey: _agentCredentialKey, agentCredentialId: _agentCredentialId, ...rest } = config;
5355
+ return rest;
5356
+ }
4508
5357
  function createGatedSteeringState() {
4509
5358
  return {
4510
5359
  phase: "idle",
@@ -4836,6 +5685,7 @@ var AgentProcessManager = class _AgentProcessManager {
4836
5685
  tracer;
4837
5686
  deliveryTraceContexts = /* @__PURE__ */ new WeakMap();
4838
5687
  processExitTraceAttrs = /* @__PURE__ */ new WeakMap();
5688
+ agentVisibleBoundaries = /* @__PURE__ */ new Map();
4839
5689
  constructor(chatBridgePath, sendToServer, daemonApiKey, opts) {
4840
5690
  this.chatBridgePath = chatBridgePath;
4841
5691
  this.slockCliPath = opts.slockCliPath ?? "";
@@ -4863,6 +5713,97 @@ var AgentProcessManager = class _AgentProcessManager {
4863
5713
  setTracer(tracer) {
4864
5714
  this.tracer = tracer;
4865
5715
  }
5716
+ visibleBoundaryMap(agentId) {
5717
+ let map = this.agentVisibleBoundaries.get(agentId);
5718
+ if (!map) {
5719
+ map = /* @__PURE__ */ new Map();
5720
+ this.agentVisibleBoundaries.set(agentId, map);
5721
+ }
5722
+ return map;
5723
+ }
5724
+ getVisibleBoundary(agentId, target) {
5725
+ return this.agentVisibleBoundaries.get(agentId)?.get(target);
5726
+ }
5727
+ pendingVisibleMessages(agentId, target) {
5728
+ const collect = (messages) => (messages ?? []).filter((message) => formatMessageTarget(message) === target && typeof message.seq === "number" && message.seq > 0);
5729
+ return [
5730
+ ...collect(this.agents.get(agentId)?.inbox),
5731
+ ...collect(this.startingInboxes.get(agentId))
5732
+ ];
5733
+ }
5734
+ /**
5735
+ * Single-inbox consume point for messages that have been rendered into the
5736
+ * agent-visible input surface. Daemon pending inbox is only a runner cache:
5737
+ * once a message is visible through wake/stdin/check/read/held/preflight, the
5738
+ * same seq must not be injected later through the local pending queue.
5739
+ */
5740
+ consumeVisibleMessages(agentId, input) {
5741
+ if (input.messages.length === 0 && (!input.target || typeof input.boundarySeq !== "number")) return;
5742
+ const byTarget = /* @__PURE__ */ new Map();
5743
+ const ensureBucket = (target) => {
5744
+ let bucket = byTarget.get(target);
5745
+ if (!bucket) {
5746
+ bucket = { maxSeq: 0, boundarySeq: 0, seqs: /* @__PURE__ */ new Set(), ids: /* @__PURE__ */ new Set() };
5747
+ byTarget.set(target, bucket);
5748
+ }
5749
+ return bucket;
5750
+ };
5751
+ for (const message of input.messages) {
5752
+ const seq = Number(message.seq ?? 0);
5753
+ if (!Number.isFinite(seq) || seq <= 0) continue;
5754
+ const target = input.target ?? formatVisibleMessageTarget(message);
5755
+ if (!target) continue;
5756
+ const bucket = ensureBucket(target);
5757
+ bucket.maxSeq = Math.max(bucket.maxSeq, Math.floor(seq));
5758
+ bucket.seqs.add(Math.floor(seq));
5759
+ const id = typeof message.id === "string" ? message.id : typeof message.message_id === "string" ? message.message_id : null;
5760
+ if (id) bucket.ids.add(id);
5761
+ }
5762
+ if (input.target && typeof input.boundarySeq === "number" && Number.isFinite(input.boundarySeq) && input.boundarySeq > 0) {
5763
+ const bucket = ensureBucket(input.target);
5764
+ bucket.boundarySeq = Math.max(bucket.boundarySeq, Math.floor(input.boundarySeq));
5765
+ }
5766
+ if (byTarget.size === 0) return;
5767
+ const boundaryMap = this.visibleBoundaryMap(agentId);
5768
+ for (const [target, bucket] of byTarget) {
5769
+ const highWaterSeq = Math.max(bucket.maxSeq, bucket.boundarySeq);
5770
+ const previous = boundaryMap.get(target) ?? 0;
5771
+ boundaryMap.set(target, Math.max(previous, highWaterSeq));
5772
+ }
5773
+ const suppress = (messages) => {
5774
+ if (!messages || messages.length === 0) return 0;
5775
+ let removed = 0;
5776
+ const retained = messages.filter((message) => {
5777
+ const target = formatMessageTarget(message);
5778
+ const bucket = byTarget.get(target);
5779
+ if (!bucket) return true;
5780
+ const seq = typeof message.seq === "number" ? Math.floor(message.seq) : 0;
5781
+ const id = message.message_id ?? "";
5782
+ const matched = seq > 0 && bucket.boundarySeq > 0 && seq <= bucket.boundarySeq || seq > 0 && bucket.seqs.has(seq) || id.length > 0 && bucket.ids.has(id);
5783
+ if (matched) removed += 1;
5784
+ return !matched;
5785
+ });
5786
+ messages.splice(0, messages.length, ...retained);
5787
+ return removed;
5788
+ };
5789
+ const active = this.agents.get(agentId);
5790
+ const removedActive = suppress(active?.inbox);
5791
+ const removedStarting = suppress(this.startingInboxes.get(agentId));
5792
+ this.recordDaemonTrace("daemon.agent.inbox.visible_consumed", {
5793
+ agentId,
5794
+ source: input.source,
5795
+ targets: [...byTarget.keys()],
5796
+ messages_count: input.messages.length,
5797
+ suppressed_pending_count: removedActive + removedStarting
5798
+ });
5799
+ }
5800
+ createAgentProxyInboxCoordinator(agentId) {
5801
+ return {
5802
+ getBoundary: (target) => this.getVisibleBoundary(agentId, target),
5803
+ getPendingMessages: (target) => this.pendingVisibleMessages(agentId, target),
5804
+ consumeVisibleMessages: (input) => this.consumeVisibleMessages(agentId, input)
5805
+ };
5806
+ }
4866
5807
  recordDaemonTrace(name, attrs, status = "ok", parentTraceparent) {
4867
5808
  const span = this.tracer.startSpan(name, {
4868
5809
  parent: parseTraceparent(parentTraceparent),
@@ -5087,26 +6028,26 @@ var AgentProcessManager = class _AgentProcessManager {
5087
6028
  this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
5088
6029
  try {
5089
6030
  const driver = this.driverResolver(config.runtime || "claude");
5090
- const agentDataDir = path12.join(this.dataDir, agentId);
6031
+ const agentDataDir = path13.join(this.dataDir, agentId);
5091
6032
  await mkdir(agentDataDir, { recursive: true });
5092
6033
  const runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
5093
- const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
6034
+ const memoryMdPath = path13.join(agentDataDir, "MEMORY.md");
5094
6035
  try {
5095
6036
  await access(memoryMdPath);
5096
6037
  } catch {
5097
6038
  const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
5098
6039
  await writeFile(memoryMdPath, initialMemoryMd);
5099
6040
  }
5100
- const notesDir = path12.join(agentDataDir, "notes");
6041
+ const notesDir = path13.join(agentDataDir, "notes");
5101
6042
  await mkdir(notesDir, { recursive: true });
5102
6043
  if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
5103
6044
  const seedFiles = buildOnboardingSeedFiles();
5104
6045
  for (const { relativePath, content } of seedFiles) {
5105
- const fullPath = path12.join(agentDataDir, relativePath);
6046
+ const fullPath = path13.join(agentDataDir, relativePath);
5106
6047
  try {
5107
6048
  await access(fullPath);
5108
6049
  } catch {
5109
- await mkdir(path12.dirname(fullPath), { recursive: true });
6050
+ await mkdir(path13.dirname(fullPath), { recursive: true });
5110
6051
  await writeFile(fullPath, content);
5111
6052
  }
5112
6053
  }
@@ -5214,7 +6155,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5214
6155
  chatBridgePath: this.chatBridgePath,
5215
6156
  slockCliPath: this.slockCliPath,
5216
6157
  daemonApiKey: this.daemonApiKey,
5217
- launchId: launchId || null
6158
+ launchId: launchId || null,
6159
+ agentCredentialProxyInboxCoordinator: this.createAgentProxyInboxCoordinator(agentId)
5218
6160
  });
5219
6161
  this.recordDaemonTrace("daemon.agent.spawn.created", {
5220
6162
  ...this.startQueueTraceAttrs(agentId, effectiveConfig, wakeMessage, unreadSummary, resumePrompt, launchId),
@@ -5270,6 +6212,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5270
6212
  this.ackInjectedRuntimeProfileControl(agentId, config.runtimeProfileControl, agentProcess.launchId);
5271
6213
  }
5272
6214
  if (wakeMessage) {
6215
+ this.consumeVisibleMessages(agentId, { messages: [wakeMessage], source: "spawn_wake_message" });
5273
6216
  this.ackInjectedRuntimeProfileMessages(agentId, [wakeMessage], agentProcess.launchId);
5274
6217
  }
5275
6218
  let buffer = "";
@@ -5362,16 +6305,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5362
6305
  ...runtimeTraceCounterAttrs(ap),
5363
6306
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "process_exit")
5364
6307
  });
5365
- if (processEndedCleanly) {
5366
- this.finishCompactionIfActive(agentId, "Context compaction finished (inferred from process exit)");
5367
- } else {
5368
- this.clearCompactionWatchdog(ap);
5369
- }
6308
+ this.interruptCompactionIfActive(agentId);
6309
+ cleanupAgentCredentialProxy(agentId, ap.launchId);
6310
+ this.revokeManagedRunnerCredential(agentId, ap.config, ap.launchId);
5370
6311
  this.agents.delete(agentId);
5371
6312
  if (missingResumeSession) {
5372
6313
  const staleSessionId = ap.sessionId;
5373
6314
  const runtimeLabel = ap.driver.id === "opencode" ? "OpenCode" : "Claude";
5374
- const restartConfig = { ...ap.config, sessionId: null };
6315
+ const restartConfig = { ...stripManagedRunnerCredential(ap.config), sessionId: null };
5375
6316
  logger.warn(
5376
6317
  `[Agent ${agentId}] Stored ${runtimeLabel} session ${staleSessionId} is unavailable locally; falling back to cold start`
5377
6318
  );
@@ -5408,7 +6349,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5408
6349
  const unreadSummary2 = queuedWakeMessage ? buildUnreadSummary(ap.inbox, formatChannelLabel(queuedWakeMessage)) : void 0;
5409
6350
  if (queuedWakeMessage) {
5410
6351
  logger.info(`[Agent ${agentId}] Turn completed; restarting immediately for queued message`);
5411
- const nextConfig = { ...ap.config, sessionId: ap.sessionId };
6352
+ const nextConfig = { ...stripManagedRunnerCredential(ap.config), sessionId: ap.sessionId };
5412
6353
  this.idleAgentConfigs.set(agentId, {
5413
6354
  config: nextConfig,
5414
6355
  sessionId: ap.sessionId,
@@ -5418,6 +6359,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5418
6359
  this.idleAgentConfigs.delete(agentId);
5419
6360
  this.startAgent(agentId, nextConfig, queuedWakeMessage, unreadSummary2, void 0, ap.launchId || void 0).catch((err) => {
5420
6361
  logger.error(`[Agent ${agentId}] Failed to continue with queued message`, err);
6362
+ if (this.reportRunnerCredentialMintFailure(agentId, err, ap.launchId, "queued_continuation")) {
6363
+ return;
6364
+ }
5421
6365
  this.idleAgentConfigs.set(agentId, {
5422
6366
  config: nextConfig,
5423
6367
  sessionId: ap.sessionId,
@@ -5428,7 +6372,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5428
6372
  return;
5429
6373
  }
5430
6374
  this.idleAgentConfigs.set(agentId, {
5431
- config: { ...ap.config, sessionId: ap.sessionId },
6375
+ config: { ...stripManagedRunnerCredential(ap.config), sessionId: ap.sessionId },
5432
6376
  sessionId: ap.sessionId,
5433
6377
  launchId: ap.launchId
5434
6378
  });
@@ -5464,27 +6408,28 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5464
6408
  }
5465
6409
  async buildSpawnConfig(agentId, config) {
5466
6410
  const baseConfig = config.serverUrl === this.serverUrl ? config : { ...config, serverUrl: this.serverUrl };
6411
+ const runnerConfig = await this.ensureManagedRunnerCredential(agentId, baseConfig);
5467
6412
  if (!this.defaultAgentEnvVarsProvider) {
5468
- return baseConfig;
6413
+ return runnerConfig;
5469
6414
  }
5470
6415
  try {
5471
6416
  const defaultEnvVars = await this.defaultAgentEnvVarsProvider({
5472
- runtime: baseConfig.runtime,
5473
- model: baseConfig.model,
5474
- envVars: baseConfig.envVars
6417
+ runtime: runnerConfig.runtime,
6418
+ model: runnerConfig.model,
6419
+ envVars: runnerConfig.envVars
5475
6420
  });
5476
6421
  if (!defaultEnvVars || Object.keys(defaultEnvVars).length === 0) {
5477
- return baseConfig;
6422
+ return runnerConfig;
5478
6423
  }
5479
6424
  const mergedEnvVars = {
5480
6425
  ...defaultEnvVars,
5481
- ...baseConfig.envVars ?? {}
6426
+ ...runnerConfig.envVars ?? {}
5482
6427
  };
5483
- if (this.sameEnvVars(mergedEnvVars, baseConfig.envVars)) {
5484
- return baseConfig;
6428
+ if (this.sameEnvVars(mergedEnvVars, runnerConfig.envVars)) {
6429
+ return runnerConfig;
5485
6430
  }
5486
6431
  return {
5487
- ...baseConfig,
6432
+ ...runnerConfig,
5488
6433
  envVars: mergedEnvVars
5489
6434
  };
5490
6435
  } catch (error) {
@@ -5492,8 +6437,130 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5492
6437
  logger.warn(
5493
6438
  `[Agent ${agentId}] Failed to resolve default runtime env vars \u2014 continuing without machine-level defaults (${reason})`
5494
6439
  );
5495
- return baseConfig;
6440
+ return runnerConfig;
6441
+ }
6442
+ }
6443
+ async requestManagedRunnerCredentialOnce(agentId, config) {
6444
+ const url = new URL(`/internal/computer/runners/${encodeURIComponent(agentId)}/credentials`, this.serverUrl);
6445
+ const res = await fetch(url, {
6446
+ method: "POST",
6447
+ headers: {
6448
+ Authorization: `Bearer ${this.daemonApiKey}`,
6449
+ "Content-Type": "application/json",
6450
+ "X-Slock-Client": "daemon-server-session-worker"
6451
+ },
6452
+ body: JSON.stringify({
6453
+ scopes: ["send", "read", "mentions", "tasks", "reactions", "server", "channels"],
6454
+ name: `runner:${config.runtime}:${agentId.slice(0, 8)}`
6455
+ })
6456
+ });
6457
+ if (!res.ok) {
6458
+ const contentType = res.headers.get("content-type") ?? "";
6459
+ let detail = `HTTP ${res.status}`;
6460
+ let code = null;
6461
+ if (contentType.includes("application/json")) {
6462
+ const body2 = await res.json().catch(() => null);
6463
+ const error = typeof body2?.error === "string" ? body2.error : null;
6464
+ code = typeof body2?.code === "string" ? body2.code : null;
6465
+ detail = [detail, code, error].filter(Boolean).join(" ");
6466
+ }
6467
+ throw new RunnerCredentialMintError(detail, {
6468
+ code: code ?? "runner_credential_mint_http_error",
6469
+ retryable: isRetryableMintHttpFailure(res.status, code),
6470
+ status: res.status
6471
+ });
6472
+ }
6473
+ const body = await res.json().catch(() => null);
6474
+ if (typeof body?.apiKey !== "string" || !body.apiKey.startsWith("sk_agent_")) {
6475
+ throw new RunnerCredentialMintError("invalid_agent_credential_payload", {
6476
+ code: "invalid_agent_credential_payload"
6477
+ });
6478
+ }
6479
+ return {
6480
+ apiKey: body.apiKey,
6481
+ credentialId: typeof body.credentialId === "string" ? body.credentialId : null
6482
+ };
6483
+ }
6484
+ async ensureManagedRunnerCredential(agentId, config) {
6485
+ if (config.agentCredentialKey) return config;
6486
+ if (process.env.SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED === "1") {
6487
+ throw new RunnerCredentialMintError("runner credential mint is disabled by SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED", {
6488
+ code: "runner_credentials_disabled"
6489
+ });
5496
6490
  }
6491
+ let lastError = null;
6492
+ for (let attempt = 1; attempt <= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS; attempt += 1) {
6493
+ try {
6494
+ const credential = await this.requestManagedRunnerCredentialOnce(agentId, config);
6495
+ return {
6496
+ ...config,
6497
+ agentCredentialKey: credential.apiKey,
6498
+ agentCredentialId: credential.credentialId
6499
+ };
6500
+ } catch (err) {
6501
+ lastError = err;
6502
+ const detail2 = runnerCredentialErrorDetail(err);
6503
+ this.recordDaemonTrace("daemon.runner_credential_mint.retry", {
6504
+ agentId,
6505
+ runtime: config.runtime,
6506
+ attempt,
6507
+ max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS,
6508
+ status: detail2.status,
6509
+ code: detail2.code,
6510
+ reason: detail2.message,
6511
+ retryable: detail2.retryable
6512
+ }, detail2.retryable && attempt < RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS ? "ok" : "error");
6513
+ if (!detail2.retryable || attempt >= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS) break;
6514
+ await waitForRunnerCredentialRetry();
6515
+ }
6516
+ }
6517
+ const detail = runnerCredentialErrorDetail(lastError);
6518
+ this.recordDaemonTrace("daemon.runner_credential_mint.failed", {
6519
+ agentId,
6520
+ runtime: config.runtime,
6521
+ status: detail.status,
6522
+ code: detail.code,
6523
+ reason: detail.message,
6524
+ retryable: detail.retryable,
6525
+ max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS
6526
+ }, "error");
6527
+ throw new RunnerCredentialMintError(
6528
+ `runner_credential_mint_failed: ${detail.message}. Managed runner startup requires /internal/computer credential mint; deploy server first or roll back the daemon binary.`,
6529
+ {
6530
+ code: detail.code,
6531
+ retryable: detail.retryable,
6532
+ status: detail.status
6533
+ }
6534
+ );
6535
+ }
6536
+ revokeManagedRunnerCredential(agentId, config, launchId) {
6537
+ const credentialId = config.agentCredentialId;
6538
+ if (!credentialId) return;
6539
+ const url = new URL(
6540
+ `/internal/computer/runners/${encodeURIComponent(agentId)}/credentials/${encodeURIComponent(credentialId)}`,
6541
+ this.serverUrl
6542
+ );
6543
+ void fetch(url, {
6544
+ method: "DELETE",
6545
+ headers: {
6546
+ Authorization: `Bearer ${this.daemonApiKey}`,
6547
+ "X-Slock-Client": "daemon-server-session-worker"
6548
+ }
6549
+ }).then((res) => {
6550
+ this.recordDaemonTrace("daemon.runner_credential.revoke", {
6551
+ agentId,
6552
+ launchId: launchId || void 0,
6553
+ credentialId,
6554
+ status: res.status
6555
+ }, res.ok ? "ok" : "error");
6556
+ }).catch((err) => {
6557
+ this.recordDaemonTrace("daemon.runner_credential.revoke", {
6558
+ agentId,
6559
+ launchId: launchId || void 0,
6560
+ credentialId,
6561
+ reason: err instanceof Error ? err.message : String(err)
6562
+ }, "error");
6563
+ });
5497
6564
  }
5498
6565
  sameEnvVars(left, right) {
5499
6566
  const leftKeys = Object.keys(left ?? {});
@@ -5583,6 +6650,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5583
6650
  }
5584
6651
  this.clearCompactionWatchdog(ap);
5585
6652
  this.clearStalledRecoverySigtermWatchdog(ap);
6653
+ cleanupAgentCredentialProxy(agentId, ap.launchId);
6654
+ this.revokeManagedRunnerCredential(agentId, ap.config, ap.launchId);
5586
6655
  this.agents.delete(agentId);
5587
6656
  this.processExitTraceAttrs.set(ap.process, {
5588
6657
  stop_source: silent ? "daemon_internal" : "explicit_request",
@@ -5666,10 +6735,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5666
6735
  session_id_present: Boolean(cached.sessionId),
5667
6736
  launchId: cached.launchId || void 0
5668
6737
  }));
5669
- this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).catch((err) => {
6738
+ return this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).then(() => true, (err) => {
5670
6739
  logger.error(`[Agent ${agentId}] Failed to auto-restart`, err);
6740
+ if (this.reportRunnerCredentialMintFailure(agentId, err, cached.launchId, "idle_auto_restart")) {
6741
+ return false;
6742
+ }
6743
+ this.idleAgentConfigs.set(agentId, cached);
6744
+ return false;
5671
6745
  });
5672
- return true;
5673
6746
  }
5674
6747
  logger.warn(`[Agent ${agentId}] Delivery received but no running process or cached idle config exists`);
5675
6748
  this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
@@ -5751,6 +6824,37 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5751
6824
  }));
5752
6825
  return true;
5753
6826
  }
6827
+ if (ap.gatedSteering.compacting) {
6828
+ ap.pendingNotificationCount++;
6829
+ if (ap.notificationTimer) {
6830
+ clearTimeout(ap.notificationTimer);
6831
+ ap.notificationTimer = null;
6832
+ }
6833
+ if (ap.driver.busyDeliveryMode === "gated") {
6834
+ this.recordGatedSteeringEvent(agentId, ap, "buffer", {
6835
+ reason: "compaction_boundary",
6836
+ pendingMessages: ap.inbox.length
6837
+ });
6838
+ }
6839
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime.compaction_boundary.delivery_buffered", {
6840
+ pendingNotificationCount: ap.pendingNotificationCount,
6841
+ pendingMessages: ap.inbox.length,
6842
+ busyDeliveryMode: ap.driver.busyDeliveryMode
6843
+ });
6844
+ this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
6845
+ outcome: "queued_compaction_boundary",
6846
+ accepted: true,
6847
+ process_present: true,
6848
+ runtime: ap.config.runtime,
6849
+ session_id_present: true,
6850
+ launchId: ap.launchId || void 0,
6851
+ inbox_count: ap.inbox.length,
6852
+ pending_notification_count: ap.pendingNotificationCount,
6853
+ busy_delivery_mode: ap.driver.busyDeliveryMode,
6854
+ notification_timer_present: false
6855
+ }));
6856
+ return true;
6857
+ }
5754
6858
  if (ap.driver.busyDeliveryMode === "gated") {
5755
6859
  ap.pendingNotificationCount++;
5756
6860
  this.recordGatedSteeringEvent(agentId, ap, "buffer", {
@@ -5788,7 +6892,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5788
6892
  return true;
5789
6893
  }
5790
6894
  async resetWorkspace(agentId) {
5791
- const agentDataDir = path12.join(this.dataDir, agentId);
6895
+ const agentDataDir = path13.join(this.dataDir, agentId);
5792
6896
  try {
5793
6897
  await rm2(agentDataDir, { recursive: true, force: true });
5794
6898
  logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
@@ -5825,7 +6929,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5825
6929
  return result;
5826
6930
  }
5827
6931
  buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
5828
- const workspacePath = path12.join(this.dataDir, agentId);
6932
+ const workspacePath = path13.join(this.dataDir, agentId);
5829
6933
  return {
5830
6934
  agentId,
5831
6935
  launchId,
@@ -5957,19 +7061,28 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5957
7061
  if (cached) {
5958
7062
  logger.info(`[Agent ${agentId}] Starting from idle state for runtime profile ${kind} ${key}`);
5959
7063
  this.idleAgentConfigs.delete(agentId);
5960
- this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).catch((err) => {
7064
+ return this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).then(() => true, (err) => {
5961
7065
  logger.error(`[Agent ${agentId}] Failed to auto-restart for runtime profile notification`, err);
5962
- this.idleAgentConfigs.set(agentId, cached);
5963
- });
5964
- span.end("ok", {
5965
- attrs: {
5966
- outcome: "restart_queued",
5967
- runtime: cached.config.runtime,
5968
- launchId: cached.launchId || void 0,
5969
- session_id_present: Boolean(cached.sessionId)
7066
+ if (this.reportRunnerCredentialMintFailure(agentId, err, cached.launchId, "runtime_profile_auto_restart")) {
7067
+ span.end("error", {
7068
+ attrs: {
7069
+ outcome: "runner_credential_mint_failed",
7070
+ runtime: cached.config.runtime,
7071
+ launchId: cached.launchId || void 0
7072
+ }
7073
+ });
7074
+ return false;
5970
7075
  }
7076
+ this.idleAgentConfigs.set(agentId, cached);
7077
+ span.end("error", {
7078
+ attrs: {
7079
+ outcome: "restart_failed",
7080
+ runtime: cached.config.runtime,
7081
+ launchId: cached.launchId || void 0
7082
+ }
7083
+ });
7084
+ return false;
5971
7085
  });
5972
- return true;
5973
7086
  }
5974
7087
  logger.warn(`[Agent ${agentId}] Runtime profile ${kind} ${key} has no runtime injection path yet; leaving unacked for retry`);
5975
7088
  span.end("ok", { attrs: { outcome: "no_path" } });
@@ -6073,7 +7186,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6073
7186
  }
6074
7187
  // Workspace file browsing
6075
7188
  async getFileTree(agentId, dirPath) {
6076
- const agentDir = path12.join(this.dataDir, agentId);
7189
+ const agentDir = path13.join(this.dataDir, agentId);
6077
7190
  try {
6078
7191
  await stat2(agentDir);
6079
7192
  } catch {
@@ -6081,8 +7194,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6081
7194
  }
6082
7195
  let targetDir = agentDir;
6083
7196
  if (dirPath) {
6084
- const resolved = path12.resolve(agentDir, dirPath);
6085
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
7197
+ const resolved = path13.resolve(agentDir, dirPath);
7198
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
6086
7199
  return [];
6087
7200
  }
6088
7201
  targetDir = resolved;
@@ -6090,14 +7203,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6090
7203
  return this.listDirectoryChildren(targetDir, agentDir);
6091
7204
  }
6092
7205
  async readFile(agentId, filePath) {
6093
- const agentDir = path12.join(this.dataDir, agentId);
6094
- const resolved = path12.resolve(agentDir, filePath);
6095
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
7206
+ const agentDir = path13.join(this.dataDir, agentId);
7207
+ const resolved = path13.resolve(agentDir, filePath);
7208
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
6096
7209
  throw new Error("Access denied");
6097
7210
  }
6098
7211
  const info = await stat2(resolved);
6099
7212
  if (info.isDirectory()) throw new Error("Cannot read a directory");
6100
- const ext = path12.extname(resolved).toLowerCase();
7213
+ const ext = path13.extname(resolved).toLowerCase();
6101
7214
  if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
6102
7215
  if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
6103
7216
  const content = await readFile(resolved, "utf-8");
@@ -6132,13 +7245,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6132
7245
  const agent = this.agents.get(agentId);
6133
7246
  const runtime = runtimeHint || agent?.config.runtime || "claude";
6134
7247
  const home = os6.homedir();
6135
- const workspaceDir = path12.join(this.dataDir, agentId);
7248
+ const workspaceDir = path13.join(this.dataDir, agentId);
6136
7249
  const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
6137
7250
  const globalResults = await Promise.all(
6138
- paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
7251
+ paths.global.map((p) => this.scanSkillsDir(path13.join(home, p)))
6139
7252
  );
6140
7253
  const workspaceResults = await Promise.all(
6141
- paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
7254
+ paths.workspace.map((p) => this.scanSkillsDir(path13.join(workspaceDir, p)))
6142
7255
  );
6143
7256
  const dedup = (skills) => {
6144
7257
  const seen = /* @__PURE__ */ new Set();
@@ -6167,7 +7280,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6167
7280
  const skills = [];
6168
7281
  for (const entry of entries) {
6169
7282
  if (entry.isDirectory() || entry.isSymbolicLink()) {
6170
- const skillMd = path12.join(dir, entry.name, "SKILL.md");
7283
+ const skillMd = path13.join(dir, entry.name, "SKILL.md");
6171
7284
  try {
6172
7285
  const content = await readFile(skillMd, "utf-8");
6173
7286
  const skill = this.parseSkillMd(entry.name, content);
@@ -6178,7 +7291,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6178
7291
  } else if (entry.name.endsWith(".md")) {
6179
7292
  const cmdName = entry.name.replace(/\.md$/, "");
6180
7293
  try {
6181
- const content = await readFile(path12.join(dir, entry.name), "utf-8");
7294
+ const content = await readFile(path13.join(dir, entry.name), "utf-8");
6182
7295
  const skill = this.parseSkillMd(cmdName, content);
6183
7296
  skill.sourcePath = dir;
6184
7297
  skills.push(skill);
@@ -6419,11 +7532,23 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6419
7532
  ap.compactionWatchdog = null;
6420
7533
  this.broadcastActivity(agentId, "working", "Context compaction still running; no finish event observed");
6421
7534
  }
6422
- finishCompactionIfActive(agentId, detail = "Context compaction finished") {
7535
+ completeCompactionIfActive(agentId, detail = "Context compaction finished", options = {}) {
6423
7536
  const ap = this.agents.get(agentId);
6424
7537
  if (!ap || !ap.compactionStartedAt) return;
6425
7538
  this.clearCompactionWatchdog(ap);
6426
7539
  this.broadcastActivity(agentId, "working", detail, [{ kind: "compaction_finished" }]);
7540
+ ap.gatedSteering.compacting = false;
7541
+ this.setGatedSteeringPhase(agentId, ap, "assistant_continuation", { event: "compaction_finished", inferred: true });
7542
+ if (options.flushBoundaryMessages ?? true) {
7543
+ this.flushCompactionBoundaryMessages(agentId, ap);
7544
+ }
7545
+ ap.isIdle = false;
7546
+ }
7547
+ interruptCompactionIfActive(agentId) {
7548
+ const ap = this.agents.get(agentId);
7549
+ if (!ap || !ap.compactionStartedAt && !ap.gatedSteering.compacting) return;
7550
+ this.clearCompactionWatchdog(ap);
7551
+ ap.gatedSteering.compacting = false;
6427
7552
  }
6428
7553
  messagesTraceAttrs(messages) {
6429
7554
  if (!messages || messages.length === 0) return {};
@@ -6746,28 +7871,28 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6746
7871
  this.sendRuntimeProfileReport(agentId);
6747
7872
  break;
6748
7873
  case "thinking": {
6749
- this.finishCompactionIfActive(agentId, "Context compaction finished (inferred from resumed output)");
7874
+ if (ap) this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
7875
+ this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from resumed output)");
6750
7876
  this.queueTrajectoryText(agentId, "thinking", event.text);
6751
7877
  if (ap) ap.isIdle = false;
6752
- if (ap) this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
6753
7878
  this.setGatedSteeringPhase(agentId, ap, "assistant_continuation", { event: "thinking" });
6754
7879
  break;
6755
7880
  }
6756
7881
  case "text": {
6757
- this.finishCompactionIfActive(agentId, "Context compaction finished (inferred from resumed output)");
7882
+ if (ap) this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
7883
+ this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from resumed output)");
6758
7884
  this.queueTrajectoryText(agentId, "text", event.text);
6759
7885
  if (ap) ap.isIdle = false;
6760
- if (ap) this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
6761
7886
  this.setGatedSteeringPhase(agentId, ap, "assistant_continuation", { event: "text" });
6762
7887
  break;
6763
7888
  }
6764
7889
  case "tool_call": {
6765
- this.finishCompactionIfActive(agentId, "Context compaction finished (inferred from resumed tool use)");
7890
+ if (ap) this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
7891
+ this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from resumed tool use)");
6766
7892
  this.flushPendingTrajectory(agentId);
6767
7893
  const invocation = normalizeToolDisplayInvocation(event.name, event.input);
6768
7894
  if (ap) {
6769
7895
  ap.gatedSteering.outstandingToolUses++;
6770
- this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
6771
7896
  this.noteRuntimeProfileToolCall(agentId, ap, invocation.toolName);
6772
7897
  this.recordRuntimeTraceEvent(agentId, ap, "tool.call.started", { tool: invocation.toolName });
6773
7898
  this.setGatedSteeringPhase(agentId, ap, "tool_wait", {
@@ -6828,7 +7953,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6828
7953
  break;
6829
7954
  case "turn_end":
6830
7955
  if (ap) this.recordRuntimeTraceEvent(agentId, ap, "runtime.turn.completed");
6831
- this.finishCompactionIfActive(agentId, "Context compaction finished (inferred from turn end)");
7956
+ this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from turn end)", {
7957
+ flushBoundaryMessages: false
7958
+ });
6832
7959
  this.flushPendingTrajectory(agentId);
6833
7960
  if (ap) {
6834
7961
  this.clearGatedInFlightBatch(agentId, ap, "turn_end");
@@ -6885,7 +8012,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6885
8012
  }
6886
8013
  break;
6887
8014
  case "error": {
6888
- this.finishCompactionIfActive(agentId, "Context compaction interrupted by runtime error");
8015
+ this.interruptCompactionIfActive(agentId);
6889
8016
  this.flushPendingTrajectory(agentId);
6890
8017
  if (ap) ap.lastRuntimeError = event.message;
6891
8018
  if (ap) {
@@ -6933,6 +8060,29 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6933
8060
  sendAgentStatus(agentId, status, launchId) {
6934
8061
  this.sendToServer({ type: "agent:status", agentId, status, launchId: launchId || void 0 });
6935
8062
  }
8063
+ reportRunnerCredentialMintFailure(agentId, err, launchId, source) {
8064
+ if (!(err instanceof RunnerCredentialMintError)) return false;
8065
+ const detail = runnerCredentialErrorDetail(err);
8066
+ this.recordDaemonTrace("daemon.runner_credential_mint.hard_fail", {
8067
+ agentId,
8068
+ launchId: launchId || void 0,
8069
+ source,
8070
+ status: detail.status,
8071
+ code: detail.code,
8072
+ reason: detail.message,
8073
+ retryable: detail.retryable
8074
+ }, "error");
8075
+ const reason = err.message;
8076
+ this.sendAgentStatus(agentId, "inactive", launchId);
8077
+ this.broadcastActivity(
8078
+ agentId,
8079
+ "error",
8080
+ `Start failed: ${reason}`,
8081
+ [{ kind: "text", text: `Error: ${reason}` }],
8082
+ launchId
8083
+ );
8084
+ return true;
8085
+ }
6936
8086
  noteRuntimeTraceCounter(ap, event) {
6937
8087
  ap.runtimeTraceCounters.events++;
6938
8088
  switch (event.kind) {
@@ -7095,6 +8245,7 @@ ${RESPONSE_TARGET_HINT}`);
7095
8245
  }, "error");
7096
8246
  return false;
7097
8247
  }
8248
+ this.consumeVisibleMessages(agentId, { messages, source: `stdin_${mode}_delivery` });
7098
8249
  const senders = [...new Set(messages.map((message) => `@${message.sender_name}`))].join(", ");
7099
8250
  logger.info(
7100
8251
  `[Agent ${agentId}] Delivering ${mode} ${messages.length === 1 ? "message" : `${messages.length} messages`} via stdin from ${senders}`
@@ -7128,8 +8279,8 @@ ${RESPONSE_TARGET_HINT}`);
7128
8279
  const nodes = [];
7129
8280
  for (const entry of entries) {
7130
8281
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
7131
- const fullPath = path12.join(dir, entry.name);
7132
- const relativePath = path12.relative(rootDir, fullPath);
8282
+ const fullPath = path13.join(dir, entry.name);
8283
+ const relativePath = path13.relative(rootDir, fullPath);
7133
8284
  let info;
7134
8285
  try {
7135
8286
  info = await stat2(fullPath);
@@ -7432,9 +8583,9 @@ var ReminderCache = class {
7432
8583
 
7433
8584
  // src/machineLock.ts
7434
8585
  import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
7435
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
8586
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
7436
8587
  import os7 from "os";
7437
- import path13 from "path";
8588
+ import path14 from "path";
7438
8589
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
7439
8590
  var DaemonMachineLockConflictError = class extends Error {
7440
8591
  code = "DAEMON_MACHINE_LOCK_HELD";
@@ -7456,7 +8607,7 @@ function resolveDefaultMachineStateRoot() {
7456
8607
  return resolveSlockHomePath("machines");
7457
8608
  }
7458
8609
  function ownerPath(lockDir) {
7459
- return path13.join(lockDir, "owner.json");
8610
+ return path14.join(lockDir, "owner.json");
7460
8611
  }
7461
8612
  function readOwner(lockDir) {
7462
8613
  try {
@@ -7486,13 +8637,13 @@ function acquireDaemonMachineLock(options) {
7486
8637
  const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
7487
8638
  const fingerprint = apiKeyFingerprint(options.apiKey);
7488
8639
  const lockId = getDaemonMachineLockId(options.apiKey);
7489
- const machineDir = path13.join(rootDir, lockId);
7490
- const lockDir = path13.join(machineDir, "daemon.lock");
8640
+ const machineDir = path14.join(rootDir, lockId);
8641
+ const lockDir = path14.join(machineDir, "daemon.lock");
7491
8642
  const token = randomUUID2();
7492
- mkdirSync5(machineDir, { recursive: true });
8643
+ mkdirSync6(machineDir, { recursive: true });
7493
8644
  for (let attempt = 0; attempt < 2; attempt += 1) {
7494
8645
  try {
7495
- mkdirSync5(lockDir);
8646
+ mkdirSync6(lockDir);
7496
8647
  const owner = {
7497
8648
  pid: process.pid,
7498
8649
  token,
@@ -7502,10 +8653,10 @@ function acquireDaemonMachineLock(options) {
7502
8653
  apiKeyFingerprint: fingerprint.slice(0, 16)
7503
8654
  };
7504
8655
  try {
7505
- writeFileSync8(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
8656
+ writeFileSync9(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
7506
8657
  `, { mode: 384 });
7507
8658
  } catch (err) {
7508
- rmSync2(lockDir, { recursive: true, force: true });
8659
+ rmSync3(lockDir, { recursive: true, force: true });
7509
8660
  throw err;
7510
8661
  }
7511
8662
  return {
@@ -7515,7 +8666,7 @@ function acquireDaemonMachineLock(options) {
7515
8666
  release: () => {
7516
8667
  const currentOwner = readOwner(lockDir);
7517
8668
  if (currentOwner?.pid === process.pid && currentOwner.token === token) {
7518
- rmSync2(lockDir, { recursive: true, force: true });
8669
+ rmSync3(lockDir, { recursive: true, force: true });
7519
8670
  }
7520
8671
  }
7521
8672
  };
@@ -7532,15 +8683,15 @@ function acquireDaemonMachineLock(options) {
7532
8683
  throw new DaemonMachineLockConflictError(lockDir, null);
7533
8684
  }
7534
8685
  }
7535
- rmSync2(lockDir, { recursive: true, force: true });
8686
+ rmSync3(lockDir, { recursive: true, force: true });
7536
8687
  }
7537
8688
  }
7538
8689
  throw new DaemonMachineLockConflictError(lockDir, readOwner(lockDir));
7539
8690
  }
7540
8691
 
7541
8692
  // src/localTraceSink.ts
7542
- import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
7543
- import path14 from "path";
8693
+ import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync10 } from "fs";
8694
+ import path15 from "path";
7544
8695
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
7545
8696
  var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
7546
8697
  var DEFAULT_MAX_FILES = 8;
@@ -7576,7 +8727,7 @@ var LocalRotatingTraceSink = class {
7576
8727
  currentSize = 0;
7577
8728
  sequence = 0;
7578
8729
  constructor(options) {
7579
- this.traceDir = path14.join(options.machineDir, "traces");
8730
+ this.traceDir = path15.join(options.machineDir, "traces");
7580
8731
  this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
7581
8732
  const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
7582
8733
  const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
@@ -7602,15 +8753,15 @@ var LocalRotatingTraceSink = class {
7602
8753
  return this.currentFile;
7603
8754
  }
7604
8755
  ensureFile(nextBytes) {
7605
- mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
8756
+ mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
7606
8757
  const nowMs = this.nowMsProvider();
7607
8758
  const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
7608
8759
  if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
7609
- this.currentFile = path14.join(
8760
+ this.currentFile = path15.join(
7610
8761
  this.traceDir,
7611
8762
  `daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
7612
8763
  );
7613
- writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
8764
+ writeFileSync10(this.currentFile, "", { flag: "a", mode: 384 });
7614
8765
  this.currentSize = statSync4(this.currentFile).size;
7615
8766
  this.currentFileOpenedAtMs = nowMs;
7616
8767
  this.pruneOldFiles();
@@ -7621,7 +8772,7 @@ var LocalRotatingTraceSink = class {
7621
8772
  const excess = files.length - this.maxFiles;
7622
8773
  if (excess <= 0) return;
7623
8774
  for (const file of files.slice(0, excess)) {
7624
- rmSync3(path14.join(this.traceDir, file), { force: true });
8775
+ rmSync4(path15.join(this.traceDir, file), { force: true });
7625
8776
  }
7626
8777
  }
7627
8778
  };
@@ -7708,11 +8859,11 @@ function isDiagnosticErrorAttr(key) {
7708
8859
  import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
7709
8860
  import { gzipSync } from "zlib";
7710
8861
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
7711
- import path15 from "path";
8862
+ import path16 from "path";
7712
8863
 
7713
8864
  // src/directUploadCapability.ts
7714
- function joinUrl(base, path17) {
7715
- return `${base.replace(/\/+$/, "")}${path17}`;
8865
+ function joinUrl(base, path18) {
8866
+ return `${base.replace(/\/+$/, "")}${path18}`;
7716
8867
  }
7717
8868
  function jsonHeaders(apiKey) {
7718
8869
  return {
@@ -7931,7 +9082,7 @@ var DaemonTraceBundleUploader = class {
7931
9082
  }, nextMs);
7932
9083
  }
7933
9084
  async findUploadCandidates() {
7934
- const traceDir = path15.join(this.options.machineDir, "traces");
9085
+ const traceDir = path16.join(this.options.machineDir, "traces");
7935
9086
  let names;
7936
9087
  try {
7937
9088
  names = await readdir3(traceDir);
@@ -7943,8 +9094,8 @@ var DaemonTraceBundleUploader = class {
7943
9094
  const currentFile = this.options.currentFileProvider?.();
7944
9095
  const candidates = [];
7945
9096
  for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
7946
- const file = path15.join(traceDir, name);
7947
- if (currentFile && path15.resolve(file) === path15.resolve(currentFile)) continue;
9097
+ const file = path16.join(traceDir, name);
9098
+ if (currentFile && path16.resolve(file) === path16.resolve(currentFile)) continue;
7948
9099
  if (await this.isUploaded(file)) continue;
7949
9100
  try {
7950
9101
  const info = await stat3(file);
@@ -8018,8 +9169,8 @@ var DaemonTraceBundleUploader = class {
8018
9169
  }
8019
9170
  }
8020
9171
  uploadStatePath(file) {
8021
- const stateDir = path15.join(this.options.machineDir, "trace-uploads");
8022
- return path15.join(stateDir, `${path15.basename(file)}.uploaded.json`);
9172
+ const stateDir = path16.join(this.options.machineDir, "trace-uploads");
9173
+ return path16.join(stateDir, `${path16.basename(file)}.uploaded.json`);
8023
9174
  }
8024
9175
  async isUploaded(file) {
8025
9176
  try {
@@ -8031,9 +9182,9 @@ var DaemonTraceBundleUploader = class {
8031
9182
  }
8032
9183
  async markUploaded(file, metadata) {
8033
9184
  const stateFile = this.uploadStatePath(file);
8034
- await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
9185
+ await mkdir2(path16.dirname(stateFile), { recursive: true, mode: 448 });
8035
9186
  await writeFile2(stateFile, `${JSON.stringify({
8036
- file: path15.basename(file),
9187
+ file: path16.basename(file),
8037
9188
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
8038
9189
  ...metadata
8039
9190
  }, null, 2)}
@@ -8052,10 +9203,48 @@ function readPositiveIntegerEnv2(name, fallback) {
8052
9203
 
8053
9204
  // src/core.ts
8054
9205
  var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
8055
- var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
8056
- function parseDaemonCliArgs(args) {
9206
+ var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels"];
9207
+ var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
9208
+ var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
9209
+ var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
9210
+ var RunnerCredentialMintError2 = class extends Error {
9211
+ code;
9212
+ retryable;
9213
+ status;
9214
+ constructor(message, opts) {
9215
+ super(message);
9216
+ this.name = "RunnerCredentialMintError";
9217
+ this.code = opts.code;
9218
+ this.retryable = opts.retryable ?? false;
9219
+ this.status = opts.status;
9220
+ }
9221
+ };
9222
+ function isRetryableMintHttpFailure2(status, code) {
9223
+ if (code === "experimental_surface_disabled") return false;
9224
+ return status === 408 || status === 425 || status === 429 || status >= 500;
9225
+ }
9226
+ function runnerCredentialErrorDetail2(error) {
9227
+ if (error instanceof RunnerCredentialMintError2) {
9228
+ return {
9229
+ message: error.message,
9230
+ code: error.code,
9231
+ retryable: error.retryable,
9232
+ status: error.status
9233
+ };
9234
+ }
9235
+ const message = error instanceof Error ? error.message : String(error);
9236
+ return {
9237
+ message,
9238
+ code: "runner_credential_mint_network_error",
9239
+ retryable: true
9240
+ };
9241
+ }
9242
+ async function waitForRunnerCredentialRetry2() {
9243
+ await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
9244
+ }
9245
+ function parseDaemonCliArgs(args, env = {}) {
8057
9246
  let serverUrl = "";
8058
- let apiKey = "";
9247
+ let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
8059
9248
  for (let i = 0; i < args.length; i++) {
8060
9249
  if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
8061
9250
  if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
@@ -8072,23 +9261,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
8072
9261
  }
8073
9262
  }
8074
9263
  function resolveChatBridgePath(moduleUrl = import.meta.url) {
8075
- const dirname = path16.dirname(fileURLToPath(moduleUrl));
8076
- const jsPath = path16.resolve(dirname, "chat-bridge.js");
9264
+ const dirname = path17.dirname(fileURLToPath2(moduleUrl));
9265
+ const jsPath = path17.resolve(dirname, "chat-bridge.js");
8077
9266
  try {
8078
9267
  accessSync(jsPath);
8079
9268
  return jsPath;
8080
9269
  } catch {
8081
- return path16.resolve(dirname, "chat-bridge.ts");
9270
+ return path17.resolve(dirname, "chat-bridge.ts");
8082
9271
  }
8083
9272
  }
8084
9273
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
8085
- const thisDir = path16.dirname(fileURLToPath(moduleUrl));
8086
- const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
9274
+ const thisDir = path17.dirname(fileURLToPath2(moduleUrl));
9275
+ const bundledDistPath = path17.resolve(thisDir, "cli", "index.js");
8087
9276
  try {
8088
9277
  accessSync(bundledDistPath);
8089
9278
  return bundledDistPath;
8090
9279
  } catch {
8091
- const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
9280
+ const workspaceDistPath = path17.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
8092
9281
  accessSync(workspaceDistPath);
8093
9282
  return workspaceDistPath;
8094
9283
  }
@@ -8267,7 +9456,7 @@ var DaemonCore = class {
8267
9456
  }
8268
9457
  resolveMachineStateRoot() {
8269
9458
  if (this.options.machineStateDir) return this.options.machineStateDir;
8270
- if (this.options.dataDir) return path16.join(path16.dirname(this.options.dataDir), "machines");
9459
+ if (this.options.dataDir) return path17.join(path17.dirname(this.options.dataDir), "machines");
8271
9460
  return resolveDefaultMachineStateRoot();
8272
9461
  }
8273
9462
  shouldEnableLocalTrace() {
@@ -8384,13 +9573,115 @@ var DaemonCore = class {
8384
9573
  });
8385
9574
  span.end(status);
8386
9575
  }
9576
+ async requestRunnerCredentialOnce(agentId, config) {
9577
+ const url = new URL(`/internal/computer/runners/${encodeURIComponent(agentId)}/credentials`, this.options.serverUrl);
9578
+ const res = await fetch(url, {
9579
+ method: "POST",
9580
+ headers: {
9581
+ "Authorization": `Bearer ${this.options.apiKey}`,
9582
+ "Content-Type": "application/json",
9583
+ "X-Slock-Client": "daemon-server-session-worker"
9584
+ },
9585
+ body: JSON.stringify({
9586
+ scopes: RUNNER_CREDENTIAL_SCOPES,
9587
+ name: `runner:${config.runtime}:${agentId.slice(0, 8)}`
9588
+ })
9589
+ });
9590
+ if (!res.ok) {
9591
+ const contentType = res.headers.get("content-type") ?? "";
9592
+ let detail = `HTTP ${res.status}`;
9593
+ let code = null;
9594
+ if (contentType.includes("application/json")) {
9595
+ const body2 = await res.json().catch(() => null);
9596
+ const error = typeof body2?.error === "string" ? body2.error : null;
9597
+ code = typeof body2?.code === "string" ? body2.code : null;
9598
+ detail = [detail, code, error].filter(Boolean).join(" ");
9599
+ }
9600
+ throw new RunnerCredentialMintError2(detail, {
9601
+ code: code ?? "runner_credential_mint_http_error",
9602
+ retryable: isRetryableMintHttpFailure2(res.status, code),
9603
+ status: res.status
9604
+ });
9605
+ }
9606
+ const body = await res.json().catch(() => null);
9607
+ if (typeof body?.apiKey !== "string" || !body.apiKey.startsWith("sk_agent_")) {
9608
+ throw new RunnerCredentialMintError2("invalid_agent_credential_payload", {
9609
+ code: "invalid_agent_credential_payload"
9610
+ });
9611
+ }
9612
+ return {
9613
+ apiKey: body.apiKey,
9614
+ credentialId: typeof body.credentialId === "string" ? body.credentialId : null
9615
+ };
9616
+ }
9617
+ async mintRunnerCredential(agentId, config) {
9618
+ if (config.agentCredentialKey) {
9619
+ return { apiKey: config.agentCredentialKey, credentialId: config.agentCredentialId ?? null };
9620
+ }
9621
+ if (process.env.SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED === "1") {
9622
+ throw new RunnerCredentialMintError2("runner credential mint is disabled by SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED", {
9623
+ code: "runner_credentials_disabled"
9624
+ });
9625
+ }
9626
+ let lastError = null;
9627
+ for (let attempt = 1; attempt <= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2; attempt += 1) {
9628
+ try {
9629
+ return await this.requestRunnerCredentialOnce(agentId, config);
9630
+ } catch (err) {
9631
+ lastError = err;
9632
+ const detail2 = runnerCredentialErrorDetail2(err);
9633
+ this.recordDaemonTrace("daemon.runner_credential_mint.retry", {
9634
+ agentId,
9635
+ runtime: config.runtime,
9636
+ attempt,
9637
+ max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2,
9638
+ status: detail2.status,
9639
+ code: detail2.code,
9640
+ reason: detail2.message,
9641
+ retryable: detail2.retryable
9642
+ }, detail2.retryable && attempt < RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 ? "ok" : "error");
9643
+ if (!detail2.retryable || attempt >= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2) break;
9644
+ await waitForRunnerCredentialRetry2();
9645
+ }
9646
+ }
9647
+ const detail = runnerCredentialErrorDetail2(lastError);
9648
+ this.recordDaemonTrace("daemon.runner_credential_mint.failed", {
9649
+ agentId,
9650
+ runtime: config.runtime,
9651
+ status: detail.status,
9652
+ code: detail.code,
9653
+ reason: detail.message,
9654
+ retryable: detail.retryable,
9655
+ max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2
9656
+ }, "error");
9657
+ throw new RunnerCredentialMintError2(
9658
+ `runner_credential_mint_failed: ${detail.message}. Managed runner startup requires /internal/computer credential mint; deploy server first or roll back the daemon binary.`,
9659
+ {
9660
+ code: detail.code,
9661
+ retryable: detail.retryable,
9662
+ status: detail.status
9663
+ }
9664
+ );
9665
+ }
9666
+ async startAgentFromMessage(msg) {
9667
+ const agentCredential = await this.mintRunnerCredential(msg.agentId, msg.config);
9668
+ const config = { ...msg.config, agentCredentialKey: agentCredential.apiKey, agentCredentialId: agentCredential.credentialId };
9669
+ await this.agentManager.startAgent(
9670
+ msg.agentId,
9671
+ config,
9672
+ msg.wakeMessage,
9673
+ msg.unreadSummary,
9674
+ msg.resumePrompt,
9675
+ msg.launchId
9676
+ );
9677
+ }
8387
9678
  handleMessage(msg) {
8388
9679
  const summary = summarizeIncomingMessage(msg);
8389
9680
  logger.info(`[Daemon] Received ${msg.type}${summary ? ` ${summary}` : ""}`);
8390
9681
  switch (msg.type) {
8391
9682
  case "agent:start":
8392
9683
  logger.info(`[Agent ${msg.agentId}] Start requested (runtime=${msg.config.runtime}, model=${msg.config.model}, session=${msg.config.sessionId || "new"}${msg.wakeMessage ? ", wake=true" : ""})`);
8393
- this.agentManager.startAgent(msg.agentId, msg.config, msg.wakeMessage, msg.unreadSummary, msg.resumePrompt, msg.launchId).catch((err) => {
9684
+ this.startAgentFromMessage(msg).catch((err) => {
8394
9685
  const reason = err instanceof Error ? err.message : String(err);
8395
9686
  logger.error(`[Agent ${msg.agentId}] Start failed (${reason})`);
8396
9687
  this.connection.send({ type: "agent:status", agentId: msg.agentId, status: "inactive", launchId: msg.launchId });
@@ -8423,22 +9714,27 @@ var DaemonCore = class {
8423
9714
  logger.info(`[Agent ${msg.agentId}] Delivery received (seq=${msg.seq}, from=@${msg.message.sender_name}, target=${formatChannelTarget(msg)})`);
8424
9715
  try {
8425
9716
  span.addEvent("daemon.receive", { seq: msg.seq, deliveryId: msg.deliveryId });
8426
- const accepted = this.agentManager.deliverMessage(msg.agentId, msg.message, { deliveryId: msg.deliveryId });
8427
- span.addEvent("daemon.deliver_to_agent_manager", { accepted });
8428
- if (!accepted) {
8429
- span.end("ok", { attrs: { outcome: "not-accepted" } });
8430
- break;
8431
- }
8432
- const ackSeq = msg.seq > 0 ? msg.seq : msg.message.seq ?? 0;
8433
- span.addEvent("daemon.ack.sent", { seq: ackSeq });
8434
- this.connection.send({
8435
- type: "agent:deliver:ack",
8436
- agentId: msg.agentId,
8437
- seq: ackSeq,
8438
- traceparent: formatTraceparent(span.context),
8439
- deliveryId: msg.deliveryId
9717
+ const acceptedOrPromise = this.agentManager.deliverMessage(msg.agentId, msg.message, { deliveryId: msg.deliveryId });
9718
+ Promise.resolve(acceptedOrPromise).then((accepted) => {
9719
+ span.addEvent("daemon.deliver_to_agent_manager", { accepted });
9720
+ if (!accepted) {
9721
+ span.end("ok", { attrs: { outcome: "not-accepted" } });
9722
+ return;
9723
+ }
9724
+ const ackSeq = msg.seq > 0 ? msg.seq : msg.message.seq ?? 0;
9725
+ span.addEvent("daemon.ack.sent", { seq: ackSeq });
9726
+ this.connection.send({
9727
+ type: "agent:deliver:ack",
9728
+ agentId: msg.agentId,
9729
+ seq: ackSeq,
9730
+ traceparent: formatTraceparent(span.context),
9731
+ deliveryId: msg.deliveryId
9732
+ });
9733
+ span.end("ok", { attrs: { outcome: "ack-sent", ackSeq, deliveryId: msg.deliveryId } });
9734
+ }, (err) => {
9735
+ logger.error(`[Agent ${msg.agentId}] Delivery handling failed`, err);
9736
+ span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
8440
9737
  });
8441
- span.end("ok", { attrs: { outcome: "ack-sent", ackSeq, deliveryId: msg.deliveryId } });
8442
9738
  } catch (err) {
8443
9739
  span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
8444
9740
  throw err;
@@ -8458,8 +9754,14 @@ var DaemonCore = class {
8458
9754
  }
8459
9755
  });
8460
9756
  logger.info(`[Agent ${msg.agentId}] Runtime profile migration received (${msg.migrationKey})`);
8461
- const accepted = this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.migrationKey, "migration", msg.message, formatTraceparent(span.context));
8462
- span.end("ok", { attrs: { outcome: accepted ? "accepted" : "no_injection_path" } });
9757
+ Promise.resolve(
9758
+ this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.migrationKey, "migration", msg.message, formatTraceparent(span.context))
9759
+ ).then((accepted) => {
9760
+ span.end("ok", { attrs: { outcome: accepted ? "accepted" : "no_injection_path" } });
9761
+ }, (err) => {
9762
+ logger.error(`[Agent ${msg.agentId}] Runtime profile migration handling failed`, err);
9763
+ span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
9764
+ });
8463
9765
  break;
8464
9766
  }
8465
9767
  case "agent:runtime_profile:daemon_release_notice": {
@@ -8475,8 +9777,14 @@ var DaemonCore = class {
8475
9777
  }
8476
9778
  });
8477
9779
  logger.info(`[Agent ${msg.agentId}] Runtime profile daemon release notice received (${msg.noticeKey})`);
8478
- const accepted = this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.noticeKey, "daemon_release_notice", msg.message, formatTraceparent(span.context));
8479
- span.end("ok", { attrs: { outcome: accepted ? "accepted" : "no_injection_path" } });
9780
+ Promise.resolve(
9781
+ this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.noticeKey, "daemon_release_notice", msg.message, formatTraceparent(span.context))
9782
+ ).then((accepted) => {
9783
+ span.end("ok", { attrs: { outcome: accepted ? "accepted" : "no_injection_path" } });
9784
+ }, (err) => {
9785
+ logger.error(`[Agent ${msg.agentId}] Runtime profile daemon release notice handling failed`, err);
9786
+ span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
9787
+ });
8480
9788
  break;
8481
9789
  }
8482
9790
  case "agent:workspace:list":
@@ -8650,6 +9958,8 @@ var DaemonCore = class {
8650
9958
  };
8651
9959
 
8652
9960
  export {
9961
+ DAEMON_API_KEY_ENV,
9962
+ scrubDaemonAuthEnv,
8653
9963
  resolveWorkspaceDirectoryPath,
8654
9964
  scanWorkspaceDirectories,
8655
9965
  deleteWorkspaceDirectory,