@slock-ai/daemon 0.55.2 → 0.55.3-play.20260602082442

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.
@@ -8,11 +8,11 @@ import {
8
8
  } from "./chunk-M2KQBJR3.js";
9
9
 
10
10
  // src/core.ts
11
- import path16 from "path";
11
+ import path17 from "path";
12
12
  import os7 from "os";
13
13
  import { createRequire as createRequire2 } from "module";
14
14
  import { accessSync } from "fs";
15
- import { fileURLToPath } from "url";
15
+ import { fileURLToPath as fileURLToPath2 } from "url";
16
16
 
17
17
  // ../shared/src/slockRefs.ts
18
18
  var SLOCK_REF_CHANNEL_NAME_PATTERN = String.raw`[\p{L}\p{N}_-]+`;
@@ -990,6 +990,7 @@ var RUNTIME_CONFIG_VERSION = 1;
990
990
  var RUNTIMES = [
991
991
  { id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
992
992
  { id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
993
+ { id: "pi", displayName: "Pi", binary: "pi", supported: true },
993
994
  { id: "antigravity", displayName: "Antigravity CLI", binary: "agy", supported: true },
994
995
  { id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true },
995
996
  { id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
@@ -999,9 +1000,14 @@ var RUNTIMES = [
999
1000
  ];
1000
1001
  var RUNTIME_MODELS = {
1001
1002
  claude: [
1002
- { id: "opus", label: "Opus" },
1003
- { id: "sonnet", label: "Sonnet" },
1004
- { id: "haiku", label: "Haiku" }
1003
+ { id: "opus", label: "Claude Opus" },
1004
+ { id: "claude-opus-4-8", label: "Claude Opus 4.8" },
1005
+ { id: "claude-opus-4-7", label: "Claude Opus 4.7" },
1006
+ { id: "claude-opus-4-6", label: "Claude Opus 4.6" },
1007
+ { id: "sonnet", label: "Claude Sonnet" },
1008
+ { id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6" },
1009
+ { id: "haiku", label: "Claude Haiku" },
1010
+ { id: "claude-haiku-4-5", label: "Claude Haiku 4.5" }
1005
1011
  ],
1006
1012
  codex: [
1007
1013
  { id: "gpt-5.5", label: "GPT-5.5" },
@@ -1042,6 +1048,13 @@ var RUNTIME_MODELS = {
1042
1048
  { id: "openrouter/anthropic/claude-opus-4.5", label: "Claude Opus 4.5 via OpenRouter", verified: "suggestion_only" },
1043
1049
  { id: "fusecode/opus[1m]", label: "Opus 1M via FuseCode", verified: "suggestion_only" }
1044
1050
  ],
1051
+ pi: [
1052
+ { id: "deepseek/deepseek-v4-pro", label: "DeepSeek V4 Pro" },
1053
+ { id: "deepseek/deepseek-v4-flash", label: "DeepSeek V4 Flash" },
1054
+ { id: "kimi-coding/kimi-for-coding", label: "Kimi for Coding" },
1055
+ { id: "zai/glm-5.1", label: "GLM-5.1" },
1056
+ { id: "zai/glm-4.7", label: "GLM-4.7" }
1057
+ ],
1045
1058
  // Kimi CLI resolves model keys from each user's local config, so the safest
1046
1059
  // built-in option is to defer to whatever default model the CLI already uses.
1047
1060
  kimi: [
@@ -1086,22 +1099,28 @@ function isPresetRuntimeModel(runtime, model) {
1086
1099
  function modelConfigFromLegacy(runtime, model) {
1087
1100
  return isPresetRuntimeModel(runtime, model) ? { kind: "preset", id: model } : { kind: "custom", name: model };
1088
1101
  }
1089
- function parseProviderConfig(runtime, value) {
1102
+ function parseProviderConfig(runtime, value, legacyApiUrl, legacyApiKey) {
1090
1103
  if (runtime !== "claude") return void 0;
1091
1104
  if (!isPlainRecord(value)) return { kind: "default" };
1092
1105
  if (value.kind === "custom" && typeof value.apiUrl === "string" && value.apiUrl.trim() && typeof value.apiKey === "string" && value.apiKey.trim()) {
1093
1106
  return { kind: "custom", apiUrl: value.apiUrl.trim(), apiKey: value.apiKey.trim() };
1094
1107
  }
1108
+ if (value.kind === "custom" && legacyApiUrl?.trim() && legacyApiKey?.trim()) {
1109
+ return { kind: "custom", apiUrl: legacyApiUrl.trim(), apiKey: legacyApiKey.trim() };
1110
+ }
1095
1111
  return { kind: "default" };
1096
1112
  }
1097
- function parseModelConfig(runtime, value, fallback) {
1113
+ function parseModelConfig(runtime, value, fallback, provider, legacyCustomModel) {
1098
1114
  if (!isPlainRecord(value)) return modelConfigFromLegacy(runtime, fallback);
1099
1115
  if (value.kind === "custom" && typeof value.name === "string" && value.name.trim()) {
1100
1116
  return { kind: "custom", name: value.name.trim() };
1101
1117
  }
1102
- if (value.kind === "preset" && typeof value.id === "string" && value.id.trim()) {
1118
+ if (value.kind === "preset" && typeof value.id === "string" && value.id.trim() && isPresetRuntimeModel(runtime, value.id.trim())) {
1103
1119
  return { kind: "preset", id: value.id.trim() };
1104
1120
  }
1121
+ if (provider?.kind === "custom" && runtime === "claude" && legacyCustomModel?.trim()) {
1122
+ return { kind: "custom", name: legacyCustomModel.trim() };
1123
+ }
1105
1124
  return modelConfigFromLegacy(runtime, fallback);
1106
1125
  }
1107
1126
  function parseModeConfig(value) {
@@ -1117,12 +1136,13 @@ function hydrateRuntimeConfig(input) {
1117
1136
  const storedEnvVars = normalizeEnvVars(stored?.envVars);
1118
1137
  const legacyClaudeApiUrl = runtime === "claude" ? legacyEnvVars?.ANTHROPIC_BASE_URL : void 0;
1119
1138
  const legacyClaudeApiKey = runtime === "claude" ? legacyEnvVars?.ANTHROPIC_API_KEY : void 0;
1120
- const provider = stored ? parseProviderConfig(runtime, stored.provider) : runtime === "claude" && legacyClaudeApiUrl && legacyClaudeApiKey ? { kind: "custom", apiUrl: legacyClaudeApiUrl, apiKey: legacyClaudeApiKey } : runtime === "claude" ? { kind: "default" } : void 0;
1139
+ const legacyClaudeCustomModel = runtime === "claude" ? legacyEnvVars?.ANTHROPIC_CUSTOM_MODEL_OPTION : void 0;
1140
+ const provider = stored ? parseProviderConfig(runtime, stored.provider, legacyClaudeApiUrl, legacyClaudeApiKey) : runtime === "claude" && legacyClaudeApiUrl && legacyClaudeApiKey ? { kind: "custom", apiUrl: legacyClaudeApiUrl, apiKey: legacyClaudeApiKey } : runtime === "claude" ? { kind: "default" } : void 0;
1121
1141
  return {
1122
1142
  version: RUNTIME_CONFIG_VERSION,
1123
1143
  runtime,
1124
1144
  ...provider ? { provider } : {},
1125
- model: stored ? parseModelConfig(runtime, stored.model, fallbackModel) : modelConfigFromLegacy(runtime, fallbackModel),
1145
+ model: stored ? parseModelConfig(runtime, stored.model, fallbackModel, provider, legacyClaudeCustomModel) : modelConfigFromLegacy(runtime, fallbackModel),
1126
1146
  mode: stored ? parseModeConfig(stored.mode) : { kind: "default" },
1127
1147
  reasoningEffort: stored?.reasoningEffort ?? input.reasoningEffort ?? null,
1128
1148
  envVars: stripControlledRuntimeEnvVars(runtime, stored ? storedEnvVars : legacyEnvVars)
@@ -1190,10 +1210,10 @@ var DISPLAY_PLAN_CONFIG = {
1190
1210
  };
1191
1211
 
1192
1212
  // src/agentProcessManager.ts
1193
- import { mkdirSync as mkdirSync4, readdirSync, statSync, writeFileSync as writeFileSync7 } from "fs";
1213
+ import { mkdirSync as mkdirSync5, readdirSync, statSync, writeFileSync as writeFileSync8 } from "fs";
1194
1214
  import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
1195
1215
  import { createHash as createHash3 } from "crypto";
1196
- import path12 from "path";
1216
+ import path13 from "path";
1197
1217
  import os5 from "os";
1198
1218
 
1199
1219
  // src/drivers/claude.ts
@@ -1374,19 +1394,19 @@ If Slock says a message was not sent and was saved as a draft, choose one path:
1374
1394
 
1375
1395
  Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
1376
1396
 
1377
- - **Thread targets** have a colon and short ID suffix: \`#general:a1b2c3d4\` (thread in #general) or \`dm:@richard:x9y8z7a0\` (thread in a DM).
1397
+ - **Thread targets** have a colon and short ID suffix: \`#general:00000000\` (thread in #general) or \`dm:@richard:11111111\` (thread in a DM).
1378
1398
  - When you receive a message from a thread (the target has a \`:shortid\` suffix), **always reply using that same target** to keep the conversation in the thread.
1379
- - **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=a1b2c3d4 ...]\`, reply with \`slock message send --target "#general:a1b2c3d4" <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`. The thread will be auto-created if it doesn't exist yet.
1399
+ - **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=00000000 ...]\`, reply with \`slock message send --target "#general:00000000" <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`. The thread will be auto-created if it doesn't exist yet. Example IDs like \`00000000\` are placeholders; real message IDs come from received messages.
1380
1400
  - When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
1381
- - You can read thread history: \`slock message read --channel "#general:a1b2c3d4"\`
1382
- - You can stop receiving ordinary delivery for a thread with \`slock thread unfollow --target "#general:a1b2c3d4"\`. Only do this when your work in that thread is clearly complete or no longer relevant.
1401
+ - You can read thread history: \`slock message read --channel "#general:00000000"\`
1402
+ - You can stop receiving ordinary delivery for a thread with \`slock thread unfollow --target "#general:00000000"\`. Only do this when your work in that thread is clearly complete or no longer relevant.
1383
1403
  - Threads cannot be nested \u2014 you cannot start a thread inside a thread.` : `### Threads
1384
1404
 
1385
1405
  Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
1386
1406
 
1387
- - **Thread targets** have a colon and short ID suffix: \`#general:a1b2c3d4\` (thread in #general) or \`dm:@richard:x9y8z7a0\` (thread in a DM).
1407
+ - **Thread targets** have a colon and short ID suffix: \`#general:00000000\` (thread in #general) or \`dm:@richard:11111111\` (thread in a DM).
1388
1408
  - When you receive a message from a thread (the target has a \`:shortid\` suffix), **always reply using that same target** to keep the conversation in the thread.
1389
- - **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=a1b2c3d4 ...]\`, call \`${t("send_message")}(target="#general:a1b2c3d4", content="...")\`. The thread will be auto-created if it doesn't exist yet.
1409
+ - **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=00000000 ...]\`, call \`${t("send_message")}(target="#general:00000000", content="...")\`. The thread will be auto-created if it doesn't exist yet. Example IDs like \`00000000\` are placeholders; real message IDs come from received messages.
1390
1410
  - When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
1391
1411
  - You can read thread history via ${readCmd} with the same thread target.
1392
1412
  - Threads cannot be nested \u2014 you cannot start a thread inside a thread.`;
@@ -1552,13 +1572,18 @@ ${opts.postStartupNotes.join("\n")}`;
1552
1572
  Messages you receive have a single RFC 5424-style structured data header followed by the sender and content:
1553
1573
 
1554
1574
  \`\`\`
1555
- [target=#general msg=a1b2c3d4 time=2026-03-15T01:00:00 type=human] @richard: hello everyone
1556
- [target=#general msg=e5f6a7b8 time=2026-03-15T01:00:01 type=agent] @Alice: hi there
1557
- [target=dm:@richard msg=c9d0e1f2 time=2026-03-15T01:00:02 type=human] @richard: hey, can you help?
1558
- [target=#general:a1b2c3d4 msg=f3a4b5c6 time=2026-03-15T01:00:03 type=human] @richard: thread reply
1559
- [target=dm:@richard:x9y8z7a0 msg=d7e8f9a0 time=2026-03-15T01:00:04 type=human] @richard: DM thread reply
1575
+ [target=#general msg=00000000 time=2026-03-15T01:00:00 type=human] @richard: hello everyone
1576
+ [target=#general msg=11111111 time=2026-03-15T01:00:01 type=agent] @Alice: hi there
1577
+ [target=dm:@richard msg=22222222 time=2026-03-15T01:00:02 type=human] @richard: hey, can you help?
1578
+ [target=#general:00000000 msg=33333333 time=2026-03-15T01:00:03 type=human] @richard: thread reply
1579
+ [target=dm:@richard:22222222 msg=44444444 time=2026-03-15T01:00:04 type=human] @richard: DM thread reply
1560
1580
  \`\`\`
1561
1581
 
1582
+ Prompt examples use obvious placeholder IDs such as \`00000000\`, \`11111111\`,
1583
+ and \`22222222\`. They show the shape of a real message ID but are not actual
1584
+ messages. Do not cite them as evidence; use only IDs from messages you actually
1585
+ received or read.
1586
+
1562
1587
  Header fields:
1563
1588
  - \`target=\` \u2014 where the message came from. Reuse as the \`target\` parameter when replying.
1564
1589
  - \`msg=\` \u2014 message short ID (first 8 chars of UUID). Use as thread suffix to start/reply in a thread.
@@ -1712,12 +1737,12 @@ How to handle these:
1712
1737
  - Call ${checkCmd} at the next safe breakpoint to materialize the pending messages before taking side-effect actions that depend on current context.
1713
1738
  - If the new message is higher priority, pivot after reading it. If not, continue your current work.`;
1714
1739
  } else {
1715
- const notifyExample = isCli ? `\`[System notification: You have N new message(s) waiting. Call slock message check to read them when you're ready.]\`` : `\`[System notification: You have N new message(s) waiting. Call ${t("check_messages")} to read them when you're ready.]\``;
1740
+ const notifyExample = isCli ? `\`[Slock inbox notice: You have N pending inbox message(s). Call slock message check to read them when you're ready.]\`` : `\`[Slock inbox notice: You have N pending inbox message(s). Call ${t("check_messages")} to read them when you're ready.]\``;
1716
1741
  prompt += `
1717
1742
 
1718
1743
  ## Message Notifications
1719
1744
 
1720
- While you are busy (executing tools, thinking, etc.), new messages may arrive. When this happens, you will receive a system notification like:
1745
+ While you are busy (executing tools, thinking, etc.), new messages may arrive. When this happens, the daemon may write a Slock inbox notice like:
1721
1746
 
1722
1747
  ${notifyExample}
1723
1748
 
@@ -1778,6 +1803,19 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
1778
1803
  return candidates.filter((candidate) => existsSync(candidate.path));
1779
1804
  }
1780
1805
 
1806
+ // src/authEnv.ts
1807
+ var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
1808
+ var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
1809
+ function scrubDaemonAuthEnv(env) {
1810
+ delete env[DAEMON_API_KEY_ENV];
1811
+ return env;
1812
+ }
1813
+ function scrubDaemonChildEnv(env) {
1814
+ delete env[DAEMON_API_KEY_ENV];
1815
+ delete env[SLOCK_AGENT_TOKEN_ENV];
1816
+ return env;
1817
+ }
1818
+
1781
1819
  // src/agentCredentialProxy.ts
1782
1820
  import { randomBytes } from "crypto";
1783
1821
  import http from "http";
@@ -2172,7 +2210,7 @@ async function handleProxyRequest(req, res) {
2172
2210
  const bodyBuffer = new ArrayBuffer(rawBodyBuffer.byteLength);
2173
2211
  new Uint8Array(bodyBuffer).set(rawBodyBuffer);
2174
2212
  body = bodyBuffer;
2175
- headers.set("content-length", String(rawBodyBuffer.byteLength));
2213
+ headers.delete("content-length");
2176
2214
  }
2177
2215
  let sendTarget;
2178
2216
  const sideEffectAction = agentApiSideEffectAction(target.pathname);
@@ -2196,7 +2234,7 @@ async function handleProxyRequest(req, res) {
2196
2234
  body = prepared.bodyText;
2197
2235
  if (sideEffectAction === "send") sendTarget = prepared.target;
2198
2236
  headers.set("content-type", "application/json");
2199
- headers.set("content-length", String(Buffer.byteLength(prepared.bodyText)));
2237
+ headers.delete("content-length");
2200
2238
  }
2201
2239
  const upstream = await daemonFetch(target, {
2202
2240
  method,
@@ -2385,6 +2423,20 @@ function maxMessageSeq(messages) {
2385
2423
  }
2386
2424
  return maxSeq > 0 ? maxSeq : void 0;
2387
2425
  }
2426
+ function messageSenderId(message) {
2427
+ if (typeof message.sender_id === "string" && message.sender_id.length > 0) return message.sender_id;
2428
+ if (typeof message.senderId === "string" && message.senderId.length > 0) return message.senderId;
2429
+ return void 0;
2430
+ }
2431
+ function isSelfAuthoredMessage(registration, message) {
2432
+ return messageSenderId(message) === registration.agentId;
2433
+ }
2434
+ function isMessageModelSeen(coordinator, target, message) {
2435
+ const seq = Math.floor(messageSeq(message));
2436
+ const boundary = coordinator.getBoundary(target);
2437
+ if (Number.isFinite(seq) && seq > 0 && typeof boundary === "number" && boundary >= seq) return true;
2438
+ return coordinator.isMessageModelSeen?.({ target, message }) === true;
2439
+ }
2388
2440
  function resolveFreshnessBoundary(messages) {
2389
2441
  const seenUpToSeq = maxMessageSeq(messages);
2390
2442
  return typeof seenUpToSeq === "number" ? { ok: true, seenUpToSeq } : { ok: false, reason: "missing_seq_boundary" };
@@ -2525,7 +2577,7 @@ function parseTargetFields(target) {
2525
2577
  }
2526
2578
  function normalizeVisibleMessage(message, target) {
2527
2579
  const targetFields = target ? parseTargetFields(target) : {};
2528
- return {
2580
+ const normalized = {
2529
2581
  ...targetFields,
2530
2582
  ...message,
2531
2583
  message_id: message.message_id ?? message.id,
@@ -2534,6 +2586,9 @@ function normalizeVisibleMessage(message, target) {
2534
2586
  sender_name: message.sender_name ?? message.senderName,
2535
2587
  sender_description: message.sender_description ?? message.senderDescription ?? null
2536
2588
  };
2589
+ const senderId = messageSenderId(message);
2590
+ if (senderId) normalized.sender_id = senderId;
2591
+ return normalized;
2537
2592
  }
2538
2593
  function normalizeVisibleMessages(messages, target) {
2539
2594
  return messages.map((message) => normalizeVisibleMessage(message, target));
@@ -2634,6 +2689,9 @@ async function prepareAgentApiSideEffectForward(registration, headers, rawBody,
2634
2689
  }
2635
2690
  const recent = await loadRecentTargetMessages(registration, headers, target);
2636
2691
  if (recent.length > 0) {
2692
+ const unconsumedCounterparty = recent.filter(
2693
+ (message) => !isSelfAuthoredMessage(registration, message) && !isMessageModelSeen(coordinator, target, message)
2694
+ );
2637
2695
  const boundary2 = resolveFreshnessBoundary(recent);
2638
2696
  if (!boundary2.ok) {
2639
2697
  recordFreshnessDecision(coordinator, {
@@ -2647,6 +2705,39 @@ async function prepareAgentApiSideEffectForward(registration, headers, rawBody,
2647
2705
  });
2648
2706
  return { bodyText: JSON.stringify(body), target };
2649
2707
  }
2708
+ if (unconsumedCounterparty.length === 0) {
2709
+ const { seenUpToSeq: seenUpToSeq2 } = boundary2;
2710
+ if (action === "send") body.seenUpToSeq = seenUpToSeq2;
2711
+ coordinator.consumeVisibleMessages({ target, messages: recent, boundarySeq: seenUpToSeq2, source: "side_effect_preflight_context" });
2712
+ recordFreshnessDecision(coordinator, {
2713
+ action,
2714
+ decision: "forward",
2715
+ target,
2716
+ inboxTrustState: "untrusted",
2717
+ reason: "target_first_touch_recent_context_already_seen",
2718
+ pendingCount: 0,
2719
+ pendingMaxSeq: seenUpToSeq2,
2720
+ modelSeenSeq: seenUpToSeq2,
2721
+ heldMessageCount: 0,
2722
+ omittedMessageCount: 0
2723
+ });
2724
+ return { bodyText: JSON.stringify(body), target };
2725
+ }
2726
+ const heldBoundary = resolveFreshnessBoundary(unconsumedCounterparty);
2727
+ if (!heldBoundary.ok) {
2728
+ recordFreshnessDecision(coordinator, {
2729
+ action,
2730
+ decision: "forward",
2731
+ target,
2732
+ inboxTrustState: "untrusted",
2733
+ reason: "target_first_touch_counterparty_context_without_seq_boundary",
2734
+ pendingCount: 0,
2735
+ modelSeenSeq: 0
2736
+ });
2737
+ return { bodyText: JSON.stringify(body), target };
2738
+ }
2739
+ const heldMessages = latestVisibleMessages(unconsumedCounterparty, LOCAL_HELD_CONTEXT_LIMIT);
2740
+ const omittedMessageCount = Math.max(0, unconsumedCounterparty.length - heldMessages.length);
2650
2741
  const { seenUpToSeq } = boundary2;
2651
2742
  coordinator.consumeVisibleMessages({ target, messages: recent, boundarySeq: seenUpToSeq, source: "side_effect_preflight_context" });
2652
2743
  const decision = {
@@ -2656,10 +2747,10 @@ async function prepareAgentApiSideEffectForward(registration, headers, rawBody,
2656
2747
  inboxTrustState: "untrusted",
2657
2748
  reason: "target_first_touch_recent_context",
2658
2749
  pendingCount: 0,
2659
- pendingMaxSeq: seenUpToSeq,
2750
+ pendingMaxSeq: heldBoundary.seenUpToSeq,
2660
2751
  modelSeenSeq: 0,
2661
- heldMessageCount: recent.length,
2662
- omittedMessageCount: 0
2752
+ heldMessageCount: heldMessages.length,
2753
+ omittedMessageCount
2663
2754
  };
2664
2755
  const producerFactId = buildApmFreshnessDecisionProducerFactId(registration.agentId, decision);
2665
2756
  recordFreshnessDecision(coordinator, { ...decision, producerFactId });
@@ -2669,9 +2760,9 @@ async function prepareAgentApiSideEffectForward(registration, headers, rawBody,
2669
2760
  localResponse: projectApmHeldFreshnessEnvelope({
2670
2761
  producerFactId,
2671
2762
  action,
2672
- heldMessages: recent,
2673
- newMessageCount: recent.length,
2674
- omittedMessageCount: 0,
2763
+ heldMessages,
2764
+ newMessageCount: unconsumedCounterparty.length,
2765
+ omittedMessageCount,
2675
2766
  seenUpToSeq
2676
2767
  }).body
2677
2768
  };
@@ -2777,7 +2868,9 @@ var LOOPBACK_NO_PROXY = "127.0.0.1,localhost";
2777
2868
  var CLI_TRANSPORT_TRACE_DIR_ENV = "SLOCK_CLI_TRANSPORT_TRACE_DIR";
2778
2869
  var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
2779
2870
  var RAW_CREDENTIAL_ENV_DENYLIST = [
2780
- "SLOCK_AGENT_CREDENTIAL_KEY"
2871
+ "SLOCK_AGENT_TOKEN",
2872
+ "SLOCK_AGENT_CREDENTIAL_KEY",
2873
+ "SLOCK_AGENT_CREDENTIAL_KEY_FILE"
2781
2874
  ];
2782
2875
  var cachedOpencliBinPath;
2783
2876
  function resolveOpencliBinPath() {
@@ -2992,7 +3085,7 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(opencliBinPath)} "
2992
3085
  ...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
2993
3086
  PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
2994
3087
  };
2995
- delete spawnEnv.SLOCK_AGENT_TOKEN;
3088
+ scrubDaemonChildEnv(spawnEnv);
2996
3089
  for (const key of RAW_CREDENTIAL_ENV_DENYLIST) {
2997
3090
  delete spawnEnv[key];
2998
3091
  }
@@ -3026,7 +3119,126 @@ function collectResultErrorDetail(message, fallback) {
3026
3119
  return parts.join(" | ") || fallback;
3027
3120
  }
3028
3121
  function isProviderApiFailureText(value, hasToolUse) {
3029
- return !hasToolUse && /^\s*API Error:/i.test(value) && (/\b(?:ECONNRESET|EPIPE|ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN)\b/i.test(value) || /\bUnable to connect to API\b/i.test(value) || /\b(?:timed out|timeout)\b/i.test(value) || /\b5\d{2}\b/.test(value));
3122
+ return !hasToolUse && /^\s*API Error:/i.test(value) && (/\b(?:ECONNRESET|EPIPE|ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN)\b/i.test(value) || /\bUnable to connect to API\b/i.test(value) || /\b(?:timed out|timeout)\b/i.test(value) || /\b4\d{2}\b/.test(value) || /\b5\d{2}\b/.test(value));
3123
+ }
3124
+ function finiteNumber(value) {
3125
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3126
+ }
3127
+ function finiteString(value) {
3128
+ return typeof value === "string" && value.length > 0 ? value : void 0;
3129
+ }
3130
+ function withDefined(attrs) {
3131
+ return Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== void 0));
3132
+ }
3133
+ function collectNumericFields(value, fields) {
3134
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
3135
+ const attrs = {};
3136
+ for (const [sourceKey, attrKey] of Object.entries(fields)) {
3137
+ const numberValue = finiteNumber(value[sourceKey]);
3138
+ if (numberValue !== void 0) attrs[attrKey] = numberValue;
3139
+ }
3140
+ return attrs;
3141
+ }
3142
+ function collectModelUsageAttrs(value) {
3143
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
3144
+ const sanitized = {};
3145
+ const aggregate = {};
3146
+ const knownNumericFields = [
3147
+ "inputTokens",
3148
+ "outputTokens",
3149
+ "cacheReadInputTokens",
3150
+ "cacheCreationInputTokens",
3151
+ "webSearchRequests",
3152
+ "costUSD",
3153
+ "contextWindow",
3154
+ "maxOutputTokens"
3155
+ ];
3156
+ for (const [modelName, rawUsage] of Object.entries(value)) {
3157
+ if (!rawUsage || typeof rawUsage !== "object" || Array.isArray(rawUsage)) continue;
3158
+ const modelUsage = {};
3159
+ for (const field of knownNumericFields) {
3160
+ const numberValue = finiteNumber(rawUsage[field]);
3161
+ if (numberValue === void 0) continue;
3162
+ modelUsage[field] = numberValue;
3163
+ const aggregateKey = field === "costUSD" ? "costUsd" : field;
3164
+ if (field === "contextWindow" || field === "maxOutputTokens") {
3165
+ aggregate[aggregateKey] = Math.max(aggregate[aggregateKey] ?? 0, numberValue);
3166
+ } else {
3167
+ aggregate[aggregateKey] = (aggregate[aggregateKey] ?? 0) + numberValue;
3168
+ }
3169
+ }
3170
+ if (Object.keys(modelUsage).length > 0) sanitized[modelName] = modelUsage;
3171
+ }
3172
+ const modelNames = Object.keys(sanitized).sort();
3173
+ if (modelNames.length === 0) return {};
3174
+ const orderedSanitized = Object.fromEntries(modelNames.map((modelName) => [modelName, sanitized[modelName]]));
3175
+ return withDefined({
3176
+ modelUsageModelCount: modelNames.length,
3177
+ modelUsageModels: modelNames.join(","),
3178
+ modelUsageJson: JSON.stringify(orderedSanitized),
3179
+ modelUsageInputTokens: aggregate.inputTokens,
3180
+ modelUsageOutputTokens: aggregate.outputTokens,
3181
+ modelUsageCachedInputTokens: aggregate.cacheReadInputTokens,
3182
+ modelUsageCacheCreationInputTokens: aggregate.cacheCreationInputTokens,
3183
+ modelUsageWebSearchRequests: aggregate.webSearchRequests,
3184
+ modelUsageCostUsd: aggregate.costUsd,
3185
+ modelUsageMaxContextWindow: aggregate.contextWindow,
3186
+ modelUsageMaxOutputTokens: aggregate.maxOutputTokens
3187
+ });
3188
+ }
3189
+ function parseClaudeResultUsageTelemetry(event) {
3190
+ const usage = event.usage && typeof event.usage === "object" ? event.usage : void 0;
3191
+ const totalCostUsd = finiteNumber(event.total_cost_usd);
3192
+ const modelUsageAttrs = collectModelUsageAttrs(event.modelUsage);
3193
+ const hasTelemetrySource = usage !== void 0 || totalCostUsd !== void 0 || Object.keys(modelUsageAttrs).length > 0;
3194
+ if (!hasTelemetrySource) return null;
3195
+ const inputTokens = finiteNumber(usage?.input_tokens);
3196
+ const outputTokens = finiteNumber(usage?.output_tokens);
3197
+ const cachedInputTokens = finiteNumber(usage?.cache_read_input_tokens);
3198
+ const cacheCreationInputTokens = finiteNumber(usage?.cache_creation_input_tokens);
3199
+ const attrs = {
3200
+ ...withDefined({
3201
+ totalCostUsd,
3202
+ durationMs: finiteNumber(event.duration_ms),
3203
+ durationApiMs: finiteNumber(event.duration_api_ms),
3204
+ numTurns: finiteNumber(event.num_turns),
3205
+ resultSubtype: finiteString(event.subtype),
3206
+ stopReason: finiteString(event.stop_reason),
3207
+ resultIsError: typeof event.is_error === "boolean" ? event.is_error : void 0,
3208
+ fastModeState: finiteString(event.fast_mode_state),
3209
+ permissionDenialsCount: Array.isArray(event.permission_denials) ? event.permission_denials.length : void 0,
3210
+ serviceTier: finiteString(usage?.service_tier),
3211
+ inferenceGeo: finiteString(usage?.inference_geo),
3212
+ usageSpeed: finiteString(usage?.speed),
3213
+ usageIterationsCount: Array.isArray(usage?.iterations) ? usage.iterations.length : void 0
3214
+ }),
3215
+ ...collectNumericFields(usage?.server_tool_use, {
3216
+ web_search_requests: "serverToolUseWebSearchRequests",
3217
+ web_fetch_requests: "serverToolUseWebFetchRequests"
3218
+ }),
3219
+ ...collectNumericFields(usage?.cache_creation, {
3220
+ ephemeral_1h_input_tokens: "cacheCreationEphemeral1hInputTokens",
3221
+ ephemeral_5m_input_tokens: "cacheCreationEphemeral5mInputTokens"
3222
+ }),
3223
+ ...modelUsageAttrs
3224
+ };
3225
+ if (inputTokens !== void 0) attrs.inputTokens = inputTokens;
3226
+ if (outputTokens !== void 0) attrs.outputTokens = outputTokens;
3227
+ if (cachedInputTokens !== void 0) attrs.cachedInputTokens = cachedInputTokens;
3228
+ if (cacheCreationInputTokens !== void 0) attrs.cacheCreationInputTokens = cacheCreationInputTokens;
3229
+ const tokenComponents = [inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens];
3230
+ const totalTokens = tokenComponents.reduce((sum, value) => sum + (value ?? 0), 0);
3231
+ if (tokenComponents.some((value) => value !== void 0)) attrs.totalTokens = totalTokens;
3232
+ if (Object.keys(attrs).length === 0) return null;
3233
+ return {
3234
+ kind: "telemetry",
3235
+ name: "token_usage",
3236
+ source: "claude_result_usage",
3237
+ usageKind: "per_turn",
3238
+ ...typeof event.session_id === "string" && event.session_id ? { sessionId: event.session_id } : {},
3239
+ ...typeof event.uuid === "string" && event.uuid ? { runtimeResultId: event.uuid } : {},
3240
+ attrs
3241
+ };
3030
3242
  }
3031
3243
  var ClaudeEventNormalizer = class {
3032
3244
  normalizeLine(line) {
@@ -3102,6 +3314,7 @@ var ClaudeEventNormalizer = class {
3102
3314
  case "result": {
3103
3315
  const subtype = typeof event.subtype === "string" ? event.subtype : "success";
3104
3316
  const stopReason = typeof event.stop_reason === "string" ? event.stop_reason : null;
3317
+ const usageTelemetry = parseClaudeResultUsageTelemetry(event);
3105
3318
  switch (subtype) {
3106
3319
  case "success":
3107
3320
  if (event.is_error && stopReason !== "max_tokens") {
@@ -3123,6 +3336,7 @@ var ClaudeEventNormalizer = class {
3123
3336
  pushResultError(event, "Structured output retries exceeded");
3124
3337
  break;
3125
3338
  }
3339
+ if (usageTelemetry) events.push(usageTelemetry);
3126
3340
  events.push({ kind: "turn_end", sessionId: event.session_id });
3127
3341
  break;
3128
3342
  }
@@ -3291,7 +3505,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn, existsSyncFn) {
3291
3505
  }
3292
3506
  function resolveCommandOnPath(command, deps = {}) {
3293
3507
  const platform = deps.platform ?? process.platform;
3294
- const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
3508
+ const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
3295
3509
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
3296
3510
  const existsSyncFn = deps.existsSyncFn ?? existsSync2;
3297
3511
  if (platform === "win32") {
@@ -3317,7 +3531,7 @@ function firstExistingPath(candidates, deps = {}) {
3317
3531
  return null;
3318
3532
  }
3319
3533
  function readCommandVersion(command, args = [], deps = {}) {
3320
- const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
3534
+ const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
3321
3535
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
3322
3536
  try {
3323
3537
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -3388,7 +3602,7 @@ function buildClaudeArgs(config, opts) {
3388
3602
  args.push("--effort", launchRuntimeFields.reasoningEffort);
3389
3603
  }
3390
3604
  if (launchRuntimeFields.mode.kind === "fast") {
3391
- args.push("--bare");
3605
+ args.push("--settings", JSON.stringify({ fastMode: true }));
3392
3606
  }
3393
3607
  if (config.sessionId) {
3394
3608
  args.push("--resume", config.sessionId);
@@ -3546,53 +3760,64 @@ var RuntimeTurnState = class {
3546
3760
  };
3547
3761
 
3548
3762
  // src/drivers/codexTelemetrySidecar.ts
3549
- function finiteNumber(value) {
3763
+ function finiteNumber2(value) {
3550
3764
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3551
3765
  }
3552
- function finiteString(value) {
3766
+ function finiteString2(value) {
3553
3767
  return typeof value === "string" && value.length > 0 ? value : void 0;
3554
3768
  }
3555
3769
  function ratio(numerator, denominator) {
3556
3770
  if (numerator === void 0 || denominator === void 0 || denominator <= 0) return void 0;
3557
3771
  return Number((numerator / denominator).toFixed(6));
3558
3772
  }
3559
- function withDefined(attrs) {
3773
+ function withDefined2(attrs) {
3560
3774
  return Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== void 0));
3561
3775
  }
3562
3776
  function parseTokenUsageTelemetry(message) {
3563
3777
  const usage = message.params?.tokenUsage;
3564
3778
  const total = usage?.total;
3565
3779
  if (!total || typeof total !== "object") return null;
3566
- const inputTokens = finiteNumber(total.inputTokens);
3567
- const cachedInputTokens = finiteNumber(total.cachedInputTokens);
3568
- const totalTokens = finiteNumber(total.totalTokens);
3569
- const modelContextWindow = finiteNumber(usage.modelContextWindow);
3570
- const attrs = withDefined({
3780
+ const inputTokens = finiteNumber2(total.inputTokens);
3781
+ const cachedInputTokens = finiteNumber2(total.cachedInputTokens);
3782
+ const totalTokens = finiteNumber2(total.totalTokens);
3783
+ const modelContextWindow = finiteNumber2(usage.modelContextWindow);
3784
+ const attrs = withDefined2({
3571
3785
  totalTokens,
3572
3786
  inputTokens,
3573
3787
  cachedInputTokens,
3574
- outputTokens: finiteNumber(total.outputTokens),
3575
- reasoningOutputTokens: finiteNumber(total.reasoningOutputTokens),
3788
+ outputTokens: finiteNumber2(total.outputTokens),
3789
+ reasoningOutputTokens: finiteNumber2(total.reasoningOutputTokens),
3576
3790
  modelContextWindow,
3577
3791
  cachedInputRatio: ratio(cachedInputTokens, inputTokens),
3578
3792
  contextUtilization: ratio(totalTokens, modelContextWindow)
3579
3793
  });
3580
3794
  if (Object.keys(attrs).length === 0) return null;
3581
- return { kind: "telemetry", name: "token_usage", attrs };
3795
+ return {
3796
+ kind: "telemetry",
3797
+ name: "token_usage",
3798
+ source: "codex_thread_token_usage_updated",
3799
+ usageKind: "cumulative_session",
3800
+ attrs
3801
+ };
3582
3802
  }
3583
3803
  function parseRateLimitTelemetry(message) {
3584
3804
  const rateLimits = message.params?.rateLimits;
3585
3805
  const primary = rateLimits?.primary;
3586
3806
  if (!rateLimits || typeof rateLimits !== "object" || !primary || typeof primary !== "object") return null;
3587
- const attrs = withDefined({
3588
- limitId: finiteString(rateLimits.limitId),
3589
- planType: finiteString(rateLimits.planType),
3590
- usedPercent: finiteNumber(primary.usedPercent),
3591
- windowDurationMins: finiteNumber(primary.windowDurationMins),
3592
- resetsAt: finiteNumber(primary.resetsAt)
3807
+ const attrs = withDefined2({
3808
+ limitId: finiteString2(rateLimits.limitId),
3809
+ planType: finiteString2(rateLimits.planType),
3810
+ usedPercent: finiteNumber2(primary.usedPercent),
3811
+ windowDurationMins: finiteNumber2(primary.windowDurationMins),
3812
+ resetsAt: finiteNumber2(primary.resetsAt)
3593
3813
  });
3594
3814
  if (Object.keys(attrs).length === 0) return null;
3595
- return { kind: "telemetry", name: "rate_limits", attrs };
3815
+ return {
3816
+ kind: "telemetry",
3817
+ name: "rate_limits",
3818
+ source: "codex_account_rate_limits_updated",
3819
+ attrs
3820
+ };
3596
3821
  }
3597
3822
  function parseCodexTelemetryEvent(message) {
3598
3823
  switch (message.method) {
@@ -3699,7 +3924,11 @@ var CodexEventNormalizer = class {
3699
3924
  }
3700
3925
  const telemetry = parseCodexTelemetryEvent(message);
3701
3926
  if (telemetry) {
3702
- events.push(telemetry);
3927
+ events.push({
3928
+ ...telemetry,
3929
+ ...this.currentThreadId ? { sessionId: this.currentThreadId } : {},
3930
+ ...this.turnState.activeTurnId ? { turnId: this.turnState.activeTurnId } : {}
3931
+ });
3703
3932
  return { events };
3704
3933
  }
3705
3934
  const rawProgress = rawResponseItemProgressEvent(message);
@@ -4052,6 +4281,7 @@ var CodexDriver = class {
4052
4281
  cwd: ctx.workingDirectory,
4053
4282
  approvalPolicy: "never",
4054
4283
  sandbox: "danger-full-access",
4284
+ sandbox_mode: "danger-full-access",
4055
4285
  developerInstructions: ctx.standingPrompt,
4056
4286
  // Raw response items are used only as payload-free liveness signals in
4057
4287
  // the daemon. They replace the previous transcript-mtime heuristic.
@@ -4748,11 +4978,11 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
4748
4978
  return parseCursorModelsOutput(String(result.stdout || ""));
4749
4979
  }
4750
4980
  function buildCursorModelProbeEnv(deps = {}) {
4751
- return withWindowsUserEnvironment({
4981
+ return scrubDaemonChildEnv(withWindowsUserEnvironment({
4752
4982
  ...deps.env ?? process.env,
4753
4983
  FORCE_COLOR: "0",
4754
4984
  NO_COLOR: "1"
4755
- }, deps);
4985
+ }, deps));
4756
4986
  }
4757
4987
  function runCursorModelsCommand() {
4758
4988
  return spawnSync("cursor-agent", ["models"], {
@@ -4808,7 +5038,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
4808
5038
  }
4809
5039
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
4810
5040
  const existsSyncFn = deps.existsSyncFn ?? existsSync5;
4811
- const env = deps.env ?? process.env;
5041
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
4812
5042
  const winPath = path8.win32;
4813
5043
  let geminiEntry = null;
4814
5044
  try {
@@ -4980,13 +5210,16 @@ var GeminiDriver = class {
4980
5210
  // src/drivers/kimi.ts
4981
5211
  import { randomUUID as randomUUID2 } from "crypto";
4982
5212
  import { spawn as spawn7 } from "child_process";
4983
- import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
5213
+ import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
4984
5214
  import os3 from "os";
4985
5215
  import path9 from "path";
4986
5216
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
4987
5217
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
4988
5218
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
4989
5219
  var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
5220
+ var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
5221
+ var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
5222
+ var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
4990
5223
  function parseToolArguments(raw) {
4991
5224
  if (typeof raw !== "string") return raw;
4992
5225
  try {
@@ -4995,6 +5228,73 @@ function parseToolArguments(raw) {
4995
5228
  return raw;
4996
5229
  }
4997
5230
  }
5231
+ function readKimiConfigSource(home = os3.homedir(), env = process.env) {
5232
+ const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
5233
+ if (inlineConfig && inlineConfig.trim()) {
5234
+ return {
5235
+ raw: inlineConfig,
5236
+ explicitPath: null,
5237
+ sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
5238
+ };
5239
+ }
5240
+ const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
5241
+ const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
5242
+ try {
5243
+ return {
5244
+ raw: readFileSync3(configPath, "utf8"),
5245
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
5246
+ sourcePath: configPath
5247
+ };
5248
+ } catch {
5249
+ return {
5250
+ raw: null,
5251
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
5252
+ sourcePath: configPath
5253
+ };
5254
+ }
5255
+ }
5256
+ function buildKimiSpawnEnv(env = process.env) {
5257
+ const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
5258
+ delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
5259
+ delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
5260
+ return scrubDaemonChildEnv(spawnEnv);
5261
+ }
5262
+ function buildKimiEffectiveEnv(ctx, overrideEnv) {
5263
+ return {
5264
+ ...process.env,
5265
+ ...ctx.config.envVars || {},
5266
+ ...overrideEnv || {}
5267
+ };
5268
+ }
5269
+ function buildKimiLaunchOptions(ctx, opts = {}) {
5270
+ const env = buildKimiEffectiveEnv(ctx, opts.env);
5271
+ const source = readKimiConfigSource(opts.home ?? os3.homedir(), env);
5272
+ const args = [];
5273
+ let configFilePath = null;
5274
+ let configContent = null;
5275
+ if (source.explicitPath) {
5276
+ configFilePath = source.explicitPath;
5277
+ } else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
5278
+ configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
5279
+ configContent = source.raw;
5280
+ if (opts.writeGeneratedConfig !== false) {
5281
+ writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
5282
+ chmodSync(configFilePath, 384);
5283
+ }
5284
+ }
5285
+ if (configFilePath) {
5286
+ args.push("--config-file", configFilePath);
5287
+ }
5288
+ if (ctx.config.model && ctx.config.model !== "default") {
5289
+ args.push("--model", ctx.config.model);
5290
+ }
5291
+ return {
5292
+ args,
5293
+ env: buildKimiSpawnEnv(env),
5294
+ configFilePath,
5295
+ configContent
5296
+ };
5297
+ }
4998
5298
  function resolveKimiSpawn(commandArgs, deps = {}) {
4999
5299
  return {
5000
5300
  command: resolveCommandOnPath("kimi", deps) ?? "kimi",
@@ -5018,7 +5318,25 @@ var KimiDriver = class {
5018
5318
  };
5019
5319
  model = {
5020
5320
  detectedModelsVerifiedAs: "launchable",
5021
- toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
5321
+ toLaunchSpec: (modelId, ctx, opts) => {
5322
+ if (!ctx) return { args: ["--model", modelId] };
5323
+ const launchCtx = {
5324
+ ...ctx,
5325
+ config: {
5326
+ ...ctx.config,
5327
+ model: modelId
5328
+ }
5329
+ };
5330
+ const launch = buildKimiLaunchOptions(launchCtx, {
5331
+ home: opts?.home,
5332
+ writeGeneratedConfig: false
5333
+ });
5334
+ return {
5335
+ args: launch.args,
5336
+ env: launch.env,
5337
+ configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
5338
+ };
5339
+ }
5022
5340
  };
5023
5341
  supportsStdinNotification = true;
5024
5342
  mcpToolPrefix = "";
@@ -5072,6 +5390,7 @@ var KimiDriver = class {
5072
5390
  }
5073
5391
  }
5074
5392
  }), "utf8");
5393
+ const launch = buildKimiLaunchOptions(ctx);
5075
5394
  const args = [
5076
5395
  "--wire",
5077
5396
  "--yolo",
@@ -5080,15 +5399,16 @@ var KimiDriver = class {
5080
5399
  "--mcp-config-file",
5081
5400
  mcpConfigPath,
5082
5401
  "--session",
5083
- this.sessionId
5402
+ this.sessionId,
5403
+ ...launch.args
5084
5404
  ];
5085
5405
  const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
5086
5406
  if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
5087
5407
  args.push("--model", launchRuntimeFields.model);
5088
5408
  }
5089
5409
  const spawnEnv = (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
5090
- const launch = resolveKimiSpawn(args);
5091
- const proc = spawn7(launch.command, launch.args, {
5410
+ const spawnTarget = resolveKimiSpawn(args);
5411
+ const proc = spawn7(spawnTarget.command, spawnTarget.args, {
5092
5412
  cwd: ctx.workingDirectory,
5093
5413
  stdio: ["pipe", "pipe", "pipe"],
5094
5414
  env: spawnEnv,
@@ -5096,7 +5416,7 @@ var KimiDriver = class {
5096
5416
  // and has an 8191-character command-line limit. Kimi's official
5097
5417
  // installer/uv entrypoint is an executable, so launch it directly and
5098
5418
  // keep prompts on stdin / files instead of routing through cmd.exe.
5099
- shell: launch.shell
5419
+ shell: spawnTarget.shell
5100
5420
  });
5101
5421
  proc.stdin?.write(JSON.stringify({
5102
5422
  jsonrpc: "2.0",
@@ -5210,14 +5530,9 @@ var KimiDriver = class {
5210
5530
  return detectKimiModels();
5211
5531
  }
5212
5532
  };
5213
- function detectKimiModels(home = os3.homedir()) {
5214
- const configPath = path9.join(home, ".kimi", "config.toml");
5215
- let raw;
5216
- try {
5217
- raw = readFileSync3(configPath, "utf8");
5218
- } catch {
5219
- return null;
5220
- }
5533
+ function detectKimiModels(home = os3.homedir(), opts = {}) {
5534
+ const raw = readKimiConfigSource(home, opts.env).raw;
5535
+ if (raw === null) return null;
5221
5536
  const models = [];
5222
5537
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
5223
5538
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -5481,7 +5796,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
5481
5796
  const platform = deps.platform ?? process.platform;
5482
5797
  const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
5483
5798
  const result = spawnSyncFn("opencode", ["models"], {
5484
- env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
5799
+ env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
5485
5800
  encoding: "utf8",
5486
5801
  timeout: 5e3,
5487
5802
  shell: platform === "win32"
@@ -5740,6 +6055,297 @@ var OpenCodeDriver = class {
5740
6055
  }
5741
6056
  };
5742
6057
 
6058
+ // src/drivers/pi.ts
6059
+ import { spawn as spawn9 } from "child_process";
6060
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
6061
+ import path11 from "path";
6062
+ import { fileURLToPath } from "url";
6063
+ import { getAgentDir, VERSION as PI_SDK_VERSION } from "@earendil-works/pi-coding-agent";
6064
+ var CHAT_MCP_TOOL_PREFIX2 = "chat_";
6065
+ var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
6066
+ var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
6067
+ var MIN_SUPPORTED_PI_VERSION = "0.74.0";
6068
+ function parseSemver2(version) {
6069
+ const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
6070
+ if (!match) return null;
6071
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
6072
+ }
6073
+ function isSupportedPiVersion(version) {
6074
+ if (!version) return true;
6075
+ const actual = parseSemver2(version);
6076
+ const minimum = parseSemver2(MIN_SUPPORTED_PI_VERSION);
6077
+ if (!actual || !minimum) return true;
6078
+ for (let i = 0; i < 3; i += 1) {
6079
+ if (actual[i] > minimum[i]) return true;
6080
+ if (actual[i] < minimum[i]) return false;
6081
+ }
6082
+ return true;
6083
+ }
6084
+ function unsupportedPiVersionMessage(version) {
6085
+ if (!version || isSupportedPiVersion(version)) return null;
6086
+ 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.`;
6087
+ }
6088
+ function probePi(version = PI_SDK_VERSION) {
6089
+ const unsupportedMessage = unsupportedPiVersionMessage(version);
6090
+ if (unsupportedMessage) {
6091
+ return {
6092
+ available: false,
6093
+ version: `${version} (requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION})`
6094
+ };
6095
+ }
6096
+ return { available: true, version };
6097
+ }
6098
+ function resolvePiSdkRunnerPath(moduleUrl = import.meta.url) {
6099
+ const moduleDir = path11.dirname(fileURLToPath(moduleUrl));
6100
+ const sourceSibling = path11.join(moduleDir, "piSdkRunner.ts");
6101
+ if (existsSync8(sourceSibling)) return sourceSibling;
6102
+ const bundledEntry = path11.join(moduleDir, "drivers", "piSdkRunner.js");
6103
+ if (existsSync8(bundledEntry)) return bundledEntry;
6104
+ return path11.join(moduleDir, "piSdkRunner.js");
6105
+ }
6106
+ function buildPiSdkNodeArgs(runnerPath = resolvePiSdkRunnerPath()) {
6107
+ if (runnerPath.endsWith(".ts")) {
6108
+ return [...process.execArgv, runnerPath];
6109
+ }
6110
+ return [runnerPath];
6111
+ }
6112
+ async function buildPiLaunchOptions(ctx, opts = {}) {
6113
+ const command = opts.command ?? process.execPath;
6114
+ const piDir = path11.join(ctx.workingDirectory, ".slock", "pi");
6115
+ const sessionDir = path11.join(piDir, "sessions");
6116
+ mkdirSync4(sessionDir, { recursive: true });
6117
+ const slock = await prepareCliTransport(ctx, { NO_COLOR: "1" });
6118
+ const runnerPath = opts.runnerPath ?? resolvePiSdkRunnerPath();
6119
+ const agentDir = opts.agentDir ?? getAgentDir();
6120
+ const runnerConfigPath = path11.join(
6121
+ piDir,
6122
+ `sdk-run-${(ctx.launchId || "launch").replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`
6123
+ );
6124
+ const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
6125
+ const runnerConfig = {
6126
+ cwd: ctx.workingDirectory,
6127
+ agentDir,
6128
+ sessionDir,
6129
+ sessionId: ctx.config.sessionId || null,
6130
+ standingPrompt: ctx.standingPrompt,
6131
+ prompt: turnPrompt,
6132
+ model: ctx.config.model && ctx.config.model !== "default" ? ctx.config.model : null
6133
+ };
6134
+ writeFileSync7(runnerConfigPath, `${JSON.stringify(runnerConfig)}
6135
+ `, { encoding: "utf8", mode: 384 });
6136
+ const args = [
6137
+ ...buildPiSdkNodeArgs(runnerPath),
6138
+ "--config",
6139
+ runnerConfigPath
6140
+ ];
6141
+ return {
6142
+ command,
6143
+ args,
6144
+ env: slock.spawnEnv,
6145
+ sessionDir,
6146
+ agentDir,
6147
+ runnerConfigPath,
6148
+ sdkVersion: PI_SDK_VERSION
6149
+ };
6150
+ }
6151
+ function isSystemFirstMessageTask2(message) {
6152
+ return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
6153
+ }
6154
+ function buildPiSystemPrompt(config) {
6155
+ return buildCliTransportSystemPrompt(config, {
6156
+ toolPrefix: CHAT_MCP_TOOL_PREFIX2,
6157
+ extraCriticalRules: [
6158
+ "- 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."
6159
+ ],
6160
+ postStartupNotes: [
6161
+ "**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."
6162
+ ],
6163
+ includeStdinNotificationSection: false,
6164
+ messageNotificationStyle: "poll"
6165
+ });
6166
+ }
6167
+ function contentText(content) {
6168
+ if (!content) return "";
6169
+ const chunks = [];
6170
+ for (const item of content) {
6171
+ if (item.type === "text" && typeof item.text === "string") {
6172
+ chunks.push(item.text);
6173
+ }
6174
+ }
6175
+ return chunks.join("");
6176
+ }
6177
+ function apiKeyErrorMessage(line) {
6178
+ const trimmed = line.trim();
6179
+ if (!trimmed) return null;
6180
+ if (/no api key found/i.test(trimmed)) return trimmed;
6181
+ if (/api key.+required/i.test(trimmed)) return trimmed;
6182
+ if (/no models available/i.test(trimmed)) return trimmed;
6183
+ return null;
6184
+ }
6185
+ var PiDriver = class {
6186
+ id = "pi";
6187
+ lifecycle = {
6188
+ kind: "per_turn",
6189
+ start: "defer_until_concrete_message",
6190
+ exit: "terminate_on_turn_end",
6191
+ inFlightWake: "coalesce_into_pending"
6192
+ };
6193
+ communication = {
6194
+ chat: "slock_cli",
6195
+ runtimeControl: "none"
6196
+ };
6197
+ session = {
6198
+ recovery: "resume_or_fresh"
6199
+ };
6200
+ model = {
6201
+ detectedModelsVerifiedAs: "launchable",
6202
+ toLaunchSpec: async (modelId, ctx) => {
6203
+ if (!ctx) return modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] };
6204
+ const launchCtx = {
6205
+ ...ctx,
6206
+ config: {
6207
+ ...ctx.config,
6208
+ model: modelId
6209
+ }
6210
+ };
6211
+ const launch = await buildPiLaunchOptions(launchCtx);
6212
+ return {
6213
+ args: launch.args,
6214
+ env: launch.env,
6215
+ configFiles: [launch.runnerConfigPath],
6216
+ params: {
6217
+ agentDir: launch.agentDir,
6218
+ sessionDir: launch.sessionDir,
6219
+ sdkVersion: launch.sdkVersion,
6220
+ resources: "extensions/skills/prompt-templates/themes/context-files disabled by Slock policy"
6221
+ }
6222
+ };
6223
+ }
6224
+ };
6225
+ supportsStdinNotification = false;
6226
+ mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
6227
+ busyDeliveryMode = "none";
6228
+ terminateProcessOnTurnEnd = true;
6229
+ deferSpawnUntilMessage = true;
6230
+ usesSlockCliForCommunication = true;
6231
+ sessionId = null;
6232
+ sessionAnnounced = false;
6233
+ apiKeyErrorAnnounced = false;
6234
+ turnEnded = false;
6235
+ assistantTextByMessageId = /* @__PURE__ */ new Map();
6236
+ shouldDeferWakeMessage(message) {
6237
+ return isSystemFirstMessageTask2(message);
6238
+ }
6239
+ probe() {
6240
+ return probePi();
6241
+ }
6242
+ async detectModels() {
6243
+ return null;
6244
+ }
6245
+ async spawn(ctx) {
6246
+ this.sessionId = ctx.config.sessionId || null;
6247
+ this.sessionAnnounced = false;
6248
+ this.apiKeyErrorAnnounced = false;
6249
+ this.turnEnded = false;
6250
+ this.assistantTextByMessageId.clear();
6251
+ const unsupportedMessage = unsupportedPiVersionMessage(PI_SDK_VERSION);
6252
+ if (unsupportedMessage) throw new Error(unsupportedMessage);
6253
+ const launch = await buildPiLaunchOptions(ctx);
6254
+ const proc = spawn9(launch.command, launch.args, {
6255
+ cwd: ctx.workingDirectory,
6256
+ stdio: ["pipe", "pipe", "pipe"],
6257
+ env: launch.env,
6258
+ shell: false
6259
+ });
6260
+ proc.stdin?.end();
6261
+ return { process: proc };
6262
+ }
6263
+ parseLine(line) {
6264
+ let event;
6265
+ try {
6266
+ event = JSON.parse(line);
6267
+ } catch {
6268
+ if (this.apiKeyErrorAnnounced) return [];
6269
+ const message = apiKeyErrorMessage(line);
6270
+ if (!message) return [];
6271
+ this.apiKeyErrorAnnounced = true;
6272
+ this.turnEnded = true;
6273
+ return [
6274
+ { kind: "error", message },
6275
+ { kind: "turn_end", sessionId: this.sessionId || void 0 }
6276
+ ];
6277
+ }
6278
+ const events = [];
6279
+ if (event.type === "session" && event.id) {
6280
+ this.sessionId = event.id;
6281
+ }
6282
+ if (!this.sessionAnnounced && this.sessionId) {
6283
+ events.push({ kind: "session_init", sessionId: this.sessionId });
6284
+ this.sessionAnnounced = true;
6285
+ }
6286
+ switch (event.type) {
6287
+ case "agent_start":
6288
+ case "turn_start":
6289
+ events.push({ kind: "thinking", text: "" });
6290
+ break;
6291
+ case "message_update":
6292
+ case "message_end":
6293
+ if (event.message?.role === "assistant") {
6294
+ const key = event.message.id || "current";
6295
+ const currentText = contentText(event.message.content);
6296
+ const previousText = this.assistantTextByMessageId.get(key) ?? "";
6297
+ if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
6298
+ events.push({ kind: "text", text: currentText.slice(previousText.length) });
6299
+ } else if (currentText && currentText !== previousText) {
6300
+ events.push({ kind: "text", text: currentText });
6301
+ }
6302
+ this.assistantTextByMessageId.set(key, currentText);
6303
+ if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
6304
+ events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
6305
+ }
6306
+ }
6307
+ break;
6308
+ case "tool_execution_start":
6309
+ events.push({
6310
+ kind: "tool_call",
6311
+ name: event.toolName || "unknown_tool",
6312
+ input: event.args
6313
+ });
6314
+ break;
6315
+ case "tool_execution_end":
6316
+ events.push({
6317
+ kind: "tool_output",
6318
+ name: event.toolName || "unknown_tool"
6319
+ });
6320
+ if (event.isError) {
6321
+ events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
6322
+ }
6323
+ break;
6324
+ case "compaction_start":
6325
+ events.push({ kind: "compaction_started" });
6326
+ break;
6327
+ case "compaction_end":
6328
+ events.push({ kind: "compaction_finished" });
6329
+ if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
6330
+ break;
6331
+ case "turn_end":
6332
+ case "agent_end":
6333
+ if (!this.turnEnded) {
6334
+ events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
6335
+ this.turnEnded = true;
6336
+ }
6337
+ break;
6338
+ }
6339
+ return events;
6340
+ }
6341
+ encodeStdinMessage(_text, _sessionId, _opts) {
6342
+ return null;
6343
+ }
6344
+ buildSystemPrompt(config, _agentId) {
6345
+ return buildPiSystemPrompt(config);
6346
+ }
6347
+ };
6348
+
5743
6349
  // src/drivers/index.ts
5744
6350
  var driverFactories = {
5745
6351
  claude: () => new ClaudeDriver(),
@@ -5749,7 +6355,8 @@ var driverFactories = {
5749
6355
  cursor: () => new CursorDriver(),
5750
6356
  gemini: () => new GeminiDriver(),
5751
6357
  kimi: () => new KimiDriver(),
5752
- opencode: () => new OpenCodeDriver()
6358
+ opencode: () => new OpenCodeDriver(),
6359
+ pi: () => new PiDriver()
5753
6360
  };
5754
6361
  function getDriver(runtimeId) {
5755
6362
  const createDriver = driverFactories[runtimeId];
@@ -5762,7 +6369,7 @@ function getDriver(runtimeId) {
5762
6369
 
5763
6370
  // src/workspaces.ts
5764
6371
  import { readdir, rm, stat } from "fs/promises";
5765
- import path11 from "path";
6372
+ import path12 from "path";
5766
6373
  function isValidWorkspaceDirectoryName(directoryName) {
5767
6374
  return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
5768
6375
  }
@@ -5770,7 +6377,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
5770
6377
  if (!isValidWorkspaceDirectoryName(directoryName)) {
5771
6378
  return null;
5772
6379
  }
5773
- return path11.join(dataDir, directoryName);
6380
+ return path12.join(dataDir, directoryName);
5774
6381
  }
5775
6382
  function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
5776
6383
  return {
@@ -5819,7 +6426,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
5819
6426
  return summary;
5820
6427
  }
5821
6428
  const childSummaries = await Promise.all(
5822
- entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
6429
+ entries.map((entry) => summarizeWorkspaceEntry(path12.join(dirPath, entry.name), entry))
5823
6430
  );
5824
6431
  for (const childSummary of childSummaries) {
5825
6432
  summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
@@ -5838,7 +6445,7 @@ async function scanWorkspaceDirectories(dataDir) {
5838
6445
  if (!entry.isDirectory()) {
5839
6446
  return null;
5840
6447
  }
5841
- const dirPath = path11.join(dataDir, entry.name);
6448
+ const dirPath = path12.join(dataDir, entry.name);
5842
6449
  try {
5843
6450
  const summary = await summarizeWorkspaceDirectory(dirPath);
5844
6451
  return {
@@ -6278,12 +6885,12 @@ function findSessionJsonl(root, predicate) {
6278
6885
  for (const entry of entries) {
6279
6886
  if (++visited > maxEntries) return null;
6280
6887
  if (!entry.isFile() || !predicate(entry.name)) continue;
6281
- return path12.join(dir, entry.name);
6888
+ return path13.join(dir, entry.name);
6282
6889
  }
6283
6890
  for (const entry of entries) {
6284
6891
  if (++visited > maxEntries) return null;
6285
6892
  if (!entry.isDirectory()) continue;
6286
- const found = visit(path12.join(dir, entry.name), depth - 1);
6893
+ const found = visit(path13.join(dir, entry.name), depth - 1);
6287
6894
  if (found) return found;
6288
6895
  }
6289
6896
  return null;
@@ -6296,10 +6903,10 @@ function safeSessionFilename(value) {
6296
6903
  }
6297
6904
  function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
6298
6905
  try {
6299
- const dir = path12.join(fallbackDir, ".slock", "runtime-sessions");
6300
- mkdirSync4(dir, { recursive: true });
6301
- const filePath = path12.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
6302
- writeFileSync7(filePath, JSON.stringify({
6906
+ const dir = path13.join(fallbackDir, ".slock", "runtime-sessions");
6907
+ mkdirSync5(dir, { recursive: true });
6908
+ const filePath = path13.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
6909
+ writeFileSync8(filePath, JSON.stringify({
6303
6910
  type: "runtime_session_handoff",
6304
6911
  runtime,
6305
6912
  sessionId,
@@ -6318,7 +6925,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
6318
6925
  }
6319
6926
  }
6320
6927
  function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), fallbackDir) {
6321
- const directPath = path12.isAbsolute(sessionId) ? sessionId : null;
6928
+ const directPath = path13.isAbsolute(sessionId) ? sessionId : null;
6322
6929
  if (directPath) {
6323
6930
  try {
6324
6931
  if (statSync(directPath).isFile()) {
@@ -6327,7 +6934,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), f
6327
6934
  } catch {
6328
6935
  }
6329
6936
  }
6330
- 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;
6937
+ 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;
6331
6938
  if (!resolvedPath && fallbackDir) {
6332
6939
  const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
6333
6940
  if (fallback) return fallback;
@@ -6416,7 +7023,7 @@ function dynamicClaimInstruction(driver) {
6416
7023
  }
6417
7024
  function formatIncomingMessage(message, driver) {
6418
7025
  const threadJoinPrefix = message.thread_join_context ? [
6419
- `[System: You were added to a new thread via @mention. Read this context before replying.]`,
7026
+ `[Slock thread context: you were added to a new thread via @mention.]`,
6420
7027
  `parent: ${message.thread_join_context.parent_target}`,
6421
7028
  `thread: ${message.thread_join_context.thread_target}`,
6422
7029
  `suggested next step: ${driver?.usesSlockCliForCommunication ? `slock message read --channel "${message.thread_join_context.suggested_read_history_target}"` : `${communicationCommand(driver, "read_history")}(channel="${message.thread_join_context.suggested_read_history_target}")`}`,
@@ -7242,7 +7849,7 @@ function getBusyDeliveryNote(driver) {
7242
7849
  if (driver.busyDeliveryMode === "gated") {
7243
7850
  return "\n\nNote: While you are busy, the daemon may write batched inbox-count notifications into your active turn at runtime-observed safe boundaries. Call check_messages to read the pending messages before context-sensitive side effects.";
7244
7851
  }
7245
- return "\n\nNote: While you are busy, you may receive [System notification: ...] messages. Finish your current step, then call check_messages to check for messages.";
7852
+ return "\n\nNote: While you are busy, you may receive [Slock inbox notice: ...] messages. Finish your current step, then call check_messages to check for messages.";
7246
7853
  }
7247
7854
  var NATIVE_STANDING_PROMPT_STARTUP_INPUT = (
7248
7855
  // Claude Code 2.1.114 treats "follow your system prompt" style user turns as
@@ -7280,6 +7887,7 @@ var AgentProcessManager = class _AgentProcessManager {
7280
7887
  deliveryTraceContexts = /* @__PURE__ */ new WeakMap();
7281
7888
  processExitTraceAttrs = /* @__PURE__ */ new WeakMap();
7282
7889
  agentVisibleBoundaries = /* @__PURE__ */ new Map();
7890
+ agentVisibleMessageIds = /* @__PURE__ */ new Map();
7283
7891
  constructor(chatBridgePath, sendToServer, daemonApiKey, opts) {
7284
7892
  this.chatBridgePath = chatBridgePath;
7285
7893
  this.slockCliPath = opts.slockCliPath ?? "";
@@ -7325,6 +7933,24 @@ var AgentProcessManager = class _AgentProcessManager {
7325
7933
  getVisibleBoundary(agentId, target) {
7326
7934
  return this.agentVisibleBoundaries.get(agentId)?.get(target);
7327
7935
  }
7936
+ visibleMessageIdMap(agentId) {
7937
+ let map = this.agentVisibleMessageIds.get(agentId);
7938
+ if (!map) {
7939
+ map = /* @__PURE__ */ new Map();
7940
+ this.agentVisibleMessageIds.set(agentId, map);
7941
+ }
7942
+ return map;
7943
+ }
7944
+ getVisibleMessageIdSet(agentId, target) {
7945
+ return this.agentVisibleMessageIds.get(agentId)?.get(target);
7946
+ }
7947
+ isVisibleMessageModelSeen(agentId, target, message) {
7948
+ const seq = Number(message.seq ?? 0);
7949
+ const boundary = this.getVisibleBoundary(agentId, target);
7950
+ if (Number.isFinite(seq) && seq > 0 && typeof boundary === "number" && boundary >= Math.floor(seq)) return true;
7951
+ const id = typeof message.message_id === "string" ? message.message_id : typeof message.id === "string" ? message.id : "";
7952
+ return id.length > 0 && this.getVisibleMessageIdSet(agentId, target)?.has(id) === true;
7953
+ }
7328
7954
  scheduleStdinNotification(agentId, ap, delayMs) {
7329
7955
  return ap.notifications.schedule(() => {
7330
7956
  this.sendStdinNotification(agentId);
@@ -7359,13 +7985,14 @@ var AgentProcessManager = class _AgentProcessManager {
7359
7985
  };
7360
7986
  for (const message of input.messages) {
7361
7987
  const seq = Number(message.seq ?? 0);
7362
- if (!Number.isFinite(seq) || seq <= 0) continue;
7363
7988
  const target = input.target ?? formatVisibleMessageTarget(message);
7364
7989
  if (!target) continue;
7365
7990
  const bucket = ensureBucket(target);
7366
- bucket.maxSeq = Math.max(bucket.maxSeq, Math.floor(seq));
7367
- bucket.seqs.add(Math.floor(seq));
7368
- const id = typeof message.id === "string" ? message.id : typeof message.message_id === "string" ? message.message_id : null;
7991
+ if (Number.isFinite(seq) && seq > 0) {
7992
+ bucket.maxSeq = Math.max(bucket.maxSeq, Math.floor(seq));
7993
+ bucket.seqs.add(Math.floor(seq));
7994
+ }
7995
+ const id = typeof message.message_id === "string" ? message.message_id : typeof message.id === "string" ? message.id : null;
7369
7996
  if (id) bucket.ids.add(id);
7370
7997
  }
7371
7998
  if (input.target && typeof input.boundarySeq === "number" && Number.isFinite(input.boundarySeq) && input.boundarySeq > 0) {
@@ -7374,10 +8001,19 @@ var AgentProcessManager = class _AgentProcessManager {
7374
8001
  }
7375
8002
  if (byTarget.size === 0) return;
7376
8003
  const boundaryMap = this.visibleBoundaryMap(agentId);
8004
+ const visibleIds = this.visibleMessageIdMap(agentId);
7377
8005
  for (const [target, bucket] of byTarget) {
7378
8006
  const highWaterSeq = Math.max(bucket.maxSeq, bucket.boundarySeq);
7379
8007
  const previous = boundaryMap.get(target) ?? 0;
7380
8008
  boundaryMap.set(target, Math.max(previous, highWaterSeq));
8009
+ if (bucket.ids.size > 0) {
8010
+ let targetIds = visibleIds.get(target);
8011
+ if (!targetIds) {
8012
+ targetIds = /* @__PURE__ */ new Set();
8013
+ visibleIds.set(target, targetIds);
8014
+ }
8015
+ for (const id of bucket.ids) targetIds.add(id);
8016
+ }
7381
8017
  }
7382
8018
  const suppress = (messages) => {
7383
8019
  if (!messages || messages.length === 0) return 0;
@@ -7410,6 +8046,7 @@ var AgentProcessManager = class _AgentProcessManager {
7410
8046
  return {
7411
8047
  getBoundary: (target) => this.getVisibleBoundary(agentId, target),
7412
8048
  getPendingMessages: (target) => this.pendingVisibleMessages(agentId, target),
8049
+ isMessageModelSeen: ({ target, message }) => this.isVisibleMessageModelSeen(agentId, target, message),
7413
8050
  getAllPendingMessages: () => this.allPendingVisibleMessages(agentId),
7414
8051
  consumeVisibleMessages: (input) => this.consumeVisibleMessages(agentId, input),
7415
8052
  recordDrainOutcome: (input) => {
@@ -7721,7 +8358,7 @@ var AgentProcessManager = class _AgentProcessManager {
7721
8358
  );
7722
8359
  wakeMessage = void 0;
7723
8360
  }
7724
- const agentDataDir = path12.join(this.dataDir, agentId);
8361
+ const agentDataDir = path13.join(this.dataDir, agentId);
7725
8362
  await mkdir(agentDataDir, { recursive: true });
7726
8363
  let runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
7727
8364
  const legacyRuntimeProfileControl = runtimeConfig.runtimeProfileControl?.kind === "migration" ? runtimeConfig.runtimeProfileControl : null;
@@ -7735,23 +8372,23 @@ var AgentProcessManager = class _AgentProcessManager {
7735
8372
  );
7736
8373
  runtimeConfig = { ...runtimeConfig, runtimeProfileControl: null };
7737
8374
  }
7738
- const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
8375
+ const memoryMdPath = path13.join(agentDataDir, "MEMORY.md");
7739
8376
  try {
7740
8377
  await access(memoryMdPath);
7741
8378
  } catch {
7742
8379
  const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
7743
8380
  await writeFile(memoryMdPath, initialMemoryMd);
7744
8381
  }
7745
- const notesDir = path12.join(agentDataDir, "notes");
8382
+ const notesDir = path13.join(agentDataDir, "notes");
7746
8383
  await mkdir(notesDir, { recursive: true });
7747
8384
  if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
7748
8385
  const seedFiles = buildOnboardingSeedFiles();
7749
8386
  for (const { relativePath, content } of seedFiles) {
7750
- const fullPath = path12.join(agentDataDir, relativePath);
8387
+ const fullPath = path13.join(agentDataDir, relativePath);
7751
8388
  try {
7752
8389
  await access(fullPath);
7753
8390
  } catch {
7754
- await mkdir(path12.dirname(fullPath), { recursive: true });
8391
+ await mkdir(path13.dirname(fullPath), { recursive: true });
7755
8392
  await writeFile(fullPath, content);
7756
8393
  }
7757
8394
  }
@@ -8623,7 +9260,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8623
9260
  return true;
8624
9261
  }
8625
9262
  async resetWorkspace(agentId) {
8626
- const agentDataDir = path12.join(this.dataDir, agentId);
9263
+ const agentDataDir = path13.join(this.dataDir, agentId);
8627
9264
  try {
8628
9265
  await rm2(agentDataDir, { recursive: true, force: true });
8629
9266
  logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
@@ -8684,7 +9321,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8684
9321
  return result;
8685
9322
  }
8686
9323
  buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
8687
- const workspacePath = path12.join(this.dataDir, agentId);
9324
+ const workspacePath = path13.join(this.dataDir, agentId);
8688
9325
  return {
8689
9326
  agentId,
8690
9327
  launchId,
@@ -8941,7 +9578,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8941
9578
  }
8942
9579
  // Workspace file browsing
8943
9580
  async getFileTree(agentId, dirPath) {
8944
- const agentDir = path12.join(this.dataDir, agentId);
9581
+ const agentDir = path13.join(this.dataDir, agentId);
8945
9582
  try {
8946
9583
  await stat2(agentDir);
8947
9584
  } catch {
@@ -8949,8 +9586,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8949
9586
  }
8950
9587
  let targetDir = agentDir;
8951
9588
  if (dirPath) {
8952
- const resolved = path12.resolve(agentDir, dirPath);
8953
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
9589
+ const resolved = path13.resolve(agentDir, dirPath);
9590
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
8954
9591
  return [];
8955
9592
  }
8956
9593
  targetDir = resolved;
@@ -8958,14 +9595,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8958
9595
  return this.listDirectoryChildren(targetDir, agentDir);
8959
9596
  }
8960
9597
  async readFile(agentId, filePath) {
8961
- const agentDir = path12.join(this.dataDir, agentId);
8962
- const resolved = path12.resolve(agentDir, filePath);
8963
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
9598
+ const agentDir = path13.join(this.dataDir, agentId);
9599
+ const resolved = path13.resolve(agentDir, filePath);
9600
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
8964
9601
  throw new Error("Access denied");
8965
9602
  }
8966
9603
  const info = await stat2(resolved);
8967
9604
  if (info.isDirectory()) throw new Error("Cannot read a directory");
8968
- const ext = path12.extname(resolved).toLowerCase();
9605
+ const ext = path13.extname(resolved).toLowerCase();
8969
9606
  if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
8970
9607
  if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
8971
9608
  const content = await readFile(resolved, "utf-8");
@@ -9000,13 +9637,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9000
9637
  const agent = this.agents.get(agentId);
9001
9638
  const runtime = runtimeHint || agent?.config.runtime || "claude";
9002
9639
  const home = os5.homedir();
9003
- const workspaceDir = path12.join(this.dataDir, agentId);
9640
+ const workspaceDir = path13.join(this.dataDir, agentId);
9004
9641
  const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
9005
9642
  const globalResults = await Promise.all(
9006
- paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
9643
+ paths.global.map((p) => this.scanSkillsDir(path13.join(home, p)))
9007
9644
  );
9008
9645
  const workspaceResults = await Promise.all(
9009
- paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
9646
+ paths.workspace.map((p) => this.scanSkillsDir(path13.join(workspaceDir, p)))
9010
9647
  );
9011
9648
  const dedup = (skills) => {
9012
9649
  const seen = /* @__PURE__ */ new Set();
@@ -9035,7 +9672,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9035
9672
  const skills = [];
9036
9673
  for (const entry of entries) {
9037
9674
  if (entry.isDirectory() || entry.isSymbolicLink()) {
9038
- const skillMd = path12.join(dir, entry.name, "SKILL.md");
9675
+ const skillMd = path13.join(dir, entry.name, "SKILL.md");
9039
9676
  try {
9040
9677
  const content = await readFile(skillMd, "utf-8");
9041
9678
  const skill = this.parseSkillMd(entry.name, content);
@@ -9046,7 +9683,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9046
9683
  } else if (entry.name.endsWith(".md")) {
9047
9684
  const cmdName = entry.name.replace(/\.md$/, "");
9048
9685
  try {
9049
- const content = await readFile(path12.join(dir, entry.name), "utf-8");
9686
+ const content = await readFile(path13.join(dir, entry.name), "utf-8");
9050
9687
  const skill = this.parseSkillMd(cmdName, content);
9051
9688
  skill.sourcePath = dir;
9052
9689
  skills.push(skill);
@@ -9606,7 +10243,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9606
10243
  }
9607
10244
  }
9608
10245
  isThinkingBlockMutationError(message) {
9609
- return /thinking.*redacted_thinking|redacted_thinking.*thinking/i.test(message) && /cannot be modified/i.test(message);
10246
+ return /thinking.*redacted_thinking|redacted_thinking.*thinking/i.test(message) && /cannot be modified/i.test(message) || /messages\.\d+\.content\.\d+\.text\.start_timestamp:\s*Extra inputs are not permitted/i.test(message);
9610
10247
  }
9611
10248
  markRuntimeProgressStaleIfNeeded(agentId, ap) {
9612
10249
  if (ap.lastActivity !== "working" && ap.lastActivity !== "thinking") return false;
@@ -9935,15 +10572,23 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9935
10572
  }
9936
10573
  }
9937
10574
  recordRuntimeTelemetry(agentId, ap, event) {
10575
+ const telemetryAttrs = {
10576
+ ...event.attrs,
10577
+ ...event.source ? { source: event.source } : {},
10578
+ ...event.usageKind ? { usageKind: event.usageKind } : {},
10579
+ ...event.sessionId ? { sessionId: event.sessionId } : {},
10580
+ ...event.turnId ? { turnId: event.turnId } : {},
10581
+ ...event.runtimeResultId ? { runtimeResultId: event.runtimeResultId } : {}
10582
+ };
9938
10583
  const attrs = {
9939
10584
  agentId,
9940
10585
  launchId: ap.launchId || void 0,
9941
10586
  runtime: ap.config.runtime,
9942
10587
  model: ap.config.model,
9943
10588
  telemetry_name: event.name,
9944
- ...event.attrs
10589
+ ...telemetryAttrs
9945
10590
  };
9946
- ap.runtimeTraceSpan?.addEvent(`runtime.telemetry.${event.name}`, event.attrs);
10591
+ ap.runtimeTraceSpan?.addEvent(`runtime.telemetry.${event.name}`, telemetryAttrs);
9947
10592
  this.recordDaemonTrace(`daemon.runtime.telemetry.${event.name}`, attrs);
9948
10593
  }
9949
10594
  sendAgentStatus(agentId, status, launchId) {
@@ -10017,7 +10662,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10017
10662
  }
10018
10663
  const inboxCount = ap.inbox.length;
10019
10664
  if (inboxCount === 0) return false;
10020
- const notification = `[System notification: You have ${inboxCount} pending inbox message${inboxCount > 1 ? "s" : ""}. Call check_messages to read ${inboxCount > 1 ? "them" : "it"} when you're ready.]`;
10665
+ const notification = `[Slock inbox notice: You have ${inboxCount} pending inbox message${inboxCount > 1 ? "s" : ""}. Call check_messages to read ${inboxCount > 1 ? "them" : "it"} when you're ready.]`;
10021
10666
  logger.info(`[Agent ${agentId}] Sending stdin notification: ${inboxCount} pending inbox message(s)`);
10022
10667
  const encoded = ap.driver.encodeStdinMessage(notification, ap.sessionId, { mode: "busy" });
10023
10668
  if (encoded) {
@@ -10164,8 +10809,8 @@ ${RESPONSE_TARGET_HINT}`);
10164
10809
  const nodes = [];
10165
10810
  for (const entry of entries) {
10166
10811
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
10167
- const fullPath = path12.join(dir, entry.name);
10168
- const relativePath = path12.relative(rootDir, fullPath);
10812
+ const fullPath = path13.join(dir, entry.name);
10813
+ const relativePath = path13.relative(rootDir, fullPath);
10169
10814
  let info;
10170
10815
  try {
10171
10816
  info = await stat2(fullPath);
@@ -10470,9 +11115,9 @@ var ReminderCache = class {
10470
11115
 
10471
11116
  // src/machineLock.ts
10472
11117
  import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
10473
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
11118
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync9 } from "fs";
10474
11119
  import os6 from "os";
10475
- import path13 from "path";
11120
+ import path14 from "path";
10476
11121
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
10477
11122
  var DaemonMachineLockConflictError = class extends Error {
10478
11123
  code = "DAEMON_MACHINE_LOCK_HELD";
@@ -10494,7 +11139,7 @@ function resolveDefaultMachineStateRoot() {
10494
11139
  return resolveSlockHomePath("machines");
10495
11140
  }
10496
11141
  function ownerPath(lockDir) {
10497
- return path13.join(lockDir, "owner.json");
11142
+ return path14.join(lockDir, "owner.json");
10498
11143
  }
10499
11144
  function readOwner(lockDir) {
10500
11145
  try {
@@ -10524,13 +11169,13 @@ function acquireDaemonMachineLock(options) {
10524
11169
  const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
10525
11170
  const fingerprint = apiKeyFingerprint(options.apiKey);
10526
11171
  const lockId = getDaemonMachineLockId(options.apiKey);
10527
- const machineDir = path13.join(rootDir, lockId);
10528
- const lockDir = path13.join(machineDir, "daemon.lock");
11172
+ const machineDir = path14.join(rootDir, lockId);
11173
+ const lockDir = path14.join(machineDir, "daemon.lock");
10529
11174
  const token = randomUUID3();
10530
- mkdirSync5(machineDir, { recursive: true });
11175
+ mkdirSync6(machineDir, { recursive: true });
10531
11176
  for (let attempt = 0; attempt < 2; attempt += 1) {
10532
11177
  try {
10533
- mkdirSync5(lockDir);
11178
+ mkdirSync6(lockDir);
10534
11179
  const owner = {
10535
11180
  pid: process.pid,
10536
11181
  token,
@@ -10540,7 +11185,7 @@ function acquireDaemonMachineLock(options) {
10540
11185
  apiKeyFingerprint: fingerprint.slice(0, 16)
10541
11186
  };
10542
11187
  try {
10543
- writeFileSync8(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
11188
+ writeFileSync9(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
10544
11189
  `, { mode: 384 });
10545
11190
  } catch (err) {
10546
11191
  rmSync3(lockDir, { recursive: true, force: true });
@@ -10553,7 +11198,15 @@ function acquireDaemonMachineLock(options) {
10553
11198
  release: () => {
10554
11199
  const currentOwner = readOwner(lockDir);
10555
11200
  if (currentOwner?.pid === process.pid && currentOwner.token === token) {
10556
- rmSync3(lockDir, { recursive: true, force: true });
11201
+ const released = { ...currentOwner, pid: 0 };
11202
+ try {
11203
+ writeFileSync9(ownerPath(lockDir), `${JSON.stringify(released, null, 2)}
11204
+ `, {
11205
+ mode: 384
11206
+ });
11207
+ } catch {
11208
+ rmSync3(lockDir, { recursive: true, force: true });
11209
+ }
10557
11210
  }
10558
11211
  }
10559
11212
  };
@@ -10577,8 +11230,8 @@ function acquireDaemonMachineLock(options) {
10577
11230
  }
10578
11231
 
10579
11232
  // src/localTraceSink.ts
10580
- import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync2, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
10581
- import path14 from "path";
11233
+ import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync2, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync10 } from "fs";
11234
+ import path15 from "path";
10582
11235
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
10583
11236
  var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
10584
11237
  var DEFAULT_MAX_FILES = 8;
@@ -10615,7 +11268,7 @@ var LocalRotatingTraceSink = class {
10615
11268
  currentSize = 0;
10616
11269
  sequence = 0;
10617
11270
  constructor(options) {
10618
- this.traceDir = path14.join(options.machineDir, "traces");
11271
+ this.traceDir = path15.join(options.machineDir, "traces");
10619
11272
  this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
10620
11273
  const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
10621
11274
  const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
@@ -10641,15 +11294,15 @@ var LocalRotatingTraceSink = class {
10641
11294
  return this.currentFile;
10642
11295
  }
10643
11296
  ensureFile(nextBytes) {
10644
- mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
11297
+ mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
10645
11298
  const nowMs = this.nowMsProvider();
10646
11299
  const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
10647
11300
  if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
10648
- this.currentFile = path14.join(
11301
+ this.currentFile = path15.join(
10649
11302
  this.traceDir,
10650
11303
  `daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
10651
11304
  );
10652
- writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
11305
+ writeFileSync10(this.currentFile, "", { flag: "a", mode: 384 });
10653
11306
  this.currentSize = statSync3(this.currentFile).size;
10654
11307
  this.currentFileOpenedAtMs = nowMs;
10655
11308
  this.pruneOldFiles();
@@ -10660,7 +11313,7 @@ var LocalRotatingTraceSink = class {
10660
11313
  const excess = files.length - this.maxFiles;
10661
11314
  if (excess <= 0) return;
10662
11315
  for (const file of files.slice(0, excess)) {
10663
- rmSync4(path14.join(this.traceDir, file), { force: true });
11316
+ rmSync4(path15.join(this.traceDir, file), { force: true });
10664
11317
  }
10665
11318
  }
10666
11319
  };
@@ -10751,11 +11404,11 @@ function isDiagnosticErrorAttr(key) {
10751
11404
  import { createHash as createHash6, randomUUID as randomUUID4 } from "crypto";
10752
11405
  import { gzipSync } from "zlib";
10753
11406
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
10754
- import path15 from "path";
11407
+ import path16 from "path";
10755
11408
 
10756
11409
  // src/directUploadCapability.ts
10757
- function joinUrl(base, path17) {
10758
- return `${base.replace(/\/+$/, "")}${path17}`;
11410
+ function joinUrl(base, path18) {
11411
+ return `${base.replace(/\/+$/, "")}${path18}`;
10759
11412
  }
10760
11413
  function jsonHeaders(apiKey) {
10761
11414
  return {
@@ -10974,7 +11627,7 @@ var DaemonTraceBundleUploader = class {
10974
11627
  }, nextMs);
10975
11628
  }
10976
11629
  async findUploadCandidates() {
10977
- const traceDir = path15.join(this.options.machineDir, "traces");
11630
+ const traceDir = path16.join(this.options.machineDir, "traces");
10978
11631
  let names;
10979
11632
  try {
10980
11633
  names = await readdir3(traceDir);
@@ -10986,8 +11639,8 @@ var DaemonTraceBundleUploader = class {
10986
11639
  const currentFile = this.options.currentFileProvider?.();
10987
11640
  const candidates = [];
10988
11641
  for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
10989
- const file = path15.join(traceDir, name);
10990
- if (currentFile && path15.resolve(file) === path15.resolve(currentFile)) continue;
11642
+ const file = path16.join(traceDir, name);
11643
+ if (currentFile && path16.resolve(file) === path16.resolve(currentFile)) continue;
10991
11644
  if (await this.isUploaded(file)) continue;
10992
11645
  try {
10993
11646
  const info = await stat3(file);
@@ -11061,8 +11714,8 @@ var DaemonTraceBundleUploader = class {
11061
11714
  }
11062
11715
  }
11063
11716
  uploadStatePath(file) {
11064
- const stateDir = path15.join(this.options.machineDir, "trace-uploads");
11065
- return path15.join(stateDir, `${path15.basename(file)}.uploaded.json`);
11717
+ const stateDir = path16.join(this.options.machineDir, "trace-uploads");
11718
+ return path16.join(stateDir, `${path16.basename(file)}.uploaded.json`);
11066
11719
  }
11067
11720
  async isUploaded(file) {
11068
11721
  try {
@@ -11074,9 +11727,9 @@ var DaemonTraceBundleUploader = class {
11074
11727
  }
11075
11728
  async markUploaded(file, metadata) {
11076
11729
  const stateFile = this.uploadStatePath(file);
11077
- await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
11730
+ await mkdir2(path16.dirname(stateFile), { recursive: true, mode: 448 });
11078
11731
  await writeFile2(stateFile, `${JSON.stringify({
11079
- file: path15.basename(file),
11732
+ file: path16.basename(file),
11080
11733
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
11081
11734
  ...metadata
11082
11735
  }, null, 2)}
@@ -11098,7 +11751,7 @@ var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
11098
11751
  var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels", "knowledge"];
11099
11752
  var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
11100
11753
  var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
11101
- var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
11754
+ var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
11102
11755
  var RunnerCredentialMintError2 = class extends Error {
11103
11756
  code;
11104
11757
  retryable;
@@ -11134,9 +11787,9 @@ function runnerCredentialErrorDetail2(error) {
11134
11787
  async function waitForRunnerCredentialRetry2() {
11135
11788
  await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
11136
11789
  }
11137
- function parseDaemonCliArgs(args) {
11790
+ function parseDaemonCliArgs(args, env = {}) {
11138
11791
  let serverUrl = "";
11139
- let apiKey = "";
11792
+ let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
11140
11793
  for (let i = 0; i < args.length; i++) {
11141
11794
  if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
11142
11795
  if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
@@ -11153,23 +11806,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
11153
11806
  }
11154
11807
  }
11155
11808
  function resolveChatBridgePath(moduleUrl = import.meta.url) {
11156
- const dirname = path16.dirname(fileURLToPath(moduleUrl));
11157
- const jsPath = path16.resolve(dirname, "chat-bridge.js");
11809
+ const dirname = path17.dirname(fileURLToPath2(moduleUrl));
11810
+ const jsPath = path17.resolve(dirname, "chat-bridge.js");
11158
11811
  try {
11159
11812
  accessSync(jsPath);
11160
11813
  return jsPath;
11161
11814
  } catch {
11162
- return path16.resolve(dirname, "chat-bridge.ts");
11815
+ return path17.resolve(dirname, "chat-bridge.ts");
11163
11816
  }
11164
11817
  }
11165
11818
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
11166
- const thisDir = path16.dirname(fileURLToPath(moduleUrl));
11167
- const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
11819
+ const thisDir = path17.dirname(fileURLToPath2(moduleUrl));
11820
+ const bundledDistPath = path17.resolve(thisDir, "cli", "index.js");
11168
11821
  try {
11169
11822
  accessSync(bundledDistPath);
11170
11823
  return bundledDistPath;
11171
11824
  } catch {
11172
- const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
11825
+ const workspaceDistPath = path17.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
11173
11826
  accessSync(workspaceDistPath);
11174
11827
  return workspaceDistPath;
11175
11828
  }
@@ -11299,6 +11952,11 @@ function summarizeIncomingMessage(msg) {
11299
11952
  var DaemonCore = class {
11300
11953
  options;
11301
11954
  daemonVersion;
11955
+ // When this runner is launched by a managed Computer service, the
11956
+ // service exports SLOCK_COMPUTER_VERSION (the `@slock-ai/computer`
11957
+ // bundle version). Reported in `ready` so the server can surface the
11958
+ // Computer's own version (distinct from the underlying daemonVersion).
11959
+ computerVersion;
11302
11960
  chatBridgePath;
11303
11961
  slockCliPath;
11304
11962
  slockHome;
@@ -11314,6 +11972,7 @@ var DaemonCore = class {
11314
11972
  constructor(options) {
11315
11973
  this.options = options;
11316
11974
  this.daemonVersion = options.daemonVersion ?? readDaemonVersion();
11975
+ this.computerVersion = (process.env.SLOCK_COMPUTER_VERSION || "").trim() || null;
11317
11976
  this.chatBridgePath = options.chatBridgePath ?? resolveChatBridgePath();
11318
11977
  this.slockCliPath = options.slockCliPath ?? resolveSlockCliPath();
11319
11978
  this.slockHome = resolveSlockHome();
@@ -11348,7 +12007,7 @@ var DaemonCore = class {
11348
12007
  }
11349
12008
  resolveMachineStateRoot() {
11350
12009
  if (this.options.machineStateDir) return this.options.machineStateDir;
11351
- if (this.options.dataDir) return path16.join(path16.dirname(this.options.dataDir), "machines");
12010
+ if (this.options.dataDir) return path17.join(path17.dirname(this.options.dataDir), "machines");
11352
12011
  return resolveDefaultMachineStateRoot();
11353
12012
  }
11354
12013
  shouldEnableLocalTrace() {
@@ -11374,7 +12033,7 @@ var DaemonCore = class {
11374
12033
  sink: this.localTraceSink
11375
12034
  });
11376
12035
  this.agentManager.setTracer(this.tracer);
11377
- this.agentManager.setCliTransportTraceDir(path16.join(machineDir, "traces"));
12036
+ this.agentManager.setCliTransportTraceDir(path17.join(machineDir, "traces"));
11378
12037
  }
11379
12038
  installTraceBundleUploader(machineDir) {
11380
12039
  if (!this.shouldEnableLocalTrace()) return;
@@ -11773,6 +12432,26 @@ var DaemonCore = class {
11773
12432
  case "ping":
11774
12433
  this.connection.send({ type: "pong" });
11775
12434
  break;
12435
+ case "computer:restart":
12436
+ case "computer:upgrade": {
12437
+ const action = msg.type === "computer:restart" ? "restart" : "upgrade";
12438
+ this.recordDaemonTrace("daemon.computer_control.received", {
12439
+ action,
12440
+ handled: Boolean(this.options.onComputerControl)
12441
+ });
12442
+ if (this.options.onComputerControl) {
12443
+ try {
12444
+ this.options.onComputerControl(action);
12445
+ } catch (err) {
12446
+ logger.error(
12447
+ `[Daemon] computer:${action} control handler failed: ${err instanceof Error ? err.message : String(err)}`
12448
+ );
12449
+ }
12450
+ } else {
12451
+ logger.info(`[Daemon] Ignoring computer:${action} \u2014 not launched by a Computer service.`);
12452
+ }
12453
+ break;
12454
+ }
11776
12455
  }
11777
12456
  }
11778
12457
  onReminderFire(job) {
@@ -11799,7 +12478,8 @@ var DaemonCore = class {
11799
12478
  runningAgents: runningAgentIds,
11800
12479
  hostname: this.options.hostname ?? os7.hostname(),
11801
12480
  os: this.options.osDescription ?? `${os7.platform()} ${os7.arch()}`,
11802
- daemonVersion: this.daemonVersion
12481
+ daemonVersion: this.daemonVersion,
12482
+ ...this.computerVersion ? { computerVersion: this.computerVersion } : {}
11803
12483
  });
11804
12484
  this.recordDaemonTrace("daemon.ready.sent", {
11805
12485
  runtimes_count: runtimes.length,
@@ -11860,6 +12540,8 @@ var DaemonCore = class {
11860
12540
  };
11861
12541
 
11862
12542
  export {
12543
+ DAEMON_API_KEY_ENV,
12544
+ scrubDaemonAuthEnv,
11863
12545
  resolveWorkspaceDirectoryPath,
11864
12546
  scanWorkspaceDirectories,
11865
12547
  deleteWorkspaceDirectory,