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

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" });
@@ -1861,6 +1908,14 @@ function unregisterAgentCredentialProxyForLaunch(input) {
1861
1908
  var shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
1862
1909
  var DEFAULT_ACTIVE_CAPABILITIES = "send,read,mentions,tasks,reactions,server,channels";
1863
1910
  var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
1911
+ function windowsUtf8Env() {
1912
+ return {
1913
+ PYTHONIOENCODING: "utf-8",
1914
+ PYTHONUTF8: "1",
1915
+ LANG: "C.UTF-8",
1916
+ LC_ALL: "C.UTF-8"
1917
+ };
1918
+ }
1864
1919
  function runtimeContextEnv(config) {
1865
1920
  const ctx = config.runtimeContext;
1866
1921
  if (!ctx) return {};
@@ -1919,9 +1974,17 @@ ${posixCredentialPrefix}exec ${shellSingleQuote(process.execPath)} ${shellSingle
1919
1974
  set "SLOCK_AGENT_PROXY_TOKEN_FILE=${agentCredentialProxyTokenFile}"\r
1920
1975
  set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
1921
1976
  ` : "";
1922
- const cmdBody = `@echo off\r
1923
- ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
1924
- `;
1977
+ const cmdBody = [
1978
+ "@echo off",
1979
+ "set PYTHONIOENCODING=utf-8",
1980
+ "set PYTHONUTF8=1",
1981
+ "set LANG=C.UTF-8",
1982
+ "set LC_ALL=C.UTF-8",
1983
+ "chcp 65001 >NUL 2>NUL",
1984
+ cmdCredentialLine.trimEnd(),
1985
+ `"${process.execPath}" "${ctx.slockCliPath}" %*`,
1986
+ ""
1987
+ ].filter((line) => line.length > 0).join("\r\n") + "\r\n";
1925
1988
  writeFileSync(cmdWrapper, cmdBody);
1926
1989
  }
1927
1990
  const wrapperPath = platform === "win32" ? path2.join(slockDir, "slock.cmd") : posixWrapper;
@@ -1930,6 +1993,7 @@ ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
1930
1993
  FORCE_COLOR: "0",
1931
1994
  ...ctx.config.envVars || {},
1932
1995
  ...extraEnv,
1996
+ ...platform === "win32" ? windowsUtf8Env() : {},
1933
1997
  ...runtimeContextEnv(ctx.config),
1934
1998
  [SLOCK_HOME_ENV]: slockHome,
1935
1999
  SLOCK_AGENT_ID: ctx.agentId,
@@ -1938,7 +2002,7 @@ ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
1938
2002
  ...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
1939
2003
  PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
1940
2004
  };
1941
- delete spawnEnv.SLOCK_AGENT_TOKEN;
2005
+ scrubDaemonChildEnv(spawnEnv);
1942
2006
  delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY;
1943
2007
  delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY_FILE;
1944
2008
  delete spawnEnv.SLOCK_AGENT_PROXY_URL;
@@ -1985,7 +2049,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn) {
1985
2049
  }
1986
2050
  function resolveCommandOnPath(command, deps = {}) {
1987
2051
  const platform = deps.platform ?? process.platform;
1988
- const env = deps.env ?? process.env;
2052
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
1989
2053
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
1990
2054
  if (platform === "win32") {
1991
2055
  return resolveCommandOnWindows(command, env, execFileSyncFn);
@@ -2010,7 +2074,7 @@ function firstExistingPath(candidates, deps = {}) {
2010
2074
  return null;
2011
2075
  }
2012
2076
  function readCommandVersion(command, args = [], deps = {}) {
2013
- const env = deps.env ?? process.env;
2077
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
2014
2078
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
2015
2079
  try {
2016
2080
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -3306,7 +3370,7 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
3306
3370
  }
3307
3371
  function runCursorModelsCommand() {
3308
3372
  return spawnSync("cursor-agent", ["models"], {
3309
- env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
3373
+ env: scrubDaemonChildEnv({ ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" }),
3310
3374
  encoding: "utf8",
3311
3375
  timeout: 5e3
3312
3376
  });
@@ -3353,7 +3417,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
3353
3417
  }
3354
3418
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
3355
3419
  const existsSyncFn = deps.existsSyncFn ?? existsSync6;
3356
- const env = deps.env ?? process.env;
3420
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
3357
3421
  const winPath = path8.win32;
3358
3422
  let geminiEntry = null;
3359
3423
  try {
@@ -3527,13 +3591,16 @@ var GeminiDriver = class {
3527
3591
  // src/drivers/kimi.ts
3528
3592
  import { randomUUID } from "crypto";
3529
3593
  import { spawn as spawn6 } from "child_process";
3530
- import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
3594
+ import { chmodSync, existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
3531
3595
  import os4 from "os";
3532
3596
  import path9 from "path";
3533
3597
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
3534
3598
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
3535
3599
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
3536
3600
  var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
3601
+ var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
3602
+ var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
3603
+ var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
3537
3604
  function parseToolArguments(raw) {
3538
3605
  if (typeof raw !== "string") return raw;
3539
3606
  try {
@@ -3542,6 +3609,73 @@ function parseToolArguments(raw) {
3542
3609
  return raw;
3543
3610
  }
3544
3611
  }
3612
+ function readKimiConfigSource(home = os4.homedir(), env = process.env) {
3613
+ const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
3614
+ if (inlineConfig && inlineConfig.trim()) {
3615
+ return {
3616
+ raw: inlineConfig,
3617
+ explicitPath: null,
3618
+ sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
3619
+ };
3620
+ }
3621
+ const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
3622
+ const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
3623
+ try {
3624
+ return {
3625
+ raw: readFileSync3(configPath, "utf8"),
3626
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
3627
+ sourcePath: configPath
3628
+ };
3629
+ } catch {
3630
+ return {
3631
+ raw: null,
3632
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
3633
+ sourcePath: configPath
3634
+ };
3635
+ }
3636
+ }
3637
+ function buildKimiSpawnEnv(env = process.env) {
3638
+ const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
3639
+ delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
3640
+ delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
3641
+ return scrubDaemonChildEnv(spawnEnv);
3642
+ }
3643
+ function buildKimiEffectiveEnv(ctx, overrideEnv) {
3644
+ return {
3645
+ ...process.env,
3646
+ ...ctx.config.envVars || {},
3647
+ ...overrideEnv || {}
3648
+ };
3649
+ }
3650
+ function buildKimiLaunchOptions(ctx, opts = {}) {
3651
+ const env = buildKimiEffectiveEnv(ctx, opts.env);
3652
+ const source = readKimiConfigSource(opts.home ?? os4.homedir(), env);
3653
+ const args = [];
3654
+ let configFilePath = null;
3655
+ let configContent = null;
3656
+ if (source.explicitPath) {
3657
+ configFilePath = source.explicitPath;
3658
+ } else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
3659
+ configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
3660
+ configContent = source.raw;
3661
+ if (opts.writeGeneratedConfig !== false) {
3662
+ writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
3663
+ chmodSync(configFilePath, 384);
3664
+ }
3665
+ }
3666
+ if (configFilePath) {
3667
+ args.push("--config-file", configFilePath);
3668
+ }
3669
+ if (ctx.config.model && ctx.config.model !== "default") {
3670
+ args.push("--model", ctx.config.model);
3671
+ }
3672
+ return {
3673
+ args,
3674
+ env: buildKimiSpawnEnv(env),
3675
+ configFilePath,
3676
+ configContent
3677
+ };
3678
+ }
3545
3679
  function resolveKimiSpawn(commandArgs, deps = {}) {
3546
3680
  return {
3547
3681
  command: resolveCommandOnPath("kimi", deps) ?? "kimi",
@@ -3565,7 +3699,25 @@ var KimiDriver = class {
3565
3699
  };
3566
3700
  model = {
3567
3701
  detectedModelsVerifiedAs: "launchable",
3568
- toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
3702
+ toLaunchSpec: (modelId, ctx, opts) => {
3703
+ if (!ctx) return { args: ["--model", modelId] };
3704
+ const launchCtx = {
3705
+ ...ctx,
3706
+ config: {
3707
+ ...ctx.config,
3708
+ model: modelId
3709
+ }
3710
+ };
3711
+ const launch = buildKimiLaunchOptions(launchCtx, {
3712
+ home: opts?.home,
3713
+ writeGeneratedConfig: false
3714
+ });
3715
+ return {
3716
+ args: launch.args,
3717
+ env: launch.env,
3718
+ configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
3719
+ };
3720
+ }
3569
3721
  };
3570
3722
  supportsStdinNotification = true;
3571
3723
  mcpToolPrefix = "";
@@ -3619,6 +3771,7 @@ var KimiDriver = class {
3619
3771
  }
3620
3772
  }
3621
3773
  }), "utf8");
3774
+ const launch = buildKimiLaunchOptions(ctx);
3622
3775
  const args = [
3623
3776
  "--wire",
3624
3777
  "--yolo",
@@ -3627,14 +3780,15 @@ var KimiDriver = class {
3627
3780
  "--mcp-config-file",
3628
3781
  mcpConfigPath,
3629
3782
  "--session",
3630
- this.sessionId
3783
+ this.sessionId,
3784
+ ...launch.args
3631
3785
  ];
3632
3786
  if (ctx.config.model && ctx.config.model !== "default") {
3633
3787
  args.push("--model", ctx.config.model);
3634
3788
  }
3635
3789
  const spawnEnv = prepareCliTransport(ctx, { NO_COLOR: "1" }).spawnEnv;
3636
- const launch = resolveKimiSpawn(args);
3637
- const proc = spawn6(launch.command, launch.args, {
3790
+ const spawnTarget = resolveKimiSpawn(args);
3791
+ const proc = spawn6(spawnTarget.command, spawnTarget.args, {
3638
3792
  cwd: ctx.workingDirectory,
3639
3793
  stdio: ["pipe", "pipe", "pipe"],
3640
3794
  env: spawnEnv,
@@ -3642,7 +3796,7 @@ var KimiDriver = class {
3642
3796
  // and has an 8191-character command-line limit. Kimi's official
3643
3797
  // installer/uv entrypoint is an executable, so launch it directly and
3644
3798
  // keep prompts on stdin / files instead of routing through cmd.exe.
3645
- shell: launch.shell
3799
+ shell: spawnTarget.shell
3646
3800
  });
3647
3801
  proc.stdin?.write(JSON.stringify({
3648
3802
  jsonrpc: "2.0",
@@ -3758,14 +3912,9 @@ var KimiDriver = class {
3758
3912
  return detectKimiModels();
3759
3913
  }
3760
3914
  };
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
- }
3915
+ function detectKimiModels(home = os4.homedir(), opts = {}) {
3916
+ const raw = readKimiConfigSource(home, opts.env).raw;
3917
+ if (raw === null) return null;
3769
3918
  const models = [];
3770
3919
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
3771
3920
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -3786,7 +3935,7 @@ function detectKimiModels(home = os4.homedir()) {
3786
3935
 
3787
3936
  // src/drivers/opencode.ts
3788
3937
  import { spawn as spawn7, spawnSync as spawnSync2 } from "child_process";
3789
- import { readFileSync as readFileSync4 } from "fs";
3938
+ import { existsSync as existsSync8, readFileSync as readFileSync4 } from "fs";
3790
3939
  import os5 from "os";
3791
3940
  import path10 from "path";
3792
3941
  var CHAT_MCP_SERVER_NAME = "chat";
@@ -4028,7 +4177,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
4028
4177
  const platform = deps.platform ?? process.platform;
4029
4178
  const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
4030
4179
  const result = spawnSyncFn("opencode", ["models"], {
4031
- env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
4180
+ env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
4032
4181
  encoding: "utf8",
4033
4182
  timeout: 5e3,
4034
4183
  shell: platform === "win32"
@@ -4039,6 +4188,102 @@ function runOpenCodeModelsCommand(home, deps = {}) {
4039
4188
  error: result.error
4040
4189
  };
4041
4190
  }
4191
+ function isWindowsCommandShim(commandPath) {
4192
+ const ext = path10.win32.extname(commandPath).toLowerCase();
4193
+ return ext === ".cmd" || ext === ".bat";
4194
+ }
4195
+ function opencodePackageEntryCandidates(packageRoot) {
4196
+ const winPath = path10.win32;
4197
+ return [
4198
+ winPath.join(packageRoot, "bin", "opencode.exe"),
4199
+ winPath.join(packageRoot, "bin", "opencode.js"),
4200
+ winPath.join(packageRoot, "bin", "opencode.mjs"),
4201
+ winPath.join(packageRoot, "dist", "index.js")
4202
+ ];
4203
+ }
4204
+ function openCodeSpecForEntry(entry, commandArgs) {
4205
+ if (path10.win32.extname(entry).toLowerCase() === ".exe") {
4206
+ return { command: entry, args: commandArgs, shell: false };
4207
+ }
4208
+ return { command: process.execPath, args: [entry, ...commandArgs], shell: false };
4209
+ }
4210
+ function resolveWindowsOpenCodePackageEntry(commandPath, deps = {}) {
4211
+ const existsSyncFn = deps.existsSyncFn ?? existsSync8;
4212
+ const execFileSyncFn = deps.execFileSyncFn;
4213
+ const env = deps.env ?? process.env;
4214
+ const winPath = path10.win32;
4215
+ const candidates = [];
4216
+ if (execFileSyncFn) {
4217
+ try {
4218
+ const globalRoot = String(execFileSyncFn("npm", ["root", "-g"], {
4219
+ encoding: "utf8",
4220
+ stdio: ["ignore", "pipe", "ignore"],
4221
+ env
4222
+ })).trim();
4223
+ if (globalRoot) {
4224
+ candidates.push(...opencodePackageEntryCandidates(winPath.join(globalRoot, "opencode-ai")));
4225
+ }
4226
+ } catch {
4227
+ }
4228
+ }
4229
+ if (commandPath) {
4230
+ const commandDir = winPath.dirname(commandPath);
4231
+ candidates.push(...opencodePackageEntryCandidates(winPath.join(commandDir, "node_modules", "opencode-ai")));
4232
+ candidates.push(...extractWindowsShimTargets(commandPath, deps));
4233
+ }
4234
+ for (const candidate of candidates) {
4235
+ if (existsSyncFn(candidate)) return candidate;
4236
+ }
4237
+ return null;
4238
+ }
4239
+ function extractWindowsShimTargets(commandPath, deps = {}) {
4240
+ if (!isWindowsCommandShim(commandPath)) return [];
4241
+ const readFileSyncFn = deps.readFileSyncFn ?? readFileSync4;
4242
+ const commandDir = path10.win32.dirname(commandPath);
4243
+ let raw;
4244
+ try {
4245
+ raw = String(readFileSyncFn(commandPath, "utf8"));
4246
+ } catch {
4247
+ return [];
4248
+ }
4249
+ const candidates = [];
4250
+ const dp0Pattern = /%~dp0\\?([^"\r\n]*?opencode\.(?:exe|js|mjs|cjs))/gi;
4251
+ for (const match of raw.matchAll(dp0Pattern)) {
4252
+ const relative = match[1]?.replace(/^\\+/, "");
4253
+ if (relative) candidates.push(path10.win32.normalize(path10.win32.join(commandDir, relative)));
4254
+ }
4255
+ return candidates;
4256
+ }
4257
+ function resolveOpenCodeSpawn(commandArgs, deps = {}) {
4258
+ const platform = deps.platform ?? process.platform;
4259
+ if (platform !== "win32") {
4260
+ return {
4261
+ command: resolveCommandOnPath("opencode", deps) ?? "opencode",
4262
+ args: commandArgs,
4263
+ shell: false
4264
+ };
4265
+ }
4266
+ const command = resolveCommandOnPath("opencode", deps);
4267
+ if (command && path10.win32.extname(command).toLowerCase() === ".exe") {
4268
+ return { command, args: commandArgs, shell: false };
4269
+ }
4270
+ const packageEntry = resolveWindowsOpenCodePackageEntry(command, deps);
4271
+ if (packageEntry) return openCodeSpecForEntry(packageEntry, commandArgs);
4272
+ if (command && !isWindowsCommandShim(command)) {
4273
+ return { command, args: commandArgs, shell: false };
4274
+ }
4275
+ throw new Error(
4276
+ "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."
4277
+ );
4278
+ }
4279
+ function readOpenCodeVersion(deps = {}) {
4280
+ try {
4281
+ const launch = resolveOpenCodeSpawn([], deps);
4282
+ return readCommandVersion(launch.command, launch.args, deps);
4283
+ } catch {
4284
+ return null;
4285
+ }
4286
+ }
4042
4287
  function isSystemFirstMessageTask(message) {
4043
4288
  return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX);
4044
4289
  }
@@ -4081,7 +4326,7 @@ var OpenCodeDriver = class {
4081
4326
  model: modelId
4082
4327
  }
4083
4328
  };
4084
- const version = readCommandVersion("opencode");
4329
+ const version = readOpenCodeVersion();
4085
4330
  const launch = buildOpenCodeLaunchOptions(launchCtx, opts?.home, version);
4086
4331
  return {
4087
4332
  args: launch.args,
@@ -4102,8 +4347,13 @@ var OpenCodeDriver = class {
4102
4347
  sessionId = null;
4103
4348
  sessionAnnounced = false;
4104
4349
  probe() {
4105
- if (!resolveCommandOnPath("opencode")) return { available: false };
4106
- const version = readCommandVersion("opencode") || void 0;
4350
+ let version;
4351
+ try {
4352
+ const launch = resolveOpenCodeSpawn([]);
4353
+ version = readCommandVersion(launch.command, launch.args);
4354
+ } catch {
4355
+ return { available: false };
4356
+ }
4107
4357
  const unsupportedMessage = unsupportedOpenCodeVersionMessage(version);
4108
4358
  if (unsupportedMessage) {
4109
4359
  return {
@@ -4111,7 +4361,7 @@ var OpenCodeDriver = class {
4111
4361
  version: `${version} (requires >= ${MIN_SUPPORTED_OPENCODE_VERSION})`
4112
4362
  };
4113
4363
  }
4114
- return { available: true, version };
4364
+ return { available: true, version: version ?? void 0 };
4115
4365
  }
4116
4366
  async detectModels() {
4117
4367
  return detectOpenCodeModels();
@@ -4119,17 +4369,18 @@ var OpenCodeDriver = class {
4119
4369
  spawn(ctx) {
4120
4370
  this.sessionId = ctx.config.sessionId || null;
4121
4371
  this.sessionAnnounced = false;
4122
- const version = readCommandVersion("opencode");
4372
+ const version = readOpenCodeVersion();
4123
4373
  const unsupportedMessage = unsupportedOpenCodeVersionMessage(version);
4124
4374
  if (unsupportedMessage) {
4125
4375
  throw new Error(unsupportedMessage);
4126
4376
  }
4127
4377
  const launch = buildOpenCodeLaunchOptions(ctx, os5.homedir(), version);
4128
- const proc = spawn7("opencode", launch.args, {
4378
+ const spawnSpec = resolveOpenCodeSpawn(launch.args);
4379
+ const proc = spawn7(spawnSpec.command, spawnSpec.args, {
4129
4380
  cwd: ctx.workingDirectory,
4130
4381
  stdio: ["pipe", "pipe", "pipe"],
4131
4382
  env: launch.env,
4132
- shell: process.platform === "win32"
4383
+ shell: spawnSpec.shell
4133
4384
  });
4134
4385
  proc.stdin?.end();
4135
4386
  return { process: proc };
@@ -4187,6 +4438,297 @@ var OpenCodeDriver = class {
4187
4438
  }
4188
4439
  };
4189
4440
 
4441
+ // src/drivers/pi.ts
4442
+ import { spawn as spawn8 } from "child_process";
4443
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
4444
+ import path11 from "path";
4445
+ import { fileURLToPath } from "url";
4446
+ import { getAgentDir, VERSION as PI_SDK_VERSION } from "@earendil-works/pi-coding-agent";
4447
+ var CHAT_MCP_TOOL_PREFIX2 = "chat_";
4448
+ var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
4449
+ var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
4450
+ var MIN_SUPPORTED_PI_VERSION = "0.74.0";
4451
+ function parseSemver2(version) {
4452
+ const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
4453
+ if (!match) return null;
4454
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
4455
+ }
4456
+ function isSupportedPiVersion(version) {
4457
+ if (!version) return true;
4458
+ const actual = parseSemver2(version);
4459
+ const minimum = parseSemver2(MIN_SUPPORTED_PI_VERSION);
4460
+ if (!actual || !minimum) return true;
4461
+ for (let i = 0; i < 3; i += 1) {
4462
+ if (actual[i] > minimum[i]) return true;
4463
+ if (actual[i] < minimum[i]) return false;
4464
+ }
4465
+ return true;
4466
+ }
4467
+ function unsupportedPiVersionMessage(version) {
4468
+ if (!version || isSupportedPiVersion(version)) return null;
4469
+ 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.`;
4470
+ }
4471
+ function probePi(version = PI_SDK_VERSION) {
4472
+ const unsupportedMessage = unsupportedPiVersionMessage(version);
4473
+ if (unsupportedMessage) {
4474
+ return {
4475
+ available: false,
4476
+ version: `${version} (requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION})`
4477
+ };
4478
+ }
4479
+ return { available: true, version };
4480
+ }
4481
+ function resolvePiSdkRunnerPath(moduleUrl = import.meta.url) {
4482
+ const moduleDir = path11.dirname(fileURLToPath(moduleUrl));
4483
+ const sourceSibling = path11.join(moduleDir, "piSdkRunner.ts");
4484
+ if (existsSync9(sourceSibling)) return sourceSibling;
4485
+ const bundledEntry = path11.join(moduleDir, "drivers", "piSdkRunner.js");
4486
+ if (existsSync9(bundledEntry)) return bundledEntry;
4487
+ return path11.join(moduleDir, "piSdkRunner.js");
4488
+ }
4489
+ function buildPiSdkNodeArgs(runnerPath = resolvePiSdkRunnerPath()) {
4490
+ if (runnerPath.endsWith(".ts")) {
4491
+ return [...process.execArgv, runnerPath];
4492
+ }
4493
+ return [runnerPath];
4494
+ }
4495
+ function buildPiLaunchOptions(ctx, opts = {}) {
4496
+ const command = opts.command ?? process.execPath;
4497
+ const piDir = path11.join(ctx.workingDirectory, ".slock", "pi");
4498
+ const sessionDir = path11.join(piDir, "sessions");
4499
+ mkdirSync4(sessionDir, { recursive: true });
4500
+ const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
4501
+ const runnerPath = opts.runnerPath ?? resolvePiSdkRunnerPath();
4502
+ const agentDir = opts.agentDir ?? getAgentDir();
4503
+ const runnerConfigPath = path11.join(
4504
+ piDir,
4505
+ `sdk-run-${(ctx.launchId || "launch").replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`
4506
+ );
4507
+ const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
4508
+ const runnerConfig = {
4509
+ cwd: ctx.workingDirectory,
4510
+ agentDir,
4511
+ sessionDir,
4512
+ sessionId: ctx.config.sessionId || null,
4513
+ standingPrompt: ctx.standingPrompt,
4514
+ prompt: turnPrompt,
4515
+ model: ctx.config.model && ctx.config.model !== "default" ? ctx.config.model : null
4516
+ };
4517
+ writeFileSync7(runnerConfigPath, `${JSON.stringify(runnerConfig)}
4518
+ `, { encoding: "utf8", mode: 384 });
4519
+ const args = [
4520
+ ...buildPiSdkNodeArgs(runnerPath),
4521
+ "--config",
4522
+ runnerConfigPath
4523
+ ];
4524
+ return {
4525
+ command,
4526
+ args,
4527
+ env: slock.spawnEnv,
4528
+ sessionDir,
4529
+ agentDir,
4530
+ runnerConfigPath,
4531
+ sdkVersion: PI_SDK_VERSION
4532
+ };
4533
+ }
4534
+ function isSystemFirstMessageTask2(message) {
4535
+ return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
4536
+ }
4537
+ function buildPiSystemPrompt(config) {
4538
+ return buildCliTransportSystemPrompt(config, {
4539
+ toolPrefix: CHAT_MCP_TOOL_PREFIX2,
4540
+ extraCriticalRules: [
4541
+ "- 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."
4542
+ ],
4543
+ postStartupNotes: [
4544
+ "**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."
4545
+ ],
4546
+ includeStdinNotificationSection: false,
4547
+ messageNotificationStyle: "poll"
4548
+ });
4549
+ }
4550
+ function contentText(content) {
4551
+ if (!content) return "";
4552
+ const chunks = [];
4553
+ for (const item of content) {
4554
+ if (item.type === "text" && typeof item.text === "string") {
4555
+ chunks.push(item.text);
4556
+ }
4557
+ }
4558
+ return chunks.join("");
4559
+ }
4560
+ function apiKeyErrorMessage(line) {
4561
+ const trimmed = line.trim();
4562
+ if (!trimmed) return null;
4563
+ if (/no api key found/i.test(trimmed)) return trimmed;
4564
+ if (/api key.+required/i.test(trimmed)) return trimmed;
4565
+ if (/no models available/i.test(trimmed)) return trimmed;
4566
+ return null;
4567
+ }
4568
+ var PiDriver = class {
4569
+ id = "pi";
4570
+ lifecycle = {
4571
+ kind: "per_turn",
4572
+ start: "defer_until_concrete_message",
4573
+ exit: "terminate_on_turn_end",
4574
+ inFlightWake: "coalesce_into_pending"
4575
+ };
4576
+ communication = {
4577
+ chat: "slock_cli",
4578
+ runtimeControl: "none"
4579
+ };
4580
+ session = {
4581
+ recovery: "resume_or_fresh"
4582
+ };
4583
+ model = {
4584
+ detectedModelsVerifiedAs: "launchable",
4585
+ toLaunchSpec: (modelId, ctx) => {
4586
+ if (!ctx) return modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] };
4587
+ const launchCtx = {
4588
+ ...ctx,
4589
+ config: {
4590
+ ...ctx.config,
4591
+ model: modelId
4592
+ }
4593
+ };
4594
+ const launch = buildPiLaunchOptions(launchCtx);
4595
+ return {
4596
+ args: launch.args,
4597
+ env: launch.env,
4598
+ configFiles: [launch.runnerConfigPath],
4599
+ params: {
4600
+ agentDir: launch.agentDir,
4601
+ sessionDir: launch.sessionDir,
4602
+ sdkVersion: launch.sdkVersion,
4603
+ resources: "extensions/skills/prompt-templates/themes/context-files disabled by Slock policy"
4604
+ }
4605
+ };
4606
+ }
4607
+ };
4608
+ supportsStdinNotification = false;
4609
+ mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
4610
+ busyDeliveryMode = "none";
4611
+ terminateProcessOnTurnEnd = true;
4612
+ deferSpawnUntilMessage = true;
4613
+ usesSlockCliForCommunication = true;
4614
+ sessionId = null;
4615
+ sessionAnnounced = false;
4616
+ apiKeyErrorAnnounced = false;
4617
+ turnEnded = false;
4618
+ assistantTextByMessageId = /* @__PURE__ */ new Map();
4619
+ shouldDeferWakeMessage(message) {
4620
+ return isSystemFirstMessageTask2(message);
4621
+ }
4622
+ probe() {
4623
+ return probePi();
4624
+ }
4625
+ async detectModels() {
4626
+ return null;
4627
+ }
4628
+ spawn(ctx) {
4629
+ this.sessionId = ctx.config.sessionId || null;
4630
+ this.sessionAnnounced = false;
4631
+ this.apiKeyErrorAnnounced = false;
4632
+ this.turnEnded = false;
4633
+ this.assistantTextByMessageId.clear();
4634
+ const unsupportedMessage = unsupportedPiVersionMessage(PI_SDK_VERSION);
4635
+ if (unsupportedMessage) throw new Error(unsupportedMessage);
4636
+ const launch = buildPiLaunchOptions(ctx);
4637
+ const proc = spawn8(launch.command, launch.args, {
4638
+ cwd: ctx.workingDirectory,
4639
+ stdio: ["pipe", "pipe", "pipe"],
4640
+ env: launch.env,
4641
+ shell: false
4642
+ });
4643
+ proc.stdin?.end();
4644
+ return { process: proc };
4645
+ }
4646
+ parseLine(line) {
4647
+ let event;
4648
+ try {
4649
+ event = JSON.parse(line);
4650
+ } catch {
4651
+ if (this.apiKeyErrorAnnounced) return [];
4652
+ const message = apiKeyErrorMessage(line);
4653
+ if (!message) return [];
4654
+ this.apiKeyErrorAnnounced = true;
4655
+ this.turnEnded = true;
4656
+ return [
4657
+ { kind: "error", message },
4658
+ { kind: "turn_end", sessionId: this.sessionId || void 0 }
4659
+ ];
4660
+ }
4661
+ const events = [];
4662
+ if (event.type === "session" && event.id) {
4663
+ this.sessionId = event.id;
4664
+ }
4665
+ if (!this.sessionAnnounced && this.sessionId) {
4666
+ events.push({ kind: "session_init", sessionId: this.sessionId });
4667
+ this.sessionAnnounced = true;
4668
+ }
4669
+ switch (event.type) {
4670
+ case "agent_start":
4671
+ case "turn_start":
4672
+ events.push({ kind: "thinking", text: "" });
4673
+ break;
4674
+ case "message_update":
4675
+ case "message_end":
4676
+ if (event.message?.role === "assistant") {
4677
+ const key = event.message.id || "current";
4678
+ const currentText = contentText(event.message.content);
4679
+ const previousText = this.assistantTextByMessageId.get(key) ?? "";
4680
+ if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
4681
+ events.push({ kind: "text", text: currentText.slice(previousText.length) });
4682
+ } else if (currentText && currentText !== previousText) {
4683
+ events.push({ kind: "text", text: currentText });
4684
+ }
4685
+ this.assistantTextByMessageId.set(key, currentText);
4686
+ if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
4687
+ events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
4688
+ }
4689
+ }
4690
+ break;
4691
+ case "tool_execution_start":
4692
+ events.push({
4693
+ kind: "tool_call",
4694
+ name: event.toolName || "unknown_tool",
4695
+ input: event.args
4696
+ });
4697
+ break;
4698
+ case "tool_execution_end":
4699
+ events.push({
4700
+ kind: "tool_output",
4701
+ name: event.toolName || "unknown_tool"
4702
+ });
4703
+ if (event.isError) {
4704
+ events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
4705
+ }
4706
+ break;
4707
+ case "compaction_start":
4708
+ events.push({ kind: "compaction_started" });
4709
+ break;
4710
+ case "compaction_end":
4711
+ events.push({ kind: "compaction_finished" });
4712
+ if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
4713
+ break;
4714
+ case "turn_end":
4715
+ case "agent_end":
4716
+ if (!this.turnEnded) {
4717
+ events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
4718
+ this.turnEnded = true;
4719
+ }
4720
+ break;
4721
+ }
4722
+ return events;
4723
+ }
4724
+ encodeStdinMessage(_text, _sessionId, _opts) {
4725
+ return null;
4726
+ }
4727
+ buildSystemPrompt(config, _agentId) {
4728
+ return buildPiSystemPrompt(config);
4729
+ }
4730
+ };
4731
+
4190
4732
  // src/drivers/index.ts
4191
4733
  var driverFactories = {
4192
4734
  claude: () => new ClaudeDriver(),
@@ -4195,7 +4737,8 @@ var driverFactories = {
4195
4737
  cursor: () => new CursorDriver(),
4196
4738
  gemini: () => new GeminiDriver(),
4197
4739
  kimi: () => new KimiDriver(),
4198
- opencode: () => new OpenCodeDriver()
4740
+ opencode: () => new OpenCodeDriver(),
4741
+ pi: () => new PiDriver()
4199
4742
  };
4200
4743
  function getDriver(runtimeId) {
4201
4744
  const createDriver = driverFactories[runtimeId];
@@ -4208,7 +4751,7 @@ function getDriver(runtimeId) {
4208
4751
 
4209
4752
  // src/workspaces.ts
4210
4753
  import { readdir, rm, stat } from "fs/promises";
4211
- import path11 from "path";
4754
+ import path12 from "path";
4212
4755
  function isValidWorkspaceDirectoryName(directoryName) {
4213
4756
  return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
4214
4757
  }
@@ -4216,7 +4759,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
4216
4759
  if (!isValidWorkspaceDirectoryName(directoryName)) {
4217
4760
  return null;
4218
4761
  }
4219
- return path11.join(dataDir, directoryName);
4762
+ return path12.join(dataDir, directoryName);
4220
4763
  }
4221
4764
  function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
4222
4765
  return {
@@ -4265,7 +4808,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
4265
4808
  return summary;
4266
4809
  }
4267
4810
  const childSummaries = await Promise.all(
4268
- entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
4811
+ entries.map((entry) => summarizeWorkspaceEntry(path12.join(dirPath, entry.name), entry))
4269
4812
  );
4270
4813
  for (const childSummary of childSummaries) {
4271
4814
  summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
@@ -4284,7 +4827,7 @@ async function scanWorkspaceDirectories(dataDir) {
4284
4827
  if (!entry.isDirectory()) {
4285
4828
  return null;
4286
4829
  }
4287
- const dirPath = path11.join(dataDir, entry.name);
4830
+ const dirPath = path12.join(dataDir, entry.name);
4288
4831
  try {
4289
4832
  const summary = await summarizeWorkspaceDirectory(dirPath);
4290
4833
  return {
@@ -4367,6 +4910,7 @@ function classifyRuntimeError(message, httpStatus) {
4367
4910
  return "ProviderApiError";
4368
4911
  }
4369
4912
  if (/\btimeout|timed out\b/i.test(message)) return "TimeoutError";
4913
+ if (/stream closed before response\.completed|error decoding response body/i.test(message)) return "ProviderStreamError";
4370
4914
  if (/\brate.?limit|too many requests\b/i.test(message)) return "RateLimitError";
4371
4915
  if (/\bnot found\b/i.test(message)) return "NotFoundError";
4372
4916
  return "RuntimeError";
@@ -4541,12 +5085,12 @@ function findSessionJsonl(root, predicate) {
4541
5085
  for (const entry of entries) {
4542
5086
  if (++visited > maxEntries) return null;
4543
5087
  if (!entry.isFile() || !predicate(entry.name)) continue;
4544
- return path12.join(dir, entry.name);
5088
+ return path13.join(dir, entry.name);
4545
5089
  }
4546
5090
  for (const entry of entries) {
4547
5091
  if (++visited > maxEntries) return null;
4548
5092
  if (!entry.isDirectory()) continue;
4549
- const found = visit(path12.join(dir, entry.name), depth - 1);
5093
+ const found = visit(path13.join(dir, entry.name), depth - 1);
4550
5094
  if (found) return found;
4551
5095
  }
4552
5096
  return null;
@@ -4559,10 +5103,10 @@ function safeSessionFilename(value) {
4559
5103
  }
4560
5104
  function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
4561
5105
  try {
4562
- const dir = path12.join(fallbackDir, ".slock", "runtime-sessions");
4563
- mkdirSync4(dir, { recursive: true });
4564
- const filePath = path12.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
4565
- writeFileSync7(filePath, JSON.stringify({
5106
+ const dir = path13.join(fallbackDir, ".slock", "runtime-sessions");
5107
+ mkdirSync5(dir, { recursive: true });
5108
+ const filePath = path13.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
5109
+ writeFileSync8(filePath, JSON.stringify({
4566
5110
  type: "runtime_session_handoff",
4567
5111
  runtime,
4568
5112
  sessionId,
@@ -4581,7 +5125,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
4581
5125
  }
4582
5126
  }
4583
5127
  function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir) {
4584
- const directPath = path12.isAbsolute(sessionId) ? sessionId : null;
5128
+ const directPath = path13.isAbsolute(sessionId) ? sessionId : null;
4585
5129
  if (directPath) {
4586
5130
  try {
4587
5131
  if (statSync2(directPath).isFile()) {
@@ -4590,7 +5134,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
4590
5134
  } catch {
4591
5135
  }
4592
5136
  }
4593
- const resolvedPath = runtime === "claude" ? findSessionJsonl(path12.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`) : runtime === "codex" ? findSessionJsonl(path12.join(homeDir, ".codex", "sessions"), (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId)) : null;
5137
+ const resolvedPath = runtime === "claude" ? findSessionJsonl(path13.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`) : runtime === "codex" ? findSessionJsonl(path13.join(homeDir, ".codex", "sessions"), (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId)) : null;
4594
5138
  if (!resolvedPath && fallbackDir) {
4595
5139
  const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
4596
5140
  if (fallback) return fallback;
@@ -5241,12 +5785,21 @@ function classifyTerminalFailure(ap) {
5241
5785
  ].filter((value) => !!value);
5242
5786
  for (const text of candidates) {
5243
5787
  const lower = text.toLowerCase();
5244
- if (lower.includes("usage limit") || lower.includes("quota exceeded") || lower.includes("quota limit") || lower.includes("budget limit exceeded") || lower.includes("usage not included in your plan") || lower.includes("modelnotfounderror") || lower.includes("requested entity was not found") || lower.includes("model deprecated") || lower.includes("model not found")) {
5788
+ if (lower.includes("usage limit") || lower.includes("quota exceeded") || lower.includes("quota limit") || lower.includes("budget limit exceeded") || lower.includes("usage not included in your plan") || lower.includes("modelnotfounderror") || lower.includes("requested entity was not found") || lower.includes("model deprecated") || lower.includes("model not found") || isProviderStreamFailureText(text)) {
5245
5789
  return text;
5246
5790
  }
5247
5791
  }
5248
5792
  return null;
5249
5793
  }
5794
+ function isProviderStreamFailureText(text) {
5795
+ return /stream closed before response\.completed|error decoding response body/i.test(text);
5796
+ }
5797
+ function isCodexProviderReconnectLog(text) {
5798
+ return /Reconnecting\.\.\.\s*\d+\s*\/\s*\d+/i.test(text);
5799
+ }
5800
+ function isCodexBenignTransportLog(text) {
5801
+ return /Falling back from WebSockets/i.test(text);
5802
+ }
5250
5803
  function hasDirectStdinRecoveryEvidence(ap) {
5251
5804
  const candidates = [
5252
5805
  ap.lastRuntimeError,
@@ -5890,26 +6443,26 @@ var AgentProcessManager = class _AgentProcessManager {
5890
6443
  this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
5891
6444
  try {
5892
6445
  const driver = this.driverResolver(config.runtime || "claude");
5893
- const agentDataDir = path12.join(this.dataDir, agentId);
6446
+ const agentDataDir = path13.join(this.dataDir, agentId);
5894
6447
  await mkdir(agentDataDir, { recursive: true });
5895
6448
  const runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
5896
- const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
6449
+ const memoryMdPath = path13.join(agentDataDir, "MEMORY.md");
5897
6450
  try {
5898
6451
  await access(memoryMdPath);
5899
6452
  } catch {
5900
6453
  const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
5901
6454
  await writeFile(memoryMdPath, initialMemoryMd);
5902
6455
  }
5903
- const notesDir = path12.join(agentDataDir, "notes");
6456
+ const notesDir = path13.join(agentDataDir, "notes");
5904
6457
  await mkdir(notesDir, { recursive: true });
5905
6458
  if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
5906
6459
  const seedFiles = buildOnboardingSeedFiles();
5907
6460
  for (const { relativePath, content } of seedFiles) {
5908
- const fullPath = path12.join(agentDataDir, relativePath);
6461
+ const fullPath = path13.join(agentDataDir, relativePath);
5909
6462
  try {
5910
6463
  await access(fullPath);
5911
6464
  } catch {
5912
- await mkdir(path12.dirname(fullPath), { recursive: true });
6465
+ await mkdir(path13.dirname(fullPath), { recursive: true });
5913
6466
  await writeFile(fullPath, content);
5914
6467
  }
5915
6468
  }
@@ -6098,8 +6651,24 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6098
6651
  proc.stderr?.on("data", (chunk) => {
6099
6652
  const text = chunk.toString().trim();
6100
6653
  if (!text) return;
6101
- if (/Reconnecting\.\.\.|Falling back from WebSockets/i.test(text)) return;
6102
6654
  const current = this.agents.get(agentId);
6655
+ if (driver.id === "codex" && isCodexProviderReconnectLog(text)) {
6656
+ if (current) {
6657
+ current.recentStderr = pushRecentStderr(current.recentStderr, text);
6658
+ }
6659
+ this.recordDaemonTrace("daemon.agent.provider_reconnect", {
6660
+ agentId,
6661
+ launchId: current?.launchId || void 0,
6662
+ runtime: config.runtime,
6663
+ model: config.model
6664
+ });
6665
+ this.broadcastActivity(agentId, "working", "Codex reconnecting to provider\u2026", [
6666
+ { kind: "text", text }
6667
+ ]);
6668
+ logger.info(`[Agent ${agentId} stderr]: ${text}`);
6669
+ return;
6670
+ }
6671
+ if (driver.id === "codex" && isCodexBenignTransportLog(text)) return;
6103
6672
  if (current) {
6104
6673
  current.recentStderr = pushRecentStderr(current.recentStderr, text);
6105
6674
  }
@@ -6243,10 +6812,20 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6243
6812
  }
6244
6813
  this.broadcastActivity(agentId, "online", "Process idle");
6245
6814
  } else {
6246
- this.idleAgentConfigs.delete(agentId);
6247
6815
  const reason = formatCrashReason(finalCode, finalSignal, ap);
6248
- logger.error(`[Agent ${agentId}] Process crashed (${reason}) \u2014 marking inactive`);
6249
- this.sendAgentStatus(agentId, "inactive", ap.launchId);
6816
+ if (terminalFailureDetail && isProviderStreamFailureText(terminalFailureDetail)) {
6817
+ this.idleAgentConfigs.set(agentId, {
6818
+ config: { ...ap.config, sessionId: ap.sessionId },
6819
+ sessionId: ap.sessionId,
6820
+ launchId: ap.launchId
6821
+ });
6822
+ logger.warn(`[Agent ${agentId}] Recoverable provider stream failure (${reason}) \u2014 keeping agent wakeable`);
6823
+ this.sendAgentStatus(agentId, "active", ap.launchId);
6824
+ } else {
6825
+ this.idleAgentConfigs.delete(agentId);
6826
+ logger.error(`[Agent ${agentId}] Process crashed (${reason}) \u2014 marking inactive`);
6827
+ this.sendAgentStatus(agentId, "inactive", ap.launchId);
6828
+ }
6250
6829
  if (terminalFailureDetail) {
6251
6830
  this.broadcastActivity(
6252
6831
  agentId,
@@ -6760,7 +7339,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6760
7339
  return true;
6761
7340
  }
6762
7341
  async resetWorkspace(agentId) {
6763
- const agentDataDir = path12.join(this.dataDir, agentId);
7342
+ const agentDataDir = path13.join(this.dataDir, agentId);
6764
7343
  try {
6765
7344
  await rm2(agentDataDir, { recursive: true, force: true });
6766
7345
  logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
@@ -6797,7 +7376,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6797
7376
  return result;
6798
7377
  }
6799
7378
  buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
6800
- const workspacePath = path12.join(this.dataDir, agentId);
7379
+ const workspacePath = path13.join(this.dataDir, agentId);
6801
7380
  return {
6802
7381
  agentId,
6803
7382
  launchId,
@@ -7054,7 +7633,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7054
7633
  }
7055
7634
  // Workspace file browsing
7056
7635
  async getFileTree(agentId, dirPath) {
7057
- const agentDir = path12.join(this.dataDir, agentId);
7636
+ const agentDir = path13.join(this.dataDir, agentId);
7058
7637
  try {
7059
7638
  await stat2(agentDir);
7060
7639
  } catch {
@@ -7062,8 +7641,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7062
7641
  }
7063
7642
  let targetDir = agentDir;
7064
7643
  if (dirPath) {
7065
- const resolved = path12.resolve(agentDir, dirPath);
7066
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
7644
+ const resolved = path13.resolve(agentDir, dirPath);
7645
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
7067
7646
  return [];
7068
7647
  }
7069
7648
  targetDir = resolved;
@@ -7071,14 +7650,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7071
7650
  return this.listDirectoryChildren(targetDir, agentDir);
7072
7651
  }
7073
7652
  async readFile(agentId, filePath) {
7074
- const agentDir = path12.join(this.dataDir, agentId);
7075
- const resolved = path12.resolve(agentDir, filePath);
7076
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
7653
+ const agentDir = path13.join(this.dataDir, agentId);
7654
+ const resolved = path13.resolve(agentDir, filePath);
7655
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
7077
7656
  throw new Error("Access denied");
7078
7657
  }
7079
7658
  const info = await stat2(resolved);
7080
7659
  if (info.isDirectory()) throw new Error("Cannot read a directory");
7081
- const ext = path12.extname(resolved).toLowerCase();
7660
+ const ext = path13.extname(resolved).toLowerCase();
7082
7661
  if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
7083
7662
  if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
7084
7663
  const content = await readFile(resolved, "utf-8");
@@ -7113,13 +7692,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7113
7692
  const agent = this.agents.get(agentId);
7114
7693
  const runtime = runtimeHint || agent?.config.runtime || "claude";
7115
7694
  const home = os6.homedir();
7116
- const workspaceDir = path12.join(this.dataDir, agentId);
7695
+ const workspaceDir = path13.join(this.dataDir, agentId);
7117
7696
  const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
7118
7697
  const globalResults = await Promise.all(
7119
- paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
7698
+ paths.global.map((p) => this.scanSkillsDir(path13.join(home, p)))
7120
7699
  );
7121
7700
  const workspaceResults = await Promise.all(
7122
- paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
7701
+ paths.workspace.map((p) => this.scanSkillsDir(path13.join(workspaceDir, p)))
7123
7702
  );
7124
7703
  const dedup = (skills) => {
7125
7704
  const seen = /* @__PURE__ */ new Set();
@@ -7148,7 +7727,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7148
7727
  const skills = [];
7149
7728
  for (const entry of entries) {
7150
7729
  if (entry.isDirectory() || entry.isSymbolicLink()) {
7151
- const skillMd = path12.join(dir, entry.name, "SKILL.md");
7730
+ const skillMd = path13.join(dir, entry.name, "SKILL.md");
7152
7731
  try {
7153
7732
  const content = await readFile(skillMd, "utf-8");
7154
7733
  const skill = this.parseSkillMd(entry.name, content);
@@ -7159,7 +7738,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7159
7738
  } else if (entry.name.endsWith(".md")) {
7160
7739
  const cmdName = entry.name.replace(/\.md$/, "");
7161
7740
  try {
7162
- const content = await readFile(path12.join(dir, entry.name), "utf-8");
7741
+ const content = await readFile(path13.join(dir, entry.name), "utf-8");
7163
7742
  const skill = this.parseSkillMd(cmdName, content);
7164
7743
  skill.sourcePath = dir;
7165
7744
  skills.push(skill);
@@ -8125,8 +8704,8 @@ ${RESPONSE_TARGET_HINT}`);
8125
8704
  const nodes = [];
8126
8705
  for (const entry of entries) {
8127
8706
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
8128
- const fullPath = path12.join(dir, entry.name);
8129
- const relativePath = path12.relative(rootDir, fullPath);
8707
+ const fullPath = path13.join(dir, entry.name);
8708
+ const relativePath = path13.relative(rootDir, fullPath);
8130
8709
  let info;
8131
8710
  try {
8132
8711
  info = await stat2(fullPath);
@@ -8431,9 +9010,9 @@ var ReminderCache = class {
8431
9010
 
8432
9011
  // src/machineLock.ts
8433
9012
  import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
8434
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
9013
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
8435
9014
  import os7 from "os";
8436
- import path13 from "path";
9015
+ import path14 from "path";
8437
9016
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
8438
9017
  var DaemonMachineLockConflictError = class extends Error {
8439
9018
  code = "DAEMON_MACHINE_LOCK_HELD";
@@ -8455,7 +9034,7 @@ function resolveDefaultMachineStateRoot() {
8455
9034
  return resolveSlockHomePath("machines");
8456
9035
  }
8457
9036
  function ownerPath(lockDir) {
8458
- return path13.join(lockDir, "owner.json");
9037
+ return path14.join(lockDir, "owner.json");
8459
9038
  }
8460
9039
  function readOwner(lockDir) {
8461
9040
  try {
@@ -8485,13 +9064,13 @@ function acquireDaemonMachineLock(options) {
8485
9064
  const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
8486
9065
  const fingerprint = apiKeyFingerprint(options.apiKey);
8487
9066
  const lockId = getDaemonMachineLockId(options.apiKey);
8488
- const machineDir = path13.join(rootDir, lockId);
8489
- const lockDir = path13.join(machineDir, "daemon.lock");
9067
+ const machineDir = path14.join(rootDir, lockId);
9068
+ const lockDir = path14.join(machineDir, "daemon.lock");
8490
9069
  const token = randomUUID2();
8491
- mkdirSync5(machineDir, { recursive: true });
9070
+ mkdirSync6(machineDir, { recursive: true });
8492
9071
  for (let attempt = 0; attempt < 2; attempt += 1) {
8493
9072
  try {
8494
- mkdirSync5(lockDir);
9073
+ mkdirSync6(lockDir);
8495
9074
  const owner = {
8496
9075
  pid: process.pid,
8497
9076
  token,
@@ -8501,7 +9080,7 @@ function acquireDaemonMachineLock(options) {
8501
9080
  apiKeyFingerprint: fingerprint.slice(0, 16)
8502
9081
  };
8503
9082
  try {
8504
- writeFileSync8(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
9083
+ writeFileSync9(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
8505
9084
  `, { mode: 384 });
8506
9085
  } catch (err) {
8507
9086
  rmSync3(lockDir, { recursive: true, force: true });
@@ -8538,8 +9117,8 @@ function acquireDaemonMachineLock(options) {
8538
9117
  }
8539
9118
 
8540
9119
  // src/localTraceSink.ts
8541
- import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
8542
- import path14 from "path";
9120
+ import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync10 } from "fs";
9121
+ import path15 from "path";
8543
9122
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
8544
9123
  var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
8545
9124
  var DEFAULT_MAX_FILES = 8;
@@ -8575,7 +9154,7 @@ var LocalRotatingTraceSink = class {
8575
9154
  currentSize = 0;
8576
9155
  sequence = 0;
8577
9156
  constructor(options) {
8578
- this.traceDir = path14.join(options.machineDir, "traces");
9157
+ this.traceDir = path15.join(options.machineDir, "traces");
8579
9158
  this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
8580
9159
  const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
8581
9160
  const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
@@ -8601,15 +9180,15 @@ var LocalRotatingTraceSink = class {
8601
9180
  return this.currentFile;
8602
9181
  }
8603
9182
  ensureFile(nextBytes) {
8604
- mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
9183
+ mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
8605
9184
  const nowMs = this.nowMsProvider();
8606
9185
  const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
8607
9186
  if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
8608
- this.currentFile = path14.join(
9187
+ this.currentFile = path15.join(
8609
9188
  this.traceDir,
8610
9189
  `daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
8611
9190
  );
8612
- writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
9191
+ writeFileSync10(this.currentFile, "", { flag: "a", mode: 384 });
8613
9192
  this.currentSize = statSync4(this.currentFile).size;
8614
9193
  this.currentFileOpenedAtMs = nowMs;
8615
9194
  this.pruneOldFiles();
@@ -8620,7 +9199,7 @@ var LocalRotatingTraceSink = class {
8620
9199
  const excess = files.length - this.maxFiles;
8621
9200
  if (excess <= 0) return;
8622
9201
  for (const file of files.slice(0, excess)) {
8623
- rmSync4(path14.join(this.traceDir, file), { force: true });
9202
+ rmSync4(path15.join(this.traceDir, file), { force: true });
8624
9203
  }
8625
9204
  }
8626
9205
  };
@@ -8707,11 +9286,11 @@ function isDiagnosticErrorAttr(key) {
8707
9286
  import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
8708
9287
  import { gzipSync } from "zlib";
8709
9288
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
8710
- import path15 from "path";
9289
+ import path16 from "path";
8711
9290
 
8712
9291
  // src/directUploadCapability.ts
8713
- function joinUrl(base, path17) {
8714
- return `${base.replace(/\/+$/, "")}${path17}`;
9292
+ function joinUrl(base, path18) {
9293
+ return `${base.replace(/\/+$/, "")}${path18}`;
8715
9294
  }
8716
9295
  function jsonHeaders(apiKey) {
8717
9296
  return {
@@ -8930,7 +9509,7 @@ var DaemonTraceBundleUploader = class {
8930
9509
  }, nextMs);
8931
9510
  }
8932
9511
  async findUploadCandidates() {
8933
- const traceDir = path15.join(this.options.machineDir, "traces");
9512
+ const traceDir = path16.join(this.options.machineDir, "traces");
8934
9513
  let names;
8935
9514
  try {
8936
9515
  names = await readdir3(traceDir);
@@ -8942,8 +9521,8 @@ var DaemonTraceBundleUploader = class {
8942
9521
  const currentFile = this.options.currentFileProvider?.();
8943
9522
  const candidates = [];
8944
9523
  for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
8945
- const file = path15.join(traceDir, name);
8946
- if (currentFile && path15.resolve(file) === path15.resolve(currentFile)) continue;
9524
+ const file = path16.join(traceDir, name);
9525
+ if (currentFile && path16.resolve(file) === path16.resolve(currentFile)) continue;
8947
9526
  if (await this.isUploaded(file)) continue;
8948
9527
  try {
8949
9528
  const info = await stat3(file);
@@ -9017,8 +9596,8 @@ var DaemonTraceBundleUploader = class {
9017
9596
  }
9018
9597
  }
9019
9598
  uploadStatePath(file) {
9020
- const stateDir = path15.join(this.options.machineDir, "trace-uploads");
9021
- return path15.join(stateDir, `${path15.basename(file)}.uploaded.json`);
9599
+ const stateDir = path16.join(this.options.machineDir, "trace-uploads");
9600
+ return path16.join(stateDir, `${path16.basename(file)}.uploaded.json`);
9022
9601
  }
9023
9602
  async isUploaded(file) {
9024
9603
  try {
@@ -9030,9 +9609,9 @@ var DaemonTraceBundleUploader = class {
9030
9609
  }
9031
9610
  async markUploaded(file, metadata) {
9032
9611
  const stateFile = this.uploadStatePath(file);
9033
- await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
9612
+ await mkdir2(path16.dirname(stateFile), { recursive: true, mode: 448 });
9034
9613
  await writeFile2(stateFile, `${JSON.stringify({
9035
- file: path15.basename(file),
9614
+ file: path16.basename(file),
9036
9615
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
9037
9616
  ...metadata
9038
9617
  }, null, 2)}
@@ -9054,7 +9633,7 @@ var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
9054
9633
  var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels"];
9055
9634
  var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
9056
9635
  var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
9057
- var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
9636
+ var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
9058
9637
  var RunnerCredentialMintError2 = class extends Error {
9059
9638
  code;
9060
9639
  retryable;
@@ -9090,9 +9669,9 @@ function runnerCredentialErrorDetail2(error) {
9090
9669
  async function waitForRunnerCredentialRetry2() {
9091
9670
  await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
9092
9671
  }
9093
- function parseDaemonCliArgs(args) {
9672
+ function parseDaemonCliArgs(args, env = {}) {
9094
9673
  let serverUrl = "";
9095
- let apiKey = "";
9674
+ let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
9096
9675
  for (let i = 0; i < args.length; i++) {
9097
9676
  if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
9098
9677
  if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
@@ -9109,23 +9688,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
9109
9688
  }
9110
9689
  }
9111
9690
  function resolveChatBridgePath(moduleUrl = import.meta.url) {
9112
- const dirname = path16.dirname(fileURLToPath(moduleUrl));
9113
- const jsPath = path16.resolve(dirname, "chat-bridge.js");
9691
+ const dirname = path17.dirname(fileURLToPath2(moduleUrl));
9692
+ const jsPath = path17.resolve(dirname, "chat-bridge.js");
9114
9693
  try {
9115
9694
  accessSync(jsPath);
9116
9695
  return jsPath;
9117
9696
  } catch {
9118
- return path16.resolve(dirname, "chat-bridge.ts");
9697
+ return path17.resolve(dirname, "chat-bridge.ts");
9119
9698
  }
9120
9699
  }
9121
9700
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
9122
- const thisDir = path16.dirname(fileURLToPath(moduleUrl));
9123
- const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
9701
+ const thisDir = path17.dirname(fileURLToPath2(moduleUrl));
9702
+ const bundledDistPath = path17.resolve(thisDir, "cli", "index.js");
9124
9703
  try {
9125
9704
  accessSync(bundledDistPath);
9126
9705
  return bundledDistPath;
9127
9706
  } catch {
9128
- const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
9707
+ const workspaceDistPath = path17.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
9129
9708
  accessSync(workspaceDistPath);
9130
9709
  return workspaceDistPath;
9131
9710
  }
@@ -9304,7 +9883,7 @@ var DaemonCore = class {
9304
9883
  }
9305
9884
  resolveMachineStateRoot() {
9306
9885
  if (this.options.machineStateDir) return this.options.machineStateDir;
9307
- if (this.options.dataDir) return path16.join(path16.dirname(this.options.dataDir), "machines");
9886
+ if (this.options.dataDir) return path17.join(path17.dirname(this.options.dataDir), "machines");
9308
9887
  return resolveDefaultMachineStateRoot();
9309
9888
  }
9310
9889
  shouldEnableLocalTrace() {
@@ -9806,6 +10385,8 @@ var DaemonCore = class {
9806
10385
  };
9807
10386
 
9808
10387
  export {
10388
+ DAEMON_API_KEY_ENV,
10389
+ scrubDaemonAuthEnv,
9809
10390
  resolveWorkspaceDirectoryPath,
9810
10391
  scanWorkspaceDirectories,
9811
10392
  deleteWorkspaceDirectory,