@slock-ai/daemon 0.55.3-play.20260531155023 → 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.
@@ -12,7 +12,7 @@ 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 as fileURLToPath2 } from "url";
15
+ import { fileURLToPath } 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,19 +990,24 @@ 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 },
994
993
  { id: "antigravity", displayName: "Antigravity CLI", binary: "agy", supported: true },
995
994
  { id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true },
996
995
  { id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
997
996
  { id: "cursor", displayName: "Cursor CLI", binary: "cursor-agent", supported: true },
998
997
  { id: "gemini", displayName: "Gemini CLI", binary: "gemini", supported: true },
999
- { 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 }
1000
1000
  ];
1001
1001
  var RUNTIME_MODELS = {
1002
1002
  claude: [
1003
- { id: "opus", label: "Opus" },
1004
- { id: "sonnet", label: "Sonnet" },
1005
- { 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" }
1006
1011
  ],
1007
1012
  codex: [
1008
1013
  { id: "gpt-5.5", label: "GPT-5.5" },
@@ -1044,11 +1049,9 @@ var RUNTIME_MODELS = {
1044
1049
  { id: "fusecode/opus[1m]", label: "Opus 1M via FuseCode", verified: "suggestion_only" }
1045
1050
  ],
1046
1051
  pi: [
1047
- { id: "deepseek/deepseek-v4-pro", label: "DeepSeek V4 Pro" },
1048
- { id: "deepseek/deepseek-v4-flash", label: "DeepSeek V4 Flash" },
1049
- { id: "kimi-coding/kimi-for-coding", label: "Kimi for Coding" },
1050
- { id: "zai/glm-5.1", label: "GLM-5.1" },
1051
- { id: "zai/glm-4.7", label: "GLM-4.7" }
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" }
1052
1055
  ],
1053
1056
  // Kimi CLI resolves model keys from each user's local config, so the safest
1054
1057
  // built-in option is to defer to whatever default model the CLI already uses.
@@ -1094,22 +1097,28 @@ function isPresetRuntimeModel(runtime, model) {
1094
1097
  function modelConfigFromLegacy(runtime, model) {
1095
1098
  return isPresetRuntimeModel(runtime, model) ? { kind: "preset", id: model } : { kind: "custom", name: model };
1096
1099
  }
1097
- function parseProviderConfig(runtime, value) {
1100
+ function parseProviderConfig(runtime, value, legacyApiUrl, legacyApiKey) {
1098
1101
  if (runtime !== "claude") return void 0;
1099
1102
  if (!isPlainRecord(value)) return { kind: "default" };
1100
1103
  if (value.kind === "custom" && typeof value.apiUrl === "string" && value.apiUrl.trim() && typeof value.apiKey === "string" && value.apiKey.trim()) {
1101
1104
  return { kind: "custom", apiUrl: value.apiUrl.trim(), apiKey: value.apiKey.trim() };
1102
1105
  }
1106
+ if (value.kind === "custom" && legacyApiUrl?.trim() && legacyApiKey?.trim()) {
1107
+ return { kind: "custom", apiUrl: legacyApiUrl.trim(), apiKey: legacyApiKey.trim() };
1108
+ }
1103
1109
  return { kind: "default" };
1104
1110
  }
1105
- function parseModelConfig(runtime, value, fallback) {
1111
+ function parseModelConfig(runtime, value, fallback, provider, legacyCustomModel) {
1106
1112
  if (!isPlainRecord(value)) return modelConfigFromLegacy(runtime, fallback);
1107
1113
  if (value.kind === "custom" && typeof value.name === "string" && value.name.trim()) {
1108
1114
  return { kind: "custom", name: value.name.trim() };
1109
1115
  }
1110
- 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())) {
1111
1117
  return { kind: "preset", id: value.id.trim() };
1112
1118
  }
1119
+ if (provider?.kind === "custom" && runtime === "claude" && legacyCustomModel?.trim()) {
1120
+ return { kind: "custom", name: legacyCustomModel.trim() };
1121
+ }
1113
1122
  return modelConfigFromLegacy(runtime, fallback);
1114
1123
  }
1115
1124
  function parseModeConfig(value) {
@@ -1125,12 +1134,13 @@ function hydrateRuntimeConfig(input) {
1125
1134
  const storedEnvVars = normalizeEnvVars(stored?.envVars);
1126
1135
  const legacyClaudeApiUrl = runtime === "claude" ? legacyEnvVars?.ANTHROPIC_BASE_URL : void 0;
1127
1136
  const legacyClaudeApiKey = runtime === "claude" ? legacyEnvVars?.ANTHROPIC_API_KEY : void 0;
1128
- 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;
1129
1139
  return {
1130
1140
  version: RUNTIME_CONFIG_VERSION,
1131
1141
  runtime,
1132
1142
  ...provider ? { provider } : {},
1133
- model: stored ? parseModelConfig(runtime, stored.model, fallbackModel) : modelConfigFromLegacy(runtime, fallbackModel),
1143
+ model: stored ? parseModelConfig(runtime, stored.model, fallbackModel, provider, legacyClaudeCustomModel) : modelConfigFromLegacy(runtime, fallbackModel),
1134
1144
  mode: stored ? parseModeConfig(stored.mode) : { kind: "default" },
1135
1145
  reasoningEffort: stored?.reasoningEffort ?? input.reasoningEffort ?? null,
1136
1146
  envVars: stripControlledRuntimeEnvVars(runtime, stored ? storedEnvVars : legacyEnvVars)
@@ -1198,7 +1208,7 @@ var DISPLAY_PLAN_CONFIG = {
1198
1208
  };
1199
1209
 
1200
1210
  // src/agentProcessManager.ts
1201
- import { mkdirSync as mkdirSync5, readdirSync, statSync, writeFileSync as writeFileSync8 } from "fs";
1211
+ import { mkdirSync as mkdirSync5, readdirSync, statSync, writeFileSync as writeFileSync7 } from "fs";
1202
1212
  import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
1203
1213
  import { createHash as createHash3 } from "crypto";
1204
1214
  import path13 from "path";
@@ -1382,19 +1392,19 @@ If Slock says a message was not sent and was saved as a draft, choose one path:
1382
1392
 
1383
1393
  Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
1384
1394
 
1385
- - **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).
1386
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.
1387
- - **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.
1388
1398
  - When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
1389
- - You can read thread history: \`slock message read --channel "#general:a1b2c3d4"\`
1390
- - 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.
1391
1401
  - Threads cannot be nested \u2014 you cannot start a thread inside a thread.` : `### Threads
1392
1402
 
1393
1403
  Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
1394
1404
 
1395
- - **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).
1396
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.
1397
- - **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.
1398
1408
  - When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
1399
1409
  - You can read thread history via ${readCmd} with the same thread target.
1400
1410
  - Threads cannot be nested \u2014 you cannot start a thread inside a thread.`;
@@ -1560,13 +1570,18 @@ ${opts.postStartupNotes.join("\n")}`;
1560
1570
  Messages you receive have a single RFC 5424-style structured data header followed by the sender and content:
1561
1571
 
1562
1572
  \`\`\`
1563
- [target=#general msg=a1b2c3d4 time=2026-03-15T01:00:00 type=human] @richard: hello everyone
1564
- [target=#general msg=e5f6a7b8 time=2026-03-15T01:00:01 type=agent] @Alice: hi there
1565
- [target=dm:@richard msg=c9d0e1f2 time=2026-03-15T01:00:02 type=human] @richard: hey, can you help?
1566
- [target=#general:a1b2c3d4 msg=f3a4b5c6 time=2026-03-15T01:00:03 type=human] @richard: thread reply
1567
- [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
1568
1578
  \`\`\`
1569
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
+
1570
1585
  Header fields:
1571
1586
  - \`target=\` \u2014 where the message came from. Reuse as the \`target\` parameter when replying.
1572
1587
  - \`msg=\` \u2014 message short ID (first 8 chars of UUID). Use as thread suffix to start/reply in a thread.
@@ -1638,9 +1653,11 @@ Slock auto-renders these inline tokens as interactive links whenever they appear
1638
1653
 
1639
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.
1640
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
+
1641
1658
  ### Formatting \u2014 URLs in non-English text
1642
1659
 
1643
- 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.
1644
1661
 
1645
1662
  - **Wrong**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1Ahttp://localhost:3000\uFF0C\u8BF7\u67E5\u770B\` (the \`\uFF0C\` gets swallowed into the link)
1646
1663
  - **Correct**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1A<http://localhost:3000>\uFF0C\u8BF7\u67E5\u770B\`
@@ -1720,12 +1737,12 @@ How to handle these:
1720
1737
  - Call ${checkCmd} at the next safe breakpoint to materialize the pending messages before taking side-effect actions that depend on current context.
1721
1738
  - If the new message is higher priority, pivot after reading it. If not, continue your current work.`;
1722
1739
  } else {
1723
- 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.]\``;
1724
1741
  prompt += `
1725
1742
 
1726
1743
  ## Message Notifications
1727
1744
 
1728
- 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:
1729
1746
 
1730
1747
  ${notifyExample}
1731
1748
 
@@ -1786,19 +1803,6 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
1786
1803
  return candidates.filter((candidate) => existsSync(candidate.path));
1787
1804
  }
1788
1805
 
1789
- // src/authEnv.ts
1790
- var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
1791
- var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
1792
- function scrubDaemonAuthEnv(env) {
1793
- delete env[DAEMON_API_KEY_ENV];
1794
- return env;
1795
- }
1796
- function scrubDaemonChildEnv(env) {
1797
- delete env[DAEMON_API_KEY_ENV];
1798
- delete env[SLOCK_AGENT_TOKEN_ENV];
1799
- return env;
1800
- }
1801
-
1802
1806
  // src/agentCredentialProxy.ts
1803
1807
  import { randomBytes } from "crypto";
1804
1808
  import http from "http";
@@ -2851,9 +2855,7 @@ var LOOPBACK_NO_PROXY = "127.0.0.1,localhost";
2851
2855
  var CLI_TRANSPORT_TRACE_DIR_ENV = "SLOCK_CLI_TRANSPORT_TRACE_DIR";
2852
2856
  var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
2853
2857
  var RAW_CREDENTIAL_ENV_DENYLIST = [
2854
- "SLOCK_AGENT_TOKEN",
2855
- "SLOCK_AGENT_CREDENTIAL_KEY",
2856
- "SLOCK_AGENT_CREDENTIAL_KEY_FILE"
2858
+ "SLOCK_AGENT_CREDENTIAL_KEY"
2857
2859
  ];
2858
2860
  var cachedOpencliBinPath;
2859
2861
  function resolveOpencliBinPath() {
@@ -3068,7 +3070,7 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(opencliBinPath)} "
3068
3070
  ...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
3069
3071
  PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
3070
3072
  };
3071
- scrubDaemonChildEnv(spawnEnv);
3073
+ delete spawnEnv.SLOCK_AGENT_TOKEN;
3072
3074
  for (const key of RAW_CREDENTIAL_ENV_DENYLIST) {
3073
3075
  delete spawnEnv[key];
3074
3076
  }
@@ -3104,6 +3106,125 @@ function collectResultErrorDetail(message, fallback) {
3104
3106
  function isProviderApiFailureText(value, hasToolUse) {
3105
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));
3106
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
+ }
3107
3228
  var ClaudeEventNormalizer = class {
3108
3229
  normalizeLine(line) {
3109
3230
  let event;
@@ -3178,6 +3299,7 @@ var ClaudeEventNormalizer = class {
3178
3299
  case "result": {
3179
3300
  const subtype = typeof event.subtype === "string" ? event.subtype : "success";
3180
3301
  const stopReason = typeof event.stop_reason === "string" ? event.stop_reason : null;
3302
+ const usageTelemetry = parseClaudeResultUsageTelemetry(event);
3181
3303
  switch (subtype) {
3182
3304
  case "success":
3183
3305
  if (event.is_error && stopReason !== "max_tokens") {
@@ -3199,6 +3321,7 @@ var ClaudeEventNormalizer = class {
3199
3321
  pushResultError(event, "Structured output retries exceeded");
3200
3322
  break;
3201
3323
  }
3324
+ if (usageTelemetry) events.push(usageTelemetry);
3202
3325
  events.push({ kind: "turn_end", sessionId: event.session_id });
3203
3326
  break;
3204
3327
  }
@@ -3367,7 +3490,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn, existsSyncFn) {
3367
3490
  }
3368
3491
  function resolveCommandOnPath(command, deps = {}) {
3369
3492
  const platform = deps.platform ?? process.platform;
3370
- const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
3493
+ const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
3371
3494
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
3372
3495
  const existsSyncFn = deps.existsSyncFn ?? existsSync2;
3373
3496
  if (platform === "win32") {
@@ -3393,7 +3516,7 @@ function firstExistingPath(candidates, deps = {}) {
3393
3516
  return null;
3394
3517
  }
3395
3518
  function readCommandVersion(command, args = [], deps = {}) {
3396
- const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
3519
+ const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
3397
3520
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
3398
3521
  try {
3399
3522
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -3622,53 +3745,64 @@ var RuntimeTurnState = class {
3622
3745
  };
3623
3746
 
3624
3747
  // src/drivers/codexTelemetrySidecar.ts
3625
- function finiteNumber(value) {
3748
+ function finiteNumber2(value) {
3626
3749
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3627
3750
  }
3628
- function finiteString(value) {
3751
+ function finiteString2(value) {
3629
3752
  return typeof value === "string" && value.length > 0 ? value : void 0;
3630
3753
  }
3631
3754
  function ratio(numerator, denominator) {
3632
3755
  if (numerator === void 0 || denominator === void 0 || denominator <= 0) return void 0;
3633
3756
  return Number((numerator / denominator).toFixed(6));
3634
3757
  }
3635
- function withDefined(attrs) {
3758
+ function withDefined2(attrs) {
3636
3759
  return Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== void 0));
3637
3760
  }
3638
3761
  function parseTokenUsageTelemetry(message) {
3639
3762
  const usage = message.params?.tokenUsage;
3640
3763
  const total = usage?.total;
3641
3764
  if (!total || typeof total !== "object") return null;
3642
- const inputTokens = finiteNumber(total.inputTokens);
3643
- const cachedInputTokens = finiteNumber(total.cachedInputTokens);
3644
- const totalTokens = finiteNumber(total.totalTokens);
3645
- const modelContextWindow = finiteNumber(usage.modelContextWindow);
3646
- 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({
3647
3770
  totalTokens,
3648
3771
  inputTokens,
3649
3772
  cachedInputTokens,
3650
- outputTokens: finiteNumber(total.outputTokens),
3651
- reasoningOutputTokens: finiteNumber(total.reasoningOutputTokens),
3773
+ outputTokens: finiteNumber2(total.outputTokens),
3774
+ reasoningOutputTokens: finiteNumber2(total.reasoningOutputTokens),
3652
3775
  modelContextWindow,
3653
3776
  cachedInputRatio: ratio(cachedInputTokens, inputTokens),
3654
3777
  contextUtilization: ratio(totalTokens, modelContextWindow)
3655
3778
  });
3656
3779
  if (Object.keys(attrs).length === 0) return null;
3657
- 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
+ };
3658
3787
  }
3659
3788
  function parseRateLimitTelemetry(message) {
3660
3789
  const rateLimits = message.params?.rateLimits;
3661
3790
  const primary = rateLimits?.primary;
3662
3791
  if (!rateLimits || typeof rateLimits !== "object" || !primary || typeof primary !== "object") return null;
3663
- const attrs = withDefined({
3664
- limitId: finiteString(rateLimits.limitId),
3665
- planType: finiteString(rateLimits.planType),
3666
- usedPercent: finiteNumber(primary.usedPercent),
3667
- windowDurationMins: finiteNumber(primary.windowDurationMins),
3668
- 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)
3669
3798
  });
3670
3799
  if (Object.keys(attrs).length === 0) return null;
3671
- 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
+ };
3672
3806
  }
3673
3807
  function parseCodexTelemetryEvent(message) {
3674
3808
  switch (message.method) {
@@ -3775,7 +3909,11 @@ var CodexEventNormalizer = class {
3775
3909
  }
3776
3910
  const telemetry = parseCodexTelemetryEvent(message);
3777
3911
  if (telemetry) {
3778
- events.push(telemetry);
3912
+ events.push({
3913
+ ...telemetry,
3914
+ ...this.currentThreadId ? { sessionId: this.currentThreadId } : {},
3915
+ ...this.turnState.activeTurnId ? { turnId: this.turnState.activeTurnId } : {}
3916
+ });
3779
3917
  return { events };
3780
3918
  }
3781
3919
  const rawProgress = rawResponseItemProgressEvent(message);
@@ -4825,11 +4963,11 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
4825
4963
  return parseCursorModelsOutput(String(result.stdout || ""));
4826
4964
  }
4827
4965
  function buildCursorModelProbeEnv(deps = {}) {
4828
- return scrubDaemonChildEnv(withWindowsUserEnvironment({
4966
+ return withWindowsUserEnvironment({
4829
4967
  ...deps.env ?? process.env,
4830
4968
  FORCE_COLOR: "0",
4831
4969
  NO_COLOR: "1"
4832
- }, deps));
4970
+ }, deps);
4833
4971
  }
4834
4972
  function runCursorModelsCommand() {
4835
4973
  return spawnSync("cursor-agent", ["models"], {
@@ -4885,7 +5023,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
4885
5023
  }
4886
5024
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
4887
5025
  const existsSyncFn = deps.existsSyncFn ?? existsSync5;
4888
- const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
5026
+ const env = deps.env ?? process.env;
4889
5027
  const winPath = path8.win32;
4890
5028
  let geminiEntry = null;
4891
5029
  try {
@@ -5057,16 +5195,13 @@ var GeminiDriver = class {
5057
5195
  // src/drivers/kimi.ts
5058
5196
  import { randomUUID as randomUUID2 } from "crypto";
5059
5197
  import { spawn as spawn7 } from "child_process";
5060
- import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
5198
+ import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
5061
5199
  import os3 from "os";
5062
5200
  import path9 from "path";
5063
5201
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
5064
5202
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
5065
5203
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
5066
5204
  var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
5067
- var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
5068
- var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
5069
- var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
5070
5205
  function parseToolArguments(raw) {
5071
5206
  if (typeof raw !== "string") return raw;
5072
5207
  try {
@@ -5075,73 +5210,6 @@ function parseToolArguments(raw) {
5075
5210
  return raw;
5076
5211
  }
5077
5212
  }
5078
- function readKimiConfigSource(home = os3.homedir(), env = process.env) {
5079
- const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
5080
- if (inlineConfig && inlineConfig.trim()) {
5081
- return {
5082
- raw: inlineConfig,
5083
- explicitPath: null,
5084
- sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
5085
- };
5086
- }
5087
- const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
5088
- const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
5089
- try {
5090
- return {
5091
- raw: readFileSync3(configPath, "utf8"),
5092
- explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
5093
- sourcePath: configPath
5094
- };
5095
- } catch {
5096
- return {
5097
- raw: null,
5098
- explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
5099
- sourcePath: configPath
5100
- };
5101
- }
5102
- }
5103
- function buildKimiSpawnEnv(env = process.env) {
5104
- const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
5105
- delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
5106
- delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
5107
- return scrubDaemonChildEnv(spawnEnv);
5108
- }
5109
- function buildKimiEffectiveEnv(ctx, overrideEnv) {
5110
- return {
5111
- ...process.env,
5112
- ...ctx.config.envVars || {},
5113
- ...overrideEnv || {}
5114
- };
5115
- }
5116
- function buildKimiLaunchOptions(ctx, opts = {}) {
5117
- const env = buildKimiEffectiveEnv(ctx, opts.env);
5118
- const source = readKimiConfigSource(opts.home ?? os3.homedir(), env);
5119
- const args = [];
5120
- let configFilePath = null;
5121
- let configContent = null;
5122
- if (source.explicitPath) {
5123
- configFilePath = source.explicitPath;
5124
- } else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
5125
- configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
5126
- configContent = source.raw;
5127
- if (opts.writeGeneratedConfig !== false) {
5128
- writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
5129
- chmodSync(configFilePath, 384);
5130
- }
5131
- }
5132
- if (configFilePath) {
5133
- args.push("--config-file", configFilePath);
5134
- }
5135
- if (ctx.config.model && ctx.config.model !== "default") {
5136
- args.push("--model", ctx.config.model);
5137
- }
5138
- return {
5139
- args,
5140
- env: buildKimiSpawnEnv(env),
5141
- configFilePath,
5142
- configContent
5143
- };
5144
- }
5145
5213
  function resolveKimiSpawn(commandArgs, deps = {}) {
5146
5214
  return {
5147
5215
  command: resolveCommandOnPath("kimi", deps) ?? "kimi",
@@ -5165,25 +5233,7 @@ var KimiDriver = class {
5165
5233
  };
5166
5234
  model = {
5167
5235
  detectedModelsVerifiedAs: "launchable",
5168
- toLaunchSpec: (modelId, ctx, opts) => {
5169
- if (!ctx) return { args: ["--model", modelId] };
5170
- const launchCtx = {
5171
- ...ctx,
5172
- config: {
5173
- ...ctx.config,
5174
- model: modelId
5175
- }
5176
- };
5177
- const launch = buildKimiLaunchOptions(launchCtx, {
5178
- home: opts?.home,
5179
- writeGeneratedConfig: false
5180
- });
5181
- return {
5182
- args: launch.args,
5183
- env: launch.env,
5184
- configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
5185
- };
5186
- }
5236
+ toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
5187
5237
  };
5188
5238
  supportsStdinNotification = true;
5189
5239
  mcpToolPrefix = "";
@@ -5237,7 +5287,6 @@ var KimiDriver = class {
5237
5287
  }
5238
5288
  }
5239
5289
  }), "utf8");
5240
- const launch = buildKimiLaunchOptions(ctx);
5241
5290
  const args = [
5242
5291
  "--wire",
5243
5292
  "--yolo",
@@ -5246,16 +5295,15 @@ var KimiDriver = class {
5246
5295
  "--mcp-config-file",
5247
5296
  mcpConfigPath,
5248
5297
  "--session",
5249
- this.sessionId,
5250
- ...launch.args
5298
+ this.sessionId
5251
5299
  ];
5252
5300
  const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
5253
5301
  if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
5254
5302
  args.push("--model", launchRuntimeFields.model);
5255
5303
  }
5256
5304
  const spawnEnv = (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
5257
- const spawnTarget = resolveKimiSpawn(args);
5258
- const proc = spawn7(spawnTarget.command, spawnTarget.args, {
5305
+ const launch = resolveKimiSpawn(args);
5306
+ const proc = spawn7(launch.command, launch.args, {
5259
5307
  cwd: ctx.workingDirectory,
5260
5308
  stdio: ["pipe", "pipe", "pipe"],
5261
5309
  env: spawnEnv,
@@ -5263,7 +5311,7 @@ var KimiDriver = class {
5263
5311
  // and has an 8191-character command-line limit. Kimi's official
5264
5312
  // installer/uv entrypoint is an executable, so launch it directly and
5265
5313
  // keep prompts on stdin / files instead of routing through cmd.exe.
5266
- shell: spawnTarget.shell
5314
+ shell: launch.shell
5267
5315
  });
5268
5316
  proc.stdin?.write(JSON.stringify({
5269
5317
  jsonrpc: "2.0",
@@ -5377,9 +5425,14 @@ var KimiDriver = class {
5377
5425
  return detectKimiModels();
5378
5426
  }
5379
5427
  };
5380
- function detectKimiModels(home = os3.homedir(), opts = {}) {
5381
- const raw = readKimiConfigSource(home, opts.env).raw;
5382
- if (raw === null) return null;
5428
+ function detectKimiModels(home = os3.homedir()) {
5429
+ const configPath = path9.join(home, ".kimi", "config.toml");
5430
+ let raw;
5431
+ try {
5432
+ raw = readFileSync3(configPath, "utf8");
5433
+ } catch {
5434
+ return null;
5435
+ }
5383
5436
  const models = [];
5384
5437
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
5385
5438
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -5643,7 +5696,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
5643
5696
  const platform = deps.platform ?? process.platform;
5644
5697
  const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
5645
5698
  const result = spawnSyncFn("opencode", ["models"], {
5646
- env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
5699
+ env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
5647
5700
  encoding: "utf8",
5648
5701
  timeout: 5e3,
5649
5702
  shell: platform === "win32"
@@ -5903,139 +5956,123 @@ var OpenCodeDriver = class {
5903
5956
  };
5904
5957
 
5905
5958
  // src/drivers/pi.ts
5906
- import { spawn as spawn9 } from "child_process";
5907
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
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";
5908
5962
  import path11 from "path";
5909
- import { fileURLToPath } from "url";
5910
- import { getAgentDir, VERSION as PI_SDK_VERSION } from "@earendil-works/pi-coding-agent";
5911
- var CHAT_MCP_TOOL_PREFIX2 = "chat_";
5912
- var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
5913
- var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
5914
- var MIN_SUPPORTED_PI_VERSION = "0.74.0";
5915
- function parseSemver2(version) {
5916
- const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
5917
- if (!match) return null;
5918
- return [Number(match[1]), Number(match[2]), Number(match[3])];
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);
5919
5972
  }
5920
- function isSupportedPiVersion(version) {
5921
- if (!version) return true;
5922
- const actual = parseSemver2(version);
5923
- const minimum = parseSemver2(MIN_SUPPORTED_PI_VERSION);
5924
- if (!actual || !minimum) return true;
5925
- for (let i = 0; i < 3; i += 1) {
5926
- if (actual[i] > minimum[i]) return true;
5927
- if (actual[i] < minimum[i]) return false;
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);
5928
5985
  }
5929
- return true;
5930
- }
5931
- function unsupportedPiVersionMessage(version) {
5932
- if (!version || isSupportedPiVersion(version)) return null;
5933
- 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.`;
5934
- }
5935
- function probePi(version = PI_SDK_VERSION) {
5936
- const unsupportedMessage = unsupportedPiVersionMessage(version);
5937
- if (unsupportedMessage) {
5938
- return {
5939
- available: false,
5940
- version: `${version} (requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION})`
5941
- };
5986
+ if (launchRuntimeFields.reasoningEffort) {
5987
+ args.push("--thinking", launchRuntimeFields.reasoningEffort);
5988
+ }
5989
+ if (sessionId) {
5990
+ args.push("--session-id", sessionId);
5942
5991
  }
5943
- return { available: true, version };
5992
+ return args;
5944
5993
  }
5945
- function resolvePiSdkRunnerPath(moduleUrl = import.meta.url) {
5946
- const moduleDir = path11.dirname(fileURLToPath(moduleUrl));
5947
- const sourceSibling = path11.join(moduleDir, "piSdkRunner.ts");
5948
- if (existsSync8(sourceSibling)) return sourceSibling;
5949
- const bundledEntry = path11.join(moduleDir, "drivers", "piSdkRunner.js");
5950
- if (existsSync8(bundledEntry)) return bundledEntry;
5951
- return path11.join(moduleDir, "piSdkRunner.js");
5994
+ async function buildPiSpawnEnv(ctx) {
5995
+ return (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
5952
5996
  }
5953
- function buildPiSdkNodeArgs(runnerPath = resolvePiSdkRunnerPath()) {
5954
- if (runnerPath.endsWith(".ts")) {
5955
- return [...process.execArgv, runnerPath];
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
+ });
5956
6017
  }
5957
- return [runnerPath];
6018
+ return models.length > 0 ? { models } : null;
5958
6019
  }
5959
- async function buildPiLaunchOptions(ctx, opts = {}) {
5960
- const command = opts.command ?? process.execPath;
5961
- const piDir = path11.join(ctx.workingDirectory, ".slock", "pi");
5962
- const sessionDir = path11.join(piDir, "sessions");
5963
- mkdirSync4(sessionDir, { recursive: true });
5964
- const slock = await prepareCliTransport(ctx, { NO_COLOR: "1" });
5965
- const runnerPath = opts.runnerPath ?? resolvePiSdkRunnerPath();
5966
- const agentDir = opts.agentDir ?? getAgentDir();
5967
- const runnerConfigPath = path11.join(
5968
- piDir,
5969
- `sdk-run-${(ctx.launchId || "launch").replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`
5970
- );
5971
- const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
5972
- const runnerConfig = {
5973
- cwd: ctx.workingDirectory,
5974
- agentDir,
5975
- sessionDir,
5976
- sessionId: ctx.config.sessionId || null,
5977
- standingPrompt: ctx.standingPrompt,
5978
- prompt: turnPrompt,
5979
- model: ctx.config.model && ctx.config.model !== "default" ? ctx.config.model : null
5980
- };
5981
- writeFileSync7(runnerConfigPath, `${JSON.stringify(runnerConfig)}
5982
- `, { encoding: "utf8", mode: 384 });
5983
- const args = [
5984
- ...buildPiSdkNodeArgs(runnerPath),
5985
- "--config",
5986
- runnerConfigPath
5987
- ];
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
+ });
5988
6031
  return {
5989
- command,
5990
- args,
5991
- env: slock.spawnEnv,
5992
- sessionDir,
5993
- agentDir,
5994
- runnerConfigPath,
5995
- sdkVersion: PI_SDK_VERSION
6032
+ status: result.status,
6033
+ stdout: String(result.stdout || ""),
6034
+ error: result.error
5996
6035
  };
5997
6036
  }
5998
- function isSystemFirstMessageTask2(message) {
5999
- return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
6037
+ function humanizePiSegment(value) {
6038
+ return value.split(/[-_/]/).filter(Boolean).map(formatPiLabelToken).join(" ");
6000
6039
  }
6001
- function buildPiSystemPrompt(config) {
6002
- return buildCliTransportSystemPrompt(config, {
6003
- toolPrefix: CHAT_MCP_TOOL_PREFIX2,
6004
- extraCriticalRules: [
6005
- "- 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."
6006
- ],
6007
- postStartupNotes: [
6008
- "**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."
6009
- ],
6010
- includeStdinNotificationSection: false,
6011
- messageNotificationStyle: "poll"
6012
- });
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);
6013
6056
  }
6014
- function contentText(content) {
6015
- if (!content) return "";
6016
- const chunks = [];
6017
- for (const item of content) {
6018
- if (item.type === "text" && typeof item.text === "string") {
6019
- chunks.push(item.text);
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 {
6020
6065
  }
6021
6066
  }
6022
- return chunks.join("");
6023
- }
6024
- function apiKeyErrorMessage(line) {
6025
- const trimmed = line.trim();
6026
- if (!trimmed) return null;
6027
- if (/no api key found/i.test(trimmed)) return trimmed;
6028
- if (/api key.+required/i.test(trimmed)) return trimmed;
6029
- if (/no models available/i.test(trimmed)) return trimmed;
6030
- return null;
6067
+ return "Unknown Pi error";
6031
6068
  }
6032
6069
  var PiDriver = class {
6033
6070
  id = "pi";
6071
+ supportsNativeStandingPrompt = true;
6034
6072
  lifecycle = {
6035
- kind: "per_turn",
6036
- start: "defer_until_concrete_message",
6037
- exit: "terminate_on_turn_end",
6038
- inFlightWake: "coalesce_into_pending"
6073
+ kind: "persistent",
6074
+ stdin: "direct",
6075
+ inFlightWake: "steer"
6039
6076
  };
6040
6077
  communication = {
6041
6078
  chat: "slock_cli",
@@ -6046,65 +6083,42 @@ var PiDriver = class {
6046
6083
  };
6047
6084
  model = {
6048
6085
  detectedModelsVerifiedAs: "launchable",
6049
- toLaunchSpec: async (modelId, ctx) => {
6050
- if (!ctx) return modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] };
6051
- const launchCtx = {
6052
- ...ctx,
6053
- config: {
6054
- ...ctx.config,
6055
- model: modelId
6056
- }
6057
- };
6058
- const launch = await buildPiLaunchOptions(launchCtx);
6059
- return {
6060
- args: launch.args,
6061
- env: launch.env,
6062
- configFiles: [launch.runnerConfigPath],
6063
- params: {
6064
- agentDir: launch.agentDir,
6065
- sessionDir: launch.sessionDir,
6066
- sdkVersion: launch.sdkVersion,
6067
- resources: "extensions/skills/prompt-templates/themes/context-files disabled by Slock policy"
6068
- }
6069
- };
6070
- }
6086
+ toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
6071
6087
  };
6072
- supportsStdinNotification = false;
6073
- mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
6074
- busyDeliveryMode = "none";
6075
- terminateProcessOnTurnEnd = true;
6076
- deferSpawnUntilMessage = true;
6088
+ supportsStdinNotification = true;
6089
+ mcpToolPrefix = "";
6090
+ busyDeliveryMode = "direct";
6077
6091
  usesSlockCliForCommunication = true;
6078
6092
  sessionId = null;
6079
6093
  sessionAnnounced = false;
6080
- apiKeyErrorAnnounced = false;
6081
- turnEnded = false;
6082
- assistantTextByMessageId = /* @__PURE__ */ new Map();
6083
- shouldDeferWakeMessage(message) {
6084
- return isSystemFirstMessageTask2(message);
6085
- }
6094
+ sawTextDelta = false;
6095
+ requestId = 0;
6096
+ process = null;
6086
6097
  probe() {
6087
- return probePi();
6098
+ const command = resolveCommandOnPath("pi");
6099
+ if (!command) return { available: false };
6100
+ return {
6101
+ available: true,
6102
+ version: readCommandVersion(command) ?? void 0
6103
+ };
6088
6104
  }
6089
6105
  async detectModels() {
6090
- return null;
6106
+ return detectPiModels();
6091
6107
  }
6092
6108
  async spawn(ctx) {
6093
- this.sessionId = ctx.config.sessionId || null;
6109
+ this.sessionId = ctx.config.sessionId || randomUUID3();
6094
6110
  this.sessionAnnounced = false;
6095
- this.apiKeyErrorAnnounced = false;
6096
- this.turnEnded = false;
6097
- this.assistantTextByMessageId.clear();
6098
- const unsupportedMessage = unsupportedPiVersionMessage(PI_SDK_VERSION);
6099
- if (unsupportedMessage) throw new Error(unsupportedMessage);
6100
- const launch = await buildPiLaunchOptions(ctx);
6101
- const proc = spawn9(launch.command, launch.args, {
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), {
6102
6115
  cwd: ctx.workingDirectory,
6103
6116
  stdio: ["pipe", "pipe", "pipe"],
6104
- env: launch.env,
6105
- shell: false
6117
+ env: await buildPiSpawnEnv(ctx),
6118
+ shell: process.platform === "win32"
6106
6119
  });
6107
- proc.stdin?.end();
6120
+ this.process = proc;
6121
+ this.sendRpcCommand("prompt", { message: ctx.prompt });
6108
6122
  return { process: proc };
6109
6123
  }
6110
6124
  parseLine(line) {
@@ -6112,84 +6126,103 @@ var PiDriver = class {
6112
6126
  try {
6113
6127
  event = JSON.parse(line);
6114
6128
  } catch {
6115
- if (this.apiKeyErrorAnnounced) return [];
6116
- const message = apiKeyErrorMessage(line);
6117
- if (!message) return [];
6118
- this.apiKeyErrorAnnounced = true;
6119
- this.turnEnded = true;
6120
- return [
6121
- { kind: "error", message },
6122
- { kind: "turn_end", sessionId: this.sessionId || void 0 }
6123
- ];
6129
+ return [];
6124
6130
  }
6125
6131
  const events = [];
6126
6132
  if (event.type === "session" && event.id) {
6127
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;
6128
6139
  }
6129
6140
  if (!this.sessionAnnounced && this.sessionId) {
6130
6141
  events.push({ kind: "session_init", sessionId: this.sessionId });
6131
6142
  this.sessionAnnounced = true;
6132
6143
  }
6133
- switch (event.type) {
6134
- case "agent_start":
6135
- case "turn_start":
6136
- events.push({ kind: "thinking", text: "" });
6137
- break;
6138
- case "message_update":
6139
- case "message_end":
6140
- if (event.message?.role === "assistant") {
6141
- const key = event.message.id || "current";
6142
- const currentText = contentText(event.message.content);
6143
- const previousText = this.assistantTextByMessageId.get(key) ?? "";
6144
- if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
6145
- events.push({ kind: "text", text: currentText.slice(previousText.length) });
6146
- } else if (currentText && currentText !== previousText) {
6147
- events.push({ kind: "text", text: currentText });
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 });
6148
6163
  }
6149
- this.assistantTextByMessageId.set(key, currentText);
6150
- if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
6151
- events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
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 });
6152
6169
  }
6153
- }
6154
- break;
6155
- case "tool_execution_start":
6156
- events.push({
6157
- kind: "tool_call",
6158
- name: event.toolName || "unknown_tool",
6159
- input: event.args
6160
- });
6161
- break;
6162
- case "tool_execution_end":
6163
- events.push({
6164
- kind: "tool_output",
6165
- name: event.toolName || "unknown_tool"
6166
- });
6167
- if (event.isError) {
6168
- events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
6169
- }
6170
- break;
6171
- case "compaction_start":
6172
- events.push({ kind: "compaction_started" });
6173
- break;
6174
- case "compaction_end":
6175
- events.push({ kind: "compaction_finished" });
6176
- if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
6177
- break;
6178
- case "turn_end":
6179
- case "agent_end":
6180
- if (!this.turnEnded) {
6181
- events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
6182
- this.turnEnded = true;
6183
- }
6184
- break;
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) });
6185
6195
  }
6186
6196
  return events;
6187
6197
  }
6188
- encodeStdinMessage(_text, _sessionId, _opts) {
6189
- return null;
6198
+ encodeStdinMessage(text, _sessionId, opts) {
6199
+ return JSON.stringify({
6200
+ id: this.nextRequestId(),
6201
+ type: opts?.mode === "idle" ? "prompt" : "steer",
6202
+ message: text
6203
+ });
6190
6204
  }
6191
6205
  buildSystemPrompt(config, _agentId) {
6192
- return buildPiSystemPrompt(config);
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");
6193
6226
  }
6194
6227
  };
6195
6228
 
@@ -6468,6 +6501,8 @@ function runtimeDisplayName(runtimeId) {
6468
6501
  return "Kimi CLI";
6469
6502
  case "opencode":
6470
6503
  return "OpenCode";
6504
+ case "pi":
6505
+ return "Pi CLI";
6471
6506
  default:
6472
6507
  return runtimeId || "This runtime";
6473
6508
  }
@@ -6753,7 +6788,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
6753
6788
  const dir = path13.join(fallbackDir, ".slock", "runtime-sessions");
6754
6789
  mkdirSync5(dir, { recursive: true });
6755
6790
  const filePath = path13.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
6756
- writeFileSync8(filePath, JSON.stringify({
6791
+ writeFileSync7(filePath, JSON.stringify({
6757
6792
  type: "runtime_session_handoff",
6758
6793
  runtime,
6759
6794
  sessionId,
@@ -6870,7 +6905,7 @@ function dynamicClaimInstruction(driver) {
6870
6905
  }
6871
6906
  function formatIncomingMessage(message, driver) {
6872
6907
  const threadJoinPrefix = message.thread_join_context ? [
6873
- `[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.]`,
6874
6909
  `parent: ${message.thread_join_context.parent_target}`,
6875
6910
  `thread: ${message.thread_join_context.thread_target}`,
6876
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}")`}`,
@@ -7696,7 +7731,7 @@ function getBusyDeliveryNote(driver) {
7696
7731
  if (driver.busyDeliveryMode === "gated") {
7697
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.";
7698
7733
  }
7699
- 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.";
7700
7735
  }
7701
7736
  var NATIVE_STANDING_PROMPT_STARTUP_INPUT = (
7702
7737
  // Claude Code 2.1.114 treats "follow your system prompt" style user turns as
@@ -10419,15 +10454,23 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10419
10454
  }
10420
10455
  }
10421
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
+ };
10422
10465
  const attrs = {
10423
10466
  agentId,
10424
10467
  launchId: ap.launchId || void 0,
10425
10468
  runtime: ap.config.runtime,
10426
10469
  model: ap.config.model,
10427
10470
  telemetry_name: event.name,
10428
- ...event.attrs
10471
+ ...telemetryAttrs
10429
10472
  };
10430
- ap.runtimeTraceSpan?.addEvent(`runtime.telemetry.${event.name}`, event.attrs);
10473
+ ap.runtimeTraceSpan?.addEvent(`runtime.telemetry.${event.name}`, telemetryAttrs);
10431
10474
  this.recordDaemonTrace(`daemon.runtime.telemetry.${event.name}`, attrs);
10432
10475
  }
10433
10476
  sendAgentStatus(agentId, status, launchId) {
@@ -10501,7 +10544,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10501
10544
  }
10502
10545
  const inboxCount = ap.inbox.length;
10503
10546
  if (inboxCount === 0) return false;
10504
- 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.]`;
10505
10548
  logger.info(`[Agent ${agentId}] Sending stdin notification: ${inboxCount} pending inbox message(s)`);
10506
10549
  const encoded = ap.driver.encodeStdinMessage(notification, ap.sessionId, { mode: "busy" });
10507
10550
  if (encoded) {
@@ -10953,8 +10996,8 @@ var ReminderCache = class {
10953
10996
  };
10954
10997
 
10955
10998
  // src/machineLock.ts
10956
- import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
10957
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync9 } 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";
10958
11001
  import os6 from "os";
10959
11002
  import path14 from "path";
10960
11003
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
@@ -11010,7 +11053,7 @@ function acquireDaemonMachineLock(options) {
11010
11053
  const lockId = getDaemonMachineLockId(options.apiKey);
11011
11054
  const machineDir = path14.join(rootDir, lockId);
11012
11055
  const lockDir = path14.join(machineDir, "daemon.lock");
11013
- const token = randomUUID3();
11056
+ const token = randomUUID4();
11014
11057
  mkdirSync6(machineDir, { recursive: true });
11015
11058
  for (let attempt = 0; attempt < 2; attempt += 1) {
11016
11059
  try {
@@ -11024,7 +11067,7 @@ function acquireDaemonMachineLock(options) {
11024
11067
  apiKeyFingerprint: fingerprint.slice(0, 16)
11025
11068
  };
11026
11069
  try {
11027
- writeFileSync9(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
11070
+ writeFileSync8(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
11028
11071
  `, { mode: 384 });
11029
11072
  } catch (err) {
11030
11073
  rmSync3(lockDir, { recursive: true, force: true });
@@ -11037,7 +11080,15 @@ function acquireDaemonMachineLock(options) {
11037
11080
  release: () => {
11038
11081
  const currentOwner = readOwner(lockDir);
11039
11082
  if (currentOwner?.pid === process.pid && currentOwner.token === token) {
11040
- 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
+ }
11041
11092
  }
11042
11093
  }
11043
11094
  };
@@ -11061,7 +11112,7 @@ function acquireDaemonMachineLock(options) {
11061
11112
  }
11062
11113
 
11063
11114
  // src/localTraceSink.ts
11064
- import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync2, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync10 } from "fs";
11115
+ import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync2, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
11065
11116
  import path15 from "path";
11066
11117
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
11067
11118
  var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
@@ -11133,7 +11184,7 @@ var LocalRotatingTraceSink = class {
11133
11184
  this.traceDir,
11134
11185
  `daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
11135
11186
  );
11136
- writeFileSync10(this.currentFile, "", { flag: "a", mode: 384 });
11187
+ writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
11137
11188
  this.currentSize = statSync3(this.currentFile).size;
11138
11189
  this.currentFileOpenedAtMs = nowMs;
11139
11190
  this.pruneOldFiles();
@@ -11232,7 +11283,7 @@ function isDiagnosticErrorAttr(key) {
11232
11283
  }
11233
11284
 
11234
11285
  // src/traceBundleUpload.ts
11235
- import { createHash as createHash6, randomUUID as randomUUID4 } from "crypto";
11286
+ import { createHash as createHash6, randomUUID as randomUUID5 } from "crypto";
11236
11287
  import { gzipSync } from "zlib";
11237
11288
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
11238
11289
  import path16 from "path";
@@ -11503,7 +11554,7 @@ var DaemonTraceBundleUploader = class {
11503
11554
  }
11504
11555
  const gzipped = gzipSync(raw);
11505
11556
  const bundleSha256 = sha256Hex(gzipped);
11506
- const bundleId = randomUUID4();
11557
+ const bundleId = randomUUID5();
11507
11558
  await uploadWithSignedCapability({
11508
11559
  serverUrl: this.options.serverUrl,
11509
11560
  apiKey: this.options.apiKey,
@@ -11582,7 +11633,7 @@ var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
11582
11633
  var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels", "knowledge"];
11583
11634
  var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
11584
11635
  var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
11585
- var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
11636
+ var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
11586
11637
  var RunnerCredentialMintError2 = class extends Error {
11587
11638
  code;
11588
11639
  retryable;
@@ -11618,9 +11669,9 @@ function runnerCredentialErrorDetail2(error) {
11618
11669
  async function waitForRunnerCredentialRetry2() {
11619
11670
  await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
11620
11671
  }
11621
- function parseDaemonCliArgs(args, env = {}) {
11672
+ function parseDaemonCliArgs(args) {
11622
11673
  let serverUrl = "";
11623
- let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
11674
+ let apiKey = "";
11624
11675
  for (let i = 0; i < args.length; i++) {
11625
11676
  if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
11626
11677
  if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
@@ -11637,7 +11688,7 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
11637
11688
  }
11638
11689
  }
11639
11690
  function resolveChatBridgePath(moduleUrl = import.meta.url) {
11640
- const dirname = path17.dirname(fileURLToPath2(moduleUrl));
11691
+ const dirname = path17.dirname(fileURLToPath(moduleUrl));
11641
11692
  const jsPath = path17.resolve(dirname, "chat-bridge.js");
11642
11693
  try {
11643
11694
  accessSync(jsPath);
@@ -11647,7 +11698,7 @@ function resolveChatBridgePath(moduleUrl = import.meta.url) {
11647
11698
  }
11648
11699
  }
11649
11700
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
11650
- const thisDir = path17.dirname(fileURLToPath2(moduleUrl));
11701
+ const thisDir = path17.dirname(fileURLToPath(moduleUrl));
11651
11702
  const bundledDistPath = path17.resolve(thisDir, "cli", "index.js");
11652
11703
  try {
11653
11704
  accessSync(bundledDistPath);
@@ -12371,8 +12422,6 @@ var DaemonCore = class {
12371
12422
  };
12372
12423
 
12373
12424
  export {
12374
- DAEMON_API_KEY_ENV,
12375
- scrubDaemonAuthEnv,
12376
12425
  resolveWorkspaceDirectoryPath,
12377
12426
  scanWorkspaceDirectories,
12378
12427
  deleteWorkspaceDirectory,