@slock-ai/daemon 0.50.0 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -777,7 +777,7 @@ import os2 from "os";
777
777
  import path4 from "path";
778
778
 
779
779
  // src/drivers/cliTransport.ts
780
- import { mkdirSync, writeFileSync } from "fs";
780
+ import { mkdirSync, rmSync, writeFileSync } from "fs";
781
781
  import path2 from "path";
782
782
 
783
783
  // src/drivers/systemPrompt.ts
@@ -870,7 +870,7 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
870
870
  16. **\`slock attachment upload\`** \u2014 Upload a file to attach to a message. Uses content sniffing for image previews; pass \`--mime-type\` only when you know the exact type. Returns an attachment ID to pass to \`slock message send\`.
871
871
  17. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
872
872
  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.
873
+ 19. **\`slock profile update\`** \u2014 Update your own profile. Supports \`--avatar-file <path>\`, \`--avatar-url pixel:random:<seed>\`, \`--display-name <name>\`, and \`--description <text>\`. Use \`--avatar-url pixel:random:<seed>\` when you want a new pixel avatar but do not have a local image file. Values must be non-empty. Provide at least one flag per call; multiple flags can be combined.
874
874
  20. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
875
875
  21. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
876
876
  22. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
@@ -1349,8 +1349,304 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
1349
1349
  return candidates.filter((candidate) => existsSync(candidate.path));
1350
1350
  }
1351
1351
 
1352
+ // src/agentCredentialProxy.ts
1353
+ import { randomBytes } from "crypto";
1354
+ import http from "http";
1355
+ import { URL as URL2 } from "url";
1356
+ var registrations = /* @__PURE__ */ new Map();
1357
+ var servers = /* @__PURE__ */ new Map();
1358
+ function allocatePort() {
1359
+ return 43e3 + randomBytes(2).readUInt16BE(0) % 1e4;
1360
+ }
1361
+ function ensureServer(port) {
1362
+ if (servers.has(port)) return;
1363
+ const server = http.createServer((req, res) => {
1364
+ void handleProxyRequest(req, res);
1365
+ });
1366
+ server.on("error", (err) => {
1367
+ logger.warn(`[Agent Credential Proxy] local proxy on port ${port} failed: ${err.message}`);
1368
+ });
1369
+ server.listen(port, "127.0.0.1");
1370
+ server.unref();
1371
+ servers.set(port, server);
1372
+ }
1373
+ async function handleProxyRequest(req, res) {
1374
+ const auth = req.headers.authorization;
1375
+ const token = typeof auth === "string" && auth.startsWith("Bearer ") ? auth.slice("Bearer ".length) : "";
1376
+ const registration = registrations.get(token);
1377
+ if (!registration) {
1378
+ res.writeHead(401, { "content-type": "application/json" });
1379
+ res.end(JSON.stringify({ error: "invalid local agent proxy token", code: "invalid_agent_proxy_token" }));
1380
+ return;
1381
+ }
1382
+ try {
1383
+ const target = new URL2(req.url ?? "/", registration.serverUrl);
1384
+ const headers = new Headers();
1385
+ for (const [name, value] of Object.entries(req.headers)) {
1386
+ if (value === void 0) continue;
1387
+ if (name.toLowerCase() === "host") continue;
1388
+ if (name.toLowerCase() === "authorization") continue;
1389
+ if (Array.isArray(value)) {
1390
+ for (const item of value) headers.append(name, item);
1391
+ } else {
1392
+ headers.set(name, value);
1393
+ }
1394
+ }
1395
+ headers.set("Authorization", `Bearer ${registration.apiKey}`);
1396
+ headers.set("X-Agent-Id", registration.agentId);
1397
+ headers.set("X-Slock-Client", "cli");
1398
+ headers.set("X-Slock-Agent-Active-Capabilities", registration.activeCapabilities);
1399
+ const method = req.method ?? "GET";
1400
+ let body = method === "GET" || method === "HEAD" ? void 0 : req;
1401
+ let sendTarget;
1402
+ if (method === "POST" && target.pathname === "/internal/agent-api/send") {
1403
+ const rawBody = await readRequestBody(req);
1404
+ const prepared = await prepareAgentApiSendForward(registration, headers, rawBody);
1405
+ if (prepared.localResponse) {
1406
+ const responseText = JSON.stringify(prepared.localResponse);
1407
+ res.writeHead(200, { "content-type": "application/json" });
1408
+ res.end(responseText);
1409
+ return;
1410
+ }
1411
+ body = prepared.bodyText;
1412
+ sendTarget = prepared.target;
1413
+ headers.set("content-type", "application/json");
1414
+ headers.set("content-length", String(Buffer.byteLength(prepared.bodyText)));
1415
+ }
1416
+ const upstream = await fetch(target, {
1417
+ method,
1418
+ headers,
1419
+ body,
1420
+ // Required by undici when forwarding a Node stream.
1421
+ duplex: body ? "half" : void 0
1422
+ });
1423
+ if (shouldBufferJsonResponse(upstream, target.pathname, registration)) {
1424
+ const responseText = await upstream.text();
1425
+ consumeVisibleResponse(registration, target, sendTarget, responseText);
1426
+ res.writeHead(upstream.status, Object.fromEntries(upstream.headers.entries()));
1427
+ res.end(responseText);
1428
+ return;
1429
+ }
1430
+ res.writeHead(upstream.status, Object.fromEntries(upstream.headers.entries()));
1431
+ if (upstream.body) {
1432
+ const reader = upstream.body.getReader();
1433
+ try {
1434
+ while (true) {
1435
+ const { done, value } = await reader.read();
1436
+ if (done) break;
1437
+ res.write(Buffer.from(value));
1438
+ }
1439
+ } finally {
1440
+ res.end();
1441
+ }
1442
+ } else {
1443
+ res.end();
1444
+ }
1445
+ } catch (err) {
1446
+ res.writeHead(502, { "content-type": "application/json" });
1447
+ res.end(JSON.stringify({
1448
+ error: "failed to proxy local agent request",
1449
+ code: "agent_proxy_failed",
1450
+ detail: err instanceof Error ? err.message : String(err)
1451
+ }));
1452
+ }
1453
+ }
1454
+ async function readRequestBody(req) {
1455
+ let body = "";
1456
+ req.setEncoding("utf8");
1457
+ for await (const chunk of req) {
1458
+ body += String(chunk);
1459
+ }
1460
+ return body;
1461
+ }
1462
+ function messageSeq(message) {
1463
+ return Number(message.seq ?? 0);
1464
+ }
1465
+ function boundaryBefore(messages) {
1466
+ let minSeq = Number.POSITIVE_INFINITY;
1467
+ for (const message of messages) {
1468
+ const seq = Math.floor(messageSeq(message));
1469
+ if (Number.isFinite(seq) && seq > 0) minSeq = Math.min(minSeq, seq);
1470
+ }
1471
+ return Number.isFinite(minSeq) ? Math.max(0, minSeq - 1) : void 0;
1472
+ }
1473
+ function maxMessageSeq(messages) {
1474
+ let maxSeq = 0;
1475
+ for (const message of messages) {
1476
+ const seq = Math.floor(messageSeq(message));
1477
+ if (Number.isFinite(seq) && seq > 0) maxSeq = Math.max(maxSeq, seq);
1478
+ }
1479
+ return maxSeq > 0 ? maxSeq : void 0;
1480
+ }
1481
+ function parseTargetFields(target) {
1482
+ if (target.startsWith("dm:@")) {
1483
+ const rest = target.slice("dm:@".length);
1484
+ const [peer, threadId] = rest.split(":", 2);
1485
+ if (threadId) {
1486
+ return {
1487
+ channel_type: "thread",
1488
+ channel_name: threadId,
1489
+ parent_channel_type: "dm",
1490
+ parent_channel_name: peer
1491
+ };
1492
+ }
1493
+ return { channel_type: "dm", channel_name: peer };
1494
+ }
1495
+ if (target.startsWith("#")) {
1496
+ const rest = target.slice(1);
1497
+ const [channel, threadId] = rest.split(":", 2);
1498
+ if (threadId) {
1499
+ return {
1500
+ channel_type: "thread",
1501
+ channel_name: threadId,
1502
+ parent_channel_type: "channel",
1503
+ parent_channel_name: channel
1504
+ };
1505
+ }
1506
+ return { channel_type: "channel", channel_name: channel };
1507
+ }
1508
+ return {};
1509
+ }
1510
+ function normalizeVisibleMessage(message, target) {
1511
+ const targetFields = target ? parseTargetFields(target) : {};
1512
+ return {
1513
+ ...targetFields,
1514
+ ...message,
1515
+ message_id: message.message_id ?? message.id,
1516
+ timestamp: message.timestamp ?? message.createdAt,
1517
+ sender_type: message.sender_type ?? message.senderType,
1518
+ sender_name: message.sender_name ?? message.senderName,
1519
+ sender_description: message.sender_description ?? message.senderDescription ?? null
1520
+ };
1521
+ }
1522
+ function normalizeVisibleMessages(messages, target) {
1523
+ return messages.map((message) => normalizeVisibleMessage(message, target));
1524
+ }
1525
+ async function loadRecentTargetMessages(registration, headers, target) {
1526
+ const historyUrl = new URL2("/internal/agent-api/history", registration.serverUrl);
1527
+ historyUrl.searchParams.set("channel", target);
1528
+ historyUrl.searchParams.set("limit", "3");
1529
+ const historyHeaders = new Headers(headers);
1530
+ historyHeaders.delete("content-length");
1531
+ historyHeaders.delete("content-type");
1532
+ const res = await fetch(historyUrl, { method: "GET", headers: historyHeaders });
1533
+ if (!res.ok) return [];
1534
+ const parsed = await res.json().catch(() => null);
1535
+ return Array.isArray(parsed?.messages) ? normalizeVisibleMessages(parsed.messages, target) : [];
1536
+ }
1537
+ async function prepareAgentApiSendForward(registration, headers, rawBody) {
1538
+ let body;
1539
+ try {
1540
+ body = rawBody ? JSON.parse(rawBody) : {};
1541
+ } catch {
1542
+ return { bodyText: rawBody };
1543
+ }
1544
+ const target = typeof body.target === "string" ? body.target : void 0;
1545
+ const coordinator = registration.inboxCoordinator;
1546
+ if (!target || !coordinator || body.continueAnyway === true) {
1547
+ return { bodyText: JSON.stringify(body), target };
1548
+ }
1549
+ const pending = coordinator.getPendingMessages(target);
1550
+ if (pending.length > 0) {
1551
+ const staleBoundary = boundaryBefore(pending);
1552
+ if (staleBoundary !== void 0) {
1553
+ body.seenUpToSeq = staleBoundary;
1554
+ }
1555
+ return { bodyText: JSON.stringify(body), target };
1556
+ }
1557
+ const existingBoundary = typeof body.seenUpToSeq === "number" && Number.isFinite(body.seenUpToSeq) ? Math.max(0, Math.floor(body.seenUpToSeq)) : void 0;
1558
+ const loadedBoundary = coordinator.getBoundary(target);
1559
+ const boundary = Math.max(existingBoundary ?? 0, loadedBoundary ?? 0);
1560
+ if (boundary > 0) {
1561
+ body.seenUpToSeq = boundary;
1562
+ return { bodyText: JSON.stringify(body), target };
1563
+ }
1564
+ const recent = await loadRecentTargetMessages(registration, headers, target);
1565
+ if (recent.length > 0) {
1566
+ const seenUpToSeq = maxMessageSeq(recent);
1567
+ coordinator.consumeVisibleMessages({ target, messages: recent, boundarySeq: seenUpToSeq, source: "send_preflight_context" });
1568
+ return {
1569
+ bodyText: JSON.stringify(body),
1570
+ target,
1571
+ localResponse: {
1572
+ state: "held",
1573
+ seenUpToSeq,
1574
+ heldMessages: recent,
1575
+ newMessageCount: recent.length,
1576
+ shownMessageCount: recent.length,
1577
+ omittedMessageCount: 0
1578
+ }
1579
+ };
1580
+ }
1581
+ return { bodyText: JSON.stringify(body), target };
1582
+ }
1583
+ function shouldBufferJsonResponse(upstream, pathname, registration) {
1584
+ if (!registration.inboxCoordinator) return false;
1585
+ const contentType = upstream.headers.get("content-type") ?? "";
1586
+ if (!contentType.includes("application/json")) return false;
1587
+ return pathname === "/internal/agent-api/send" || pathname === "/internal/agent-api/events" || pathname === "/internal/agent-api/history";
1588
+ }
1589
+ function consumeVisibleResponse(registration, targetUrl, sendTarget, responseText) {
1590
+ const coordinator = registration.inboxCoordinator;
1591
+ if (!coordinator) return;
1592
+ let parsed;
1593
+ try {
1594
+ parsed = JSON.parse(responseText);
1595
+ } catch {
1596
+ return;
1597
+ }
1598
+ if (targetUrl.pathname === "/internal/agent-api/send" && parsed.state === "held" && Array.isArray(parsed.heldMessages)) {
1599
+ coordinator.consumeVisibleMessages({
1600
+ target: sendTarget,
1601
+ messages: normalizeVisibleMessages(parsed.heldMessages, sendTarget),
1602
+ boundarySeq: typeof parsed.seenUpToSeq === "number" ? parsed.seenUpToSeq : void 0,
1603
+ source: "server_held_context"
1604
+ });
1605
+ return;
1606
+ }
1607
+ if (targetUrl.pathname === "/internal/agent-api/events" && Array.isArray(parsed.events)) {
1608
+ coordinator.consumeVisibleMessages({ messages: normalizeVisibleMessages(parsed.events), source: "agent_api_events" });
1609
+ return;
1610
+ }
1611
+ if (targetUrl.pathname === "/internal/agent-api/history" && Array.isArray(parsed.messages)) {
1612
+ const target = targetUrl.searchParams.get("channel") ?? void 0;
1613
+ const messages = normalizeVisibleMessages(parsed.messages, target);
1614
+ coordinator.consumeVisibleMessages({ target, messages, boundarySeq: maxMessageSeq(messages), source: "agent_api_history" });
1615
+ }
1616
+ }
1617
+ function registerAgentCredentialProxy(input) {
1618
+ const port = allocatePort();
1619
+ ensureServer(port);
1620
+ const proxyToken = `sap_${randomBytes(32).toString("base64url")}`;
1621
+ registrations.set(proxyToken, {
1622
+ serverUrl: input.serverUrl,
1623
+ apiKey: input.apiKey,
1624
+ agentId: input.agentId,
1625
+ launchId: input.launchId ?? null,
1626
+ activeCapabilities: input.activeCapabilities,
1627
+ inboxCoordinator: input.inboxCoordinator
1628
+ });
1629
+ return {
1630
+ proxyUrl: `http://127.0.0.1:${port}`,
1631
+ proxyToken
1632
+ };
1633
+ }
1634
+ function unregisterAgentCredentialProxyForLaunch(input) {
1635
+ let removed = 0;
1636
+ const launchId = input.launchId ?? null;
1637
+ for (const [token, registration] of registrations) {
1638
+ if (registration.agentId === input.agentId && registration.launchId === launchId) {
1639
+ registrations.delete(token);
1640
+ removed += 1;
1641
+ }
1642
+ }
1643
+ return removed;
1644
+ }
1645
+
1352
1646
  // src/drivers/cliTransport.ts
1353
1647
  var shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
1648
+ var DEFAULT_ACTIVE_CAPABILITIES = "send,read,mentions,tasks,reactions,server,channels";
1649
+ var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
1354
1650
  function runtimeContextEnv(config) {
1355
1651
  const ctx = config.runtimeContext;
1356
1652
  if (!ctx) return {};
@@ -1374,17 +1670,43 @@ function prepareCliTransport(ctx, extraEnv = {}, platform = process.platform) {
1374
1670
  }
1375
1671
  const slockDir = path2.join(ctx.workingDirectory, ".slock");
1376
1672
  mkdirSync(slockDir, { recursive: true });
1673
+ const slockHome = resolveSlockHome();
1377
1674
  const tokenFile = path2.join(slockDir, "agent-token");
1378
- writeFileSync(tokenFile, ctx.config.authToken || ctx.daemonApiKey, { mode: 384 });
1675
+ const agentCredentialKey = ctx.config.agentCredentialKey;
1676
+ let agentCredentialProxy = null;
1677
+ let agentCredentialProxyTokenFile = null;
1678
+ if (typeof agentCredentialKey === "string" && agentCredentialKey.length > 0) {
1679
+ rmSync(tokenFile, { force: true });
1680
+ agentCredentialProxy = registerAgentCredentialProxy({
1681
+ agentId: ctx.agentId,
1682
+ launchId: ctx.launchId,
1683
+ serverUrl: ctx.config.serverUrl,
1684
+ apiKey: agentCredentialKey,
1685
+ activeCapabilities: DEFAULT_ACTIVE_CAPABILITIES,
1686
+ inboxCoordinator: ctx.agentCredentialProxyInboxCoordinator
1687
+ });
1688
+ const launchPart = safePathPart(ctx.launchId || `pid-${process.pid}`);
1689
+ const proxyTokenDir = path2.join(slockHome, "agent-proxy-tokens", safePathPart(ctx.agentId));
1690
+ mkdirSync(proxyTokenDir, { recursive: true, mode: 448 });
1691
+ agentCredentialProxyTokenFile = path2.join(proxyTokenDir, `${launchPart}.token`);
1692
+ writeFileSync(agentCredentialProxyTokenFile, agentCredentialProxy.proxyToken, { mode: 384 });
1693
+ } else {
1694
+ writeFileSync(tokenFile, ctx.config.authToken || ctx.daemonApiKey, { mode: 384 });
1695
+ }
1379
1696
  const posixWrapper = path2.join(slockDir, "slock");
1697
+ const posixCredentialPrefix = agentCredentialProxy ? `SLOCK_AGENT_PROXY_URL=${shellSingleQuote(agentCredentialProxy.proxyUrl)} SLOCK_AGENT_PROXY_TOKEN_FILE=${shellSingleQuote(agentCredentialProxyTokenFile)} SLOCK_AGENT_ACTIVE_CAPABILITIES=${shellSingleQuote(DEFAULT_ACTIVE_CAPABILITIES)} ` : "";
1380
1698
  const posixBody = `#!/usr/bin/env bash
1381
- exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)} "$@"
1699
+ ${posixCredentialPrefix}exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)} "$@"
1382
1700
  `;
1383
1701
  writeFileSync(posixWrapper, posixBody, { mode: 493 });
1384
1702
  if (platform === "win32") {
1385
1703
  const cmdWrapper = path2.join(slockDir, "slock.cmd");
1704
+ const cmdCredentialLine = agentCredentialProxy ? `set "SLOCK_AGENT_PROXY_URL=${agentCredentialProxy.proxyUrl}"\r
1705
+ set "SLOCK_AGENT_PROXY_TOKEN_FILE=${agentCredentialProxyTokenFile}"\r
1706
+ set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
1707
+ ` : "";
1386
1708
  const cmdBody = `@echo off\r
1387
- "${process.execPath}" "${ctx.slockCliPath}" %*\r
1709
+ ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
1388
1710
  `;
1389
1711
  writeFileSync(cmdWrapper, cmdBody);
1390
1712
  }
@@ -1395,17 +1717,27 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)}
1395
1717
  ...ctx.config.envVars || {},
1396
1718
  ...extraEnv,
1397
1719
  ...runtimeContextEnv(ctx.config),
1398
- [SLOCK_HOME_ENV]: resolveSlockHome(),
1720
+ [SLOCK_HOME_ENV]: slockHome,
1399
1721
  SLOCK_AGENT_ID: ctx.agentId,
1400
1722
  ...ctx.launchId ? { SLOCK_AGENT_LAUNCH_ID: ctx.launchId } : {},
1401
1723
  SLOCK_SERVER_URL: ctx.config.serverUrl,
1402
- SLOCK_AGENT_TOKEN_FILE: tokenFile,
1724
+ ...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
1403
1725
  PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
1404
1726
  };
1405
1727
  delete spawnEnv.SLOCK_AGENT_TOKEN;
1728
+ delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY;
1729
+ delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY_FILE;
1730
+ delete spawnEnv.SLOCK_AGENT_PROXY_URL;
1731
+ delete spawnEnv.SLOCK_AGENT_PROXY_TOKEN;
1732
+ delete spawnEnv.SLOCK_AGENT_PROXY_TOKEN_FILE;
1733
+ delete spawnEnv.SLOCK_AGENT_ACTIVE_CAPABILITIES;
1734
+ if (agentCredentialProxy) {
1735
+ delete spawnEnv.SLOCK_AGENT_TOKEN_FILE;
1736
+ }
1406
1737
  return {
1407
1738
  slockDir,
1408
1739
  tokenFile,
1740
+ agentCredentialProxyUrl: agentCredentialProxy?.proxyUrl ?? null,
1409
1741
  wrapperPath,
1410
1742
  spawnEnv
1411
1743
  };
@@ -1573,7 +1905,7 @@ function collectClaudeMcpConfigFiles(ctx, home) {
1573
1905
  return files;
1574
1906
  }
1575
1907
  function buildClaudeUserMcpServers(ctx, home) {
1576
- const servers = /* @__PURE__ */ Object.create(null);
1908
+ const servers2 = /* @__PURE__ */ Object.create(null);
1577
1909
  for (const configFile of collectClaudeMcpConfigFiles(ctx, home)) {
1578
1910
  const mcpServers = readClaudeMcpServers(configFile.path, configFile.vars);
1579
1911
  if (!mcpServers) continue;
@@ -1582,10 +1914,10 @@ function buildClaudeUserMcpServers(ctx, home) {
1582
1914
  logger.warn(`[Claude] ignoring invalid MCP server "${name}" in ${configFile.path}`);
1583
1915
  continue;
1584
1916
  }
1585
- servers[name] = server;
1917
+ servers2[name] = server;
1586
1918
  }
1587
1919
  }
1588
- return servers;
1920
+ return servers2;
1589
1921
  }
1590
1922
  var ClaudeDriver = class {
1591
1923
  id = "claude";
@@ -1633,9 +1965,9 @@ var ClaudeDriver = class {
1633
1965
  CLAUDE_DISALLOWED_TOOLS
1634
1966
  ];
1635
1967
  if (opts.standingPromptFilePath) {
1636
- args.push("--system-prompt-file", opts.standingPromptFilePath);
1968
+ args.push("--append-system-prompt-file", opts.standingPromptFilePath);
1637
1969
  } else {
1638
- args.push("--system-prompt", standingPrompt);
1970
+ args.push("--append-system-prompt", standingPrompt);
1639
1971
  }
1640
1972
  if (config.sessionId) {
1641
1973
  args.push("--resume", config.sessionId);
@@ -1883,13 +2215,29 @@ function resolveWindowsNpmCodexEntry(deps = {}) {
1883
2215
  }
1884
2216
  return null;
1885
2217
  }
2218
+ function resolveWindowsCodexDesktopEntry(deps = {}) {
2219
+ const existsSyncFn = deps.existsSyncFn ?? existsSync4;
2220
+ const env = deps.env ?? process.env;
2221
+ const homeDir = deps.homeDir;
2222
+ const winPath = path5.win32;
2223
+ const candidates = [
2224
+ env.LOCALAPPDATA ? winPath.join(env.LOCALAPPDATA, "OpenAI", "Codex", "bin", "codex.exe") : null,
2225
+ env.USERPROFILE ? winPath.join(env.USERPROFILE, "AppData", "Local", "OpenAI", "Codex", "bin", "codex.exe") : null,
2226
+ homeDir ? winPath.join(homeDir, "AppData", "Local", "OpenAI", "Codex", "bin", "codex.exe") : null
2227
+ ].filter((candidate) => Boolean(candidate));
2228
+ for (const candidate of candidates) {
2229
+ if (existsSyncFn(candidate)) return candidate;
2230
+ }
2231
+ return null;
2232
+ }
1886
2233
  function resolveCodexCommand(deps = {}) {
1887
2234
  const platform = deps.platform ?? process.platform;
1888
2235
  if (platform === "win32") {
1889
2236
  const npmEntry = resolveWindowsNpmCodexEntry(deps);
1890
2237
  if (npmEntry) return npmEntry;
1891
2238
  const command = resolveCommandOnPath("codex", deps);
1892
- return command && !isWindowsSandboxRunner(command) ? command : null;
2239
+ if (command && !isWindowsSandboxRunner(command)) return command;
2240
+ return resolveWindowsCodexDesktopEntry(deps);
1893
2241
  }
1894
2242
  const pathCommand = resolveCommandOnPath("codex", deps);
1895
2243
  if (pathCommand) return pathCommand;
@@ -1932,6 +2280,10 @@ function resolveCodexSpawn(commandArgs, deps = {}) {
1932
2280
  if (command && !isWindowsSandboxRunner(command)) {
1933
2281
  return { command, args: commandArgs };
1934
2282
  }
2283
+ const desktopEntry = resolveWindowsCodexDesktopEntry(deps);
2284
+ if (desktopEntry) {
2285
+ return { command: desktopEntry, args: commandArgs };
2286
+ }
1935
2287
  throw new Error(
1936
2288
  "Cannot resolve Codex CLI entry point on Windows. Install Codex Desktop or install @openai/codex globally via npm (npm i -g @openai/codex). Ignoring .codex/.sandbox-bin/codex-command-runner because it is a sandbox helper, not the Codex CLI."
1937
2289
  );
@@ -3297,6 +3649,18 @@ function isSupportedOpenCodeVersion(version) {
3297
3649
  }
3298
3650
  return true;
3299
3651
  }
3652
+ var AGENT_FLAG_CUTOFF_VERSION = "1.15.0";
3653
+ function requiresAgentCliFlag(version) {
3654
+ if (!version) return true;
3655
+ const actual = parseSemver(version);
3656
+ const cutoff = parseSemver(AGENT_FLAG_CUTOFF_VERSION);
3657
+ if (!actual || !cutoff) return true;
3658
+ for (let i = 0; i < 3; i += 1) {
3659
+ if (actual[i] > cutoff[i]) return false;
3660
+ if (actual[i] < cutoff[i]) return true;
3661
+ }
3662
+ return false;
3663
+ }
3300
3664
  function unsupportedOpenCodeVersionMessage(version) {
3301
3665
  if (!version || isSupportedOpenCodeVersion(version)) return null;
3302
3666
  return `OpenCode CLI ${version} is unsupported; requires OpenCode >= ${MIN_SUPPORTED_OPENCODE_VERSION}. Upgrade opencode before starting this runtime.`;
@@ -3344,7 +3708,7 @@ function buildOpenCodeConfig(ctx, home = os5.homedir()) {
3344
3708
  }
3345
3709
  };
3346
3710
  }
3347
- function buildOpenCodeLaunchOptions(ctx, home = os5.homedir()) {
3711
+ function buildOpenCodeLaunchOptions(ctx, home = os5.homedir(), version = null) {
3348
3712
  const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
3349
3713
  const config = buildOpenCodeConfig(ctx, home);
3350
3714
  const env = {
@@ -3363,7 +3727,9 @@ function buildOpenCodeLaunchOptions(ctx, home = os5.homedir()) {
3363
3727
  if (ctx.config.model && ctx.config.model !== "default") {
3364
3728
  args.push("--model", ctx.config.model);
3365
3729
  }
3366
- args.push("--agent", SLOCK_AGENT_NAME);
3730
+ if (requiresAgentCliFlag(version)) {
3731
+ args.push("--agent", SLOCK_AGENT_NAME);
3732
+ }
3367
3733
  if (ctx.config.sessionId) {
3368
3734
  args.push("--session", ctx.config.sessionId);
3369
3735
  }
@@ -3499,7 +3865,8 @@ var OpenCodeDriver = class {
3499
3865
  model: modelId
3500
3866
  }
3501
3867
  };
3502
- const launch = buildOpenCodeLaunchOptions(launchCtx, opts?.home);
3868
+ const version = readCommandVersion("opencode");
3869
+ const launch = buildOpenCodeLaunchOptions(launchCtx, opts?.home, version);
3503
3870
  return {
3504
3871
  args: launch.args,
3505
3872
  env: launch.env,
@@ -3536,11 +3903,12 @@ var OpenCodeDriver = class {
3536
3903
  spawn(ctx) {
3537
3904
  this.sessionId = ctx.config.sessionId || null;
3538
3905
  this.sessionAnnounced = false;
3539
- const unsupportedMessage = unsupportedOpenCodeVersionMessage(readCommandVersion("opencode"));
3906
+ const version = readCommandVersion("opencode");
3907
+ const unsupportedMessage = unsupportedOpenCodeVersionMessage(version);
3540
3908
  if (unsupportedMessage) {
3541
3909
  throw new Error(unsupportedMessage);
3542
3910
  }
3543
- const launch = buildOpenCodeLaunchOptions(ctx);
3911
+ const launch = buildOpenCodeLaunchOptions(ctx, os5.homedir(), version);
3544
3912
  const proc = spawn7("opencode", launch.args, {
3545
3913
  cwd: ctx.workingDirectory,
3546
3914
  stdio: ["pipe", "pipe", "pipe"],
@@ -3813,6 +4181,8 @@ function redactUrlQuery(value) {
3813
4181
  // src/agentProcessManager.ts
3814
4182
  var DEFAULT_MAX_CONCURRENT_AGENT_STARTS = 5;
3815
4183
  var DEFAULT_AGENT_START_INTERVAL_MS = 500;
4184
+ var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS = 3;
4185
+ var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS = 250;
3816
4186
  var WORKSPACE_TEXT_FILE_MAX_BYTES = 1048576;
3817
4187
  var WORKSPACE_IMAGE_PREVIEW_MAX_BYTES = 5 * 1024 * 1024;
3818
4188
  var WORKSPACE_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
@@ -3857,6 +4227,41 @@ function readNonNegativeIntegerEnv(name, fallback) {
3857
4227
  if (!Number.isFinite(parsed) || parsed < 0) return fallback;
3858
4228
  return Math.floor(parsed);
3859
4229
  }
4230
+ var RunnerCredentialMintError = class extends Error {
4231
+ code;
4232
+ retryable;
4233
+ status;
4234
+ constructor(message, opts) {
4235
+ super(message);
4236
+ this.name = "RunnerCredentialMintError";
4237
+ this.code = opts.code;
4238
+ this.retryable = opts.retryable ?? false;
4239
+ this.status = opts.status;
4240
+ }
4241
+ };
4242
+ function isRetryableMintHttpFailure(status, code) {
4243
+ if (code === "experimental_surface_disabled") return false;
4244
+ return status === 408 || status === 425 || status === 429 || status >= 500;
4245
+ }
4246
+ function runnerCredentialErrorDetail(error) {
4247
+ if (error instanceof RunnerCredentialMintError) {
4248
+ return {
4249
+ message: error.message,
4250
+ code: error.code,
4251
+ retryable: error.retryable,
4252
+ status: error.status
4253
+ };
4254
+ }
4255
+ const message = error instanceof Error ? error.message : String(error);
4256
+ return {
4257
+ message,
4258
+ code: "runner_credential_mint_network_error",
4259
+ retryable: true
4260
+ };
4261
+ }
4262
+ async function waitForRunnerCredentialRetry() {
4263
+ await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS));
4264
+ }
3860
4265
  function stalledRecoverySigtermTimeoutMs() {
3861
4266
  return readNonNegativeIntegerEnv(
3862
4267
  "SLOCK_DAEMON_STALLED_RECOVERY_SIGTERM_TIMEOUT_MS",
@@ -3885,6 +4290,22 @@ function formatMessageTarget(message) {
3885
4290
  }
3886
4291
  return `#${message.channel_name}`;
3887
4292
  }
4293
+ function formatVisibleMessageTarget(message) {
4294
+ if (message.channel_type === "thread" && message.parent_channel_name && message.channel_name) {
4295
+ const shortId = getMessageShortId(String(message.channel_name));
4296
+ if (message.parent_channel_type === "dm") {
4297
+ return `dm:@${message.parent_channel_name}:${shortId}`;
4298
+ }
4299
+ return `#${message.parent_channel_name}:${shortId}`;
4300
+ }
4301
+ if (message.channel_type === "dm" && message.channel_name) {
4302
+ return `dm:@${message.channel_name}`;
4303
+ }
4304
+ if (message.channel_name) {
4305
+ return `#${message.channel_name}`;
4306
+ }
4307
+ return null;
4308
+ }
3888
4309
  function getMessageShortId(messageId) {
3889
4310
  return messageId.startsWith("thread-") ? messageId.slice(7) : messageId.slice(0, 8);
3890
4311
  }
@@ -4505,6 +4926,14 @@ function createRuntimeTraceCounters() {
4505
4926
  thinkingEvents: 0
4506
4927
  };
4507
4928
  }
4929
+ function cleanupAgentCredentialProxy(agentId, launchId) {
4930
+ unregisterAgentCredentialProxyForLaunch({ agentId, launchId });
4931
+ }
4932
+ function stripManagedRunnerCredential(config) {
4933
+ if (!config.agentCredentialKey && !config.agentCredentialId) return config;
4934
+ const { agentCredentialKey: _agentCredentialKey, agentCredentialId: _agentCredentialId, ...rest } = config;
4935
+ return rest;
4936
+ }
4508
4937
  function createGatedSteeringState() {
4509
4938
  return {
4510
4939
  phase: "idle",
@@ -4836,6 +5265,7 @@ var AgentProcessManager = class _AgentProcessManager {
4836
5265
  tracer;
4837
5266
  deliveryTraceContexts = /* @__PURE__ */ new WeakMap();
4838
5267
  processExitTraceAttrs = /* @__PURE__ */ new WeakMap();
5268
+ agentVisibleBoundaries = /* @__PURE__ */ new Map();
4839
5269
  constructor(chatBridgePath, sendToServer, daemonApiKey, opts) {
4840
5270
  this.chatBridgePath = chatBridgePath;
4841
5271
  this.slockCliPath = opts.slockCliPath ?? "";
@@ -4863,6 +5293,97 @@ var AgentProcessManager = class _AgentProcessManager {
4863
5293
  setTracer(tracer) {
4864
5294
  this.tracer = tracer;
4865
5295
  }
5296
+ visibleBoundaryMap(agentId) {
5297
+ let map = this.agentVisibleBoundaries.get(agentId);
5298
+ if (!map) {
5299
+ map = /* @__PURE__ */ new Map();
5300
+ this.agentVisibleBoundaries.set(agentId, map);
5301
+ }
5302
+ return map;
5303
+ }
5304
+ getVisibleBoundary(agentId, target) {
5305
+ return this.agentVisibleBoundaries.get(agentId)?.get(target);
5306
+ }
5307
+ pendingVisibleMessages(agentId, target) {
5308
+ const collect = (messages) => (messages ?? []).filter((message) => formatMessageTarget(message) === target && typeof message.seq === "number" && message.seq > 0);
5309
+ return [
5310
+ ...collect(this.agents.get(agentId)?.inbox),
5311
+ ...collect(this.startingInboxes.get(agentId))
5312
+ ];
5313
+ }
5314
+ /**
5315
+ * Single-inbox consume point for messages that have been rendered into the
5316
+ * agent-visible input surface. Daemon pending inbox is only a runner cache:
5317
+ * once a message is visible through wake/stdin/check/read/held/preflight, the
5318
+ * same seq must not be injected later through the local pending queue.
5319
+ */
5320
+ consumeVisibleMessages(agentId, input) {
5321
+ if (input.messages.length === 0 && (!input.target || typeof input.boundarySeq !== "number")) return;
5322
+ const byTarget = /* @__PURE__ */ new Map();
5323
+ const ensureBucket = (target) => {
5324
+ let bucket = byTarget.get(target);
5325
+ if (!bucket) {
5326
+ bucket = { maxSeq: 0, boundarySeq: 0, seqs: /* @__PURE__ */ new Set(), ids: /* @__PURE__ */ new Set() };
5327
+ byTarget.set(target, bucket);
5328
+ }
5329
+ return bucket;
5330
+ };
5331
+ for (const message of input.messages) {
5332
+ const seq = Number(message.seq ?? 0);
5333
+ if (!Number.isFinite(seq) || seq <= 0) continue;
5334
+ const target = input.target ?? formatVisibleMessageTarget(message);
5335
+ if (!target) continue;
5336
+ const bucket = ensureBucket(target);
5337
+ bucket.maxSeq = Math.max(bucket.maxSeq, Math.floor(seq));
5338
+ bucket.seqs.add(Math.floor(seq));
5339
+ const id = typeof message.id === "string" ? message.id : typeof message.message_id === "string" ? message.message_id : null;
5340
+ if (id) bucket.ids.add(id);
5341
+ }
5342
+ if (input.target && typeof input.boundarySeq === "number" && Number.isFinite(input.boundarySeq) && input.boundarySeq > 0) {
5343
+ const bucket = ensureBucket(input.target);
5344
+ bucket.boundarySeq = Math.max(bucket.boundarySeq, Math.floor(input.boundarySeq));
5345
+ }
5346
+ if (byTarget.size === 0) return;
5347
+ const boundaryMap = this.visibleBoundaryMap(agentId);
5348
+ for (const [target, bucket] of byTarget) {
5349
+ const highWaterSeq = Math.max(bucket.maxSeq, bucket.boundarySeq);
5350
+ const previous = boundaryMap.get(target) ?? 0;
5351
+ boundaryMap.set(target, Math.max(previous, highWaterSeq));
5352
+ }
5353
+ const suppress = (messages) => {
5354
+ if (!messages || messages.length === 0) return 0;
5355
+ let removed = 0;
5356
+ const retained = messages.filter((message) => {
5357
+ const target = formatMessageTarget(message);
5358
+ const bucket = byTarget.get(target);
5359
+ if (!bucket) return true;
5360
+ const seq = typeof message.seq === "number" ? Math.floor(message.seq) : 0;
5361
+ const id = message.message_id ?? "";
5362
+ const matched = seq > 0 && bucket.boundarySeq > 0 && seq <= bucket.boundarySeq || seq > 0 && bucket.seqs.has(seq) || id.length > 0 && bucket.ids.has(id);
5363
+ if (matched) removed += 1;
5364
+ return !matched;
5365
+ });
5366
+ messages.splice(0, messages.length, ...retained);
5367
+ return removed;
5368
+ };
5369
+ const active = this.agents.get(agentId);
5370
+ const removedActive = suppress(active?.inbox);
5371
+ const removedStarting = suppress(this.startingInboxes.get(agentId));
5372
+ this.recordDaemonTrace("daemon.agent.inbox.visible_consumed", {
5373
+ agentId,
5374
+ source: input.source,
5375
+ targets: [...byTarget.keys()],
5376
+ messages_count: input.messages.length,
5377
+ suppressed_pending_count: removedActive + removedStarting
5378
+ });
5379
+ }
5380
+ createAgentProxyInboxCoordinator(agentId) {
5381
+ return {
5382
+ getBoundary: (target) => this.getVisibleBoundary(agentId, target),
5383
+ getPendingMessages: (target) => this.pendingVisibleMessages(agentId, target),
5384
+ consumeVisibleMessages: (input) => this.consumeVisibleMessages(agentId, input)
5385
+ };
5386
+ }
4866
5387
  recordDaemonTrace(name, attrs, status = "ok", parentTraceparent) {
4867
5388
  const span = this.tracer.startSpan(name, {
4868
5389
  parent: parseTraceparent(parentTraceparent),
@@ -5214,7 +5735,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5214
5735
  chatBridgePath: this.chatBridgePath,
5215
5736
  slockCliPath: this.slockCliPath,
5216
5737
  daemonApiKey: this.daemonApiKey,
5217
- launchId: launchId || null
5738
+ launchId: launchId || null,
5739
+ agentCredentialProxyInboxCoordinator: this.createAgentProxyInboxCoordinator(agentId)
5218
5740
  });
5219
5741
  this.recordDaemonTrace("daemon.agent.spawn.created", {
5220
5742
  ...this.startQueueTraceAttrs(agentId, effectiveConfig, wakeMessage, unreadSummary, resumePrompt, launchId),
@@ -5270,6 +5792,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5270
5792
  this.ackInjectedRuntimeProfileControl(agentId, config.runtimeProfileControl, agentProcess.launchId);
5271
5793
  }
5272
5794
  if (wakeMessage) {
5795
+ this.consumeVisibleMessages(agentId, { messages: [wakeMessage], source: "spawn_wake_message" });
5273
5796
  this.ackInjectedRuntimeProfileMessages(agentId, [wakeMessage], agentProcess.launchId);
5274
5797
  }
5275
5798
  let buffer = "";
@@ -5362,16 +5885,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5362
5885
  ...runtimeTraceCounterAttrs(ap),
5363
5886
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "process_exit")
5364
5887
  });
5365
- if (processEndedCleanly) {
5366
- this.finishCompactionIfActive(agentId, "Context compaction finished (inferred from process exit)");
5367
- } else {
5368
- this.clearCompactionWatchdog(ap);
5369
- }
5888
+ this.interruptCompactionIfActive(agentId);
5889
+ cleanupAgentCredentialProxy(agentId, ap.launchId);
5890
+ this.revokeManagedRunnerCredential(agentId, ap.config, ap.launchId);
5370
5891
  this.agents.delete(agentId);
5371
5892
  if (missingResumeSession) {
5372
5893
  const staleSessionId = ap.sessionId;
5373
5894
  const runtimeLabel = ap.driver.id === "opencode" ? "OpenCode" : "Claude";
5374
- const restartConfig = { ...ap.config, sessionId: null };
5895
+ const restartConfig = { ...stripManagedRunnerCredential(ap.config), sessionId: null };
5375
5896
  logger.warn(
5376
5897
  `[Agent ${agentId}] Stored ${runtimeLabel} session ${staleSessionId} is unavailable locally; falling back to cold start`
5377
5898
  );
@@ -5408,7 +5929,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5408
5929
  const unreadSummary2 = queuedWakeMessage ? buildUnreadSummary(ap.inbox, formatChannelLabel(queuedWakeMessage)) : void 0;
5409
5930
  if (queuedWakeMessage) {
5410
5931
  logger.info(`[Agent ${agentId}] Turn completed; restarting immediately for queued message`);
5411
- const nextConfig = { ...ap.config, sessionId: ap.sessionId };
5932
+ const nextConfig = { ...stripManagedRunnerCredential(ap.config), sessionId: ap.sessionId };
5412
5933
  this.idleAgentConfigs.set(agentId, {
5413
5934
  config: nextConfig,
5414
5935
  sessionId: ap.sessionId,
@@ -5418,6 +5939,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5418
5939
  this.idleAgentConfigs.delete(agentId);
5419
5940
  this.startAgent(agentId, nextConfig, queuedWakeMessage, unreadSummary2, void 0, ap.launchId || void 0).catch((err) => {
5420
5941
  logger.error(`[Agent ${agentId}] Failed to continue with queued message`, err);
5942
+ if (this.reportRunnerCredentialMintFailure(agentId, err, ap.launchId, "queued_continuation")) {
5943
+ return;
5944
+ }
5421
5945
  this.idleAgentConfigs.set(agentId, {
5422
5946
  config: nextConfig,
5423
5947
  sessionId: ap.sessionId,
@@ -5428,7 +5952,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5428
5952
  return;
5429
5953
  }
5430
5954
  this.idleAgentConfigs.set(agentId, {
5431
- config: { ...ap.config, sessionId: ap.sessionId },
5955
+ config: { ...stripManagedRunnerCredential(ap.config), sessionId: ap.sessionId },
5432
5956
  sessionId: ap.sessionId,
5433
5957
  launchId: ap.launchId
5434
5958
  });
@@ -5464,27 +5988,28 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5464
5988
  }
5465
5989
  async buildSpawnConfig(agentId, config) {
5466
5990
  const baseConfig = config.serverUrl === this.serverUrl ? config : { ...config, serverUrl: this.serverUrl };
5991
+ const runnerConfig = await this.ensureManagedRunnerCredential(agentId, baseConfig);
5467
5992
  if (!this.defaultAgentEnvVarsProvider) {
5468
- return baseConfig;
5993
+ return runnerConfig;
5469
5994
  }
5470
5995
  try {
5471
5996
  const defaultEnvVars = await this.defaultAgentEnvVarsProvider({
5472
- runtime: baseConfig.runtime,
5473
- model: baseConfig.model,
5474
- envVars: baseConfig.envVars
5997
+ runtime: runnerConfig.runtime,
5998
+ model: runnerConfig.model,
5999
+ envVars: runnerConfig.envVars
5475
6000
  });
5476
6001
  if (!defaultEnvVars || Object.keys(defaultEnvVars).length === 0) {
5477
- return baseConfig;
6002
+ return runnerConfig;
5478
6003
  }
5479
6004
  const mergedEnvVars = {
5480
6005
  ...defaultEnvVars,
5481
- ...baseConfig.envVars ?? {}
6006
+ ...runnerConfig.envVars ?? {}
5482
6007
  };
5483
- if (this.sameEnvVars(mergedEnvVars, baseConfig.envVars)) {
5484
- return baseConfig;
6008
+ if (this.sameEnvVars(mergedEnvVars, runnerConfig.envVars)) {
6009
+ return runnerConfig;
5485
6010
  }
5486
6011
  return {
5487
- ...baseConfig,
6012
+ ...runnerConfig,
5488
6013
  envVars: mergedEnvVars
5489
6014
  };
5490
6015
  } catch (error) {
@@ -5492,9 +6017,131 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5492
6017
  logger.warn(
5493
6018
  `[Agent ${agentId}] Failed to resolve default runtime env vars \u2014 continuing without machine-level defaults (${reason})`
5494
6019
  );
5495
- return baseConfig;
6020
+ return runnerConfig;
5496
6021
  }
5497
6022
  }
6023
+ async requestManagedRunnerCredentialOnce(agentId, config) {
6024
+ const url = new URL(`/internal/computer/runners/${encodeURIComponent(agentId)}/credentials`, this.serverUrl);
6025
+ const res = await fetch(url, {
6026
+ method: "POST",
6027
+ headers: {
6028
+ Authorization: `Bearer ${this.daemonApiKey}`,
6029
+ "Content-Type": "application/json",
6030
+ "X-Slock-Client": "daemon-server-session-worker"
6031
+ },
6032
+ body: JSON.stringify({
6033
+ scopes: ["send", "read", "mentions", "tasks", "reactions", "server", "channels"],
6034
+ name: `runner:${config.runtime}:${agentId.slice(0, 8)}`
6035
+ })
6036
+ });
6037
+ if (!res.ok) {
6038
+ const contentType = res.headers.get("content-type") ?? "";
6039
+ let detail = `HTTP ${res.status}`;
6040
+ let code = null;
6041
+ if (contentType.includes("application/json")) {
6042
+ const body2 = await res.json().catch(() => null);
6043
+ const error = typeof body2?.error === "string" ? body2.error : null;
6044
+ code = typeof body2?.code === "string" ? body2.code : null;
6045
+ detail = [detail, code, error].filter(Boolean).join(" ");
6046
+ }
6047
+ throw new RunnerCredentialMintError(detail, {
6048
+ code: code ?? "runner_credential_mint_http_error",
6049
+ retryable: isRetryableMintHttpFailure(res.status, code),
6050
+ status: res.status
6051
+ });
6052
+ }
6053
+ const body = await res.json().catch(() => null);
6054
+ if (typeof body?.apiKey !== "string" || !body.apiKey.startsWith("sk_agent_")) {
6055
+ throw new RunnerCredentialMintError("invalid_agent_credential_payload", {
6056
+ code: "invalid_agent_credential_payload"
6057
+ });
6058
+ }
6059
+ return {
6060
+ apiKey: body.apiKey,
6061
+ credentialId: typeof body.credentialId === "string" ? body.credentialId : null
6062
+ };
6063
+ }
6064
+ async ensureManagedRunnerCredential(agentId, config) {
6065
+ if (config.agentCredentialKey) return config;
6066
+ if (process.env.SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED === "1") {
6067
+ throw new RunnerCredentialMintError("runner credential mint is disabled by SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED", {
6068
+ code: "runner_credentials_disabled"
6069
+ });
6070
+ }
6071
+ let lastError = null;
6072
+ for (let attempt = 1; attempt <= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS; attempt += 1) {
6073
+ try {
6074
+ const credential = await this.requestManagedRunnerCredentialOnce(agentId, config);
6075
+ return {
6076
+ ...config,
6077
+ agentCredentialKey: credential.apiKey,
6078
+ agentCredentialId: credential.credentialId
6079
+ };
6080
+ } catch (err) {
6081
+ lastError = err;
6082
+ const detail2 = runnerCredentialErrorDetail(err);
6083
+ this.recordDaemonTrace("daemon.runner_credential_mint.retry", {
6084
+ agentId,
6085
+ runtime: config.runtime,
6086
+ attempt,
6087
+ max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS,
6088
+ status: detail2.status,
6089
+ code: detail2.code,
6090
+ reason: detail2.message,
6091
+ retryable: detail2.retryable
6092
+ }, detail2.retryable && attempt < RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS ? "ok" : "error");
6093
+ if (!detail2.retryable || attempt >= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS) break;
6094
+ await waitForRunnerCredentialRetry();
6095
+ }
6096
+ }
6097
+ const detail = runnerCredentialErrorDetail(lastError);
6098
+ this.recordDaemonTrace("daemon.runner_credential_mint.failed", {
6099
+ agentId,
6100
+ runtime: config.runtime,
6101
+ status: detail.status,
6102
+ code: detail.code,
6103
+ reason: detail.message,
6104
+ retryable: detail.retryable,
6105
+ max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS
6106
+ }, "error");
6107
+ throw new RunnerCredentialMintError(
6108
+ `runner_credential_mint_failed: ${detail.message}. Managed runner startup requires /internal/computer credential mint; deploy server first or roll back the daemon binary.`,
6109
+ {
6110
+ code: detail.code,
6111
+ retryable: detail.retryable,
6112
+ status: detail.status
6113
+ }
6114
+ );
6115
+ }
6116
+ revokeManagedRunnerCredential(agentId, config, launchId) {
6117
+ const credentialId = config.agentCredentialId;
6118
+ if (!credentialId) return;
6119
+ const url = new URL(
6120
+ `/internal/computer/runners/${encodeURIComponent(agentId)}/credentials/${encodeURIComponent(credentialId)}`,
6121
+ this.serverUrl
6122
+ );
6123
+ void fetch(url, {
6124
+ method: "DELETE",
6125
+ headers: {
6126
+ Authorization: `Bearer ${this.daemonApiKey}`,
6127
+ "X-Slock-Client": "daemon-server-session-worker"
6128
+ }
6129
+ }).then((res) => {
6130
+ this.recordDaemonTrace("daemon.runner_credential.revoke", {
6131
+ agentId,
6132
+ launchId: launchId || void 0,
6133
+ credentialId,
6134
+ status: res.status
6135
+ }, res.ok ? "ok" : "error");
6136
+ }).catch((err) => {
6137
+ this.recordDaemonTrace("daemon.runner_credential.revoke", {
6138
+ agentId,
6139
+ launchId: launchId || void 0,
6140
+ credentialId,
6141
+ reason: err instanceof Error ? err.message : String(err)
6142
+ }, "error");
6143
+ });
6144
+ }
5498
6145
  sameEnvVars(left, right) {
5499
6146
  const leftKeys = Object.keys(left ?? {});
5500
6147
  const rightKeys = Object.keys(right ?? {});
@@ -5583,6 +6230,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5583
6230
  }
5584
6231
  this.clearCompactionWatchdog(ap);
5585
6232
  this.clearStalledRecoverySigtermWatchdog(ap);
6233
+ cleanupAgentCredentialProxy(agentId, ap.launchId);
6234
+ this.revokeManagedRunnerCredential(agentId, ap.config, ap.launchId);
5586
6235
  this.agents.delete(agentId);
5587
6236
  this.processExitTraceAttrs.set(ap.process, {
5588
6237
  stop_source: silent ? "daemon_internal" : "explicit_request",
@@ -5666,10 +6315,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5666
6315
  session_id_present: Boolean(cached.sessionId),
5667
6316
  launchId: cached.launchId || void 0
5668
6317
  }));
5669
- this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).catch((err) => {
6318
+ return this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).then(() => true, (err) => {
5670
6319
  logger.error(`[Agent ${agentId}] Failed to auto-restart`, err);
6320
+ if (this.reportRunnerCredentialMintFailure(agentId, err, cached.launchId, "idle_auto_restart")) {
6321
+ return false;
6322
+ }
6323
+ this.idleAgentConfigs.set(agentId, cached);
6324
+ return false;
5671
6325
  });
5672
- return true;
5673
6326
  }
5674
6327
  logger.warn(`[Agent ${agentId}] Delivery received but no running process or cached idle config exists`);
5675
6328
  this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
@@ -5751,6 +6404,37 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5751
6404
  }));
5752
6405
  return true;
5753
6406
  }
6407
+ if (ap.gatedSteering.compacting) {
6408
+ ap.pendingNotificationCount++;
6409
+ if (ap.notificationTimer) {
6410
+ clearTimeout(ap.notificationTimer);
6411
+ ap.notificationTimer = null;
6412
+ }
6413
+ if (ap.driver.busyDeliveryMode === "gated") {
6414
+ this.recordGatedSteeringEvent(agentId, ap, "buffer", {
6415
+ reason: "compaction_boundary",
6416
+ pendingMessages: ap.inbox.length
6417
+ });
6418
+ }
6419
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime.compaction_boundary.delivery_buffered", {
6420
+ pendingNotificationCount: ap.pendingNotificationCount,
6421
+ pendingMessages: ap.inbox.length,
6422
+ busyDeliveryMode: ap.driver.busyDeliveryMode
6423
+ });
6424
+ this.recordDaemonTrace("daemon.agent.delivery.routed", this.deliveryTraceAttrs(agentId, message, {
6425
+ outcome: "queued_compaction_boundary",
6426
+ accepted: true,
6427
+ process_present: true,
6428
+ runtime: ap.config.runtime,
6429
+ session_id_present: true,
6430
+ launchId: ap.launchId || void 0,
6431
+ inbox_count: ap.inbox.length,
6432
+ pending_notification_count: ap.pendingNotificationCount,
6433
+ busy_delivery_mode: ap.driver.busyDeliveryMode,
6434
+ notification_timer_present: false
6435
+ }));
6436
+ return true;
6437
+ }
5754
6438
  if (ap.driver.busyDeliveryMode === "gated") {
5755
6439
  ap.pendingNotificationCount++;
5756
6440
  this.recordGatedSteeringEvent(agentId, ap, "buffer", {
@@ -5957,19 +6641,28 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5957
6641
  if (cached) {
5958
6642
  logger.info(`[Agent ${agentId}] Starting from idle state for runtime profile ${kind} ${key}`);
5959
6643
  this.idleAgentConfigs.delete(agentId);
5960
- this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).catch((err) => {
6644
+ return this.startAgent(agentId, cached.config, message, void 0, void 0, cached.launchId || void 0).then(() => true, (err) => {
5961
6645
  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)
6646
+ if (this.reportRunnerCredentialMintFailure(agentId, err, cached.launchId, "runtime_profile_auto_restart")) {
6647
+ span.end("error", {
6648
+ attrs: {
6649
+ outcome: "runner_credential_mint_failed",
6650
+ runtime: cached.config.runtime,
6651
+ launchId: cached.launchId || void 0
6652
+ }
6653
+ });
6654
+ return false;
5970
6655
  }
6656
+ this.idleAgentConfigs.set(agentId, cached);
6657
+ span.end("error", {
6658
+ attrs: {
6659
+ outcome: "restart_failed",
6660
+ runtime: cached.config.runtime,
6661
+ launchId: cached.launchId || void 0
6662
+ }
6663
+ });
6664
+ return false;
5971
6665
  });
5972
- return true;
5973
6666
  }
5974
6667
  logger.warn(`[Agent ${agentId}] Runtime profile ${kind} ${key} has no runtime injection path yet; leaving unacked for retry`);
5975
6668
  span.end("ok", { attrs: { outcome: "no_path" } });
@@ -6419,11 +7112,23 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6419
7112
  ap.compactionWatchdog = null;
6420
7113
  this.broadcastActivity(agentId, "working", "Context compaction still running; no finish event observed");
6421
7114
  }
6422
- finishCompactionIfActive(agentId, detail = "Context compaction finished") {
7115
+ completeCompactionIfActive(agentId, detail = "Context compaction finished", options = {}) {
6423
7116
  const ap = this.agents.get(agentId);
6424
7117
  if (!ap || !ap.compactionStartedAt) return;
6425
7118
  this.clearCompactionWatchdog(ap);
6426
7119
  this.broadcastActivity(agentId, "working", detail, [{ kind: "compaction_finished" }]);
7120
+ ap.gatedSteering.compacting = false;
7121
+ this.setGatedSteeringPhase(agentId, ap, "assistant_continuation", { event: "compaction_finished", inferred: true });
7122
+ if (options.flushBoundaryMessages ?? true) {
7123
+ this.flushCompactionBoundaryMessages(agentId, ap);
7124
+ }
7125
+ ap.isIdle = false;
7126
+ }
7127
+ interruptCompactionIfActive(agentId) {
7128
+ const ap = this.agents.get(agentId);
7129
+ if (!ap || !ap.compactionStartedAt && !ap.gatedSteering.compacting) return;
7130
+ this.clearCompactionWatchdog(ap);
7131
+ ap.gatedSteering.compacting = false;
6427
7132
  }
6428
7133
  messagesTraceAttrs(messages) {
6429
7134
  if (!messages || messages.length === 0) return {};
@@ -6746,28 +7451,28 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6746
7451
  this.sendRuntimeProfileReport(agentId);
6747
7452
  break;
6748
7453
  case "thinking": {
6749
- this.finishCompactionIfActive(agentId, "Context compaction finished (inferred from resumed output)");
7454
+ if (ap) this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
7455
+ this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from resumed output)");
6750
7456
  this.queueTrajectoryText(agentId, "thinking", event.text);
6751
7457
  if (ap) ap.isIdle = false;
6752
- if (ap) this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
6753
7458
  this.setGatedSteeringPhase(agentId, ap, "assistant_continuation", { event: "thinking" });
6754
7459
  break;
6755
7460
  }
6756
7461
  case "text": {
6757
- this.finishCompactionIfActive(agentId, "Context compaction finished (inferred from resumed output)");
7462
+ if (ap) this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
7463
+ this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from resumed output)");
6758
7464
  this.queueTrajectoryText(agentId, "text", event.text);
6759
7465
  if (ap) ap.isIdle = false;
6760
- if (ap) this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
6761
7466
  this.setGatedSteeringPhase(agentId, ap, "assistant_continuation", { event: "text" });
6762
7467
  break;
6763
7468
  }
6764
7469
  case "tool_call": {
6765
- this.finishCompactionIfActive(agentId, "Context compaction finished (inferred from resumed tool use)");
7470
+ if (ap) this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
7471
+ this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from resumed tool use)");
6766
7472
  this.flushPendingTrajectory(agentId);
6767
7473
  const invocation = normalizeToolDisplayInvocation(event.name, event.input);
6768
7474
  if (ap) {
6769
7475
  ap.gatedSteering.outstandingToolUses++;
6770
- this.clearGatedInFlightBatch(agentId, ap, "non_error_progress");
6771
7476
  this.noteRuntimeProfileToolCall(agentId, ap, invocation.toolName);
6772
7477
  this.recordRuntimeTraceEvent(agentId, ap, "tool.call.started", { tool: invocation.toolName });
6773
7478
  this.setGatedSteeringPhase(agentId, ap, "tool_wait", {
@@ -6828,7 +7533,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6828
7533
  break;
6829
7534
  case "turn_end":
6830
7535
  if (ap) this.recordRuntimeTraceEvent(agentId, ap, "runtime.turn.completed");
6831
- this.finishCompactionIfActive(agentId, "Context compaction finished (inferred from turn end)");
7536
+ this.completeCompactionIfActive(agentId, "Context compaction finished (inferred from turn end)", {
7537
+ flushBoundaryMessages: false
7538
+ });
6832
7539
  this.flushPendingTrajectory(agentId);
6833
7540
  if (ap) {
6834
7541
  this.clearGatedInFlightBatch(agentId, ap, "turn_end");
@@ -6885,7 +7592,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6885
7592
  }
6886
7593
  break;
6887
7594
  case "error": {
6888
- this.finishCompactionIfActive(agentId, "Context compaction interrupted by runtime error");
7595
+ this.interruptCompactionIfActive(agentId);
6889
7596
  this.flushPendingTrajectory(agentId);
6890
7597
  if (ap) ap.lastRuntimeError = event.message;
6891
7598
  if (ap) {
@@ -6933,6 +7640,29 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6933
7640
  sendAgentStatus(agentId, status, launchId) {
6934
7641
  this.sendToServer({ type: "agent:status", agentId, status, launchId: launchId || void 0 });
6935
7642
  }
7643
+ reportRunnerCredentialMintFailure(agentId, err, launchId, source) {
7644
+ if (!(err instanceof RunnerCredentialMintError)) return false;
7645
+ const detail = runnerCredentialErrorDetail(err);
7646
+ this.recordDaemonTrace("daemon.runner_credential_mint.hard_fail", {
7647
+ agentId,
7648
+ launchId: launchId || void 0,
7649
+ source,
7650
+ status: detail.status,
7651
+ code: detail.code,
7652
+ reason: detail.message,
7653
+ retryable: detail.retryable
7654
+ }, "error");
7655
+ const reason = err.message;
7656
+ this.sendAgentStatus(agentId, "inactive", launchId);
7657
+ this.broadcastActivity(
7658
+ agentId,
7659
+ "error",
7660
+ `Start failed: ${reason}`,
7661
+ [{ kind: "text", text: `Error: ${reason}` }],
7662
+ launchId
7663
+ );
7664
+ return true;
7665
+ }
6936
7666
  noteRuntimeTraceCounter(ap, event) {
6937
7667
  ap.runtimeTraceCounters.events++;
6938
7668
  switch (event.kind) {
@@ -7095,6 +7825,7 @@ ${RESPONSE_TARGET_HINT}`);
7095
7825
  }, "error");
7096
7826
  return false;
7097
7827
  }
7828
+ this.consumeVisibleMessages(agentId, { messages, source: `stdin_${mode}_delivery` });
7098
7829
  const senders = [...new Set(messages.map((message) => `@${message.sender_name}`))].join(", ");
7099
7830
  logger.info(
7100
7831
  `[Agent ${agentId}] Delivering ${mode} ${messages.length === 1 ? "message" : `${messages.length} messages`} via stdin from ${senders}`
@@ -7432,7 +8163,7 @@ var ReminderCache = class {
7432
8163
 
7433
8164
  // src/machineLock.ts
7434
8165
  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";
8166
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
7436
8167
  import os7 from "os";
7437
8168
  import path13 from "path";
7438
8169
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
@@ -7505,7 +8236,7 @@ function acquireDaemonMachineLock(options) {
7505
8236
  writeFileSync8(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
7506
8237
  `, { mode: 384 });
7507
8238
  } catch (err) {
7508
- rmSync2(lockDir, { recursive: true, force: true });
8239
+ rmSync3(lockDir, { recursive: true, force: true });
7509
8240
  throw err;
7510
8241
  }
7511
8242
  return {
@@ -7515,7 +8246,7 @@ function acquireDaemonMachineLock(options) {
7515
8246
  release: () => {
7516
8247
  const currentOwner = readOwner(lockDir);
7517
8248
  if (currentOwner?.pid === process.pid && currentOwner.token === token) {
7518
- rmSync2(lockDir, { recursive: true, force: true });
8249
+ rmSync3(lockDir, { recursive: true, force: true });
7519
8250
  }
7520
8251
  }
7521
8252
  };
@@ -7532,14 +8263,14 @@ function acquireDaemonMachineLock(options) {
7532
8263
  throw new DaemonMachineLockConflictError(lockDir, null);
7533
8264
  }
7534
8265
  }
7535
- rmSync2(lockDir, { recursive: true, force: true });
8266
+ rmSync3(lockDir, { recursive: true, force: true });
7536
8267
  }
7537
8268
  }
7538
8269
  throw new DaemonMachineLockConflictError(lockDir, readOwner(lockDir));
7539
8270
  }
7540
8271
 
7541
8272
  // src/localTraceSink.ts
7542
- import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
8273
+ import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
7543
8274
  import path14 from "path";
7544
8275
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
7545
8276
  var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
@@ -7621,7 +8352,7 @@ var LocalRotatingTraceSink = class {
7621
8352
  const excess = files.length - this.maxFiles;
7622
8353
  if (excess <= 0) return;
7623
8354
  for (const file of files.slice(0, excess)) {
7624
- rmSync3(path14.join(this.traceDir, file), { force: true });
8355
+ rmSync4(path14.join(this.traceDir, file), { force: true });
7625
8356
  }
7626
8357
  }
7627
8358
  };
@@ -8052,7 +8783,45 @@ function readPositiveIntegerEnv2(name, fallback) {
8052
8783
 
8053
8784
  // src/core.ts
8054
8785
  var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
8786
+ var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels"];
8787
+ var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
8788
+ var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
8055
8789
  var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
8790
+ var RunnerCredentialMintError2 = class extends Error {
8791
+ code;
8792
+ retryable;
8793
+ status;
8794
+ constructor(message, opts) {
8795
+ super(message);
8796
+ this.name = "RunnerCredentialMintError";
8797
+ this.code = opts.code;
8798
+ this.retryable = opts.retryable ?? false;
8799
+ this.status = opts.status;
8800
+ }
8801
+ };
8802
+ function isRetryableMintHttpFailure2(status, code) {
8803
+ if (code === "experimental_surface_disabled") return false;
8804
+ return status === 408 || status === 425 || status === 429 || status >= 500;
8805
+ }
8806
+ function runnerCredentialErrorDetail2(error) {
8807
+ if (error instanceof RunnerCredentialMintError2) {
8808
+ return {
8809
+ message: error.message,
8810
+ code: error.code,
8811
+ retryable: error.retryable,
8812
+ status: error.status
8813
+ };
8814
+ }
8815
+ const message = error instanceof Error ? error.message : String(error);
8816
+ return {
8817
+ message,
8818
+ code: "runner_credential_mint_network_error",
8819
+ retryable: true
8820
+ };
8821
+ }
8822
+ async function waitForRunnerCredentialRetry2() {
8823
+ await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
8824
+ }
8056
8825
  function parseDaemonCliArgs(args) {
8057
8826
  let serverUrl = "";
8058
8827
  let apiKey = "";
@@ -8384,13 +9153,115 @@ var DaemonCore = class {
8384
9153
  });
8385
9154
  span.end(status);
8386
9155
  }
9156
+ async requestRunnerCredentialOnce(agentId, config) {
9157
+ const url = new URL(`/internal/computer/runners/${encodeURIComponent(agentId)}/credentials`, this.options.serverUrl);
9158
+ const res = await fetch(url, {
9159
+ method: "POST",
9160
+ headers: {
9161
+ "Authorization": `Bearer ${this.options.apiKey}`,
9162
+ "Content-Type": "application/json",
9163
+ "X-Slock-Client": "daemon-server-session-worker"
9164
+ },
9165
+ body: JSON.stringify({
9166
+ scopes: RUNNER_CREDENTIAL_SCOPES,
9167
+ name: `runner:${config.runtime}:${agentId.slice(0, 8)}`
9168
+ })
9169
+ });
9170
+ if (!res.ok) {
9171
+ const contentType = res.headers.get("content-type") ?? "";
9172
+ let detail = `HTTP ${res.status}`;
9173
+ let code = null;
9174
+ if (contentType.includes("application/json")) {
9175
+ const body2 = await res.json().catch(() => null);
9176
+ const error = typeof body2?.error === "string" ? body2.error : null;
9177
+ code = typeof body2?.code === "string" ? body2.code : null;
9178
+ detail = [detail, code, error].filter(Boolean).join(" ");
9179
+ }
9180
+ throw new RunnerCredentialMintError2(detail, {
9181
+ code: code ?? "runner_credential_mint_http_error",
9182
+ retryable: isRetryableMintHttpFailure2(res.status, code),
9183
+ status: res.status
9184
+ });
9185
+ }
9186
+ const body = await res.json().catch(() => null);
9187
+ if (typeof body?.apiKey !== "string" || !body.apiKey.startsWith("sk_agent_")) {
9188
+ throw new RunnerCredentialMintError2("invalid_agent_credential_payload", {
9189
+ code: "invalid_agent_credential_payload"
9190
+ });
9191
+ }
9192
+ return {
9193
+ apiKey: body.apiKey,
9194
+ credentialId: typeof body.credentialId === "string" ? body.credentialId : null
9195
+ };
9196
+ }
9197
+ async mintRunnerCredential(agentId, config) {
9198
+ if (config.agentCredentialKey) {
9199
+ return { apiKey: config.agentCredentialKey, credentialId: config.agentCredentialId ?? null };
9200
+ }
9201
+ if (process.env.SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED === "1") {
9202
+ throw new RunnerCredentialMintError2("runner credential mint is disabled by SLOCK_AGENT_RUNNER_CREDENTIALS_DISABLED", {
9203
+ code: "runner_credentials_disabled"
9204
+ });
9205
+ }
9206
+ let lastError = null;
9207
+ for (let attempt = 1; attempt <= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2; attempt += 1) {
9208
+ try {
9209
+ return await this.requestRunnerCredentialOnce(agentId, config);
9210
+ } catch (err) {
9211
+ lastError = err;
9212
+ const detail2 = runnerCredentialErrorDetail2(err);
9213
+ this.recordDaemonTrace("daemon.runner_credential_mint.retry", {
9214
+ agentId,
9215
+ runtime: config.runtime,
9216
+ attempt,
9217
+ max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2,
9218
+ status: detail2.status,
9219
+ code: detail2.code,
9220
+ reason: detail2.message,
9221
+ retryable: detail2.retryable
9222
+ }, detail2.retryable && attempt < RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 ? "ok" : "error");
9223
+ if (!detail2.retryable || attempt >= RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2) break;
9224
+ await waitForRunnerCredentialRetry2();
9225
+ }
9226
+ }
9227
+ const detail = runnerCredentialErrorDetail2(lastError);
9228
+ this.recordDaemonTrace("daemon.runner_credential_mint.failed", {
9229
+ agentId,
9230
+ runtime: config.runtime,
9231
+ status: detail.status,
9232
+ code: detail.code,
9233
+ reason: detail.message,
9234
+ retryable: detail.retryable,
9235
+ max_attempts: RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2
9236
+ }, "error");
9237
+ throw new RunnerCredentialMintError2(
9238
+ `runner_credential_mint_failed: ${detail.message}. Managed runner startup requires /internal/computer credential mint; deploy server first or roll back the daemon binary.`,
9239
+ {
9240
+ code: detail.code,
9241
+ retryable: detail.retryable,
9242
+ status: detail.status
9243
+ }
9244
+ );
9245
+ }
9246
+ async startAgentFromMessage(msg) {
9247
+ const agentCredential = await this.mintRunnerCredential(msg.agentId, msg.config);
9248
+ const config = { ...msg.config, agentCredentialKey: agentCredential.apiKey, agentCredentialId: agentCredential.credentialId };
9249
+ await this.agentManager.startAgent(
9250
+ msg.agentId,
9251
+ config,
9252
+ msg.wakeMessage,
9253
+ msg.unreadSummary,
9254
+ msg.resumePrompt,
9255
+ msg.launchId
9256
+ );
9257
+ }
8387
9258
  handleMessage(msg) {
8388
9259
  const summary = summarizeIncomingMessage(msg);
8389
9260
  logger.info(`[Daemon] Received ${msg.type}${summary ? ` ${summary}` : ""}`);
8390
9261
  switch (msg.type) {
8391
9262
  case "agent:start":
8392
9263
  logger.info(`[Agent ${msg.agentId}] Start requested (runtime=${msg.config.runtime}, model=${msg.config.model}, session=${msg.config.sessionId || "new"}${msg.wakeMessage ? ", wake=true" : ""})`);
8393
- this.agentManager.startAgent(msg.agentId, msg.config, msg.wakeMessage, msg.unreadSummary, msg.resumePrompt, msg.launchId).catch((err) => {
9264
+ this.startAgentFromMessage(msg).catch((err) => {
8394
9265
  const reason = err instanceof Error ? err.message : String(err);
8395
9266
  logger.error(`[Agent ${msg.agentId}] Start failed (${reason})`);
8396
9267
  this.connection.send({ type: "agent:status", agentId: msg.agentId, status: "inactive", launchId: msg.launchId });
@@ -8423,22 +9294,27 @@ var DaemonCore = class {
8423
9294
  logger.info(`[Agent ${msg.agentId}] Delivery received (seq=${msg.seq}, from=@${msg.message.sender_name}, target=${formatChannelTarget(msg)})`);
8424
9295
  try {
8425
9296
  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
9297
+ const acceptedOrPromise = this.agentManager.deliverMessage(msg.agentId, msg.message, { deliveryId: msg.deliveryId });
9298
+ Promise.resolve(acceptedOrPromise).then((accepted) => {
9299
+ span.addEvent("daemon.deliver_to_agent_manager", { accepted });
9300
+ if (!accepted) {
9301
+ span.end("ok", { attrs: { outcome: "not-accepted" } });
9302
+ return;
9303
+ }
9304
+ const ackSeq = msg.seq > 0 ? msg.seq : msg.message.seq ?? 0;
9305
+ span.addEvent("daemon.ack.sent", { seq: ackSeq });
9306
+ this.connection.send({
9307
+ type: "agent:deliver:ack",
9308
+ agentId: msg.agentId,
9309
+ seq: ackSeq,
9310
+ traceparent: formatTraceparent(span.context),
9311
+ deliveryId: msg.deliveryId
9312
+ });
9313
+ span.end("ok", { attrs: { outcome: "ack-sent", ackSeq, deliveryId: msg.deliveryId } });
9314
+ }, (err) => {
9315
+ logger.error(`[Agent ${msg.agentId}] Delivery handling failed`, err);
9316
+ span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
8440
9317
  });
8441
- span.end("ok", { attrs: { outcome: "ack-sent", ackSeq, deliveryId: msg.deliveryId } });
8442
9318
  } catch (err) {
8443
9319
  span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
8444
9320
  throw err;
@@ -8458,8 +9334,14 @@ var DaemonCore = class {
8458
9334
  }
8459
9335
  });
8460
9336
  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" } });
9337
+ Promise.resolve(
9338
+ this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.migrationKey, "migration", msg.message, formatTraceparent(span.context))
9339
+ ).then((accepted) => {
9340
+ span.end("ok", { attrs: { outcome: accepted ? "accepted" : "no_injection_path" } });
9341
+ }, (err) => {
9342
+ logger.error(`[Agent ${msg.agentId}] Runtime profile migration handling failed`, err);
9343
+ span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
9344
+ });
8463
9345
  break;
8464
9346
  }
8465
9347
  case "agent:runtime_profile:daemon_release_notice": {
@@ -8475,8 +9357,14 @@ var DaemonCore = class {
8475
9357
  }
8476
9358
  });
8477
9359
  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" } });
9360
+ Promise.resolve(
9361
+ this.agentManager.deliverRuntimeProfileNotification(msg.agentId, msg.noticeKey, "daemon_release_notice", msg.message, formatTraceparent(span.context))
9362
+ ).then((accepted) => {
9363
+ span.end("ok", { attrs: { outcome: accepted ? "accepted" : "no_injection_path" } });
9364
+ }, (err) => {
9365
+ logger.error(`[Agent ${msg.agentId}] Runtime profile daemon release notice handling failed`, err);
9366
+ span.end("error", { attrs: { error_class: err instanceof Error ? err.name : typeof err } });
9367
+ });
8480
9368
  break;
8481
9369
  }
8482
9370
  case "agent:workspace:list":