@slock-ai/daemon 0.52.2 → 0.53.0-play.20260524064439

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
@@ -872,13 +874,15 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
872
874
  17. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
873
875
  18. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
874
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.
875
- 20. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
876
- 21. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
877
- 22. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
878
- 23. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
879
- 24. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
880
- 25. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
881
- 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\`).
882
886
 
883
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:
884
888
  - failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
@@ -913,20 +917,23 @@ Use reminders for follow-up that depends on future state you cannot resolve now,
913
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.
914
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.
915
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";
916
921
  const sendingMessagesSection = isCli ? `### Sending messages
917
922
 
918
- - **Reply to a channel**: \`slock message send --target "#channel-name" <<'EOF'\` followed by the message body and \`EOF\`
919
- - **Reply to a DM**: \`slock message send --target dm:@peer-name <<'EOF'\` followed by the message body and \`EOF\`
920
- - **Reply in a thread**: \`slock message send --target "#channel:shortid" <<'EOF'\` followed by the message body and \`EOF\`
921
- - **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}\`
922
927
 
923
928
  Message content is always read from stdin. Use a heredoc so quotes, backticks, code blocks, and newlines are not interpreted by the shell:
924
929
  \`\`\`bash
925
- slock message send --target "#channel-name" <<'EOF'
930
+ slock message send --target "#channel-name" <<'${messageHeredocDelimiter}'
926
931
  Long message with "quotes", $vars, \`backticks\`, and code blocks.
927
- EOF
932
+ ${messageHeredocDelimiter}
928
933
  \`\`\`
929
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
+
930
937
  If Slock says a message was not sent and was saved as a draft, choose one path:
931
938
  - To update the draft, use a normal \`slock message send --target <target>\` with the revised content.
932
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.
@@ -945,7 +952,7 @@ Threads are sub-conversations attached to a specific message. They let you discu
945
952
 
946
953
  - **Thread targets** have a colon and short ID suffix: \`#general:a1b2c3d4\` (thread in #general) or \`dm:@richard:x9y8z7a0\` (thread in a DM).
947
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.
948
- - **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.
949
956
  - When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
950
957
  - You can read thread history: \`slock message read --channel "#general:a1b2c3d4"\`
951
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.
@@ -979,6 +986,11 @@ Each channel has a **name** and optionally a **description** that define its pur
979
986
  - **Reply in context** \u2014 always respond in the channel/thread the message came from.
980
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.
981
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. If the CLI reports that the \`integration\` command is unknown, the local daemon/CLI is too old for Slock Agent Login; report that the machine must be upgraded/restarted instead of calling internal HTTP endpoints yourself. 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, call internal Slock integration endpoints directly, 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.`;
982
994
  const readingHistorySection = isCli ? `### Reading history
983
995
 
984
996
  \`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
@@ -1015,7 +1027,7 @@ Only top-level channel / DM messages can become tasks. Messages inside threads a
1015
1027
  **Workflow:**
1016
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)
1017
1029
  2. If the claim fails, someone else is working on it \u2014 move on to another task
1018
- 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}\`
1019
1031
  4. When done, set status to \`in_review\` so a human can validate via \`slock task update\`
1020
1032
  5. After approval (e.g. "looks good", "merge it"), set status to \`done\`
1021
1033
 
@@ -1150,6 +1162,8 @@ ${discoverySection}
1150
1162
 
1151
1163
  ${channelAwarenessSection}
1152
1164
 
1165
+ ${thirdPartyIntegrationsSection}
1166
+
1153
1167
  ${readingHistorySection}
1154
1168
 
1155
1169
  ${historicalReferenceSection}
@@ -1349,6 +1363,19 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
1349
1363
  return candidates.filter((candidate) => existsSync(candidate.path));
1350
1364
  }
1351
1365
 
1366
+ // src/authEnv.ts
1367
+ var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
1368
+ var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
1369
+ function scrubDaemonAuthEnv(env) {
1370
+ delete env[DAEMON_API_KEY_ENV];
1371
+ return env;
1372
+ }
1373
+ function scrubDaemonChildEnv(env) {
1374
+ delete env[DAEMON_API_KEY_ENV];
1375
+ delete env[SLOCK_AGENT_TOKEN_ENV];
1376
+ return env;
1377
+ }
1378
+
1352
1379
  // src/agentCredentialProxy.ts
1353
1380
  import { randomBytes } from "crypto";
1354
1381
  import http from "http";
@@ -1872,8 +1899,51 @@ function unregisterAgentCredentialProxyForLaunch(input) {
1872
1899
 
1873
1900
  // src/drivers/cliTransport.ts
1874
1901
  var shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
1902
+ var powershellSingleQuote = (value) => `'${value.replace(/'/g, "''")}'`;
1875
1903
  var DEFAULT_ACTIVE_CAPABILITIES = "send,read,mentions,tasks,reactions,server,channels";
1876
1904
  var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
1905
+ var cachedOpencliBinPath;
1906
+ function resolveOpencliBinPath() {
1907
+ if (cachedOpencliBinPath !== void 0) return cachedOpencliBinPath;
1908
+ try {
1909
+ const require2 = createRequire(import.meta.url);
1910
+ const mainPath = require2.resolve("@jackwener/opencli");
1911
+ let dir = path2.dirname(mainPath);
1912
+ const root = path2.parse(dir).root;
1913
+ while (dir && dir !== root) {
1914
+ const candidate = path2.join(dir, "package.json");
1915
+ try {
1916
+ const pkg = JSON.parse(readFileSync(candidate, "utf8"));
1917
+ if (pkg.name === "@jackwener/opencli") {
1918
+ const binEntry = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.opencli;
1919
+ if (!binEntry) {
1920
+ cachedOpencliBinPath = null;
1921
+ return null;
1922
+ }
1923
+ cachedOpencliBinPath = path2.resolve(dir, binEntry);
1924
+ return cachedOpencliBinPath;
1925
+ }
1926
+ } catch {
1927
+ }
1928
+ const parent = path2.dirname(dir);
1929
+ if (parent === dir) break;
1930
+ dir = parent;
1931
+ }
1932
+ cachedOpencliBinPath = null;
1933
+ return null;
1934
+ } catch {
1935
+ cachedOpencliBinPath = null;
1936
+ return null;
1937
+ }
1938
+ }
1939
+ function windowsUtf8Env() {
1940
+ return {
1941
+ PYTHONIOENCODING: "utf-8",
1942
+ PYTHONUTF8: "1",
1943
+ LANG: "C.UTF-8",
1944
+ LC_ALL: "C.UTF-8"
1945
+ };
1946
+ }
1877
1947
  function runtimeContextEnv(config) {
1878
1948
  const ctx = config.runtimeContext;
1879
1949
  if (!ctx) return {};
@@ -1932,10 +2002,70 @@ ${posixCredentialPrefix}exec ${shellSingleQuote(process.execPath)} ${shellSingle
1932
2002
  set "SLOCK_AGENT_PROXY_TOKEN_FILE=${agentCredentialProxyTokenFile}"\r
1933
2003
  set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
1934
2004
  ` : "";
1935
- const cmdBody = `@echo off\r
1936
- ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
1937
- `;
2005
+ const cmdBody = [
2006
+ "@echo off",
2007
+ "set PYTHONIOENCODING=utf-8",
2008
+ "set PYTHONUTF8=1",
2009
+ "set LANG=C.UTF-8",
2010
+ "set LC_ALL=C.UTF-8",
2011
+ "chcp 65001 >NUL 2>NUL",
2012
+ cmdCredentialLine.trimEnd(),
2013
+ `"${process.execPath}" "${ctx.slockCliPath}" %*`,
2014
+ ""
2015
+ ].filter((line) => line.length > 0).join("\r\n") + "\r\n";
1938
2016
  writeFileSync(cmdWrapper, cmdBody);
2017
+ const psWrapper = path2.join(slockDir, "slock.ps1");
2018
+ const psCredentialLines = agentCredentialProxy ? [
2019
+ `$env:SLOCK_AGENT_PROXY_URL=${powershellSingleQuote(agentCredentialProxy.proxyUrl)}`,
2020
+ `$env:SLOCK_AGENT_PROXY_TOKEN_FILE=${powershellSingleQuote(agentCredentialProxyTokenFile)}`,
2021
+ `$env:SLOCK_AGENT_ACTIVE_CAPABILITIES=${powershellSingleQuote(DEFAULT_ACTIVE_CAPABILITIES)}`
2022
+ ] : [];
2023
+ const psBody = [
2024
+ "$ErrorActionPreference = 'Stop'",
2025
+ "$utf8NoBom = [System.Text.UTF8Encoding]::new($false)",
2026
+ "[Console]::OutputEncoding = $utf8NoBom",
2027
+ "$OutputEncoding = $utf8NoBom",
2028
+ "$env:PYTHONIOENCODING = 'utf-8'",
2029
+ "$env:PYTHONUTF8 = '1'",
2030
+ "$env:LANG = 'C.UTF-8'",
2031
+ "$env:LC_ALL = 'C.UTF-8'",
2032
+ ...psCredentialLines,
2033
+ `$node = ${powershellSingleQuote(process.execPath)}`,
2034
+ `$cli = ${powershellSingleQuote(ctx.slockCliPath)}`,
2035
+ "if ($MyInvocation.ExpectingInput) {",
2036
+ " $input | & $node $cli @args",
2037
+ "} else {",
2038
+ " & $node $cli @args",
2039
+ "}",
2040
+ "exit $LASTEXITCODE",
2041
+ ""
2042
+ ].join("\r\n");
2043
+ writeFileSync(psWrapper, psBody);
2044
+ }
2045
+ const opencliBinPath = resolveOpencliBinPath();
2046
+ if (opencliBinPath) {
2047
+ const opencliPosixWrapper = path2.join(slockDir, "opencli");
2048
+ writeFileSync(
2049
+ opencliPosixWrapper,
2050
+ `#!/usr/bin/env bash
2051
+ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(opencliBinPath)} "$@"
2052
+ `,
2053
+ { mode: 493 }
2054
+ );
2055
+ if (platform === "win32") {
2056
+ const opencliCmdWrapper = path2.join(slockDir, "opencli.cmd");
2057
+ const opencliCmdBody = [
2058
+ "@echo off",
2059
+ "set PYTHONIOENCODING=utf-8",
2060
+ "set PYTHONUTF8=1",
2061
+ "set LANG=C.UTF-8",
2062
+ "set LC_ALL=C.UTF-8",
2063
+ "chcp 65001 >NUL 2>NUL",
2064
+ `"${process.execPath}" "${opencliBinPath}" %*`,
2065
+ ""
2066
+ ].join("\r\n") + "\r\n";
2067
+ writeFileSync(opencliCmdWrapper, opencliCmdBody);
2068
+ }
1939
2069
  }
1940
2070
  const wrapperPath = platform === "win32" ? path2.join(slockDir, "slock.cmd") : posixWrapper;
1941
2071
  const spawnEnv = {
@@ -1943,6 +2073,7 @@ ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
1943
2073
  FORCE_COLOR: "0",
1944
2074
  ...ctx.config.envVars || {},
1945
2075
  ...extraEnv,
2076
+ ...platform === "win32" ? windowsUtf8Env() : {},
1946
2077
  ...runtimeContextEnv(ctx.config),
1947
2078
  [SLOCK_HOME_ENV]: slockHome,
1948
2079
  SLOCK_AGENT_ID: ctx.agentId,
@@ -1951,7 +2082,7 @@ ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
1951
2082
  ...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
1952
2083
  PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
1953
2084
  };
1954
- delete spawnEnv.SLOCK_AGENT_TOKEN;
2085
+ scrubDaemonChildEnv(spawnEnv);
1955
2086
  delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY;
1956
2087
  delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY_FILE;
1957
2088
  delete spawnEnv.SLOCK_AGENT_PROXY_URL;
@@ -1998,7 +2129,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn) {
1998
2129
  }
1999
2130
  function resolveCommandOnPath(command, deps = {}) {
2000
2131
  const platform = deps.platform ?? process.platform;
2001
- const env = deps.env ?? process.env;
2132
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
2002
2133
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
2003
2134
  if (platform === "win32") {
2004
2135
  return resolveCommandOnWindows(command, env, execFileSyncFn);
@@ -2023,7 +2154,7 @@ function firstExistingPath(candidates, deps = {}) {
2023
2154
  return null;
2024
2155
  }
2025
2156
  function readCommandVersion(command, args = [], deps = {}) {
2026
- const env = deps.env ?? process.env;
2157
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
2027
2158
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
2028
2159
  try {
2029
2160
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -2085,7 +2216,7 @@ function expandClaudeMcpConfigVariables(raw, vars) {
2085
2216
  function readClaudeMcpServers(configPath, vars = {}) {
2086
2217
  try {
2087
2218
  const parsed = JSON.parse(
2088
- expandClaudeMcpConfigVariables(readFileSync(configPath, "utf8"), vars)
2219
+ expandClaudeMcpConfigVariables(readFileSync2(configPath, "utf8"), vars)
2089
2220
  );
2090
2221
  if (!isRecord(parsed) || !isRecord(parsed.mcpServers)) return null;
2091
2222
  return parsed.mcpServers;
@@ -2387,7 +2518,7 @@ var ClaudeDriver = class {
2387
2518
 
2388
2519
  // src/drivers/codex.ts
2389
2520
  import { spawn as spawn2, execFileSync as execFileSync2, execSync } from "child_process";
2390
- import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
2521
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
2391
2522
  import os3 from "os";
2392
2523
  import path5 from "path";
2393
2524
  function getCodexNotificationErrorMessage(params) {
@@ -2925,7 +3056,7 @@ function detectCodexModels(home = os3.homedir()) {
2925
3056
  const configPath = path5.join(home, ".codex", "config.toml");
2926
3057
  let models = [];
2927
3058
  try {
2928
- const raw = readFileSync2(cachePath, "utf8");
3059
+ const raw = readFileSync3(cachePath, "utf8");
2929
3060
  const parsed = JSON.parse(raw);
2930
3061
  const entries = Array.isArray(parsed?.models) ? parsed.models : [];
2931
3062
  for (const entry of entries) {
@@ -2942,7 +3073,7 @@ function detectCodexModels(home = os3.homedir()) {
2942
3073
  if (models.length === 0) return null;
2943
3074
  let defaultModel;
2944
3075
  try {
2945
- const raw = readFileSync2(configPath, "utf8");
3076
+ const raw = readFileSync3(configPath, "utf8");
2946
3077
  const match = raw.match(/^\s*model\s*=\s*"([^"]+)"/m);
2947
3078
  if (match) defaultModel = match[1];
2948
3079
  } catch {
@@ -3319,7 +3450,7 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
3319
3450
  }
3320
3451
  function runCursorModelsCommand() {
3321
3452
  return spawnSync("cursor-agent", ["models"], {
3322
- env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
3453
+ env: scrubDaemonChildEnv({ ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" }),
3323
3454
  encoding: "utf8",
3324
3455
  timeout: 5e3
3325
3456
  });
@@ -3366,7 +3497,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
3366
3497
  }
3367
3498
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
3368
3499
  const existsSyncFn = deps.existsSyncFn ?? existsSync6;
3369
- const env = deps.env ?? process.env;
3500
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
3370
3501
  const winPath = path8.win32;
3371
3502
  let geminiEntry = null;
3372
3503
  try {
@@ -3540,13 +3671,16 @@ var GeminiDriver = class {
3540
3671
  // src/drivers/kimi.ts
3541
3672
  import { randomUUID } from "crypto";
3542
3673
  import { spawn as spawn6 } from "child_process";
3543
- import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
3674
+ import { chmodSync, existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "fs";
3544
3675
  import os4 from "os";
3545
3676
  import path9 from "path";
3546
3677
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
3547
3678
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
3548
3679
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
3549
3680
  var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
3681
+ var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
3682
+ var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
3683
+ var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
3550
3684
  function parseToolArguments(raw) {
3551
3685
  if (typeof raw !== "string") return raw;
3552
3686
  try {
@@ -3555,6 +3689,73 @@ function parseToolArguments(raw) {
3555
3689
  return raw;
3556
3690
  }
3557
3691
  }
3692
+ function readKimiConfigSource(home = os4.homedir(), env = process.env) {
3693
+ const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
3694
+ if (inlineConfig && inlineConfig.trim()) {
3695
+ return {
3696
+ raw: inlineConfig,
3697
+ explicitPath: null,
3698
+ sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
3699
+ };
3700
+ }
3701
+ const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
3702
+ const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
3703
+ try {
3704
+ return {
3705
+ raw: readFileSync4(configPath, "utf8"),
3706
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
3707
+ sourcePath: configPath
3708
+ };
3709
+ } catch {
3710
+ return {
3711
+ raw: null,
3712
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
3713
+ sourcePath: configPath
3714
+ };
3715
+ }
3716
+ }
3717
+ function buildKimiSpawnEnv(env = process.env) {
3718
+ const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
3719
+ delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
3720
+ delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
3721
+ return scrubDaemonChildEnv(spawnEnv);
3722
+ }
3723
+ function buildKimiEffectiveEnv(ctx, overrideEnv) {
3724
+ return {
3725
+ ...process.env,
3726
+ ...ctx.config.envVars || {},
3727
+ ...overrideEnv || {}
3728
+ };
3729
+ }
3730
+ function buildKimiLaunchOptions(ctx, opts = {}) {
3731
+ const env = buildKimiEffectiveEnv(ctx, opts.env);
3732
+ const source = readKimiConfigSource(opts.home ?? os4.homedir(), env);
3733
+ const args = [];
3734
+ let configFilePath = null;
3735
+ let configContent = null;
3736
+ if (source.explicitPath) {
3737
+ configFilePath = source.explicitPath;
3738
+ } else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
3739
+ configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
3740
+ configContent = source.raw;
3741
+ if (opts.writeGeneratedConfig !== false) {
3742
+ writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
3743
+ chmodSync(configFilePath, 384);
3744
+ }
3745
+ }
3746
+ if (configFilePath) {
3747
+ args.push("--config-file", configFilePath);
3748
+ }
3749
+ if (ctx.config.model && ctx.config.model !== "default") {
3750
+ args.push("--model", ctx.config.model);
3751
+ }
3752
+ return {
3753
+ args,
3754
+ env: buildKimiSpawnEnv(env),
3755
+ configFilePath,
3756
+ configContent
3757
+ };
3758
+ }
3558
3759
  function resolveKimiSpawn(commandArgs, deps = {}) {
3559
3760
  return {
3560
3761
  command: resolveCommandOnPath("kimi", deps) ?? "kimi",
@@ -3578,7 +3779,25 @@ var KimiDriver = class {
3578
3779
  };
3579
3780
  model = {
3580
3781
  detectedModelsVerifiedAs: "launchable",
3581
- toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
3782
+ toLaunchSpec: (modelId, ctx, opts) => {
3783
+ if (!ctx) return { args: ["--model", modelId] };
3784
+ const launchCtx = {
3785
+ ...ctx,
3786
+ config: {
3787
+ ...ctx.config,
3788
+ model: modelId
3789
+ }
3790
+ };
3791
+ const launch = buildKimiLaunchOptions(launchCtx, {
3792
+ home: opts?.home,
3793
+ writeGeneratedConfig: false
3794
+ });
3795
+ return {
3796
+ args: launch.args,
3797
+ env: launch.env,
3798
+ configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
3799
+ };
3800
+ }
3582
3801
  };
3583
3802
  supportsStdinNotification = true;
3584
3803
  mcpToolPrefix = "";
@@ -3632,6 +3851,7 @@ var KimiDriver = class {
3632
3851
  }
3633
3852
  }
3634
3853
  }), "utf8");
3854
+ const launch = buildKimiLaunchOptions(ctx);
3635
3855
  const args = [
3636
3856
  "--wire",
3637
3857
  "--yolo",
@@ -3640,14 +3860,15 @@ var KimiDriver = class {
3640
3860
  "--mcp-config-file",
3641
3861
  mcpConfigPath,
3642
3862
  "--session",
3643
- this.sessionId
3863
+ this.sessionId,
3864
+ ...launch.args
3644
3865
  ];
3645
3866
  if (ctx.config.model && ctx.config.model !== "default") {
3646
3867
  args.push("--model", ctx.config.model);
3647
3868
  }
3648
3869
  const spawnEnv = prepareCliTransport(ctx, { NO_COLOR: "1" }).spawnEnv;
3649
- const launch = resolveKimiSpawn(args);
3650
- const proc = spawn6(launch.command, launch.args, {
3870
+ const spawnTarget = resolveKimiSpawn(args);
3871
+ const proc = spawn6(spawnTarget.command, spawnTarget.args, {
3651
3872
  cwd: ctx.workingDirectory,
3652
3873
  stdio: ["pipe", "pipe", "pipe"],
3653
3874
  env: spawnEnv,
@@ -3655,7 +3876,7 @@ var KimiDriver = class {
3655
3876
  // and has an 8191-character command-line limit. Kimi's official
3656
3877
  // installer/uv entrypoint is an executable, so launch it directly and
3657
3878
  // keep prompts on stdin / files instead of routing through cmd.exe.
3658
- shell: launch.shell
3879
+ shell: spawnTarget.shell
3659
3880
  });
3660
3881
  proc.stdin?.write(JSON.stringify({
3661
3882
  jsonrpc: "2.0",
@@ -3771,14 +3992,9 @@ var KimiDriver = class {
3771
3992
  return detectKimiModels();
3772
3993
  }
3773
3994
  };
3774
- function detectKimiModels(home = os4.homedir()) {
3775
- const configPath = path9.join(home, ".kimi", "config.toml");
3776
- let raw;
3777
- try {
3778
- raw = readFileSync3(configPath, "utf8");
3779
- } catch {
3780
- return null;
3781
- }
3995
+ function detectKimiModels(home = os4.homedir(), opts = {}) {
3996
+ const raw = readKimiConfigSource(home, opts.env).raw;
3997
+ if (raw === null) return null;
3782
3998
  const models = [];
3783
3999
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
3784
4000
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -3799,7 +4015,7 @@ function detectKimiModels(home = os4.homedir()) {
3799
4015
 
3800
4016
  // src/drivers/opencode.ts
3801
4017
  import { spawn as spawn7, spawnSync as spawnSync2 } from "child_process";
3802
- import { existsSync as existsSync8, readFileSync as readFileSync4 } from "fs";
4018
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
3803
4019
  import os5 from "os";
3804
4020
  import path10 from "path";
3805
4021
  var CHAT_MCP_SERVER_NAME = "chat";
@@ -3851,7 +4067,7 @@ function parseUserOpenCodeConfig(ctx) {
3851
4067
  function readLocalOpenCodeConfig(home = os5.homedir()) {
3852
4068
  const configPath = path10.join(home, ".config", "opencode", "opencode.json");
3853
4069
  try {
3854
- return parseOpenCodeConfigContent(readFileSync4(configPath, "utf8"));
4070
+ return parseOpenCodeConfigContent(readFileSync5(configPath, "utf8"));
3855
4071
  } catch {
3856
4072
  }
3857
4073
  return {};
@@ -4041,7 +4257,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
4041
4257
  const platform = deps.platform ?? process.platform;
4042
4258
  const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
4043
4259
  const result = spawnSyncFn("opencode", ["models"], {
4044
- env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
4260
+ env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
4045
4261
  encoding: "utf8",
4046
4262
  timeout: 5e3,
4047
4263
  shell: platform === "win32"
@@ -4102,7 +4318,7 @@ function resolveWindowsOpenCodePackageEntry(commandPath, deps = {}) {
4102
4318
  }
4103
4319
  function extractWindowsShimTargets(commandPath, deps = {}) {
4104
4320
  if (!isWindowsCommandShim(commandPath)) return [];
4105
- const readFileSyncFn = deps.readFileSyncFn ?? readFileSync4;
4321
+ const readFileSyncFn = deps.readFileSyncFn ?? readFileSync5;
4106
4322
  const commandDir = path10.win32.dirname(commandPath);
4107
4323
  let raw;
4108
4324
  try {
@@ -4302,6 +4518,297 @@ var OpenCodeDriver = class {
4302
4518
  }
4303
4519
  };
4304
4520
 
4521
+ // src/drivers/pi.ts
4522
+ import { spawn as spawn8 } from "child_process";
4523
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
4524
+ import path11 from "path";
4525
+ import { fileURLToPath } from "url";
4526
+ import { getAgentDir, VERSION as PI_SDK_VERSION } from "@earendil-works/pi-coding-agent";
4527
+ var CHAT_MCP_TOOL_PREFIX2 = "chat_";
4528
+ var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
4529
+ var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
4530
+ var MIN_SUPPORTED_PI_VERSION = "0.74.0";
4531
+ function parseSemver2(version) {
4532
+ const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
4533
+ if (!match) return null;
4534
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
4535
+ }
4536
+ function isSupportedPiVersion(version) {
4537
+ if (!version) return true;
4538
+ const actual = parseSemver2(version);
4539
+ const minimum = parseSemver2(MIN_SUPPORTED_PI_VERSION);
4540
+ if (!actual || !minimum) return true;
4541
+ for (let i = 0; i < 3; i += 1) {
4542
+ if (actual[i] > minimum[i]) return true;
4543
+ if (actual[i] < minimum[i]) return false;
4544
+ }
4545
+ return true;
4546
+ }
4547
+ function unsupportedPiVersionMessage(version) {
4548
+ if (!version || isSupportedPiVersion(version)) return null;
4549
+ 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.`;
4550
+ }
4551
+ function probePi(version = PI_SDK_VERSION) {
4552
+ const unsupportedMessage = unsupportedPiVersionMessage(version);
4553
+ if (unsupportedMessage) {
4554
+ return {
4555
+ available: false,
4556
+ version: `${version} (requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION})`
4557
+ };
4558
+ }
4559
+ return { available: true, version };
4560
+ }
4561
+ function resolvePiSdkRunnerPath(moduleUrl = import.meta.url) {
4562
+ const moduleDir = path11.dirname(fileURLToPath(moduleUrl));
4563
+ const sourceSibling = path11.join(moduleDir, "piSdkRunner.ts");
4564
+ if (existsSync9(sourceSibling)) return sourceSibling;
4565
+ const bundledEntry = path11.join(moduleDir, "drivers", "piSdkRunner.js");
4566
+ if (existsSync9(bundledEntry)) return bundledEntry;
4567
+ return path11.join(moduleDir, "piSdkRunner.js");
4568
+ }
4569
+ function buildPiSdkNodeArgs(runnerPath = resolvePiSdkRunnerPath()) {
4570
+ if (runnerPath.endsWith(".ts")) {
4571
+ return [...process.execArgv, runnerPath];
4572
+ }
4573
+ return [runnerPath];
4574
+ }
4575
+ function buildPiLaunchOptions(ctx, opts = {}) {
4576
+ const command = opts.command ?? process.execPath;
4577
+ const piDir = path11.join(ctx.workingDirectory, ".slock", "pi");
4578
+ const sessionDir = path11.join(piDir, "sessions");
4579
+ mkdirSync4(sessionDir, { recursive: true });
4580
+ const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
4581
+ const runnerPath = opts.runnerPath ?? resolvePiSdkRunnerPath();
4582
+ const agentDir = opts.agentDir ?? getAgentDir();
4583
+ const runnerConfigPath = path11.join(
4584
+ piDir,
4585
+ `sdk-run-${(ctx.launchId || "launch").replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`
4586
+ );
4587
+ const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
4588
+ const runnerConfig = {
4589
+ cwd: ctx.workingDirectory,
4590
+ agentDir,
4591
+ sessionDir,
4592
+ sessionId: ctx.config.sessionId || null,
4593
+ standingPrompt: ctx.standingPrompt,
4594
+ prompt: turnPrompt,
4595
+ model: ctx.config.model && ctx.config.model !== "default" ? ctx.config.model : null
4596
+ };
4597
+ writeFileSync7(runnerConfigPath, `${JSON.stringify(runnerConfig)}
4598
+ `, { encoding: "utf8", mode: 384 });
4599
+ const args = [
4600
+ ...buildPiSdkNodeArgs(runnerPath),
4601
+ "--config",
4602
+ runnerConfigPath
4603
+ ];
4604
+ return {
4605
+ command,
4606
+ args,
4607
+ env: slock.spawnEnv,
4608
+ sessionDir,
4609
+ agentDir,
4610
+ runnerConfigPath,
4611
+ sdkVersion: PI_SDK_VERSION
4612
+ };
4613
+ }
4614
+ function isSystemFirstMessageTask2(message) {
4615
+ return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
4616
+ }
4617
+ function buildPiSystemPrompt(config) {
4618
+ return buildCliTransportSystemPrompt(config, {
4619
+ toolPrefix: CHAT_MCP_TOOL_PREFIX2,
4620
+ extraCriticalRules: [
4621
+ "- 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."
4622
+ ],
4623
+ postStartupNotes: [
4624
+ "**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."
4625
+ ],
4626
+ includeStdinNotificationSection: false,
4627
+ messageNotificationStyle: "poll"
4628
+ });
4629
+ }
4630
+ function contentText(content) {
4631
+ if (!content) return "";
4632
+ const chunks = [];
4633
+ for (const item of content) {
4634
+ if (item.type === "text" && typeof item.text === "string") {
4635
+ chunks.push(item.text);
4636
+ }
4637
+ }
4638
+ return chunks.join("");
4639
+ }
4640
+ function apiKeyErrorMessage(line) {
4641
+ const trimmed = line.trim();
4642
+ if (!trimmed) return null;
4643
+ if (/no api key found/i.test(trimmed)) return trimmed;
4644
+ if (/api key.+required/i.test(trimmed)) return trimmed;
4645
+ if (/no models available/i.test(trimmed)) return trimmed;
4646
+ return null;
4647
+ }
4648
+ var PiDriver = class {
4649
+ id = "pi";
4650
+ lifecycle = {
4651
+ kind: "per_turn",
4652
+ start: "defer_until_concrete_message",
4653
+ exit: "terminate_on_turn_end",
4654
+ inFlightWake: "coalesce_into_pending"
4655
+ };
4656
+ communication = {
4657
+ chat: "slock_cli",
4658
+ runtimeControl: "none"
4659
+ };
4660
+ session = {
4661
+ recovery: "resume_or_fresh"
4662
+ };
4663
+ model = {
4664
+ detectedModelsVerifiedAs: "launchable",
4665
+ toLaunchSpec: (modelId, ctx) => {
4666
+ if (!ctx) return modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] };
4667
+ const launchCtx = {
4668
+ ...ctx,
4669
+ config: {
4670
+ ...ctx.config,
4671
+ model: modelId
4672
+ }
4673
+ };
4674
+ const launch = buildPiLaunchOptions(launchCtx);
4675
+ return {
4676
+ args: launch.args,
4677
+ env: launch.env,
4678
+ configFiles: [launch.runnerConfigPath],
4679
+ params: {
4680
+ agentDir: launch.agentDir,
4681
+ sessionDir: launch.sessionDir,
4682
+ sdkVersion: launch.sdkVersion,
4683
+ resources: "extensions/skills/prompt-templates/themes/context-files disabled by Slock policy"
4684
+ }
4685
+ };
4686
+ }
4687
+ };
4688
+ supportsStdinNotification = false;
4689
+ mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
4690
+ busyDeliveryMode = "none";
4691
+ terminateProcessOnTurnEnd = true;
4692
+ deferSpawnUntilMessage = true;
4693
+ usesSlockCliForCommunication = true;
4694
+ sessionId = null;
4695
+ sessionAnnounced = false;
4696
+ apiKeyErrorAnnounced = false;
4697
+ turnEnded = false;
4698
+ assistantTextByMessageId = /* @__PURE__ */ new Map();
4699
+ shouldDeferWakeMessage(message) {
4700
+ return isSystemFirstMessageTask2(message);
4701
+ }
4702
+ probe() {
4703
+ return probePi();
4704
+ }
4705
+ async detectModels() {
4706
+ return null;
4707
+ }
4708
+ spawn(ctx) {
4709
+ this.sessionId = ctx.config.sessionId || null;
4710
+ this.sessionAnnounced = false;
4711
+ this.apiKeyErrorAnnounced = false;
4712
+ this.turnEnded = false;
4713
+ this.assistantTextByMessageId.clear();
4714
+ const unsupportedMessage = unsupportedPiVersionMessage(PI_SDK_VERSION);
4715
+ if (unsupportedMessage) throw new Error(unsupportedMessage);
4716
+ const launch = buildPiLaunchOptions(ctx);
4717
+ const proc = spawn8(launch.command, launch.args, {
4718
+ cwd: ctx.workingDirectory,
4719
+ stdio: ["pipe", "pipe", "pipe"],
4720
+ env: launch.env,
4721
+ shell: false
4722
+ });
4723
+ proc.stdin?.end();
4724
+ return { process: proc };
4725
+ }
4726
+ parseLine(line) {
4727
+ let event;
4728
+ try {
4729
+ event = JSON.parse(line);
4730
+ } catch {
4731
+ if (this.apiKeyErrorAnnounced) return [];
4732
+ const message = apiKeyErrorMessage(line);
4733
+ if (!message) return [];
4734
+ this.apiKeyErrorAnnounced = true;
4735
+ this.turnEnded = true;
4736
+ return [
4737
+ { kind: "error", message },
4738
+ { kind: "turn_end", sessionId: this.sessionId || void 0 }
4739
+ ];
4740
+ }
4741
+ const events = [];
4742
+ if (event.type === "session" && event.id) {
4743
+ this.sessionId = event.id;
4744
+ }
4745
+ if (!this.sessionAnnounced && this.sessionId) {
4746
+ events.push({ kind: "session_init", sessionId: this.sessionId });
4747
+ this.sessionAnnounced = true;
4748
+ }
4749
+ switch (event.type) {
4750
+ case "agent_start":
4751
+ case "turn_start":
4752
+ events.push({ kind: "thinking", text: "" });
4753
+ break;
4754
+ case "message_update":
4755
+ case "message_end":
4756
+ if (event.message?.role === "assistant") {
4757
+ const key = event.message.id || "current";
4758
+ const currentText = contentText(event.message.content);
4759
+ const previousText = this.assistantTextByMessageId.get(key) ?? "";
4760
+ if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
4761
+ events.push({ kind: "text", text: currentText.slice(previousText.length) });
4762
+ } else if (currentText && currentText !== previousText) {
4763
+ events.push({ kind: "text", text: currentText });
4764
+ }
4765
+ this.assistantTextByMessageId.set(key, currentText);
4766
+ if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
4767
+ events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
4768
+ }
4769
+ }
4770
+ break;
4771
+ case "tool_execution_start":
4772
+ events.push({
4773
+ kind: "tool_call",
4774
+ name: event.toolName || "unknown_tool",
4775
+ input: event.args
4776
+ });
4777
+ break;
4778
+ case "tool_execution_end":
4779
+ events.push({
4780
+ kind: "tool_output",
4781
+ name: event.toolName || "unknown_tool"
4782
+ });
4783
+ if (event.isError) {
4784
+ events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
4785
+ }
4786
+ break;
4787
+ case "compaction_start":
4788
+ events.push({ kind: "compaction_started" });
4789
+ break;
4790
+ case "compaction_end":
4791
+ events.push({ kind: "compaction_finished" });
4792
+ if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
4793
+ break;
4794
+ case "turn_end":
4795
+ case "agent_end":
4796
+ if (!this.turnEnded) {
4797
+ events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
4798
+ this.turnEnded = true;
4799
+ }
4800
+ break;
4801
+ }
4802
+ return events;
4803
+ }
4804
+ encodeStdinMessage(_text, _sessionId, _opts) {
4805
+ return null;
4806
+ }
4807
+ buildSystemPrompt(config, _agentId) {
4808
+ return buildPiSystemPrompt(config);
4809
+ }
4810
+ };
4811
+
4305
4812
  // src/drivers/index.ts
4306
4813
  var driverFactories = {
4307
4814
  claude: () => new ClaudeDriver(),
@@ -4310,7 +4817,8 @@ var driverFactories = {
4310
4817
  cursor: () => new CursorDriver(),
4311
4818
  gemini: () => new GeminiDriver(),
4312
4819
  kimi: () => new KimiDriver(),
4313
- opencode: () => new OpenCodeDriver()
4820
+ opencode: () => new OpenCodeDriver(),
4821
+ pi: () => new PiDriver()
4314
4822
  };
4315
4823
  function getDriver(runtimeId) {
4316
4824
  const createDriver = driverFactories[runtimeId];
@@ -4323,7 +4831,7 @@ function getDriver(runtimeId) {
4323
4831
 
4324
4832
  // src/workspaces.ts
4325
4833
  import { readdir, rm, stat } from "fs/promises";
4326
- import path11 from "path";
4834
+ import path12 from "path";
4327
4835
  function isValidWorkspaceDirectoryName(directoryName) {
4328
4836
  return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
4329
4837
  }
@@ -4331,7 +4839,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
4331
4839
  if (!isValidWorkspaceDirectoryName(directoryName)) {
4332
4840
  return null;
4333
4841
  }
4334
- return path11.join(dataDir, directoryName);
4842
+ return path12.join(dataDir, directoryName);
4335
4843
  }
4336
4844
  function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
4337
4845
  return {
@@ -4380,7 +4888,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
4380
4888
  return summary;
4381
4889
  }
4382
4890
  const childSummaries = await Promise.all(
4383
- entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
4891
+ entries.map((entry) => summarizeWorkspaceEntry(path12.join(dirPath, entry.name), entry))
4384
4892
  );
4385
4893
  for (const childSummary of childSummaries) {
4386
4894
  summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
@@ -4399,7 +4907,7 @@ async function scanWorkspaceDirectories(dataDir) {
4399
4907
  if (!entry.isDirectory()) {
4400
4908
  return null;
4401
4909
  }
4402
- const dirPath = path11.join(dataDir, entry.name);
4910
+ const dirPath = path12.join(dataDir, entry.name);
4403
4911
  try {
4404
4912
  const summary = await summarizeWorkspaceDirectory(dirPath);
4405
4913
  return {
@@ -4657,12 +5165,12 @@ function findSessionJsonl(root, predicate) {
4657
5165
  for (const entry of entries) {
4658
5166
  if (++visited > maxEntries) return null;
4659
5167
  if (!entry.isFile() || !predicate(entry.name)) continue;
4660
- return path12.join(dir, entry.name);
5168
+ return path13.join(dir, entry.name);
4661
5169
  }
4662
5170
  for (const entry of entries) {
4663
5171
  if (++visited > maxEntries) return null;
4664
5172
  if (!entry.isDirectory()) continue;
4665
- const found = visit(path12.join(dir, entry.name), depth - 1);
5173
+ const found = visit(path13.join(dir, entry.name), depth - 1);
4666
5174
  if (found) return found;
4667
5175
  }
4668
5176
  return null;
@@ -4675,10 +5183,10 @@ function safeSessionFilename(value) {
4675
5183
  }
4676
5184
  function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
4677
5185
  try {
4678
- const dir = path12.join(fallbackDir, ".slock", "runtime-sessions");
4679
- mkdirSync4(dir, { recursive: true });
4680
- const filePath = path12.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
4681
- writeFileSync7(filePath, JSON.stringify({
5186
+ const dir = path13.join(fallbackDir, ".slock", "runtime-sessions");
5187
+ mkdirSync5(dir, { recursive: true });
5188
+ const filePath = path13.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
5189
+ writeFileSync8(filePath, JSON.stringify({
4682
5190
  type: "runtime_session_handoff",
4683
5191
  runtime,
4684
5192
  sessionId,
@@ -4697,7 +5205,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
4697
5205
  }
4698
5206
  }
4699
5207
  function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir) {
4700
- const directPath = path12.isAbsolute(sessionId) ? sessionId : null;
5208
+ const directPath = path13.isAbsolute(sessionId) ? sessionId : null;
4701
5209
  if (directPath) {
4702
5210
  try {
4703
5211
  if (statSync2(directPath).isFile()) {
@@ -4706,7 +5214,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
4706
5214
  } catch {
4707
5215
  }
4708
5216
  }
4709
- 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;
5217
+ 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;
4710
5218
  if (!resolvedPath && fallbackDir) {
4711
5219
  const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
4712
5220
  if (fallback) return fallback;
@@ -5158,7 +5666,7 @@ Do not copy these answers verbatim.
5158
5666
  ## FAQ 15: How do I create agents or channels?
5159
5667
  ### Answer idea
5160
5668
  - 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.
5161
- - v1 supports three action types via \`slock action prepare --target '<channel>' <<EOF { ... } EOF\`:
5669
+ - v1 supports three action types via \`slock action prepare --target '<channel>' <<'SLOCKACTION' { ... } SLOCKACTION\`:
5162
5670
  - \`{type: "channel:create", name, visibility: "public" | "private", description?, initialHumans?: ["@alice"], initialAgents?: ["@scout"], draftHint?}\`
5163
5671
  - \`{type: "agent:create", name, description?, draftHint?}\` \u2014 runtime / model / computer are the owner's call, not yours
5164
5672
  - \`{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.
@@ -6015,26 +6523,26 @@ var AgentProcessManager = class _AgentProcessManager {
6015
6523
  this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
6016
6524
  try {
6017
6525
  const driver = this.driverResolver(config.runtime || "claude");
6018
- const agentDataDir = path12.join(this.dataDir, agentId);
6526
+ const agentDataDir = path13.join(this.dataDir, agentId);
6019
6527
  await mkdir(agentDataDir, { recursive: true });
6020
6528
  const runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
6021
- const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
6529
+ const memoryMdPath = path13.join(agentDataDir, "MEMORY.md");
6022
6530
  try {
6023
6531
  await access(memoryMdPath);
6024
6532
  } catch {
6025
6533
  const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
6026
6534
  await writeFile(memoryMdPath, initialMemoryMd);
6027
6535
  }
6028
- const notesDir = path12.join(agentDataDir, "notes");
6536
+ const notesDir = path13.join(agentDataDir, "notes");
6029
6537
  await mkdir(notesDir, { recursive: true });
6030
6538
  if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
6031
6539
  const seedFiles = buildOnboardingSeedFiles();
6032
6540
  for (const { relativePath, content } of seedFiles) {
6033
- const fullPath = path12.join(agentDataDir, relativePath);
6541
+ const fullPath = path13.join(agentDataDir, relativePath);
6034
6542
  try {
6035
6543
  await access(fullPath);
6036
6544
  } catch {
6037
- await mkdir(path12.dirname(fullPath), { recursive: true });
6545
+ await mkdir(path13.dirname(fullPath), { recursive: true });
6038
6546
  await writeFile(fullPath, content);
6039
6547
  }
6040
6548
  }
@@ -6911,7 +7419,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6911
7419
  return true;
6912
7420
  }
6913
7421
  async resetWorkspace(agentId) {
6914
- const agentDataDir = path12.join(this.dataDir, agentId);
7422
+ const agentDataDir = path13.join(this.dataDir, agentId);
6915
7423
  try {
6916
7424
  await rm2(agentDataDir, { recursive: true, force: true });
6917
7425
  logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
@@ -6948,7 +7456,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6948
7456
  return result;
6949
7457
  }
6950
7458
  buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
6951
- const workspacePath = path12.join(this.dataDir, agentId);
7459
+ const workspacePath = path13.join(this.dataDir, agentId);
6952
7460
  return {
6953
7461
  agentId,
6954
7462
  launchId,
@@ -7205,7 +7713,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7205
7713
  }
7206
7714
  // Workspace file browsing
7207
7715
  async getFileTree(agentId, dirPath) {
7208
- const agentDir = path12.join(this.dataDir, agentId);
7716
+ const agentDir = path13.join(this.dataDir, agentId);
7209
7717
  try {
7210
7718
  await stat2(agentDir);
7211
7719
  } catch {
@@ -7213,8 +7721,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7213
7721
  }
7214
7722
  let targetDir = agentDir;
7215
7723
  if (dirPath) {
7216
- const resolved = path12.resolve(agentDir, dirPath);
7217
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
7724
+ const resolved = path13.resolve(agentDir, dirPath);
7725
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
7218
7726
  return [];
7219
7727
  }
7220
7728
  targetDir = resolved;
@@ -7222,14 +7730,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7222
7730
  return this.listDirectoryChildren(targetDir, agentDir);
7223
7731
  }
7224
7732
  async readFile(agentId, filePath) {
7225
- const agentDir = path12.join(this.dataDir, agentId);
7226
- const resolved = path12.resolve(agentDir, filePath);
7227
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
7733
+ const agentDir = path13.join(this.dataDir, agentId);
7734
+ const resolved = path13.resolve(agentDir, filePath);
7735
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
7228
7736
  throw new Error("Access denied");
7229
7737
  }
7230
7738
  const info = await stat2(resolved);
7231
7739
  if (info.isDirectory()) throw new Error("Cannot read a directory");
7232
- const ext = path12.extname(resolved).toLowerCase();
7740
+ const ext = path13.extname(resolved).toLowerCase();
7233
7741
  if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
7234
7742
  if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
7235
7743
  const content = await readFile(resolved, "utf-8");
@@ -7264,13 +7772,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7264
7772
  const agent = this.agents.get(agentId);
7265
7773
  const runtime = runtimeHint || agent?.config.runtime || "claude";
7266
7774
  const home = os6.homedir();
7267
- const workspaceDir = path12.join(this.dataDir, agentId);
7775
+ const workspaceDir = path13.join(this.dataDir, agentId);
7268
7776
  const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
7269
7777
  const globalResults = await Promise.all(
7270
- paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
7778
+ paths.global.map((p) => this.scanSkillsDir(path13.join(home, p)))
7271
7779
  );
7272
7780
  const workspaceResults = await Promise.all(
7273
- paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
7781
+ paths.workspace.map((p) => this.scanSkillsDir(path13.join(workspaceDir, p)))
7274
7782
  );
7275
7783
  const dedup = (skills) => {
7276
7784
  const seen = /* @__PURE__ */ new Set();
@@ -7299,7 +7807,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7299
7807
  const skills = [];
7300
7808
  for (const entry of entries) {
7301
7809
  if (entry.isDirectory() || entry.isSymbolicLink()) {
7302
- const skillMd = path12.join(dir, entry.name, "SKILL.md");
7810
+ const skillMd = path13.join(dir, entry.name, "SKILL.md");
7303
7811
  try {
7304
7812
  const content = await readFile(skillMd, "utf-8");
7305
7813
  const skill = this.parseSkillMd(entry.name, content);
@@ -7310,7 +7818,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7310
7818
  } else if (entry.name.endsWith(".md")) {
7311
7819
  const cmdName = entry.name.replace(/\.md$/, "");
7312
7820
  try {
7313
- const content = await readFile(path12.join(dir, entry.name), "utf-8");
7821
+ const content = await readFile(path13.join(dir, entry.name), "utf-8");
7314
7822
  const skill = this.parseSkillMd(cmdName, content);
7315
7823
  skill.sourcePath = dir;
7316
7824
  skills.push(skill);
@@ -8276,8 +8784,8 @@ ${RESPONSE_TARGET_HINT}`);
8276
8784
  const nodes = [];
8277
8785
  for (const entry of entries) {
8278
8786
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
8279
- const fullPath = path12.join(dir, entry.name);
8280
- const relativePath = path12.relative(rootDir, fullPath);
8787
+ const fullPath = path13.join(dir, entry.name);
8788
+ const relativePath = path13.relative(rootDir, fullPath);
8281
8789
  let info;
8282
8790
  try {
8283
8791
  info = await stat2(fullPath);
@@ -8582,9 +9090,9 @@ var ReminderCache = class {
8582
9090
 
8583
9091
  // src/machineLock.ts
8584
9092
  import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
8585
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
9093
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync6, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
8586
9094
  import os7 from "os";
8587
- import path13 from "path";
9095
+ import path14 from "path";
8588
9096
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
8589
9097
  var DaemonMachineLockConflictError = class extends Error {
8590
9098
  code = "DAEMON_MACHINE_LOCK_HELD";
@@ -8606,11 +9114,11 @@ function resolveDefaultMachineStateRoot() {
8606
9114
  return resolveSlockHomePath("machines");
8607
9115
  }
8608
9116
  function ownerPath(lockDir) {
8609
- return path13.join(lockDir, "owner.json");
9117
+ return path14.join(lockDir, "owner.json");
8610
9118
  }
8611
9119
  function readOwner(lockDir) {
8612
9120
  try {
8613
- return JSON.parse(readFileSync5(ownerPath(lockDir), "utf8"));
9121
+ return JSON.parse(readFileSync6(ownerPath(lockDir), "utf8"));
8614
9122
  } catch {
8615
9123
  return null;
8616
9124
  }
@@ -8636,13 +9144,13 @@ function acquireDaemonMachineLock(options) {
8636
9144
  const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
8637
9145
  const fingerprint = apiKeyFingerprint(options.apiKey);
8638
9146
  const lockId = getDaemonMachineLockId(options.apiKey);
8639
- const machineDir = path13.join(rootDir, lockId);
8640
- const lockDir = path13.join(machineDir, "daemon.lock");
9147
+ const machineDir = path14.join(rootDir, lockId);
9148
+ const lockDir = path14.join(machineDir, "daemon.lock");
8641
9149
  const token = randomUUID2();
8642
- mkdirSync5(machineDir, { recursive: true });
9150
+ mkdirSync6(machineDir, { recursive: true });
8643
9151
  for (let attempt = 0; attempt < 2; attempt += 1) {
8644
9152
  try {
8645
- mkdirSync5(lockDir);
9153
+ mkdirSync6(lockDir);
8646
9154
  const owner = {
8647
9155
  pid: process.pid,
8648
9156
  token,
@@ -8652,7 +9160,7 @@ function acquireDaemonMachineLock(options) {
8652
9160
  apiKeyFingerprint: fingerprint.slice(0, 16)
8653
9161
  };
8654
9162
  try {
8655
- writeFileSync8(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
9163
+ writeFileSync9(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
8656
9164
  `, { mode: 384 });
8657
9165
  } catch (err) {
8658
9166
  rmSync3(lockDir, { recursive: true, force: true });
@@ -8689,8 +9197,8 @@ function acquireDaemonMachineLock(options) {
8689
9197
  }
8690
9198
 
8691
9199
  // src/localTraceSink.ts
8692
- import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
8693
- import path14 from "path";
9200
+ import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync10 } from "fs";
9201
+ import path15 from "path";
8694
9202
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
8695
9203
  var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
8696
9204
  var DEFAULT_MAX_FILES = 8;
@@ -8726,7 +9234,7 @@ var LocalRotatingTraceSink = class {
8726
9234
  currentSize = 0;
8727
9235
  sequence = 0;
8728
9236
  constructor(options) {
8729
- this.traceDir = path14.join(options.machineDir, "traces");
9237
+ this.traceDir = path15.join(options.machineDir, "traces");
8730
9238
  this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
8731
9239
  const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
8732
9240
  const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
@@ -8752,15 +9260,15 @@ var LocalRotatingTraceSink = class {
8752
9260
  return this.currentFile;
8753
9261
  }
8754
9262
  ensureFile(nextBytes) {
8755
- mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
9263
+ mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
8756
9264
  const nowMs = this.nowMsProvider();
8757
9265
  const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
8758
9266
  if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
8759
- this.currentFile = path14.join(
9267
+ this.currentFile = path15.join(
8760
9268
  this.traceDir,
8761
9269
  `daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
8762
9270
  );
8763
- writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
9271
+ writeFileSync10(this.currentFile, "", { flag: "a", mode: 384 });
8764
9272
  this.currentSize = statSync4(this.currentFile).size;
8765
9273
  this.currentFileOpenedAtMs = nowMs;
8766
9274
  this.pruneOldFiles();
@@ -8771,7 +9279,7 @@ var LocalRotatingTraceSink = class {
8771
9279
  const excess = files.length - this.maxFiles;
8772
9280
  if (excess <= 0) return;
8773
9281
  for (const file of files.slice(0, excess)) {
8774
- rmSync4(path14.join(this.traceDir, file), { force: true });
9282
+ rmSync4(path15.join(this.traceDir, file), { force: true });
8775
9283
  }
8776
9284
  }
8777
9285
  };
@@ -8858,11 +9366,11 @@ function isDiagnosticErrorAttr(key) {
8858
9366
  import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
8859
9367
  import { gzipSync } from "zlib";
8860
9368
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
8861
- import path15 from "path";
9369
+ import path16 from "path";
8862
9370
 
8863
9371
  // src/directUploadCapability.ts
8864
- function joinUrl(base, path17) {
8865
- return `${base.replace(/\/+$/, "")}${path17}`;
9372
+ function joinUrl(base, path18) {
9373
+ return `${base.replace(/\/+$/, "")}${path18}`;
8866
9374
  }
8867
9375
  function jsonHeaders(apiKey) {
8868
9376
  return {
@@ -9081,7 +9589,7 @@ var DaemonTraceBundleUploader = class {
9081
9589
  }, nextMs);
9082
9590
  }
9083
9591
  async findUploadCandidates() {
9084
- const traceDir = path15.join(this.options.machineDir, "traces");
9592
+ const traceDir = path16.join(this.options.machineDir, "traces");
9085
9593
  let names;
9086
9594
  try {
9087
9595
  names = await readdir3(traceDir);
@@ -9093,8 +9601,8 @@ var DaemonTraceBundleUploader = class {
9093
9601
  const currentFile = this.options.currentFileProvider?.();
9094
9602
  const candidates = [];
9095
9603
  for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
9096
- const file = path15.join(traceDir, name);
9097
- if (currentFile && path15.resolve(file) === path15.resolve(currentFile)) continue;
9604
+ const file = path16.join(traceDir, name);
9605
+ if (currentFile && path16.resolve(file) === path16.resolve(currentFile)) continue;
9098
9606
  if (await this.isUploaded(file)) continue;
9099
9607
  try {
9100
9608
  const info = await stat3(file);
@@ -9168,8 +9676,8 @@ var DaemonTraceBundleUploader = class {
9168
9676
  }
9169
9677
  }
9170
9678
  uploadStatePath(file) {
9171
- const stateDir = path15.join(this.options.machineDir, "trace-uploads");
9172
- return path15.join(stateDir, `${path15.basename(file)}.uploaded.json`);
9679
+ const stateDir = path16.join(this.options.machineDir, "trace-uploads");
9680
+ return path16.join(stateDir, `${path16.basename(file)}.uploaded.json`);
9173
9681
  }
9174
9682
  async isUploaded(file) {
9175
9683
  try {
@@ -9181,9 +9689,9 @@ var DaemonTraceBundleUploader = class {
9181
9689
  }
9182
9690
  async markUploaded(file, metadata) {
9183
9691
  const stateFile = this.uploadStatePath(file);
9184
- await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
9692
+ await mkdir2(path16.dirname(stateFile), { recursive: true, mode: 448 });
9185
9693
  await writeFile2(stateFile, `${JSON.stringify({
9186
- file: path15.basename(file),
9694
+ file: path16.basename(file),
9187
9695
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
9188
9696
  ...metadata
9189
9697
  }, null, 2)}
@@ -9205,7 +9713,7 @@ var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
9205
9713
  var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels"];
9206
9714
  var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
9207
9715
  var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
9208
- var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
9716
+ var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
9209
9717
  var RunnerCredentialMintError2 = class extends Error {
9210
9718
  code;
9211
9719
  retryable;
@@ -9241,9 +9749,9 @@ function runnerCredentialErrorDetail2(error) {
9241
9749
  async function waitForRunnerCredentialRetry2() {
9242
9750
  await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
9243
9751
  }
9244
- function parseDaemonCliArgs(args) {
9752
+ function parseDaemonCliArgs(args, env = {}) {
9245
9753
  let serverUrl = "";
9246
- let apiKey = "";
9754
+ let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
9247
9755
  for (let i = 0; i < args.length; i++) {
9248
9756
  if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
9249
9757
  if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
@@ -9253,30 +9761,30 @@ function parseDaemonCliArgs(args) {
9253
9761
  }
9254
9762
  function readDaemonVersion(moduleUrl = import.meta.url) {
9255
9763
  try {
9256
- const require2 = createRequire(moduleUrl);
9764
+ const require2 = createRequire2(moduleUrl);
9257
9765
  return require2("../package.json").version;
9258
9766
  } catch {
9259
9767
  return "0.0.0-dev";
9260
9768
  }
9261
9769
  }
9262
9770
  function resolveChatBridgePath(moduleUrl = import.meta.url) {
9263
- const dirname = path16.dirname(fileURLToPath(moduleUrl));
9264
- const jsPath = path16.resolve(dirname, "chat-bridge.js");
9771
+ const dirname = path17.dirname(fileURLToPath2(moduleUrl));
9772
+ const jsPath = path17.resolve(dirname, "chat-bridge.js");
9265
9773
  try {
9266
9774
  accessSync(jsPath);
9267
9775
  return jsPath;
9268
9776
  } catch {
9269
- return path16.resolve(dirname, "chat-bridge.ts");
9777
+ return path17.resolve(dirname, "chat-bridge.ts");
9270
9778
  }
9271
9779
  }
9272
9780
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
9273
- const thisDir = path16.dirname(fileURLToPath(moduleUrl));
9274
- const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
9781
+ const thisDir = path17.dirname(fileURLToPath2(moduleUrl));
9782
+ const bundledDistPath = path17.resolve(thisDir, "cli", "index.js");
9275
9783
  try {
9276
9784
  accessSync(bundledDistPath);
9277
9785
  return bundledDistPath;
9278
9786
  } catch {
9279
- const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
9787
+ const workspaceDistPath = path17.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
9280
9788
  accessSync(workspaceDistPath);
9281
9789
  return workspaceDistPath;
9282
9790
  }
@@ -9455,7 +9963,7 @@ var DaemonCore = class {
9455
9963
  }
9456
9964
  resolveMachineStateRoot() {
9457
9965
  if (this.options.machineStateDir) return this.options.machineStateDir;
9458
- if (this.options.dataDir) return path16.join(path16.dirname(this.options.dataDir), "machines");
9966
+ if (this.options.dataDir) return path17.join(path17.dirname(this.options.dataDir), "machines");
9459
9967
  return resolveDefaultMachineStateRoot();
9460
9968
  }
9461
9969
  shouldEnableLocalTrace() {
@@ -9957,6 +10465,8 @@ var DaemonCore = class {
9957
10465
  };
9958
10466
 
9959
10467
  export {
10468
+ DAEMON_API_KEY_ENV,
10469
+ scrubDaemonAuthEnv,
9960
10470
  resolveWorkspaceDirectoryPath,
9961
10471
  scanWorkspaceDirectories,
9962
10472
  deleteWorkspaceDirectory,