@slock-ai/daemon 0.55.3 → 0.55.4

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,7 +8,7 @@ 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";
@@ -995,13 +995,19 @@ var RUNTIMES = [
995
995
  { id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
996
996
  { id: "cursor", displayName: "Cursor CLI", binary: "cursor-agent", supported: true },
997
997
  { id: "gemini", displayName: "Gemini CLI", binary: "gemini", supported: true },
998
- { id: "opencode", displayName: "OpenCode", binary: "opencode", supported: true }
998
+ { id: "opencode", displayName: "OpenCode", binary: "opencode", supported: true },
999
+ { id: "pi", displayName: "Pi CLI", binary: "pi", supported: true }
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,11 @@ 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: "default", label: "Configured Default / Auto", verified: "suggestion_only" },
1053
+ { id: "deepseek/deepseek-v4-pro", label: "DeepSeek V4 Pro (Pi)", verified: "suggestion_only" },
1054
+ { id: "deepseek/deepseek-v4-flash", label: "DeepSeek V4 Flash (Pi)", verified: "suggestion_only" }
1055
+ ],
1045
1056
  // Kimi CLI resolves model keys from each user's local config, so the safest
1046
1057
  // built-in option is to defer to whatever default model the CLI already uses.
1047
1058
  kimi: [
@@ -1086,22 +1097,28 @@ function isPresetRuntimeModel(runtime, model) {
1086
1097
  function modelConfigFromLegacy(runtime, model) {
1087
1098
  return isPresetRuntimeModel(runtime, model) ? { kind: "preset", id: model } : { kind: "custom", name: model };
1088
1099
  }
1089
- function parseProviderConfig(runtime, value) {
1100
+ function parseProviderConfig(runtime, value, legacyApiUrl, legacyApiKey) {
1090
1101
  if (runtime !== "claude") return void 0;
1091
1102
  if (!isPlainRecord(value)) return { kind: "default" };
1092
1103
  if (value.kind === "custom" && typeof value.apiUrl === "string" && value.apiUrl.trim() && typeof value.apiKey === "string" && value.apiKey.trim()) {
1093
1104
  return { kind: "custom", apiUrl: value.apiUrl.trim(), apiKey: value.apiKey.trim() };
1094
1105
  }
1106
+ if (value.kind === "custom" && legacyApiUrl?.trim() && legacyApiKey?.trim()) {
1107
+ return { kind: "custom", apiUrl: legacyApiUrl.trim(), apiKey: legacyApiKey.trim() };
1108
+ }
1095
1109
  return { kind: "default" };
1096
1110
  }
1097
- function parseModelConfig(runtime, value, fallback) {
1111
+ function parseModelConfig(runtime, value, fallback, provider, legacyCustomModel) {
1098
1112
  if (!isPlainRecord(value)) return modelConfigFromLegacy(runtime, fallback);
1099
1113
  if (value.kind === "custom" && typeof value.name === "string" && value.name.trim()) {
1100
1114
  return { kind: "custom", name: value.name.trim() };
1101
1115
  }
1102
- if (value.kind === "preset" && typeof value.id === "string" && value.id.trim()) {
1116
+ if (value.kind === "preset" && typeof value.id === "string" && value.id.trim() && isPresetRuntimeModel(runtime, value.id.trim())) {
1103
1117
  return { kind: "preset", id: value.id.trim() };
1104
1118
  }
1119
+ if (provider?.kind === "custom" && runtime === "claude" && legacyCustomModel?.trim()) {
1120
+ return { kind: "custom", name: legacyCustomModel.trim() };
1121
+ }
1105
1122
  return modelConfigFromLegacy(runtime, fallback);
1106
1123
  }
1107
1124
  function parseModeConfig(value) {
@@ -1117,12 +1134,13 @@ function hydrateRuntimeConfig(input) {
1117
1134
  const storedEnvVars = normalizeEnvVars(stored?.envVars);
1118
1135
  const legacyClaudeApiUrl = runtime === "claude" ? legacyEnvVars?.ANTHROPIC_BASE_URL : void 0;
1119
1136
  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;
1137
+ const legacyClaudeCustomModel = runtime === "claude" ? legacyEnvVars?.ANTHROPIC_CUSTOM_MODEL_OPTION : void 0;
1138
+ 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
1139
  return {
1122
1140
  version: RUNTIME_CONFIG_VERSION,
1123
1141
  runtime,
1124
1142
  ...provider ? { provider } : {},
1125
- model: stored ? parseModelConfig(runtime, stored.model, fallbackModel) : modelConfigFromLegacy(runtime, fallbackModel),
1143
+ model: stored ? parseModelConfig(runtime, stored.model, fallbackModel, provider, legacyClaudeCustomModel) : modelConfigFromLegacy(runtime, fallbackModel),
1126
1144
  mode: stored ? parseModeConfig(stored.mode) : { kind: "default" },
1127
1145
  reasoningEffort: stored?.reasoningEffort ?? input.reasoningEffort ?? null,
1128
1146
  envVars: stripControlledRuntimeEnvVars(runtime, stored ? storedEnvVars : legacyEnvVars)
@@ -1190,10 +1208,10 @@ var DISPLAY_PLAN_CONFIG = {
1190
1208
  };
1191
1209
 
1192
1210
  // src/agentProcessManager.ts
1193
- import { mkdirSync as mkdirSync4, readdirSync, statSync, writeFileSync as writeFileSync7 } from "fs";
1211
+ import { mkdirSync as mkdirSync5, readdirSync, statSync, writeFileSync as writeFileSync7 } from "fs";
1194
1212
  import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
1195
1213
  import { createHash as createHash3 } from "crypto";
1196
- import path12 from "path";
1214
+ import path13 from "path";
1197
1215
  import os5 from "os";
1198
1216
 
1199
1217
  // src/drivers/claude.ts
@@ -1374,19 +1392,19 @@ If Slock says a message was not sent and was saved as a draft, choose one path:
1374
1392
 
1375
1393
  Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
1376
1394
 
1377
- - **Thread targets** have a colon and short ID suffix: \`#general:a1b2c3d4\` (thread in #general) or \`dm:@richard:x9y8z7a0\` (thread in a DM).
1395
+ - **Thread targets** have a colon and short ID suffix: \`#general:00000000\` (thread in #general) or \`dm:@richard:11111111\` (thread in a DM).
1378
1396
  - 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.
1397
+ - **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
1398
  - 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.
1399
+ - You can read thread history: \`slock message read --channel "#general:00000000"\`
1400
+ - 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
1401
  - Threads cannot be nested \u2014 you cannot start a thread inside a thread.` : `### Threads
1384
1402
 
1385
1403
  Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
1386
1404
 
1387
- - **Thread targets** have a colon and short ID suffix: \`#general:a1b2c3d4\` (thread in #general) or \`dm:@richard:x9y8z7a0\` (thread in a DM).
1405
+ - **Thread targets** have a colon and short ID suffix: \`#general:00000000\` (thread in #general) or \`dm:@richard:11111111\` (thread in a DM).
1388
1406
  - 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.
1407
+ - **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
1408
  - When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
1391
1409
  - You can read thread history via ${readCmd} with the same thread target.
1392
1410
  - Threads cannot be nested \u2014 you cannot start a thread inside a thread.`;
@@ -1552,13 +1570,18 @@ ${opts.postStartupNotes.join("\n")}`;
1552
1570
  Messages you receive have a single RFC 5424-style structured data header followed by the sender and content:
1553
1571
 
1554
1572
  \`\`\`
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
1573
+ [target=#general msg=00000000 time=2026-03-15T01:00:00 type=human] @richard: hello everyone
1574
+ [target=#general msg=11111111 time=2026-03-15T01:00:01 type=agent] @Alice: hi there
1575
+ [target=dm:@richard msg=22222222 time=2026-03-15T01:00:02 type=human] @richard: hey, can you help?
1576
+ [target=#general:00000000 msg=33333333 time=2026-03-15T01:00:03 type=human] @richard: thread reply
1577
+ [target=dm:@richard:22222222 msg=44444444 time=2026-03-15T01:00:04 type=human] @richard: DM thread reply
1560
1578
  \`\`\`
1561
1579
 
1580
+ Prompt examples use obvious placeholder IDs such as \`00000000\`, \`11111111\`,
1581
+ and \`22222222\`. They show the shape of a real message ID but are not actual
1582
+ messages. Do not cite them as evidence; use only IDs from messages you actually
1583
+ received or read.
1584
+
1562
1585
  Header fields:
1563
1586
  - \`target=\` \u2014 where the message came from. Reuse as the \`target\` parameter when replying.
1564
1587
  - \`msg=\` \u2014 message short ID (first 8 chars of UUID). Use as thread suffix to start/reply in a thread.
@@ -1630,9 +1653,11 @@ Slock auto-renders these inline tokens as interactive links whenever they appear
1630
1653
 
1631
1654
  Write them inline as plain words in your sentence \u2014 the same way you'd type any other word \u2014 and Slock turns them into clickable references.
1632
1655
 
1656
+ Markdown markup expresses presentation semantics; do not mix markup delimiters into literal payloads. Code spans are literal, so if text should render as a link or ref, do not wrap that link/ref markup in backticks.
1657
+
1633
1658
  ### Formatting \u2014 URLs in non-English text
1634
1659
 
1635
- When writing a URL next to non-ASCII punctuation (Chinese, Japanese, etc.), always wrap the URL in angle brackets or use markdown link syntax. Otherwise the punctuation may be rendered as part of the URL.
1660
+ When writing a URL next to non-ASCII punctuation (Chinese, Japanese, etc.) and the URL should render as a link, wrap the URL in angle brackets or use markdown link syntax. Otherwise the punctuation may be rendered as part of the URL.
1636
1661
 
1637
1662
  - **Wrong**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1Ahttp://localhost:3000\uFF0C\u8BF7\u67E5\u770B\` (the \`\uFF0C\` gets swallowed into the link)
1638
1663
  - **Correct**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1A<http://localhost:3000>\uFF0C\u8BF7\u67E5\u770B\`
@@ -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
 
@@ -3081,6 +3106,125 @@ function collectResultErrorDetail(message, fallback) {
3081
3106
  function isProviderApiFailureText(value, hasToolUse) {
3082
3107
  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));
3083
3108
  }
3109
+ function finiteNumber(value) {
3110
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3111
+ }
3112
+ function finiteString(value) {
3113
+ return typeof value === "string" && value.length > 0 ? value : void 0;
3114
+ }
3115
+ function withDefined(attrs) {
3116
+ return Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== void 0));
3117
+ }
3118
+ function collectNumericFields(value, fields) {
3119
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
3120
+ const attrs = {};
3121
+ for (const [sourceKey, attrKey] of Object.entries(fields)) {
3122
+ const numberValue = finiteNumber(value[sourceKey]);
3123
+ if (numberValue !== void 0) attrs[attrKey] = numberValue;
3124
+ }
3125
+ return attrs;
3126
+ }
3127
+ function collectModelUsageAttrs(value) {
3128
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
3129
+ const sanitized = {};
3130
+ const aggregate = {};
3131
+ const knownNumericFields = [
3132
+ "inputTokens",
3133
+ "outputTokens",
3134
+ "cacheReadInputTokens",
3135
+ "cacheCreationInputTokens",
3136
+ "webSearchRequests",
3137
+ "costUSD",
3138
+ "contextWindow",
3139
+ "maxOutputTokens"
3140
+ ];
3141
+ for (const [modelName, rawUsage] of Object.entries(value)) {
3142
+ if (!rawUsage || typeof rawUsage !== "object" || Array.isArray(rawUsage)) continue;
3143
+ const modelUsage = {};
3144
+ for (const field of knownNumericFields) {
3145
+ const numberValue = finiteNumber(rawUsage[field]);
3146
+ if (numberValue === void 0) continue;
3147
+ modelUsage[field] = numberValue;
3148
+ const aggregateKey = field === "costUSD" ? "costUsd" : field;
3149
+ if (field === "contextWindow" || field === "maxOutputTokens") {
3150
+ aggregate[aggregateKey] = Math.max(aggregate[aggregateKey] ?? 0, numberValue);
3151
+ } else {
3152
+ aggregate[aggregateKey] = (aggregate[aggregateKey] ?? 0) + numberValue;
3153
+ }
3154
+ }
3155
+ if (Object.keys(modelUsage).length > 0) sanitized[modelName] = modelUsage;
3156
+ }
3157
+ const modelNames = Object.keys(sanitized).sort();
3158
+ if (modelNames.length === 0) return {};
3159
+ const orderedSanitized = Object.fromEntries(modelNames.map((modelName) => [modelName, sanitized[modelName]]));
3160
+ return withDefined({
3161
+ modelUsageModelCount: modelNames.length,
3162
+ modelUsageModels: modelNames.join(","),
3163
+ modelUsageJson: JSON.stringify(orderedSanitized),
3164
+ modelUsageInputTokens: aggregate.inputTokens,
3165
+ modelUsageOutputTokens: aggregate.outputTokens,
3166
+ modelUsageCachedInputTokens: aggregate.cacheReadInputTokens,
3167
+ modelUsageCacheCreationInputTokens: aggregate.cacheCreationInputTokens,
3168
+ modelUsageWebSearchRequests: aggregate.webSearchRequests,
3169
+ modelUsageCostUsd: aggregate.costUsd,
3170
+ modelUsageMaxContextWindow: aggregate.contextWindow,
3171
+ modelUsageMaxOutputTokens: aggregate.maxOutputTokens
3172
+ });
3173
+ }
3174
+ function parseClaudeResultUsageTelemetry(event) {
3175
+ const usage = event.usage && typeof event.usage === "object" ? event.usage : void 0;
3176
+ const totalCostUsd = finiteNumber(event.total_cost_usd);
3177
+ const modelUsageAttrs = collectModelUsageAttrs(event.modelUsage);
3178
+ const hasTelemetrySource = usage !== void 0 || totalCostUsd !== void 0 || Object.keys(modelUsageAttrs).length > 0;
3179
+ if (!hasTelemetrySource) return null;
3180
+ const inputTokens = finiteNumber(usage?.input_tokens);
3181
+ const outputTokens = finiteNumber(usage?.output_tokens);
3182
+ const cachedInputTokens = finiteNumber(usage?.cache_read_input_tokens);
3183
+ const cacheCreationInputTokens = finiteNumber(usage?.cache_creation_input_tokens);
3184
+ const attrs = {
3185
+ ...withDefined({
3186
+ totalCostUsd,
3187
+ durationMs: finiteNumber(event.duration_ms),
3188
+ durationApiMs: finiteNumber(event.duration_api_ms),
3189
+ numTurns: finiteNumber(event.num_turns),
3190
+ resultSubtype: finiteString(event.subtype),
3191
+ stopReason: finiteString(event.stop_reason),
3192
+ resultIsError: typeof event.is_error === "boolean" ? event.is_error : void 0,
3193
+ fastModeState: finiteString(event.fast_mode_state),
3194
+ permissionDenialsCount: Array.isArray(event.permission_denials) ? event.permission_denials.length : void 0,
3195
+ serviceTier: finiteString(usage?.service_tier),
3196
+ inferenceGeo: finiteString(usage?.inference_geo),
3197
+ usageSpeed: finiteString(usage?.speed),
3198
+ usageIterationsCount: Array.isArray(usage?.iterations) ? usage.iterations.length : void 0
3199
+ }),
3200
+ ...collectNumericFields(usage?.server_tool_use, {
3201
+ web_search_requests: "serverToolUseWebSearchRequests",
3202
+ web_fetch_requests: "serverToolUseWebFetchRequests"
3203
+ }),
3204
+ ...collectNumericFields(usage?.cache_creation, {
3205
+ ephemeral_1h_input_tokens: "cacheCreationEphemeral1hInputTokens",
3206
+ ephemeral_5m_input_tokens: "cacheCreationEphemeral5mInputTokens"
3207
+ }),
3208
+ ...modelUsageAttrs
3209
+ };
3210
+ if (inputTokens !== void 0) attrs.inputTokens = inputTokens;
3211
+ if (outputTokens !== void 0) attrs.outputTokens = outputTokens;
3212
+ if (cachedInputTokens !== void 0) attrs.cachedInputTokens = cachedInputTokens;
3213
+ if (cacheCreationInputTokens !== void 0) attrs.cacheCreationInputTokens = cacheCreationInputTokens;
3214
+ const tokenComponents = [inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens];
3215
+ const totalTokens = tokenComponents.reduce((sum, value) => sum + (value ?? 0), 0);
3216
+ if (tokenComponents.some((value) => value !== void 0)) attrs.totalTokens = totalTokens;
3217
+ if (Object.keys(attrs).length === 0) return null;
3218
+ return {
3219
+ kind: "telemetry",
3220
+ name: "token_usage",
3221
+ source: "claude_result_usage",
3222
+ usageKind: "per_turn",
3223
+ ...typeof event.session_id === "string" && event.session_id ? { sessionId: event.session_id } : {},
3224
+ ...typeof event.uuid === "string" && event.uuid ? { runtimeResultId: event.uuid } : {},
3225
+ attrs
3226
+ };
3227
+ }
3084
3228
  var ClaudeEventNormalizer = class {
3085
3229
  normalizeLine(line) {
3086
3230
  let event;
@@ -3155,6 +3299,7 @@ var ClaudeEventNormalizer = class {
3155
3299
  case "result": {
3156
3300
  const subtype = typeof event.subtype === "string" ? event.subtype : "success";
3157
3301
  const stopReason = typeof event.stop_reason === "string" ? event.stop_reason : null;
3302
+ const usageTelemetry = parseClaudeResultUsageTelemetry(event);
3158
3303
  switch (subtype) {
3159
3304
  case "success":
3160
3305
  if (event.is_error && stopReason !== "max_tokens") {
@@ -3176,6 +3321,7 @@ var ClaudeEventNormalizer = class {
3176
3321
  pushResultError(event, "Structured output retries exceeded");
3177
3322
  break;
3178
3323
  }
3324
+ if (usageTelemetry) events.push(usageTelemetry);
3179
3325
  events.push({ kind: "turn_end", sessionId: event.session_id });
3180
3326
  break;
3181
3327
  }
@@ -3441,7 +3587,7 @@ function buildClaudeArgs(config, opts) {
3441
3587
  args.push("--effort", launchRuntimeFields.reasoningEffort);
3442
3588
  }
3443
3589
  if (launchRuntimeFields.mode.kind === "fast") {
3444
- args.push("--bare");
3590
+ args.push("--settings", JSON.stringify({ fastMode: true }));
3445
3591
  }
3446
3592
  if (config.sessionId) {
3447
3593
  args.push("--resume", config.sessionId);
@@ -3599,53 +3745,64 @@ var RuntimeTurnState = class {
3599
3745
  };
3600
3746
 
3601
3747
  // src/drivers/codexTelemetrySidecar.ts
3602
- function finiteNumber(value) {
3748
+ function finiteNumber2(value) {
3603
3749
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3604
3750
  }
3605
- function finiteString(value) {
3751
+ function finiteString2(value) {
3606
3752
  return typeof value === "string" && value.length > 0 ? value : void 0;
3607
3753
  }
3608
3754
  function ratio(numerator, denominator) {
3609
3755
  if (numerator === void 0 || denominator === void 0 || denominator <= 0) return void 0;
3610
3756
  return Number((numerator / denominator).toFixed(6));
3611
3757
  }
3612
- function withDefined(attrs) {
3758
+ function withDefined2(attrs) {
3613
3759
  return Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== void 0));
3614
3760
  }
3615
3761
  function parseTokenUsageTelemetry(message) {
3616
3762
  const usage = message.params?.tokenUsage;
3617
3763
  const total = usage?.total;
3618
3764
  if (!total || typeof total !== "object") return null;
3619
- const inputTokens = finiteNumber(total.inputTokens);
3620
- const cachedInputTokens = finiteNumber(total.cachedInputTokens);
3621
- const totalTokens = finiteNumber(total.totalTokens);
3622
- const modelContextWindow = finiteNumber(usage.modelContextWindow);
3623
- const attrs = withDefined({
3765
+ const inputTokens = finiteNumber2(total.inputTokens);
3766
+ const cachedInputTokens = finiteNumber2(total.cachedInputTokens);
3767
+ const totalTokens = finiteNumber2(total.totalTokens);
3768
+ const modelContextWindow = finiteNumber2(usage.modelContextWindow);
3769
+ const attrs = withDefined2({
3624
3770
  totalTokens,
3625
3771
  inputTokens,
3626
3772
  cachedInputTokens,
3627
- outputTokens: finiteNumber(total.outputTokens),
3628
- reasoningOutputTokens: finiteNumber(total.reasoningOutputTokens),
3773
+ outputTokens: finiteNumber2(total.outputTokens),
3774
+ reasoningOutputTokens: finiteNumber2(total.reasoningOutputTokens),
3629
3775
  modelContextWindow,
3630
3776
  cachedInputRatio: ratio(cachedInputTokens, inputTokens),
3631
3777
  contextUtilization: ratio(totalTokens, modelContextWindow)
3632
3778
  });
3633
3779
  if (Object.keys(attrs).length === 0) return null;
3634
- return { kind: "telemetry", name: "token_usage", attrs };
3780
+ return {
3781
+ kind: "telemetry",
3782
+ name: "token_usage",
3783
+ source: "codex_thread_token_usage_updated",
3784
+ usageKind: "cumulative_session",
3785
+ attrs
3786
+ };
3635
3787
  }
3636
3788
  function parseRateLimitTelemetry(message) {
3637
3789
  const rateLimits = message.params?.rateLimits;
3638
3790
  const primary = rateLimits?.primary;
3639
3791
  if (!rateLimits || typeof rateLimits !== "object" || !primary || typeof primary !== "object") return null;
3640
- const attrs = withDefined({
3641
- limitId: finiteString(rateLimits.limitId),
3642
- planType: finiteString(rateLimits.planType),
3643
- usedPercent: finiteNumber(primary.usedPercent),
3644
- windowDurationMins: finiteNumber(primary.windowDurationMins),
3645
- resetsAt: finiteNumber(primary.resetsAt)
3792
+ const attrs = withDefined2({
3793
+ limitId: finiteString2(rateLimits.limitId),
3794
+ planType: finiteString2(rateLimits.planType),
3795
+ usedPercent: finiteNumber2(primary.usedPercent),
3796
+ windowDurationMins: finiteNumber2(primary.windowDurationMins),
3797
+ resetsAt: finiteNumber2(primary.resetsAt)
3646
3798
  });
3647
3799
  if (Object.keys(attrs).length === 0) return null;
3648
- return { kind: "telemetry", name: "rate_limits", attrs };
3800
+ return {
3801
+ kind: "telemetry",
3802
+ name: "rate_limits",
3803
+ source: "codex_account_rate_limits_updated",
3804
+ attrs
3805
+ };
3649
3806
  }
3650
3807
  function parseCodexTelemetryEvent(message) {
3651
3808
  switch (message.method) {
@@ -3752,7 +3909,11 @@ var CodexEventNormalizer = class {
3752
3909
  }
3753
3910
  const telemetry = parseCodexTelemetryEvent(message);
3754
3911
  if (telemetry) {
3755
- events.push(telemetry);
3912
+ events.push({
3913
+ ...telemetry,
3914
+ ...this.currentThreadId ? { sessionId: this.currentThreadId } : {},
3915
+ ...this.turnState.activeTurnId ? { turnId: this.turnState.activeTurnId } : {}
3916
+ });
3756
3917
  return { events };
3757
3918
  }
3758
3919
  const rawProgress = rawResponseItemProgressEvent(message);
@@ -5794,6 +5955,277 @@ var OpenCodeDriver = class {
5794
5955
  }
5795
5956
  };
5796
5957
 
5958
+ // src/drivers/pi.ts
5959
+ import { randomUUID as randomUUID3 } from "crypto";
5960
+ import { spawn as spawn9, spawnSync as spawnSync3 } from "child_process";
5961
+ import { mkdirSync as mkdirSync4 } from "fs";
5962
+ import path11 from "path";
5963
+ var PI_SESSION_DIR = ".pi-sessions";
5964
+ var PI_PROVIDER_LABELS = {
5965
+ deepseek: "DeepSeek",
5966
+ google: "Google",
5967
+ openai: "OpenAI",
5968
+ openrouter: "OpenRouter"
5969
+ };
5970
+ function buildPiSessionDir(workingDirectory) {
5971
+ return path11.join(workingDirectory, PI_SESSION_DIR);
5972
+ }
5973
+ function buildPiRpcArgs(ctx, sessionId) {
5974
+ const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
5975
+ const args = [
5976
+ "--mode",
5977
+ "rpc",
5978
+ "--session-dir",
5979
+ buildPiSessionDir(ctx.workingDirectory),
5980
+ "--system-prompt",
5981
+ ctx.standingPrompt
5982
+ ];
5983
+ if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
5984
+ args.push("--model", launchRuntimeFields.model);
5985
+ }
5986
+ if (launchRuntimeFields.reasoningEffort) {
5987
+ args.push("--thinking", launchRuntimeFields.reasoningEffort);
5988
+ }
5989
+ if (sessionId) {
5990
+ args.push("--session-id", sessionId);
5991
+ }
5992
+ return args;
5993
+ }
5994
+ async function buildPiSpawnEnv(ctx) {
5995
+ return (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
5996
+ }
5997
+ function parsePiModelsOutput(output) {
5998
+ const stripAnsi = (value) => value.replace(/\u001b\[[0-9;]*m/g, "");
5999
+ const models = [];
6000
+ const seen = /* @__PURE__ */ new Set();
6001
+ for (const rawLine of stripAnsi(output).split(/\r?\n/)) {
6002
+ const line = rawLine.trim();
6003
+ if (!line || /^provider\s+model\s+/i.test(line) || /^[-\s]+$/.test(line)) continue;
6004
+ const columns = line.split(/\s+/);
6005
+ const provider = columns[0];
6006
+ const model = columns[1];
6007
+ if (!provider || !model || provider.startsWith("-") || model.startsWith("-")) continue;
6008
+ if (/^(yes|no)$/i.test(model)) continue;
6009
+ const id = `${provider}/${model}`;
6010
+ if (seen.has(id)) continue;
6011
+ seen.add(id);
6012
+ models.push({
6013
+ id,
6014
+ label: `${humanizePiSegment(model)} \xB7 ${PI_PROVIDER_LABELS[provider] || humanizePiSegment(provider)}`,
6015
+ verified: "launchable"
6016
+ });
6017
+ }
6018
+ return models.length > 0 ? { models } : null;
6019
+ }
6020
+ function detectPiModels(runCommand = runPiModelsCommand) {
6021
+ const result = runCommand();
6022
+ if (result.error || result.status !== 0) return null;
6023
+ return parsePiModelsOutput(result.stdout);
6024
+ }
6025
+ function runPiModelsCommand() {
6026
+ const result = spawnSync3("pi", ["--list-models"], {
6027
+ env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
6028
+ encoding: "utf8",
6029
+ timeout: 5e3
6030
+ });
6031
+ return {
6032
+ status: result.status,
6033
+ stdout: String(result.stdout || ""),
6034
+ error: result.error
6035
+ };
6036
+ }
6037
+ function humanizePiSegment(value) {
6038
+ return value.split(/[-_/]/).filter(Boolean).map(formatPiLabelToken).join(" ");
6039
+ }
6040
+ function formatPiLabelToken(token) {
6041
+ const normalized = token.toLowerCase();
6042
+ const specialCases = {
6043
+ ai: "AI",
6044
+ api: "API",
6045
+ deepseek: "DeepSeek",
6046
+ flash: "Flash",
6047
+ gpt: "GPT",
6048
+ pro: "Pro",
6049
+ v4: "V4"
6050
+ };
6051
+ if (specialCases[normalized]) return specialCases[normalized];
6052
+ if (/^v\d+(\.\d+)?$/.test(normalized)) return normalized.toUpperCase();
6053
+ if (/^\d+[bk]$/i.test(token)) return token.toUpperCase();
6054
+ if (/^\d/.test(token)) return token;
6055
+ return normalized.charAt(0).toUpperCase() + normalized.slice(1);
6056
+ }
6057
+ function piErrorMessage(error) {
6058
+ if (typeof error === "string" && error.trim()) return error.trim();
6059
+ if (error && typeof error === "object") {
6060
+ const record = error;
6061
+ if (typeof record.message === "string" && record.message.trim()) return record.message.trim();
6062
+ try {
6063
+ return JSON.stringify(error);
6064
+ } catch {
6065
+ }
6066
+ }
6067
+ return "Unknown Pi error";
6068
+ }
6069
+ var PiDriver = class {
6070
+ id = "pi";
6071
+ supportsNativeStandingPrompt = true;
6072
+ lifecycle = {
6073
+ kind: "persistent",
6074
+ stdin: "direct",
6075
+ inFlightWake: "steer"
6076
+ };
6077
+ communication = {
6078
+ chat: "slock_cli",
6079
+ runtimeControl: "none"
6080
+ };
6081
+ session = {
6082
+ recovery: "resume_or_fresh"
6083
+ };
6084
+ model = {
6085
+ detectedModelsVerifiedAs: "launchable",
6086
+ toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
6087
+ };
6088
+ supportsStdinNotification = true;
6089
+ mcpToolPrefix = "";
6090
+ busyDeliveryMode = "direct";
6091
+ usesSlockCliForCommunication = true;
6092
+ sessionId = null;
6093
+ sessionAnnounced = false;
6094
+ sawTextDelta = false;
6095
+ requestId = 0;
6096
+ process = null;
6097
+ probe() {
6098
+ const command = resolveCommandOnPath("pi");
6099
+ if (!command) return { available: false };
6100
+ return {
6101
+ available: true,
6102
+ version: readCommandVersion(command) ?? void 0
6103
+ };
6104
+ }
6105
+ async detectModels() {
6106
+ return detectPiModels();
6107
+ }
6108
+ async spawn(ctx) {
6109
+ this.sessionId = ctx.config.sessionId || randomUUID3();
6110
+ this.sessionAnnounced = false;
6111
+ this.sawTextDelta = false;
6112
+ this.requestId = 0;
6113
+ mkdirSync4(buildPiSessionDir(ctx.workingDirectory), { recursive: true });
6114
+ const proc = spawn9(resolveCommandOnPath("pi") ?? "pi", buildPiRpcArgs(ctx, this.sessionId), {
6115
+ cwd: ctx.workingDirectory,
6116
+ stdio: ["pipe", "pipe", "pipe"],
6117
+ env: await buildPiSpawnEnv(ctx),
6118
+ shell: process.platform === "win32"
6119
+ });
6120
+ this.process = proc;
6121
+ this.sendRpcCommand("prompt", { message: ctx.prompt });
6122
+ return { process: proc };
6123
+ }
6124
+ parseLine(line) {
6125
+ let event;
6126
+ try {
6127
+ event = JSON.parse(line);
6128
+ } catch {
6129
+ return [];
6130
+ }
6131
+ const events = [];
6132
+ if (event.type === "session" && event.id) {
6133
+ this.sessionId = event.id;
6134
+ if (!this.sessionAnnounced) {
6135
+ this.sessionAnnounced = true;
6136
+ events.push({ kind: "session_init", sessionId: event.id });
6137
+ }
6138
+ return events;
6139
+ }
6140
+ if (!this.sessionAnnounced && this.sessionId) {
6141
+ events.push({ kind: "session_init", sessionId: this.sessionId });
6142
+ this.sessionAnnounced = true;
6143
+ }
6144
+ if (event.type === "response") {
6145
+ if (event.data?.sessionId && event.data.sessionId !== this.sessionId) {
6146
+ this.sessionId = event.data.sessionId;
6147
+ }
6148
+ if (event.success === false) {
6149
+ events.push({ kind: "error", message: piErrorMessage(event.error) });
6150
+ }
6151
+ return events;
6152
+ }
6153
+ if (event.type === "message_start" && event.message?.role === "assistant") {
6154
+ this.sawTextDelta = false;
6155
+ return events;
6156
+ }
6157
+ const assistantEvent = event.assistantMessageEvent;
6158
+ if (event.type === "message_update" && assistantEvent) {
6159
+ switch (assistantEvent.type) {
6160
+ case "thinking_delta":
6161
+ if (typeof assistantEvent.delta === "string" && assistantEvent.delta.length > 0) {
6162
+ events.push({ kind: "thinking", text: assistantEvent.delta });
6163
+ }
6164
+ break;
6165
+ case "text_delta":
6166
+ if (typeof assistantEvent.delta === "string" && assistantEvent.delta.length > 0) {
6167
+ this.sawTextDelta = true;
6168
+ events.push({ kind: "text", text: assistantEvent.delta });
6169
+ }
6170
+ break;
6171
+ case "thinking_start":
6172
+ case "text_start":
6173
+ break;
6174
+ case "tool_use":
6175
+ case "tool_call":
6176
+ case "tool_start":
6177
+ events.push({
6178
+ kind: "tool_call",
6179
+ name: assistantEvent.name || assistantEvent.toolName || "unknown_tool",
6180
+ input: assistantEvent.input ?? assistantEvent.parameters ?? {}
6181
+ });
6182
+ break;
6183
+ case "text_end":
6184
+ if (!this.sawTextDelta && typeof assistantEvent.content === "string" && assistantEvent.content.length > 0) {
6185
+ events.push({ kind: "text", text: assistantEvent.content });
6186
+ }
6187
+ break;
6188
+ }
6189
+ return events;
6190
+ }
6191
+ if (event.type === "agent_end") {
6192
+ events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
6193
+ } else if (event.type === "error") {
6194
+ events.push({ kind: "error", message: piErrorMessage(event.error ?? event.message) });
6195
+ }
6196
+ return events;
6197
+ }
6198
+ encodeStdinMessage(text, _sessionId, opts) {
6199
+ return JSON.stringify({
6200
+ id: this.nextRequestId(),
6201
+ type: opts?.mode === "idle" ? "prompt" : "steer",
6202
+ message: text
6203
+ });
6204
+ }
6205
+ buildSystemPrompt(config, _agentId) {
6206
+ return buildCliTransportSystemPrompt(config, {
6207
+ toolPrefix: "",
6208
+ extraCriticalRules: [],
6209
+ postStartupNotes: [
6210
+ "**Pi runtime note:** Slock keeps Pi running in RPC mode. While you are working, Slock may send inbox-count notifications into the current turn; call `slock message check` at natural breakpoints."
6211
+ ],
6212
+ includeStdinNotificationSection: true,
6213
+ messageNotificationStyle: "direct"
6214
+ });
6215
+ }
6216
+ nextRequestId() {
6217
+ this.requestId += 1;
6218
+ return String(this.requestId);
6219
+ }
6220
+ sendRpcCommand(type, params) {
6221
+ this.process?.stdin?.write(JSON.stringify({
6222
+ id: this.nextRequestId(),
6223
+ type,
6224
+ ...params
6225
+ }) + "\n");
6226
+ }
6227
+ };
6228
+
5797
6229
  // src/drivers/index.ts
5798
6230
  var driverFactories = {
5799
6231
  claude: () => new ClaudeDriver(),
@@ -5803,7 +6235,8 @@ var driverFactories = {
5803
6235
  cursor: () => new CursorDriver(),
5804
6236
  gemini: () => new GeminiDriver(),
5805
6237
  kimi: () => new KimiDriver(),
5806
- opencode: () => new OpenCodeDriver()
6238
+ opencode: () => new OpenCodeDriver(),
6239
+ pi: () => new PiDriver()
5807
6240
  };
5808
6241
  function getDriver(runtimeId) {
5809
6242
  const createDriver = driverFactories[runtimeId];
@@ -5816,7 +6249,7 @@ function getDriver(runtimeId) {
5816
6249
 
5817
6250
  // src/workspaces.ts
5818
6251
  import { readdir, rm, stat } from "fs/promises";
5819
- import path11 from "path";
6252
+ import path12 from "path";
5820
6253
  function isValidWorkspaceDirectoryName(directoryName) {
5821
6254
  return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
5822
6255
  }
@@ -5824,7 +6257,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
5824
6257
  if (!isValidWorkspaceDirectoryName(directoryName)) {
5825
6258
  return null;
5826
6259
  }
5827
- return path11.join(dataDir, directoryName);
6260
+ return path12.join(dataDir, directoryName);
5828
6261
  }
5829
6262
  function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
5830
6263
  return {
@@ -5873,7 +6306,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
5873
6306
  return summary;
5874
6307
  }
5875
6308
  const childSummaries = await Promise.all(
5876
- entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
6309
+ entries.map((entry) => summarizeWorkspaceEntry(path12.join(dirPath, entry.name), entry))
5877
6310
  );
5878
6311
  for (const childSummary of childSummaries) {
5879
6312
  summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
@@ -5892,7 +6325,7 @@ async function scanWorkspaceDirectories(dataDir) {
5892
6325
  if (!entry.isDirectory()) {
5893
6326
  return null;
5894
6327
  }
5895
- const dirPath = path11.join(dataDir, entry.name);
6328
+ const dirPath = path12.join(dataDir, entry.name);
5896
6329
  try {
5897
6330
  const summary = await summarizeWorkspaceDirectory(dirPath);
5898
6331
  return {
@@ -6068,6 +6501,8 @@ function runtimeDisplayName(runtimeId) {
6068
6501
  return "Kimi CLI";
6069
6502
  case "opencode":
6070
6503
  return "OpenCode";
6504
+ case "pi":
6505
+ return "Pi CLI";
6071
6506
  default:
6072
6507
  return runtimeId || "This runtime";
6073
6508
  }
@@ -6332,12 +6767,12 @@ function findSessionJsonl(root, predicate) {
6332
6767
  for (const entry of entries) {
6333
6768
  if (++visited > maxEntries) return null;
6334
6769
  if (!entry.isFile() || !predicate(entry.name)) continue;
6335
- return path12.join(dir, entry.name);
6770
+ return path13.join(dir, entry.name);
6336
6771
  }
6337
6772
  for (const entry of entries) {
6338
6773
  if (++visited > maxEntries) return null;
6339
6774
  if (!entry.isDirectory()) continue;
6340
- const found = visit(path12.join(dir, entry.name), depth - 1);
6775
+ const found = visit(path13.join(dir, entry.name), depth - 1);
6341
6776
  if (found) return found;
6342
6777
  }
6343
6778
  return null;
@@ -6350,9 +6785,9 @@ function safeSessionFilename(value) {
6350
6785
  }
6351
6786
  function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
6352
6787
  try {
6353
- const dir = path12.join(fallbackDir, ".slock", "runtime-sessions");
6354
- mkdirSync4(dir, { recursive: true });
6355
- const filePath = path12.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
6788
+ const dir = path13.join(fallbackDir, ".slock", "runtime-sessions");
6789
+ mkdirSync5(dir, { recursive: true });
6790
+ const filePath = path13.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
6356
6791
  writeFileSync7(filePath, JSON.stringify({
6357
6792
  type: "runtime_session_handoff",
6358
6793
  runtime,
@@ -6372,7 +6807,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
6372
6807
  }
6373
6808
  }
6374
6809
  function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), fallbackDir) {
6375
- const directPath = path12.isAbsolute(sessionId) ? sessionId : null;
6810
+ const directPath = path13.isAbsolute(sessionId) ? sessionId : null;
6376
6811
  if (directPath) {
6377
6812
  try {
6378
6813
  if (statSync(directPath).isFile()) {
@@ -6381,7 +6816,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), f
6381
6816
  } catch {
6382
6817
  }
6383
6818
  }
6384
- 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;
6819
+ 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;
6385
6820
  if (!resolvedPath && fallbackDir) {
6386
6821
  const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
6387
6822
  if (fallback) return fallback;
@@ -6470,7 +6905,7 @@ function dynamicClaimInstruction(driver) {
6470
6905
  }
6471
6906
  function formatIncomingMessage(message, driver) {
6472
6907
  const threadJoinPrefix = message.thread_join_context ? [
6473
- `[System: You were added to a new thread via @mention. Read this context before replying.]`,
6908
+ `[Slock thread context: you were added to a new thread via @mention.]`,
6474
6909
  `parent: ${message.thread_join_context.parent_target}`,
6475
6910
  `thread: ${message.thread_join_context.thread_target}`,
6476
6911
  `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}")`}`,
@@ -7296,7 +7731,7 @@ function getBusyDeliveryNote(driver) {
7296
7731
  if (driver.busyDeliveryMode === "gated") {
7297
7732
  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.";
7298
7733
  }
7299
- 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.";
7734
+ 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.";
7300
7735
  }
7301
7736
  var NATIVE_STANDING_PROMPT_STARTUP_INPUT = (
7302
7737
  // Claude Code 2.1.114 treats "follow your system prompt" style user turns as
@@ -7805,7 +8240,7 @@ var AgentProcessManager = class _AgentProcessManager {
7805
8240
  );
7806
8241
  wakeMessage = void 0;
7807
8242
  }
7808
- const agentDataDir = path12.join(this.dataDir, agentId);
8243
+ const agentDataDir = path13.join(this.dataDir, agentId);
7809
8244
  await mkdir(agentDataDir, { recursive: true });
7810
8245
  let runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
7811
8246
  const legacyRuntimeProfileControl = runtimeConfig.runtimeProfileControl?.kind === "migration" ? runtimeConfig.runtimeProfileControl : null;
@@ -7819,23 +8254,23 @@ var AgentProcessManager = class _AgentProcessManager {
7819
8254
  );
7820
8255
  runtimeConfig = { ...runtimeConfig, runtimeProfileControl: null };
7821
8256
  }
7822
- const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
8257
+ const memoryMdPath = path13.join(agentDataDir, "MEMORY.md");
7823
8258
  try {
7824
8259
  await access(memoryMdPath);
7825
8260
  } catch {
7826
8261
  const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
7827
8262
  await writeFile(memoryMdPath, initialMemoryMd);
7828
8263
  }
7829
- const notesDir = path12.join(agentDataDir, "notes");
8264
+ const notesDir = path13.join(agentDataDir, "notes");
7830
8265
  await mkdir(notesDir, { recursive: true });
7831
8266
  if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
7832
8267
  const seedFiles = buildOnboardingSeedFiles();
7833
8268
  for (const { relativePath, content } of seedFiles) {
7834
- const fullPath = path12.join(agentDataDir, relativePath);
8269
+ const fullPath = path13.join(agentDataDir, relativePath);
7835
8270
  try {
7836
8271
  await access(fullPath);
7837
8272
  } catch {
7838
- await mkdir(path12.dirname(fullPath), { recursive: true });
8273
+ await mkdir(path13.dirname(fullPath), { recursive: true });
7839
8274
  await writeFile(fullPath, content);
7840
8275
  }
7841
8276
  }
@@ -8707,7 +9142,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8707
9142
  return true;
8708
9143
  }
8709
9144
  async resetWorkspace(agentId) {
8710
- const agentDataDir = path12.join(this.dataDir, agentId);
9145
+ const agentDataDir = path13.join(this.dataDir, agentId);
8711
9146
  try {
8712
9147
  await rm2(agentDataDir, { recursive: true, force: true });
8713
9148
  logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
@@ -8768,7 +9203,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8768
9203
  return result;
8769
9204
  }
8770
9205
  buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
8771
- const workspacePath = path12.join(this.dataDir, agentId);
9206
+ const workspacePath = path13.join(this.dataDir, agentId);
8772
9207
  return {
8773
9208
  agentId,
8774
9209
  launchId,
@@ -9025,7 +9460,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9025
9460
  }
9026
9461
  // Workspace file browsing
9027
9462
  async getFileTree(agentId, dirPath) {
9028
- const agentDir = path12.join(this.dataDir, agentId);
9463
+ const agentDir = path13.join(this.dataDir, agentId);
9029
9464
  try {
9030
9465
  await stat2(agentDir);
9031
9466
  } catch {
@@ -9033,8 +9468,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9033
9468
  }
9034
9469
  let targetDir = agentDir;
9035
9470
  if (dirPath) {
9036
- const resolved = path12.resolve(agentDir, dirPath);
9037
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
9471
+ const resolved = path13.resolve(agentDir, dirPath);
9472
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
9038
9473
  return [];
9039
9474
  }
9040
9475
  targetDir = resolved;
@@ -9042,14 +9477,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9042
9477
  return this.listDirectoryChildren(targetDir, agentDir);
9043
9478
  }
9044
9479
  async readFile(agentId, filePath) {
9045
- const agentDir = path12.join(this.dataDir, agentId);
9046
- const resolved = path12.resolve(agentDir, filePath);
9047
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
9480
+ const agentDir = path13.join(this.dataDir, agentId);
9481
+ const resolved = path13.resolve(agentDir, filePath);
9482
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
9048
9483
  throw new Error("Access denied");
9049
9484
  }
9050
9485
  const info = await stat2(resolved);
9051
9486
  if (info.isDirectory()) throw new Error("Cannot read a directory");
9052
- const ext = path12.extname(resolved).toLowerCase();
9487
+ const ext = path13.extname(resolved).toLowerCase();
9053
9488
  if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
9054
9489
  if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
9055
9490
  const content = await readFile(resolved, "utf-8");
@@ -9084,13 +9519,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9084
9519
  const agent = this.agents.get(agentId);
9085
9520
  const runtime = runtimeHint || agent?.config.runtime || "claude";
9086
9521
  const home = os5.homedir();
9087
- const workspaceDir = path12.join(this.dataDir, agentId);
9522
+ const workspaceDir = path13.join(this.dataDir, agentId);
9088
9523
  const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
9089
9524
  const globalResults = await Promise.all(
9090
- paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
9525
+ paths.global.map((p) => this.scanSkillsDir(path13.join(home, p)))
9091
9526
  );
9092
9527
  const workspaceResults = await Promise.all(
9093
- paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
9528
+ paths.workspace.map((p) => this.scanSkillsDir(path13.join(workspaceDir, p)))
9094
9529
  );
9095
9530
  const dedup = (skills) => {
9096
9531
  const seen = /* @__PURE__ */ new Set();
@@ -9119,7 +9554,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9119
9554
  const skills = [];
9120
9555
  for (const entry of entries) {
9121
9556
  if (entry.isDirectory() || entry.isSymbolicLink()) {
9122
- const skillMd = path12.join(dir, entry.name, "SKILL.md");
9557
+ const skillMd = path13.join(dir, entry.name, "SKILL.md");
9123
9558
  try {
9124
9559
  const content = await readFile(skillMd, "utf-8");
9125
9560
  const skill = this.parseSkillMd(entry.name, content);
@@ -9130,7 +9565,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9130
9565
  } else if (entry.name.endsWith(".md")) {
9131
9566
  const cmdName = entry.name.replace(/\.md$/, "");
9132
9567
  try {
9133
- const content = await readFile(path12.join(dir, entry.name), "utf-8");
9568
+ const content = await readFile(path13.join(dir, entry.name), "utf-8");
9134
9569
  const skill = this.parseSkillMd(cmdName, content);
9135
9570
  skill.sourcePath = dir;
9136
9571
  skills.push(skill);
@@ -10019,15 +10454,23 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10019
10454
  }
10020
10455
  }
10021
10456
  recordRuntimeTelemetry(agentId, ap, event) {
10457
+ const telemetryAttrs = {
10458
+ ...event.attrs,
10459
+ ...event.source ? { source: event.source } : {},
10460
+ ...event.usageKind ? { usageKind: event.usageKind } : {},
10461
+ ...event.sessionId ? { sessionId: event.sessionId } : {},
10462
+ ...event.turnId ? { turnId: event.turnId } : {},
10463
+ ...event.runtimeResultId ? { runtimeResultId: event.runtimeResultId } : {}
10464
+ };
10022
10465
  const attrs = {
10023
10466
  agentId,
10024
10467
  launchId: ap.launchId || void 0,
10025
10468
  runtime: ap.config.runtime,
10026
10469
  model: ap.config.model,
10027
10470
  telemetry_name: event.name,
10028
- ...event.attrs
10471
+ ...telemetryAttrs
10029
10472
  };
10030
- ap.runtimeTraceSpan?.addEvent(`runtime.telemetry.${event.name}`, event.attrs);
10473
+ ap.runtimeTraceSpan?.addEvent(`runtime.telemetry.${event.name}`, telemetryAttrs);
10031
10474
  this.recordDaemonTrace(`daemon.runtime.telemetry.${event.name}`, attrs);
10032
10475
  }
10033
10476
  sendAgentStatus(agentId, status, launchId) {
@@ -10101,7 +10544,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10101
10544
  }
10102
10545
  const inboxCount = ap.inbox.length;
10103
10546
  if (inboxCount === 0) return false;
10104
- 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.]`;
10547
+ 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.]`;
10105
10548
  logger.info(`[Agent ${agentId}] Sending stdin notification: ${inboxCount} pending inbox message(s)`);
10106
10549
  const encoded = ap.driver.encodeStdinMessage(notification, ap.sessionId, { mode: "busy" });
10107
10550
  if (encoded) {
@@ -10248,8 +10691,8 @@ ${RESPONSE_TARGET_HINT}`);
10248
10691
  const nodes = [];
10249
10692
  for (const entry of entries) {
10250
10693
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
10251
- const fullPath = path12.join(dir, entry.name);
10252
- const relativePath = path12.relative(rootDir, fullPath);
10694
+ const fullPath = path13.join(dir, entry.name);
10695
+ const relativePath = path13.relative(rootDir, fullPath);
10253
10696
  let info;
10254
10697
  try {
10255
10698
  info = await stat2(fullPath);
@@ -10553,10 +10996,10 @@ var ReminderCache = class {
10553
10996
  };
10554
10997
 
10555
10998
  // src/machineLock.ts
10556
- import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
10557
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
10999
+ import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
11000
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
10558
11001
  import os6 from "os";
10559
- import path13 from "path";
11002
+ import path14 from "path";
10560
11003
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
10561
11004
  var DaemonMachineLockConflictError = class extends Error {
10562
11005
  code = "DAEMON_MACHINE_LOCK_HELD";
@@ -10578,7 +11021,7 @@ function resolveDefaultMachineStateRoot() {
10578
11021
  return resolveSlockHomePath("machines");
10579
11022
  }
10580
11023
  function ownerPath(lockDir) {
10581
- return path13.join(lockDir, "owner.json");
11024
+ return path14.join(lockDir, "owner.json");
10582
11025
  }
10583
11026
  function readOwner(lockDir) {
10584
11027
  try {
@@ -10608,13 +11051,13 @@ function acquireDaemonMachineLock(options) {
10608
11051
  const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
10609
11052
  const fingerprint = apiKeyFingerprint(options.apiKey);
10610
11053
  const lockId = getDaemonMachineLockId(options.apiKey);
10611
- const machineDir = path13.join(rootDir, lockId);
10612
- const lockDir = path13.join(machineDir, "daemon.lock");
10613
- const token = randomUUID3();
10614
- mkdirSync5(machineDir, { recursive: true });
11054
+ const machineDir = path14.join(rootDir, lockId);
11055
+ const lockDir = path14.join(machineDir, "daemon.lock");
11056
+ const token = randomUUID4();
11057
+ mkdirSync6(machineDir, { recursive: true });
10615
11058
  for (let attempt = 0; attempt < 2; attempt += 1) {
10616
11059
  try {
10617
- mkdirSync5(lockDir);
11060
+ mkdirSync6(lockDir);
10618
11061
  const owner = {
10619
11062
  pid: process.pid,
10620
11063
  token,
@@ -10637,7 +11080,15 @@ function acquireDaemonMachineLock(options) {
10637
11080
  release: () => {
10638
11081
  const currentOwner = readOwner(lockDir);
10639
11082
  if (currentOwner?.pid === process.pid && currentOwner.token === token) {
10640
- rmSync3(lockDir, { recursive: true, force: true });
11083
+ const released = { ...currentOwner, pid: 0 };
11084
+ try {
11085
+ writeFileSync8(ownerPath(lockDir), `${JSON.stringify(released, null, 2)}
11086
+ `, {
11087
+ mode: 384
11088
+ });
11089
+ } catch {
11090
+ rmSync3(lockDir, { recursive: true, force: true });
11091
+ }
10641
11092
  }
10642
11093
  }
10643
11094
  };
@@ -10661,8 +11112,8 @@ function acquireDaemonMachineLock(options) {
10661
11112
  }
10662
11113
 
10663
11114
  // src/localTraceSink.ts
10664
- import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync2, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
10665
- import path14 from "path";
11115
+ import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync2, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
11116
+ import path15 from "path";
10666
11117
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
10667
11118
  var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
10668
11119
  var DEFAULT_MAX_FILES = 8;
@@ -10699,7 +11150,7 @@ var LocalRotatingTraceSink = class {
10699
11150
  currentSize = 0;
10700
11151
  sequence = 0;
10701
11152
  constructor(options) {
10702
- this.traceDir = path14.join(options.machineDir, "traces");
11153
+ this.traceDir = path15.join(options.machineDir, "traces");
10703
11154
  this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
10704
11155
  const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
10705
11156
  const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
@@ -10725,11 +11176,11 @@ var LocalRotatingTraceSink = class {
10725
11176
  return this.currentFile;
10726
11177
  }
10727
11178
  ensureFile(nextBytes) {
10728
- mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
11179
+ mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
10729
11180
  const nowMs = this.nowMsProvider();
10730
11181
  const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
10731
11182
  if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
10732
- this.currentFile = path14.join(
11183
+ this.currentFile = path15.join(
10733
11184
  this.traceDir,
10734
11185
  `daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
10735
11186
  );
@@ -10744,7 +11195,7 @@ var LocalRotatingTraceSink = class {
10744
11195
  const excess = files.length - this.maxFiles;
10745
11196
  if (excess <= 0) return;
10746
11197
  for (const file of files.slice(0, excess)) {
10747
- rmSync4(path14.join(this.traceDir, file), { force: true });
11198
+ rmSync4(path15.join(this.traceDir, file), { force: true });
10748
11199
  }
10749
11200
  }
10750
11201
  };
@@ -10832,14 +11283,14 @@ function isDiagnosticErrorAttr(key) {
10832
11283
  }
10833
11284
 
10834
11285
  // src/traceBundleUpload.ts
10835
- import { createHash as createHash6, randomUUID as randomUUID4 } from "crypto";
11286
+ import { createHash as createHash6, randomUUID as randomUUID5 } from "crypto";
10836
11287
  import { gzipSync } from "zlib";
10837
11288
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
10838
- import path15 from "path";
11289
+ import path16 from "path";
10839
11290
 
10840
11291
  // src/directUploadCapability.ts
10841
- function joinUrl(base, path17) {
10842
- return `${base.replace(/\/+$/, "")}${path17}`;
11292
+ function joinUrl(base, path18) {
11293
+ return `${base.replace(/\/+$/, "")}${path18}`;
10843
11294
  }
10844
11295
  function jsonHeaders(apiKey) {
10845
11296
  return {
@@ -11058,7 +11509,7 @@ var DaemonTraceBundleUploader = class {
11058
11509
  }, nextMs);
11059
11510
  }
11060
11511
  async findUploadCandidates() {
11061
- const traceDir = path15.join(this.options.machineDir, "traces");
11512
+ const traceDir = path16.join(this.options.machineDir, "traces");
11062
11513
  let names;
11063
11514
  try {
11064
11515
  names = await readdir3(traceDir);
@@ -11070,8 +11521,8 @@ var DaemonTraceBundleUploader = class {
11070
11521
  const currentFile = this.options.currentFileProvider?.();
11071
11522
  const candidates = [];
11072
11523
  for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
11073
- const file = path15.join(traceDir, name);
11074
- if (currentFile && path15.resolve(file) === path15.resolve(currentFile)) continue;
11524
+ const file = path16.join(traceDir, name);
11525
+ if (currentFile && path16.resolve(file) === path16.resolve(currentFile)) continue;
11075
11526
  if (await this.isUploaded(file)) continue;
11076
11527
  try {
11077
11528
  const info = await stat3(file);
@@ -11103,7 +11554,7 @@ var DaemonTraceBundleUploader = class {
11103
11554
  }
11104
11555
  const gzipped = gzipSync(raw);
11105
11556
  const bundleSha256 = sha256Hex(gzipped);
11106
- const bundleId = randomUUID4();
11557
+ const bundleId = randomUUID5();
11107
11558
  await uploadWithSignedCapability({
11108
11559
  serverUrl: this.options.serverUrl,
11109
11560
  apiKey: this.options.apiKey,
@@ -11145,8 +11596,8 @@ var DaemonTraceBundleUploader = class {
11145
11596
  }
11146
11597
  }
11147
11598
  uploadStatePath(file) {
11148
- const stateDir = path15.join(this.options.machineDir, "trace-uploads");
11149
- return path15.join(stateDir, `${path15.basename(file)}.uploaded.json`);
11599
+ const stateDir = path16.join(this.options.machineDir, "trace-uploads");
11600
+ return path16.join(stateDir, `${path16.basename(file)}.uploaded.json`);
11150
11601
  }
11151
11602
  async isUploaded(file) {
11152
11603
  try {
@@ -11158,9 +11609,9 @@ var DaemonTraceBundleUploader = class {
11158
11609
  }
11159
11610
  async markUploaded(file, metadata) {
11160
11611
  const stateFile = this.uploadStatePath(file);
11161
- await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
11612
+ await mkdir2(path16.dirname(stateFile), { recursive: true, mode: 448 });
11162
11613
  await writeFile2(stateFile, `${JSON.stringify({
11163
- file: path15.basename(file),
11614
+ file: path16.basename(file),
11164
11615
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
11165
11616
  ...metadata
11166
11617
  }, null, 2)}
@@ -11237,23 +11688,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
11237
11688
  }
11238
11689
  }
11239
11690
  function resolveChatBridgePath(moduleUrl = import.meta.url) {
11240
- const dirname = path16.dirname(fileURLToPath(moduleUrl));
11241
- const jsPath = path16.resolve(dirname, "chat-bridge.js");
11691
+ const dirname = path17.dirname(fileURLToPath(moduleUrl));
11692
+ const jsPath = path17.resolve(dirname, "chat-bridge.js");
11242
11693
  try {
11243
11694
  accessSync(jsPath);
11244
11695
  return jsPath;
11245
11696
  } catch {
11246
- return path16.resolve(dirname, "chat-bridge.ts");
11697
+ return path17.resolve(dirname, "chat-bridge.ts");
11247
11698
  }
11248
11699
  }
11249
11700
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
11250
- const thisDir = path16.dirname(fileURLToPath(moduleUrl));
11251
- const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
11701
+ const thisDir = path17.dirname(fileURLToPath(moduleUrl));
11702
+ const bundledDistPath = path17.resolve(thisDir, "cli", "index.js");
11252
11703
  try {
11253
11704
  accessSync(bundledDistPath);
11254
11705
  return bundledDistPath;
11255
11706
  } catch {
11256
- const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
11707
+ const workspaceDistPath = path17.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
11257
11708
  accessSync(workspaceDistPath);
11258
11709
  return workspaceDistPath;
11259
11710
  }
@@ -11383,6 +11834,11 @@ function summarizeIncomingMessage(msg) {
11383
11834
  var DaemonCore = class {
11384
11835
  options;
11385
11836
  daemonVersion;
11837
+ // When this runner is launched by a managed Computer service, the
11838
+ // service exports SLOCK_COMPUTER_VERSION (the `@slock-ai/computer`
11839
+ // bundle version). Reported in `ready` so the server can surface the
11840
+ // Computer's own version (distinct from the underlying daemonVersion).
11841
+ computerVersion;
11386
11842
  chatBridgePath;
11387
11843
  slockCliPath;
11388
11844
  slockHome;
@@ -11398,6 +11854,7 @@ var DaemonCore = class {
11398
11854
  constructor(options) {
11399
11855
  this.options = options;
11400
11856
  this.daemonVersion = options.daemonVersion ?? readDaemonVersion();
11857
+ this.computerVersion = (process.env.SLOCK_COMPUTER_VERSION || "").trim() || null;
11401
11858
  this.chatBridgePath = options.chatBridgePath ?? resolveChatBridgePath();
11402
11859
  this.slockCliPath = options.slockCliPath ?? resolveSlockCliPath();
11403
11860
  this.slockHome = resolveSlockHome();
@@ -11432,7 +11889,7 @@ var DaemonCore = class {
11432
11889
  }
11433
11890
  resolveMachineStateRoot() {
11434
11891
  if (this.options.machineStateDir) return this.options.machineStateDir;
11435
- if (this.options.dataDir) return path16.join(path16.dirname(this.options.dataDir), "machines");
11892
+ if (this.options.dataDir) return path17.join(path17.dirname(this.options.dataDir), "machines");
11436
11893
  return resolveDefaultMachineStateRoot();
11437
11894
  }
11438
11895
  shouldEnableLocalTrace() {
@@ -11458,7 +11915,7 @@ var DaemonCore = class {
11458
11915
  sink: this.localTraceSink
11459
11916
  });
11460
11917
  this.agentManager.setTracer(this.tracer);
11461
- this.agentManager.setCliTransportTraceDir(path16.join(machineDir, "traces"));
11918
+ this.agentManager.setCliTransportTraceDir(path17.join(machineDir, "traces"));
11462
11919
  }
11463
11920
  installTraceBundleUploader(machineDir) {
11464
11921
  if (!this.shouldEnableLocalTrace()) return;
@@ -11857,6 +12314,26 @@ var DaemonCore = class {
11857
12314
  case "ping":
11858
12315
  this.connection.send({ type: "pong" });
11859
12316
  break;
12317
+ case "computer:restart":
12318
+ case "computer:upgrade": {
12319
+ const action = msg.type === "computer:restart" ? "restart" : "upgrade";
12320
+ this.recordDaemonTrace("daemon.computer_control.received", {
12321
+ action,
12322
+ handled: Boolean(this.options.onComputerControl)
12323
+ });
12324
+ if (this.options.onComputerControl) {
12325
+ try {
12326
+ this.options.onComputerControl(action);
12327
+ } catch (err) {
12328
+ logger.error(
12329
+ `[Daemon] computer:${action} control handler failed: ${err instanceof Error ? err.message : String(err)}`
12330
+ );
12331
+ }
12332
+ } else {
12333
+ logger.info(`[Daemon] Ignoring computer:${action} \u2014 not launched by a Computer service.`);
12334
+ }
12335
+ break;
12336
+ }
11860
12337
  }
11861
12338
  }
11862
12339
  onReminderFire(job) {
@@ -11883,7 +12360,8 @@ var DaemonCore = class {
11883
12360
  runningAgents: runningAgentIds,
11884
12361
  hostname: this.options.hostname ?? os7.hostname(),
11885
12362
  os: this.options.osDescription ?? `${os7.platform()} ${os7.arch()}`,
11886
- daemonVersion: this.daemonVersion
12363
+ daemonVersion: this.daemonVersion,
12364
+ ...this.computerVersion ? { computerVersion: this.computerVersion } : {}
11887
12365
  });
11888
12366
  this.recordDaemonTrace("daemon.ready.sent", {
11889
12367
  runtimes_count: runtimes.length,