@slock-ai/daemon 0.52.1 → 0.52.2-play.20260522165849

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.
@@ -7,11 +7,11 @@ import {
7
7
  } from "./chunk-KNMCE6WB.js";
8
8
 
9
9
  // src/core.ts
10
- import path16 from "path";
10
+ import path17 from "path";
11
11
  import os8 from "os";
12
- import { createRequire } from "module";
12
+ import { createRequire as createRequire2 } from "module";
13
13
  import { accessSync } from "fs";
14
- import { fileURLToPath } from "url";
14
+ import { fileURLToPath as fileURLToPath2 } from "url";
15
15
 
16
16
  // ../shared/src/tracing/index.ts
17
17
  var DEFAULT_TRACE_FLAGS = "00";
@@ -723,6 +723,7 @@ var SERVER_CAPABILITY_MATRIX = {
723
723
  var RUNTIMES = [
724
724
  { id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
725
725
  { id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
726
+ { id: "pi", displayName: "Pi", binary: "pi", supported: true },
726
727
  { id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true },
727
728
  { id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
728
729
  { id: "cursor", displayName: "Cursor CLI", binary: "cursor-agent", supported: true },
@@ -764,20 +765,21 @@ var DISPLAY_PLAN_CONFIG = {
764
765
  };
765
766
 
766
767
  // src/agentProcessManager.ts
767
- import { mkdirSync as mkdirSync4, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync7 } from "fs";
768
+ import { mkdirSync as mkdirSync5, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
768
769
  import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
769
770
  import { createHash as createHash2 } from "crypto";
770
- import path12 from "path";
771
+ import path13 from "path";
771
772
  import os6 from "os";
772
773
 
773
774
  // src/drivers/claude.ts
774
775
  import { spawn } from "child_process";
775
- import { existsSync as existsSync3, readdirSync, readFileSync, statSync, writeFileSync as writeFileSync2 } from "fs";
776
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
776
777
  import os2 from "os";
777
778
  import path4 from "path";
778
779
 
779
780
  // src/drivers/cliTransport.ts
780
- import { mkdirSync, rmSync, writeFileSync } from "fs";
781
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
782
+ import { createRequire } from "module";
781
783
  import path2 from "path";
782
784
 
783
785
  // src/drivers/systemPrompt.ts
@@ -823,6 +825,7 @@ function buildPrompt(config, variant, opts) {
823
825
  "- Always communicate through `slock` CLI commands. This is your only output channel.",
824
826
  ...opts.extraCriticalRules,
825
827
  "- Use only the provided `slock` CLI commands for messaging.",
828
+ "- Do not combine multiple `slock` CLI commands in one shell command. Run one `slock` command per tool call, read its output, then decide the next command.",
826
829
  "- Always claim a task via `slock task claim` before starting work on it. If the claim fails, move on to a different task."
827
830
  ] : [
828
831
  `- Always communicate through ${sendCmd}. This is your only output channel.`,
@@ -871,13 +874,15 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
871
874
  17. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
872
875
  18. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
873
876
  19. **\`slock profile update\`** \u2014 Update your own profile. Supports \`--avatar-file <path>\`, \`--avatar-url pixel:random:<seed>\`, \`--display-name <name>\`, and \`--description <text>\`. Use \`--avatar-url pixel:random:<seed>\` when you want a new pixel avatar but do not have a local image file. Values must be non-empty. Provide at least one flag per call; multiple flags can be combined.
874
- 20. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
875
- 21. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
876
- 22. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
877
- 23. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
878
- 24. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
879
- 25. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
880
- 26. **\`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\`).
877
+ 20. **\`slock integration list\`** \u2014 List registered third-party services and this agent's active Slock Agent Logins.
878
+ 21. **\`slock integration login\`** \u2014 Provision or reuse this agent's login for a registered third-party service.
879
+ 22. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
880
+ 23. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
881
+ 24. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
882
+ 25. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
883
+ 26. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
884
+ 27. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
885
+ 28. **\`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\`).
881
886
 
882
887
  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:
883
888
  - failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
@@ -912,20 +917,23 @@ Use reminders for follow-up that depends on future state you cannot resolve now,
912
917
  When a reminder already exists, prefer \`slock reminder snooze\` to push it later, \`slock reminder update\` to change its meaning or schedule, and \`slock reminder cancel\` only when it is truly no longer needed.
913
918
  Use ${scheduleReminderCmd} rather than runtime-native wake or cron tools such as ScheduleWakeup or CronCreate for user-visible reminders, so reminders stay author-owned, persistent, observable, snoozable, updatable, and cancelable in Slock.
914
919
  Create agent reminders only after resolving the anchor message from the current conversation and passing its msgId explicitly; if no anchor can be resolved, consider posting a status update in the relevant thread so the intent is visible, then revisit when context is available.`;
920
+ const messageHeredocDelimiter = "SLOCKMSG";
915
921
  const sendingMessagesSection = isCli ? `### Sending messages
916
922
 
917
- - **Reply to a channel**: \`slock message send --target "#channel-name" <<'EOF'\` followed by the message body and \`EOF\`
918
- - **Reply to a DM**: \`slock message send --target dm:@peer-name <<'EOF'\` followed by the message body and \`EOF\`
919
- - **Reply in a thread**: \`slock message send --target "#channel:shortid" <<'EOF'\` followed by the message body and \`EOF\`
920
- - **Start a NEW DM**: \`slock message send --target dm:@person-name <<'EOF'\` followed by the message body and \`EOF\`
923
+ - **Reply to a channel**: \`slock message send --target "#channel-name" <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
924
+ - **Reply to a DM**: \`slock message send --target dm:@peer-name <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
925
+ - **Reply in a thread**: \`slock message send --target "#channel:shortid" <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
926
+ - **Start a NEW DM**: \`slock message send --target dm:@person-name <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
921
927
 
922
928
  Message content is always read from stdin. Use a heredoc so quotes, backticks, code blocks, and newlines are not interpreted by the shell:
923
929
  \`\`\`bash
924
- slock message send --target "#channel-name" <<'EOF'
930
+ slock message send --target "#channel-name" <<'${messageHeredocDelimiter}'
925
931
  Long message with "quotes", $vars, \`backticks\`, and code blocks.
926
- EOF
932
+ ${messageHeredocDelimiter}
927
933
  \`\`\`
928
934
 
935
+ Use a delimiter that is unlikely to appear in the message body; the examples use \`${messageHeredocDelimiter}\` instead of \`EOF\` so shell snippets and recovery drafts are less likely to leak delimiter text into sent messages.
936
+
929
937
  If Slock says a message was not sent and was saved as a draft, choose one path:
930
938
  - To update the draft, use a normal \`slock message send --target <target>\` with the revised content.
931
939
  - To send the current draft unchanged, use \`slock message send --send-draft --target <target>\` with no stdin. Do not use \`--send-draft\` when changing content.
@@ -944,7 +952,7 @@ Threads are sub-conversations attached to a specific message. They let you discu
944
952
 
945
953
  - **Thread targets** have a colon and short ID suffix: \`#general:a1b2c3d4\` (thread in #general) or \`dm:@richard:x9y8z7a0\` (thread in a DM).
946
954
  - 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.
947
- - **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" <<'EOF'\` followed by the message body and \`EOF\`. The thread will be auto-created if it doesn't exist yet.
955
+ - **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.
948
956
  - When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
949
957
  - You can read thread history: \`slock message read --channel "#general:a1b2c3d4"\`
950
958
  - 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.
@@ -978,6 +986,11 @@ Each channel has a **name** and optionally a **description** that define its pur
978
986
  - **Reply in context** \u2014 always respond in the channel/thread the message came from.
979
987
  - **Stay on topic** \u2014 when proactively sharing results or updates, post in the channel most relevant to the work. Don't scatter messages across unrelated channels.
980
988
  - If unsure where something belongs, call ${serverInfoCmd} to review channel descriptions.`;
989
+ const thirdPartyIntegrationsSection = isCli ? `### Third-party integrations
990
+
991
+ If a registered third-party service requires login, use Slock Agent Login through the CLI instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app, first run \`slock integration list\` and match the app to a registered service before browsing the app. Use \`slock integration login --service <service>\` to provision or reuse your agent login for that service. When the command returns \`Agent login ready\` or \`Already logged in\`, the agent-side login is ready. If the output includes an app URL, open that URL as the service-provided third-party app surface; it should look like the service's normal Login with Slock callback and not require you to understand Slock's internal grant/request protocol. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow, use internal request IDs as OAuth callback codes, or call third-party exchange endpoints unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use \`slock profile show\`. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the registered service / Slock Agent Login path.` : `### Third-party integrations
992
+
993
+ If a registered third-party service requires login, use Slock Agent Login through the available registered-service interface instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app, first inspect the registered-service interface and match the app to a registered service before browsing the app. Once the registered-service interface reports the agent login is ready, the agent-side login is ready. If that interface provides an app URL, use it as the service-provided third-party app surface; it should look like the service's normal Login with Slock callback and not require you to understand Slock's internal grant/request protocol. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow or treat internal request IDs as OAuth callback codes unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use your Slock profile view. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the registered service / Slock Agent Login path.`;
981
994
  const readingHistorySection = isCli ? `### Reading history
982
995
 
983
996
  \`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
@@ -1014,7 +1027,7 @@ Only top-level channel / DM messages can become tasks. Messages inside threads a
1014
1027
  **Workflow:**
1015
1028
  1. Receive a message that requires action \u2192 claim it first (by task number if already a task, or by message ID if it's a regular message)
1016
1029
  2. If the claim fails, someone else is working on it \u2014 move on to another task
1017
- 3. Post updates in the task's thread: \`slock message send --target "#channel:msgShortId" <<'EOF'\` followed by the message body and \`EOF\`
1030
+ 3. Post updates in the task's thread: \`slock message send --target "#channel:msgShortId" <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
1018
1031
  4. When done, set status to \`in_review\` so a human can validate via \`slock task update\`
1019
1032
  5. After approval (e.g. "looks good", "merge it"), set status to \`done\`
1020
1033
 
@@ -1149,6 +1162,8 @@ ${discoverySection}
1149
1162
 
1150
1163
  ${channelAwarenessSection}
1151
1164
 
1165
+ ${thirdPartyIntegrationsSection}
1166
+
1152
1167
  ${readingHistorySection}
1153
1168
 
1154
1169
  ${historicalReferenceSection}
@@ -1180,6 +1195,17 @@ Keep the user informed. They cannot see your internal reasoning, so:
1180
1195
  - For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
1181
1196
  - When done, summarize the result.
1182
1197
  - Keep updates concise \u2014 one or two sentences. Don't flood the chat.
1198
+ - For long answers where users need the conclusion first but details still matter, put the conclusion and next action outside any collapse, then use sanitized HTML details blocks for optional depth:
1199
+
1200
+ \`\`\`html
1201
+ <details>
1202
+ <summary>Evidence, logs, or edge cases</summary>
1203
+
1204
+ Detailed notes go here.
1205
+ </details>
1206
+ \`\`\`
1207
+
1208
+ Do not hide the main recommendation, blocker, or required action inside \`<details>\`; only fold supporting evidence, logs, alternatives, or extended rationale.
1183
1209
 
1184
1210
  ### Conversation etiquette
1185
1211
 
@@ -1348,6 +1374,19 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
1348
1374
  return candidates.filter((candidate) => existsSync(candidate.path));
1349
1375
  }
1350
1376
 
1377
+ // src/authEnv.ts
1378
+ var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
1379
+ var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
1380
+ function scrubDaemonAuthEnv(env) {
1381
+ delete env[DAEMON_API_KEY_ENV];
1382
+ return env;
1383
+ }
1384
+ function scrubDaemonChildEnv(env) {
1385
+ delete env[DAEMON_API_KEY_ENV];
1386
+ delete env[SLOCK_AGENT_TOKEN_ENV];
1387
+ return env;
1388
+ }
1389
+
1351
1390
  // src/agentCredentialProxy.ts
1352
1391
  import { randomBytes } from "crypto";
1353
1392
  import http from "http";
@@ -1810,6 +1849,18 @@ function consumeVisibleResponse(registration, targetUrl, sendTarget, responseTex
1810
1849
  });
1811
1850
  return;
1812
1851
  }
1852
+ if (targetUrl.pathname === "/internal/agent-api/send" && parsed.state === "sent") {
1853
+ const messageSeq2 = typeof parsed.messageSeq === "number" && Number.isFinite(parsed.messageSeq) ? Math.floor(parsed.messageSeq) : void 0;
1854
+ if (sendTarget && messageSeq2 && messageSeq2 > 0) {
1855
+ coordinator.consumeVisibleMessages({
1856
+ target: sendTarget,
1857
+ messages: normalizeVisibleMessages([{ seq: messageSeq2, id: parsed.messageId }], sendTarget),
1858
+ boundarySeq: messageSeq2,
1859
+ source: "agent_api_send_commit"
1860
+ });
1861
+ }
1862
+ return;
1863
+ }
1813
1864
  if (targetUrl.pathname === "/internal/agent-api/events" && Array.isArray(parsed.events)) {
1814
1865
  const messages = normalizeVisibleMessages(parsed.events);
1815
1866
  coordinator.consumeVisibleMessages({ messages, source: "agent_api_events" });
@@ -1859,8 +1910,51 @@ function unregisterAgentCredentialProxyForLaunch(input) {
1859
1910
 
1860
1911
  // src/drivers/cliTransport.ts
1861
1912
  var shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
1913
+ var powershellSingleQuote = (value) => `'${value.replace(/'/g, "''")}'`;
1862
1914
  var DEFAULT_ACTIVE_CAPABILITIES = "send,read,mentions,tasks,reactions,server,channels";
1863
1915
  var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
1916
+ var cachedOpencliBinPath;
1917
+ function resolveOpencliBinPath() {
1918
+ if (cachedOpencliBinPath !== void 0) return cachedOpencliBinPath;
1919
+ try {
1920
+ const require2 = createRequire(import.meta.url);
1921
+ const mainPath = require2.resolve("@jackwener/opencli");
1922
+ let dir = path2.dirname(mainPath);
1923
+ const root = path2.parse(dir).root;
1924
+ while (dir && dir !== root) {
1925
+ const candidate = path2.join(dir, "package.json");
1926
+ try {
1927
+ const pkg = JSON.parse(readFileSync(candidate, "utf8"));
1928
+ if (pkg.name === "@jackwener/opencli") {
1929
+ const binEntry = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.opencli;
1930
+ if (!binEntry) {
1931
+ cachedOpencliBinPath = null;
1932
+ return null;
1933
+ }
1934
+ cachedOpencliBinPath = path2.resolve(dir, binEntry);
1935
+ return cachedOpencliBinPath;
1936
+ }
1937
+ } catch {
1938
+ }
1939
+ const parent = path2.dirname(dir);
1940
+ if (parent === dir) break;
1941
+ dir = parent;
1942
+ }
1943
+ cachedOpencliBinPath = null;
1944
+ return null;
1945
+ } catch {
1946
+ cachedOpencliBinPath = null;
1947
+ return null;
1948
+ }
1949
+ }
1950
+ function windowsUtf8Env() {
1951
+ return {
1952
+ PYTHONIOENCODING: "utf-8",
1953
+ PYTHONUTF8: "1",
1954
+ LANG: "C.UTF-8",
1955
+ LC_ALL: "C.UTF-8"
1956
+ };
1957
+ }
1864
1958
  function runtimeContextEnv(config) {
1865
1959
  const ctx = config.runtimeContext;
1866
1960
  if (!ctx) return {};
@@ -1919,10 +2013,70 @@ ${posixCredentialPrefix}exec ${shellSingleQuote(process.execPath)} ${shellSingle
1919
2013
  set "SLOCK_AGENT_PROXY_TOKEN_FILE=${agentCredentialProxyTokenFile}"\r
1920
2014
  set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
1921
2015
  ` : "";
1922
- const cmdBody = `@echo off\r
1923
- ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
1924
- `;
2016
+ const cmdBody = [
2017
+ "@echo off",
2018
+ "set PYTHONIOENCODING=utf-8",
2019
+ "set PYTHONUTF8=1",
2020
+ "set LANG=C.UTF-8",
2021
+ "set LC_ALL=C.UTF-8",
2022
+ "chcp 65001 >NUL 2>NUL",
2023
+ cmdCredentialLine.trimEnd(),
2024
+ `"${process.execPath}" "${ctx.slockCliPath}" %*`,
2025
+ ""
2026
+ ].filter((line) => line.length > 0).join("\r\n") + "\r\n";
1925
2027
  writeFileSync(cmdWrapper, cmdBody);
2028
+ const psWrapper = path2.join(slockDir, "slock.ps1");
2029
+ const psCredentialLines = agentCredentialProxy ? [
2030
+ `$env:SLOCK_AGENT_PROXY_URL=${powershellSingleQuote(agentCredentialProxy.proxyUrl)}`,
2031
+ `$env:SLOCK_AGENT_PROXY_TOKEN_FILE=${powershellSingleQuote(agentCredentialProxyTokenFile)}`,
2032
+ `$env:SLOCK_AGENT_ACTIVE_CAPABILITIES=${powershellSingleQuote(DEFAULT_ACTIVE_CAPABILITIES)}`
2033
+ ] : [];
2034
+ const psBody = [
2035
+ "$ErrorActionPreference = 'Stop'",
2036
+ "$utf8NoBom = [System.Text.UTF8Encoding]::new($false)",
2037
+ "[Console]::OutputEncoding = $utf8NoBom",
2038
+ "$OutputEncoding = $utf8NoBom",
2039
+ "$env:PYTHONIOENCODING = 'utf-8'",
2040
+ "$env:PYTHONUTF8 = '1'",
2041
+ "$env:LANG = 'C.UTF-8'",
2042
+ "$env:LC_ALL = 'C.UTF-8'",
2043
+ ...psCredentialLines,
2044
+ `$node = ${powershellSingleQuote(process.execPath)}`,
2045
+ `$cli = ${powershellSingleQuote(ctx.slockCliPath)}`,
2046
+ "if ($MyInvocation.ExpectingInput) {",
2047
+ " $input | & $node $cli @args",
2048
+ "} else {",
2049
+ " & $node $cli @args",
2050
+ "}",
2051
+ "exit $LASTEXITCODE",
2052
+ ""
2053
+ ].join("\r\n");
2054
+ writeFileSync(psWrapper, psBody);
2055
+ }
2056
+ const opencliBinPath = resolveOpencliBinPath();
2057
+ if (opencliBinPath) {
2058
+ const opencliPosixWrapper = path2.join(slockDir, "opencli");
2059
+ writeFileSync(
2060
+ opencliPosixWrapper,
2061
+ `#!/usr/bin/env bash
2062
+ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(opencliBinPath)} "$@"
2063
+ `,
2064
+ { mode: 493 }
2065
+ );
2066
+ if (platform === "win32") {
2067
+ const opencliCmdWrapper = path2.join(slockDir, "opencli.cmd");
2068
+ const opencliCmdBody = [
2069
+ "@echo off",
2070
+ "set PYTHONIOENCODING=utf-8",
2071
+ "set PYTHONUTF8=1",
2072
+ "set LANG=C.UTF-8",
2073
+ "set LC_ALL=C.UTF-8",
2074
+ "chcp 65001 >NUL 2>NUL",
2075
+ `"${process.execPath}" "${opencliBinPath}" %*`,
2076
+ ""
2077
+ ].join("\r\n") + "\r\n";
2078
+ writeFileSync(opencliCmdWrapper, opencliCmdBody);
2079
+ }
1926
2080
  }
1927
2081
  const wrapperPath = platform === "win32" ? path2.join(slockDir, "slock.cmd") : posixWrapper;
1928
2082
  const spawnEnv = {
@@ -1930,6 +2084,7 @@ ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
1930
2084
  FORCE_COLOR: "0",
1931
2085
  ...ctx.config.envVars || {},
1932
2086
  ...extraEnv,
2087
+ ...platform === "win32" ? windowsUtf8Env() : {},
1933
2088
  ...runtimeContextEnv(ctx.config),
1934
2089
  [SLOCK_HOME_ENV]: slockHome,
1935
2090
  SLOCK_AGENT_ID: ctx.agentId,
@@ -1938,7 +2093,7 @@ ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
1938
2093
  ...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
1939
2094
  PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
1940
2095
  };
1941
- delete spawnEnv.SLOCK_AGENT_TOKEN;
2096
+ scrubDaemonChildEnv(spawnEnv);
1942
2097
  delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY;
1943
2098
  delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY_FILE;
1944
2099
  delete spawnEnv.SLOCK_AGENT_PROXY_URL;
@@ -1985,7 +2140,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn) {
1985
2140
  }
1986
2141
  function resolveCommandOnPath(command, deps = {}) {
1987
2142
  const platform = deps.platform ?? process.platform;
1988
- const env = deps.env ?? process.env;
2143
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
1989
2144
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
1990
2145
  if (platform === "win32") {
1991
2146
  return resolveCommandOnWindows(command, env, execFileSyncFn);
@@ -2010,7 +2165,7 @@ function firstExistingPath(candidates, deps = {}) {
2010
2165
  return null;
2011
2166
  }
2012
2167
  function readCommandVersion(command, args = [], deps = {}) {
2013
- const env = deps.env ?? process.env;
2168
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
2014
2169
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
2015
2170
  try {
2016
2171
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -2072,7 +2227,7 @@ function expandClaudeMcpConfigVariables(raw, vars) {
2072
2227
  function readClaudeMcpServers(configPath, vars = {}) {
2073
2228
  try {
2074
2229
  const parsed = JSON.parse(
2075
- expandClaudeMcpConfigVariables(readFileSync(configPath, "utf8"), vars)
2230
+ expandClaudeMcpConfigVariables(readFileSync2(configPath, "utf8"), vars)
2076
2231
  );
2077
2232
  if (!isRecord(parsed) || !isRecord(parsed.mcpServers)) return null;
2078
2233
  return parsed.mcpServers;
@@ -2374,7 +2529,7 @@ var ClaudeDriver = class {
2374
2529
 
2375
2530
  // src/drivers/codex.ts
2376
2531
  import { spawn as spawn2, execFileSync as execFileSync2, execSync } from "child_process";
2377
- import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
2532
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
2378
2533
  import os3 from "os";
2379
2534
  import path5 from "path";
2380
2535
  function getCodexNotificationErrorMessage(params) {
@@ -2912,7 +3067,7 @@ function detectCodexModels(home = os3.homedir()) {
2912
3067
  const configPath = path5.join(home, ".codex", "config.toml");
2913
3068
  let models = [];
2914
3069
  try {
2915
- const raw = readFileSync2(cachePath, "utf8");
3070
+ const raw = readFileSync3(cachePath, "utf8");
2916
3071
  const parsed = JSON.parse(raw);
2917
3072
  const entries = Array.isArray(parsed?.models) ? parsed.models : [];
2918
3073
  for (const entry of entries) {
@@ -2929,7 +3084,7 @@ function detectCodexModels(home = os3.homedir()) {
2929
3084
  if (models.length === 0) return null;
2930
3085
  let defaultModel;
2931
3086
  try {
2932
- const raw = readFileSync2(configPath, "utf8");
3087
+ const raw = readFileSync3(configPath, "utf8");
2933
3088
  const match = raw.match(/^\s*model\s*=\s*"([^"]+)"/m);
2934
3089
  if (match) defaultModel = match[1];
2935
3090
  } catch {
@@ -3306,7 +3461,7 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
3306
3461
  }
3307
3462
  function runCursorModelsCommand() {
3308
3463
  return spawnSync("cursor-agent", ["models"], {
3309
- env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
3464
+ env: scrubDaemonChildEnv({ ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" }),
3310
3465
  encoding: "utf8",
3311
3466
  timeout: 5e3
3312
3467
  });
@@ -3353,7 +3508,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
3353
3508
  }
3354
3509
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
3355
3510
  const existsSyncFn = deps.existsSyncFn ?? existsSync6;
3356
- const env = deps.env ?? process.env;
3511
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
3357
3512
  const winPath = path8.win32;
3358
3513
  let geminiEntry = null;
3359
3514
  try {
@@ -3527,13 +3682,16 @@ var GeminiDriver = class {
3527
3682
  // src/drivers/kimi.ts
3528
3683
  import { randomUUID } from "crypto";
3529
3684
  import { spawn as spawn6 } from "child_process";
3530
- import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
3685
+ import { chmodSync, existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "fs";
3531
3686
  import os4 from "os";
3532
3687
  import path9 from "path";
3533
3688
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
3534
3689
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
3535
3690
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
3536
3691
  var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
3692
+ var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
3693
+ var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
3694
+ var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
3537
3695
  function parseToolArguments(raw) {
3538
3696
  if (typeof raw !== "string") return raw;
3539
3697
  try {
@@ -3542,6 +3700,73 @@ function parseToolArguments(raw) {
3542
3700
  return raw;
3543
3701
  }
3544
3702
  }
3703
+ function readKimiConfigSource(home = os4.homedir(), env = process.env) {
3704
+ const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
3705
+ if (inlineConfig && inlineConfig.trim()) {
3706
+ return {
3707
+ raw: inlineConfig,
3708
+ explicitPath: null,
3709
+ sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
3710
+ };
3711
+ }
3712
+ const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
3713
+ const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
3714
+ try {
3715
+ return {
3716
+ raw: readFileSync4(configPath, "utf8"),
3717
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
3718
+ sourcePath: configPath
3719
+ };
3720
+ } catch {
3721
+ return {
3722
+ raw: null,
3723
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
3724
+ sourcePath: configPath
3725
+ };
3726
+ }
3727
+ }
3728
+ function buildKimiSpawnEnv(env = process.env) {
3729
+ const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
3730
+ delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
3731
+ delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
3732
+ return scrubDaemonChildEnv(spawnEnv);
3733
+ }
3734
+ function buildKimiEffectiveEnv(ctx, overrideEnv) {
3735
+ return {
3736
+ ...process.env,
3737
+ ...ctx.config.envVars || {},
3738
+ ...overrideEnv || {}
3739
+ };
3740
+ }
3741
+ function buildKimiLaunchOptions(ctx, opts = {}) {
3742
+ const env = buildKimiEffectiveEnv(ctx, opts.env);
3743
+ const source = readKimiConfigSource(opts.home ?? os4.homedir(), env);
3744
+ const args = [];
3745
+ let configFilePath = null;
3746
+ let configContent = null;
3747
+ if (source.explicitPath) {
3748
+ configFilePath = source.explicitPath;
3749
+ } else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
3750
+ configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
3751
+ configContent = source.raw;
3752
+ if (opts.writeGeneratedConfig !== false) {
3753
+ writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
3754
+ chmodSync(configFilePath, 384);
3755
+ }
3756
+ }
3757
+ if (configFilePath) {
3758
+ args.push("--config-file", configFilePath);
3759
+ }
3760
+ if (ctx.config.model && ctx.config.model !== "default") {
3761
+ args.push("--model", ctx.config.model);
3762
+ }
3763
+ return {
3764
+ args,
3765
+ env: buildKimiSpawnEnv(env),
3766
+ configFilePath,
3767
+ configContent
3768
+ };
3769
+ }
3545
3770
  function resolveKimiSpawn(commandArgs, deps = {}) {
3546
3771
  return {
3547
3772
  command: resolveCommandOnPath("kimi", deps) ?? "kimi",
@@ -3565,7 +3790,25 @@ var KimiDriver = class {
3565
3790
  };
3566
3791
  model = {
3567
3792
  detectedModelsVerifiedAs: "launchable",
3568
- toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
3793
+ toLaunchSpec: (modelId, ctx, opts) => {
3794
+ if (!ctx) return { args: ["--model", modelId] };
3795
+ const launchCtx = {
3796
+ ...ctx,
3797
+ config: {
3798
+ ...ctx.config,
3799
+ model: modelId
3800
+ }
3801
+ };
3802
+ const launch = buildKimiLaunchOptions(launchCtx, {
3803
+ home: opts?.home,
3804
+ writeGeneratedConfig: false
3805
+ });
3806
+ return {
3807
+ args: launch.args,
3808
+ env: launch.env,
3809
+ configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
3810
+ };
3811
+ }
3569
3812
  };
3570
3813
  supportsStdinNotification = true;
3571
3814
  mcpToolPrefix = "";
@@ -3619,6 +3862,7 @@ var KimiDriver = class {
3619
3862
  }
3620
3863
  }
3621
3864
  }), "utf8");
3865
+ const launch = buildKimiLaunchOptions(ctx);
3622
3866
  const args = [
3623
3867
  "--wire",
3624
3868
  "--yolo",
@@ -3627,14 +3871,15 @@ var KimiDriver = class {
3627
3871
  "--mcp-config-file",
3628
3872
  mcpConfigPath,
3629
3873
  "--session",
3630
- this.sessionId
3874
+ this.sessionId,
3875
+ ...launch.args
3631
3876
  ];
3632
3877
  if (ctx.config.model && ctx.config.model !== "default") {
3633
3878
  args.push("--model", ctx.config.model);
3634
3879
  }
3635
3880
  const spawnEnv = prepareCliTransport(ctx, { NO_COLOR: "1" }).spawnEnv;
3636
- const launch = resolveKimiSpawn(args);
3637
- const proc = spawn6(launch.command, launch.args, {
3881
+ const spawnTarget = resolveKimiSpawn(args);
3882
+ const proc = spawn6(spawnTarget.command, spawnTarget.args, {
3638
3883
  cwd: ctx.workingDirectory,
3639
3884
  stdio: ["pipe", "pipe", "pipe"],
3640
3885
  env: spawnEnv,
@@ -3642,7 +3887,7 @@ var KimiDriver = class {
3642
3887
  // and has an 8191-character command-line limit. Kimi's official
3643
3888
  // installer/uv entrypoint is an executable, so launch it directly and
3644
3889
  // keep prompts on stdin / files instead of routing through cmd.exe.
3645
- shell: launch.shell
3890
+ shell: spawnTarget.shell
3646
3891
  });
3647
3892
  proc.stdin?.write(JSON.stringify({
3648
3893
  jsonrpc: "2.0",
@@ -3758,14 +4003,9 @@ var KimiDriver = class {
3758
4003
  return detectKimiModels();
3759
4004
  }
3760
4005
  };
3761
- function detectKimiModels(home = os4.homedir()) {
3762
- const configPath = path9.join(home, ".kimi", "config.toml");
3763
- let raw;
3764
- try {
3765
- raw = readFileSync3(configPath, "utf8");
3766
- } catch {
3767
- return null;
3768
- }
4006
+ function detectKimiModels(home = os4.homedir(), opts = {}) {
4007
+ const raw = readKimiConfigSource(home, opts.env).raw;
4008
+ if (raw === null) return null;
3769
4009
  const models = [];
3770
4010
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
3771
4011
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -3786,7 +4026,7 @@ function detectKimiModels(home = os4.homedir()) {
3786
4026
 
3787
4027
  // src/drivers/opencode.ts
3788
4028
  import { spawn as spawn7, spawnSync as spawnSync2 } from "child_process";
3789
- import { readFileSync as readFileSync4 } from "fs";
4029
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
3790
4030
  import os5 from "os";
3791
4031
  import path10 from "path";
3792
4032
  var CHAT_MCP_SERVER_NAME = "chat";
@@ -3838,7 +4078,7 @@ function parseUserOpenCodeConfig(ctx) {
3838
4078
  function readLocalOpenCodeConfig(home = os5.homedir()) {
3839
4079
  const configPath = path10.join(home, ".config", "opencode", "opencode.json");
3840
4080
  try {
3841
- return parseOpenCodeConfigContent(readFileSync4(configPath, "utf8"));
4081
+ return parseOpenCodeConfigContent(readFileSync5(configPath, "utf8"));
3842
4082
  } catch {
3843
4083
  }
3844
4084
  return {};
@@ -4028,7 +4268,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
4028
4268
  const platform = deps.platform ?? process.platform;
4029
4269
  const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
4030
4270
  const result = spawnSyncFn("opencode", ["models"], {
4031
- env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
4271
+ env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
4032
4272
  encoding: "utf8",
4033
4273
  timeout: 5e3,
4034
4274
  shell: platform === "win32"
@@ -4039,6 +4279,102 @@ function runOpenCodeModelsCommand(home, deps = {}) {
4039
4279
  error: result.error
4040
4280
  };
4041
4281
  }
4282
+ function isWindowsCommandShim(commandPath) {
4283
+ const ext = path10.win32.extname(commandPath).toLowerCase();
4284
+ return ext === ".cmd" || ext === ".bat";
4285
+ }
4286
+ function opencodePackageEntryCandidates(packageRoot) {
4287
+ const winPath = path10.win32;
4288
+ return [
4289
+ winPath.join(packageRoot, "bin", "opencode.exe"),
4290
+ winPath.join(packageRoot, "bin", "opencode.js"),
4291
+ winPath.join(packageRoot, "bin", "opencode.mjs"),
4292
+ winPath.join(packageRoot, "dist", "index.js")
4293
+ ];
4294
+ }
4295
+ function openCodeSpecForEntry(entry, commandArgs) {
4296
+ if (path10.win32.extname(entry).toLowerCase() === ".exe") {
4297
+ return { command: entry, args: commandArgs, shell: false };
4298
+ }
4299
+ return { command: process.execPath, args: [entry, ...commandArgs], shell: false };
4300
+ }
4301
+ function resolveWindowsOpenCodePackageEntry(commandPath, deps = {}) {
4302
+ const existsSyncFn = deps.existsSyncFn ?? existsSync8;
4303
+ const execFileSyncFn = deps.execFileSyncFn;
4304
+ const env = deps.env ?? process.env;
4305
+ const winPath = path10.win32;
4306
+ const candidates = [];
4307
+ if (execFileSyncFn) {
4308
+ try {
4309
+ const globalRoot = String(execFileSyncFn("npm", ["root", "-g"], {
4310
+ encoding: "utf8",
4311
+ stdio: ["ignore", "pipe", "ignore"],
4312
+ env
4313
+ })).trim();
4314
+ if (globalRoot) {
4315
+ candidates.push(...opencodePackageEntryCandidates(winPath.join(globalRoot, "opencode-ai")));
4316
+ }
4317
+ } catch {
4318
+ }
4319
+ }
4320
+ if (commandPath) {
4321
+ const commandDir = winPath.dirname(commandPath);
4322
+ candidates.push(...opencodePackageEntryCandidates(winPath.join(commandDir, "node_modules", "opencode-ai")));
4323
+ candidates.push(...extractWindowsShimTargets(commandPath, deps));
4324
+ }
4325
+ for (const candidate of candidates) {
4326
+ if (existsSyncFn(candidate)) return candidate;
4327
+ }
4328
+ return null;
4329
+ }
4330
+ function extractWindowsShimTargets(commandPath, deps = {}) {
4331
+ if (!isWindowsCommandShim(commandPath)) return [];
4332
+ const readFileSyncFn = deps.readFileSyncFn ?? readFileSync5;
4333
+ const commandDir = path10.win32.dirname(commandPath);
4334
+ let raw;
4335
+ try {
4336
+ raw = String(readFileSyncFn(commandPath, "utf8"));
4337
+ } catch {
4338
+ return [];
4339
+ }
4340
+ const candidates = [];
4341
+ const dp0Pattern = /%~dp0\\?([^"\r\n]*?opencode\.(?:exe|js|mjs|cjs))/gi;
4342
+ for (const match of raw.matchAll(dp0Pattern)) {
4343
+ const relative = match[1]?.replace(/^\\+/, "");
4344
+ if (relative) candidates.push(path10.win32.normalize(path10.win32.join(commandDir, relative)));
4345
+ }
4346
+ return candidates;
4347
+ }
4348
+ function resolveOpenCodeSpawn(commandArgs, deps = {}) {
4349
+ const platform = deps.platform ?? process.platform;
4350
+ if (platform !== "win32") {
4351
+ return {
4352
+ command: resolveCommandOnPath("opencode", deps) ?? "opencode",
4353
+ args: commandArgs,
4354
+ shell: false
4355
+ };
4356
+ }
4357
+ const command = resolveCommandOnPath("opencode", deps);
4358
+ if (command && path10.win32.extname(command).toLowerCase() === ".exe") {
4359
+ return { command, args: commandArgs, shell: false };
4360
+ }
4361
+ const packageEntry = resolveWindowsOpenCodePackageEntry(command, deps);
4362
+ if (packageEntry) return openCodeSpecForEntry(packageEntry, commandArgs);
4363
+ if (command && !isWindowsCommandShim(command)) {
4364
+ return { command, args: commandArgs, shell: false };
4365
+ }
4366
+ throw new Error(
4367
+ "Cannot resolve OpenCode CLI entry point on Windows without cmd.exe. Install the native OpenCode executable or install opencode-ai globally so Slock can launch node_modules/opencode-ai/bin/opencode.exe directly."
4368
+ );
4369
+ }
4370
+ function readOpenCodeVersion(deps = {}) {
4371
+ try {
4372
+ const launch = resolveOpenCodeSpawn([], deps);
4373
+ return readCommandVersion(launch.command, launch.args, deps);
4374
+ } catch {
4375
+ return null;
4376
+ }
4377
+ }
4042
4378
  function isSystemFirstMessageTask(message) {
4043
4379
  return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX);
4044
4380
  }
@@ -4081,7 +4417,7 @@ var OpenCodeDriver = class {
4081
4417
  model: modelId
4082
4418
  }
4083
4419
  };
4084
- const version = readCommandVersion("opencode");
4420
+ const version = readOpenCodeVersion();
4085
4421
  const launch = buildOpenCodeLaunchOptions(launchCtx, opts?.home, version);
4086
4422
  return {
4087
4423
  args: launch.args,
@@ -4102,8 +4438,13 @@ var OpenCodeDriver = class {
4102
4438
  sessionId = null;
4103
4439
  sessionAnnounced = false;
4104
4440
  probe() {
4105
- if (!resolveCommandOnPath("opencode")) return { available: false };
4106
- const version = readCommandVersion("opencode") || void 0;
4441
+ let version;
4442
+ try {
4443
+ const launch = resolveOpenCodeSpawn([]);
4444
+ version = readCommandVersion(launch.command, launch.args);
4445
+ } catch {
4446
+ return { available: false };
4447
+ }
4107
4448
  const unsupportedMessage = unsupportedOpenCodeVersionMessage(version);
4108
4449
  if (unsupportedMessage) {
4109
4450
  return {
@@ -4111,7 +4452,7 @@ var OpenCodeDriver = class {
4111
4452
  version: `${version} (requires >= ${MIN_SUPPORTED_OPENCODE_VERSION})`
4112
4453
  };
4113
4454
  }
4114
- return { available: true, version };
4455
+ return { available: true, version: version ?? void 0 };
4115
4456
  }
4116
4457
  async detectModels() {
4117
4458
  return detectOpenCodeModels();
@@ -4119,17 +4460,18 @@ var OpenCodeDriver = class {
4119
4460
  spawn(ctx) {
4120
4461
  this.sessionId = ctx.config.sessionId || null;
4121
4462
  this.sessionAnnounced = false;
4122
- const version = readCommandVersion("opencode");
4463
+ const version = readOpenCodeVersion();
4123
4464
  const unsupportedMessage = unsupportedOpenCodeVersionMessage(version);
4124
4465
  if (unsupportedMessage) {
4125
4466
  throw new Error(unsupportedMessage);
4126
4467
  }
4127
4468
  const launch = buildOpenCodeLaunchOptions(ctx, os5.homedir(), version);
4128
- const proc = spawn7("opencode", launch.args, {
4469
+ const spawnSpec = resolveOpenCodeSpawn(launch.args);
4470
+ const proc = spawn7(spawnSpec.command, spawnSpec.args, {
4129
4471
  cwd: ctx.workingDirectory,
4130
4472
  stdio: ["pipe", "pipe", "pipe"],
4131
4473
  env: launch.env,
4132
- shell: process.platform === "win32"
4474
+ shell: spawnSpec.shell
4133
4475
  });
4134
4476
  proc.stdin?.end();
4135
4477
  return { process: proc };
@@ -4187,6 +4529,297 @@ var OpenCodeDriver = class {
4187
4529
  }
4188
4530
  };
4189
4531
 
4532
+ // src/drivers/pi.ts
4533
+ import { spawn as spawn8 } from "child_process";
4534
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
4535
+ import path11 from "path";
4536
+ import { fileURLToPath } from "url";
4537
+ import { getAgentDir, VERSION as PI_SDK_VERSION } from "@earendil-works/pi-coding-agent";
4538
+ var CHAT_MCP_TOOL_PREFIX2 = "chat_";
4539
+ var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
4540
+ var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
4541
+ var MIN_SUPPORTED_PI_VERSION = "0.74.0";
4542
+ function parseSemver2(version) {
4543
+ const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
4544
+ if (!match) return null;
4545
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
4546
+ }
4547
+ function isSupportedPiVersion(version) {
4548
+ if (!version) return true;
4549
+ const actual = parseSemver2(version);
4550
+ const minimum = parseSemver2(MIN_SUPPORTED_PI_VERSION);
4551
+ if (!actual || !minimum) return true;
4552
+ for (let i = 0; i < 3; i += 1) {
4553
+ if (actual[i] > minimum[i]) return true;
4554
+ if (actual[i] < minimum[i]) return false;
4555
+ }
4556
+ return true;
4557
+ }
4558
+ function unsupportedPiVersionMessage(version) {
4559
+ if (!version || isSupportedPiVersion(version)) return null;
4560
+ 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.`;
4561
+ }
4562
+ function probePi(version = PI_SDK_VERSION) {
4563
+ const unsupportedMessage = unsupportedPiVersionMessage(version);
4564
+ if (unsupportedMessage) {
4565
+ return {
4566
+ available: false,
4567
+ version: `${version} (requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION})`
4568
+ };
4569
+ }
4570
+ return { available: true, version };
4571
+ }
4572
+ function resolvePiSdkRunnerPath(moduleUrl = import.meta.url) {
4573
+ const moduleDir = path11.dirname(fileURLToPath(moduleUrl));
4574
+ const sourceSibling = path11.join(moduleDir, "piSdkRunner.ts");
4575
+ if (existsSync9(sourceSibling)) return sourceSibling;
4576
+ const bundledEntry = path11.join(moduleDir, "drivers", "piSdkRunner.js");
4577
+ if (existsSync9(bundledEntry)) return bundledEntry;
4578
+ return path11.join(moduleDir, "piSdkRunner.js");
4579
+ }
4580
+ function buildPiSdkNodeArgs(runnerPath = resolvePiSdkRunnerPath()) {
4581
+ if (runnerPath.endsWith(".ts")) {
4582
+ return [...process.execArgv, runnerPath];
4583
+ }
4584
+ return [runnerPath];
4585
+ }
4586
+ function buildPiLaunchOptions(ctx, opts = {}) {
4587
+ const command = opts.command ?? process.execPath;
4588
+ const piDir = path11.join(ctx.workingDirectory, ".slock", "pi");
4589
+ const sessionDir = path11.join(piDir, "sessions");
4590
+ mkdirSync4(sessionDir, { recursive: true });
4591
+ const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
4592
+ const runnerPath = opts.runnerPath ?? resolvePiSdkRunnerPath();
4593
+ const agentDir = opts.agentDir ?? getAgentDir();
4594
+ const runnerConfigPath = path11.join(
4595
+ piDir,
4596
+ `sdk-run-${(ctx.launchId || "launch").replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`
4597
+ );
4598
+ const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
4599
+ const runnerConfig = {
4600
+ cwd: ctx.workingDirectory,
4601
+ agentDir,
4602
+ sessionDir,
4603
+ sessionId: ctx.config.sessionId || null,
4604
+ standingPrompt: ctx.standingPrompt,
4605
+ prompt: turnPrompt,
4606
+ model: ctx.config.model && ctx.config.model !== "default" ? ctx.config.model : null
4607
+ };
4608
+ writeFileSync7(runnerConfigPath, `${JSON.stringify(runnerConfig)}
4609
+ `, { encoding: "utf8", mode: 384 });
4610
+ const args = [
4611
+ ...buildPiSdkNodeArgs(runnerPath),
4612
+ "--config",
4613
+ runnerConfigPath
4614
+ ];
4615
+ return {
4616
+ command,
4617
+ args,
4618
+ env: slock.spawnEnv,
4619
+ sessionDir,
4620
+ agentDir,
4621
+ runnerConfigPath,
4622
+ sdkVersion: PI_SDK_VERSION
4623
+ };
4624
+ }
4625
+ function isSystemFirstMessageTask2(message) {
4626
+ return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
4627
+ }
4628
+ function buildPiSystemPrompt(config) {
4629
+ return buildCliTransportSystemPrompt(config, {
4630
+ toolPrefix: CHAT_MCP_TOOL_PREFIX2,
4631
+ extraCriticalRules: [
4632
+ "- 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."
4633
+ ],
4634
+ postStartupNotes: [
4635
+ "**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."
4636
+ ],
4637
+ includeStdinNotificationSection: false,
4638
+ messageNotificationStyle: "poll"
4639
+ });
4640
+ }
4641
+ function contentText(content) {
4642
+ if (!content) return "";
4643
+ const chunks = [];
4644
+ for (const item of content) {
4645
+ if (item.type === "text" && typeof item.text === "string") {
4646
+ chunks.push(item.text);
4647
+ }
4648
+ }
4649
+ return chunks.join("");
4650
+ }
4651
+ function apiKeyErrorMessage(line) {
4652
+ const trimmed = line.trim();
4653
+ if (!trimmed) return null;
4654
+ if (/no api key found/i.test(trimmed)) return trimmed;
4655
+ if (/api key.+required/i.test(trimmed)) return trimmed;
4656
+ if (/no models available/i.test(trimmed)) return trimmed;
4657
+ return null;
4658
+ }
4659
+ var PiDriver = class {
4660
+ id = "pi";
4661
+ lifecycle = {
4662
+ kind: "per_turn",
4663
+ start: "defer_until_concrete_message",
4664
+ exit: "terminate_on_turn_end",
4665
+ inFlightWake: "coalesce_into_pending"
4666
+ };
4667
+ communication = {
4668
+ chat: "slock_cli",
4669
+ runtimeControl: "none"
4670
+ };
4671
+ session = {
4672
+ recovery: "resume_or_fresh"
4673
+ };
4674
+ model = {
4675
+ detectedModelsVerifiedAs: "launchable",
4676
+ toLaunchSpec: (modelId, ctx) => {
4677
+ if (!ctx) return modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] };
4678
+ const launchCtx = {
4679
+ ...ctx,
4680
+ config: {
4681
+ ...ctx.config,
4682
+ model: modelId
4683
+ }
4684
+ };
4685
+ const launch = buildPiLaunchOptions(launchCtx);
4686
+ return {
4687
+ args: launch.args,
4688
+ env: launch.env,
4689
+ configFiles: [launch.runnerConfigPath],
4690
+ params: {
4691
+ agentDir: launch.agentDir,
4692
+ sessionDir: launch.sessionDir,
4693
+ sdkVersion: launch.sdkVersion,
4694
+ resources: "extensions/skills/prompt-templates/themes/context-files disabled by Slock policy"
4695
+ }
4696
+ };
4697
+ }
4698
+ };
4699
+ supportsStdinNotification = false;
4700
+ mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
4701
+ busyDeliveryMode = "none";
4702
+ terminateProcessOnTurnEnd = true;
4703
+ deferSpawnUntilMessage = true;
4704
+ usesSlockCliForCommunication = true;
4705
+ sessionId = null;
4706
+ sessionAnnounced = false;
4707
+ apiKeyErrorAnnounced = false;
4708
+ turnEnded = false;
4709
+ assistantTextByMessageId = /* @__PURE__ */ new Map();
4710
+ shouldDeferWakeMessage(message) {
4711
+ return isSystemFirstMessageTask2(message);
4712
+ }
4713
+ probe() {
4714
+ return probePi();
4715
+ }
4716
+ async detectModels() {
4717
+ return null;
4718
+ }
4719
+ spawn(ctx) {
4720
+ this.sessionId = ctx.config.sessionId || null;
4721
+ this.sessionAnnounced = false;
4722
+ this.apiKeyErrorAnnounced = false;
4723
+ this.turnEnded = false;
4724
+ this.assistantTextByMessageId.clear();
4725
+ const unsupportedMessage = unsupportedPiVersionMessage(PI_SDK_VERSION);
4726
+ if (unsupportedMessage) throw new Error(unsupportedMessage);
4727
+ const launch = buildPiLaunchOptions(ctx);
4728
+ const proc = spawn8(launch.command, launch.args, {
4729
+ cwd: ctx.workingDirectory,
4730
+ stdio: ["pipe", "pipe", "pipe"],
4731
+ env: launch.env,
4732
+ shell: false
4733
+ });
4734
+ proc.stdin?.end();
4735
+ return { process: proc };
4736
+ }
4737
+ parseLine(line) {
4738
+ let event;
4739
+ try {
4740
+ event = JSON.parse(line);
4741
+ } catch {
4742
+ if (this.apiKeyErrorAnnounced) return [];
4743
+ const message = apiKeyErrorMessage(line);
4744
+ if (!message) return [];
4745
+ this.apiKeyErrorAnnounced = true;
4746
+ this.turnEnded = true;
4747
+ return [
4748
+ { kind: "error", message },
4749
+ { kind: "turn_end", sessionId: this.sessionId || void 0 }
4750
+ ];
4751
+ }
4752
+ const events = [];
4753
+ if (event.type === "session" && event.id) {
4754
+ this.sessionId = event.id;
4755
+ }
4756
+ if (!this.sessionAnnounced && this.sessionId) {
4757
+ events.push({ kind: "session_init", sessionId: this.sessionId });
4758
+ this.sessionAnnounced = true;
4759
+ }
4760
+ switch (event.type) {
4761
+ case "agent_start":
4762
+ case "turn_start":
4763
+ events.push({ kind: "thinking", text: "" });
4764
+ break;
4765
+ case "message_update":
4766
+ case "message_end":
4767
+ if (event.message?.role === "assistant") {
4768
+ const key = event.message.id || "current";
4769
+ const currentText = contentText(event.message.content);
4770
+ const previousText = this.assistantTextByMessageId.get(key) ?? "";
4771
+ if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
4772
+ events.push({ kind: "text", text: currentText.slice(previousText.length) });
4773
+ } else if (currentText && currentText !== previousText) {
4774
+ events.push({ kind: "text", text: currentText });
4775
+ }
4776
+ this.assistantTextByMessageId.set(key, currentText);
4777
+ if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
4778
+ events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
4779
+ }
4780
+ }
4781
+ break;
4782
+ case "tool_execution_start":
4783
+ events.push({
4784
+ kind: "tool_call",
4785
+ name: event.toolName || "unknown_tool",
4786
+ input: event.args
4787
+ });
4788
+ break;
4789
+ case "tool_execution_end":
4790
+ events.push({
4791
+ kind: "tool_output",
4792
+ name: event.toolName || "unknown_tool"
4793
+ });
4794
+ if (event.isError) {
4795
+ events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
4796
+ }
4797
+ break;
4798
+ case "compaction_start":
4799
+ events.push({ kind: "compaction_started" });
4800
+ break;
4801
+ case "compaction_end":
4802
+ events.push({ kind: "compaction_finished" });
4803
+ if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
4804
+ break;
4805
+ case "turn_end":
4806
+ case "agent_end":
4807
+ if (!this.turnEnded) {
4808
+ events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
4809
+ this.turnEnded = true;
4810
+ }
4811
+ break;
4812
+ }
4813
+ return events;
4814
+ }
4815
+ encodeStdinMessage(_text, _sessionId, _opts) {
4816
+ return null;
4817
+ }
4818
+ buildSystemPrompt(config, _agentId) {
4819
+ return buildPiSystemPrompt(config);
4820
+ }
4821
+ };
4822
+
4190
4823
  // src/drivers/index.ts
4191
4824
  var driverFactories = {
4192
4825
  claude: () => new ClaudeDriver(),
@@ -4195,7 +4828,8 @@ var driverFactories = {
4195
4828
  cursor: () => new CursorDriver(),
4196
4829
  gemini: () => new GeminiDriver(),
4197
4830
  kimi: () => new KimiDriver(),
4198
- opencode: () => new OpenCodeDriver()
4831
+ opencode: () => new OpenCodeDriver(),
4832
+ pi: () => new PiDriver()
4199
4833
  };
4200
4834
  function getDriver(runtimeId) {
4201
4835
  const createDriver = driverFactories[runtimeId];
@@ -4208,7 +4842,7 @@ function getDriver(runtimeId) {
4208
4842
 
4209
4843
  // src/workspaces.ts
4210
4844
  import { readdir, rm, stat } from "fs/promises";
4211
- import path11 from "path";
4845
+ import path12 from "path";
4212
4846
  function isValidWorkspaceDirectoryName(directoryName) {
4213
4847
  return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
4214
4848
  }
@@ -4216,7 +4850,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
4216
4850
  if (!isValidWorkspaceDirectoryName(directoryName)) {
4217
4851
  return null;
4218
4852
  }
4219
- return path11.join(dataDir, directoryName);
4853
+ return path12.join(dataDir, directoryName);
4220
4854
  }
4221
4855
  function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
4222
4856
  return {
@@ -4265,7 +4899,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
4265
4899
  return summary;
4266
4900
  }
4267
4901
  const childSummaries = await Promise.all(
4268
- entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
4902
+ entries.map((entry) => summarizeWorkspaceEntry(path12.join(dirPath, entry.name), entry))
4269
4903
  );
4270
4904
  for (const childSummary of childSummaries) {
4271
4905
  summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
@@ -4284,7 +4918,7 @@ async function scanWorkspaceDirectories(dataDir) {
4284
4918
  if (!entry.isDirectory()) {
4285
4919
  return null;
4286
4920
  }
4287
- const dirPath = path11.join(dataDir, entry.name);
4921
+ const dirPath = path12.join(dataDir, entry.name);
4288
4922
  try {
4289
4923
  const summary = await summarizeWorkspaceDirectory(dirPath);
4290
4924
  return {
@@ -4367,6 +5001,7 @@ function classifyRuntimeError(message, httpStatus) {
4367
5001
  return "ProviderApiError";
4368
5002
  }
4369
5003
  if (/\btimeout|timed out\b/i.test(message)) return "TimeoutError";
5004
+ if (/stream closed before response\.completed|error decoding response body/i.test(message)) return "ProviderStreamError";
4370
5005
  if (/\brate.?limit|too many requests\b/i.test(message)) return "RateLimitError";
4371
5006
  if (/\bnot found\b/i.test(message)) return "NotFoundError";
4372
5007
  return "RuntimeError";
@@ -4541,12 +5176,12 @@ function findSessionJsonl(root, predicate) {
4541
5176
  for (const entry of entries) {
4542
5177
  if (++visited > maxEntries) return null;
4543
5178
  if (!entry.isFile() || !predicate(entry.name)) continue;
4544
- return path12.join(dir, entry.name);
5179
+ return path13.join(dir, entry.name);
4545
5180
  }
4546
5181
  for (const entry of entries) {
4547
5182
  if (++visited > maxEntries) return null;
4548
5183
  if (!entry.isDirectory()) continue;
4549
- const found = visit(path12.join(dir, entry.name), depth - 1);
5184
+ const found = visit(path13.join(dir, entry.name), depth - 1);
4550
5185
  if (found) return found;
4551
5186
  }
4552
5187
  return null;
@@ -4559,10 +5194,10 @@ function safeSessionFilename(value) {
4559
5194
  }
4560
5195
  function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
4561
5196
  try {
4562
- const dir = path12.join(fallbackDir, ".slock", "runtime-sessions");
4563
- mkdirSync4(dir, { recursive: true });
4564
- const filePath = path12.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
4565
- writeFileSync7(filePath, JSON.stringify({
5197
+ const dir = path13.join(fallbackDir, ".slock", "runtime-sessions");
5198
+ mkdirSync5(dir, { recursive: true });
5199
+ const filePath = path13.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
5200
+ writeFileSync8(filePath, JSON.stringify({
4566
5201
  type: "runtime_session_handoff",
4567
5202
  runtime,
4568
5203
  sessionId,
@@ -4581,7 +5216,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
4581
5216
  }
4582
5217
  }
4583
5218
  function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir) {
4584
- const directPath = path12.isAbsolute(sessionId) ? sessionId : null;
5219
+ const directPath = path13.isAbsolute(sessionId) ? sessionId : null;
4585
5220
  if (directPath) {
4586
5221
  try {
4587
5222
  if (statSync2(directPath).isFile()) {
@@ -4590,7 +5225,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
4590
5225
  } catch {
4591
5226
  }
4592
5227
  }
4593
- 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;
5228
+ 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;
4594
5229
  if (!resolvedPath && fallbackDir) {
4595
5230
  const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
4596
5231
  if (fallback) return fallback;
@@ -5042,7 +5677,7 @@ Do not copy these answers verbatim.
5042
5677
  ## FAQ 15: How do I create agents or channels?
5043
5678
  ### Answer idea
5044
5679
  - When the owner agrees to a new agent or channel, **post an action card** with \`slock action prepare\`. The card lives inline in chat; the owner clicks the action button, the matching create dialog opens prefilled with your values (editable), and the resource is created under their identity when they submit.
5045
- - v1 supports three action types via \`slock action prepare --target '<channel>' <<EOF { ... } EOF\`:
5680
+ - v1 supports three action types via \`slock action prepare --target '<channel>' <<'SLOCKACTION' { ... } SLOCKACTION\`:
5046
5681
  - \`{type: "channel:create", name, visibility: "public" | "private", description?, initialHumans?: ["@alice"], initialAgents?: ["@scout"], draftHint?}\`
5047
5682
  - \`{type: "agent:create", name, description?, draftHint?}\` \u2014 runtime / model / computer are the owner's call, not yours
5048
5683
  - \`{type: "channel:add_member", channel: "#existing-channel", humans?: ["@alice"], agents?: ["@scout"], draftHint?}\` \u2014 at least one of humans / agents must be non-empty. The owner clicks "Add Members" on the card; an AddMembers dialog opens with your suggested list (each row toggleable) and the owner submits to actually add them.
@@ -5241,12 +5876,21 @@ function classifyTerminalFailure(ap) {
5241
5876
  ].filter((value) => !!value);
5242
5877
  for (const text of candidates) {
5243
5878
  const lower = text.toLowerCase();
5244
- if (lower.includes("usage limit") || lower.includes("quota exceeded") || lower.includes("quota limit") || lower.includes("budget limit exceeded") || lower.includes("usage not included in your plan") || lower.includes("modelnotfounderror") || lower.includes("requested entity was not found") || lower.includes("model deprecated") || lower.includes("model not found")) {
5879
+ if (lower.includes("usage limit") || lower.includes("quota exceeded") || lower.includes("quota limit") || lower.includes("budget limit exceeded") || lower.includes("usage not included in your plan") || lower.includes("modelnotfounderror") || lower.includes("requested entity was not found") || lower.includes("model deprecated") || lower.includes("model not found") || isProviderStreamFailureText(text)) {
5245
5880
  return text;
5246
5881
  }
5247
5882
  }
5248
5883
  return null;
5249
5884
  }
5885
+ function isProviderStreamFailureText(text) {
5886
+ return /stream closed before response\.completed|error decoding response body/i.test(text);
5887
+ }
5888
+ function isCodexProviderReconnectLog(text) {
5889
+ return /Reconnecting\.\.\.\s*\d+\s*\/\s*\d+/i.test(text);
5890
+ }
5891
+ function isCodexBenignTransportLog(text) {
5892
+ return /Falling back from WebSockets/i.test(text);
5893
+ }
5250
5894
  function hasDirectStdinRecoveryEvidence(ap) {
5251
5895
  const candidates = [
5252
5896
  ap.lastRuntimeError,
@@ -5890,26 +6534,26 @@ var AgentProcessManager = class _AgentProcessManager {
5890
6534
  this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
5891
6535
  try {
5892
6536
  const driver = this.driverResolver(config.runtime || "claude");
5893
- const agentDataDir = path12.join(this.dataDir, agentId);
6537
+ const agentDataDir = path13.join(this.dataDir, agentId);
5894
6538
  await mkdir(agentDataDir, { recursive: true });
5895
6539
  const runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
5896
- const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
6540
+ const memoryMdPath = path13.join(agentDataDir, "MEMORY.md");
5897
6541
  try {
5898
6542
  await access(memoryMdPath);
5899
6543
  } catch {
5900
6544
  const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
5901
6545
  await writeFile(memoryMdPath, initialMemoryMd);
5902
6546
  }
5903
- const notesDir = path12.join(agentDataDir, "notes");
6547
+ const notesDir = path13.join(agentDataDir, "notes");
5904
6548
  await mkdir(notesDir, { recursive: true });
5905
6549
  if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
5906
6550
  const seedFiles = buildOnboardingSeedFiles();
5907
6551
  for (const { relativePath, content } of seedFiles) {
5908
- const fullPath = path12.join(agentDataDir, relativePath);
6552
+ const fullPath = path13.join(agentDataDir, relativePath);
5909
6553
  try {
5910
6554
  await access(fullPath);
5911
6555
  } catch {
5912
- await mkdir(path12.dirname(fullPath), { recursive: true });
6556
+ await mkdir(path13.dirname(fullPath), { recursive: true });
5913
6557
  await writeFile(fullPath, content);
5914
6558
  }
5915
6559
  }
@@ -6098,8 +6742,24 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6098
6742
  proc.stderr?.on("data", (chunk) => {
6099
6743
  const text = chunk.toString().trim();
6100
6744
  if (!text) return;
6101
- if (/Reconnecting\.\.\.|Falling back from WebSockets/i.test(text)) return;
6102
6745
  const current = this.agents.get(agentId);
6746
+ if (driver.id === "codex" && isCodexProviderReconnectLog(text)) {
6747
+ if (current) {
6748
+ current.recentStderr = pushRecentStderr(current.recentStderr, text);
6749
+ }
6750
+ this.recordDaemonTrace("daemon.agent.provider_reconnect", {
6751
+ agentId,
6752
+ launchId: current?.launchId || void 0,
6753
+ runtime: config.runtime,
6754
+ model: config.model
6755
+ });
6756
+ this.broadcastActivity(agentId, "working", "Codex reconnecting to provider\u2026", [
6757
+ { kind: "text", text }
6758
+ ]);
6759
+ logger.info(`[Agent ${agentId} stderr]: ${text}`);
6760
+ return;
6761
+ }
6762
+ if (driver.id === "codex" && isCodexBenignTransportLog(text)) return;
6103
6763
  if (current) {
6104
6764
  current.recentStderr = pushRecentStderr(current.recentStderr, text);
6105
6765
  }
@@ -6243,10 +6903,20 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6243
6903
  }
6244
6904
  this.broadcastActivity(agentId, "online", "Process idle");
6245
6905
  } else {
6246
- this.idleAgentConfigs.delete(agentId);
6247
6906
  const reason = formatCrashReason(finalCode, finalSignal, ap);
6248
- logger.error(`[Agent ${agentId}] Process crashed (${reason}) \u2014 marking inactive`);
6249
- this.sendAgentStatus(agentId, "inactive", ap.launchId);
6907
+ if (terminalFailureDetail && isProviderStreamFailureText(terminalFailureDetail)) {
6908
+ this.idleAgentConfigs.set(agentId, {
6909
+ config: { ...ap.config, sessionId: ap.sessionId },
6910
+ sessionId: ap.sessionId,
6911
+ launchId: ap.launchId
6912
+ });
6913
+ logger.warn(`[Agent ${agentId}] Recoverable provider stream failure (${reason}) \u2014 keeping agent wakeable`);
6914
+ this.sendAgentStatus(agentId, "active", ap.launchId);
6915
+ } else {
6916
+ this.idleAgentConfigs.delete(agentId);
6917
+ logger.error(`[Agent ${agentId}] Process crashed (${reason}) \u2014 marking inactive`);
6918
+ this.sendAgentStatus(agentId, "inactive", ap.launchId);
6919
+ }
6250
6920
  if (terminalFailureDetail) {
6251
6921
  this.broadcastActivity(
6252
6922
  agentId,
@@ -6760,7 +7430,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6760
7430
  return true;
6761
7431
  }
6762
7432
  async resetWorkspace(agentId) {
6763
- const agentDataDir = path12.join(this.dataDir, agentId);
7433
+ const agentDataDir = path13.join(this.dataDir, agentId);
6764
7434
  try {
6765
7435
  await rm2(agentDataDir, { recursive: true, force: true });
6766
7436
  logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
@@ -6797,7 +7467,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6797
7467
  return result;
6798
7468
  }
6799
7469
  buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
6800
- const workspacePath = path12.join(this.dataDir, agentId);
7470
+ const workspacePath = path13.join(this.dataDir, agentId);
6801
7471
  return {
6802
7472
  agentId,
6803
7473
  launchId,
@@ -7054,7 +7724,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7054
7724
  }
7055
7725
  // Workspace file browsing
7056
7726
  async getFileTree(agentId, dirPath) {
7057
- const agentDir = path12.join(this.dataDir, agentId);
7727
+ const agentDir = path13.join(this.dataDir, agentId);
7058
7728
  try {
7059
7729
  await stat2(agentDir);
7060
7730
  } catch {
@@ -7062,8 +7732,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7062
7732
  }
7063
7733
  let targetDir = agentDir;
7064
7734
  if (dirPath) {
7065
- const resolved = path12.resolve(agentDir, dirPath);
7066
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
7735
+ const resolved = path13.resolve(agentDir, dirPath);
7736
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
7067
7737
  return [];
7068
7738
  }
7069
7739
  targetDir = resolved;
@@ -7071,14 +7741,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7071
7741
  return this.listDirectoryChildren(targetDir, agentDir);
7072
7742
  }
7073
7743
  async readFile(agentId, filePath) {
7074
- const agentDir = path12.join(this.dataDir, agentId);
7075
- const resolved = path12.resolve(agentDir, filePath);
7076
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
7744
+ const agentDir = path13.join(this.dataDir, agentId);
7745
+ const resolved = path13.resolve(agentDir, filePath);
7746
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
7077
7747
  throw new Error("Access denied");
7078
7748
  }
7079
7749
  const info = await stat2(resolved);
7080
7750
  if (info.isDirectory()) throw new Error("Cannot read a directory");
7081
- const ext = path12.extname(resolved).toLowerCase();
7751
+ const ext = path13.extname(resolved).toLowerCase();
7082
7752
  if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
7083
7753
  if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
7084
7754
  const content = await readFile(resolved, "utf-8");
@@ -7113,13 +7783,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7113
7783
  const agent = this.agents.get(agentId);
7114
7784
  const runtime = runtimeHint || agent?.config.runtime || "claude";
7115
7785
  const home = os6.homedir();
7116
- const workspaceDir = path12.join(this.dataDir, agentId);
7786
+ const workspaceDir = path13.join(this.dataDir, agentId);
7117
7787
  const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
7118
7788
  const globalResults = await Promise.all(
7119
- paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
7789
+ paths.global.map((p) => this.scanSkillsDir(path13.join(home, p)))
7120
7790
  );
7121
7791
  const workspaceResults = await Promise.all(
7122
- paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
7792
+ paths.workspace.map((p) => this.scanSkillsDir(path13.join(workspaceDir, p)))
7123
7793
  );
7124
7794
  const dedup = (skills) => {
7125
7795
  const seen = /* @__PURE__ */ new Set();
@@ -7148,7 +7818,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7148
7818
  const skills = [];
7149
7819
  for (const entry of entries) {
7150
7820
  if (entry.isDirectory() || entry.isSymbolicLink()) {
7151
- const skillMd = path12.join(dir, entry.name, "SKILL.md");
7821
+ const skillMd = path13.join(dir, entry.name, "SKILL.md");
7152
7822
  try {
7153
7823
  const content = await readFile(skillMd, "utf-8");
7154
7824
  const skill = this.parseSkillMd(entry.name, content);
@@ -7159,7 +7829,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7159
7829
  } else if (entry.name.endsWith(".md")) {
7160
7830
  const cmdName = entry.name.replace(/\.md$/, "");
7161
7831
  try {
7162
- const content = await readFile(path12.join(dir, entry.name), "utf-8");
7832
+ const content = await readFile(path13.join(dir, entry.name), "utf-8");
7163
7833
  const skill = this.parseSkillMd(cmdName, content);
7164
7834
  skill.sourcePath = dir;
7165
7835
  skills.push(skill);
@@ -8125,8 +8795,8 @@ ${RESPONSE_TARGET_HINT}`);
8125
8795
  const nodes = [];
8126
8796
  for (const entry of entries) {
8127
8797
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
8128
- const fullPath = path12.join(dir, entry.name);
8129
- const relativePath = path12.relative(rootDir, fullPath);
8798
+ const fullPath = path13.join(dir, entry.name);
8799
+ const relativePath = path13.relative(rootDir, fullPath);
8130
8800
  let info;
8131
8801
  try {
8132
8802
  info = await stat2(fullPath);
@@ -8431,9 +9101,9 @@ var ReminderCache = class {
8431
9101
 
8432
9102
  // src/machineLock.ts
8433
9103
  import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
8434
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
9104
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync6, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
8435
9105
  import os7 from "os";
8436
- import path13 from "path";
9106
+ import path14 from "path";
8437
9107
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
8438
9108
  var DaemonMachineLockConflictError = class extends Error {
8439
9109
  code = "DAEMON_MACHINE_LOCK_HELD";
@@ -8455,11 +9125,11 @@ function resolveDefaultMachineStateRoot() {
8455
9125
  return resolveSlockHomePath("machines");
8456
9126
  }
8457
9127
  function ownerPath(lockDir) {
8458
- return path13.join(lockDir, "owner.json");
9128
+ return path14.join(lockDir, "owner.json");
8459
9129
  }
8460
9130
  function readOwner(lockDir) {
8461
9131
  try {
8462
- return JSON.parse(readFileSync5(ownerPath(lockDir), "utf8"));
9132
+ return JSON.parse(readFileSync6(ownerPath(lockDir), "utf8"));
8463
9133
  } catch {
8464
9134
  return null;
8465
9135
  }
@@ -8485,13 +9155,13 @@ function acquireDaemonMachineLock(options) {
8485
9155
  const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
8486
9156
  const fingerprint = apiKeyFingerprint(options.apiKey);
8487
9157
  const lockId = getDaemonMachineLockId(options.apiKey);
8488
- const machineDir = path13.join(rootDir, lockId);
8489
- const lockDir = path13.join(machineDir, "daemon.lock");
9158
+ const machineDir = path14.join(rootDir, lockId);
9159
+ const lockDir = path14.join(machineDir, "daemon.lock");
8490
9160
  const token = randomUUID2();
8491
- mkdirSync5(machineDir, { recursive: true });
9161
+ mkdirSync6(machineDir, { recursive: true });
8492
9162
  for (let attempt = 0; attempt < 2; attempt += 1) {
8493
9163
  try {
8494
- mkdirSync5(lockDir);
9164
+ mkdirSync6(lockDir);
8495
9165
  const owner = {
8496
9166
  pid: process.pid,
8497
9167
  token,
@@ -8501,7 +9171,7 @@ function acquireDaemonMachineLock(options) {
8501
9171
  apiKeyFingerprint: fingerprint.slice(0, 16)
8502
9172
  };
8503
9173
  try {
8504
- writeFileSync8(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
9174
+ writeFileSync9(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
8505
9175
  `, { mode: 384 });
8506
9176
  } catch (err) {
8507
9177
  rmSync3(lockDir, { recursive: true, force: true });
@@ -8538,8 +9208,8 @@ function acquireDaemonMachineLock(options) {
8538
9208
  }
8539
9209
 
8540
9210
  // src/localTraceSink.ts
8541
- import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
8542
- import path14 from "path";
9211
+ import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync10 } from "fs";
9212
+ import path15 from "path";
8543
9213
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
8544
9214
  var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
8545
9215
  var DEFAULT_MAX_FILES = 8;
@@ -8575,7 +9245,7 @@ var LocalRotatingTraceSink = class {
8575
9245
  currentSize = 0;
8576
9246
  sequence = 0;
8577
9247
  constructor(options) {
8578
- this.traceDir = path14.join(options.machineDir, "traces");
9248
+ this.traceDir = path15.join(options.machineDir, "traces");
8579
9249
  this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
8580
9250
  const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
8581
9251
  const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
@@ -8601,15 +9271,15 @@ var LocalRotatingTraceSink = class {
8601
9271
  return this.currentFile;
8602
9272
  }
8603
9273
  ensureFile(nextBytes) {
8604
- mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
9274
+ mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
8605
9275
  const nowMs = this.nowMsProvider();
8606
9276
  const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
8607
9277
  if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
8608
- this.currentFile = path14.join(
9278
+ this.currentFile = path15.join(
8609
9279
  this.traceDir,
8610
9280
  `daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
8611
9281
  );
8612
- writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
9282
+ writeFileSync10(this.currentFile, "", { flag: "a", mode: 384 });
8613
9283
  this.currentSize = statSync4(this.currentFile).size;
8614
9284
  this.currentFileOpenedAtMs = nowMs;
8615
9285
  this.pruneOldFiles();
@@ -8620,7 +9290,7 @@ var LocalRotatingTraceSink = class {
8620
9290
  const excess = files.length - this.maxFiles;
8621
9291
  if (excess <= 0) return;
8622
9292
  for (const file of files.slice(0, excess)) {
8623
- rmSync4(path14.join(this.traceDir, file), { force: true });
9293
+ rmSync4(path15.join(this.traceDir, file), { force: true });
8624
9294
  }
8625
9295
  }
8626
9296
  };
@@ -8707,11 +9377,11 @@ function isDiagnosticErrorAttr(key) {
8707
9377
  import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
8708
9378
  import { gzipSync } from "zlib";
8709
9379
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
8710
- import path15 from "path";
9380
+ import path16 from "path";
8711
9381
 
8712
9382
  // src/directUploadCapability.ts
8713
- function joinUrl(base, path17) {
8714
- return `${base.replace(/\/+$/, "")}${path17}`;
9383
+ function joinUrl(base, path18) {
9384
+ return `${base.replace(/\/+$/, "")}${path18}`;
8715
9385
  }
8716
9386
  function jsonHeaders(apiKey) {
8717
9387
  return {
@@ -8930,7 +9600,7 @@ var DaemonTraceBundleUploader = class {
8930
9600
  }, nextMs);
8931
9601
  }
8932
9602
  async findUploadCandidates() {
8933
- const traceDir = path15.join(this.options.machineDir, "traces");
9603
+ const traceDir = path16.join(this.options.machineDir, "traces");
8934
9604
  let names;
8935
9605
  try {
8936
9606
  names = await readdir3(traceDir);
@@ -8942,8 +9612,8 @@ var DaemonTraceBundleUploader = class {
8942
9612
  const currentFile = this.options.currentFileProvider?.();
8943
9613
  const candidates = [];
8944
9614
  for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
8945
- const file = path15.join(traceDir, name);
8946
- if (currentFile && path15.resolve(file) === path15.resolve(currentFile)) continue;
9615
+ const file = path16.join(traceDir, name);
9616
+ if (currentFile && path16.resolve(file) === path16.resolve(currentFile)) continue;
8947
9617
  if (await this.isUploaded(file)) continue;
8948
9618
  try {
8949
9619
  const info = await stat3(file);
@@ -9017,8 +9687,8 @@ var DaemonTraceBundleUploader = class {
9017
9687
  }
9018
9688
  }
9019
9689
  uploadStatePath(file) {
9020
- const stateDir = path15.join(this.options.machineDir, "trace-uploads");
9021
- return path15.join(stateDir, `${path15.basename(file)}.uploaded.json`);
9690
+ const stateDir = path16.join(this.options.machineDir, "trace-uploads");
9691
+ return path16.join(stateDir, `${path16.basename(file)}.uploaded.json`);
9022
9692
  }
9023
9693
  async isUploaded(file) {
9024
9694
  try {
@@ -9030,9 +9700,9 @@ var DaemonTraceBundleUploader = class {
9030
9700
  }
9031
9701
  async markUploaded(file, metadata) {
9032
9702
  const stateFile = this.uploadStatePath(file);
9033
- await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
9703
+ await mkdir2(path16.dirname(stateFile), { recursive: true, mode: 448 });
9034
9704
  await writeFile2(stateFile, `${JSON.stringify({
9035
- file: path15.basename(file),
9705
+ file: path16.basename(file),
9036
9706
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
9037
9707
  ...metadata
9038
9708
  }, null, 2)}
@@ -9054,7 +9724,7 @@ var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
9054
9724
  var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels"];
9055
9725
  var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
9056
9726
  var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
9057
- var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
9727
+ var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
9058
9728
  var RunnerCredentialMintError2 = class extends Error {
9059
9729
  code;
9060
9730
  retryable;
@@ -9090,9 +9760,9 @@ function runnerCredentialErrorDetail2(error) {
9090
9760
  async function waitForRunnerCredentialRetry2() {
9091
9761
  await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
9092
9762
  }
9093
- function parseDaemonCliArgs(args) {
9763
+ function parseDaemonCliArgs(args, env = {}) {
9094
9764
  let serverUrl = "";
9095
- let apiKey = "";
9765
+ let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
9096
9766
  for (let i = 0; i < args.length; i++) {
9097
9767
  if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
9098
9768
  if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
@@ -9102,30 +9772,30 @@ function parseDaemonCliArgs(args) {
9102
9772
  }
9103
9773
  function readDaemonVersion(moduleUrl = import.meta.url) {
9104
9774
  try {
9105
- const require2 = createRequire(moduleUrl);
9775
+ const require2 = createRequire2(moduleUrl);
9106
9776
  return require2("../package.json").version;
9107
9777
  } catch {
9108
9778
  return "0.0.0-dev";
9109
9779
  }
9110
9780
  }
9111
9781
  function resolveChatBridgePath(moduleUrl = import.meta.url) {
9112
- const dirname = path16.dirname(fileURLToPath(moduleUrl));
9113
- const jsPath = path16.resolve(dirname, "chat-bridge.js");
9782
+ const dirname = path17.dirname(fileURLToPath2(moduleUrl));
9783
+ const jsPath = path17.resolve(dirname, "chat-bridge.js");
9114
9784
  try {
9115
9785
  accessSync(jsPath);
9116
9786
  return jsPath;
9117
9787
  } catch {
9118
- return path16.resolve(dirname, "chat-bridge.ts");
9788
+ return path17.resolve(dirname, "chat-bridge.ts");
9119
9789
  }
9120
9790
  }
9121
9791
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
9122
- const thisDir = path16.dirname(fileURLToPath(moduleUrl));
9123
- const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
9792
+ const thisDir = path17.dirname(fileURLToPath2(moduleUrl));
9793
+ const bundledDistPath = path17.resolve(thisDir, "cli", "index.js");
9124
9794
  try {
9125
9795
  accessSync(bundledDistPath);
9126
9796
  return bundledDistPath;
9127
9797
  } catch {
9128
- const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
9798
+ const workspaceDistPath = path17.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
9129
9799
  accessSync(workspaceDistPath);
9130
9800
  return workspaceDistPath;
9131
9801
  }
@@ -9304,7 +9974,7 @@ var DaemonCore = class {
9304
9974
  }
9305
9975
  resolveMachineStateRoot() {
9306
9976
  if (this.options.machineStateDir) return this.options.machineStateDir;
9307
- if (this.options.dataDir) return path16.join(path16.dirname(this.options.dataDir), "machines");
9977
+ if (this.options.dataDir) return path17.join(path17.dirname(this.options.dataDir), "machines");
9308
9978
  return resolveDefaultMachineStateRoot();
9309
9979
  }
9310
9980
  shouldEnableLocalTrace() {
@@ -9806,6 +10476,8 @@ var DaemonCore = class {
9806
10476
  };
9807
10477
 
9808
10478
  export {
10479
+ DAEMON_API_KEY_ENV,
10480
+ scrubDaemonAuthEnv,
9809
10481
  resolveWorkspaceDirectoryPath,
9810
10482
  scanWorkspaceDirectories,
9811
10483
  deleteWorkspaceDirectory,