@slock-ai/daemon 0.52.0 → 0.52.1-play.20260520171228

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
12
  import { createRequire } 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,10 +765,10 @@ 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
@@ -823,6 +824,7 @@ function buildPrompt(config, variant, opts) {
823
824
  "- Always communicate through `slock` CLI commands. This is your only output channel.",
824
825
  ...opts.extraCriticalRules,
825
826
  "- Use only the provided `slock` CLI commands for messaging.",
827
+ "- 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
828
  "- Always claim a task via `slock task claim` before starting work on it. If the claim fails, move on to a different task."
827
829
  ] : [
828
830
  `- Always communicate through ${sendCmd}. This is your only output channel.`,
@@ -871,13 +873,15 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
871
873
  17. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
872
874
  18. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
873
875
  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\`).
876
+ 20. **\`slock integration list\`** \u2014 List registered third-party services and this agent's active Slock Agent Logins.
877
+ 21. **\`slock integration login\`** \u2014 Provision or reuse this agent's login for a registered third-party service.
878
+ 22. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
879
+ 23. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
880
+ 24. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
881
+ 25. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
882
+ 26. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
883
+ 27. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
884
+ 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
885
 
882
886
  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
887
  - failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
@@ -978,6 +982,11 @@ Each channel has a **name** and optionally a **description** that define its pur
978
982
  - **Reply in context** \u2014 always respond in the channel/thread the message came from.
979
983
  - **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
984
  - If unsure where something belongs, call ${serverInfoCmd} to review channel descriptions.`;
985
+ const thirdPartyIntegrationsSection = isCli ? `### Third-party integrations
986
+
987
+ 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
988
+
989
+ 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
990
  const readingHistorySection = isCli ? `### Reading history
982
991
 
983
992
  \`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
@@ -1149,6 +1158,8 @@ ${discoverySection}
1149
1158
 
1150
1159
  ${channelAwarenessSection}
1151
1160
 
1161
+ ${thirdPartyIntegrationsSection}
1162
+
1152
1163
  ${readingHistorySection}
1153
1164
 
1154
1165
  ${historicalReferenceSection}
@@ -1180,6 +1191,17 @@ Keep the user informed. They cannot see your internal reasoning, so:
1180
1191
  - For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
1181
1192
  - When done, summarize the result.
1182
1193
  - Keep updates concise \u2014 one or two sentences. Don't flood the chat.
1194
+ - 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:
1195
+
1196
+ \`\`\`html
1197
+ <details>
1198
+ <summary>Evidence, logs, or edge cases</summary>
1199
+
1200
+ Detailed notes go here.
1201
+ </details>
1202
+ \`\`\`
1203
+
1204
+ Do not hide the main recommendation, blocker, or required action inside \`<details>\`; only fold supporting evidence, logs, alternatives, or extended rationale.
1183
1205
 
1184
1206
  ### Conversation etiquette
1185
1207
 
@@ -1348,6 +1370,19 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
1348
1370
  return candidates.filter((candidate) => existsSync(candidate.path));
1349
1371
  }
1350
1372
 
1373
+ // src/authEnv.ts
1374
+ var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
1375
+ var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
1376
+ function scrubDaemonAuthEnv(env) {
1377
+ delete env[DAEMON_API_KEY_ENV];
1378
+ return env;
1379
+ }
1380
+ function scrubDaemonChildEnv(env) {
1381
+ delete env[DAEMON_API_KEY_ENV];
1382
+ delete env[SLOCK_AGENT_TOKEN_ENV];
1383
+ return env;
1384
+ }
1385
+
1351
1386
  // src/agentCredentialProxy.ts
1352
1387
  import { randomBytes } from "crypto";
1353
1388
  import http from "http";
@@ -1810,6 +1845,18 @@ function consumeVisibleResponse(registration, targetUrl, sendTarget, responseTex
1810
1845
  });
1811
1846
  return;
1812
1847
  }
1848
+ if (targetUrl.pathname === "/internal/agent-api/send" && parsed.state === "sent") {
1849
+ const messageSeq2 = typeof parsed.messageSeq === "number" && Number.isFinite(parsed.messageSeq) ? Math.floor(parsed.messageSeq) : void 0;
1850
+ if (sendTarget && messageSeq2 && messageSeq2 > 0) {
1851
+ coordinator.consumeVisibleMessages({
1852
+ target: sendTarget,
1853
+ messages: normalizeVisibleMessages([{ seq: messageSeq2, id: parsed.messageId }], sendTarget),
1854
+ boundarySeq: messageSeq2,
1855
+ source: "agent_api_send_commit"
1856
+ });
1857
+ }
1858
+ return;
1859
+ }
1813
1860
  if (targetUrl.pathname === "/internal/agent-api/events" && Array.isArray(parsed.events)) {
1814
1861
  const messages = normalizeVisibleMessages(parsed.events);
1815
1862
  coordinator.consumeVisibleMessages({ messages, source: "agent_api_events" });
@@ -1938,7 +1985,7 @@ ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
1938
1985
  ...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
1939
1986
  PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
1940
1987
  };
1941
- delete spawnEnv.SLOCK_AGENT_TOKEN;
1988
+ scrubDaemonChildEnv(spawnEnv);
1942
1989
  delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY;
1943
1990
  delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY_FILE;
1944
1991
  delete spawnEnv.SLOCK_AGENT_PROXY_URL;
@@ -1985,7 +2032,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn) {
1985
2032
  }
1986
2033
  function resolveCommandOnPath(command, deps = {}) {
1987
2034
  const platform = deps.platform ?? process.platform;
1988
- const env = deps.env ?? process.env;
2035
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
1989
2036
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
1990
2037
  if (platform === "win32") {
1991
2038
  return resolveCommandOnWindows(command, env, execFileSyncFn);
@@ -2010,7 +2057,7 @@ function firstExistingPath(candidates, deps = {}) {
2010
2057
  return null;
2011
2058
  }
2012
2059
  function readCommandVersion(command, args = [], deps = {}) {
2013
- const env = deps.env ?? process.env;
2060
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
2014
2061
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
2015
2062
  try {
2016
2063
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -3306,7 +3353,7 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
3306
3353
  }
3307
3354
  function runCursorModelsCommand() {
3308
3355
  return spawnSync("cursor-agent", ["models"], {
3309
- env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
3356
+ env: scrubDaemonChildEnv({ ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" }),
3310
3357
  encoding: "utf8",
3311
3358
  timeout: 5e3
3312
3359
  });
@@ -3353,7 +3400,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
3353
3400
  }
3354
3401
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
3355
3402
  const existsSyncFn = deps.existsSyncFn ?? existsSync6;
3356
- const env = deps.env ?? process.env;
3403
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
3357
3404
  const winPath = path8.win32;
3358
3405
  let geminiEntry = null;
3359
3406
  try {
@@ -3527,13 +3574,16 @@ var GeminiDriver = class {
3527
3574
  // src/drivers/kimi.ts
3528
3575
  import { randomUUID } from "crypto";
3529
3576
  import { spawn as spawn6 } from "child_process";
3530
- import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
3577
+ import { chmodSync, existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
3531
3578
  import os4 from "os";
3532
3579
  import path9 from "path";
3533
3580
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
3534
3581
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
3535
3582
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
3536
3583
  var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
3584
+ var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
3585
+ var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
3586
+ var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
3537
3587
  function parseToolArguments(raw) {
3538
3588
  if (typeof raw !== "string") return raw;
3539
3589
  try {
@@ -3542,6 +3592,73 @@ function parseToolArguments(raw) {
3542
3592
  return raw;
3543
3593
  }
3544
3594
  }
3595
+ function readKimiConfigSource(home = os4.homedir(), env = process.env) {
3596
+ const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
3597
+ if (inlineConfig && inlineConfig.trim()) {
3598
+ return {
3599
+ raw: inlineConfig,
3600
+ explicitPath: null,
3601
+ sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
3602
+ };
3603
+ }
3604
+ const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
3605
+ const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
3606
+ try {
3607
+ return {
3608
+ raw: readFileSync3(configPath, "utf8"),
3609
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
3610
+ sourcePath: configPath
3611
+ };
3612
+ } catch {
3613
+ return {
3614
+ raw: null,
3615
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
3616
+ sourcePath: configPath
3617
+ };
3618
+ }
3619
+ }
3620
+ function buildKimiSpawnEnv(env = process.env) {
3621
+ const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
3622
+ delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
3623
+ delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
3624
+ return scrubDaemonChildEnv(spawnEnv);
3625
+ }
3626
+ function buildKimiEffectiveEnv(ctx, overrideEnv) {
3627
+ return {
3628
+ ...process.env,
3629
+ ...ctx.config.envVars || {},
3630
+ ...overrideEnv || {}
3631
+ };
3632
+ }
3633
+ function buildKimiLaunchOptions(ctx, opts = {}) {
3634
+ const env = buildKimiEffectiveEnv(ctx, opts.env);
3635
+ const source = readKimiConfigSource(opts.home ?? os4.homedir(), env);
3636
+ const args = [];
3637
+ let configFilePath = null;
3638
+ let configContent = null;
3639
+ if (source.explicitPath) {
3640
+ configFilePath = source.explicitPath;
3641
+ } else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
3642
+ configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
3643
+ configContent = source.raw;
3644
+ if (opts.writeGeneratedConfig !== false) {
3645
+ writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
3646
+ chmodSync(configFilePath, 384);
3647
+ }
3648
+ }
3649
+ if (configFilePath) {
3650
+ args.push("--config-file", configFilePath);
3651
+ }
3652
+ if (ctx.config.model && ctx.config.model !== "default") {
3653
+ args.push("--model", ctx.config.model);
3654
+ }
3655
+ return {
3656
+ args,
3657
+ env: buildKimiSpawnEnv(env),
3658
+ configFilePath,
3659
+ configContent
3660
+ };
3661
+ }
3545
3662
  function resolveKimiSpawn(commandArgs, deps = {}) {
3546
3663
  return {
3547
3664
  command: resolveCommandOnPath("kimi", deps) ?? "kimi",
@@ -3565,7 +3682,25 @@ var KimiDriver = class {
3565
3682
  };
3566
3683
  model = {
3567
3684
  detectedModelsVerifiedAs: "launchable",
3568
- toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
3685
+ toLaunchSpec: (modelId, ctx, opts) => {
3686
+ if (!ctx) return { args: ["--model", modelId] };
3687
+ const launchCtx = {
3688
+ ...ctx,
3689
+ config: {
3690
+ ...ctx.config,
3691
+ model: modelId
3692
+ }
3693
+ };
3694
+ const launch = buildKimiLaunchOptions(launchCtx, {
3695
+ home: opts?.home,
3696
+ writeGeneratedConfig: false
3697
+ });
3698
+ return {
3699
+ args: launch.args,
3700
+ env: launch.env,
3701
+ configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
3702
+ };
3703
+ }
3569
3704
  };
3570
3705
  supportsStdinNotification = true;
3571
3706
  mcpToolPrefix = "";
@@ -3619,6 +3754,7 @@ var KimiDriver = class {
3619
3754
  }
3620
3755
  }
3621
3756
  }), "utf8");
3757
+ const launch = buildKimiLaunchOptions(ctx);
3622
3758
  const args = [
3623
3759
  "--wire",
3624
3760
  "--yolo",
@@ -3627,14 +3763,15 @@ var KimiDriver = class {
3627
3763
  "--mcp-config-file",
3628
3764
  mcpConfigPath,
3629
3765
  "--session",
3630
- this.sessionId
3766
+ this.sessionId,
3767
+ ...launch.args
3631
3768
  ];
3632
3769
  if (ctx.config.model && ctx.config.model !== "default") {
3633
3770
  args.push("--model", ctx.config.model);
3634
3771
  }
3635
3772
  const spawnEnv = prepareCliTransport(ctx, { NO_COLOR: "1" }).spawnEnv;
3636
- const launch = resolveKimiSpawn(args);
3637
- const proc = spawn6(launch.command, launch.args, {
3773
+ const spawnTarget = resolveKimiSpawn(args);
3774
+ const proc = spawn6(spawnTarget.command, spawnTarget.args, {
3638
3775
  cwd: ctx.workingDirectory,
3639
3776
  stdio: ["pipe", "pipe", "pipe"],
3640
3777
  env: spawnEnv,
@@ -3642,7 +3779,7 @@ var KimiDriver = class {
3642
3779
  // and has an 8191-character command-line limit. Kimi's official
3643
3780
  // installer/uv entrypoint is an executable, so launch it directly and
3644
3781
  // keep prompts on stdin / files instead of routing through cmd.exe.
3645
- shell: launch.shell
3782
+ shell: spawnTarget.shell
3646
3783
  });
3647
3784
  proc.stdin?.write(JSON.stringify({
3648
3785
  jsonrpc: "2.0",
@@ -3758,14 +3895,9 @@ var KimiDriver = class {
3758
3895
  return detectKimiModels();
3759
3896
  }
3760
3897
  };
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
- }
3898
+ function detectKimiModels(home = os4.homedir(), opts = {}) {
3899
+ const raw = readKimiConfigSource(home, opts.env).raw;
3900
+ if (raw === null) return null;
3769
3901
  const models = [];
3770
3902
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
3771
3903
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -3786,7 +3918,7 @@ function detectKimiModels(home = os4.homedir()) {
3786
3918
 
3787
3919
  // src/drivers/opencode.ts
3788
3920
  import { spawn as spawn7, spawnSync as spawnSync2 } from "child_process";
3789
- import { readFileSync as readFileSync4 } from "fs";
3921
+ import { existsSync as existsSync8, readFileSync as readFileSync4 } from "fs";
3790
3922
  import os5 from "os";
3791
3923
  import path10 from "path";
3792
3924
  var CHAT_MCP_SERVER_NAME = "chat";
@@ -4024,11 +4156,14 @@ function detectOpenCodeModels(home = os5.homedir(), runCommand = runOpenCodeMode
4024
4156
  if (commandResult.error || commandResult.status !== 0) return null;
4025
4157
  return parseOpenCodeModelsOutput(commandResult.stdout);
4026
4158
  }
4027
- function runOpenCodeModelsCommand(home) {
4028
- const result = spawnSync2("opencode", ["models"], {
4029
- env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
4159
+ function runOpenCodeModelsCommand(home, deps = {}) {
4160
+ const platform = deps.platform ?? process.platform;
4161
+ const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
4162
+ const result = spawnSyncFn("opencode", ["models"], {
4163
+ env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
4030
4164
  encoding: "utf8",
4031
- timeout: 5e3
4165
+ timeout: 5e3,
4166
+ shell: platform === "win32"
4032
4167
  });
4033
4168
  return {
4034
4169
  status: result.status,
@@ -4036,6 +4171,102 @@ function runOpenCodeModelsCommand(home) {
4036
4171
  error: result.error
4037
4172
  };
4038
4173
  }
4174
+ function isWindowsCommandShim(commandPath) {
4175
+ const ext = path10.win32.extname(commandPath).toLowerCase();
4176
+ return ext === ".cmd" || ext === ".bat";
4177
+ }
4178
+ function opencodePackageEntryCandidates(packageRoot) {
4179
+ const winPath = path10.win32;
4180
+ return [
4181
+ winPath.join(packageRoot, "bin", "opencode.exe"),
4182
+ winPath.join(packageRoot, "bin", "opencode.js"),
4183
+ winPath.join(packageRoot, "bin", "opencode.mjs"),
4184
+ winPath.join(packageRoot, "dist", "index.js")
4185
+ ];
4186
+ }
4187
+ function openCodeSpecForEntry(entry, commandArgs) {
4188
+ if (path10.win32.extname(entry).toLowerCase() === ".exe") {
4189
+ return { command: entry, args: commandArgs, shell: false };
4190
+ }
4191
+ return { command: process.execPath, args: [entry, ...commandArgs], shell: false };
4192
+ }
4193
+ function resolveWindowsOpenCodePackageEntry(commandPath, deps = {}) {
4194
+ const existsSyncFn = deps.existsSyncFn ?? existsSync8;
4195
+ const execFileSyncFn = deps.execFileSyncFn;
4196
+ const env = deps.env ?? process.env;
4197
+ const winPath = path10.win32;
4198
+ const candidates = [];
4199
+ if (execFileSyncFn) {
4200
+ try {
4201
+ const globalRoot = String(execFileSyncFn("npm", ["root", "-g"], {
4202
+ encoding: "utf8",
4203
+ stdio: ["ignore", "pipe", "ignore"],
4204
+ env
4205
+ })).trim();
4206
+ if (globalRoot) {
4207
+ candidates.push(...opencodePackageEntryCandidates(winPath.join(globalRoot, "opencode-ai")));
4208
+ }
4209
+ } catch {
4210
+ }
4211
+ }
4212
+ if (commandPath) {
4213
+ const commandDir = winPath.dirname(commandPath);
4214
+ candidates.push(...opencodePackageEntryCandidates(winPath.join(commandDir, "node_modules", "opencode-ai")));
4215
+ candidates.push(...extractWindowsShimTargets(commandPath, deps));
4216
+ }
4217
+ for (const candidate of candidates) {
4218
+ if (existsSyncFn(candidate)) return candidate;
4219
+ }
4220
+ return null;
4221
+ }
4222
+ function extractWindowsShimTargets(commandPath, deps = {}) {
4223
+ if (!isWindowsCommandShim(commandPath)) return [];
4224
+ const readFileSyncFn = deps.readFileSyncFn ?? readFileSync4;
4225
+ const commandDir = path10.win32.dirname(commandPath);
4226
+ let raw;
4227
+ try {
4228
+ raw = String(readFileSyncFn(commandPath, "utf8"));
4229
+ } catch {
4230
+ return [];
4231
+ }
4232
+ const candidates = [];
4233
+ const dp0Pattern = /%~dp0\\?([^"\r\n]*?opencode\.(?:exe|js|mjs|cjs))/gi;
4234
+ for (const match of raw.matchAll(dp0Pattern)) {
4235
+ const relative = match[1]?.replace(/^\\+/, "");
4236
+ if (relative) candidates.push(path10.win32.normalize(path10.win32.join(commandDir, relative)));
4237
+ }
4238
+ return candidates;
4239
+ }
4240
+ function resolveOpenCodeSpawn(commandArgs, deps = {}) {
4241
+ const platform = deps.platform ?? process.platform;
4242
+ if (platform !== "win32") {
4243
+ return {
4244
+ command: resolveCommandOnPath("opencode", deps) ?? "opencode",
4245
+ args: commandArgs,
4246
+ shell: false
4247
+ };
4248
+ }
4249
+ const command = resolveCommandOnPath("opencode", deps);
4250
+ if (command && path10.win32.extname(command).toLowerCase() === ".exe") {
4251
+ return { command, args: commandArgs, shell: false };
4252
+ }
4253
+ const packageEntry = resolveWindowsOpenCodePackageEntry(command, deps);
4254
+ if (packageEntry) return openCodeSpecForEntry(packageEntry, commandArgs);
4255
+ if (command && !isWindowsCommandShim(command)) {
4256
+ return { command, args: commandArgs, shell: false };
4257
+ }
4258
+ throw new Error(
4259
+ "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."
4260
+ );
4261
+ }
4262
+ function readOpenCodeVersion(deps = {}) {
4263
+ try {
4264
+ const launch = resolveOpenCodeSpawn([], deps);
4265
+ return readCommandVersion(launch.command, launch.args, deps);
4266
+ } catch {
4267
+ return null;
4268
+ }
4269
+ }
4039
4270
  function isSystemFirstMessageTask(message) {
4040
4271
  return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX);
4041
4272
  }
@@ -4078,7 +4309,7 @@ var OpenCodeDriver = class {
4078
4309
  model: modelId
4079
4310
  }
4080
4311
  };
4081
- const version = readCommandVersion("opencode");
4312
+ const version = readOpenCodeVersion();
4082
4313
  const launch = buildOpenCodeLaunchOptions(launchCtx, opts?.home, version);
4083
4314
  return {
4084
4315
  args: launch.args,
@@ -4099,8 +4330,13 @@ var OpenCodeDriver = class {
4099
4330
  sessionId = null;
4100
4331
  sessionAnnounced = false;
4101
4332
  probe() {
4102
- if (!resolveCommandOnPath("opencode")) return { available: false };
4103
- const version = readCommandVersion("opencode") || void 0;
4333
+ let version;
4334
+ try {
4335
+ const launch = resolveOpenCodeSpawn([]);
4336
+ version = readCommandVersion(launch.command, launch.args);
4337
+ } catch {
4338
+ return { available: false };
4339
+ }
4104
4340
  const unsupportedMessage = unsupportedOpenCodeVersionMessage(version);
4105
4341
  if (unsupportedMessage) {
4106
4342
  return {
@@ -4108,7 +4344,7 @@ var OpenCodeDriver = class {
4108
4344
  version: `${version} (requires >= ${MIN_SUPPORTED_OPENCODE_VERSION})`
4109
4345
  };
4110
4346
  }
4111
- return { available: true, version };
4347
+ return { available: true, version: version ?? void 0 };
4112
4348
  }
4113
4349
  async detectModels() {
4114
4350
  return detectOpenCodeModels();
@@ -4116,17 +4352,18 @@ var OpenCodeDriver = class {
4116
4352
  spawn(ctx) {
4117
4353
  this.sessionId = ctx.config.sessionId || null;
4118
4354
  this.sessionAnnounced = false;
4119
- const version = readCommandVersion("opencode");
4355
+ const version = readOpenCodeVersion();
4120
4356
  const unsupportedMessage = unsupportedOpenCodeVersionMessage(version);
4121
4357
  if (unsupportedMessage) {
4122
4358
  throw new Error(unsupportedMessage);
4123
4359
  }
4124
4360
  const launch = buildOpenCodeLaunchOptions(ctx, os5.homedir(), version);
4125
- const proc = spawn7("opencode", launch.args, {
4361
+ const spawnSpec = resolveOpenCodeSpawn(launch.args);
4362
+ const proc = spawn7(spawnSpec.command, spawnSpec.args, {
4126
4363
  cwd: ctx.workingDirectory,
4127
4364
  stdio: ["pipe", "pipe", "pipe"],
4128
4365
  env: launch.env,
4129
- shell: process.platform === "win32"
4366
+ shell: spawnSpec.shell
4130
4367
  });
4131
4368
  proc.stdin?.end();
4132
4369
  return { process: proc };
@@ -4184,6 +4421,297 @@ var OpenCodeDriver = class {
4184
4421
  }
4185
4422
  };
4186
4423
 
4424
+ // src/drivers/pi.ts
4425
+ import { spawn as spawn8 } from "child_process";
4426
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
4427
+ import path11 from "path";
4428
+ import { fileURLToPath } from "url";
4429
+ import { getAgentDir, VERSION as PI_SDK_VERSION } from "@earendil-works/pi-coding-agent";
4430
+ var CHAT_MCP_TOOL_PREFIX2 = "chat_";
4431
+ var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
4432
+ var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
4433
+ var MIN_SUPPORTED_PI_VERSION = "0.74.0";
4434
+ function parseSemver2(version) {
4435
+ const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
4436
+ if (!match) return null;
4437
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
4438
+ }
4439
+ function isSupportedPiVersion(version) {
4440
+ if (!version) return true;
4441
+ const actual = parseSemver2(version);
4442
+ const minimum = parseSemver2(MIN_SUPPORTED_PI_VERSION);
4443
+ if (!actual || !minimum) return true;
4444
+ for (let i = 0; i < 3; i += 1) {
4445
+ if (actual[i] > minimum[i]) return true;
4446
+ if (actual[i] < minimum[i]) return false;
4447
+ }
4448
+ return true;
4449
+ }
4450
+ function unsupportedPiVersionMessage(version) {
4451
+ if (!version || isSupportedPiVersion(version)) return null;
4452
+ 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.`;
4453
+ }
4454
+ function probePi(version = PI_SDK_VERSION) {
4455
+ const unsupportedMessage = unsupportedPiVersionMessage(version);
4456
+ if (unsupportedMessage) {
4457
+ return {
4458
+ available: false,
4459
+ version: `${version} (requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION})`
4460
+ };
4461
+ }
4462
+ return { available: true, version };
4463
+ }
4464
+ function resolvePiSdkRunnerPath(moduleUrl = import.meta.url) {
4465
+ const moduleDir = path11.dirname(fileURLToPath(moduleUrl));
4466
+ const sourceSibling = path11.join(moduleDir, "piSdkRunner.ts");
4467
+ if (existsSync9(sourceSibling)) return sourceSibling;
4468
+ const bundledEntry = path11.join(moduleDir, "drivers", "piSdkRunner.js");
4469
+ if (existsSync9(bundledEntry)) return bundledEntry;
4470
+ return path11.join(moduleDir, "piSdkRunner.js");
4471
+ }
4472
+ function buildPiSdkNodeArgs(runnerPath = resolvePiSdkRunnerPath()) {
4473
+ if (runnerPath.endsWith(".ts")) {
4474
+ return [...process.execArgv, runnerPath];
4475
+ }
4476
+ return [runnerPath];
4477
+ }
4478
+ function buildPiLaunchOptions(ctx, opts = {}) {
4479
+ const command = opts.command ?? process.execPath;
4480
+ const piDir = path11.join(ctx.workingDirectory, ".slock", "pi");
4481
+ const sessionDir = path11.join(piDir, "sessions");
4482
+ mkdirSync4(sessionDir, { recursive: true });
4483
+ const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
4484
+ const runnerPath = opts.runnerPath ?? resolvePiSdkRunnerPath();
4485
+ const agentDir = opts.agentDir ?? getAgentDir();
4486
+ const runnerConfigPath = path11.join(
4487
+ piDir,
4488
+ `sdk-run-${(ctx.launchId || "launch").replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`
4489
+ );
4490
+ const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
4491
+ const runnerConfig = {
4492
+ cwd: ctx.workingDirectory,
4493
+ agentDir,
4494
+ sessionDir,
4495
+ sessionId: ctx.config.sessionId || null,
4496
+ standingPrompt: ctx.standingPrompt,
4497
+ prompt: turnPrompt,
4498
+ model: ctx.config.model && ctx.config.model !== "default" ? ctx.config.model : null
4499
+ };
4500
+ writeFileSync7(runnerConfigPath, `${JSON.stringify(runnerConfig)}
4501
+ `, { encoding: "utf8", mode: 384 });
4502
+ const args = [
4503
+ ...buildPiSdkNodeArgs(runnerPath),
4504
+ "--config",
4505
+ runnerConfigPath
4506
+ ];
4507
+ return {
4508
+ command,
4509
+ args,
4510
+ env: slock.spawnEnv,
4511
+ sessionDir,
4512
+ agentDir,
4513
+ runnerConfigPath,
4514
+ sdkVersion: PI_SDK_VERSION
4515
+ };
4516
+ }
4517
+ function isSystemFirstMessageTask2(message) {
4518
+ return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
4519
+ }
4520
+ function buildPiSystemPrompt(config) {
4521
+ return buildCliTransportSystemPrompt(config, {
4522
+ toolPrefix: CHAT_MCP_TOOL_PREFIX2,
4523
+ extraCriticalRules: [
4524
+ "- 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."
4525
+ ],
4526
+ postStartupNotes: [
4527
+ "**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."
4528
+ ],
4529
+ includeStdinNotificationSection: false,
4530
+ messageNotificationStyle: "poll"
4531
+ });
4532
+ }
4533
+ function contentText(content) {
4534
+ if (!content) return "";
4535
+ const chunks = [];
4536
+ for (const item of content) {
4537
+ if (item.type === "text" && typeof item.text === "string") {
4538
+ chunks.push(item.text);
4539
+ }
4540
+ }
4541
+ return chunks.join("");
4542
+ }
4543
+ function apiKeyErrorMessage(line) {
4544
+ const trimmed = line.trim();
4545
+ if (!trimmed) return null;
4546
+ if (/no api key found/i.test(trimmed)) return trimmed;
4547
+ if (/api key.+required/i.test(trimmed)) return trimmed;
4548
+ if (/no models available/i.test(trimmed)) return trimmed;
4549
+ return null;
4550
+ }
4551
+ var PiDriver = class {
4552
+ id = "pi";
4553
+ lifecycle = {
4554
+ kind: "per_turn",
4555
+ start: "defer_until_concrete_message",
4556
+ exit: "terminate_on_turn_end",
4557
+ inFlightWake: "coalesce_into_pending"
4558
+ };
4559
+ communication = {
4560
+ chat: "slock_cli",
4561
+ runtimeControl: "none"
4562
+ };
4563
+ session = {
4564
+ recovery: "resume_or_fresh"
4565
+ };
4566
+ model = {
4567
+ detectedModelsVerifiedAs: "launchable",
4568
+ toLaunchSpec: (modelId, ctx) => {
4569
+ if (!ctx) return modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] };
4570
+ const launchCtx = {
4571
+ ...ctx,
4572
+ config: {
4573
+ ...ctx.config,
4574
+ model: modelId
4575
+ }
4576
+ };
4577
+ const launch = buildPiLaunchOptions(launchCtx);
4578
+ return {
4579
+ args: launch.args,
4580
+ env: launch.env,
4581
+ configFiles: [launch.runnerConfigPath],
4582
+ params: {
4583
+ agentDir: launch.agentDir,
4584
+ sessionDir: launch.sessionDir,
4585
+ sdkVersion: launch.sdkVersion,
4586
+ resources: "extensions/skills/prompt-templates/themes/context-files disabled by Slock policy"
4587
+ }
4588
+ };
4589
+ }
4590
+ };
4591
+ supportsStdinNotification = false;
4592
+ mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
4593
+ busyDeliveryMode = "none";
4594
+ terminateProcessOnTurnEnd = true;
4595
+ deferSpawnUntilMessage = true;
4596
+ usesSlockCliForCommunication = true;
4597
+ sessionId = null;
4598
+ sessionAnnounced = false;
4599
+ apiKeyErrorAnnounced = false;
4600
+ turnEnded = false;
4601
+ assistantTextByMessageId = /* @__PURE__ */ new Map();
4602
+ shouldDeferWakeMessage(message) {
4603
+ return isSystemFirstMessageTask2(message);
4604
+ }
4605
+ probe() {
4606
+ return probePi();
4607
+ }
4608
+ async detectModels() {
4609
+ return null;
4610
+ }
4611
+ spawn(ctx) {
4612
+ this.sessionId = ctx.config.sessionId || null;
4613
+ this.sessionAnnounced = false;
4614
+ this.apiKeyErrorAnnounced = false;
4615
+ this.turnEnded = false;
4616
+ this.assistantTextByMessageId.clear();
4617
+ const unsupportedMessage = unsupportedPiVersionMessage(PI_SDK_VERSION);
4618
+ if (unsupportedMessage) throw new Error(unsupportedMessage);
4619
+ const launch = buildPiLaunchOptions(ctx);
4620
+ const proc = spawn8(launch.command, launch.args, {
4621
+ cwd: ctx.workingDirectory,
4622
+ stdio: ["pipe", "pipe", "pipe"],
4623
+ env: launch.env,
4624
+ shell: false
4625
+ });
4626
+ proc.stdin?.end();
4627
+ return { process: proc };
4628
+ }
4629
+ parseLine(line) {
4630
+ let event;
4631
+ try {
4632
+ event = JSON.parse(line);
4633
+ } catch {
4634
+ if (this.apiKeyErrorAnnounced) return [];
4635
+ const message = apiKeyErrorMessage(line);
4636
+ if (!message) return [];
4637
+ this.apiKeyErrorAnnounced = true;
4638
+ this.turnEnded = true;
4639
+ return [
4640
+ { kind: "error", message },
4641
+ { kind: "turn_end", sessionId: this.sessionId || void 0 }
4642
+ ];
4643
+ }
4644
+ const events = [];
4645
+ if (event.type === "session" && event.id) {
4646
+ this.sessionId = event.id;
4647
+ }
4648
+ if (!this.sessionAnnounced && this.sessionId) {
4649
+ events.push({ kind: "session_init", sessionId: this.sessionId });
4650
+ this.sessionAnnounced = true;
4651
+ }
4652
+ switch (event.type) {
4653
+ case "agent_start":
4654
+ case "turn_start":
4655
+ events.push({ kind: "thinking", text: "" });
4656
+ break;
4657
+ case "message_update":
4658
+ case "message_end":
4659
+ if (event.message?.role === "assistant") {
4660
+ const key = event.message.id || "current";
4661
+ const currentText = contentText(event.message.content);
4662
+ const previousText = this.assistantTextByMessageId.get(key) ?? "";
4663
+ if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
4664
+ events.push({ kind: "text", text: currentText.slice(previousText.length) });
4665
+ } else if (currentText && currentText !== previousText) {
4666
+ events.push({ kind: "text", text: currentText });
4667
+ }
4668
+ this.assistantTextByMessageId.set(key, currentText);
4669
+ if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
4670
+ events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
4671
+ }
4672
+ }
4673
+ break;
4674
+ case "tool_execution_start":
4675
+ events.push({
4676
+ kind: "tool_call",
4677
+ name: event.toolName || "unknown_tool",
4678
+ input: event.args
4679
+ });
4680
+ break;
4681
+ case "tool_execution_end":
4682
+ events.push({
4683
+ kind: "tool_output",
4684
+ name: event.toolName || "unknown_tool"
4685
+ });
4686
+ if (event.isError) {
4687
+ events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
4688
+ }
4689
+ break;
4690
+ case "compaction_start":
4691
+ events.push({ kind: "compaction_started" });
4692
+ break;
4693
+ case "compaction_end":
4694
+ events.push({ kind: "compaction_finished" });
4695
+ if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
4696
+ break;
4697
+ case "turn_end":
4698
+ case "agent_end":
4699
+ if (!this.turnEnded) {
4700
+ events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
4701
+ this.turnEnded = true;
4702
+ }
4703
+ break;
4704
+ }
4705
+ return events;
4706
+ }
4707
+ encodeStdinMessage(_text, _sessionId, _opts) {
4708
+ return null;
4709
+ }
4710
+ buildSystemPrompt(config, _agentId) {
4711
+ return buildPiSystemPrompt(config);
4712
+ }
4713
+ };
4714
+
4187
4715
  // src/drivers/index.ts
4188
4716
  var driverFactories = {
4189
4717
  claude: () => new ClaudeDriver(),
@@ -4192,7 +4720,8 @@ var driverFactories = {
4192
4720
  cursor: () => new CursorDriver(),
4193
4721
  gemini: () => new GeminiDriver(),
4194
4722
  kimi: () => new KimiDriver(),
4195
- opencode: () => new OpenCodeDriver()
4723
+ opencode: () => new OpenCodeDriver(),
4724
+ pi: () => new PiDriver()
4196
4725
  };
4197
4726
  function getDriver(runtimeId) {
4198
4727
  const createDriver = driverFactories[runtimeId];
@@ -4205,7 +4734,7 @@ function getDriver(runtimeId) {
4205
4734
 
4206
4735
  // src/workspaces.ts
4207
4736
  import { readdir, rm, stat } from "fs/promises";
4208
- import path11 from "path";
4737
+ import path12 from "path";
4209
4738
  function isValidWorkspaceDirectoryName(directoryName) {
4210
4739
  return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
4211
4740
  }
@@ -4213,7 +4742,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
4213
4742
  if (!isValidWorkspaceDirectoryName(directoryName)) {
4214
4743
  return null;
4215
4744
  }
4216
- return path11.join(dataDir, directoryName);
4745
+ return path12.join(dataDir, directoryName);
4217
4746
  }
4218
4747
  function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
4219
4748
  return {
@@ -4262,7 +4791,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
4262
4791
  return summary;
4263
4792
  }
4264
4793
  const childSummaries = await Promise.all(
4265
- entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
4794
+ entries.map((entry) => summarizeWorkspaceEntry(path12.join(dirPath, entry.name), entry))
4266
4795
  );
4267
4796
  for (const childSummary of childSummaries) {
4268
4797
  summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
@@ -4281,7 +4810,7 @@ async function scanWorkspaceDirectories(dataDir) {
4281
4810
  if (!entry.isDirectory()) {
4282
4811
  return null;
4283
4812
  }
4284
- const dirPath = path11.join(dataDir, entry.name);
4813
+ const dirPath = path12.join(dataDir, entry.name);
4285
4814
  try {
4286
4815
  const summary = await summarizeWorkspaceDirectory(dirPath);
4287
4816
  return {
@@ -4364,6 +4893,7 @@ function classifyRuntimeError(message, httpStatus) {
4364
4893
  return "ProviderApiError";
4365
4894
  }
4366
4895
  if (/\btimeout|timed out\b/i.test(message)) return "TimeoutError";
4896
+ if (/stream closed before response\.completed|error decoding response body/i.test(message)) return "ProviderStreamError";
4367
4897
  if (/\brate.?limit|too many requests\b/i.test(message)) return "RateLimitError";
4368
4898
  if (/\bnot found\b/i.test(message)) return "NotFoundError";
4369
4899
  return "RuntimeError";
@@ -4538,12 +5068,12 @@ function findSessionJsonl(root, predicate) {
4538
5068
  for (const entry of entries) {
4539
5069
  if (++visited > maxEntries) return null;
4540
5070
  if (!entry.isFile() || !predicate(entry.name)) continue;
4541
- return path12.join(dir, entry.name);
5071
+ return path13.join(dir, entry.name);
4542
5072
  }
4543
5073
  for (const entry of entries) {
4544
5074
  if (++visited > maxEntries) return null;
4545
5075
  if (!entry.isDirectory()) continue;
4546
- const found = visit(path12.join(dir, entry.name), depth - 1);
5076
+ const found = visit(path13.join(dir, entry.name), depth - 1);
4547
5077
  if (found) return found;
4548
5078
  }
4549
5079
  return null;
@@ -4556,10 +5086,10 @@ function safeSessionFilename(value) {
4556
5086
  }
4557
5087
  function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
4558
5088
  try {
4559
- const dir = path12.join(fallbackDir, ".slock", "runtime-sessions");
4560
- mkdirSync4(dir, { recursive: true });
4561
- const filePath = path12.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
4562
- writeFileSync7(filePath, JSON.stringify({
5089
+ const dir = path13.join(fallbackDir, ".slock", "runtime-sessions");
5090
+ mkdirSync5(dir, { recursive: true });
5091
+ const filePath = path13.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
5092
+ writeFileSync8(filePath, JSON.stringify({
4563
5093
  type: "runtime_session_handoff",
4564
5094
  runtime,
4565
5095
  sessionId,
@@ -4578,7 +5108,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
4578
5108
  }
4579
5109
  }
4580
5110
  function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir) {
4581
- const directPath = path12.isAbsolute(sessionId) ? sessionId : null;
5111
+ const directPath = path13.isAbsolute(sessionId) ? sessionId : null;
4582
5112
  if (directPath) {
4583
5113
  try {
4584
5114
  if (statSync2(directPath).isFile()) {
@@ -4587,7 +5117,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
4587
5117
  } catch {
4588
5118
  }
4589
5119
  }
4590
- 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;
5120
+ 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;
4591
5121
  if (!resolvedPath && fallbackDir) {
4592
5122
  const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
4593
5123
  if (fallback) return fallback;
@@ -5238,12 +5768,21 @@ function classifyTerminalFailure(ap) {
5238
5768
  ].filter((value) => !!value);
5239
5769
  for (const text of candidates) {
5240
5770
  const lower = text.toLowerCase();
5241
- 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")) {
5771
+ 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)) {
5242
5772
  return text;
5243
5773
  }
5244
5774
  }
5245
5775
  return null;
5246
5776
  }
5777
+ function isProviderStreamFailureText(text) {
5778
+ return /stream closed before response\.completed|error decoding response body/i.test(text);
5779
+ }
5780
+ function isCodexProviderReconnectLog(text) {
5781
+ return /Reconnecting\.\.\.\s*\d+\s*\/\s*\d+/i.test(text);
5782
+ }
5783
+ function isCodexBenignTransportLog(text) {
5784
+ return /Falling back from WebSockets/i.test(text);
5785
+ }
5247
5786
  function hasDirectStdinRecoveryEvidence(ap) {
5248
5787
  const candidates = [
5249
5788
  ap.lastRuntimeError,
@@ -5887,26 +6426,26 @@ var AgentProcessManager = class _AgentProcessManager {
5887
6426
  this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
5888
6427
  try {
5889
6428
  const driver = this.driverResolver(config.runtime || "claude");
5890
- const agentDataDir = path12.join(this.dataDir, agentId);
6429
+ const agentDataDir = path13.join(this.dataDir, agentId);
5891
6430
  await mkdir(agentDataDir, { recursive: true });
5892
6431
  const runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
5893
- const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
6432
+ const memoryMdPath = path13.join(agentDataDir, "MEMORY.md");
5894
6433
  try {
5895
6434
  await access(memoryMdPath);
5896
6435
  } catch {
5897
6436
  const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
5898
6437
  await writeFile(memoryMdPath, initialMemoryMd);
5899
6438
  }
5900
- const notesDir = path12.join(agentDataDir, "notes");
6439
+ const notesDir = path13.join(agentDataDir, "notes");
5901
6440
  await mkdir(notesDir, { recursive: true });
5902
6441
  if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
5903
6442
  const seedFiles = buildOnboardingSeedFiles();
5904
6443
  for (const { relativePath, content } of seedFiles) {
5905
- const fullPath = path12.join(agentDataDir, relativePath);
6444
+ const fullPath = path13.join(agentDataDir, relativePath);
5906
6445
  try {
5907
6446
  await access(fullPath);
5908
6447
  } catch {
5909
- await mkdir(path12.dirname(fullPath), { recursive: true });
6448
+ await mkdir(path13.dirname(fullPath), { recursive: true });
5910
6449
  await writeFile(fullPath, content);
5911
6450
  }
5912
6451
  }
@@ -6095,8 +6634,24 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6095
6634
  proc.stderr?.on("data", (chunk) => {
6096
6635
  const text = chunk.toString().trim();
6097
6636
  if (!text) return;
6098
- if (/Reconnecting\.\.\.|Falling back from WebSockets/i.test(text)) return;
6099
6637
  const current = this.agents.get(agentId);
6638
+ if (driver.id === "codex" && isCodexProviderReconnectLog(text)) {
6639
+ if (current) {
6640
+ current.recentStderr = pushRecentStderr(current.recentStderr, text);
6641
+ }
6642
+ this.recordDaemonTrace("daemon.agent.provider_reconnect", {
6643
+ agentId,
6644
+ launchId: current?.launchId || void 0,
6645
+ runtime: config.runtime,
6646
+ model: config.model
6647
+ });
6648
+ this.broadcastActivity(agentId, "working", "Codex reconnecting to provider\u2026", [
6649
+ { kind: "text", text }
6650
+ ]);
6651
+ logger.info(`[Agent ${agentId} stderr]: ${text}`);
6652
+ return;
6653
+ }
6654
+ if (driver.id === "codex" && isCodexBenignTransportLog(text)) return;
6100
6655
  if (current) {
6101
6656
  current.recentStderr = pushRecentStderr(current.recentStderr, text);
6102
6657
  }
@@ -6240,10 +6795,20 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6240
6795
  }
6241
6796
  this.broadcastActivity(agentId, "online", "Process idle");
6242
6797
  } else {
6243
- this.idleAgentConfigs.delete(agentId);
6244
6798
  const reason = formatCrashReason(finalCode, finalSignal, ap);
6245
- logger.error(`[Agent ${agentId}] Process crashed (${reason}) \u2014 marking inactive`);
6246
- this.sendAgentStatus(agentId, "inactive", ap.launchId);
6799
+ if (terminalFailureDetail && isProviderStreamFailureText(terminalFailureDetail)) {
6800
+ this.idleAgentConfigs.set(agentId, {
6801
+ config: { ...ap.config, sessionId: ap.sessionId },
6802
+ sessionId: ap.sessionId,
6803
+ launchId: ap.launchId
6804
+ });
6805
+ logger.warn(`[Agent ${agentId}] Recoverable provider stream failure (${reason}) \u2014 keeping agent wakeable`);
6806
+ this.sendAgentStatus(agentId, "active", ap.launchId);
6807
+ } else {
6808
+ this.idleAgentConfigs.delete(agentId);
6809
+ logger.error(`[Agent ${agentId}] Process crashed (${reason}) \u2014 marking inactive`);
6810
+ this.sendAgentStatus(agentId, "inactive", ap.launchId);
6811
+ }
6247
6812
  if (terminalFailureDetail) {
6248
6813
  this.broadcastActivity(
6249
6814
  agentId,
@@ -6757,7 +7322,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6757
7322
  return true;
6758
7323
  }
6759
7324
  async resetWorkspace(agentId) {
6760
- const agentDataDir = path12.join(this.dataDir, agentId);
7325
+ const agentDataDir = path13.join(this.dataDir, agentId);
6761
7326
  try {
6762
7327
  await rm2(agentDataDir, { recursive: true, force: true });
6763
7328
  logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
@@ -6794,7 +7359,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6794
7359
  return result;
6795
7360
  }
6796
7361
  buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
6797
- const workspacePath = path12.join(this.dataDir, agentId);
7362
+ const workspacePath = path13.join(this.dataDir, agentId);
6798
7363
  return {
6799
7364
  agentId,
6800
7365
  launchId,
@@ -7051,7 +7616,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7051
7616
  }
7052
7617
  // Workspace file browsing
7053
7618
  async getFileTree(agentId, dirPath) {
7054
- const agentDir = path12.join(this.dataDir, agentId);
7619
+ const agentDir = path13.join(this.dataDir, agentId);
7055
7620
  try {
7056
7621
  await stat2(agentDir);
7057
7622
  } catch {
@@ -7059,8 +7624,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7059
7624
  }
7060
7625
  let targetDir = agentDir;
7061
7626
  if (dirPath) {
7062
- const resolved = path12.resolve(agentDir, dirPath);
7063
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
7627
+ const resolved = path13.resolve(agentDir, dirPath);
7628
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
7064
7629
  return [];
7065
7630
  }
7066
7631
  targetDir = resolved;
@@ -7068,14 +7633,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7068
7633
  return this.listDirectoryChildren(targetDir, agentDir);
7069
7634
  }
7070
7635
  async readFile(agentId, filePath) {
7071
- const agentDir = path12.join(this.dataDir, agentId);
7072
- const resolved = path12.resolve(agentDir, filePath);
7073
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
7636
+ const agentDir = path13.join(this.dataDir, agentId);
7637
+ const resolved = path13.resolve(agentDir, filePath);
7638
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
7074
7639
  throw new Error("Access denied");
7075
7640
  }
7076
7641
  const info = await stat2(resolved);
7077
7642
  if (info.isDirectory()) throw new Error("Cannot read a directory");
7078
- const ext = path12.extname(resolved).toLowerCase();
7643
+ const ext = path13.extname(resolved).toLowerCase();
7079
7644
  if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
7080
7645
  if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
7081
7646
  const content = await readFile(resolved, "utf-8");
@@ -7110,13 +7675,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7110
7675
  const agent = this.agents.get(agentId);
7111
7676
  const runtime = runtimeHint || agent?.config.runtime || "claude";
7112
7677
  const home = os6.homedir();
7113
- const workspaceDir = path12.join(this.dataDir, agentId);
7678
+ const workspaceDir = path13.join(this.dataDir, agentId);
7114
7679
  const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
7115
7680
  const globalResults = await Promise.all(
7116
- paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
7681
+ paths.global.map((p) => this.scanSkillsDir(path13.join(home, p)))
7117
7682
  );
7118
7683
  const workspaceResults = await Promise.all(
7119
- paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
7684
+ paths.workspace.map((p) => this.scanSkillsDir(path13.join(workspaceDir, p)))
7120
7685
  );
7121
7686
  const dedup = (skills) => {
7122
7687
  const seen = /* @__PURE__ */ new Set();
@@ -7145,7 +7710,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7145
7710
  const skills = [];
7146
7711
  for (const entry of entries) {
7147
7712
  if (entry.isDirectory() || entry.isSymbolicLink()) {
7148
- const skillMd = path12.join(dir, entry.name, "SKILL.md");
7713
+ const skillMd = path13.join(dir, entry.name, "SKILL.md");
7149
7714
  try {
7150
7715
  const content = await readFile(skillMd, "utf-8");
7151
7716
  const skill = this.parseSkillMd(entry.name, content);
@@ -7156,7 +7721,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7156
7721
  } else if (entry.name.endsWith(".md")) {
7157
7722
  const cmdName = entry.name.replace(/\.md$/, "");
7158
7723
  try {
7159
- const content = await readFile(path12.join(dir, entry.name), "utf-8");
7724
+ const content = await readFile(path13.join(dir, entry.name), "utf-8");
7160
7725
  const skill = this.parseSkillMd(cmdName, content);
7161
7726
  skill.sourcePath = dir;
7162
7727
  skills.push(skill);
@@ -8122,8 +8687,8 @@ ${RESPONSE_TARGET_HINT}`);
8122
8687
  const nodes = [];
8123
8688
  for (const entry of entries) {
8124
8689
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
8125
- const fullPath = path12.join(dir, entry.name);
8126
- const relativePath = path12.relative(rootDir, fullPath);
8690
+ const fullPath = path13.join(dir, entry.name);
8691
+ const relativePath = path13.relative(rootDir, fullPath);
8127
8692
  let info;
8128
8693
  try {
8129
8694
  info = await stat2(fullPath);
@@ -8428,9 +8993,9 @@ var ReminderCache = class {
8428
8993
 
8429
8994
  // src/machineLock.ts
8430
8995
  import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
8431
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
8996
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
8432
8997
  import os7 from "os";
8433
- import path13 from "path";
8998
+ import path14 from "path";
8434
8999
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
8435
9000
  var DaemonMachineLockConflictError = class extends Error {
8436
9001
  code = "DAEMON_MACHINE_LOCK_HELD";
@@ -8452,7 +9017,7 @@ function resolveDefaultMachineStateRoot() {
8452
9017
  return resolveSlockHomePath("machines");
8453
9018
  }
8454
9019
  function ownerPath(lockDir) {
8455
- return path13.join(lockDir, "owner.json");
9020
+ return path14.join(lockDir, "owner.json");
8456
9021
  }
8457
9022
  function readOwner(lockDir) {
8458
9023
  try {
@@ -8482,13 +9047,13 @@ function acquireDaemonMachineLock(options) {
8482
9047
  const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
8483
9048
  const fingerprint = apiKeyFingerprint(options.apiKey);
8484
9049
  const lockId = getDaemonMachineLockId(options.apiKey);
8485
- const machineDir = path13.join(rootDir, lockId);
8486
- const lockDir = path13.join(machineDir, "daemon.lock");
9050
+ const machineDir = path14.join(rootDir, lockId);
9051
+ const lockDir = path14.join(machineDir, "daemon.lock");
8487
9052
  const token = randomUUID2();
8488
- mkdirSync5(machineDir, { recursive: true });
9053
+ mkdirSync6(machineDir, { recursive: true });
8489
9054
  for (let attempt = 0; attempt < 2; attempt += 1) {
8490
9055
  try {
8491
- mkdirSync5(lockDir);
9056
+ mkdirSync6(lockDir);
8492
9057
  const owner = {
8493
9058
  pid: process.pid,
8494
9059
  token,
@@ -8498,7 +9063,7 @@ function acquireDaemonMachineLock(options) {
8498
9063
  apiKeyFingerprint: fingerprint.slice(0, 16)
8499
9064
  };
8500
9065
  try {
8501
- writeFileSync8(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
9066
+ writeFileSync9(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
8502
9067
  `, { mode: 384 });
8503
9068
  } catch (err) {
8504
9069
  rmSync3(lockDir, { recursive: true, force: true });
@@ -8535,8 +9100,8 @@ function acquireDaemonMachineLock(options) {
8535
9100
  }
8536
9101
 
8537
9102
  // src/localTraceSink.ts
8538
- import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
8539
- import path14 from "path";
9103
+ import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync10 } from "fs";
9104
+ import path15 from "path";
8540
9105
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
8541
9106
  var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
8542
9107
  var DEFAULT_MAX_FILES = 8;
@@ -8572,7 +9137,7 @@ var LocalRotatingTraceSink = class {
8572
9137
  currentSize = 0;
8573
9138
  sequence = 0;
8574
9139
  constructor(options) {
8575
- this.traceDir = path14.join(options.machineDir, "traces");
9140
+ this.traceDir = path15.join(options.machineDir, "traces");
8576
9141
  this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
8577
9142
  const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
8578
9143
  const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
@@ -8598,15 +9163,15 @@ var LocalRotatingTraceSink = class {
8598
9163
  return this.currentFile;
8599
9164
  }
8600
9165
  ensureFile(nextBytes) {
8601
- mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
9166
+ mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
8602
9167
  const nowMs = this.nowMsProvider();
8603
9168
  const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
8604
9169
  if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
8605
- this.currentFile = path14.join(
9170
+ this.currentFile = path15.join(
8606
9171
  this.traceDir,
8607
9172
  `daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
8608
9173
  );
8609
- writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
9174
+ writeFileSync10(this.currentFile, "", { flag: "a", mode: 384 });
8610
9175
  this.currentSize = statSync4(this.currentFile).size;
8611
9176
  this.currentFileOpenedAtMs = nowMs;
8612
9177
  this.pruneOldFiles();
@@ -8617,7 +9182,7 @@ var LocalRotatingTraceSink = class {
8617
9182
  const excess = files.length - this.maxFiles;
8618
9183
  if (excess <= 0) return;
8619
9184
  for (const file of files.slice(0, excess)) {
8620
- rmSync4(path14.join(this.traceDir, file), { force: true });
9185
+ rmSync4(path15.join(this.traceDir, file), { force: true });
8621
9186
  }
8622
9187
  }
8623
9188
  };
@@ -8704,11 +9269,11 @@ function isDiagnosticErrorAttr(key) {
8704
9269
  import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
8705
9270
  import { gzipSync } from "zlib";
8706
9271
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
8707
- import path15 from "path";
9272
+ import path16 from "path";
8708
9273
 
8709
9274
  // src/directUploadCapability.ts
8710
- function joinUrl(base, path17) {
8711
- return `${base.replace(/\/+$/, "")}${path17}`;
9275
+ function joinUrl(base, path18) {
9276
+ return `${base.replace(/\/+$/, "")}${path18}`;
8712
9277
  }
8713
9278
  function jsonHeaders(apiKey) {
8714
9279
  return {
@@ -8927,7 +9492,7 @@ var DaemonTraceBundleUploader = class {
8927
9492
  }, nextMs);
8928
9493
  }
8929
9494
  async findUploadCandidates() {
8930
- const traceDir = path15.join(this.options.machineDir, "traces");
9495
+ const traceDir = path16.join(this.options.machineDir, "traces");
8931
9496
  let names;
8932
9497
  try {
8933
9498
  names = await readdir3(traceDir);
@@ -8939,8 +9504,8 @@ var DaemonTraceBundleUploader = class {
8939
9504
  const currentFile = this.options.currentFileProvider?.();
8940
9505
  const candidates = [];
8941
9506
  for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
8942
- const file = path15.join(traceDir, name);
8943
- if (currentFile && path15.resolve(file) === path15.resolve(currentFile)) continue;
9507
+ const file = path16.join(traceDir, name);
9508
+ if (currentFile && path16.resolve(file) === path16.resolve(currentFile)) continue;
8944
9509
  if (await this.isUploaded(file)) continue;
8945
9510
  try {
8946
9511
  const info = await stat3(file);
@@ -9014,8 +9579,8 @@ var DaemonTraceBundleUploader = class {
9014
9579
  }
9015
9580
  }
9016
9581
  uploadStatePath(file) {
9017
- const stateDir = path15.join(this.options.machineDir, "trace-uploads");
9018
- return path15.join(stateDir, `${path15.basename(file)}.uploaded.json`);
9582
+ const stateDir = path16.join(this.options.machineDir, "trace-uploads");
9583
+ return path16.join(stateDir, `${path16.basename(file)}.uploaded.json`);
9019
9584
  }
9020
9585
  async isUploaded(file) {
9021
9586
  try {
@@ -9027,9 +9592,9 @@ var DaemonTraceBundleUploader = class {
9027
9592
  }
9028
9593
  async markUploaded(file, metadata) {
9029
9594
  const stateFile = this.uploadStatePath(file);
9030
- await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
9595
+ await mkdir2(path16.dirname(stateFile), { recursive: true, mode: 448 });
9031
9596
  await writeFile2(stateFile, `${JSON.stringify({
9032
- file: path15.basename(file),
9597
+ file: path16.basename(file),
9033
9598
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
9034
9599
  ...metadata
9035
9600
  }, null, 2)}
@@ -9051,7 +9616,7 @@ var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
9051
9616
  var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels"];
9052
9617
  var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
9053
9618
  var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
9054
- var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
9619
+ var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
9055
9620
  var RunnerCredentialMintError2 = class extends Error {
9056
9621
  code;
9057
9622
  retryable;
@@ -9087,9 +9652,9 @@ function runnerCredentialErrorDetail2(error) {
9087
9652
  async function waitForRunnerCredentialRetry2() {
9088
9653
  await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
9089
9654
  }
9090
- function parseDaemonCliArgs(args) {
9655
+ function parseDaemonCliArgs(args, env = {}) {
9091
9656
  let serverUrl = "";
9092
- let apiKey = "";
9657
+ let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
9093
9658
  for (let i = 0; i < args.length; i++) {
9094
9659
  if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
9095
9660
  if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
@@ -9106,23 +9671,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
9106
9671
  }
9107
9672
  }
9108
9673
  function resolveChatBridgePath(moduleUrl = import.meta.url) {
9109
- const dirname = path16.dirname(fileURLToPath(moduleUrl));
9110
- const jsPath = path16.resolve(dirname, "chat-bridge.js");
9674
+ const dirname = path17.dirname(fileURLToPath2(moduleUrl));
9675
+ const jsPath = path17.resolve(dirname, "chat-bridge.js");
9111
9676
  try {
9112
9677
  accessSync(jsPath);
9113
9678
  return jsPath;
9114
9679
  } catch {
9115
- return path16.resolve(dirname, "chat-bridge.ts");
9680
+ return path17.resolve(dirname, "chat-bridge.ts");
9116
9681
  }
9117
9682
  }
9118
9683
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
9119
- const thisDir = path16.dirname(fileURLToPath(moduleUrl));
9120
- const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
9684
+ const thisDir = path17.dirname(fileURLToPath2(moduleUrl));
9685
+ const bundledDistPath = path17.resolve(thisDir, "cli", "index.js");
9121
9686
  try {
9122
9687
  accessSync(bundledDistPath);
9123
9688
  return bundledDistPath;
9124
9689
  } catch {
9125
- const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
9690
+ const workspaceDistPath = path17.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
9126
9691
  accessSync(workspaceDistPath);
9127
9692
  return workspaceDistPath;
9128
9693
  }
@@ -9301,7 +9866,7 @@ var DaemonCore = class {
9301
9866
  }
9302
9867
  resolveMachineStateRoot() {
9303
9868
  if (this.options.machineStateDir) return this.options.machineStateDir;
9304
- if (this.options.dataDir) return path16.join(path16.dirname(this.options.dataDir), "machines");
9869
+ if (this.options.dataDir) return path17.join(path17.dirname(this.options.dataDir), "machines");
9305
9870
  return resolveDefaultMachineStateRoot();
9306
9871
  }
9307
9872
  shouldEnableLocalTrace() {
@@ -9803,6 +10368,8 @@ var DaemonCore = class {
9803
10368
  };
9804
10369
 
9805
10370
  export {
10371
+ DAEMON_API_KEY_ENV,
10372
+ scrubDaemonAuthEnv,
9806
10373
  resolveWorkspaceDirectoryPath,
9807
10374
  scanWorkspaceDirectories,
9808
10375
  deleteWorkspaceDirectory,