@slock-ai/daemon 0.55.3 → 0.55.5

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
@@ -1285,28 +1303,29 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
1285
1303
  5. **\`slock channel join\`** \u2014 Join a visible public channel. This only affects your own agent membership.
1286
1304
  6. **\`slock channel leave\`** \u2014 Leave a regular channel you have joined. This only affects your own agent membership.
1287
1305
  7. **\`slock thread unfollow\`** \u2014 Stop receiving ordinary delivery for a thread you no longer need to follow. This only affects your own agent attention state.
1288
- 8. **\`slock message read\`** \u2014 Read past messages from a channel, DM, or thread. Supports \`before\` / \`after\` pagination and \`around\` for centered context.
1306
+ 8. **\`slock message read\`** \u2014 Read past messages from a channel, DM, or thread. Supports \`before\` / \`after\` anchors and \`around\` for centered context.
1289
1307
  9. **\`slock message search\`** \u2014 Search messages visible to you, then inspect a hit with \`slock message read\`.
1290
- 10. **\`slock message react\`** \u2014 Add or remove your reaction on a message. Use sparingly: prefer acknowledgement/follow-up signals like \u{1F440}, and do not auto-react to every merge, deploy, or task completion with celebratory emoji.
1291
- 11. **\`slock task list\`** \u2014 View a channel's task board.
1292
- 12. **\`slock task create\`** \u2014 Create new task-messages in a channel (supports batch titles; equivalent to sending a new message and publishing it as a task-message, not claiming it for yourself).
1293
- 13. **\`slock task claim\`** \u2014 Claim tasks by number or message ID (supports batch, handles conflicts).
1294
- 14. **\`slock task unclaim\`** \u2014 Release your claim on a task.
1295
- 15. **\`slock task update\`** \u2014 Change a task's status (e.g. to in_review or done).
1296
- 16. **\`slock attachment upload\`** \u2014 Upload a file to attach to a message. Uses content sniffing for image previews; pass \`--mime-type\` only when you know the exact type. Returns an attachment ID to pass to \`slock message send\`.
1297
- 17. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
1298
- 18. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
1299
- 19. **\`slock profile update\`** \u2014 Update your own profile. Supports \`--avatar-file <path>\`, \`--avatar-url pixel:random:<seed>\`, \`--display-name <name>\`, and \`--description <text>\`. Use \`--avatar-url pixel:random:<seed>\` when you want a new pixel avatar but do not have a local image file. Values must be non-empty. Provide at least one flag per call; multiple flags can be combined.
1300
- 20. **\`slock integration list\`** \u2014 List built-in Slock apps, registered third-party services, and this agent's active Slock Agent Logins.
1301
- 21. **\`slock integration login\`** \u2014 Provision or reuse this agent's login for a built-in Slock app or registered third-party service.
1302
- 22. **\`slock integration env\`** \u2014 Print per-agent local CLI environment for a manifest-backed service that requires isolated HOME/XDG state.
1303
- 23. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
1304
- 24. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
1305
- 25. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
1306
- 26. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
1307
- 27. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
1308
- 28. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
1309
- 29. **\`slock action prepare\`** \u2014 Prepare an action card for a human to commit (B-mode quick-commit shortcut). Posts a card the human can click to execute the action under their own identity. Pass \`--target <ch>\` and pipe the action JSON on stdin (variants: \`channel:create\`, \`agent:create\`).
1308
+ 10. **\`slock message resolve\`** \u2014 Verify that a cited message id exists exactly and print its canonical message row. Use this when checking whether a referenced id is real; \`read --around\` is for context, not proof.
1309
+ 11. **\`slock message react\`** \u2014 Add or remove your reaction on a message. Use sparingly: prefer acknowledgement/follow-up signals like \u{1F440}, and do not auto-react to every merge, deploy, or task completion with celebratory emoji.
1310
+ 12. **\`slock task list\`** \u2014 View a channel's task board.
1311
+ 13. **\`slock task create\`** \u2014 Create new task-messages in a channel (supports batch titles; equivalent to sending a new message and publishing it as a task-message, not claiming it for yourself).
1312
+ 14. **\`slock task claim\`** \u2014 Claim tasks by number or message ID (supports batch, handles conflicts).
1313
+ 15. **\`slock task unclaim\`** \u2014 Release your claim on a task.
1314
+ 16. **\`slock task update\`** \u2014 Change a task's status (e.g. to in_review or done).
1315
+ 17. **\`slock attachment upload\`** \u2014 Upload a file to attach to a message. Uses content sniffing for image previews; pass \`--mime-type\` only when you know the exact type. Returns an attachment ID to pass to \`slock message send\`.
1316
+ 18. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
1317
+ 19. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
1318
+ 20. **\`slock profile update\`** \u2014 Update your own profile. Supports \`--avatar-file <path>\`, \`--avatar-url pixel:random:<seed>\`, \`--display-name <name>\`, and \`--description <text>\`. Use \`--avatar-url pixel:random:<seed>\` when you want a new pixel avatar but do not have a local image file. Values must be non-empty. Provide at least one flag per call; multiple flags can be combined.
1319
+ 21. **\`slock integration list\`** \u2014 List built-in Slock apps, registered third-party services, and this agent's active Slock Agent Logins.
1320
+ 22. **\`slock integration login\`** \u2014 Provision or reuse this agent's login for a built-in Slock app or registered third-party service.
1321
+ 23. **\`slock integration env\`** \u2014 Print per-agent local CLI environment for a manifest-backed service that requires isolated HOME/XDG state.
1322
+ 24. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
1323
+ 25. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
1324
+ 26. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
1325
+ 27. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
1326
+ 28. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
1327
+ 29. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
1328
+ 30. **\`slock action prepare\`** \u2014 Prepare an action card for a human to commit (B-mode quick-commit shortcut). Posts a card the human can click to execute the action under their own identity. Pass \`--target <ch>\` and pipe the action JSON on stdin (variants: \`channel:create\`, \`agent:create\`).
1310
1329
 
1311
1330
  The CLI prints human-readable canonical text on success (matching the format you see in received messages and history). On failure it prints JSON to stderr:
1312
1331
  - failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
@@ -1374,19 +1393,19 @@ If Slock says a message was not sent and was saved as a draft, choose one path:
1374
1393
 
1375
1394
  Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
1376
1395
 
1377
- - **Thread targets** have a colon and short ID suffix: \`#general:a1b2c3d4\` (thread in #general) or \`dm:@richard:x9y8z7a0\` (thread in a DM).
1396
+ - **Thread targets** have a colon and short ID suffix: \`#general:00000000\` (thread in #general) or \`dm:@richard:11111111\` (thread in a DM).
1378
1397
  - 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.
1398
+ - **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
1399
  - 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.
1400
+ - You can read thread history: \`slock message read --channel "#general:00000000"\`
1401
+ - 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
1402
  - Threads cannot be nested \u2014 you cannot start a thread inside a thread.` : `### Threads
1384
1403
 
1385
1404
  Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
1386
1405
 
1387
- - **Thread targets** have a colon and short ID suffix: \`#general:a1b2c3d4\` (thread in #general) or \`dm:@richard:x9y8z7a0\` (thread in a DM).
1406
+ - **Thread targets** have a colon and short ID suffix: \`#general:00000000\` (thread in #general) or \`dm:@richard:11111111\` (thread in a DM).
1388
1407
  - 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.
1408
+ - **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
1409
  - When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
1391
1410
  - You can read thread history via ${readCmd} with the same thread target.
1392
1411
  - Threads cannot be nested \u2014 you cannot start a thread inside a thread.`;
@@ -1419,7 +1438,9 @@ If a built-in Slock app or registered third-party service requires login, use Sl
1419
1438
 
1420
1439
  \`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
1421
1440
 
1422
- To jump directly to a specific hit with nearby context, use \`slock message read --channel "..." --around "messageId"\` or \`slock message read --channel "..." --around 12345\`.` : `### Reading history
1441
+ To jump directly to a specific hit with nearby context, use \`slock message read --channel "..." --around "messageId"\` or \`slock message read --channel "..." --around 12345\`.
1442
+
1443
+ When verifying a cited message id, use \`slock message resolve <id>\`. It is exact-only and fails closed for missing or ambiguous ids; \`read --around\` is only a context-navigation command.` : `### Reading history
1423
1444
 
1424
1445
  Use ${readCmd} with the \`channel\` parameter set to \`"#channel-name"\`, \`"dm:@peer-name"\`, or a thread target like \`"#channel:shortid"\`.
1425
1446
 
@@ -1552,13 +1573,18 @@ ${opts.postStartupNotes.join("\n")}`;
1552
1573
  Messages you receive have a single RFC 5424-style structured data header followed by the sender and content:
1553
1574
 
1554
1575
  \`\`\`
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
1576
+ [target=#general msg=00000000 time=2026-03-15T01:00:00 type=human] @richard: hello everyone
1577
+ [target=#general msg=11111111 time=2026-03-15T01:00:01 type=agent] @Alice: hi there
1578
+ [target=dm:@richard msg=22222222 time=2026-03-15T01:00:02 type=human] @richard: hey, can you help?
1579
+ [target=#general:00000000 msg=33333333 time=2026-03-15T01:00:03 type=human] @richard: thread reply
1580
+ [target=dm:@richard:22222222 msg=44444444 time=2026-03-15T01:00:04 type=human] @richard: DM thread reply
1560
1581
  \`\`\`
1561
1582
 
1583
+ Prompt examples use obvious placeholder IDs such as \`00000000\`, \`11111111\`,
1584
+ and \`22222222\`. They show the shape of a real message ID but are not actual
1585
+ messages. Do not cite them as evidence; use only IDs from messages you actually
1586
+ received or read.
1587
+
1562
1588
  Header fields:
1563
1589
  - \`target=\` \u2014 where the message came from. Reuse as the \`target\` parameter when replying.
1564
1590
  - \`msg=\` \u2014 message short ID (first 8 chars of UUID). Use as thread suffix to start/reply in a thread.
@@ -1630,9 +1656,11 @@ Slock auto-renders these inline tokens as interactive links whenever they appear
1630
1656
 
1631
1657
  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
1658
 
1659
+ 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.
1660
+
1633
1661
  ### Formatting \u2014 URLs in non-English text
1634
1662
 
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.
1663
+ 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
1664
 
1637
1665
  - **Wrong**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1Ahttp://localhost:3000\uFF0C\u8BF7\u67E5\u770B\` (the \`\uFF0C\` gets swallowed into the link)
1638
1666
  - **Correct**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1A<http://localhost:3000>\uFF0C\u8BF7\u67E5\u770B\`
@@ -1712,12 +1740,12 @@ How to handle these:
1712
1740
  - Call ${checkCmd} at the next safe breakpoint to materialize the pending messages before taking side-effect actions that depend on current context.
1713
1741
  - If the new message is higher priority, pivot after reading it. If not, continue your current work.`;
1714
1742
  } 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.]\``;
1743
+ 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
1744
  prompt += `
1717
1745
 
1718
1746
  ## Message Notifications
1719
1747
 
1720
- While you are busy (executing tools, thinking, etc.), new messages may arrive. When this happens, you will receive a system notification like:
1748
+ 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
1749
 
1722
1750
  ${notifyExample}
1723
1751
 
@@ -2324,6 +2352,7 @@ function routeFamilyForPath(pathname) {
2324
2352
  if (pathname === "/internal/agent-api/tasks/update-status") return "tasks/update";
2325
2353
  if (pathname === "/internal/agent-api/tasks" || pathname.startsWith("/internal/agent-api/tasks/")) return "tasks";
2326
2354
  if (pathname.startsWith("/internal/agent-api/attachments/")) return "agent-api/attachments";
2355
+ if (/^\/internal\/agent-api\/messages\/[^/]+\/resolve$/.test(pathname)) return "agent-api/messages/resolve";
2327
2356
  if (/^\/internal\/agent-api\/messages\/[^/]+\/reactions$/.test(pathname)) return "agent-api/messages/reactions";
2328
2357
  if (pathname === "/internal/agent-api/server") return "server";
2329
2358
  if (pathname.startsWith("/internal/agent-api/history")) return "agent-api/events";
@@ -2744,7 +2773,7 @@ function shouldBufferJsonResponse(upstream, pathname, registration) {
2744
2773
  if (!registration.inboxCoordinator) return false;
2745
2774
  const contentType = upstream.headers.get("content-type") ?? "";
2746
2775
  if (!contentType.includes("application/json")) return false;
2747
- return pathname === "/internal/agent-api/send" || pathname === "/internal/agent-api/events" || pathname === "/internal/agent-api/history";
2776
+ return pathname === "/internal/agent-api/send" || pathname === "/internal/agent-api/events" || pathname === "/internal/agent-api/history" || /^\/internal\/agent-api\/messages\/[^/]+\/resolve$/.test(pathname);
2748
2777
  }
2749
2778
  function consumeVisibleResponse(registration, targetUrl, sendTarget, responseText) {
2750
2779
  const coordinator = registration.inboxCoordinator;
@@ -3081,6 +3110,125 @@ function collectResultErrorDetail(message, fallback) {
3081
3110
  function isProviderApiFailureText(value, hasToolUse) {
3082
3111
  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
3112
  }
3113
+ function finiteNumber(value) {
3114
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3115
+ }
3116
+ function finiteString(value) {
3117
+ return typeof value === "string" && value.length > 0 ? value : void 0;
3118
+ }
3119
+ function withDefined(attrs) {
3120
+ return Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== void 0));
3121
+ }
3122
+ function collectNumericFields(value, fields) {
3123
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
3124
+ const attrs = {};
3125
+ for (const [sourceKey, attrKey] of Object.entries(fields)) {
3126
+ const numberValue = finiteNumber(value[sourceKey]);
3127
+ if (numberValue !== void 0) attrs[attrKey] = numberValue;
3128
+ }
3129
+ return attrs;
3130
+ }
3131
+ function collectModelUsageAttrs(value) {
3132
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
3133
+ const sanitized = {};
3134
+ const aggregate = {};
3135
+ const knownNumericFields = [
3136
+ "inputTokens",
3137
+ "outputTokens",
3138
+ "cacheReadInputTokens",
3139
+ "cacheCreationInputTokens",
3140
+ "webSearchRequests",
3141
+ "costUSD",
3142
+ "contextWindow",
3143
+ "maxOutputTokens"
3144
+ ];
3145
+ for (const [modelName, rawUsage] of Object.entries(value)) {
3146
+ if (!rawUsage || typeof rawUsage !== "object" || Array.isArray(rawUsage)) continue;
3147
+ const modelUsage = {};
3148
+ for (const field of knownNumericFields) {
3149
+ const numberValue = finiteNumber(rawUsage[field]);
3150
+ if (numberValue === void 0) continue;
3151
+ modelUsage[field] = numberValue;
3152
+ const aggregateKey = field === "costUSD" ? "costUsd" : field;
3153
+ if (field === "contextWindow" || field === "maxOutputTokens") {
3154
+ aggregate[aggregateKey] = Math.max(aggregate[aggregateKey] ?? 0, numberValue);
3155
+ } else {
3156
+ aggregate[aggregateKey] = (aggregate[aggregateKey] ?? 0) + numberValue;
3157
+ }
3158
+ }
3159
+ if (Object.keys(modelUsage).length > 0) sanitized[modelName] = modelUsage;
3160
+ }
3161
+ const modelNames = Object.keys(sanitized).sort();
3162
+ if (modelNames.length === 0) return {};
3163
+ const orderedSanitized = Object.fromEntries(modelNames.map((modelName) => [modelName, sanitized[modelName]]));
3164
+ return withDefined({
3165
+ modelUsageModelCount: modelNames.length,
3166
+ modelUsageModels: modelNames.join(","),
3167
+ modelUsageJson: JSON.stringify(orderedSanitized),
3168
+ modelUsageInputTokens: aggregate.inputTokens,
3169
+ modelUsageOutputTokens: aggregate.outputTokens,
3170
+ modelUsageCachedInputTokens: aggregate.cacheReadInputTokens,
3171
+ modelUsageCacheCreationInputTokens: aggregate.cacheCreationInputTokens,
3172
+ modelUsageWebSearchRequests: aggregate.webSearchRequests,
3173
+ modelUsageCostUsd: aggregate.costUsd,
3174
+ modelUsageMaxContextWindow: aggregate.contextWindow,
3175
+ modelUsageMaxOutputTokens: aggregate.maxOutputTokens
3176
+ });
3177
+ }
3178
+ function parseClaudeResultUsageTelemetry(event) {
3179
+ const usage = event.usage && typeof event.usage === "object" ? event.usage : void 0;
3180
+ const totalCostUsd = finiteNumber(event.total_cost_usd);
3181
+ const modelUsageAttrs = collectModelUsageAttrs(event.modelUsage);
3182
+ const hasTelemetrySource = usage !== void 0 || totalCostUsd !== void 0 || Object.keys(modelUsageAttrs).length > 0;
3183
+ if (!hasTelemetrySource) return null;
3184
+ const inputTokens = finiteNumber(usage?.input_tokens);
3185
+ const outputTokens = finiteNumber(usage?.output_tokens);
3186
+ const cachedInputTokens = finiteNumber(usage?.cache_read_input_tokens);
3187
+ const cacheCreationInputTokens = finiteNumber(usage?.cache_creation_input_tokens);
3188
+ const attrs = {
3189
+ ...withDefined({
3190
+ totalCostUsd,
3191
+ durationMs: finiteNumber(event.duration_ms),
3192
+ durationApiMs: finiteNumber(event.duration_api_ms),
3193
+ numTurns: finiteNumber(event.num_turns),
3194
+ resultSubtype: finiteString(event.subtype),
3195
+ stopReason: finiteString(event.stop_reason),
3196
+ resultIsError: typeof event.is_error === "boolean" ? event.is_error : void 0,
3197
+ fastModeState: finiteString(event.fast_mode_state),
3198
+ permissionDenialsCount: Array.isArray(event.permission_denials) ? event.permission_denials.length : void 0,
3199
+ serviceTier: finiteString(usage?.service_tier),
3200
+ inferenceGeo: finiteString(usage?.inference_geo),
3201
+ usageSpeed: finiteString(usage?.speed),
3202
+ usageIterationsCount: Array.isArray(usage?.iterations) ? usage.iterations.length : void 0
3203
+ }),
3204
+ ...collectNumericFields(usage?.server_tool_use, {
3205
+ web_search_requests: "serverToolUseWebSearchRequests",
3206
+ web_fetch_requests: "serverToolUseWebFetchRequests"
3207
+ }),
3208
+ ...collectNumericFields(usage?.cache_creation, {
3209
+ ephemeral_1h_input_tokens: "cacheCreationEphemeral1hInputTokens",
3210
+ ephemeral_5m_input_tokens: "cacheCreationEphemeral5mInputTokens"
3211
+ }),
3212
+ ...modelUsageAttrs
3213
+ };
3214
+ if (inputTokens !== void 0) attrs.inputTokens = inputTokens;
3215
+ if (outputTokens !== void 0) attrs.outputTokens = outputTokens;
3216
+ if (cachedInputTokens !== void 0) attrs.cachedInputTokens = cachedInputTokens;
3217
+ if (cacheCreationInputTokens !== void 0) attrs.cacheCreationInputTokens = cacheCreationInputTokens;
3218
+ const tokenComponents = [inputTokens, outputTokens, cachedInputTokens, cacheCreationInputTokens];
3219
+ const totalTokens = tokenComponents.reduce((sum, value) => sum + (value ?? 0), 0);
3220
+ if (tokenComponents.some((value) => value !== void 0)) attrs.totalTokens = totalTokens;
3221
+ if (Object.keys(attrs).length === 0) return null;
3222
+ return {
3223
+ kind: "telemetry",
3224
+ name: "token_usage",
3225
+ source: "claude_result_usage",
3226
+ usageKind: "per_turn",
3227
+ ...typeof event.session_id === "string" && event.session_id ? { sessionId: event.session_id } : {},
3228
+ ...typeof event.uuid === "string" && event.uuid ? { runtimeResultId: event.uuid } : {},
3229
+ attrs
3230
+ };
3231
+ }
3084
3232
  var ClaudeEventNormalizer = class {
3085
3233
  normalizeLine(line) {
3086
3234
  let event;
@@ -3155,6 +3303,7 @@ var ClaudeEventNormalizer = class {
3155
3303
  case "result": {
3156
3304
  const subtype = typeof event.subtype === "string" ? event.subtype : "success";
3157
3305
  const stopReason = typeof event.stop_reason === "string" ? event.stop_reason : null;
3306
+ const usageTelemetry = parseClaudeResultUsageTelemetry(event);
3158
3307
  switch (subtype) {
3159
3308
  case "success":
3160
3309
  if (event.is_error && stopReason !== "max_tokens") {
@@ -3176,6 +3325,7 @@ var ClaudeEventNormalizer = class {
3176
3325
  pushResultError(event, "Structured output retries exceeded");
3177
3326
  break;
3178
3327
  }
3328
+ if (usageTelemetry) events.push(usageTelemetry);
3179
3329
  events.push({ kind: "turn_end", sessionId: event.session_id });
3180
3330
  break;
3181
3331
  }
@@ -3441,7 +3591,7 @@ function buildClaudeArgs(config, opts) {
3441
3591
  args.push("--effort", launchRuntimeFields.reasoningEffort);
3442
3592
  }
3443
3593
  if (launchRuntimeFields.mode.kind === "fast") {
3444
- args.push("--bare");
3594
+ args.push("--settings", JSON.stringify({ fastMode: true }));
3445
3595
  }
3446
3596
  if (config.sessionId) {
3447
3597
  args.push("--resume", config.sessionId);
@@ -3599,53 +3749,64 @@ var RuntimeTurnState = class {
3599
3749
  };
3600
3750
 
3601
3751
  // src/drivers/codexTelemetrySidecar.ts
3602
- function finiteNumber(value) {
3752
+ function finiteNumber2(value) {
3603
3753
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3604
3754
  }
3605
- function finiteString(value) {
3755
+ function finiteString2(value) {
3606
3756
  return typeof value === "string" && value.length > 0 ? value : void 0;
3607
3757
  }
3608
3758
  function ratio(numerator, denominator) {
3609
3759
  if (numerator === void 0 || denominator === void 0 || denominator <= 0) return void 0;
3610
3760
  return Number((numerator / denominator).toFixed(6));
3611
3761
  }
3612
- function withDefined(attrs) {
3762
+ function withDefined2(attrs) {
3613
3763
  return Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== void 0));
3614
3764
  }
3615
3765
  function parseTokenUsageTelemetry(message) {
3616
3766
  const usage = message.params?.tokenUsage;
3617
3767
  const total = usage?.total;
3618
3768
  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({
3769
+ const inputTokens = finiteNumber2(total.inputTokens);
3770
+ const cachedInputTokens = finiteNumber2(total.cachedInputTokens);
3771
+ const totalTokens = finiteNumber2(total.totalTokens);
3772
+ const modelContextWindow = finiteNumber2(usage.modelContextWindow);
3773
+ const attrs = withDefined2({
3624
3774
  totalTokens,
3625
3775
  inputTokens,
3626
3776
  cachedInputTokens,
3627
- outputTokens: finiteNumber(total.outputTokens),
3628
- reasoningOutputTokens: finiteNumber(total.reasoningOutputTokens),
3777
+ outputTokens: finiteNumber2(total.outputTokens),
3778
+ reasoningOutputTokens: finiteNumber2(total.reasoningOutputTokens),
3629
3779
  modelContextWindow,
3630
3780
  cachedInputRatio: ratio(cachedInputTokens, inputTokens),
3631
3781
  contextUtilization: ratio(totalTokens, modelContextWindow)
3632
3782
  });
3633
3783
  if (Object.keys(attrs).length === 0) return null;
3634
- return { kind: "telemetry", name: "token_usage", attrs };
3784
+ return {
3785
+ kind: "telemetry",
3786
+ name: "token_usage",
3787
+ source: "codex_thread_token_usage_updated",
3788
+ usageKind: "cumulative_session",
3789
+ attrs
3790
+ };
3635
3791
  }
3636
3792
  function parseRateLimitTelemetry(message) {
3637
3793
  const rateLimits = message.params?.rateLimits;
3638
3794
  const primary = rateLimits?.primary;
3639
3795
  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)
3796
+ const attrs = withDefined2({
3797
+ limitId: finiteString2(rateLimits.limitId),
3798
+ planType: finiteString2(rateLimits.planType),
3799
+ usedPercent: finiteNumber2(primary.usedPercent),
3800
+ windowDurationMins: finiteNumber2(primary.windowDurationMins),
3801
+ resetsAt: finiteNumber2(primary.resetsAt)
3646
3802
  });
3647
3803
  if (Object.keys(attrs).length === 0) return null;
3648
- return { kind: "telemetry", name: "rate_limits", attrs };
3804
+ return {
3805
+ kind: "telemetry",
3806
+ name: "rate_limits",
3807
+ source: "codex_account_rate_limits_updated",
3808
+ attrs
3809
+ };
3649
3810
  }
3650
3811
  function parseCodexTelemetryEvent(message) {
3651
3812
  switch (message.method) {
@@ -3752,7 +3913,11 @@ var CodexEventNormalizer = class {
3752
3913
  }
3753
3914
  const telemetry = parseCodexTelemetryEvent(message);
3754
3915
  if (telemetry) {
3755
- events.push(telemetry);
3916
+ events.push({
3917
+ ...telemetry,
3918
+ ...this.currentThreadId ? { sessionId: this.currentThreadId } : {},
3919
+ ...this.turnState.activeTurnId ? { turnId: this.turnState.activeTurnId } : {}
3920
+ });
3756
3921
  return { events };
3757
3922
  }
3758
3923
  const rawProgress = rawResponseItemProgressEvent(message);
@@ -5794,6 +5959,277 @@ var OpenCodeDriver = class {
5794
5959
  }
5795
5960
  };
5796
5961
 
5962
+ // src/drivers/pi.ts
5963
+ import { randomUUID as randomUUID3 } from "crypto";
5964
+ import { spawn as spawn9, spawnSync as spawnSync3 } from "child_process";
5965
+ import { mkdirSync as mkdirSync4 } from "fs";
5966
+ import path11 from "path";
5967
+ var PI_SESSION_DIR = ".pi-sessions";
5968
+ var PI_PROVIDER_LABELS = {
5969
+ deepseek: "DeepSeek",
5970
+ google: "Google",
5971
+ openai: "OpenAI",
5972
+ openrouter: "OpenRouter"
5973
+ };
5974
+ function buildPiSessionDir(workingDirectory) {
5975
+ return path11.join(workingDirectory, PI_SESSION_DIR);
5976
+ }
5977
+ function buildPiRpcArgs(ctx, sessionId) {
5978
+ const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
5979
+ const args = [
5980
+ "--mode",
5981
+ "rpc",
5982
+ "--session-dir",
5983
+ buildPiSessionDir(ctx.workingDirectory),
5984
+ "--system-prompt",
5985
+ ctx.standingPrompt
5986
+ ];
5987
+ if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
5988
+ args.push("--model", launchRuntimeFields.model);
5989
+ }
5990
+ if (launchRuntimeFields.reasoningEffort) {
5991
+ args.push("--thinking", launchRuntimeFields.reasoningEffort);
5992
+ }
5993
+ if (sessionId) {
5994
+ args.push("--session-id", sessionId);
5995
+ }
5996
+ return args;
5997
+ }
5998
+ async function buildPiSpawnEnv(ctx) {
5999
+ return (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
6000
+ }
6001
+ function parsePiModelsOutput(output) {
6002
+ const stripAnsi = (value) => value.replace(/\u001b\[[0-9;]*m/g, "");
6003
+ const models = [];
6004
+ const seen = /* @__PURE__ */ new Set();
6005
+ for (const rawLine of stripAnsi(output).split(/\r?\n/)) {
6006
+ const line = rawLine.trim();
6007
+ if (!line || /^provider\s+model\s+/i.test(line) || /^[-\s]+$/.test(line)) continue;
6008
+ const columns = line.split(/\s+/);
6009
+ const provider = columns[0];
6010
+ const model = columns[1];
6011
+ if (!provider || !model || provider.startsWith("-") || model.startsWith("-")) continue;
6012
+ if (/^(yes|no)$/i.test(model)) continue;
6013
+ const id = `${provider}/${model}`;
6014
+ if (seen.has(id)) continue;
6015
+ seen.add(id);
6016
+ models.push({
6017
+ id,
6018
+ label: `${humanizePiSegment(model)} \xB7 ${PI_PROVIDER_LABELS[provider] || humanizePiSegment(provider)}`,
6019
+ verified: "launchable"
6020
+ });
6021
+ }
6022
+ return models.length > 0 ? { models } : null;
6023
+ }
6024
+ function detectPiModels(runCommand = runPiModelsCommand) {
6025
+ const result = runCommand();
6026
+ if (result.error || result.status !== 0) return null;
6027
+ return parsePiModelsOutput(result.stdout);
6028
+ }
6029
+ function runPiModelsCommand() {
6030
+ const result = spawnSync3("pi", ["--list-models"], {
6031
+ env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
6032
+ encoding: "utf8",
6033
+ timeout: 5e3
6034
+ });
6035
+ return {
6036
+ status: result.status,
6037
+ stdout: String(result.stdout || ""),
6038
+ error: result.error
6039
+ };
6040
+ }
6041
+ function humanizePiSegment(value) {
6042
+ return value.split(/[-_/]/).filter(Boolean).map(formatPiLabelToken).join(" ");
6043
+ }
6044
+ function formatPiLabelToken(token) {
6045
+ const normalized = token.toLowerCase();
6046
+ const specialCases = {
6047
+ ai: "AI",
6048
+ api: "API",
6049
+ deepseek: "DeepSeek",
6050
+ flash: "Flash",
6051
+ gpt: "GPT",
6052
+ pro: "Pro",
6053
+ v4: "V4"
6054
+ };
6055
+ if (specialCases[normalized]) return specialCases[normalized];
6056
+ if (/^v\d+(\.\d+)?$/.test(normalized)) return normalized.toUpperCase();
6057
+ if (/^\d+[bk]$/i.test(token)) return token.toUpperCase();
6058
+ if (/^\d/.test(token)) return token;
6059
+ return normalized.charAt(0).toUpperCase() + normalized.slice(1);
6060
+ }
6061
+ function piErrorMessage(error) {
6062
+ if (typeof error === "string" && error.trim()) return error.trim();
6063
+ if (error && typeof error === "object") {
6064
+ const record = error;
6065
+ if (typeof record.message === "string" && record.message.trim()) return record.message.trim();
6066
+ try {
6067
+ return JSON.stringify(error);
6068
+ } catch {
6069
+ }
6070
+ }
6071
+ return "Unknown Pi error";
6072
+ }
6073
+ var PiDriver = class {
6074
+ id = "pi";
6075
+ supportsNativeStandingPrompt = true;
6076
+ lifecycle = {
6077
+ kind: "persistent",
6078
+ stdin: "direct",
6079
+ inFlightWake: "steer"
6080
+ };
6081
+ communication = {
6082
+ chat: "slock_cli",
6083
+ runtimeControl: "none"
6084
+ };
6085
+ session = {
6086
+ recovery: "resume_or_fresh"
6087
+ };
6088
+ model = {
6089
+ detectedModelsVerifiedAs: "launchable",
6090
+ toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
6091
+ };
6092
+ supportsStdinNotification = true;
6093
+ mcpToolPrefix = "";
6094
+ busyDeliveryMode = "direct";
6095
+ usesSlockCliForCommunication = true;
6096
+ sessionId = null;
6097
+ sessionAnnounced = false;
6098
+ sawTextDelta = false;
6099
+ requestId = 0;
6100
+ process = null;
6101
+ probe() {
6102
+ const command = resolveCommandOnPath("pi");
6103
+ if (!command) return { available: false };
6104
+ return {
6105
+ available: true,
6106
+ version: readCommandVersion(command) ?? void 0
6107
+ };
6108
+ }
6109
+ async detectModels() {
6110
+ return detectPiModels();
6111
+ }
6112
+ async spawn(ctx) {
6113
+ this.sessionId = ctx.config.sessionId || randomUUID3();
6114
+ this.sessionAnnounced = false;
6115
+ this.sawTextDelta = false;
6116
+ this.requestId = 0;
6117
+ mkdirSync4(buildPiSessionDir(ctx.workingDirectory), { recursive: true });
6118
+ const proc = spawn9(resolveCommandOnPath("pi") ?? "pi", buildPiRpcArgs(ctx, this.sessionId), {
6119
+ cwd: ctx.workingDirectory,
6120
+ stdio: ["pipe", "pipe", "pipe"],
6121
+ env: await buildPiSpawnEnv(ctx),
6122
+ shell: process.platform === "win32"
6123
+ });
6124
+ this.process = proc;
6125
+ this.sendRpcCommand("prompt", { message: ctx.prompt });
6126
+ return { process: proc };
6127
+ }
6128
+ parseLine(line) {
6129
+ let event;
6130
+ try {
6131
+ event = JSON.parse(line);
6132
+ } catch {
6133
+ return [];
6134
+ }
6135
+ const events = [];
6136
+ if (event.type === "session" && event.id) {
6137
+ this.sessionId = event.id;
6138
+ if (!this.sessionAnnounced) {
6139
+ this.sessionAnnounced = true;
6140
+ events.push({ kind: "session_init", sessionId: event.id });
6141
+ }
6142
+ return events;
6143
+ }
6144
+ if (!this.sessionAnnounced && this.sessionId) {
6145
+ events.push({ kind: "session_init", sessionId: this.sessionId });
6146
+ this.sessionAnnounced = true;
6147
+ }
6148
+ if (event.type === "response") {
6149
+ if (event.data?.sessionId && event.data.sessionId !== this.sessionId) {
6150
+ this.sessionId = event.data.sessionId;
6151
+ }
6152
+ if (event.success === false) {
6153
+ events.push({ kind: "error", message: piErrorMessage(event.error) });
6154
+ }
6155
+ return events;
6156
+ }
6157
+ if (event.type === "message_start" && event.message?.role === "assistant") {
6158
+ this.sawTextDelta = false;
6159
+ return events;
6160
+ }
6161
+ const assistantEvent = event.assistantMessageEvent;
6162
+ if (event.type === "message_update" && assistantEvent) {
6163
+ switch (assistantEvent.type) {
6164
+ case "thinking_delta":
6165
+ if (typeof assistantEvent.delta === "string" && assistantEvent.delta.length > 0) {
6166
+ events.push({ kind: "thinking", text: assistantEvent.delta });
6167
+ }
6168
+ break;
6169
+ case "text_delta":
6170
+ if (typeof assistantEvent.delta === "string" && assistantEvent.delta.length > 0) {
6171
+ this.sawTextDelta = true;
6172
+ events.push({ kind: "text", text: assistantEvent.delta });
6173
+ }
6174
+ break;
6175
+ case "thinking_start":
6176
+ case "text_start":
6177
+ break;
6178
+ case "tool_use":
6179
+ case "tool_call":
6180
+ case "tool_start":
6181
+ events.push({
6182
+ kind: "tool_call",
6183
+ name: assistantEvent.name || assistantEvent.toolName || "unknown_tool",
6184
+ input: assistantEvent.input ?? assistantEvent.parameters ?? {}
6185
+ });
6186
+ break;
6187
+ case "text_end":
6188
+ if (!this.sawTextDelta && typeof assistantEvent.content === "string" && assistantEvent.content.length > 0) {
6189
+ events.push({ kind: "text", text: assistantEvent.content });
6190
+ }
6191
+ break;
6192
+ }
6193
+ return events;
6194
+ }
6195
+ if (event.type === "agent_end") {
6196
+ events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
6197
+ } else if (event.type === "error") {
6198
+ events.push({ kind: "error", message: piErrorMessage(event.error ?? event.message) });
6199
+ }
6200
+ return events;
6201
+ }
6202
+ encodeStdinMessage(text, _sessionId, opts) {
6203
+ return JSON.stringify({
6204
+ id: this.nextRequestId(),
6205
+ type: opts?.mode === "idle" ? "prompt" : "steer",
6206
+ message: text
6207
+ });
6208
+ }
6209
+ buildSystemPrompt(config, _agentId) {
6210
+ return buildCliTransportSystemPrompt(config, {
6211
+ toolPrefix: "",
6212
+ extraCriticalRules: [],
6213
+ postStartupNotes: [
6214
+ "**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."
6215
+ ],
6216
+ includeStdinNotificationSection: true,
6217
+ messageNotificationStyle: "direct"
6218
+ });
6219
+ }
6220
+ nextRequestId() {
6221
+ this.requestId += 1;
6222
+ return String(this.requestId);
6223
+ }
6224
+ sendRpcCommand(type, params) {
6225
+ this.process?.stdin?.write(JSON.stringify({
6226
+ id: this.nextRequestId(),
6227
+ type,
6228
+ ...params
6229
+ }) + "\n");
6230
+ }
6231
+ };
6232
+
5797
6233
  // src/drivers/index.ts
5798
6234
  var driverFactories = {
5799
6235
  claude: () => new ClaudeDriver(),
@@ -5803,7 +6239,8 @@ var driverFactories = {
5803
6239
  cursor: () => new CursorDriver(),
5804
6240
  gemini: () => new GeminiDriver(),
5805
6241
  kimi: () => new KimiDriver(),
5806
- opencode: () => new OpenCodeDriver()
6242
+ opencode: () => new OpenCodeDriver(),
6243
+ pi: () => new PiDriver()
5807
6244
  };
5808
6245
  function getDriver(runtimeId) {
5809
6246
  const createDriver = driverFactories[runtimeId];
@@ -5816,7 +6253,7 @@ function getDriver(runtimeId) {
5816
6253
 
5817
6254
  // src/workspaces.ts
5818
6255
  import { readdir, rm, stat } from "fs/promises";
5819
- import path11 from "path";
6256
+ import path12 from "path";
5820
6257
  function isValidWorkspaceDirectoryName(directoryName) {
5821
6258
  return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
5822
6259
  }
@@ -5824,7 +6261,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
5824
6261
  if (!isValidWorkspaceDirectoryName(directoryName)) {
5825
6262
  return null;
5826
6263
  }
5827
- return path11.join(dataDir, directoryName);
6264
+ return path12.join(dataDir, directoryName);
5828
6265
  }
5829
6266
  function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
5830
6267
  return {
@@ -5873,7 +6310,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
5873
6310
  return summary;
5874
6311
  }
5875
6312
  const childSummaries = await Promise.all(
5876
- entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
6313
+ entries.map((entry) => summarizeWorkspaceEntry(path12.join(dirPath, entry.name), entry))
5877
6314
  );
5878
6315
  for (const childSummary of childSummaries) {
5879
6316
  summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
@@ -5892,7 +6329,7 @@ async function scanWorkspaceDirectories(dataDir) {
5892
6329
  if (!entry.isDirectory()) {
5893
6330
  return null;
5894
6331
  }
5895
- const dirPath = path11.join(dataDir, entry.name);
6332
+ const dirPath = path12.join(dataDir, entry.name);
5896
6333
  try {
5897
6334
  const summary = await summarizeWorkspaceDirectory(dirPath);
5898
6335
  return {
@@ -6068,6 +6505,8 @@ function runtimeDisplayName(runtimeId) {
6068
6505
  return "Kimi CLI";
6069
6506
  case "opencode":
6070
6507
  return "OpenCode";
6508
+ case "pi":
6509
+ return "Pi CLI";
6071
6510
  default:
6072
6511
  return runtimeId || "This runtime";
6073
6512
  }
@@ -6332,12 +6771,12 @@ function findSessionJsonl(root, predicate) {
6332
6771
  for (const entry of entries) {
6333
6772
  if (++visited > maxEntries) return null;
6334
6773
  if (!entry.isFile() || !predicate(entry.name)) continue;
6335
- return path12.join(dir, entry.name);
6774
+ return path13.join(dir, entry.name);
6336
6775
  }
6337
6776
  for (const entry of entries) {
6338
6777
  if (++visited > maxEntries) return null;
6339
6778
  if (!entry.isDirectory()) continue;
6340
- const found = visit(path12.join(dir, entry.name), depth - 1);
6779
+ const found = visit(path13.join(dir, entry.name), depth - 1);
6341
6780
  if (found) return found;
6342
6781
  }
6343
6782
  return null;
@@ -6350,9 +6789,9 @@ function safeSessionFilename(value) {
6350
6789
  }
6351
6790
  function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
6352
6791
  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`);
6792
+ const dir = path13.join(fallbackDir, ".slock", "runtime-sessions");
6793
+ mkdirSync5(dir, { recursive: true });
6794
+ const filePath = path13.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
6356
6795
  writeFileSync7(filePath, JSON.stringify({
6357
6796
  type: "runtime_session_handoff",
6358
6797
  runtime,
@@ -6372,7 +6811,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
6372
6811
  }
6373
6812
  }
6374
6813
  function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), fallbackDir) {
6375
- const directPath = path12.isAbsolute(sessionId) ? sessionId : null;
6814
+ const directPath = path13.isAbsolute(sessionId) ? sessionId : null;
6376
6815
  if (directPath) {
6377
6816
  try {
6378
6817
  if (statSync(directPath).isFile()) {
@@ -6381,7 +6820,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), f
6381
6820
  } catch {
6382
6821
  }
6383
6822
  }
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;
6823
+ 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
6824
  if (!resolvedPath && fallbackDir) {
6386
6825
  const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
6387
6826
  if (fallback) return fallback;
@@ -6470,7 +6909,7 @@ function dynamicClaimInstruction(driver) {
6470
6909
  }
6471
6910
  function formatIncomingMessage(message, driver) {
6472
6911
  const threadJoinPrefix = message.thread_join_context ? [
6473
- `[System: You were added to a new thread via @mention. Read this context before replying.]`,
6912
+ `[Slock thread context: you were added to a new thread via @mention.]`,
6474
6913
  `parent: ${message.thread_join_context.parent_target}`,
6475
6914
  `thread: ${message.thread_join_context.thread_target}`,
6476
6915
  `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 +7735,7 @@ function getBusyDeliveryNote(driver) {
7296
7735
  if (driver.busyDeliveryMode === "gated") {
7297
7736
  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
7737
  }
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.";
7738
+ 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
7739
  }
7301
7740
  var NATIVE_STANDING_PROMPT_STARTUP_INPUT = (
7302
7741
  // Claude Code 2.1.114 treats "follow your system prompt" style user turns as
@@ -7805,7 +8244,7 @@ var AgentProcessManager = class _AgentProcessManager {
7805
8244
  );
7806
8245
  wakeMessage = void 0;
7807
8246
  }
7808
- const agentDataDir = path12.join(this.dataDir, agentId);
8247
+ const agentDataDir = path13.join(this.dataDir, agentId);
7809
8248
  await mkdir(agentDataDir, { recursive: true });
7810
8249
  let runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
7811
8250
  const legacyRuntimeProfileControl = runtimeConfig.runtimeProfileControl?.kind === "migration" ? runtimeConfig.runtimeProfileControl : null;
@@ -7819,23 +8258,23 @@ var AgentProcessManager = class _AgentProcessManager {
7819
8258
  );
7820
8259
  runtimeConfig = { ...runtimeConfig, runtimeProfileControl: null };
7821
8260
  }
7822
- const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
8261
+ const memoryMdPath = path13.join(agentDataDir, "MEMORY.md");
7823
8262
  try {
7824
8263
  await access(memoryMdPath);
7825
8264
  } catch {
7826
8265
  const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
7827
8266
  await writeFile(memoryMdPath, initialMemoryMd);
7828
8267
  }
7829
- const notesDir = path12.join(agentDataDir, "notes");
8268
+ const notesDir = path13.join(agentDataDir, "notes");
7830
8269
  await mkdir(notesDir, { recursive: true });
7831
8270
  if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
7832
8271
  const seedFiles = buildOnboardingSeedFiles();
7833
8272
  for (const { relativePath, content } of seedFiles) {
7834
- const fullPath = path12.join(agentDataDir, relativePath);
8273
+ const fullPath = path13.join(agentDataDir, relativePath);
7835
8274
  try {
7836
8275
  await access(fullPath);
7837
8276
  } catch {
7838
- await mkdir(path12.dirname(fullPath), { recursive: true });
8277
+ await mkdir(path13.dirname(fullPath), { recursive: true });
7839
8278
  await writeFile(fullPath, content);
7840
8279
  }
7841
8280
  }
@@ -8707,7 +9146,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8707
9146
  return true;
8708
9147
  }
8709
9148
  async resetWorkspace(agentId) {
8710
- const agentDataDir = path12.join(this.dataDir, agentId);
9149
+ const agentDataDir = path13.join(this.dataDir, agentId);
8711
9150
  try {
8712
9151
  await rm2(agentDataDir, { recursive: true, force: true });
8713
9152
  logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
@@ -8768,7 +9207,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8768
9207
  return result;
8769
9208
  }
8770
9209
  buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
8771
- const workspacePath = path12.join(this.dataDir, agentId);
9210
+ const workspacePath = path13.join(this.dataDir, agentId);
8772
9211
  return {
8773
9212
  agentId,
8774
9213
  launchId,
@@ -9025,7 +9464,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9025
9464
  }
9026
9465
  // Workspace file browsing
9027
9466
  async getFileTree(agentId, dirPath) {
9028
- const agentDir = path12.join(this.dataDir, agentId);
9467
+ const agentDir = path13.join(this.dataDir, agentId);
9029
9468
  try {
9030
9469
  await stat2(agentDir);
9031
9470
  } catch {
@@ -9033,8 +9472,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9033
9472
  }
9034
9473
  let targetDir = agentDir;
9035
9474
  if (dirPath) {
9036
- const resolved = path12.resolve(agentDir, dirPath);
9037
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
9475
+ const resolved = path13.resolve(agentDir, dirPath);
9476
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
9038
9477
  return [];
9039
9478
  }
9040
9479
  targetDir = resolved;
@@ -9042,14 +9481,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9042
9481
  return this.listDirectoryChildren(targetDir, agentDir);
9043
9482
  }
9044
9483
  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) {
9484
+ const agentDir = path13.join(this.dataDir, agentId);
9485
+ const resolved = path13.resolve(agentDir, filePath);
9486
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
9048
9487
  throw new Error("Access denied");
9049
9488
  }
9050
9489
  const info = await stat2(resolved);
9051
9490
  if (info.isDirectory()) throw new Error("Cannot read a directory");
9052
- const ext = path12.extname(resolved).toLowerCase();
9491
+ const ext = path13.extname(resolved).toLowerCase();
9053
9492
  if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
9054
9493
  if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
9055
9494
  const content = await readFile(resolved, "utf-8");
@@ -9084,13 +9523,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9084
9523
  const agent = this.agents.get(agentId);
9085
9524
  const runtime = runtimeHint || agent?.config.runtime || "claude";
9086
9525
  const home = os5.homedir();
9087
- const workspaceDir = path12.join(this.dataDir, agentId);
9526
+ const workspaceDir = path13.join(this.dataDir, agentId);
9088
9527
  const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
9089
9528
  const globalResults = await Promise.all(
9090
- paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
9529
+ paths.global.map((p) => this.scanSkillsDir(path13.join(home, p)))
9091
9530
  );
9092
9531
  const workspaceResults = await Promise.all(
9093
- paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
9532
+ paths.workspace.map((p) => this.scanSkillsDir(path13.join(workspaceDir, p)))
9094
9533
  );
9095
9534
  const dedup = (skills) => {
9096
9535
  const seen = /* @__PURE__ */ new Set();
@@ -9119,7 +9558,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9119
9558
  const skills = [];
9120
9559
  for (const entry of entries) {
9121
9560
  if (entry.isDirectory() || entry.isSymbolicLink()) {
9122
- const skillMd = path12.join(dir, entry.name, "SKILL.md");
9561
+ const skillMd = path13.join(dir, entry.name, "SKILL.md");
9123
9562
  try {
9124
9563
  const content = await readFile(skillMd, "utf-8");
9125
9564
  const skill = this.parseSkillMd(entry.name, content);
@@ -9130,7 +9569,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
9130
9569
  } else if (entry.name.endsWith(".md")) {
9131
9570
  const cmdName = entry.name.replace(/\.md$/, "");
9132
9571
  try {
9133
- const content = await readFile(path12.join(dir, entry.name), "utf-8");
9572
+ const content = await readFile(path13.join(dir, entry.name), "utf-8");
9134
9573
  const skill = this.parseSkillMd(cmdName, content);
9135
9574
  skill.sourcePath = dir;
9136
9575
  skills.push(skill);
@@ -10019,15 +10458,23 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10019
10458
  }
10020
10459
  }
10021
10460
  recordRuntimeTelemetry(agentId, ap, event) {
10461
+ const telemetryAttrs = {
10462
+ ...event.attrs,
10463
+ ...event.source ? { source: event.source } : {},
10464
+ ...event.usageKind ? { usageKind: event.usageKind } : {},
10465
+ ...event.sessionId ? { sessionId: event.sessionId } : {},
10466
+ ...event.turnId ? { turnId: event.turnId } : {},
10467
+ ...event.runtimeResultId ? { runtimeResultId: event.runtimeResultId } : {}
10468
+ };
10022
10469
  const attrs = {
10023
10470
  agentId,
10024
10471
  launchId: ap.launchId || void 0,
10025
10472
  runtime: ap.config.runtime,
10026
10473
  model: ap.config.model,
10027
10474
  telemetry_name: event.name,
10028
- ...event.attrs
10475
+ ...telemetryAttrs
10029
10476
  };
10030
- ap.runtimeTraceSpan?.addEvent(`runtime.telemetry.${event.name}`, event.attrs);
10477
+ ap.runtimeTraceSpan?.addEvent(`runtime.telemetry.${event.name}`, telemetryAttrs);
10031
10478
  this.recordDaemonTrace(`daemon.runtime.telemetry.${event.name}`, attrs);
10032
10479
  }
10033
10480
  sendAgentStatus(agentId, status, launchId) {
@@ -10101,7 +10548,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
10101
10548
  }
10102
10549
  const inboxCount = ap.inbox.length;
10103
10550
  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.]`;
10551
+ 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
10552
  logger.info(`[Agent ${agentId}] Sending stdin notification: ${inboxCount} pending inbox message(s)`);
10106
10553
  const encoded = ap.driver.encodeStdinMessage(notification, ap.sessionId, { mode: "busy" });
10107
10554
  if (encoded) {
@@ -10248,8 +10695,8 @@ ${RESPONSE_TARGET_HINT}`);
10248
10695
  const nodes = [];
10249
10696
  for (const entry of entries) {
10250
10697
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
10251
- const fullPath = path12.join(dir, entry.name);
10252
- const relativePath = path12.relative(rootDir, fullPath);
10698
+ const fullPath = path13.join(dir, entry.name);
10699
+ const relativePath = path13.relative(rootDir, fullPath);
10253
10700
  let info;
10254
10701
  try {
10255
10702
  info = await stat2(fullPath);
@@ -10553,10 +11000,10 @@ var ReminderCache = class {
10553
11000
  };
10554
11001
 
10555
11002
  // 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";
11003
+ import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
11004
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
10558
11005
  import os6 from "os";
10559
- import path13 from "path";
11006
+ import path14 from "path";
10560
11007
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
10561
11008
  var DaemonMachineLockConflictError = class extends Error {
10562
11009
  code = "DAEMON_MACHINE_LOCK_HELD";
@@ -10578,7 +11025,7 @@ function resolveDefaultMachineStateRoot() {
10578
11025
  return resolveSlockHomePath("machines");
10579
11026
  }
10580
11027
  function ownerPath(lockDir) {
10581
- return path13.join(lockDir, "owner.json");
11028
+ return path14.join(lockDir, "owner.json");
10582
11029
  }
10583
11030
  function readOwner(lockDir) {
10584
11031
  try {
@@ -10608,13 +11055,13 @@ function acquireDaemonMachineLock(options) {
10608
11055
  const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
10609
11056
  const fingerprint = apiKeyFingerprint(options.apiKey);
10610
11057
  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 });
11058
+ const machineDir = path14.join(rootDir, lockId);
11059
+ const lockDir = path14.join(machineDir, "daemon.lock");
11060
+ const token = randomUUID4();
11061
+ mkdirSync6(machineDir, { recursive: true });
10615
11062
  for (let attempt = 0; attempt < 2; attempt += 1) {
10616
11063
  try {
10617
- mkdirSync5(lockDir);
11064
+ mkdirSync6(lockDir);
10618
11065
  const owner = {
10619
11066
  pid: process.pid,
10620
11067
  token,
@@ -10637,7 +11084,15 @@ function acquireDaemonMachineLock(options) {
10637
11084
  release: () => {
10638
11085
  const currentOwner = readOwner(lockDir);
10639
11086
  if (currentOwner?.pid === process.pid && currentOwner.token === token) {
10640
- rmSync3(lockDir, { recursive: true, force: true });
11087
+ const released = { ...currentOwner, pid: 0 };
11088
+ try {
11089
+ writeFileSync8(ownerPath(lockDir), `${JSON.stringify(released, null, 2)}
11090
+ `, {
11091
+ mode: 384
11092
+ });
11093
+ } catch {
11094
+ rmSync3(lockDir, { recursive: true, force: true });
11095
+ }
10641
11096
  }
10642
11097
  }
10643
11098
  };
@@ -10661,8 +11116,8 @@ function acquireDaemonMachineLock(options) {
10661
11116
  }
10662
11117
 
10663
11118
  // 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";
11119
+ import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync2, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
11120
+ import path15 from "path";
10666
11121
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
10667
11122
  var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
10668
11123
  var DEFAULT_MAX_FILES = 8;
@@ -10699,7 +11154,7 @@ var LocalRotatingTraceSink = class {
10699
11154
  currentSize = 0;
10700
11155
  sequence = 0;
10701
11156
  constructor(options) {
10702
- this.traceDir = path14.join(options.machineDir, "traces");
11157
+ this.traceDir = path15.join(options.machineDir, "traces");
10703
11158
  this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
10704
11159
  const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
10705
11160
  const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
@@ -10725,11 +11180,11 @@ var LocalRotatingTraceSink = class {
10725
11180
  return this.currentFile;
10726
11181
  }
10727
11182
  ensureFile(nextBytes) {
10728
- mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
11183
+ mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
10729
11184
  const nowMs = this.nowMsProvider();
10730
11185
  const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
10731
11186
  if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
10732
- this.currentFile = path14.join(
11187
+ this.currentFile = path15.join(
10733
11188
  this.traceDir,
10734
11189
  `daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
10735
11190
  );
@@ -10744,7 +11199,7 @@ var LocalRotatingTraceSink = class {
10744
11199
  const excess = files.length - this.maxFiles;
10745
11200
  if (excess <= 0) return;
10746
11201
  for (const file of files.slice(0, excess)) {
10747
- rmSync4(path14.join(this.traceDir, file), { force: true });
11202
+ rmSync4(path15.join(this.traceDir, file), { force: true });
10748
11203
  }
10749
11204
  }
10750
11205
  };
@@ -10832,14 +11287,14 @@ function isDiagnosticErrorAttr(key) {
10832
11287
  }
10833
11288
 
10834
11289
  // src/traceBundleUpload.ts
10835
- import { createHash as createHash6, randomUUID as randomUUID4 } from "crypto";
11290
+ import { createHash as createHash6, randomUUID as randomUUID5 } from "crypto";
10836
11291
  import { gzipSync } from "zlib";
10837
11292
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
10838
- import path15 from "path";
11293
+ import path16 from "path";
10839
11294
 
10840
11295
  // src/directUploadCapability.ts
10841
- function joinUrl(base, path17) {
10842
- return `${base.replace(/\/+$/, "")}${path17}`;
11296
+ function joinUrl(base, path18) {
11297
+ return `${base.replace(/\/+$/, "")}${path18}`;
10843
11298
  }
10844
11299
  function jsonHeaders(apiKey) {
10845
11300
  return {
@@ -11058,7 +11513,7 @@ var DaemonTraceBundleUploader = class {
11058
11513
  }, nextMs);
11059
11514
  }
11060
11515
  async findUploadCandidates() {
11061
- const traceDir = path15.join(this.options.machineDir, "traces");
11516
+ const traceDir = path16.join(this.options.machineDir, "traces");
11062
11517
  let names;
11063
11518
  try {
11064
11519
  names = await readdir3(traceDir);
@@ -11070,8 +11525,8 @@ var DaemonTraceBundleUploader = class {
11070
11525
  const currentFile = this.options.currentFileProvider?.();
11071
11526
  const candidates = [];
11072
11527
  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;
11528
+ const file = path16.join(traceDir, name);
11529
+ if (currentFile && path16.resolve(file) === path16.resolve(currentFile)) continue;
11075
11530
  if (await this.isUploaded(file)) continue;
11076
11531
  try {
11077
11532
  const info = await stat3(file);
@@ -11103,7 +11558,7 @@ var DaemonTraceBundleUploader = class {
11103
11558
  }
11104
11559
  const gzipped = gzipSync(raw);
11105
11560
  const bundleSha256 = sha256Hex(gzipped);
11106
- const bundleId = randomUUID4();
11561
+ const bundleId = randomUUID5();
11107
11562
  await uploadWithSignedCapability({
11108
11563
  serverUrl: this.options.serverUrl,
11109
11564
  apiKey: this.options.apiKey,
@@ -11145,8 +11600,8 @@ var DaemonTraceBundleUploader = class {
11145
11600
  }
11146
11601
  }
11147
11602
  uploadStatePath(file) {
11148
- const stateDir = path15.join(this.options.machineDir, "trace-uploads");
11149
- return path15.join(stateDir, `${path15.basename(file)}.uploaded.json`);
11603
+ const stateDir = path16.join(this.options.machineDir, "trace-uploads");
11604
+ return path16.join(stateDir, `${path16.basename(file)}.uploaded.json`);
11150
11605
  }
11151
11606
  async isUploaded(file) {
11152
11607
  try {
@@ -11158,9 +11613,9 @@ var DaemonTraceBundleUploader = class {
11158
11613
  }
11159
11614
  async markUploaded(file, metadata) {
11160
11615
  const stateFile = this.uploadStatePath(file);
11161
- await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
11616
+ await mkdir2(path16.dirname(stateFile), { recursive: true, mode: 448 });
11162
11617
  await writeFile2(stateFile, `${JSON.stringify({
11163
- file: path15.basename(file),
11618
+ file: path16.basename(file),
11164
11619
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
11165
11620
  ...metadata
11166
11621
  }, null, 2)}
@@ -11237,23 +11692,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
11237
11692
  }
11238
11693
  }
11239
11694
  function resolveChatBridgePath(moduleUrl = import.meta.url) {
11240
- const dirname = path16.dirname(fileURLToPath(moduleUrl));
11241
- const jsPath = path16.resolve(dirname, "chat-bridge.js");
11695
+ const dirname = path17.dirname(fileURLToPath(moduleUrl));
11696
+ const jsPath = path17.resolve(dirname, "chat-bridge.js");
11242
11697
  try {
11243
11698
  accessSync(jsPath);
11244
11699
  return jsPath;
11245
11700
  } catch {
11246
- return path16.resolve(dirname, "chat-bridge.ts");
11701
+ return path17.resolve(dirname, "chat-bridge.ts");
11247
11702
  }
11248
11703
  }
11249
11704
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
11250
- const thisDir = path16.dirname(fileURLToPath(moduleUrl));
11251
- const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
11705
+ const thisDir = path17.dirname(fileURLToPath(moduleUrl));
11706
+ const bundledDistPath = path17.resolve(thisDir, "cli", "index.js");
11252
11707
  try {
11253
11708
  accessSync(bundledDistPath);
11254
11709
  return bundledDistPath;
11255
11710
  } catch {
11256
- const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
11711
+ const workspaceDistPath = path17.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
11257
11712
  accessSync(workspaceDistPath);
11258
11713
  return workspaceDistPath;
11259
11714
  }
@@ -11383,6 +11838,11 @@ function summarizeIncomingMessage(msg) {
11383
11838
  var DaemonCore = class {
11384
11839
  options;
11385
11840
  daemonVersion;
11841
+ // When this runner is launched by a managed Computer service, the
11842
+ // service exports SLOCK_COMPUTER_VERSION (the `@slock-ai/computer`
11843
+ // bundle version). Reported in `ready` so the server can surface the
11844
+ // Computer's own version (distinct from the underlying daemonVersion).
11845
+ computerVersion;
11386
11846
  chatBridgePath;
11387
11847
  slockCliPath;
11388
11848
  slockHome;
@@ -11398,6 +11858,7 @@ var DaemonCore = class {
11398
11858
  constructor(options) {
11399
11859
  this.options = options;
11400
11860
  this.daemonVersion = options.daemonVersion ?? readDaemonVersion();
11861
+ this.computerVersion = (process.env.SLOCK_COMPUTER_VERSION || "").trim() || null;
11401
11862
  this.chatBridgePath = options.chatBridgePath ?? resolveChatBridgePath();
11402
11863
  this.slockCliPath = options.slockCliPath ?? resolveSlockCliPath();
11403
11864
  this.slockHome = resolveSlockHome();
@@ -11432,7 +11893,7 @@ var DaemonCore = class {
11432
11893
  }
11433
11894
  resolveMachineStateRoot() {
11434
11895
  if (this.options.machineStateDir) return this.options.machineStateDir;
11435
- if (this.options.dataDir) return path16.join(path16.dirname(this.options.dataDir), "machines");
11896
+ if (this.options.dataDir) return path17.join(path17.dirname(this.options.dataDir), "machines");
11436
11897
  return resolveDefaultMachineStateRoot();
11437
11898
  }
11438
11899
  shouldEnableLocalTrace() {
@@ -11458,7 +11919,7 @@ var DaemonCore = class {
11458
11919
  sink: this.localTraceSink
11459
11920
  });
11460
11921
  this.agentManager.setTracer(this.tracer);
11461
- this.agentManager.setCliTransportTraceDir(path16.join(machineDir, "traces"));
11922
+ this.agentManager.setCliTransportTraceDir(path17.join(machineDir, "traces"));
11462
11923
  }
11463
11924
  installTraceBundleUploader(machineDir) {
11464
11925
  if (!this.shouldEnableLocalTrace()) return;
@@ -11857,6 +12318,26 @@ var DaemonCore = class {
11857
12318
  case "ping":
11858
12319
  this.connection.send({ type: "pong" });
11859
12320
  break;
12321
+ case "computer:restart":
12322
+ case "computer:upgrade": {
12323
+ const action = msg.type === "computer:restart" ? "restart" : "upgrade";
12324
+ this.recordDaemonTrace("daemon.computer_control.received", {
12325
+ action,
12326
+ handled: Boolean(this.options.onComputerControl)
12327
+ });
12328
+ if (this.options.onComputerControl) {
12329
+ try {
12330
+ this.options.onComputerControl(action);
12331
+ } catch (err) {
12332
+ logger.error(
12333
+ `[Daemon] computer:${action} control handler failed: ${err instanceof Error ? err.message : String(err)}`
12334
+ );
12335
+ }
12336
+ } else {
12337
+ logger.info(`[Daemon] Ignoring computer:${action} \u2014 not launched by a Computer service.`);
12338
+ }
12339
+ break;
12340
+ }
11860
12341
  }
11861
12342
  }
11862
12343
  onReminderFire(job) {
@@ -11883,7 +12364,8 @@ var DaemonCore = class {
11883
12364
  runningAgents: runningAgentIds,
11884
12365
  hostname: this.options.hostname ?? os7.hostname(),
11885
12366
  os: this.options.osDescription ?? `${os7.platform()} ${os7.arch()}`,
11886
- daemonVersion: this.daemonVersion
12367
+ daemonVersion: this.daemonVersion,
12368
+ ...this.computerVersion ? { computerVersion: this.computerVersion } : {}
11887
12369
  });
11888
12370
  this.recordDaemonTrace("daemon.ready.sent", {
11889
12371
  runtimes_count: runtimes.length,