@slock-ai/daemon 0.51.0 → 0.51.1-play.20260519174619

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
@@ -871,13 +872,15 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
871
872
  17. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
872
873
  18. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
873
874
  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\`).
875
+ 20. **\`slock integration list\`** \u2014 List registered third-party services and this agent's active Slock Agent Logins.
876
+ 21. **\`slock integration login\`** \u2014 Provision or reuse this agent's login for a registered third-party service.
877
+ 22. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
878
+ 23. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
879
+ 24. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
880
+ 25. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
881
+ 26. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
882
+ 27. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
883
+ 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
884
 
882
885
  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
886
  - failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
@@ -978,6 +981,11 @@ Each channel has a **name** and optionally a **description** that define its pur
978
981
  - **Reply in context** \u2014 always respond in the channel/thread the message came from.
979
982
  - **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
983
  - If unsure where something belongs, call ${serverInfoCmd} to review channel descriptions.`;
984
+ const thirdPartyIntegrationsSection = isCli ? `### Third-party integrations
985
+
986
+ 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
987
+
988
+ 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
989
  const readingHistorySection = isCli ? `### Reading history
982
990
 
983
991
  \`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
@@ -1149,6 +1157,8 @@ ${discoverySection}
1149
1157
 
1150
1158
  ${channelAwarenessSection}
1151
1159
 
1160
+ ${thirdPartyIntegrationsSection}
1161
+
1152
1162
  ${readingHistorySection}
1153
1163
 
1154
1164
  ${historicalReferenceSection}
@@ -1180,6 +1190,17 @@ Keep the user informed. They cannot see your internal reasoning, so:
1180
1190
  - For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
1181
1191
  - When done, summarize the result.
1182
1192
  - Keep updates concise \u2014 one or two sentences. Don't flood the chat.
1193
+ - 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:
1194
+
1195
+ \`\`\`html
1196
+ <details>
1197
+ <summary>Evidence, logs, or edge cases</summary>
1198
+
1199
+ Detailed notes go here.
1200
+ </details>
1201
+ \`\`\`
1202
+
1203
+ Do not hide the main recommendation, blocker, or required action inside \`<details>\`; only fold supporting evidence, logs, alternatives, or extended rationale.
1183
1204
 
1184
1205
  ### Conversation etiquette
1185
1206
 
@@ -1349,12 +1370,26 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
1349
1370
  return candidates.filter((candidate) => existsSync(candidate.path));
1350
1371
  }
1351
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
+
1352
1386
  // src/agentCredentialProxy.ts
1353
1387
  import { randomBytes } from "crypto";
1354
1388
  import http from "http";
1355
1389
  import { URL as URL2 } from "url";
1356
1390
  var registrations = /* @__PURE__ */ new Map();
1357
1391
  var servers = /* @__PURE__ */ new Map();
1392
+ var DECODED_RESPONSE_HEADERS = /* @__PURE__ */ new Set(["content-encoding", "content-length", "transfer-encoding"]);
1358
1393
  function allocatePort() {
1359
1394
  return 43e3 + randomBytes(2).readUInt16BE(0) % 1e4;
1360
1395
  }
@@ -1370,6 +1405,14 @@ function ensureServer(port) {
1370
1405
  server.unref();
1371
1406
  servers.set(port, server);
1372
1407
  }
1408
+ function responseHeadersForLocalProxy(upstream) {
1409
+ const headers = {};
1410
+ for (const [name, value] of upstream.headers.entries()) {
1411
+ if (DECODED_RESPONSE_HEADERS.has(name.toLowerCase())) continue;
1412
+ headers[name] = value;
1413
+ }
1414
+ return headers;
1415
+ }
1373
1416
  async function handleProxyRequest(req, res) {
1374
1417
  const auth = req.headers.authorization;
1375
1418
  const token = typeof auth === "string" && auth.startsWith("Bearer ") ? auth.slice("Bearer ".length) : "";
@@ -1423,11 +1466,11 @@ async function handleProxyRequest(req, res) {
1423
1466
  if (shouldBufferJsonResponse(upstream, target.pathname, registration)) {
1424
1467
  const responseText = await upstream.text();
1425
1468
  consumeVisibleResponse(registration, target, sendTarget, responseText);
1426
- res.writeHead(upstream.status, Object.fromEntries(upstream.headers.entries()));
1469
+ res.writeHead(upstream.status, responseHeadersForLocalProxy(upstream));
1427
1470
  res.end(responseText);
1428
1471
  return;
1429
1472
  }
1430
- res.writeHead(upstream.status, Object.fromEntries(upstream.headers.entries()));
1473
+ res.writeHead(upstream.status, responseHeadersForLocalProxy(upstream));
1431
1474
  if (upstream.body) {
1432
1475
  const reader = upstream.body.getReader();
1433
1476
  try {
@@ -1724,7 +1767,7 @@ ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
1724
1767
  ...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
1725
1768
  PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
1726
1769
  };
1727
- delete spawnEnv.SLOCK_AGENT_TOKEN;
1770
+ scrubDaemonChildEnv(spawnEnv);
1728
1771
  delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY;
1729
1772
  delete spawnEnv.SLOCK_AGENT_CREDENTIAL_KEY_FILE;
1730
1773
  delete spawnEnv.SLOCK_AGENT_PROXY_URL;
@@ -1771,7 +1814,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn) {
1771
1814
  }
1772
1815
  function resolveCommandOnPath(command, deps = {}) {
1773
1816
  const platform = deps.platform ?? process.platform;
1774
- const env = deps.env ?? process.env;
1817
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
1775
1818
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
1776
1819
  if (platform === "win32") {
1777
1820
  return resolveCommandOnWindows(command, env, execFileSyncFn);
@@ -1796,7 +1839,7 @@ function firstExistingPath(candidates, deps = {}) {
1796
1839
  return null;
1797
1840
  }
1798
1841
  function readCommandVersion(command, args = [], deps = {}) {
1799
- const env = deps.env ?? process.env;
1842
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
1800
1843
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
1801
1844
  try {
1802
1845
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -3093,7 +3136,7 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
3093
3136
  }
3094
3137
  function runCursorModelsCommand() {
3095
3138
  return spawnSync("cursor-agent", ["models"], {
3096
- env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
3139
+ env: scrubDaemonChildEnv({ ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" }),
3097
3140
  encoding: "utf8",
3098
3141
  timeout: 5e3
3099
3142
  });
@@ -3140,7 +3183,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
3140
3183
  }
3141
3184
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
3142
3185
  const existsSyncFn = deps.existsSyncFn ?? existsSync6;
3143
- const env = deps.env ?? process.env;
3186
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
3144
3187
  const winPath = path8.win32;
3145
3188
  let geminiEntry = null;
3146
3189
  try {
@@ -3314,13 +3357,16 @@ var GeminiDriver = class {
3314
3357
  // src/drivers/kimi.ts
3315
3358
  import { randomUUID } from "crypto";
3316
3359
  import { spawn as spawn6 } from "child_process";
3317
- import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
3360
+ import { chmodSync, existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
3318
3361
  import os4 from "os";
3319
3362
  import path9 from "path";
3320
3363
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
3321
3364
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
3322
3365
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
3323
3366
  var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
3367
+ var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
3368
+ var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
3369
+ var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
3324
3370
  function parseToolArguments(raw) {
3325
3371
  if (typeof raw !== "string") return raw;
3326
3372
  try {
@@ -3329,6 +3375,73 @@ function parseToolArguments(raw) {
3329
3375
  return raw;
3330
3376
  }
3331
3377
  }
3378
+ function readKimiConfigSource(home = os4.homedir(), env = process.env) {
3379
+ const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
3380
+ if (inlineConfig && inlineConfig.trim()) {
3381
+ return {
3382
+ raw: inlineConfig,
3383
+ explicitPath: null,
3384
+ sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
3385
+ };
3386
+ }
3387
+ const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
3388
+ const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
3389
+ try {
3390
+ return {
3391
+ raw: readFileSync3(configPath, "utf8"),
3392
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
3393
+ sourcePath: configPath
3394
+ };
3395
+ } catch {
3396
+ return {
3397
+ raw: null,
3398
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
3399
+ sourcePath: configPath
3400
+ };
3401
+ }
3402
+ }
3403
+ function buildKimiSpawnEnv(env = process.env) {
3404
+ const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
3405
+ delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
3406
+ delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
3407
+ return scrubDaemonChildEnv(spawnEnv);
3408
+ }
3409
+ function buildKimiEffectiveEnv(ctx, overrideEnv) {
3410
+ return {
3411
+ ...process.env,
3412
+ ...ctx.config.envVars || {},
3413
+ ...overrideEnv || {}
3414
+ };
3415
+ }
3416
+ function buildKimiLaunchOptions(ctx, opts = {}) {
3417
+ const env = buildKimiEffectiveEnv(ctx, opts.env);
3418
+ const source = readKimiConfigSource(opts.home ?? os4.homedir(), env);
3419
+ const args = [];
3420
+ let configFilePath = null;
3421
+ let configContent = null;
3422
+ if (source.explicitPath) {
3423
+ configFilePath = source.explicitPath;
3424
+ } else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
3425
+ configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
3426
+ configContent = source.raw;
3427
+ if (opts.writeGeneratedConfig !== false) {
3428
+ writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
3429
+ chmodSync(configFilePath, 384);
3430
+ }
3431
+ }
3432
+ if (configFilePath) {
3433
+ args.push("--config-file", configFilePath);
3434
+ }
3435
+ if (ctx.config.model && ctx.config.model !== "default") {
3436
+ args.push("--model", ctx.config.model);
3437
+ }
3438
+ return {
3439
+ args,
3440
+ env: buildKimiSpawnEnv(env),
3441
+ configFilePath,
3442
+ configContent
3443
+ };
3444
+ }
3332
3445
  function resolveKimiSpawn(commandArgs, deps = {}) {
3333
3446
  return {
3334
3447
  command: resolveCommandOnPath("kimi", deps) ?? "kimi",
@@ -3352,7 +3465,25 @@ var KimiDriver = class {
3352
3465
  };
3353
3466
  model = {
3354
3467
  detectedModelsVerifiedAs: "launchable",
3355
- toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
3468
+ toLaunchSpec: (modelId, ctx, opts) => {
3469
+ if (!ctx) return { args: ["--model", modelId] };
3470
+ const launchCtx = {
3471
+ ...ctx,
3472
+ config: {
3473
+ ...ctx.config,
3474
+ model: modelId
3475
+ }
3476
+ };
3477
+ const launch = buildKimiLaunchOptions(launchCtx, {
3478
+ home: opts?.home,
3479
+ writeGeneratedConfig: false
3480
+ });
3481
+ return {
3482
+ args: launch.args,
3483
+ env: launch.env,
3484
+ configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
3485
+ };
3486
+ }
3356
3487
  };
3357
3488
  supportsStdinNotification = true;
3358
3489
  mcpToolPrefix = "";
@@ -3406,6 +3537,7 @@ var KimiDriver = class {
3406
3537
  }
3407
3538
  }
3408
3539
  }), "utf8");
3540
+ const launch = buildKimiLaunchOptions(ctx);
3409
3541
  const args = [
3410
3542
  "--wire",
3411
3543
  "--yolo",
@@ -3414,14 +3546,15 @@ var KimiDriver = class {
3414
3546
  "--mcp-config-file",
3415
3547
  mcpConfigPath,
3416
3548
  "--session",
3417
- this.sessionId
3549
+ this.sessionId,
3550
+ ...launch.args
3418
3551
  ];
3419
3552
  if (ctx.config.model && ctx.config.model !== "default") {
3420
3553
  args.push("--model", ctx.config.model);
3421
3554
  }
3422
3555
  const spawnEnv = prepareCliTransport(ctx, { NO_COLOR: "1" }).spawnEnv;
3423
- const launch = resolveKimiSpawn(args);
3424
- const proc = spawn6(launch.command, launch.args, {
3556
+ const spawnTarget = resolveKimiSpawn(args);
3557
+ const proc = spawn6(spawnTarget.command, spawnTarget.args, {
3425
3558
  cwd: ctx.workingDirectory,
3426
3559
  stdio: ["pipe", "pipe", "pipe"],
3427
3560
  env: spawnEnv,
@@ -3429,7 +3562,7 @@ var KimiDriver = class {
3429
3562
  // and has an 8191-character command-line limit. Kimi's official
3430
3563
  // installer/uv entrypoint is an executable, so launch it directly and
3431
3564
  // keep prompts on stdin / files instead of routing through cmd.exe.
3432
- shell: launch.shell
3565
+ shell: spawnTarget.shell
3433
3566
  });
3434
3567
  proc.stdin?.write(JSON.stringify({
3435
3568
  jsonrpc: "2.0",
@@ -3545,14 +3678,9 @@ var KimiDriver = class {
3545
3678
  return detectKimiModels();
3546
3679
  }
3547
3680
  };
3548
- function detectKimiModels(home = os4.homedir()) {
3549
- const configPath = path9.join(home, ".kimi", "config.toml");
3550
- let raw;
3551
- try {
3552
- raw = readFileSync3(configPath, "utf8");
3553
- } catch {
3554
- return null;
3555
- }
3681
+ function detectKimiModels(home = os4.homedir(), opts = {}) {
3682
+ const raw = readKimiConfigSource(home, opts.env).raw;
3683
+ if (raw === null) return null;
3556
3684
  const models = [];
3557
3685
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
3558
3686
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -3813,7 +3941,7 @@ function detectOpenCodeModels(home = os5.homedir(), runCommand = runOpenCodeMode
3813
3941
  }
3814
3942
  function runOpenCodeModelsCommand(home) {
3815
3943
  const result = spawnSync2("opencode", ["models"], {
3816
- env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
3944
+ env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
3817
3945
  encoding: "utf8",
3818
3946
  timeout: 5e3
3819
3947
  });
@@ -3971,6 +4099,297 @@ var OpenCodeDriver = class {
3971
4099
  }
3972
4100
  };
3973
4101
 
4102
+ // src/drivers/pi.ts
4103
+ import { spawn as spawn8 } from "child_process";
4104
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
4105
+ import path11 from "path";
4106
+ import { fileURLToPath } from "url";
4107
+ import { getAgentDir, VERSION as PI_SDK_VERSION } from "@earendil-works/pi-coding-agent";
4108
+ var CHAT_MCP_TOOL_PREFIX2 = "chat_";
4109
+ var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
4110
+ var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
4111
+ var MIN_SUPPORTED_PI_VERSION = "0.74.0";
4112
+ function parseSemver2(version) {
4113
+ const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
4114
+ if (!match) return null;
4115
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
4116
+ }
4117
+ function isSupportedPiVersion(version) {
4118
+ if (!version) return true;
4119
+ const actual = parseSemver2(version);
4120
+ const minimum = parseSemver2(MIN_SUPPORTED_PI_VERSION);
4121
+ if (!actual || !minimum) return true;
4122
+ for (let i = 0; i < 3; i += 1) {
4123
+ if (actual[i] > minimum[i]) return true;
4124
+ if (actual[i] < minimum[i]) return false;
4125
+ }
4126
+ return true;
4127
+ }
4128
+ function unsupportedPiVersionMessage(version) {
4129
+ if (!version || isSupportedPiVersion(version)) return null;
4130
+ 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.`;
4131
+ }
4132
+ function probePi(version = PI_SDK_VERSION) {
4133
+ const unsupportedMessage = unsupportedPiVersionMessage(version);
4134
+ if (unsupportedMessage) {
4135
+ return {
4136
+ available: false,
4137
+ version: `${version} (requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION})`
4138
+ };
4139
+ }
4140
+ return { available: true, version };
4141
+ }
4142
+ function resolvePiSdkRunnerPath(moduleUrl = import.meta.url) {
4143
+ const moduleDir = path11.dirname(fileURLToPath(moduleUrl));
4144
+ const sourceSibling = path11.join(moduleDir, "piSdkRunner.ts");
4145
+ if (existsSync8(sourceSibling)) return sourceSibling;
4146
+ const bundledEntry = path11.join(moduleDir, "drivers", "piSdkRunner.js");
4147
+ if (existsSync8(bundledEntry)) return bundledEntry;
4148
+ return path11.join(moduleDir, "piSdkRunner.js");
4149
+ }
4150
+ function buildPiSdkNodeArgs(runnerPath = resolvePiSdkRunnerPath()) {
4151
+ if (runnerPath.endsWith(".ts")) {
4152
+ return [...process.execArgv, runnerPath];
4153
+ }
4154
+ return [runnerPath];
4155
+ }
4156
+ function buildPiLaunchOptions(ctx, opts = {}) {
4157
+ const command = opts.command ?? process.execPath;
4158
+ const piDir = path11.join(ctx.workingDirectory, ".slock", "pi");
4159
+ const sessionDir = path11.join(piDir, "sessions");
4160
+ mkdirSync4(sessionDir, { recursive: true });
4161
+ const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
4162
+ const runnerPath = opts.runnerPath ?? resolvePiSdkRunnerPath();
4163
+ const agentDir = opts.agentDir ?? getAgentDir();
4164
+ const runnerConfigPath = path11.join(
4165
+ piDir,
4166
+ `sdk-run-${(ctx.launchId || "launch").replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`
4167
+ );
4168
+ const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
4169
+ const runnerConfig = {
4170
+ cwd: ctx.workingDirectory,
4171
+ agentDir,
4172
+ sessionDir,
4173
+ sessionId: ctx.config.sessionId || null,
4174
+ standingPrompt: ctx.standingPrompt,
4175
+ prompt: turnPrompt,
4176
+ model: ctx.config.model && ctx.config.model !== "default" ? ctx.config.model : null
4177
+ };
4178
+ writeFileSync7(runnerConfigPath, `${JSON.stringify(runnerConfig)}
4179
+ `, { encoding: "utf8", mode: 384 });
4180
+ const args = [
4181
+ ...buildPiSdkNodeArgs(runnerPath),
4182
+ "--config",
4183
+ runnerConfigPath
4184
+ ];
4185
+ return {
4186
+ command,
4187
+ args,
4188
+ env: slock.spawnEnv,
4189
+ sessionDir,
4190
+ agentDir,
4191
+ runnerConfigPath,
4192
+ sdkVersion: PI_SDK_VERSION
4193
+ };
4194
+ }
4195
+ function isSystemFirstMessageTask2(message) {
4196
+ return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
4197
+ }
4198
+ function buildPiSystemPrompt(config) {
4199
+ return buildCliTransportSystemPrompt(config, {
4200
+ toolPrefix: CHAT_MCP_TOOL_PREFIX2,
4201
+ extraCriticalRules: [
4202
+ "- 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."
4203
+ ],
4204
+ postStartupNotes: [
4205
+ "**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."
4206
+ ],
4207
+ includeStdinNotificationSection: false,
4208
+ messageNotificationStyle: "poll"
4209
+ });
4210
+ }
4211
+ function contentText(content) {
4212
+ if (!content) return "";
4213
+ const chunks = [];
4214
+ for (const item of content) {
4215
+ if (item.type === "text" && typeof item.text === "string") {
4216
+ chunks.push(item.text);
4217
+ }
4218
+ }
4219
+ return chunks.join("");
4220
+ }
4221
+ function apiKeyErrorMessage(line) {
4222
+ const trimmed = line.trim();
4223
+ if (!trimmed) return null;
4224
+ if (/no api key found/i.test(trimmed)) return trimmed;
4225
+ if (/api key.+required/i.test(trimmed)) return trimmed;
4226
+ if (/no models available/i.test(trimmed)) return trimmed;
4227
+ return null;
4228
+ }
4229
+ var PiDriver = class {
4230
+ id = "pi";
4231
+ lifecycle = {
4232
+ kind: "per_turn",
4233
+ start: "defer_until_concrete_message",
4234
+ exit: "terminate_on_turn_end",
4235
+ inFlightWake: "coalesce_into_pending"
4236
+ };
4237
+ communication = {
4238
+ chat: "slock_cli",
4239
+ runtimeControl: "none"
4240
+ };
4241
+ session = {
4242
+ recovery: "resume_or_fresh"
4243
+ };
4244
+ model = {
4245
+ detectedModelsVerifiedAs: "launchable",
4246
+ toLaunchSpec: (modelId, ctx) => {
4247
+ if (!ctx) return modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] };
4248
+ const launchCtx = {
4249
+ ...ctx,
4250
+ config: {
4251
+ ...ctx.config,
4252
+ model: modelId
4253
+ }
4254
+ };
4255
+ const launch = buildPiLaunchOptions(launchCtx);
4256
+ return {
4257
+ args: launch.args,
4258
+ env: launch.env,
4259
+ configFiles: [launch.runnerConfigPath],
4260
+ params: {
4261
+ agentDir: launch.agentDir,
4262
+ sessionDir: launch.sessionDir,
4263
+ sdkVersion: launch.sdkVersion,
4264
+ resources: "extensions/skills/prompt-templates/themes/context-files disabled by Slock policy"
4265
+ }
4266
+ };
4267
+ }
4268
+ };
4269
+ supportsStdinNotification = false;
4270
+ mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
4271
+ busyDeliveryMode = "none";
4272
+ terminateProcessOnTurnEnd = true;
4273
+ deferSpawnUntilMessage = true;
4274
+ usesSlockCliForCommunication = true;
4275
+ sessionId = null;
4276
+ sessionAnnounced = false;
4277
+ apiKeyErrorAnnounced = false;
4278
+ turnEnded = false;
4279
+ assistantTextByMessageId = /* @__PURE__ */ new Map();
4280
+ shouldDeferWakeMessage(message) {
4281
+ return isSystemFirstMessageTask2(message);
4282
+ }
4283
+ probe() {
4284
+ return probePi();
4285
+ }
4286
+ async detectModels() {
4287
+ return null;
4288
+ }
4289
+ spawn(ctx) {
4290
+ this.sessionId = ctx.config.sessionId || null;
4291
+ this.sessionAnnounced = false;
4292
+ this.apiKeyErrorAnnounced = false;
4293
+ this.turnEnded = false;
4294
+ this.assistantTextByMessageId.clear();
4295
+ const unsupportedMessage = unsupportedPiVersionMessage(PI_SDK_VERSION);
4296
+ if (unsupportedMessage) throw new Error(unsupportedMessage);
4297
+ const launch = buildPiLaunchOptions(ctx);
4298
+ const proc = spawn8(launch.command, launch.args, {
4299
+ cwd: ctx.workingDirectory,
4300
+ stdio: ["pipe", "pipe", "pipe"],
4301
+ env: launch.env,
4302
+ shell: false
4303
+ });
4304
+ proc.stdin?.end();
4305
+ return { process: proc };
4306
+ }
4307
+ parseLine(line) {
4308
+ let event;
4309
+ try {
4310
+ event = JSON.parse(line);
4311
+ } catch {
4312
+ if (this.apiKeyErrorAnnounced) return [];
4313
+ const message = apiKeyErrorMessage(line);
4314
+ if (!message) return [];
4315
+ this.apiKeyErrorAnnounced = true;
4316
+ this.turnEnded = true;
4317
+ return [
4318
+ { kind: "error", message },
4319
+ { kind: "turn_end", sessionId: this.sessionId || void 0 }
4320
+ ];
4321
+ }
4322
+ const events = [];
4323
+ if (event.type === "session" && event.id) {
4324
+ this.sessionId = event.id;
4325
+ }
4326
+ if (!this.sessionAnnounced && this.sessionId) {
4327
+ events.push({ kind: "session_init", sessionId: this.sessionId });
4328
+ this.sessionAnnounced = true;
4329
+ }
4330
+ switch (event.type) {
4331
+ case "agent_start":
4332
+ case "turn_start":
4333
+ events.push({ kind: "thinking", text: "" });
4334
+ break;
4335
+ case "message_update":
4336
+ case "message_end":
4337
+ if (event.message?.role === "assistant") {
4338
+ const key = event.message.id || "current";
4339
+ const currentText = contentText(event.message.content);
4340
+ const previousText = this.assistantTextByMessageId.get(key) ?? "";
4341
+ if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
4342
+ events.push({ kind: "text", text: currentText.slice(previousText.length) });
4343
+ } else if (currentText && currentText !== previousText) {
4344
+ events.push({ kind: "text", text: currentText });
4345
+ }
4346
+ this.assistantTextByMessageId.set(key, currentText);
4347
+ if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
4348
+ events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
4349
+ }
4350
+ }
4351
+ break;
4352
+ case "tool_execution_start":
4353
+ events.push({
4354
+ kind: "tool_call",
4355
+ name: event.toolName || "unknown_tool",
4356
+ input: event.args
4357
+ });
4358
+ break;
4359
+ case "tool_execution_end":
4360
+ events.push({
4361
+ kind: "tool_output",
4362
+ name: event.toolName || "unknown_tool"
4363
+ });
4364
+ if (event.isError) {
4365
+ events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
4366
+ }
4367
+ break;
4368
+ case "compaction_start":
4369
+ events.push({ kind: "compaction_started" });
4370
+ break;
4371
+ case "compaction_end":
4372
+ events.push({ kind: "compaction_finished" });
4373
+ if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
4374
+ break;
4375
+ case "turn_end":
4376
+ case "agent_end":
4377
+ if (!this.turnEnded) {
4378
+ events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
4379
+ this.turnEnded = true;
4380
+ }
4381
+ break;
4382
+ }
4383
+ return events;
4384
+ }
4385
+ encodeStdinMessage(_text, _sessionId, _opts) {
4386
+ return null;
4387
+ }
4388
+ buildSystemPrompt(config, _agentId) {
4389
+ return buildPiSystemPrompt(config);
4390
+ }
4391
+ };
4392
+
3974
4393
  // src/drivers/index.ts
3975
4394
  var driverFactories = {
3976
4395
  claude: () => new ClaudeDriver(),
@@ -3979,7 +4398,8 @@ var driverFactories = {
3979
4398
  cursor: () => new CursorDriver(),
3980
4399
  gemini: () => new GeminiDriver(),
3981
4400
  kimi: () => new KimiDriver(),
3982
- opencode: () => new OpenCodeDriver()
4401
+ opencode: () => new OpenCodeDriver(),
4402
+ pi: () => new PiDriver()
3983
4403
  };
3984
4404
  function getDriver(runtimeId) {
3985
4405
  const createDriver = driverFactories[runtimeId];
@@ -3992,7 +4412,7 @@ function getDriver(runtimeId) {
3992
4412
 
3993
4413
  // src/workspaces.ts
3994
4414
  import { readdir, rm, stat } from "fs/promises";
3995
- import path11 from "path";
4415
+ import path12 from "path";
3996
4416
  function isValidWorkspaceDirectoryName(directoryName) {
3997
4417
  return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
3998
4418
  }
@@ -4000,7 +4420,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
4000
4420
  if (!isValidWorkspaceDirectoryName(directoryName)) {
4001
4421
  return null;
4002
4422
  }
4003
- return path11.join(dataDir, directoryName);
4423
+ return path12.join(dataDir, directoryName);
4004
4424
  }
4005
4425
  function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
4006
4426
  return {
@@ -4049,7 +4469,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
4049
4469
  return summary;
4050
4470
  }
4051
4471
  const childSummaries = await Promise.all(
4052
- entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
4472
+ entries.map((entry) => summarizeWorkspaceEntry(path12.join(dirPath, entry.name), entry))
4053
4473
  );
4054
4474
  for (const childSummary of childSummaries) {
4055
4475
  summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
@@ -4068,7 +4488,7 @@ async function scanWorkspaceDirectories(dataDir) {
4068
4488
  if (!entry.isDirectory()) {
4069
4489
  return null;
4070
4490
  }
4071
- const dirPath = path11.join(dataDir, entry.name);
4491
+ const dirPath = path12.join(dataDir, entry.name);
4072
4492
  try {
4073
4493
  const summary = await summarizeWorkspaceDirectory(dirPath);
4074
4494
  return {
@@ -4325,12 +4745,12 @@ function findSessionJsonl(root, predicate) {
4325
4745
  for (const entry of entries) {
4326
4746
  if (++visited > maxEntries) return null;
4327
4747
  if (!entry.isFile() || !predicate(entry.name)) continue;
4328
- return path12.join(dir, entry.name);
4748
+ return path13.join(dir, entry.name);
4329
4749
  }
4330
4750
  for (const entry of entries) {
4331
4751
  if (++visited > maxEntries) return null;
4332
4752
  if (!entry.isDirectory()) continue;
4333
- const found = visit(path12.join(dir, entry.name), depth - 1);
4753
+ const found = visit(path13.join(dir, entry.name), depth - 1);
4334
4754
  if (found) return found;
4335
4755
  }
4336
4756
  return null;
@@ -4343,10 +4763,10 @@ function safeSessionFilename(value) {
4343
4763
  }
4344
4764
  function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
4345
4765
  try {
4346
- const dir = path12.join(fallbackDir, ".slock", "runtime-sessions");
4347
- mkdirSync4(dir, { recursive: true });
4348
- const filePath = path12.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
4349
- writeFileSync7(filePath, JSON.stringify({
4766
+ const dir = path13.join(fallbackDir, ".slock", "runtime-sessions");
4767
+ mkdirSync5(dir, { recursive: true });
4768
+ const filePath = path13.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
4769
+ writeFileSync8(filePath, JSON.stringify({
4350
4770
  type: "runtime_session_handoff",
4351
4771
  runtime,
4352
4772
  sessionId,
@@ -4365,7 +4785,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
4365
4785
  }
4366
4786
  }
4367
4787
  function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir) {
4368
- const directPath = path12.isAbsolute(sessionId) ? sessionId : null;
4788
+ const directPath = path13.isAbsolute(sessionId) ? sessionId : null;
4369
4789
  if (directPath) {
4370
4790
  try {
4371
4791
  if (statSync2(directPath).isFile()) {
@@ -4374,7 +4794,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
4374
4794
  } catch {
4375
4795
  }
4376
4796
  }
4377
- 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;
4797
+ 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;
4378
4798
  if (!resolvedPath && fallbackDir) {
4379
4799
  const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
4380
4800
  if (fallback) return fallback;
@@ -5608,26 +6028,26 @@ var AgentProcessManager = class _AgentProcessManager {
5608
6028
  this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
5609
6029
  try {
5610
6030
  const driver = this.driverResolver(config.runtime || "claude");
5611
- const agentDataDir = path12.join(this.dataDir, agentId);
6031
+ const agentDataDir = path13.join(this.dataDir, agentId);
5612
6032
  await mkdir(agentDataDir, { recursive: true });
5613
6033
  const runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
5614
- const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
6034
+ const memoryMdPath = path13.join(agentDataDir, "MEMORY.md");
5615
6035
  try {
5616
6036
  await access(memoryMdPath);
5617
6037
  } catch {
5618
6038
  const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
5619
6039
  await writeFile(memoryMdPath, initialMemoryMd);
5620
6040
  }
5621
- const notesDir = path12.join(agentDataDir, "notes");
6041
+ const notesDir = path13.join(agentDataDir, "notes");
5622
6042
  await mkdir(notesDir, { recursive: true });
5623
6043
  if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
5624
6044
  const seedFiles = buildOnboardingSeedFiles();
5625
6045
  for (const { relativePath, content } of seedFiles) {
5626
- const fullPath = path12.join(agentDataDir, relativePath);
6046
+ const fullPath = path13.join(agentDataDir, relativePath);
5627
6047
  try {
5628
6048
  await access(fullPath);
5629
6049
  } catch {
5630
- await mkdir(path12.dirname(fullPath), { recursive: true });
6050
+ await mkdir(path13.dirname(fullPath), { recursive: true });
5631
6051
  await writeFile(fullPath, content);
5632
6052
  }
5633
6053
  }
@@ -6472,7 +6892,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6472
6892
  return true;
6473
6893
  }
6474
6894
  async resetWorkspace(agentId) {
6475
- const agentDataDir = path12.join(this.dataDir, agentId);
6895
+ const agentDataDir = path13.join(this.dataDir, agentId);
6476
6896
  try {
6477
6897
  await rm2(agentDataDir, { recursive: true, force: true });
6478
6898
  logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
@@ -6509,7 +6929,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6509
6929
  return result;
6510
6930
  }
6511
6931
  buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
6512
- const workspacePath = path12.join(this.dataDir, agentId);
6932
+ const workspacePath = path13.join(this.dataDir, agentId);
6513
6933
  return {
6514
6934
  agentId,
6515
6935
  launchId,
@@ -6766,7 +7186,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6766
7186
  }
6767
7187
  // Workspace file browsing
6768
7188
  async getFileTree(agentId, dirPath) {
6769
- const agentDir = path12.join(this.dataDir, agentId);
7189
+ const agentDir = path13.join(this.dataDir, agentId);
6770
7190
  try {
6771
7191
  await stat2(agentDir);
6772
7192
  } catch {
@@ -6774,8 +7194,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6774
7194
  }
6775
7195
  let targetDir = agentDir;
6776
7196
  if (dirPath) {
6777
- const resolved = path12.resolve(agentDir, dirPath);
6778
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
7197
+ const resolved = path13.resolve(agentDir, dirPath);
7198
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
6779
7199
  return [];
6780
7200
  }
6781
7201
  targetDir = resolved;
@@ -6783,14 +7203,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6783
7203
  return this.listDirectoryChildren(targetDir, agentDir);
6784
7204
  }
6785
7205
  async readFile(agentId, filePath) {
6786
- const agentDir = path12.join(this.dataDir, agentId);
6787
- const resolved = path12.resolve(agentDir, filePath);
6788
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
7206
+ const agentDir = path13.join(this.dataDir, agentId);
7207
+ const resolved = path13.resolve(agentDir, filePath);
7208
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
6789
7209
  throw new Error("Access denied");
6790
7210
  }
6791
7211
  const info = await stat2(resolved);
6792
7212
  if (info.isDirectory()) throw new Error("Cannot read a directory");
6793
- const ext = path12.extname(resolved).toLowerCase();
7213
+ const ext = path13.extname(resolved).toLowerCase();
6794
7214
  if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
6795
7215
  if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
6796
7216
  const content = await readFile(resolved, "utf-8");
@@ -6825,13 +7245,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6825
7245
  const agent = this.agents.get(agentId);
6826
7246
  const runtime = runtimeHint || agent?.config.runtime || "claude";
6827
7247
  const home = os6.homedir();
6828
- const workspaceDir = path12.join(this.dataDir, agentId);
7248
+ const workspaceDir = path13.join(this.dataDir, agentId);
6829
7249
  const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
6830
7250
  const globalResults = await Promise.all(
6831
- paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
7251
+ paths.global.map((p) => this.scanSkillsDir(path13.join(home, p)))
6832
7252
  );
6833
7253
  const workspaceResults = await Promise.all(
6834
- paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
7254
+ paths.workspace.map((p) => this.scanSkillsDir(path13.join(workspaceDir, p)))
6835
7255
  );
6836
7256
  const dedup = (skills) => {
6837
7257
  const seen = /* @__PURE__ */ new Set();
@@ -6860,7 +7280,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6860
7280
  const skills = [];
6861
7281
  for (const entry of entries) {
6862
7282
  if (entry.isDirectory() || entry.isSymbolicLink()) {
6863
- const skillMd = path12.join(dir, entry.name, "SKILL.md");
7283
+ const skillMd = path13.join(dir, entry.name, "SKILL.md");
6864
7284
  try {
6865
7285
  const content = await readFile(skillMd, "utf-8");
6866
7286
  const skill = this.parseSkillMd(entry.name, content);
@@ -6871,7 +7291,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6871
7291
  } else if (entry.name.endsWith(".md")) {
6872
7292
  const cmdName = entry.name.replace(/\.md$/, "");
6873
7293
  try {
6874
- const content = await readFile(path12.join(dir, entry.name), "utf-8");
7294
+ const content = await readFile(path13.join(dir, entry.name), "utf-8");
6875
7295
  const skill = this.parseSkillMd(cmdName, content);
6876
7296
  skill.sourcePath = dir;
6877
7297
  skills.push(skill);
@@ -7859,8 +8279,8 @@ ${RESPONSE_TARGET_HINT}`);
7859
8279
  const nodes = [];
7860
8280
  for (const entry of entries) {
7861
8281
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
7862
- const fullPath = path12.join(dir, entry.name);
7863
- const relativePath = path12.relative(rootDir, fullPath);
8282
+ const fullPath = path13.join(dir, entry.name);
8283
+ const relativePath = path13.relative(rootDir, fullPath);
7864
8284
  let info;
7865
8285
  try {
7866
8286
  info = await stat2(fullPath);
@@ -8163,9 +8583,9 @@ var ReminderCache = class {
8163
8583
 
8164
8584
  // src/machineLock.ts
8165
8585
  import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
8166
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
8586
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
8167
8587
  import os7 from "os";
8168
- import path13 from "path";
8588
+ import path14 from "path";
8169
8589
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
8170
8590
  var DaemonMachineLockConflictError = class extends Error {
8171
8591
  code = "DAEMON_MACHINE_LOCK_HELD";
@@ -8187,7 +8607,7 @@ function resolveDefaultMachineStateRoot() {
8187
8607
  return resolveSlockHomePath("machines");
8188
8608
  }
8189
8609
  function ownerPath(lockDir) {
8190
- return path13.join(lockDir, "owner.json");
8610
+ return path14.join(lockDir, "owner.json");
8191
8611
  }
8192
8612
  function readOwner(lockDir) {
8193
8613
  try {
@@ -8217,13 +8637,13 @@ function acquireDaemonMachineLock(options) {
8217
8637
  const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
8218
8638
  const fingerprint = apiKeyFingerprint(options.apiKey);
8219
8639
  const lockId = getDaemonMachineLockId(options.apiKey);
8220
- const machineDir = path13.join(rootDir, lockId);
8221
- const lockDir = path13.join(machineDir, "daemon.lock");
8640
+ const machineDir = path14.join(rootDir, lockId);
8641
+ const lockDir = path14.join(machineDir, "daemon.lock");
8222
8642
  const token = randomUUID2();
8223
- mkdirSync5(machineDir, { recursive: true });
8643
+ mkdirSync6(machineDir, { recursive: true });
8224
8644
  for (let attempt = 0; attempt < 2; attempt += 1) {
8225
8645
  try {
8226
- mkdirSync5(lockDir);
8646
+ mkdirSync6(lockDir);
8227
8647
  const owner = {
8228
8648
  pid: process.pid,
8229
8649
  token,
@@ -8233,7 +8653,7 @@ function acquireDaemonMachineLock(options) {
8233
8653
  apiKeyFingerprint: fingerprint.slice(0, 16)
8234
8654
  };
8235
8655
  try {
8236
- writeFileSync8(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
8656
+ writeFileSync9(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
8237
8657
  `, { mode: 384 });
8238
8658
  } catch (err) {
8239
8659
  rmSync3(lockDir, { recursive: true, force: true });
@@ -8270,8 +8690,8 @@ function acquireDaemonMachineLock(options) {
8270
8690
  }
8271
8691
 
8272
8692
  // src/localTraceSink.ts
8273
- import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
8274
- import path14 from "path";
8693
+ import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync10 } from "fs";
8694
+ import path15 from "path";
8275
8695
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
8276
8696
  var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
8277
8697
  var DEFAULT_MAX_FILES = 8;
@@ -8307,7 +8727,7 @@ var LocalRotatingTraceSink = class {
8307
8727
  currentSize = 0;
8308
8728
  sequence = 0;
8309
8729
  constructor(options) {
8310
- this.traceDir = path14.join(options.machineDir, "traces");
8730
+ this.traceDir = path15.join(options.machineDir, "traces");
8311
8731
  this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
8312
8732
  const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
8313
8733
  const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
@@ -8333,15 +8753,15 @@ var LocalRotatingTraceSink = class {
8333
8753
  return this.currentFile;
8334
8754
  }
8335
8755
  ensureFile(nextBytes) {
8336
- mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
8756
+ mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
8337
8757
  const nowMs = this.nowMsProvider();
8338
8758
  const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
8339
8759
  if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
8340
- this.currentFile = path14.join(
8760
+ this.currentFile = path15.join(
8341
8761
  this.traceDir,
8342
8762
  `daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
8343
8763
  );
8344
- writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
8764
+ writeFileSync10(this.currentFile, "", { flag: "a", mode: 384 });
8345
8765
  this.currentSize = statSync4(this.currentFile).size;
8346
8766
  this.currentFileOpenedAtMs = nowMs;
8347
8767
  this.pruneOldFiles();
@@ -8352,7 +8772,7 @@ var LocalRotatingTraceSink = class {
8352
8772
  const excess = files.length - this.maxFiles;
8353
8773
  if (excess <= 0) return;
8354
8774
  for (const file of files.slice(0, excess)) {
8355
- rmSync4(path14.join(this.traceDir, file), { force: true });
8775
+ rmSync4(path15.join(this.traceDir, file), { force: true });
8356
8776
  }
8357
8777
  }
8358
8778
  };
@@ -8439,11 +8859,11 @@ function isDiagnosticErrorAttr(key) {
8439
8859
  import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
8440
8860
  import { gzipSync } from "zlib";
8441
8861
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
8442
- import path15 from "path";
8862
+ import path16 from "path";
8443
8863
 
8444
8864
  // src/directUploadCapability.ts
8445
- function joinUrl(base, path17) {
8446
- return `${base.replace(/\/+$/, "")}${path17}`;
8865
+ function joinUrl(base, path18) {
8866
+ return `${base.replace(/\/+$/, "")}${path18}`;
8447
8867
  }
8448
8868
  function jsonHeaders(apiKey) {
8449
8869
  return {
@@ -8662,7 +9082,7 @@ var DaemonTraceBundleUploader = class {
8662
9082
  }, nextMs);
8663
9083
  }
8664
9084
  async findUploadCandidates() {
8665
- const traceDir = path15.join(this.options.machineDir, "traces");
9085
+ const traceDir = path16.join(this.options.machineDir, "traces");
8666
9086
  let names;
8667
9087
  try {
8668
9088
  names = await readdir3(traceDir);
@@ -8674,8 +9094,8 @@ var DaemonTraceBundleUploader = class {
8674
9094
  const currentFile = this.options.currentFileProvider?.();
8675
9095
  const candidates = [];
8676
9096
  for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
8677
- const file = path15.join(traceDir, name);
8678
- if (currentFile && path15.resolve(file) === path15.resolve(currentFile)) continue;
9097
+ const file = path16.join(traceDir, name);
9098
+ if (currentFile && path16.resolve(file) === path16.resolve(currentFile)) continue;
8679
9099
  if (await this.isUploaded(file)) continue;
8680
9100
  try {
8681
9101
  const info = await stat3(file);
@@ -8749,8 +9169,8 @@ var DaemonTraceBundleUploader = class {
8749
9169
  }
8750
9170
  }
8751
9171
  uploadStatePath(file) {
8752
- const stateDir = path15.join(this.options.machineDir, "trace-uploads");
8753
- return path15.join(stateDir, `${path15.basename(file)}.uploaded.json`);
9172
+ const stateDir = path16.join(this.options.machineDir, "trace-uploads");
9173
+ return path16.join(stateDir, `${path16.basename(file)}.uploaded.json`);
8754
9174
  }
8755
9175
  async isUploaded(file) {
8756
9176
  try {
@@ -8762,9 +9182,9 @@ var DaemonTraceBundleUploader = class {
8762
9182
  }
8763
9183
  async markUploaded(file, metadata) {
8764
9184
  const stateFile = this.uploadStatePath(file);
8765
- await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
9185
+ await mkdir2(path16.dirname(stateFile), { recursive: true, mode: 448 });
8766
9186
  await writeFile2(stateFile, `${JSON.stringify({
8767
- file: path15.basename(file),
9187
+ file: path16.basename(file),
8768
9188
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
8769
9189
  ...metadata
8770
9190
  }, null, 2)}
@@ -8786,7 +9206,7 @@ var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
8786
9206
  var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels"];
8787
9207
  var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
8788
9208
  var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
8789
- var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
9209
+ var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
8790
9210
  var RunnerCredentialMintError2 = class extends Error {
8791
9211
  code;
8792
9212
  retryable;
@@ -8822,9 +9242,9 @@ function runnerCredentialErrorDetail2(error) {
8822
9242
  async function waitForRunnerCredentialRetry2() {
8823
9243
  await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
8824
9244
  }
8825
- function parseDaemonCliArgs(args) {
9245
+ function parseDaemonCliArgs(args, env = {}) {
8826
9246
  let serverUrl = "";
8827
- let apiKey = "";
9247
+ let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
8828
9248
  for (let i = 0; i < args.length; i++) {
8829
9249
  if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
8830
9250
  if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
@@ -8841,23 +9261,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
8841
9261
  }
8842
9262
  }
8843
9263
  function resolveChatBridgePath(moduleUrl = import.meta.url) {
8844
- const dirname = path16.dirname(fileURLToPath(moduleUrl));
8845
- const jsPath = path16.resolve(dirname, "chat-bridge.js");
9264
+ const dirname = path17.dirname(fileURLToPath2(moduleUrl));
9265
+ const jsPath = path17.resolve(dirname, "chat-bridge.js");
8846
9266
  try {
8847
9267
  accessSync(jsPath);
8848
9268
  return jsPath;
8849
9269
  } catch {
8850
- return path16.resolve(dirname, "chat-bridge.ts");
9270
+ return path17.resolve(dirname, "chat-bridge.ts");
8851
9271
  }
8852
9272
  }
8853
9273
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
8854
- const thisDir = path16.dirname(fileURLToPath(moduleUrl));
8855
- const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
9274
+ const thisDir = path17.dirname(fileURLToPath2(moduleUrl));
9275
+ const bundledDistPath = path17.resolve(thisDir, "cli", "index.js");
8856
9276
  try {
8857
9277
  accessSync(bundledDistPath);
8858
9278
  return bundledDistPath;
8859
9279
  } catch {
8860
- const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
9280
+ const workspaceDistPath = path17.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
8861
9281
  accessSync(workspaceDistPath);
8862
9282
  return workspaceDistPath;
8863
9283
  }
@@ -9036,7 +9456,7 @@ var DaemonCore = class {
9036
9456
  }
9037
9457
  resolveMachineStateRoot() {
9038
9458
  if (this.options.machineStateDir) return this.options.machineStateDir;
9039
- if (this.options.dataDir) return path16.join(path16.dirname(this.options.dataDir), "machines");
9459
+ if (this.options.dataDir) return path17.join(path17.dirname(this.options.dataDir), "machines");
9040
9460
  return resolveDefaultMachineStateRoot();
9041
9461
  }
9042
9462
  shouldEnableLocalTrace() {
@@ -9538,6 +9958,8 @@ var DaemonCore = class {
9538
9958
  };
9539
9959
 
9540
9960
  export {
9961
+ DAEMON_API_KEY_ENV,
9962
+ scrubDaemonAuthEnv,
9541
9963
  resolveWorkspaceDirectoryPath,
9542
9964
  scanWorkspaceDirectories,
9543
9965
  deleteWorkspaceDirectory,