@slock-ai/daemon 0.47.0 → 0.48.0-play.20260513145259

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.
@@ -11,11 +11,11 @@ import {
11
11
  } from "./chunk-B7XIMLOT.js";
12
12
 
13
13
  // src/core.ts
14
- import path15 from "path";
14
+ import path16 from "path";
15
15
  import os7 from "os";
16
16
  import { createRequire } from "module";
17
17
  import { accessSync } from "fs";
18
- import { fileURLToPath } from "url";
18
+ import { fileURLToPath as fileURLToPath2 } from "url";
19
19
 
20
20
  // ../shared/src/tracing/index.ts
21
21
  var DEFAULT_TRACE_FLAGS = "00";
@@ -640,6 +640,22 @@ var actionCardActionSchema = z.discriminatedUnion("type", [
640
640
  channelAddMemberOperationSchema
641
641
  ]);
642
642
 
643
+ // ../shared/src/translationLanguages.ts
644
+ var SUPPORTED_TRANSLATION_LANGUAGE_CODES = [
645
+ "en",
646
+ "zh-cn",
647
+ "zh-tw",
648
+ "ja",
649
+ "ko",
650
+ "es",
651
+ "fr",
652
+ "de",
653
+ "pt-br"
654
+ ];
655
+ var SUPPORTED_TRANSLATION_LANGUAGE_SET = new Set(
656
+ SUPPORTED_TRANSLATION_LANGUAGE_CODES
657
+ );
658
+
643
659
  // ../shared/src/testing/failpoints.ts
644
660
  var NoopFailpointRegistry = class {
645
661
  get enabled() {
@@ -709,6 +725,7 @@ var SERVER_CAPABILITY_MATRIX = {
709
725
  var RUNTIMES = [
710
726
  { id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
711
727
  { id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
728
+ { id: "pi", displayName: "Pi", binary: "pi", supported: true },
712
729
  { id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true },
713
730
  { id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
714
731
  { id: "cursor", displayName: "Cursor CLI", binary: "cursor-agent", supported: true },
@@ -750,10 +767,10 @@ var DISPLAY_PLAN_CONFIG = {
750
767
  };
751
768
 
752
769
  // src/agentProcessManager.ts
753
- import { mkdirSync as mkdirSync4, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync7 } from "fs";
770
+ import { mkdirSync as mkdirSync5, readdirSync as readdirSync3, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
754
771
  import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
755
772
  import { createHash as createHash2 } from "crypto";
756
- import path11 from "path";
773
+ import path12 from "path";
757
774
  import os5 from "os";
758
775
 
759
776
  // src/drivers/claude.ts
@@ -846,22 +863,23 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
846
863
  6. **\`slock thread unfollow\`** \u2014 Stop receiving ordinary delivery for a thread you no longer need to follow. This only affects your own agent attention state.
847
864
  7. **\`slock message read\`** \u2014 Read past messages from a channel, DM, or thread. Supports \`before\` / \`after\` pagination and \`around\` for centered context.
848
865
  8. **\`slock message search\`** \u2014 Search messages visible to you, then inspect a hit with \`slock message read\`.
849
- 9. **\`slock task list\`** \u2014 View a channel's task board.
850
- 10. **\`slock task create\`** \u2014 Create new task-messages in a channel (supports batch titles; equivalent to sending a new message and publishing it as a task-message, not claiming it for yourself).
851
- 11. **\`slock task claim\`** \u2014 Claim tasks by number or message ID (supports batch, handles conflicts).
852
- 12. **\`slock task unclaim\`** \u2014 Release your claim on a task.
853
- 13. **\`slock task update\`** \u2014 Change a task's status (e.g. to in_review or done).
854
- 14. **\`slock attachment upload\`** \u2014 Upload a file to attach to a message. Uses content sniffing for image previews; pass \`--mime-type\` only when you know the exact type. Returns an attachment ID to pass to \`slock message send\`.
855
- 15. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
856
- 16. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
857
- 17. **\`slock profile update\`** \u2014 Update your own profile. Supports \`--avatar-file <path>\`, \`--display-name <name>\`, and \`--description <text>\`. Values must be non-empty. Provide at least one flag per call; multiple flags can be combined.
858
- 18. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
859
- 19. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
860
- 20. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
861
- 21. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
862
- 22. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
863
- 23. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
864
- 24. **\`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\`).
866
+ 9. **\`slock message react\`** \u2014 Add or remove your reaction on a message. Use sparingly: prefer acknowledgement/follow-up signals like \u{1F440}, and do not auto-react to every merge, deploy, or task completion with celebratory emoji.
867
+ 10. **\`slock task list\`** \u2014 View a channel's task board.
868
+ 11. **\`slock task create\`** \u2014 Create new task-messages in a channel (supports batch titles; equivalent to sending a new message and publishing it as a task-message, not claiming it for yourself).
869
+ 12. **\`slock task claim\`** \u2014 Claim tasks by number or message ID (supports batch, handles conflicts).
870
+ 13. **\`slock task unclaim\`** \u2014 Release your claim on a task.
871
+ 14. **\`slock task update\`** \u2014 Change a task's status (e.g. to in_review or done).
872
+ 15. **\`slock attachment upload\`** \u2014 Upload a file to attach to a message. Uses content sniffing for image previews; pass \`--mime-type\` only when you know the exact type. Returns an attachment ID to pass to \`slock message send\`.
873
+ 16. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
874
+ 17. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
875
+ 18. **\`slock profile update\`** \u2014 Update your own profile. Supports \`--avatar-file <path>\`, \`--display-name <name>\`, and \`--description <text>\`. Values must be non-empty. Provide at least one flag per call; multiple flags can be combined.
876
+ 19. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
877
+ 20. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
878
+ 21. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
879
+ 22. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
880
+ 23. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
881
+ 24. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
882
+ 25. **\`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\`).
865
883
 
866
884
  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:
867
885
  - failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
@@ -1159,6 +1177,17 @@ Keep the user informed. They cannot see your internal reasoning, so:
1159
1177
  - For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
1160
1178
  - When done, summarize the result.
1161
1179
  - Keep updates concise \u2014 one or two sentences. Don't flood the chat.
1180
+ - 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:
1181
+
1182
+ \`\`\`html
1183
+ <details>
1184
+ <summary>Evidence, logs, or edge cases</summary>
1185
+
1186
+ Detailed notes go here.
1187
+ </details>
1188
+ \`\`\`
1189
+
1190
+ Do not hide the main recommendation, blocker, or required action inside \`<details>\`; only fold supporting evidence, logs, alternatives, or extended rationale.
1162
1191
 
1163
1192
  ### Conversation etiquette
1164
1193
 
@@ -1292,6 +1321,19 @@ function buildMcpSystemPrompt(config, opts) {
1292
1321
  return buildPrompt(config, "mcp", opts);
1293
1322
  }
1294
1323
 
1324
+ // src/authEnv.ts
1325
+ var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
1326
+ var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
1327
+ function scrubDaemonAuthEnv(env) {
1328
+ delete env[DAEMON_API_KEY_ENV];
1329
+ return env;
1330
+ }
1331
+ function scrubDaemonChildEnv(env) {
1332
+ delete env[DAEMON_API_KEY_ENV];
1333
+ delete env[SLOCK_AGENT_TOKEN_ENV];
1334
+ return env;
1335
+ }
1336
+
1295
1337
  // src/drivers/cliTransport.ts
1296
1338
  var shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
1297
1339
  function runtimeContextEnv(config) {
@@ -1345,7 +1387,7 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)}
1345
1387
  SLOCK_AGENT_TOKEN_FILE: tokenFile,
1346
1388
  PATH: `${slockDir}${path.delimiter}${process.env.PATH ?? ""}`
1347
1389
  };
1348
- delete spawnEnv.SLOCK_AGENT_TOKEN;
1390
+ scrubDaemonChildEnv(spawnEnv);
1349
1391
  return {
1350
1392
  slockDir,
1351
1393
  tokenFile,
@@ -1382,7 +1424,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn) {
1382
1424
  }
1383
1425
  function resolveCommandOnPath(command, deps = {}) {
1384
1426
  const platform = deps.platform ?? process.platform;
1385
- const env = deps.env ?? process.env;
1427
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
1386
1428
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
1387
1429
  if (platform === "win32") {
1388
1430
  return resolveCommandOnWindows(command, env, execFileSyncFn);
@@ -1407,7 +1449,7 @@ function firstExistingPath(candidates, deps = {}) {
1407
1449
  return null;
1408
1450
  }
1409
1451
  function readCommandVersion(command, args = [], deps = {}) {
1410
- const env = deps.env ?? process.env;
1452
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
1411
1453
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
1412
1454
  try {
1413
1455
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -1772,7 +1814,7 @@ var ClaudeDriver = class {
1772
1814
 
1773
1815
  // src/drivers/codex.ts
1774
1816
  import { spawn as spawn2, execSync } from "child_process";
1775
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
1817
+ import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
1776
1818
  import os2 from "os";
1777
1819
  import path4 from "path";
1778
1820
  function getCodexNotificationErrorMessage(params) {
@@ -1801,11 +1843,29 @@ function ensureGitRepoForCodex(workingDirectory, deps = {}) {
1801
1843
  );
1802
1844
  }
1803
1845
  var CODEX_DESKTOP_BUNDLE_PATH = "/Applications/Codex.app/Contents/Resources/codex";
1846
+ function resolveWindowsSandboxRunner(deps = {}) {
1847
+ const homeDir = deps.homeDir ?? os2.homedir();
1848
+ const sandboxBinDir = path4.join(homeDir, ".codex", ".sandbox-bin");
1849
+ if (!(deps.existsSyncFn ?? existsSync3)(sandboxBinDir)) return null;
1850
+ try {
1851
+ const files = readdirSync2(sandboxBinDir);
1852
+ const runners = files.filter((f) => f.startsWith("codex-command-runner") && f.endsWith(".exe")).sort((a, b) => b.localeCompare(a));
1853
+ if (runners.length > 0) return path4.join(sandboxBinDir, runners[0]);
1854
+ } catch {
1855
+ }
1856
+ return null;
1857
+ }
1804
1858
  function resolveCodexCommand(deps = {}) {
1805
1859
  const pathCommand = resolveCommandOnPath("codex", deps);
1806
1860
  if (pathCommand) return pathCommand;
1807
- if ((deps.platform ?? process.platform) !== "darwin") return null;
1808
- return firstExistingPath([CODEX_DESKTOP_BUNDLE_PATH], deps);
1861
+ const platform = deps.platform ?? process.platform;
1862
+ if (platform === "darwin") {
1863
+ return firstExistingPath([CODEX_DESKTOP_BUNDLE_PATH], deps);
1864
+ }
1865
+ if (platform === "win32") {
1866
+ return resolveWindowsSandboxRunner(deps);
1867
+ }
1868
+ return null;
1809
1869
  }
1810
1870
  function probeCodex(deps = {}) {
1811
1871
  if ((deps.platform ?? process.platform) === "win32") {
@@ -1830,22 +1890,32 @@ function resolveCodexSpawn(commandArgs, deps = {}) {
1830
1890
  if ((deps.platform ?? process.platform) !== "win32") {
1831
1891
  return { command: resolveCodexCommand(deps) ?? "codex", args: commandArgs };
1832
1892
  }
1893
+ const execSyncFn = deps.execSyncFn ?? execSync;
1894
+ const existsSyncFn = deps.existsSyncFn ?? existsSync3;
1895
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
1833
1896
  let codexEntry = null;
1834
1897
  try {
1835
- const globalRoot = execSync("npm root -g", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
1898
+ const globalRoot = execSyncFn("npm root -g", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], env }).trim();
1836
1899
  const candidate = path4.join(globalRoot, "@openai", "codex", "bin", "codex.js");
1837
- if (existsSync3(candidate)) codexEntry = candidate;
1900
+ if (existsSyncFn(candidate)) codexEntry = candidate;
1838
1901
  } catch {
1839
1902
  }
1840
1903
  if (!codexEntry) {
1841
1904
  try {
1842
- const cmdPath = execSync("where codex", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim().split(/\r?\n/)[0];
1905
+ const cmdPath = execSyncFn("where codex", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], env }).trim().split(/\r?\n/)[0];
1843
1906
  const candidate = path4.join(path4.dirname(cmdPath), "node_modules", "@openai", "codex", "bin", "codex.js");
1844
- if (existsSync3(candidate)) codexEntry = candidate;
1907
+ if (existsSyncFn(candidate)) codexEntry = candidate;
1845
1908
  } catch {
1846
1909
  }
1847
1910
  }
1848
1911
  if (!codexEntry) {
1912
+ const sandboxRunner = resolveWindowsSandboxRunner(deps);
1913
+ if (sandboxRunner) {
1914
+ return {
1915
+ command: sandboxRunner,
1916
+ args: commandArgs
1917
+ };
1918
+ }
1849
1919
  throw new Error(
1850
1920
  "Cannot resolve Codex CLI entry point on Windows. Ensure @openai/codex is installed globally via npm (npm i -g @openai/codex)."
1851
1921
  );
@@ -2296,13 +2366,13 @@ import { spawn as spawn3 } from "child_process";
2296
2366
  import path5 from "path";
2297
2367
  import { writeFileSync as writeFileSync3 } from "fs";
2298
2368
  function buildCopilotSpawnEnv(ctx) {
2299
- return {
2369
+ return scrubDaemonChildEnv({
2300
2370
  ...process.env,
2301
2371
  FORCE_COLOR: "0",
2302
2372
  NO_COLOR: "1",
2303
2373
  ...ctx.config.envVars || {},
2304
2374
  [SLOCK_HOME_ENV]: resolveSlockHome()
2305
- };
2375
+ });
2306
2376
  }
2307
2377
  var CopilotDriver = class {
2308
2378
  id = "copilot";
@@ -2460,13 +2530,13 @@ import { spawn as spawn4, spawnSync } from "child_process";
2460
2530
  import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
2461
2531
  import path6 from "path";
2462
2532
  function buildCursorSpawnEnv(ctx) {
2463
- return {
2533
+ return scrubDaemonChildEnv({
2464
2534
  ...process.env,
2465
2535
  FORCE_COLOR: "0",
2466
2536
  NO_COLOR: "1",
2467
2537
  ...ctx.config.envVars || {},
2468
2538
  [SLOCK_HOME_ENV]: resolveSlockHome()
2469
- };
2539
+ });
2470
2540
  }
2471
2541
  var CursorDriver = class {
2472
2542
  id = "cursor";
@@ -2636,7 +2706,7 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
2636
2706
  }
2637
2707
  function runCursorModelsCommand() {
2638
2708
  return spawnSync("cursor-agent", ["models"], {
2639
- env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
2709
+ env: scrubDaemonChildEnv({ ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" }),
2640
2710
  encoding: "utf8",
2641
2711
  timeout: 5e3
2642
2712
  });
@@ -2683,7 +2753,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
2683
2753
  }
2684
2754
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync2;
2685
2755
  const existsSyncFn = deps.existsSyncFn ?? existsSync5;
2686
- const env = deps.env ?? process.env;
2756
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
2687
2757
  const winPath = path7.win32;
2688
2758
  let geminiEntry = null;
2689
2759
  try {
@@ -2857,13 +2927,16 @@ var GeminiDriver = class {
2857
2927
  // src/drivers/kimi.ts
2858
2928
  import { randomUUID } from "crypto";
2859
2929
  import { spawn as spawn6 } from "child_process";
2860
- import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
2930
+ import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
2861
2931
  import os3 from "os";
2862
2932
  import path8 from "path";
2863
2933
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
2864
2934
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
2865
2935
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
2866
2936
  var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
2937
+ var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
2938
+ var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
2939
+ var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
2867
2940
  function parseToolArguments(raw) {
2868
2941
  if (typeof raw !== "string") return raw;
2869
2942
  try {
@@ -2872,6 +2945,73 @@ function parseToolArguments(raw) {
2872
2945
  return raw;
2873
2946
  }
2874
2947
  }
2948
+ function readKimiConfigSource(home = os3.homedir(), env = process.env) {
2949
+ const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
2950
+ if (inlineConfig && inlineConfig.trim()) {
2951
+ return {
2952
+ raw: inlineConfig,
2953
+ explicitPath: null,
2954
+ sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
2955
+ };
2956
+ }
2957
+ const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
2958
+ const configPath = explicitPath && explicitPath.trim() ? explicitPath : path8.join(home, ".kimi", "config.toml");
2959
+ try {
2960
+ return {
2961
+ raw: readFileSync3(configPath, "utf8"),
2962
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
2963
+ sourcePath: configPath
2964
+ };
2965
+ } catch {
2966
+ return {
2967
+ raw: null,
2968
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
2969
+ sourcePath: configPath
2970
+ };
2971
+ }
2972
+ }
2973
+ function buildKimiSpawnEnv(env = process.env) {
2974
+ const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
2975
+ delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
2976
+ delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
2977
+ return scrubDaemonChildEnv(spawnEnv);
2978
+ }
2979
+ function buildKimiEffectiveEnv(ctx, overrideEnv) {
2980
+ return {
2981
+ ...process.env,
2982
+ ...ctx.config.envVars || {},
2983
+ ...overrideEnv || {}
2984
+ };
2985
+ }
2986
+ function buildKimiLaunchOptions(ctx, opts = {}) {
2987
+ const env = buildKimiEffectiveEnv(ctx, opts.env);
2988
+ const source = readKimiConfigSource(opts.home ?? os3.homedir(), env);
2989
+ const args = [];
2990
+ let configFilePath = null;
2991
+ let configContent = null;
2992
+ if (source.explicitPath) {
2993
+ configFilePath = source.explicitPath;
2994
+ } else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
2995
+ configFilePath = path8.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
2996
+ configContent = source.raw;
2997
+ if (opts.writeGeneratedConfig !== false) {
2998
+ writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
2999
+ chmodSync(configFilePath, 384);
3000
+ }
3001
+ }
3002
+ if (configFilePath) {
3003
+ args.push("--config-file", configFilePath);
3004
+ }
3005
+ if (ctx.config.model && ctx.config.model !== "default") {
3006
+ args.push("--model", ctx.config.model);
3007
+ }
3008
+ return {
3009
+ args,
3010
+ env: buildKimiSpawnEnv(env),
3011
+ configFilePath,
3012
+ configContent
3013
+ };
3014
+ }
2875
3015
  var KimiDriver = class {
2876
3016
  id = "kimi";
2877
3017
  lifecycle = {
@@ -2888,7 +3028,25 @@ var KimiDriver = class {
2888
3028
  };
2889
3029
  model = {
2890
3030
  detectedModelsVerifiedAs: "launchable",
2891
- toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
3031
+ toLaunchSpec: (modelId, ctx, opts) => {
3032
+ if (!ctx) return { args: ["--model", modelId] };
3033
+ const launchCtx = {
3034
+ ...ctx,
3035
+ config: {
3036
+ ...ctx.config,
3037
+ model: modelId
3038
+ }
3039
+ };
3040
+ const launch = buildKimiLaunchOptions(launchCtx, {
3041
+ home: opts?.home,
3042
+ writeGeneratedConfig: false
3043
+ });
3044
+ return {
3045
+ args: launch.args,
3046
+ env: launch.env,
3047
+ configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
3048
+ };
3049
+ }
2892
3050
  };
2893
3051
  supportsStdinNotification = true;
2894
3052
  mcpToolPrefix = "";
@@ -2940,6 +3098,7 @@ var KimiDriver = class {
2940
3098
  }
2941
3099
  }
2942
3100
  }), "utf8");
3101
+ const launch = buildKimiLaunchOptions(ctx);
2943
3102
  const args = [
2944
3103
  "--wire",
2945
3104
  "--yolo",
@@ -2948,16 +3107,13 @@ var KimiDriver = class {
2948
3107
  "--mcp-config-file",
2949
3108
  mcpConfigPath,
2950
3109
  "--session",
2951
- this.sessionId
3110
+ this.sessionId,
3111
+ ...launch.args
2952
3112
  ];
2953
- if (ctx.config.model && ctx.config.model !== "default") {
2954
- args.push("--model", ctx.config.model);
2955
- }
2956
- const spawnEnv = { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" };
2957
3113
  const proc = spawn6("kimi", args, {
2958
3114
  cwd: ctx.workingDirectory,
2959
3115
  stdio: ["pipe", "pipe", "pipe"],
2960
- env: spawnEnv,
3116
+ env: launch.env,
2961
3117
  shell: process.platform === "win32"
2962
3118
  });
2963
3119
  proc.stdin?.write(JSON.stringify({
@@ -3074,14 +3230,9 @@ var KimiDriver = class {
3074
3230
  return detectKimiModels();
3075
3231
  }
3076
3232
  };
3077
- function detectKimiModels(home = os3.homedir()) {
3078
- const configPath = path8.join(home, ".kimi", "config.toml");
3079
- let raw;
3080
- try {
3081
- raw = readFileSync3(configPath, "utf8");
3082
- } catch {
3083
- return null;
3084
- }
3233
+ function detectKimiModels(home = os3.homedir(), opts = {}) {
3234
+ const raw = readKimiConfigSource(home, opts.env).raw;
3235
+ if (raw === null) return null;
3085
3236
  const models = [];
3086
3237
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
3087
3238
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -3328,7 +3479,7 @@ function detectOpenCodeModels(home = os4.homedir(), runCommand = runOpenCodeMode
3328
3479
  }
3329
3480
  function runOpenCodeModelsCommand(home) {
3330
3481
  const result = spawnSync2("opencode", ["models"], {
3331
- env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
3482
+ env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
3332
3483
  encoding: "utf8",
3333
3484
  timeout: 5e3
3334
3485
  });
@@ -3484,6 +3635,305 @@ var OpenCodeDriver = class {
3484
3635
  }
3485
3636
  };
3486
3637
 
3638
+ // src/drivers/pi.ts
3639
+ import { spawn as spawn8, spawnSync as spawnSync3 } from "child_process";
3640
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
3641
+ import { fileURLToPath } from "url";
3642
+ import path10 from "path";
3643
+ var CHAT_MCP_TOOL_PREFIX2 = "chat_";
3644
+ var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
3645
+ var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
3646
+ var MIN_BUNDLED_PI_VERSION = "0.74.0";
3647
+ function defaultPackageResolver(specifier) {
3648
+ return import.meta.resolve(specifier);
3649
+ }
3650
+ function parseSemver2(version) {
3651
+ const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
3652
+ if (!match) return null;
3653
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
3654
+ }
3655
+ function isSupportedPiVersion(version) {
3656
+ if (!version) return true;
3657
+ const actual = parseSemver2(version);
3658
+ const minimum = parseSemver2(MIN_BUNDLED_PI_VERSION);
3659
+ if (!actual || !minimum) return true;
3660
+ for (let i = 0; i < 3; i += 1) {
3661
+ if (actual[i] > minimum[i]) return true;
3662
+ if (actual[i] < minimum[i]) return false;
3663
+ }
3664
+ return true;
3665
+ }
3666
+ function resolveBundledPiCliPath(resolver = defaultPackageResolver) {
3667
+ try {
3668
+ const mainPath = fileURLToPath(resolver("@earendil-works/pi-coding-agent"));
3669
+ const cliPath = path10.join(path10.dirname(mainPath), "cli.js");
3670
+ return existsSync7(cliPath) ? cliPath : null;
3671
+ } catch {
3672
+ return null;
3673
+ }
3674
+ }
3675
+ function readBundledPiVersion(cliPath) {
3676
+ const result = spawnSync3(process.execPath, [cliPath, "--version"], {
3677
+ env: scrubDaemonChildEnv({ ...process.env }),
3678
+ encoding: "utf8",
3679
+ timeout: 5e3
3680
+ });
3681
+ if (result.error || result.status !== 0) return null;
3682
+ return String(result.stdout || result.stderr || "").trim() || null;
3683
+ }
3684
+ function unsupportedPiVersionMessage(version) {
3685
+ if (!version || isSupportedPiVersion(version)) return null;
3686
+ return `Bundled Pi CLI ${version} is unsupported; requires Pi >= ${MIN_BUNDLED_PI_VERSION}.`;
3687
+ }
3688
+ function buildPiLaunchOptions(ctx, opts = {}) {
3689
+ const cliPath = opts.cliPath ?? resolveBundledPiCliPath();
3690
+ if (!cliPath) {
3691
+ throw new Error("Bundled Pi CLI not found in @earendil-works/pi-coding-agent");
3692
+ }
3693
+ const piDir = path10.join(ctx.workingDirectory, ".slock", "pi");
3694
+ const sessionDir = path10.join(piDir, "sessions");
3695
+ mkdirSync4(sessionDir, { recursive: true });
3696
+ const slock = prepareCliTransport(ctx, {
3697
+ NO_COLOR: "1",
3698
+ PI_CODING_AGENT_DIR: piDir,
3699
+ PI_CODING_AGENT_SESSION_DIR: sessionDir
3700
+ });
3701
+ const systemPromptPath = path10.join(slock.slockDir, "pi-system-prompt.md");
3702
+ writeFileSync7(systemPromptPath, ctx.standingPrompt, { encoding: "utf8", mode: 384 });
3703
+ const args = [
3704
+ cliPath,
3705
+ "--mode",
3706
+ "json",
3707
+ "--session-dir",
3708
+ sessionDir,
3709
+ "--append-system-prompt",
3710
+ systemPromptPath,
3711
+ "--no-context-files",
3712
+ "--no-extensions",
3713
+ "--no-skills",
3714
+ "--no-prompt-templates",
3715
+ "--no-themes"
3716
+ ];
3717
+ if (ctx.config.model && ctx.config.model !== "default") {
3718
+ args.push("--model", ctx.config.model);
3719
+ }
3720
+ if (ctx.config.sessionId) {
3721
+ args.push("--session", ctx.config.sessionId);
3722
+ }
3723
+ const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
3724
+ args.push(turnPrompt);
3725
+ return {
3726
+ command: process.execPath,
3727
+ args,
3728
+ env: slock.spawnEnv,
3729
+ sessionDir,
3730
+ systemPromptPath
3731
+ };
3732
+ }
3733
+ function isSystemFirstMessageTask2(message) {
3734
+ return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
3735
+ }
3736
+ function buildPiSystemPrompt(config) {
3737
+ return buildCliTransportSystemPrompt(config, {
3738
+ toolPrefix: CHAT_MCP_TOOL_PREFIX2,
3739
+ extraCriticalRules: [
3740
+ "- 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."
3741
+ ],
3742
+ postStartupNotes: [
3743
+ "**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."
3744
+ ],
3745
+ includeStdinNotificationSection: false,
3746
+ messageNotificationStyle: "poll"
3747
+ });
3748
+ }
3749
+ function contentText(content) {
3750
+ if (!content) return "";
3751
+ const chunks = [];
3752
+ for (const item of content) {
3753
+ if (item.type === "text" && typeof item.text === "string") {
3754
+ chunks.push(item.text);
3755
+ }
3756
+ }
3757
+ return chunks.join("");
3758
+ }
3759
+ function apiKeyErrorMessage(line) {
3760
+ const trimmed = line.trim();
3761
+ if (!trimmed) return null;
3762
+ if (/no api key found/i.test(trimmed)) return trimmed;
3763
+ if (/api key.+required/i.test(trimmed)) return trimmed;
3764
+ if (/no models available/i.test(trimmed)) return trimmed;
3765
+ return null;
3766
+ }
3767
+ var PiDriver = class {
3768
+ id = "pi";
3769
+ lifecycle = {
3770
+ kind: "per_turn",
3771
+ start: "defer_until_concrete_message",
3772
+ exit: "terminate_on_turn_end",
3773
+ inFlightWake: "coalesce_into_pending"
3774
+ };
3775
+ communication = {
3776
+ chat: "slock_cli",
3777
+ runtimeControl: "none"
3778
+ };
3779
+ session = {
3780
+ recovery: "resume_or_fresh"
3781
+ };
3782
+ model = {
3783
+ detectedModelsVerifiedAs: "launchable",
3784
+ toLaunchSpec: (modelId, ctx) => {
3785
+ if (!ctx) return { args: ["--model", modelId] };
3786
+ const launchCtx = {
3787
+ ...ctx,
3788
+ config: {
3789
+ ...ctx.config,
3790
+ model: modelId
3791
+ }
3792
+ };
3793
+ const launch = buildPiLaunchOptions(launchCtx);
3794
+ return {
3795
+ args: launch.args,
3796
+ env: launch.env,
3797
+ configFiles: [launch.systemPromptPath]
3798
+ };
3799
+ }
3800
+ };
3801
+ supportsStdinNotification = false;
3802
+ mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
3803
+ busyDeliveryMode = "none";
3804
+ terminateProcessOnTurnEnd = true;
3805
+ deferSpawnUntilMessage = true;
3806
+ usesSlockCliForCommunication = true;
3807
+ sessionId = null;
3808
+ sessionAnnounced = false;
3809
+ apiKeyErrorAnnounced = false;
3810
+ turnEnded = false;
3811
+ assistantTextByMessageId = /* @__PURE__ */ new Map();
3812
+ shouldDeferWakeMessage(message) {
3813
+ return isSystemFirstMessageTask2(message);
3814
+ }
3815
+ probe() {
3816
+ const cliPath = resolveBundledPiCliPath();
3817
+ if (!cliPath) return { available: false };
3818
+ const version = readBundledPiVersion(cliPath) || void 0;
3819
+ const unsupportedMessage = unsupportedPiVersionMessage(version);
3820
+ if (unsupportedMessage) {
3821
+ return {
3822
+ available: false,
3823
+ version: `${version} (requires >= ${MIN_BUNDLED_PI_VERSION})`
3824
+ };
3825
+ }
3826
+ return { available: true, version };
3827
+ }
3828
+ async detectModels() {
3829
+ return null;
3830
+ }
3831
+ spawn(ctx) {
3832
+ this.sessionId = ctx.config.sessionId || null;
3833
+ this.sessionAnnounced = false;
3834
+ this.apiKeyErrorAnnounced = false;
3835
+ this.turnEnded = false;
3836
+ this.assistantTextByMessageId.clear();
3837
+ const cliPath = resolveBundledPiCliPath();
3838
+ if (!cliPath) throw new Error("Bundled Pi CLI not found in @earendil-works/pi-coding-agent");
3839
+ const unsupportedMessage = unsupportedPiVersionMessage(readBundledPiVersion(cliPath));
3840
+ if (unsupportedMessage) throw new Error(unsupportedMessage);
3841
+ const launch = buildPiLaunchOptions(ctx, { cliPath });
3842
+ const proc = spawn8(launch.command, launch.args, {
3843
+ cwd: ctx.workingDirectory,
3844
+ stdio: ["pipe", "pipe", "pipe"],
3845
+ env: launch.env,
3846
+ shell: process.platform === "win32"
3847
+ });
3848
+ proc.stdin?.end();
3849
+ return { process: proc };
3850
+ }
3851
+ parseLine(line) {
3852
+ let event;
3853
+ try {
3854
+ event = JSON.parse(line);
3855
+ } catch {
3856
+ if (this.apiKeyErrorAnnounced) return [];
3857
+ const message = apiKeyErrorMessage(line);
3858
+ if (!message) return [];
3859
+ this.apiKeyErrorAnnounced = true;
3860
+ this.turnEnded = true;
3861
+ return [
3862
+ { kind: "error", message },
3863
+ { kind: "turn_end", sessionId: this.sessionId || void 0 }
3864
+ ];
3865
+ }
3866
+ const events = [];
3867
+ if (event.type === "session" && event.id) {
3868
+ this.sessionId = event.id;
3869
+ }
3870
+ if (!this.sessionAnnounced && this.sessionId) {
3871
+ events.push({ kind: "session_init", sessionId: this.sessionId });
3872
+ this.sessionAnnounced = true;
3873
+ }
3874
+ switch (event.type) {
3875
+ case "agent_start":
3876
+ case "turn_start":
3877
+ events.push({ kind: "thinking", text: "" });
3878
+ break;
3879
+ case "message_update":
3880
+ case "message_end":
3881
+ if (event.message?.role === "assistant") {
3882
+ const key = event.message.id || "current";
3883
+ const currentText = contentText(event.message.content);
3884
+ const previousText = this.assistantTextByMessageId.get(key) ?? "";
3885
+ if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
3886
+ events.push({ kind: "text", text: currentText.slice(previousText.length) });
3887
+ } else if (currentText && currentText !== previousText) {
3888
+ events.push({ kind: "text", text: currentText });
3889
+ }
3890
+ this.assistantTextByMessageId.set(key, currentText);
3891
+ if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
3892
+ events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
3893
+ }
3894
+ }
3895
+ break;
3896
+ case "tool_execution_start":
3897
+ events.push({
3898
+ kind: "tool_call",
3899
+ name: event.toolName || "unknown_tool",
3900
+ input: event.args
3901
+ });
3902
+ break;
3903
+ case "tool_execution_end":
3904
+ events.push({
3905
+ kind: "tool_output",
3906
+ name: event.toolName || "unknown_tool"
3907
+ });
3908
+ if (event.isError) {
3909
+ events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
3910
+ }
3911
+ break;
3912
+ case "compaction_start":
3913
+ events.push({ kind: "compaction_started" });
3914
+ break;
3915
+ case "compaction_end":
3916
+ events.push({ kind: "compaction_finished" });
3917
+ if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
3918
+ break;
3919
+ case "turn_end":
3920
+ case "agent_end":
3921
+ if (!this.turnEnded) {
3922
+ events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
3923
+ this.turnEnded = true;
3924
+ }
3925
+ break;
3926
+ }
3927
+ return events;
3928
+ }
3929
+ encodeStdinMessage(_text, _sessionId, _opts) {
3930
+ return null;
3931
+ }
3932
+ buildSystemPrompt(config, _agentId) {
3933
+ return buildPiSystemPrompt(config);
3934
+ }
3935
+ };
3936
+
3487
3937
  // src/drivers/index.ts
3488
3938
  var driverFactories = {
3489
3939
  claude: () => new ClaudeDriver(),
@@ -3492,7 +3942,8 @@ var driverFactories = {
3492
3942
  cursor: () => new CursorDriver(),
3493
3943
  gemini: () => new GeminiDriver(),
3494
3944
  kimi: () => new KimiDriver(),
3495
- opencode: () => new OpenCodeDriver()
3945
+ opencode: () => new OpenCodeDriver(),
3946
+ pi: () => new PiDriver()
3496
3947
  };
3497
3948
  function getDriver(runtimeId) {
3498
3949
  const createDriver = driverFactories[runtimeId];
@@ -3505,7 +3956,7 @@ function getDriver(runtimeId) {
3505
3956
 
3506
3957
  // src/workspaces.ts
3507
3958
  import { readdir, rm, stat } from "fs/promises";
3508
- import path10 from "path";
3959
+ import path11 from "path";
3509
3960
  function isValidWorkspaceDirectoryName(directoryName) {
3510
3961
  return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
3511
3962
  }
@@ -3513,7 +3964,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
3513
3964
  if (!isValidWorkspaceDirectoryName(directoryName)) {
3514
3965
  return null;
3515
3966
  }
3516
- return path10.join(dataDir, directoryName);
3967
+ return path11.join(dataDir, directoryName);
3517
3968
  }
3518
3969
  function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
3519
3970
  return {
@@ -3562,7 +4013,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
3562
4013
  return summary;
3563
4014
  }
3564
4015
  const childSummaries = await Promise.all(
3565
- entries.map((entry) => summarizeWorkspaceEntry(path10.join(dirPath, entry.name), entry))
4016
+ entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
3566
4017
  );
3567
4018
  for (const childSummary of childSummaries) {
3568
4019
  summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
@@ -3581,7 +4032,7 @@ async function scanWorkspaceDirectories(dataDir) {
3581
4032
  if (!entry.isDirectory()) {
3582
4033
  return null;
3583
4034
  }
3584
- const dirPath = path10.join(dataDir, entry.name);
4035
+ const dirPath = path11.join(dataDir, entry.name);
3585
4036
  try {
3586
4037
  const summary = await summarizeWorkspaceDirectory(dirPath);
3587
4038
  return {
@@ -3772,19 +4223,19 @@ function findSessionJsonl(root, predicate) {
3772
4223
  if (depth < 0 || visited >= maxEntries) return null;
3773
4224
  let entries;
3774
4225
  try {
3775
- entries = readdirSync2(dir, { withFileTypes: true }).sort((a, b) => b.name.localeCompare(a.name));
4226
+ entries = readdirSync3(dir, { withFileTypes: true }).sort((a, b) => b.name.localeCompare(a.name));
3776
4227
  } catch {
3777
4228
  return null;
3778
4229
  }
3779
4230
  for (const entry of entries) {
3780
4231
  if (++visited > maxEntries) return null;
3781
4232
  if (!entry.isFile() || !predicate(entry.name)) continue;
3782
- return path11.join(dir, entry.name);
4233
+ return path12.join(dir, entry.name);
3783
4234
  }
3784
4235
  for (const entry of entries) {
3785
4236
  if (++visited > maxEntries) return null;
3786
4237
  if (!entry.isDirectory()) continue;
3787
- const found = visit(path11.join(dir, entry.name), depth - 1);
4238
+ const found = visit(path12.join(dir, entry.name), depth - 1);
3788
4239
  if (found) return found;
3789
4240
  }
3790
4241
  return null;
@@ -3797,10 +4248,10 @@ function safeSessionFilename(value) {
3797
4248
  }
3798
4249
  function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
3799
4250
  try {
3800
- const dir = path11.join(fallbackDir, ".slock", "runtime-sessions");
3801
- mkdirSync4(dir, { recursive: true });
3802
- const filePath = path11.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
3803
- writeFileSync7(filePath, JSON.stringify({
4251
+ const dir = path12.join(fallbackDir, ".slock", "runtime-sessions");
4252
+ mkdirSync5(dir, { recursive: true });
4253
+ const filePath = path12.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
4254
+ writeFileSync8(filePath, JSON.stringify({
3804
4255
  type: "runtime_session_handoff",
3805
4256
  runtime,
3806
4257
  sessionId,
@@ -3819,7 +4270,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
3819
4270
  }
3820
4271
  }
3821
4272
  function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), fallbackDir) {
3822
- const directPath = path11.isAbsolute(sessionId) ? sessionId : null;
4273
+ const directPath = path12.isAbsolute(sessionId) ? sessionId : null;
3823
4274
  if (directPath) {
3824
4275
  try {
3825
4276
  if (statSync2(directPath).isFile()) {
@@ -3828,7 +4279,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), f
3828
4279
  } catch {
3829
4280
  }
3830
4281
  }
3831
- const resolvedPath = runtime === "claude" ? findSessionJsonl(path11.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`) : runtime === "codex" ? findSessionJsonl(path11.join(homeDir, ".codex", "sessions"), (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId)) : null;
4282
+ 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;
3832
4283
  if (!resolvedPath && fallbackDir) {
3833
4284
  const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
3834
4285
  if (fallback) return fallback;
@@ -4368,6 +4819,17 @@ Success = user starts useful collaboration and setup progresses,
4368
4819
  not finishing a long onboarding conversation in one channel.
4369
4820
  `;
4370
4821
  }
4822
+ function createRuntimeTraceCounters() {
4823
+ return {
4824
+ events: 0,
4825
+ toolCalls: 0,
4826
+ toolOutputs: 0,
4827
+ compactionStarts: 0,
4828
+ compactionFinishes: 0,
4829
+ textEvents: 0,
4830
+ thinkingEvents: 0
4831
+ };
4832
+ }
4371
4833
  function createGatedSteeringState() {
4372
4834
  return {
4373
4835
  phase: "idle",
@@ -4546,8 +5008,111 @@ function buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes) {
4546
5008
  outstandingToolUses: ap.gatedSteering.outstandingToolUses,
4547
5009
  compacting: ap.gatedSteering.compacting,
4548
5010
  recentStderrCount: ap.recentStderr.length,
4549
- recentStdoutCount: ap.recentStdout.length
5011
+ recentStdoutCount: ap.recentStdout.length,
5012
+ ...runtimeTraceCounterAttrs(ap)
5013
+ }
5014
+ };
5015
+ }
5016
+ function bucketBytes(value) {
5017
+ const bytes = typeof value === "string" ? Buffer.byteLength(value, "utf8") : Math.max(0, Math.floor(value ?? 0));
5018
+ if (bytes === 0) return "0";
5019
+ if (bytes < 1024) return "<1KB";
5020
+ if (bytes < 10 * 1024) return "1KB-10KB";
5021
+ if (bytes < 100 * 1024) return "10KB-100KB";
5022
+ if (bytes < 1024 * 1024) return "100KB-1MB";
5023
+ return "1MB+";
5024
+ }
5025
+ function attachmentBytesBucket(bytes, knownCount) {
5026
+ return knownCount > 0 ? bucketBytes(bytes) : "unknown";
5027
+ }
5028
+ function summarizeMessageInputBytes(messages) {
5029
+ if (!messages || messages.length === 0) {
5030
+ return {
5031
+ runtime_input_messages_count: 0,
5032
+ runtime_input_messages_content_bytes_bucket: "0",
5033
+ runtime_input_attachments_count: 0,
5034
+ runtime_input_image_attachments_count: 0,
5035
+ runtime_input_attachments_size_known_count: 0,
5036
+ runtime_input_attachments_bytes_bucket: "0",
5037
+ runtime_input_image_attachments_size_known_count: 0,
5038
+ runtime_input_image_attachments_bytes_bucket: "0",
5039
+ runtime_input_largest_attachment_bytes_bucket: "0",
5040
+ runtime_input_thread_context_messages_count: 0,
5041
+ runtime_input_thread_context_content_bytes_bucket: "0"
5042
+ };
5043
+ }
5044
+ let contentBytes = 0;
5045
+ let attachmentCount = 0;
5046
+ let imageAttachmentCount = 0;
5047
+ let attachmentSizeKnownCount = 0;
5048
+ let attachmentBytes = 0;
5049
+ let imageAttachmentSizeKnownCount = 0;
5050
+ let imageAttachmentBytes = 0;
5051
+ let largestAttachmentBytes = 0;
5052
+ let threadContextMessages = 0;
5053
+ let threadContextContentBytes = 0;
5054
+ for (const message of messages) {
5055
+ contentBytes += Buffer.byteLength(message.content || "", "utf8");
5056
+ for (const attachment of message.attachments || []) {
5057
+ attachmentCount++;
5058
+ if (typeof attachment.sizeBytes === "number" && Number.isFinite(attachment.sizeBytes) && attachment.sizeBytes >= 0) {
5059
+ attachmentSizeKnownCount++;
5060
+ attachmentBytes += attachment.sizeBytes;
5061
+ largestAttachmentBytes = Math.max(largestAttachmentBytes, attachment.sizeBytes);
5062
+ }
5063
+ if (attachment.mimeType?.startsWith("image/")) {
5064
+ imageAttachmentCount++;
5065
+ if (typeof attachment.sizeBytes === "number" && Number.isFinite(attachment.sizeBytes) && attachment.sizeBytes >= 0) {
5066
+ imageAttachmentSizeKnownCount++;
5067
+ imageAttachmentBytes += attachment.sizeBytes;
5068
+ }
5069
+ }
4550
5070
  }
5071
+ const joinContext = message.thread_join_context;
5072
+ if (joinContext) {
5073
+ const contextMessages = [joinContext.parent_message, ...joinContext.recent_messages];
5074
+ threadContextMessages += contextMessages.length;
5075
+ for (const contextMessage of contextMessages) {
5076
+ threadContextContentBytes += Buffer.byteLength(contextMessage.content || "", "utf8");
5077
+ }
5078
+ }
5079
+ }
5080
+ return {
5081
+ runtime_input_messages_count: messages.length,
5082
+ runtime_input_messages_content_bytes_bucket: bucketBytes(contentBytes),
5083
+ runtime_input_attachments_count: attachmentCount,
5084
+ runtime_input_image_attachments_count: imageAttachmentCount,
5085
+ runtime_input_attachments_size_known_count: attachmentSizeKnownCount,
5086
+ runtime_input_attachments_bytes_bucket: attachmentCount > 0 ? attachmentBytesBucket(attachmentBytes, attachmentSizeKnownCount) : "0",
5087
+ runtime_input_image_attachments_size_known_count: imageAttachmentSizeKnownCount,
5088
+ runtime_input_image_attachments_bytes_bucket: imageAttachmentCount > 0 ? attachmentBytesBucket(imageAttachmentBytes, imageAttachmentSizeKnownCount) : "0",
5089
+ runtime_input_largest_attachment_bytes_bucket: attachmentCount > 0 ? attachmentBytesBucket(largestAttachmentBytes, attachmentSizeKnownCount) : "0",
5090
+ runtime_input_thread_context_messages_count: threadContextMessages,
5091
+ runtime_input_thread_context_content_bytes_bucket: bucketBytes(threadContextContentBytes)
5092
+ };
5093
+ }
5094
+ function buildRuntimeInputTraceAttrs(opts) {
5095
+ return {
5096
+ runtime_input_source: opts.source,
5097
+ runtime_input_prompt_bytes_bucket: bucketBytes(opts.prompt),
5098
+ runtime_input_standing_prompt_bytes_bucket: bucketBytes(opts.standingPrompt),
5099
+ runtime_input_resume_prompt_present: Boolean(opts.resumePrompt),
5100
+ runtime_input_resume_prompt_bytes_bucket: bucketBytes(opts.resumePrompt),
5101
+ runtime_input_session_present: opts.sessionIdPresent,
5102
+ runtime_input_native_standing_prompt_present: opts.nativeStandingPrompt,
5103
+ runtime_input_unread_channels_count: opts.unreadSummary ? Object.keys(opts.unreadSummary).length : 0,
5104
+ ...summarizeMessageInputBytes(opts.messages)
5105
+ };
5106
+ }
5107
+ function runtimeTraceCounterAttrs(ap) {
5108
+ return {
5109
+ runtime_events_count: ap.runtimeTraceCounters.events,
5110
+ runtime_tool_calls_count: ap.runtimeTraceCounters.toolCalls,
5111
+ runtime_tool_outputs_count: ap.runtimeTraceCounters.toolOutputs,
5112
+ runtime_compaction_starts_count: ap.runtimeTraceCounters.compactionStarts,
5113
+ runtime_compaction_finishes_count: ap.runtimeTraceCounters.compactionFinishes,
5114
+ runtime_text_events_count: ap.runtimeTraceCounters.textEvents,
5115
+ runtime_thinking_events_count: ap.runtimeTraceCounters.thinkingEvents
4551
5116
  };
4552
5117
  }
4553
5118
  function getMessageDeliveryText(driver) {
@@ -4847,26 +5412,26 @@ var AgentProcessManager = class _AgentProcessManager {
4847
5412
  this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
4848
5413
  try {
4849
5414
  const driver = this.driverResolver(config.runtime || "claude");
4850
- const agentDataDir = path11.join(this.dataDir, agentId);
5415
+ const agentDataDir = path12.join(this.dataDir, agentId);
4851
5416
  await mkdir(agentDataDir, { recursive: true });
4852
5417
  const runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
4853
- const memoryMdPath = path11.join(agentDataDir, "MEMORY.md");
5418
+ const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
4854
5419
  try {
4855
5420
  await access(memoryMdPath);
4856
5421
  } catch {
4857
5422
  const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
4858
5423
  await writeFile(memoryMdPath, initialMemoryMd);
4859
5424
  }
4860
- const notesDir = path11.join(agentDataDir, "notes");
5425
+ const notesDir = path12.join(agentDataDir, "notes");
4861
5426
  await mkdir(notesDir, { recursive: true });
4862
5427
  if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
4863
5428
  const seedFiles = buildOnboardingSeedFiles();
4864
5429
  for (const { relativePath, content } of seedFiles) {
4865
- const fullPath = path11.join(agentDataDir, relativePath);
5430
+ const fullPath = path12.join(agentDataDir, relativePath);
4866
5431
  try {
4867
5432
  await access(fullPath);
4868
5433
  } catch {
4869
- await mkdir(path11.dirname(fullPath), { recursive: true });
5434
+ await mkdir(path12.dirname(fullPath), { recursive: true });
4870
5435
  await writeFile(fullPath, content);
4871
5436
  }
4872
5437
  }
@@ -4874,17 +5439,21 @@ var AgentProcessManager = class _AgentProcessManager {
4874
5439
  const isResume = !!runtimeConfig.sessionId;
4875
5440
  const standingPrompt = driver.buildSystemPrompt(runtimeConfig, agentId);
4876
5441
  let prompt;
5442
+ let promptSource;
4877
5443
  if (runtimeConfig.runtimeProfileControl && !wakeMessage) {
4878
5444
  prompt = driver.supportsNativeStandingPrompt ? NATIVE_STANDING_PROMPT_STARTUP_INPUT : formatRuntimeProfileControlStartupInput(runtimeConfig.runtimeProfileControl, driver);
5445
+ promptSource = "runtime_profile_control";
4879
5446
  } else if (isResume && resumePrompt) {
4880
5447
  prompt = resumePrompt;
4881
5448
  prompt += getBusyDeliveryNote(driver);
5449
+ promptSource = "resume_prompt";
4882
5450
  } else if (wakeMessage) {
4883
5451
  const runtimeProfileControlPrompt = formatRuntimeProfileControlPrompt([wakeMessage]);
4884
5452
  const channelLabel = formatChannelLabel(wakeMessage);
4885
5453
  prompt = runtimeProfileControlPrompt ?? `New message received:
4886
5454
 
4887
5455
  ${formatIncomingMessage(wakeMessage, driver)}`;
5456
+ promptSource = runtimeProfileControlPrompt ? "runtime_profile_control_message" : "wake_message";
4888
5457
  if (!runtimeProfileControlPrompt && unreadSummary && Object.keys(unreadSummary).length > 0) {
4889
5458
  const otherUnread = Object.entries(unreadSummary).filter(([key]) => key !== channelLabel);
4890
5459
  if (otherUnread.length > 0) {
@@ -4918,12 +5487,25 @@ IMPORTANT: If the message requires multi-step work (e.g. research, code changes,
4918
5487
  prompt += `
4919
5488
 
4920
5489
  Use ${communicationCommand(driver, "read_history")} to catch up on the channels listed above, then stop. Read each listed channel at most once unless a read fails. Do NOT call ${communicationCommand(driver, "check_messages")} in this mode. If the history reveals a direct request, assignment, @mention, review request, or task clearly addressed to you, switch into active handling instead of stopping: ${dynamicReplyInstruction(driver)} and ${dynamicClaimInstruction(driver)} before starting work. Otherwise, do NOT send any message in this mode. ${getMessageDeliveryText(driver)}`;
5490
+ promptSource = "resume_unread_summary";
4921
5491
  } else if (isResume) {
4922
5492
  prompt = `No new messages while you were away. Nothing to do \u2014 just stop. ${getMessageDeliveryText(driver)}`;
4923
5493
  prompt += getBusyDeliveryNote(driver);
5494
+ promptSource = "resume_empty";
4924
5495
  } else {
4925
5496
  prompt = driver.supportsNativeStandingPrompt ? NATIVE_STANDING_PROMPT_STARTUP_INPUT : standingPrompt;
5497
+ promptSource = "cold_start";
4926
5498
  }
5499
+ const runtimeInputTraceAttrs = buildRuntimeInputTraceAttrs({
5500
+ source: promptSource,
5501
+ prompt,
5502
+ standingPrompt,
5503
+ resumePrompt,
5504
+ messages: wakeMessage ? [wakeMessage] : void 0,
5505
+ unreadSummary,
5506
+ sessionIdPresent: isResume,
5507
+ nativeStandingPrompt: Boolean(driver.supportsNativeStandingPrompt)
5508
+ });
4927
5509
  const effectiveConfig = await this.buildSpawnConfig(agentId, runtimeConfig);
4928
5510
  const canDeferEmptyStart = driver.deferSpawnUntilMessage === true && !wakeMessage && !runtimeConfig.runtimeProfileControl && (!unreadSummary || Object.keys(unreadSummary).length === 0);
4929
5511
  if (canDeferEmptyStart) {
@@ -4984,6 +5566,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
4984
5566
  lastRuntimeEventAt: Date.now(),
4985
5567
  runtimeProgressStaleSince: null,
4986
5568
  runtimeTraceSpan: null,
5569
+ runtimeTraceCounters: createRuntimeTraceCounters(),
4987
5570
  lastActivity: "",
4988
5571
  lastActivityDetail: "",
4989
5572
  activityClientSeq: 0,
@@ -5005,7 +5588,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5005
5588
  sessionId: effectiveConfig.sessionId || null,
5006
5589
  launchId: launchId || null
5007
5590
  });
5008
- this.startRuntimeTrace(agentId, agentProcess, "spawn", wakeMessage ? [wakeMessage] : void 0);
5591
+ this.startRuntimeTrace(agentId, agentProcess, "spawn", wakeMessage ? [wakeMessage] : void 0, runtimeInputTraceAttrs);
5009
5592
  this.agentsStarting.delete(agentId);
5010
5593
  if (config.runtimeProfileControl) {
5011
5594
  this.ackInjectedRuntimeProfileControl(agentId, config.runtimeProfileControl, agentProcess.launchId);
@@ -5099,6 +5682,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5099
5682
  expectedTerminationReason: ap.expectedTerminationReason || void 0,
5100
5683
  exitCode: finalCode,
5101
5684
  exitSignal: finalSignal,
5685
+ ...runtimeTraceCounterAttrs(ap),
5102
5686
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "process_exit")
5103
5687
  });
5104
5688
  if (processEndedCleanly) {
@@ -5526,7 +6110,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5526
6110
  return true;
5527
6111
  }
5528
6112
  async resetWorkspace(agentId) {
5529
- const agentDataDir = path11.join(this.dataDir, agentId);
6113
+ const agentDataDir = path12.join(this.dataDir, agentId);
5530
6114
  try {
5531
6115
  await rm2(agentDataDir, { recursive: true, force: true });
5532
6116
  logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
@@ -5563,7 +6147,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5563
6147
  return result;
5564
6148
  }
5565
6149
  buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
5566
- const workspacePath = path11.join(this.dataDir, agentId);
6150
+ const workspacePath = path12.join(this.dataDir, agentId);
5567
6151
  return {
5568
6152
  agentId,
5569
6153
  launchId,
@@ -5811,7 +6395,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5811
6395
  }
5812
6396
  // Workspace file browsing
5813
6397
  async getFileTree(agentId, dirPath) {
5814
- const agentDir = path11.join(this.dataDir, agentId);
6398
+ const agentDir = path12.join(this.dataDir, agentId);
5815
6399
  try {
5816
6400
  await stat2(agentDir);
5817
6401
  } catch {
@@ -5819,8 +6403,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5819
6403
  }
5820
6404
  let targetDir = agentDir;
5821
6405
  if (dirPath) {
5822
- const resolved = path11.resolve(agentDir, dirPath);
5823
- if (!resolved.startsWith(agentDir + path11.sep) && resolved !== agentDir) {
6406
+ const resolved = path12.resolve(agentDir, dirPath);
6407
+ if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
5824
6408
  return [];
5825
6409
  }
5826
6410
  targetDir = resolved;
@@ -5828,14 +6412,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5828
6412
  return this.listDirectoryChildren(targetDir, agentDir);
5829
6413
  }
5830
6414
  async readFile(agentId, filePath) {
5831
- const agentDir = path11.join(this.dataDir, agentId);
5832
- const resolved = path11.resolve(agentDir, filePath);
5833
- if (!resolved.startsWith(agentDir + path11.sep) && resolved !== agentDir) {
6415
+ const agentDir = path12.join(this.dataDir, agentId);
6416
+ const resolved = path12.resolve(agentDir, filePath);
6417
+ if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
5834
6418
  throw new Error("Access denied");
5835
6419
  }
5836
6420
  const info = await stat2(resolved);
5837
6421
  if (info.isDirectory()) throw new Error("Cannot read a directory");
5838
- const ext = path11.extname(resolved).toLowerCase();
6422
+ const ext = path12.extname(resolved).toLowerCase();
5839
6423
  if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
5840
6424
  if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
5841
6425
  const content = await readFile(resolved, "utf-8");
@@ -5870,13 +6454,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5870
6454
  const agent = this.agents.get(agentId);
5871
6455
  const runtime = runtimeHint || agent?.config.runtime || "claude";
5872
6456
  const home = os5.homedir();
5873
- const workspaceDir = path11.join(this.dataDir, agentId);
6457
+ const workspaceDir = path12.join(this.dataDir, agentId);
5874
6458
  const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
5875
6459
  const globalResults = await Promise.all(
5876
- paths.global.map((p) => this.scanSkillsDir(path11.join(home, p)))
6460
+ paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
5877
6461
  );
5878
6462
  const workspaceResults = await Promise.all(
5879
- paths.workspace.map((p) => this.scanSkillsDir(path11.join(workspaceDir, p)))
6463
+ paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
5880
6464
  );
5881
6465
  const dedup = (skills) => {
5882
6466
  const seen = /* @__PURE__ */ new Set();
@@ -5905,7 +6489,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5905
6489
  const skills = [];
5906
6490
  for (const entry of entries) {
5907
6491
  if (entry.isDirectory() || entry.isSymbolicLink()) {
5908
- const skillMd = path11.join(dir, entry.name, "SKILL.md");
6492
+ const skillMd = path12.join(dir, entry.name, "SKILL.md");
5909
6493
  try {
5910
6494
  const content = await readFile(skillMd, "utf-8");
5911
6495
  const skill = this.parseSkillMd(entry.name, content);
@@ -5916,7 +6500,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5916
6500
  } else if (entry.name.endsWith(".md")) {
5917
6501
  const cmdName = entry.name.replace(/\.md$/, "");
5918
6502
  try {
5919
- const content = await readFile(path11.join(dir, entry.name), "utf-8");
6503
+ const content = await readFile(path12.join(dir, entry.name), "utf-8");
5920
6504
  const skill = this.parseSkillMd(cmdName, content);
5921
6505
  skill.sourcePath = dir;
5922
6506
  skills.push(skill);
@@ -5951,6 +6535,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5951
6535
  /**
5952
6536
  * Broadcast an activity change — emits a single agent:activity event that carries
5953
6537
  * both the status (for the dot indicator) and trajectory entries (for the activity log).
6538
+ *
6539
+ * TODO(lifecycle-v2/daemon-protocol): split this legacy frame into
6540
+ * structured lifecycle producers. Runtime progress, transient heartbeat,
6541
+ * provider/runtime error, process exit, and user-visible activity entries
6542
+ * should carry explicit reason/correlation/window attrs so the server no
6543
+ * longer infers lifecycle semantics from generic activity strings.
5954
6544
  */
5955
6545
  broadcastActivity(agentId, activity, detail, extraTrajectory = [], launchIdOverride) {
5956
6546
  const ap = this.agents.get(agentId);
@@ -6176,8 +6766,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6176
6766
  runtime_profile_turn_outcome: control.kind === "migration" ? control.migrationDoneToolObserved ? "migration_done_observed" : "missing_migration_done" : "notice_only"
6177
6767
  };
6178
6768
  }
6179
- startRuntimeTrace(agentId, ap, reason, messages) {
6769
+ startRuntimeTrace(agentId, ap, reason, messages, inputTraceAttrs = {}) {
6180
6770
  if (ap.runtimeTraceSpan) return ap.runtimeTraceSpan;
6771
+ ap.runtimeTraceCounters = createRuntimeTraceCounters();
6181
6772
  const messageControl = this.runtimeProfileTurnControlFromMessages(messages);
6182
6773
  if (messageControl) {
6183
6774
  this.activateRuntimeProfileTurnControl(ap, messageControl);
@@ -6192,12 +6783,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6192
6783
  reason,
6193
6784
  hasSession: Boolean(ap.sessionId),
6194
6785
  ...this.messagesTraceAttrs(messages),
6786
+ ...inputTraceAttrs,
6195
6787
  ...this.runtimeProfileTurnControlTraceAttrs(ap.runtimeProfileTurnControl)
6196
6788
  }
6197
6789
  });
6198
6790
  span.addEvent("daemon.turn.started", {
6199
6791
  reason,
6200
6792
  ...this.messagesTraceAttrs(messages),
6793
+ ...inputTraceAttrs,
6201
6794
  ...this.runtimeProfileTurnControlTraceAttrs(ap.runtimeProfileTurnControl)
6202
6795
  });
6203
6796
  ap.runtimeTraceSpan = span;
@@ -6306,6 +6899,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6306
6899
  lastActivity: ap.lastActivity,
6307
6900
  lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
6308
6901
  lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
6902
+ ...runtimeTraceCounterAttrs(ap),
6309
6903
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_stalled")
6310
6904
  });
6311
6905
  this.broadcastActivity(agentId, "error", diagnostic.detail);
@@ -6338,6 +6932,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6338
6932
  lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
6339
6933
  pendingMessages: ap.inbox.length,
6340
6934
  recovery: "terminate_for_queued_message",
6935
+ ...runtimeTraceCounterAttrs(ap),
6341
6936
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_stalled")
6342
6937
  });
6343
6938
  ap.expectedTerminationReason = "stalled_recovery";
@@ -6365,6 +6960,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6365
6960
  const ap = this.agents.get(agentId);
6366
6961
  if (ap) {
6367
6962
  const wasStalled = Boolean(ap.runtimeProgressStaleSince);
6963
+ this.noteRuntimeTraceCounter(ap, event);
6368
6964
  this.recordRuntimeTraceEvent(agentId, ap, "runtime.event.received", { kind: event.kind });
6369
6965
  if (wasStalled) {
6370
6966
  this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.observed", { afterStall: true });
@@ -6492,6 +7088,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6492
7088
  }
6493
7089
  this.endRuntimeTrace(ap, "ok", {
6494
7090
  outcome: "turn-completed",
7091
+ ...runtimeTraceCounterAttrs(ap),
6495
7092
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "turn_end")
6496
7093
  });
6497
7094
  if (ap.driver.terminateProcessOnTurnEnd) {
@@ -6533,10 +7130,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6533
7130
  `[Agent ${agentId}] Disabled Claude tool-boundary gated steering after thinking-block mutation error; lastFlushReason=${ap.gatedSteering.lastFlushReason || "none"}`
6534
7131
  );
6535
7132
  }
6536
- this.recordRuntimeTraceEvent(agentId, ap, "runtime.error", runtimeErrorDiagnostics.eventAttrs);
7133
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime.error", {
7134
+ ...runtimeErrorDiagnostics.eventAttrs,
7135
+ ...runtimeTraceCounterAttrs(ap)
7136
+ });
6537
7137
  this.endRuntimeTrace(ap, "error", {
6538
7138
  outcome: "runtime-error",
6539
7139
  ...runtimeErrorDiagnostics.spanAttrs,
7140
+ ...runtimeTraceCounterAttrs(ap),
6540
7141
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_error")
6541
7142
  });
6542
7143
  if (ap.driver.supportsStdinNotification && classifyTerminalFailure(ap)) {
@@ -6559,6 +7160,29 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6559
7160
  sendAgentStatus(agentId, status, launchId) {
6560
7161
  this.sendToServer({ type: "agent:status", agentId, status, launchId: launchId || void 0 });
6561
7162
  }
7163
+ noteRuntimeTraceCounter(ap, event) {
7164
+ ap.runtimeTraceCounters.events++;
7165
+ switch (event.kind) {
7166
+ case "tool_call":
7167
+ ap.runtimeTraceCounters.toolCalls++;
7168
+ break;
7169
+ case "tool_output":
7170
+ ap.runtimeTraceCounters.toolOutputs++;
7171
+ break;
7172
+ case "compaction_started":
7173
+ ap.runtimeTraceCounters.compactionStarts++;
7174
+ break;
7175
+ case "compaction_finished":
7176
+ ap.runtimeTraceCounters.compactionFinishes++;
7177
+ break;
7178
+ case "text":
7179
+ ap.runtimeTraceCounters.textEvents++;
7180
+ break;
7181
+ case "thinking":
7182
+ ap.runtimeTraceCounters.thinkingEvents++;
7183
+ break;
7184
+ }
7185
+ }
6562
7186
  /** Send a batched notification to the agent via stdin about pending messages */
6563
7187
  sendStdinNotification(agentId) {
6564
7188
  const ap = this.agents.get(agentId);
@@ -6661,6 +7285,14 @@ ${messages.map((message) => formatIncomingMessage(message, ap.driver)).join("\n"
6661
7285
 
6662
7286
  Respond as appropriate. Complete all your work before stopping.
6663
7287
  ${RESPONSE_TARGET_HINT}`);
7288
+ const inputTraceAttrs = buildRuntimeInputTraceAttrs({
7289
+ source: `stdin_${mode}_delivery`,
7290
+ prompt,
7291
+ messages,
7292
+ sessionIdPresent: Boolean(ap.sessionId),
7293
+ nativeStandingPrompt: Boolean(ap.driver.supportsNativeStandingPrompt)
7294
+ });
7295
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime.input.prepared", inputTraceAttrs);
6664
7296
  const encoded = ap.driver.encodeStdinMessage(prompt, ap.sessionId, { mode });
6665
7297
  if (!encoded) {
6666
7298
  ap.inbox.unshift(...messages);
@@ -6672,6 +7304,7 @@ ${RESPONSE_TARGET_HINT}`);
6672
7304
  );
6673
7305
  this.recordDaemonTrace("daemon.agent.stdin_delivery", {
6674
7306
  ...traceAttrs,
7307
+ ...inputTraceAttrs,
6675
7308
  outcome: "encode_failed",
6676
7309
  requeued_messages_count: messages.length
6677
7310
  }, "error");
@@ -6688,6 +7321,7 @@ ${RESPONSE_TARGET_HINT}`);
6688
7321
  this.ackInjectedRuntimeProfileMessages(agentId, messages, ap.launchId);
6689
7322
  this.recordDaemonTrace("daemon.agent.stdin_delivery", {
6690
7323
  ...traceAttrs,
7324
+ ...inputTraceAttrs,
6691
7325
  outcome: "written",
6692
7326
  stdin_write_attempted: true
6693
7327
  });
@@ -6709,8 +7343,8 @@ ${RESPONSE_TARGET_HINT}`);
6709
7343
  const nodes = [];
6710
7344
  for (const entry of entries) {
6711
7345
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
6712
- const fullPath = path11.join(dir, entry.name);
6713
- const relativePath = path11.relative(rootDir, fullPath);
7346
+ const fullPath = path12.join(dir, entry.name);
7347
+ const relativePath = path12.relative(rootDir, fullPath);
6714
7348
  let info;
6715
7349
  try {
6716
7350
  info = await stat2(fullPath);
@@ -7013,9 +7647,9 @@ var ReminderCache = class {
7013
7647
 
7014
7648
  // src/machineLock.ts
7015
7649
  import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
7016
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
7650
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
7017
7651
  import os6 from "os";
7018
- import path12 from "path";
7652
+ import path13 from "path";
7019
7653
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
7020
7654
  var DaemonMachineLockConflictError = class extends Error {
7021
7655
  code = "DAEMON_MACHINE_LOCK_HELD";
@@ -7037,7 +7671,7 @@ function resolveDefaultMachineStateRoot() {
7037
7671
  return resolveSlockHomePath("machines");
7038
7672
  }
7039
7673
  function ownerPath(lockDir) {
7040
- return path12.join(lockDir, "owner.json");
7674
+ return path13.join(lockDir, "owner.json");
7041
7675
  }
7042
7676
  function readOwner(lockDir) {
7043
7677
  try {
@@ -7067,13 +7701,13 @@ function acquireDaemonMachineLock(options) {
7067
7701
  const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
7068
7702
  const fingerprint = apiKeyFingerprint(options.apiKey);
7069
7703
  const lockId = getDaemonMachineLockId(options.apiKey);
7070
- const machineDir = path12.join(rootDir, lockId);
7071
- const lockDir = path12.join(machineDir, "daemon.lock");
7704
+ const machineDir = path13.join(rootDir, lockId);
7705
+ const lockDir = path13.join(machineDir, "daemon.lock");
7072
7706
  const token = randomUUID2();
7073
- mkdirSync5(machineDir, { recursive: true });
7707
+ mkdirSync6(machineDir, { recursive: true });
7074
7708
  for (let attempt = 0; attempt < 2; attempt += 1) {
7075
7709
  try {
7076
- mkdirSync5(lockDir);
7710
+ mkdirSync6(lockDir);
7077
7711
  const owner = {
7078
7712
  pid: process.pid,
7079
7713
  token,
@@ -7083,7 +7717,7 @@ function acquireDaemonMachineLock(options) {
7083
7717
  apiKeyFingerprint: fingerprint.slice(0, 16)
7084
7718
  };
7085
7719
  try {
7086
- writeFileSync8(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
7720
+ writeFileSync9(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
7087
7721
  `, { mode: 384 });
7088
7722
  } catch (err) {
7089
7723
  rmSync2(lockDir, { recursive: true, force: true });
@@ -7120,8 +7754,8 @@ function acquireDaemonMachineLock(options) {
7120
7754
  }
7121
7755
 
7122
7756
  // src/localTraceSink.ts
7123
- import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
7124
- import path13 from "path";
7757
+ import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync4, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync10 } from "fs";
7758
+ import path14 from "path";
7125
7759
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
7126
7760
  var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
7127
7761
  var DEFAULT_MAX_FILES = 8;
@@ -7157,7 +7791,7 @@ var LocalRotatingTraceSink = class {
7157
7791
  currentSize = 0;
7158
7792
  sequence = 0;
7159
7793
  constructor(options) {
7160
- this.traceDir = path13.join(options.machineDir, "traces");
7794
+ this.traceDir = path14.join(options.machineDir, "traces");
7161
7795
  this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
7162
7796
  const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
7163
7797
  const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
@@ -7183,26 +7817,26 @@ var LocalRotatingTraceSink = class {
7183
7817
  return this.currentFile;
7184
7818
  }
7185
7819
  ensureFile(nextBytes) {
7186
- mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
7820
+ mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
7187
7821
  const nowMs = this.nowMsProvider();
7188
7822
  const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
7189
7823
  if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
7190
- this.currentFile = path13.join(
7824
+ this.currentFile = path14.join(
7191
7825
  this.traceDir,
7192
7826
  `daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
7193
7827
  );
7194
- writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
7828
+ writeFileSync10(this.currentFile, "", { flag: "a", mode: 384 });
7195
7829
  this.currentSize = statSync4(this.currentFile).size;
7196
7830
  this.currentFileOpenedAtMs = nowMs;
7197
7831
  this.pruneOldFiles();
7198
7832
  }
7199
7833
  }
7200
7834
  pruneOldFiles() {
7201
- const files = readdirSync3(this.traceDir).filter((name) => name.startsWith("daemon-trace-") && name.endsWith(".jsonl")).sort();
7835
+ const files = readdirSync4(this.traceDir).filter((name) => name.startsWith("daemon-trace-") && name.endsWith(".jsonl")).sort();
7202
7836
  const excess = files.length - this.maxFiles;
7203
7837
  if (excess <= 0) return;
7204
7838
  for (const file of files.slice(0, excess)) {
7205
- rmSync3(path13.join(this.traceDir, file), { force: true });
7839
+ rmSync3(path14.join(this.traceDir, file), { force: true });
7206
7840
  }
7207
7841
  }
7208
7842
  };
@@ -7289,11 +7923,11 @@ function isDiagnosticErrorAttr(key) {
7289
7923
  import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
7290
7924
  import { gzipSync } from "zlib";
7291
7925
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
7292
- import path14 from "path";
7926
+ import path15 from "path";
7293
7927
 
7294
7928
  // src/directUploadCapability.ts
7295
- function joinUrl(base, path16) {
7296
- return `${base.replace(/\/+$/, "")}${path16}`;
7929
+ function joinUrl(base, path17) {
7930
+ return `${base.replace(/\/+$/, "")}${path17}`;
7297
7931
  }
7298
7932
  function jsonHeaders(apiKey) {
7299
7933
  return {
@@ -7512,7 +8146,7 @@ var DaemonTraceBundleUploader = class {
7512
8146
  }, nextMs);
7513
8147
  }
7514
8148
  async findUploadCandidates() {
7515
- const traceDir = path14.join(this.options.machineDir, "traces");
8149
+ const traceDir = path15.join(this.options.machineDir, "traces");
7516
8150
  let names;
7517
8151
  try {
7518
8152
  names = await readdir3(traceDir);
@@ -7524,8 +8158,8 @@ var DaemonTraceBundleUploader = class {
7524
8158
  const currentFile = this.options.currentFileProvider?.();
7525
8159
  const candidates = [];
7526
8160
  for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
7527
- const file = path14.join(traceDir, name);
7528
- if (currentFile && path14.resolve(file) === path14.resolve(currentFile)) continue;
8161
+ const file = path15.join(traceDir, name);
8162
+ if (currentFile && path15.resolve(file) === path15.resolve(currentFile)) continue;
7529
8163
  if (await this.isUploaded(file)) continue;
7530
8164
  try {
7531
8165
  const info = await stat3(file);
@@ -7599,8 +8233,8 @@ var DaemonTraceBundleUploader = class {
7599
8233
  }
7600
8234
  }
7601
8235
  uploadStatePath(file) {
7602
- const stateDir = path14.join(this.options.machineDir, "trace-uploads");
7603
- return path14.join(stateDir, `${path14.basename(file)}.uploaded.json`);
8236
+ const stateDir = path15.join(this.options.machineDir, "trace-uploads");
8237
+ return path15.join(stateDir, `${path15.basename(file)}.uploaded.json`);
7604
8238
  }
7605
8239
  async isUploaded(file) {
7606
8240
  try {
@@ -7612,9 +8246,9 @@ var DaemonTraceBundleUploader = class {
7612
8246
  }
7613
8247
  async markUploaded(file, metadata) {
7614
8248
  const stateFile = this.uploadStatePath(file);
7615
- await mkdir2(path14.dirname(stateFile), { recursive: true, mode: 448 });
8249
+ await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
7616
8250
  await writeFile2(stateFile, `${JSON.stringify({
7617
- file: path14.basename(file),
8251
+ file: path15.basename(file),
7618
8252
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
7619
8253
  ...metadata
7620
8254
  }, null, 2)}
@@ -7633,10 +8267,10 @@ function readPositiveIntegerEnv2(name, fallback) {
7633
8267
 
7634
8268
  // src/core.ts
7635
8269
  var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
7636
- var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
7637
- function parseDaemonCliArgs(args) {
8270
+ var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
8271
+ function parseDaemonCliArgs(args, env = {}) {
7638
8272
  let serverUrl = "";
7639
- let apiKey = "";
8273
+ let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
7640
8274
  for (let i = 0; i < args.length; i++) {
7641
8275
  if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
7642
8276
  if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
@@ -7653,23 +8287,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
7653
8287
  }
7654
8288
  }
7655
8289
  function resolveChatBridgePath(moduleUrl = import.meta.url) {
7656
- const dirname = path15.dirname(fileURLToPath(moduleUrl));
7657
- const jsPath = path15.resolve(dirname, "chat-bridge.js");
8290
+ const dirname = path16.dirname(fileURLToPath2(moduleUrl));
8291
+ const jsPath = path16.resolve(dirname, "chat-bridge.js");
7658
8292
  try {
7659
8293
  accessSync(jsPath);
7660
8294
  return jsPath;
7661
8295
  } catch {
7662
- return path15.resolve(dirname, "chat-bridge.ts");
8296
+ return path16.resolve(dirname, "chat-bridge.ts");
7663
8297
  }
7664
8298
  }
7665
8299
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
7666
- const thisDir = path15.dirname(fileURLToPath(moduleUrl));
7667
- const bundledDistPath = path15.resolve(thisDir, "cli", "index.js");
8300
+ const thisDir = path16.dirname(fileURLToPath2(moduleUrl));
8301
+ const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
7668
8302
  try {
7669
8303
  accessSync(bundledDistPath);
7670
8304
  return bundledDistPath;
7671
8305
  } catch {
7672
- const workspaceDistPath = path15.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
8306
+ const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
7673
8307
  accessSync(workspaceDistPath);
7674
8308
  return workspaceDistPath;
7675
8309
  }
@@ -7848,7 +8482,7 @@ var DaemonCore = class {
7848
8482
  }
7849
8483
  resolveMachineStateRoot() {
7850
8484
  if (this.options.machineStateDir) return this.options.machineStateDir;
7851
- if (this.options.dataDir) return path15.join(path15.dirname(this.options.dataDir), "machines");
8485
+ if (this.options.dataDir) return path16.join(path16.dirname(this.options.dataDir), "machines");
7852
8486
  return resolveDefaultMachineStateRoot();
7853
8487
  }
7854
8488
  shouldEnableLocalTrace() {
@@ -8231,6 +8865,8 @@ var DaemonCore = class {
8231
8865
  };
8232
8866
 
8233
8867
  export {
8868
+ DAEMON_API_KEY_ENV,
8869
+ scrubDaemonAuthEnv,
8234
8870
  resolveWorkspaceDirectoryPath,
8235
8871
  scanWorkspaceDirectories,
8236
8872
  deleteWorkspaceDirectory,