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

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,25 @@ 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 integration list\`** \u2014 List registered third-party services and this agent's active Slock Agent Logins.
877
+ 20. **\`slock integration login\`** \u2014 Provision or reuse this agent's login for a registered third-party service.
878
+ 21. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
879
+ 22. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
880
+ 23. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
881
+ 24. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
882
+ 25. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
883
+ 26. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
884
+ 27. **\`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
885
 
866
886
  The CLI prints human-readable canonical text on success (matching the format you see in received messages and history). On failure it prints JSON to stderr:
867
887
  - failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
@@ -957,6 +977,11 @@ Each channel has a **name** and optionally a **description** that define its pur
957
977
  - **Reply in context** \u2014 always respond in the channel/thread the message came from.
958
978
  - **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.
959
979
  - If unsure where something belongs, call ${serverInfoCmd} to review channel descriptions.`;
980
+ const thirdPartyIntegrationsSection = isCli ? `### Third-party integrations
981
+
982
+ 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
983
+
984
+ 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.`;
960
985
  const readingHistorySection = isCli ? `### Reading history
961
986
 
962
987
  \`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
@@ -1128,6 +1153,8 @@ ${discoverySection}
1128
1153
 
1129
1154
  ${channelAwarenessSection}
1130
1155
 
1156
+ ${thirdPartyIntegrationsSection}
1157
+
1131
1158
  ${readingHistorySection}
1132
1159
 
1133
1160
  ${historicalReferenceSection}
@@ -1159,6 +1186,17 @@ Keep the user informed. They cannot see your internal reasoning, so:
1159
1186
  - For multi-step work, send short progress updates (e.g. "Working on step 2/3\u2026").
1160
1187
  - When done, summarize the result.
1161
1188
  - Keep updates concise \u2014 one or two sentences. Don't flood the chat.
1189
+ - 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:
1190
+
1191
+ \`\`\`html
1192
+ <details>
1193
+ <summary>Evidence, logs, or edge cases</summary>
1194
+
1195
+ Detailed notes go here.
1196
+ </details>
1197
+ \`\`\`
1198
+
1199
+ Do not hide the main recommendation, blocker, or required action inside \`<details>\`; only fold supporting evidence, logs, alternatives, or extended rationale.
1162
1200
 
1163
1201
  ### Conversation etiquette
1164
1202
 
@@ -1292,6 +1330,19 @@ function buildMcpSystemPrompt(config, opts) {
1292
1330
  return buildPrompt(config, "mcp", opts);
1293
1331
  }
1294
1332
 
1333
+ // src/authEnv.ts
1334
+ var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
1335
+ var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
1336
+ function scrubDaemonAuthEnv(env) {
1337
+ delete env[DAEMON_API_KEY_ENV];
1338
+ return env;
1339
+ }
1340
+ function scrubDaemonChildEnv(env) {
1341
+ delete env[DAEMON_API_KEY_ENV];
1342
+ delete env[SLOCK_AGENT_TOKEN_ENV];
1343
+ return env;
1344
+ }
1345
+
1295
1346
  // src/drivers/cliTransport.ts
1296
1347
  var shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
1297
1348
  function runtimeContextEnv(config) {
@@ -1345,7 +1396,7 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)}
1345
1396
  SLOCK_AGENT_TOKEN_FILE: tokenFile,
1346
1397
  PATH: `${slockDir}${path.delimiter}${process.env.PATH ?? ""}`
1347
1398
  };
1348
- delete spawnEnv.SLOCK_AGENT_TOKEN;
1399
+ scrubDaemonChildEnv(spawnEnv);
1349
1400
  return {
1350
1401
  slockDir,
1351
1402
  tokenFile,
@@ -1382,7 +1433,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn) {
1382
1433
  }
1383
1434
  function resolveCommandOnPath(command, deps = {}) {
1384
1435
  const platform = deps.platform ?? process.platform;
1385
- const env = deps.env ?? process.env;
1436
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
1386
1437
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
1387
1438
  if (platform === "win32") {
1388
1439
  return resolveCommandOnWindows(command, env, execFileSyncFn);
@@ -1407,7 +1458,7 @@ function firstExistingPath(candidates, deps = {}) {
1407
1458
  return null;
1408
1459
  }
1409
1460
  function readCommandVersion(command, args = [], deps = {}) {
1410
- const env = deps.env ?? process.env;
1461
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
1411
1462
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
1412
1463
  try {
1413
1464
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -1772,7 +1823,7 @@ var ClaudeDriver = class {
1772
1823
 
1773
1824
  // src/drivers/codex.ts
1774
1825
  import { spawn as spawn2, execSync } from "child_process";
1775
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
1826
+ import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
1776
1827
  import os2 from "os";
1777
1828
  import path4 from "path";
1778
1829
  function getCodexNotificationErrorMessage(params) {
@@ -1801,11 +1852,29 @@ function ensureGitRepoForCodex(workingDirectory, deps = {}) {
1801
1852
  );
1802
1853
  }
1803
1854
  var CODEX_DESKTOP_BUNDLE_PATH = "/Applications/Codex.app/Contents/Resources/codex";
1855
+ function resolveWindowsSandboxRunner(deps = {}) {
1856
+ const homeDir = deps.homeDir ?? os2.homedir();
1857
+ const sandboxBinDir = path4.join(homeDir, ".codex", ".sandbox-bin");
1858
+ if (!(deps.existsSyncFn ?? existsSync3)(sandboxBinDir)) return null;
1859
+ try {
1860
+ const files = readdirSync2(sandboxBinDir);
1861
+ const runners = files.filter((f) => f.startsWith("codex-command-runner") && f.endsWith(".exe")).sort((a, b) => b.localeCompare(a));
1862
+ if (runners.length > 0) return path4.join(sandboxBinDir, runners[0]);
1863
+ } catch {
1864
+ }
1865
+ return null;
1866
+ }
1804
1867
  function resolveCodexCommand(deps = {}) {
1805
1868
  const pathCommand = resolveCommandOnPath("codex", deps);
1806
1869
  if (pathCommand) return pathCommand;
1807
- if ((deps.platform ?? process.platform) !== "darwin") return null;
1808
- return firstExistingPath([CODEX_DESKTOP_BUNDLE_PATH], deps);
1870
+ const platform = deps.platform ?? process.platform;
1871
+ if (platform === "darwin") {
1872
+ return firstExistingPath([CODEX_DESKTOP_BUNDLE_PATH], deps);
1873
+ }
1874
+ if (platform === "win32") {
1875
+ return resolveWindowsSandboxRunner(deps);
1876
+ }
1877
+ return null;
1809
1878
  }
1810
1879
  function probeCodex(deps = {}) {
1811
1880
  if ((deps.platform ?? process.platform) === "win32") {
@@ -1830,22 +1899,32 @@ function resolveCodexSpawn(commandArgs, deps = {}) {
1830
1899
  if ((deps.platform ?? process.platform) !== "win32") {
1831
1900
  return { command: resolveCodexCommand(deps) ?? "codex", args: commandArgs };
1832
1901
  }
1902
+ const execSyncFn = deps.execSyncFn ?? execSync;
1903
+ const existsSyncFn = deps.existsSyncFn ?? existsSync3;
1904
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
1833
1905
  let codexEntry = null;
1834
1906
  try {
1835
- const globalRoot = execSync("npm root -g", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
1907
+ const globalRoot = execSyncFn("npm root -g", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], env }).trim();
1836
1908
  const candidate = path4.join(globalRoot, "@openai", "codex", "bin", "codex.js");
1837
- if (existsSync3(candidate)) codexEntry = candidate;
1909
+ if (existsSyncFn(candidate)) codexEntry = candidate;
1838
1910
  } catch {
1839
1911
  }
1840
1912
  if (!codexEntry) {
1841
1913
  try {
1842
- const cmdPath = execSync("where codex", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim().split(/\r?\n/)[0];
1914
+ const cmdPath = execSyncFn("where codex", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], env }).trim().split(/\r?\n/)[0];
1843
1915
  const candidate = path4.join(path4.dirname(cmdPath), "node_modules", "@openai", "codex", "bin", "codex.js");
1844
- if (existsSync3(candidate)) codexEntry = candidate;
1916
+ if (existsSyncFn(candidate)) codexEntry = candidate;
1845
1917
  } catch {
1846
1918
  }
1847
1919
  }
1848
1920
  if (!codexEntry) {
1921
+ const sandboxRunner = resolveWindowsSandboxRunner(deps);
1922
+ if (sandboxRunner) {
1923
+ return {
1924
+ command: sandboxRunner,
1925
+ args: commandArgs
1926
+ };
1927
+ }
1849
1928
  throw new Error(
1850
1929
  "Cannot resolve Codex CLI entry point on Windows. Ensure @openai/codex is installed globally via npm (npm i -g @openai/codex)."
1851
1930
  );
@@ -2296,13 +2375,13 @@ import { spawn as spawn3 } from "child_process";
2296
2375
  import path5 from "path";
2297
2376
  import { writeFileSync as writeFileSync3 } from "fs";
2298
2377
  function buildCopilotSpawnEnv(ctx) {
2299
- return {
2378
+ return scrubDaemonChildEnv({
2300
2379
  ...process.env,
2301
2380
  FORCE_COLOR: "0",
2302
2381
  NO_COLOR: "1",
2303
2382
  ...ctx.config.envVars || {},
2304
2383
  [SLOCK_HOME_ENV]: resolveSlockHome()
2305
- };
2384
+ });
2306
2385
  }
2307
2386
  var CopilotDriver = class {
2308
2387
  id = "copilot";
@@ -2460,13 +2539,13 @@ import { spawn as spawn4, spawnSync } from "child_process";
2460
2539
  import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
2461
2540
  import path6 from "path";
2462
2541
  function buildCursorSpawnEnv(ctx) {
2463
- return {
2542
+ return scrubDaemonChildEnv({
2464
2543
  ...process.env,
2465
2544
  FORCE_COLOR: "0",
2466
2545
  NO_COLOR: "1",
2467
2546
  ...ctx.config.envVars || {},
2468
2547
  [SLOCK_HOME_ENV]: resolveSlockHome()
2469
- };
2548
+ });
2470
2549
  }
2471
2550
  var CursorDriver = class {
2472
2551
  id = "cursor";
@@ -2636,7 +2715,7 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
2636
2715
  }
2637
2716
  function runCursorModelsCommand() {
2638
2717
  return spawnSync("cursor-agent", ["models"], {
2639
- env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
2718
+ env: scrubDaemonChildEnv({ ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" }),
2640
2719
  encoding: "utf8",
2641
2720
  timeout: 5e3
2642
2721
  });
@@ -2683,7 +2762,7 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
2683
2762
  }
2684
2763
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync2;
2685
2764
  const existsSyncFn = deps.existsSyncFn ?? existsSync5;
2686
- const env = deps.env ?? process.env;
2765
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
2687
2766
  const winPath = path7.win32;
2688
2767
  let geminiEntry = null;
2689
2768
  try {
@@ -2857,13 +2936,16 @@ var GeminiDriver = class {
2857
2936
  // src/drivers/kimi.ts
2858
2937
  import { randomUUID } from "crypto";
2859
2938
  import { spawn as spawn6 } from "child_process";
2860
- import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
2939
+ import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
2861
2940
  import os3 from "os";
2862
2941
  import path8 from "path";
2863
2942
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
2864
2943
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
2865
2944
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
2866
2945
  var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
2946
+ var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
2947
+ var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
2948
+ var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
2867
2949
  function parseToolArguments(raw) {
2868
2950
  if (typeof raw !== "string") return raw;
2869
2951
  try {
@@ -2872,6 +2954,73 @@ function parseToolArguments(raw) {
2872
2954
  return raw;
2873
2955
  }
2874
2956
  }
2957
+ function readKimiConfigSource(home = os3.homedir(), env = process.env) {
2958
+ const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
2959
+ if (inlineConfig && inlineConfig.trim()) {
2960
+ return {
2961
+ raw: inlineConfig,
2962
+ explicitPath: null,
2963
+ sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
2964
+ };
2965
+ }
2966
+ const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
2967
+ const configPath = explicitPath && explicitPath.trim() ? explicitPath : path8.join(home, ".kimi", "config.toml");
2968
+ try {
2969
+ return {
2970
+ raw: readFileSync3(configPath, "utf8"),
2971
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
2972
+ sourcePath: configPath
2973
+ };
2974
+ } catch {
2975
+ return {
2976
+ raw: null,
2977
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
2978
+ sourcePath: configPath
2979
+ };
2980
+ }
2981
+ }
2982
+ function buildKimiSpawnEnv(env = process.env) {
2983
+ const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
2984
+ delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
2985
+ delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
2986
+ return scrubDaemonChildEnv(spawnEnv);
2987
+ }
2988
+ function buildKimiEffectiveEnv(ctx, overrideEnv) {
2989
+ return {
2990
+ ...process.env,
2991
+ ...ctx.config.envVars || {},
2992
+ ...overrideEnv || {}
2993
+ };
2994
+ }
2995
+ function buildKimiLaunchOptions(ctx, opts = {}) {
2996
+ const env = buildKimiEffectiveEnv(ctx, opts.env);
2997
+ const source = readKimiConfigSource(opts.home ?? os3.homedir(), env);
2998
+ const args = [];
2999
+ let configFilePath = null;
3000
+ let configContent = null;
3001
+ if (source.explicitPath) {
3002
+ configFilePath = source.explicitPath;
3003
+ } else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
3004
+ configFilePath = path8.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
3005
+ configContent = source.raw;
3006
+ if (opts.writeGeneratedConfig !== false) {
3007
+ writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
3008
+ chmodSync(configFilePath, 384);
3009
+ }
3010
+ }
3011
+ if (configFilePath) {
3012
+ args.push("--config-file", configFilePath);
3013
+ }
3014
+ if (ctx.config.model && ctx.config.model !== "default") {
3015
+ args.push("--model", ctx.config.model);
3016
+ }
3017
+ return {
3018
+ args,
3019
+ env: buildKimiSpawnEnv(env),
3020
+ configFilePath,
3021
+ configContent
3022
+ };
3023
+ }
2875
3024
  var KimiDriver = class {
2876
3025
  id = "kimi";
2877
3026
  lifecycle = {
@@ -2888,7 +3037,25 @@ var KimiDriver = class {
2888
3037
  };
2889
3038
  model = {
2890
3039
  detectedModelsVerifiedAs: "launchable",
2891
- toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
3040
+ toLaunchSpec: (modelId, ctx, opts) => {
3041
+ if (!ctx) return { args: ["--model", modelId] };
3042
+ const launchCtx = {
3043
+ ...ctx,
3044
+ config: {
3045
+ ...ctx.config,
3046
+ model: modelId
3047
+ }
3048
+ };
3049
+ const launch = buildKimiLaunchOptions(launchCtx, {
3050
+ home: opts?.home,
3051
+ writeGeneratedConfig: false
3052
+ });
3053
+ return {
3054
+ args: launch.args,
3055
+ env: launch.env,
3056
+ configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
3057
+ };
3058
+ }
2892
3059
  };
2893
3060
  supportsStdinNotification = true;
2894
3061
  mcpToolPrefix = "";
@@ -2940,6 +3107,7 @@ var KimiDriver = class {
2940
3107
  }
2941
3108
  }
2942
3109
  }), "utf8");
3110
+ const launch = buildKimiLaunchOptions(ctx);
2943
3111
  const args = [
2944
3112
  "--wire",
2945
3113
  "--yolo",
@@ -2948,16 +3116,13 @@ var KimiDriver = class {
2948
3116
  "--mcp-config-file",
2949
3117
  mcpConfigPath,
2950
3118
  "--session",
2951
- this.sessionId
3119
+ this.sessionId,
3120
+ ...launch.args
2952
3121
  ];
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
3122
  const proc = spawn6("kimi", args, {
2958
3123
  cwd: ctx.workingDirectory,
2959
3124
  stdio: ["pipe", "pipe", "pipe"],
2960
- env: spawnEnv,
3125
+ env: launch.env,
2961
3126
  shell: process.platform === "win32"
2962
3127
  });
2963
3128
  proc.stdin?.write(JSON.stringify({
@@ -3074,14 +3239,9 @@ var KimiDriver = class {
3074
3239
  return detectKimiModels();
3075
3240
  }
3076
3241
  };
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
- }
3242
+ function detectKimiModels(home = os3.homedir(), opts = {}) {
3243
+ const raw = readKimiConfigSource(home, opts.env).raw;
3244
+ if (raw === null) return null;
3085
3245
  const models = [];
3086
3246
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
3087
3247
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -3328,7 +3488,7 @@ function detectOpenCodeModels(home = os4.homedir(), runCommand = runOpenCodeMode
3328
3488
  }
3329
3489
  function runOpenCodeModelsCommand(home) {
3330
3490
  const result = spawnSync2("opencode", ["models"], {
3331
- env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
3491
+ env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
3332
3492
  encoding: "utf8",
3333
3493
  timeout: 5e3
3334
3494
  });
@@ -3484,6 +3644,297 @@ var OpenCodeDriver = class {
3484
3644
  }
3485
3645
  };
3486
3646
 
3647
+ // src/drivers/pi.ts
3648
+ import { spawn as spawn8 } from "child_process";
3649
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
3650
+ import path10 from "path";
3651
+ import { fileURLToPath } from "url";
3652
+ import { getAgentDir, VERSION as PI_SDK_VERSION } from "@earendil-works/pi-coding-agent";
3653
+ var CHAT_MCP_TOOL_PREFIX2 = "chat_";
3654
+ var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
3655
+ var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
3656
+ var MIN_SUPPORTED_PI_VERSION = "0.74.0";
3657
+ function parseSemver2(version) {
3658
+ const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
3659
+ if (!match) return null;
3660
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
3661
+ }
3662
+ function isSupportedPiVersion(version) {
3663
+ if (!version) return true;
3664
+ const actual = parseSemver2(version);
3665
+ const minimum = parseSemver2(MIN_SUPPORTED_PI_VERSION);
3666
+ if (!actual || !minimum) return true;
3667
+ for (let i = 0; i < 3; i += 1) {
3668
+ if (actual[i] > minimum[i]) return true;
3669
+ if (actual[i] < minimum[i]) return false;
3670
+ }
3671
+ return true;
3672
+ }
3673
+ function unsupportedPiVersionMessage(version) {
3674
+ if (!version || isSupportedPiVersion(version)) return null;
3675
+ 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.`;
3676
+ }
3677
+ function probePi(version = PI_SDK_VERSION) {
3678
+ const unsupportedMessage = unsupportedPiVersionMessage(version);
3679
+ if (unsupportedMessage) {
3680
+ return {
3681
+ available: false,
3682
+ version: `${version} (requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION})`
3683
+ };
3684
+ }
3685
+ return { available: true, version };
3686
+ }
3687
+ function resolvePiSdkRunnerPath(moduleUrl = import.meta.url) {
3688
+ const moduleDir = path10.dirname(fileURLToPath(moduleUrl));
3689
+ const sourceSibling = path10.join(moduleDir, "piSdkRunner.ts");
3690
+ if (existsSync7(sourceSibling)) return sourceSibling;
3691
+ const bundledEntry = path10.join(moduleDir, "drivers", "piSdkRunner.js");
3692
+ if (existsSync7(bundledEntry)) return bundledEntry;
3693
+ return path10.join(moduleDir, "piSdkRunner.js");
3694
+ }
3695
+ function buildPiSdkNodeArgs(runnerPath = resolvePiSdkRunnerPath()) {
3696
+ if (runnerPath.endsWith(".ts")) {
3697
+ return [...process.execArgv, runnerPath];
3698
+ }
3699
+ return [runnerPath];
3700
+ }
3701
+ function buildPiLaunchOptions(ctx, opts = {}) {
3702
+ const command = opts.command ?? process.execPath;
3703
+ const piDir = path10.join(ctx.workingDirectory, ".slock", "pi");
3704
+ const sessionDir = path10.join(piDir, "sessions");
3705
+ mkdirSync4(sessionDir, { recursive: true });
3706
+ const slock = prepareCliTransport(ctx, { NO_COLOR: "1" });
3707
+ const runnerPath = opts.runnerPath ?? resolvePiSdkRunnerPath();
3708
+ const agentDir = opts.agentDir ?? getAgentDir();
3709
+ const runnerConfigPath = path10.join(
3710
+ piDir,
3711
+ `sdk-run-${(ctx.launchId || "launch").replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`
3712
+ );
3713
+ const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
3714
+ const runnerConfig = {
3715
+ cwd: ctx.workingDirectory,
3716
+ agentDir,
3717
+ sessionDir,
3718
+ sessionId: ctx.config.sessionId || null,
3719
+ standingPrompt: ctx.standingPrompt,
3720
+ prompt: turnPrompt,
3721
+ model: ctx.config.model && ctx.config.model !== "default" ? ctx.config.model : null
3722
+ };
3723
+ writeFileSync7(runnerConfigPath, `${JSON.stringify(runnerConfig)}
3724
+ `, { encoding: "utf8", mode: 384 });
3725
+ const args = [
3726
+ ...buildPiSdkNodeArgs(runnerPath),
3727
+ "--config",
3728
+ runnerConfigPath
3729
+ ];
3730
+ return {
3731
+ command,
3732
+ args,
3733
+ env: slock.spawnEnv,
3734
+ sessionDir,
3735
+ agentDir,
3736
+ runnerConfigPath,
3737
+ sdkVersion: PI_SDK_VERSION
3738
+ };
3739
+ }
3740
+ function isSystemFirstMessageTask2(message) {
3741
+ return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
3742
+ }
3743
+ function buildPiSystemPrompt(config) {
3744
+ return buildCliTransportSystemPrompt(config, {
3745
+ toolPrefix: CHAT_MCP_TOOL_PREFIX2,
3746
+ extraCriticalRules: [
3747
+ "- 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."
3748
+ ],
3749
+ postStartupNotes: [
3750
+ "**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."
3751
+ ],
3752
+ includeStdinNotificationSection: false,
3753
+ messageNotificationStyle: "poll"
3754
+ });
3755
+ }
3756
+ function contentText(content) {
3757
+ if (!content) return "";
3758
+ const chunks = [];
3759
+ for (const item of content) {
3760
+ if (item.type === "text" && typeof item.text === "string") {
3761
+ chunks.push(item.text);
3762
+ }
3763
+ }
3764
+ return chunks.join("");
3765
+ }
3766
+ function apiKeyErrorMessage(line) {
3767
+ const trimmed = line.trim();
3768
+ if (!trimmed) return null;
3769
+ if (/no api key found/i.test(trimmed)) return trimmed;
3770
+ if (/api key.+required/i.test(trimmed)) return trimmed;
3771
+ if (/no models available/i.test(trimmed)) return trimmed;
3772
+ return null;
3773
+ }
3774
+ var PiDriver = class {
3775
+ id = "pi";
3776
+ lifecycle = {
3777
+ kind: "per_turn",
3778
+ start: "defer_until_concrete_message",
3779
+ exit: "terminate_on_turn_end",
3780
+ inFlightWake: "coalesce_into_pending"
3781
+ };
3782
+ communication = {
3783
+ chat: "slock_cli",
3784
+ runtimeControl: "none"
3785
+ };
3786
+ session = {
3787
+ recovery: "resume_or_fresh"
3788
+ };
3789
+ model = {
3790
+ detectedModelsVerifiedAs: "launchable",
3791
+ toLaunchSpec: (modelId, ctx) => {
3792
+ if (!ctx) return modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] };
3793
+ const launchCtx = {
3794
+ ...ctx,
3795
+ config: {
3796
+ ...ctx.config,
3797
+ model: modelId
3798
+ }
3799
+ };
3800
+ const launch = buildPiLaunchOptions(launchCtx);
3801
+ return {
3802
+ args: launch.args,
3803
+ env: launch.env,
3804
+ configFiles: [launch.runnerConfigPath],
3805
+ params: {
3806
+ agentDir: launch.agentDir,
3807
+ sessionDir: launch.sessionDir,
3808
+ sdkVersion: launch.sdkVersion,
3809
+ resources: "extensions/skills/prompt-templates/themes/context-files disabled by Slock policy"
3810
+ }
3811
+ };
3812
+ }
3813
+ };
3814
+ supportsStdinNotification = false;
3815
+ mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
3816
+ busyDeliveryMode = "none";
3817
+ terminateProcessOnTurnEnd = true;
3818
+ deferSpawnUntilMessage = true;
3819
+ usesSlockCliForCommunication = true;
3820
+ sessionId = null;
3821
+ sessionAnnounced = false;
3822
+ apiKeyErrorAnnounced = false;
3823
+ turnEnded = false;
3824
+ assistantTextByMessageId = /* @__PURE__ */ new Map();
3825
+ shouldDeferWakeMessage(message) {
3826
+ return isSystemFirstMessageTask2(message);
3827
+ }
3828
+ probe() {
3829
+ return probePi();
3830
+ }
3831
+ async detectModels() {
3832
+ return null;
3833
+ }
3834
+ spawn(ctx) {
3835
+ this.sessionId = ctx.config.sessionId || null;
3836
+ this.sessionAnnounced = false;
3837
+ this.apiKeyErrorAnnounced = false;
3838
+ this.turnEnded = false;
3839
+ this.assistantTextByMessageId.clear();
3840
+ const unsupportedMessage = unsupportedPiVersionMessage(PI_SDK_VERSION);
3841
+ if (unsupportedMessage) throw new Error(unsupportedMessage);
3842
+ const launch = buildPiLaunchOptions(ctx);
3843
+ const proc = spawn8(launch.command, launch.args, {
3844
+ cwd: ctx.workingDirectory,
3845
+ stdio: ["pipe", "pipe", "pipe"],
3846
+ env: launch.env,
3847
+ shell: false
3848
+ });
3849
+ proc.stdin?.end();
3850
+ return { process: proc };
3851
+ }
3852
+ parseLine(line) {
3853
+ let event;
3854
+ try {
3855
+ event = JSON.parse(line);
3856
+ } catch {
3857
+ if (this.apiKeyErrorAnnounced) return [];
3858
+ const message = apiKeyErrorMessage(line);
3859
+ if (!message) return [];
3860
+ this.apiKeyErrorAnnounced = true;
3861
+ this.turnEnded = true;
3862
+ return [
3863
+ { kind: "error", message },
3864
+ { kind: "turn_end", sessionId: this.sessionId || void 0 }
3865
+ ];
3866
+ }
3867
+ const events = [];
3868
+ if (event.type === "session" && event.id) {
3869
+ this.sessionId = event.id;
3870
+ }
3871
+ if (!this.sessionAnnounced && this.sessionId) {
3872
+ events.push({ kind: "session_init", sessionId: this.sessionId });
3873
+ this.sessionAnnounced = true;
3874
+ }
3875
+ switch (event.type) {
3876
+ case "agent_start":
3877
+ case "turn_start":
3878
+ events.push({ kind: "thinking", text: "" });
3879
+ break;
3880
+ case "message_update":
3881
+ case "message_end":
3882
+ if (event.message?.role === "assistant") {
3883
+ const key = event.message.id || "current";
3884
+ const currentText = contentText(event.message.content);
3885
+ const previousText = this.assistantTextByMessageId.get(key) ?? "";
3886
+ if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
3887
+ events.push({ kind: "text", text: currentText.slice(previousText.length) });
3888
+ } else if (currentText && currentText !== previousText) {
3889
+ events.push({ kind: "text", text: currentText });
3890
+ }
3891
+ this.assistantTextByMessageId.set(key, currentText);
3892
+ if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
3893
+ events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
3894
+ }
3895
+ }
3896
+ break;
3897
+ case "tool_execution_start":
3898
+ events.push({
3899
+ kind: "tool_call",
3900
+ name: event.toolName || "unknown_tool",
3901
+ input: event.args
3902
+ });
3903
+ break;
3904
+ case "tool_execution_end":
3905
+ events.push({
3906
+ kind: "tool_output",
3907
+ name: event.toolName || "unknown_tool"
3908
+ });
3909
+ if (event.isError) {
3910
+ events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
3911
+ }
3912
+ break;
3913
+ case "compaction_start":
3914
+ events.push({ kind: "compaction_started" });
3915
+ break;
3916
+ case "compaction_end":
3917
+ events.push({ kind: "compaction_finished" });
3918
+ if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
3919
+ break;
3920
+ case "turn_end":
3921
+ case "agent_end":
3922
+ if (!this.turnEnded) {
3923
+ events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
3924
+ this.turnEnded = true;
3925
+ }
3926
+ break;
3927
+ }
3928
+ return events;
3929
+ }
3930
+ encodeStdinMessage(_text, _sessionId, _opts) {
3931
+ return null;
3932
+ }
3933
+ buildSystemPrompt(config, _agentId) {
3934
+ return buildPiSystemPrompt(config);
3935
+ }
3936
+ };
3937
+
3487
3938
  // src/drivers/index.ts
3488
3939
  var driverFactories = {
3489
3940
  claude: () => new ClaudeDriver(),
@@ -3492,7 +3943,8 @@ var driverFactories = {
3492
3943
  cursor: () => new CursorDriver(),
3493
3944
  gemini: () => new GeminiDriver(),
3494
3945
  kimi: () => new KimiDriver(),
3495
- opencode: () => new OpenCodeDriver()
3946
+ opencode: () => new OpenCodeDriver(),
3947
+ pi: () => new PiDriver()
3496
3948
  };
3497
3949
  function getDriver(runtimeId) {
3498
3950
  const createDriver = driverFactories[runtimeId];
@@ -3505,7 +3957,7 @@ function getDriver(runtimeId) {
3505
3957
 
3506
3958
  // src/workspaces.ts
3507
3959
  import { readdir, rm, stat } from "fs/promises";
3508
- import path10 from "path";
3960
+ import path11 from "path";
3509
3961
  function isValidWorkspaceDirectoryName(directoryName) {
3510
3962
  return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
3511
3963
  }
@@ -3513,7 +3965,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
3513
3965
  if (!isValidWorkspaceDirectoryName(directoryName)) {
3514
3966
  return null;
3515
3967
  }
3516
- return path10.join(dataDir, directoryName);
3968
+ return path11.join(dataDir, directoryName);
3517
3969
  }
3518
3970
  function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
3519
3971
  return {
@@ -3562,7 +4014,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
3562
4014
  return summary;
3563
4015
  }
3564
4016
  const childSummaries = await Promise.all(
3565
- entries.map((entry) => summarizeWorkspaceEntry(path10.join(dirPath, entry.name), entry))
4017
+ entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
3566
4018
  );
3567
4019
  for (const childSummary of childSummaries) {
3568
4020
  summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
@@ -3581,7 +4033,7 @@ async function scanWorkspaceDirectories(dataDir) {
3581
4033
  if (!entry.isDirectory()) {
3582
4034
  return null;
3583
4035
  }
3584
- const dirPath = path10.join(dataDir, entry.name);
4036
+ const dirPath = path11.join(dataDir, entry.name);
3585
4037
  try {
3586
4038
  const summary = await summarizeWorkspaceDirectory(dirPath);
3587
4039
  return {
@@ -3772,19 +4224,19 @@ function findSessionJsonl(root, predicate) {
3772
4224
  if (depth < 0 || visited >= maxEntries) return null;
3773
4225
  let entries;
3774
4226
  try {
3775
- entries = readdirSync2(dir, { withFileTypes: true }).sort((a, b) => b.name.localeCompare(a.name));
4227
+ entries = readdirSync3(dir, { withFileTypes: true }).sort((a, b) => b.name.localeCompare(a.name));
3776
4228
  } catch {
3777
4229
  return null;
3778
4230
  }
3779
4231
  for (const entry of entries) {
3780
4232
  if (++visited > maxEntries) return null;
3781
4233
  if (!entry.isFile() || !predicate(entry.name)) continue;
3782
- return path11.join(dir, entry.name);
4234
+ return path12.join(dir, entry.name);
3783
4235
  }
3784
4236
  for (const entry of entries) {
3785
4237
  if (++visited > maxEntries) return null;
3786
4238
  if (!entry.isDirectory()) continue;
3787
- const found = visit(path11.join(dir, entry.name), depth - 1);
4239
+ const found = visit(path12.join(dir, entry.name), depth - 1);
3788
4240
  if (found) return found;
3789
4241
  }
3790
4242
  return null;
@@ -3797,10 +4249,10 @@ function safeSessionFilename(value) {
3797
4249
  }
3798
4250
  function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
3799
4251
  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({
4252
+ const dir = path12.join(fallbackDir, ".slock", "runtime-sessions");
4253
+ mkdirSync5(dir, { recursive: true });
4254
+ const filePath = path12.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
4255
+ writeFileSync8(filePath, JSON.stringify({
3804
4256
  type: "runtime_session_handoff",
3805
4257
  runtime,
3806
4258
  sessionId,
@@ -3819,7 +4271,7 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
3819
4271
  }
3820
4272
  }
3821
4273
  function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), fallbackDir) {
3822
- const directPath = path11.isAbsolute(sessionId) ? sessionId : null;
4274
+ const directPath = path12.isAbsolute(sessionId) ? sessionId : null;
3823
4275
  if (directPath) {
3824
4276
  try {
3825
4277
  if (statSync2(directPath).isFile()) {
@@ -3828,7 +4280,7 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), f
3828
4280
  } catch {
3829
4281
  }
3830
4282
  }
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;
4283
+ 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
4284
  if (!resolvedPath && fallbackDir) {
3833
4285
  const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
3834
4286
  if (fallback) return fallback;
@@ -4368,6 +4820,17 @@ Success = user starts useful collaboration and setup progresses,
4368
4820
  not finishing a long onboarding conversation in one channel.
4369
4821
  `;
4370
4822
  }
4823
+ function createRuntimeTraceCounters() {
4824
+ return {
4825
+ events: 0,
4826
+ toolCalls: 0,
4827
+ toolOutputs: 0,
4828
+ compactionStarts: 0,
4829
+ compactionFinishes: 0,
4830
+ textEvents: 0,
4831
+ thinkingEvents: 0
4832
+ };
4833
+ }
4371
4834
  function createGatedSteeringState() {
4372
4835
  return {
4373
4836
  phase: "idle",
@@ -4546,10 +5009,113 @@ function buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes) {
4546
5009
  outstandingToolUses: ap.gatedSteering.outstandingToolUses,
4547
5010
  compacting: ap.gatedSteering.compacting,
4548
5011
  recentStderrCount: ap.recentStderr.length,
4549
- recentStdoutCount: ap.recentStdout.length
5012
+ recentStdoutCount: ap.recentStdout.length,
5013
+ ...runtimeTraceCounterAttrs(ap)
4550
5014
  }
4551
5015
  };
4552
5016
  }
5017
+ function bucketBytes(value) {
5018
+ const bytes = typeof value === "string" ? Buffer.byteLength(value, "utf8") : Math.max(0, Math.floor(value ?? 0));
5019
+ if (bytes === 0) return "0";
5020
+ if (bytes < 1024) return "<1KB";
5021
+ if (bytes < 10 * 1024) return "1KB-10KB";
5022
+ if (bytes < 100 * 1024) return "10KB-100KB";
5023
+ if (bytes < 1024 * 1024) return "100KB-1MB";
5024
+ return "1MB+";
5025
+ }
5026
+ function attachmentBytesBucket(bytes, knownCount) {
5027
+ return knownCount > 0 ? bucketBytes(bytes) : "unknown";
5028
+ }
5029
+ function summarizeMessageInputBytes(messages) {
5030
+ if (!messages || messages.length === 0) {
5031
+ return {
5032
+ runtime_input_messages_count: 0,
5033
+ runtime_input_messages_content_bytes_bucket: "0",
5034
+ runtime_input_attachments_count: 0,
5035
+ runtime_input_image_attachments_count: 0,
5036
+ runtime_input_attachments_size_known_count: 0,
5037
+ runtime_input_attachments_bytes_bucket: "0",
5038
+ runtime_input_image_attachments_size_known_count: 0,
5039
+ runtime_input_image_attachments_bytes_bucket: "0",
5040
+ runtime_input_largest_attachment_bytes_bucket: "0",
5041
+ runtime_input_thread_context_messages_count: 0,
5042
+ runtime_input_thread_context_content_bytes_bucket: "0"
5043
+ };
5044
+ }
5045
+ let contentBytes = 0;
5046
+ let attachmentCount = 0;
5047
+ let imageAttachmentCount = 0;
5048
+ let attachmentSizeKnownCount = 0;
5049
+ let attachmentBytes = 0;
5050
+ let imageAttachmentSizeKnownCount = 0;
5051
+ let imageAttachmentBytes = 0;
5052
+ let largestAttachmentBytes = 0;
5053
+ let threadContextMessages = 0;
5054
+ let threadContextContentBytes = 0;
5055
+ for (const message of messages) {
5056
+ contentBytes += Buffer.byteLength(message.content || "", "utf8");
5057
+ for (const attachment of message.attachments || []) {
5058
+ attachmentCount++;
5059
+ if (typeof attachment.sizeBytes === "number" && Number.isFinite(attachment.sizeBytes) && attachment.sizeBytes >= 0) {
5060
+ attachmentSizeKnownCount++;
5061
+ attachmentBytes += attachment.sizeBytes;
5062
+ largestAttachmentBytes = Math.max(largestAttachmentBytes, attachment.sizeBytes);
5063
+ }
5064
+ if (attachment.mimeType?.startsWith("image/")) {
5065
+ imageAttachmentCount++;
5066
+ if (typeof attachment.sizeBytes === "number" && Number.isFinite(attachment.sizeBytes) && attachment.sizeBytes >= 0) {
5067
+ imageAttachmentSizeKnownCount++;
5068
+ imageAttachmentBytes += attachment.sizeBytes;
5069
+ }
5070
+ }
5071
+ }
5072
+ const joinContext = message.thread_join_context;
5073
+ if (joinContext) {
5074
+ const contextMessages = [joinContext.parent_message, ...joinContext.recent_messages];
5075
+ threadContextMessages += contextMessages.length;
5076
+ for (const contextMessage of contextMessages) {
5077
+ threadContextContentBytes += Buffer.byteLength(contextMessage.content || "", "utf8");
5078
+ }
5079
+ }
5080
+ }
5081
+ return {
5082
+ runtime_input_messages_count: messages.length,
5083
+ runtime_input_messages_content_bytes_bucket: bucketBytes(contentBytes),
5084
+ runtime_input_attachments_count: attachmentCount,
5085
+ runtime_input_image_attachments_count: imageAttachmentCount,
5086
+ runtime_input_attachments_size_known_count: attachmentSizeKnownCount,
5087
+ runtime_input_attachments_bytes_bucket: attachmentCount > 0 ? attachmentBytesBucket(attachmentBytes, attachmentSizeKnownCount) : "0",
5088
+ runtime_input_image_attachments_size_known_count: imageAttachmentSizeKnownCount,
5089
+ runtime_input_image_attachments_bytes_bucket: imageAttachmentCount > 0 ? attachmentBytesBucket(imageAttachmentBytes, imageAttachmentSizeKnownCount) : "0",
5090
+ runtime_input_largest_attachment_bytes_bucket: attachmentCount > 0 ? attachmentBytesBucket(largestAttachmentBytes, attachmentSizeKnownCount) : "0",
5091
+ runtime_input_thread_context_messages_count: threadContextMessages,
5092
+ runtime_input_thread_context_content_bytes_bucket: bucketBytes(threadContextContentBytes)
5093
+ };
5094
+ }
5095
+ function buildRuntimeInputTraceAttrs(opts) {
5096
+ return {
5097
+ runtime_input_source: opts.source,
5098
+ runtime_input_prompt_bytes_bucket: bucketBytes(opts.prompt),
5099
+ runtime_input_standing_prompt_bytes_bucket: bucketBytes(opts.standingPrompt),
5100
+ runtime_input_resume_prompt_present: Boolean(opts.resumePrompt),
5101
+ runtime_input_resume_prompt_bytes_bucket: bucketBytes(opts.resumePrompt),
5102
+ runtime_input_session_present: opts.sessionIdPresent,
5103
+ runtime_input_native_standing_prompt_present: opts.nativeStandingPrompt,
5104
+ runtime_input_unread_channels_count: opts.unreadSummary ? Object.keys(opts.unreadSummary).length : 0,
5105
+ ...summarizeMessageInputBytes(opts.messages)
5106
+ };
5107
+ }
5108
+ function runtimeTraceCounterAttrs(ap) {
5109
+ return {
5110
+ runtime_events_count: ap.runtimeTraceCounters.events,
5111
+ runtime_tool_calls_count: ap.runtimeTraceCounters.toolCalls,
5112
+ runtime_tool_outputs_count: ap.runtimeTraceCounters.toolOutputs,
5113
+ runtime_compaction_starts_count: ap.runtimeTraceCounters.compactionStarts,
5114
+ runtime_compaction_finishes_count: ap.runtimeTraceCounters.compactionFinishes,
5115
+ runtime_text_events_count: ap.runtimeTraceCounters.textEvents,
5116
+ runtime_thinking_events_count: ap.runtimeTraceCounters.thinkingEvents
5117
+ };
5118
+ }
4553
5119
  function getMessageDeliveryText(driver) {
4554
5120
  return driver.supportsStdinNotification ? "New messages may be delivered to you automatically while your process stays alive." : "The daemon will automatically restart you when new messages arrive.";
4555
5121
  }
@@ -4847,26 +5413,26 @@ var AgentProcessManager = class _AgentProcessManager {
4847
5413
  this.recordDaemonTrace("daemon.agent.spawn.started", this.startQueueTraceAttrs(agentId, config, wakeMessage, unreadSummary, resumePrompt, launchId));
4848
5414
  try {
4849
5415
  const driver = this.driverResolver(config.runtime || "claude");
4850
- const agentDataDir = path11.join(this.dataDir, agentId);
5416
+ const agentDataDir = path12.join(this.dataDir, agentId);
4851
5417
  await mkdir(agentDataDir, { recursive: true });
4852
5418
  const runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
4853
- const memoryMdPath = path11.join(agentDataDir, "MEMORY.md");
5419
+ const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
4854
5420
  try {
4855
5421
  await access(memoryMdPath);
4856
5422
  } catch {
4857
5423
  const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
4858
5424
  await writeFile(memoryMdPath, initialMemoryMd);
4859
5425
  }
4860
- const notesDir = path11.join(agentDataDir, "notes");
5426
+ const notesDir = path12.join(agentDataDir, "notes");
4861
5427
  await mkdir(notesDir, { recursive: true });
4862
5428
  if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
4863
5429
  const seedFiles = buildOnboardingSeedFiles();
4864
5430
  for (const { relativePath, content } of seedFiles) {
4865
- const fullPath = path11.join(agentDataDir, relativePath);
5431
+ const fullPath = path12.join(agentDataDir, relativePath);
4866
5432
  try {
4867
5433
  await access(fullPath);
4868
5434
  } catch {
4869
- await mkdir(path11.dirname(fullPath), { recursive: true });
5435
+ await mkdir(path12.dirname(fullPath), { recursive: true });
4870
5436
  await writeFile(fullPath, content);
4871
5437
  }
4872
5438
  }
@@ -4874,17 +5440,21 @@ var AgentProcessManager = class _AgentProcessManager {
4874
5440
  const isResume = !!runtimeConfig.sessionId;
4875
5441
  const standingPrompt = driver.buildSystemPrompt(runtimeConfig, agentId);
4876
5442
  let prompt;
5443
+ let promptSource;
4877
5444
  if (runtimeConfig.runtimeProfileControl && !wakeMessage) {
4878
5445
  prompt = driver.supportsNativeStandingPrompt ? NATIVE_STANDING_PROMPT_STARTUP_INPUT : formatRuntimeProfileControlStartupInput(runtimeConfig.runtimeProfileControl, driver);
5446
+ promptSource = "runtime_profile_control";
4879
5447
  } else if (isResume && resumePrompt) {
4880
5448
  prompt = resumePrompt;
4881
5449
  prompt += getBusyDeliveryNote(driver);
5450
+ promptSource = "resume_prompt";
4882
5451
  } else if (wakeMessage) {
4883
5452
  const runtimeProfileControlPrompt = formatRuntimeProfileControlPrompt([wakeMessage]);
4884
5453
  const channelLabel = formatChannelLabel(wakeMessage);
4885
5454
  prompt = runtimeProfileControlPrompt ?? `New message received:
4886
5455
 
4887
5456
  ${formatIncomingMessage(wakeMessage, driver)}`;
5457
+ promptSource = runtimeProfileControlPrompt ? "runtime_profile_control_message" : "wake_message";
4888
5458
  if (!runtimeProfileControlPrompt && unreadSummary && Object.keys(unreadSummary).length > 0) {
4889
5459
  const otherUnread = Object.entries(unreadSummary).filter(([key]) => key !== channelLabel);
4890
5460
  if (otherUnread.length > 0) {
@@ -4918,12 +5488,25 @@ IMPORTANT: If the message requires multi-step work (e.g. research, code changes,
4918
5488
  prompt += `
4919
5489
 
4920
5490
  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)}`;
5491
+ promptSource = "resume_unread_summary";
4921
5492
  } else if (isResume) {
4922
5493
  prompt = `No new messages while you were away. Nothing to do \u2014 just stop. ${getMessageDeliveryText(driver)}`;
4923
5494
  prompt += getBusyDeliveryNote(driver);
5495
+ promptSource = "resume_empty";
4924
5496
  } else {
4925
5497
  prompt = driver.supportsNativeStandingPrompt ? NATIVE_STANDING_PROMPT_STARTUP_INPUT : standingPrompt;
5498
+ promptSource = "cold_start";
4926
5499
  }
5500
+ const runtimeInputTraceAttrs = buildRuntimeInputTraceAttrs({
5501
+ source: promptSource,
5502
+ prompt,
5503
+ standingPrompt,
5504
+ resumePrompt,
5505
+ messages: wakeMessage ? [wakeMessage] : void 0,
5506
+ unreadSummary,
5507
+ sessionIdPresent: isResume,
5508
+ nativeStandingPrompt: Boolean(driver.supportsNativeStandingPrompt)
5509
+ });
4927
5510
  const effectiveConfig = await this.buildSpawnConfig(agentId, runtimeConfig);
4928
5511
  const canDeferEmptyStart = driver.deferSpawnUntilMessage === true && !wakeMessage && !runtimeConfig.runtimeProfileControl && (!unreadSummary || Object.keys(unreadSummary).length === 0);
4929
5512
  if (canDeferEmptyStart) {
@@ -4984,6 +5567,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
4984
5567
  lastRuntimeEventAt: Date.now(),
4985
5568
  runtimeProgressStaleSince: null,
4986
5569
  runtimeTraceSpan: null,
5570
+ runtimeTraceCounters: createRuntimeTraceCounters(),
4987
5571
  lastActivity: "",
4988
5572
  lastActivityDetail: "",
4989
5573
  activityClientSeq: 0,
@@ -5005,7 +5589,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5005
5589
  sessionId: effectiveConfig.sessionId || null,
5006
5590
  launchId: launchId || null
5007
5591
  });
5008
- this.startRuntimeTrace(agentId, agentProcess, "spawn", wakeMessage ? [wakeMessage] : void 0);
5592
+ this.startRuntimeTrace(agentId, agentProcess, "spawn", wakeMessage ? [wakeMessage] : void 0, runtimeInputTraceAttrs);
5009
5593
  this.agentsStarting.delete(agentId);
5010
5594
  if (config.runtimeProfileControl) {
5011
5595
  this.ackInjectedRuntimeProfileControl(agentId, config.runtimeProfileControl, agentProcess.launchId);
@@ -5099,6 +5683,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5099
5683
  expectedTerminationReason: ap.expectedTerminationReason || void 0,
5100
5684
  exitCode: finalCode,
5101
5685
  exitSignal: finalSignal,
5686
+ ...runtimeTraceCounterAttrs(ap),
5102
5687
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "process_exit")
5103
5688
  });
5104
5689
  if (processEndedCleanly) {
@@ -5526,7 +6111,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5526
6111
  return true;
5527
6112
  }
5528
6113
  async resetWorkspace(agentId) {
5529
- const agentDataDir = path11.join(this.dataDir, agentId);
6114
+ const agentDataDir = path12.join(this.dataDir, agentId);
5530
6115
  try {
5531
6116
  await rm2(agentDataDir, { recursive: true, force: true });
5532
6117
  logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
@@ -5563,7 +6148,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5563
6148
  return result;
5564
6149
  }
5565
6150
  buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
5566
- const workspacePath = path11.join(this.dataDir, agentId);
6151
+ const workspacePath = path12.join(this.dataDir, agentId);
5567
6152
  return {
5568
6153
  agentId,
5569
6154
  launchId,
@@ -5811,7 +6396,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5811
6396
  }
5812
6397
  // Workspace file browsing
5813
6398
  async getFileTree(agentId, dirPath) {
5814
- const agentDir = path11.join(this.dataDir, agentId);
6399
+ const agentDir = path12.join(this.dataDir, agentId);
5815
6400
  try {
5816
6401
  await stat2(agentDir);
5817
6402
  } catch {
@@ -5819,8 +6404,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5819
6404
  }
5820
6405
  let targetDir = agentDir;
5821
6406
  if (dirPath) {
5822
- const resolved = path11.resolve(agentDir, dirPath);
5823
- if (!resolved.startsWith(agentDir + path11.sep) && resolved !== agentDir) {
6407
+ const resolved = path12.resolve(agentDir, dirPath);
6408
+ if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
5824
6409
  return [];
5825
6410
  }
5826
6411
  targetDir = resolved;
@@ -5828,14 +6413,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5828
6413
  return this.listDirectoryChildren(targetDir, agentDir);
5829
6414
  }
5830
6415
  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) {
6416
+ const agentDir = path12.join(this.dataDir, agentId);
6417
+ const resolved = path12.resolve(agentDir, filePath);
6418
+ if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
5834
6419
  throw new Error("Access denied");
5835
6420
  }
5836
6421
  const info = await stat2(resolved);
5837
6422
  if (info.isDirectory()) throw new Error("Cannot read a directory");
5838
- const ext = path11.extname(resolved).toLowerCase();
6423
+ const ext = path12.extname(resolved).toLowerCase();
5839
6424
  if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
5840
6425
  if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
5841
6426
  const content = await readFile(resolved, "utf-8");
@@ -5870,13 +6455,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5870
6455
  const agent = this.agents.get(agentId);
5871
6456
  const runtime = runtimeHint || agent?.config.runtime || "claude";
5872
6457
  const home = os5.homedir();
5873
- const workspaceDir = path11.join(this.dataDir, agentId);
6458
+ const workspaceDir = path12.join(this.dataDir, agentId);
5874
6459
  const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
5875
6460
  const globalResults = await Promise.all(
5876
- paths.global.map((p) => this.scanSkillsDir(path11.join(home, p)))
6461
+ paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
5877
6462
  );
5878
6463
  const workspaceResults = await Promise.all(
5879
- paths.workspace.map((p) => this.scanSkillsDir(path11.join(workspaceDir, p)))
6464
+ paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
5880
6465
  );
5881
6466
  const dedup = (skills) => {
5882
6467
  const seen = /* @__PURE__ */ new Set();
@@ -5905,7 +6490,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5905
6490
  const skills = [];
5906
6491
  for (const entry of entries) {
5907
6492
  if (entry.isDirectory() || entry.isSymbolicLink()) {
5908
- const skillMd = path11.join(dir, entry.name, "SKILL.md");
6493
+ const skillMd = path12.join(dir, entry.name, "SKILL.md");
5909
6494
  try {
5910
6495
  const content = await readFile(skillMd, "utf-8");
5911
6496
  const skill = this.parseSkillMd(entry.name, content);
@@ -5916,7 +6501,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5916
6501
  } else if (entry.name.endsWith(".md")) {
5917
6502
  const cmdName = entry.name.replace(/\.md$/, "");
5918
6503
  try {
5919
- const content = await readFile(path11.join(dir, entry.name), "utf-8");
6504
+ const content = await readFile(path12.join(dir, entry.name), "utf-8");
5920
6505
  const skill = this.parseSkillMd(cmdName, content);
5921
6506
  skill.sourcePath = dir;
5922
6507
  skills.push(skill);
@@ -5951,6 +6536,12 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
5951
6536
  /**
5952
6537
  * Broadcast an activity change — emits a single agent:activity event that carries
5953
6538
  * both the status (for the dot indicator) and trajectory entries (for the activity log).
6539
+ *
6540
+ * TODO(lifecycle-v2/daemon-protocol): split this legacy frame into
6541
+ * structured lifecycle producers. Runtime progress, transient heartbeat,
6542
+ * provider/runtime error, process exit, and user-visible activity entries
6543
+ * should carry explicit reason/correlation/window attrs so the server no
6544
+ * longer infers lifecycle semantics from generic activity strings.
5954
6545
  */
5955
6546
  broadcastActivity(agentId, activity, detail, extraTrajectory = [], launchIdOverride) {
5956
6547
  const ap = this.agents.get(agentId);
@@ -6176,8 +6767,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6176
6767
  runtime_profile_turn_outcome: control.kind === "migration" ? control.migrationDoneToolObserved ? "migration_done_observed" : "missing_migration_done" : "notice_only"
6177
6768
  };
6178
6769
  }
6179
- startRuntimeTrace(agentId, ap, reason, messages) {
6770
+ startRuntimeTrace(agentId, ap, reason, messages, inputTraceAttrs = {}) {
6180
6771
  if (ap.runtimeTraceSpan) return ap.runtimeTraceSpan;
6772
+ ap.runtimeTraceCounters = createRuntimeTraceCounters();
6181
6773
  const messageControl = this.runtimeProfileTurnControlFromMessages(messages);
6182
6774
  if (messageControl) {
6183
6775
  this.activateRuntimeProfileTurnControl(ap, messageControl);
@@ -6192,12 +6784,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6192
6784
  reason,
6193
6785
  hasSession: Boolean(ap.sessionId),
6194
6786
  ...this.messagesTraceAttrs(messages),
6787
+ ...inputTraceAttrs,
6195
6788
  ...this.runtimeProfileTurnControlTraceAttrs(ap.runtimeProfileTurnControl)
6196
6789
  }
6197
6790
  });
6198
6791
  span.addEvent("daemon.turn.started", {
6199
6792
  reason,
6200
6793
  ...this.messagesTraceAttrs(messages),
6794
+ ...inputTraceAttrs,
6201
6795
  ...this.runtimeProfileTurnControlTraceAttrs(ap.runtimeProfileTurnControl)
6202
6796
  });
6203
6797
  ap.runtimeTraceSpan = span;
@@ -6306,6 +6900,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6306
6900
  lastActivity: ap.lastActivity,
6307
6901
  lastActivityDetailPresent: Boolean(ap.lastActivityDetail),
6308
6902
  lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
6903
+ ...runtimeTraceCounterAttrs(ap),
6309
6904
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_stalled")
6310
6905
  });
6311
6906
  this.broadcastActivity(agentId, "error", diagnostic.detail);
@@ -6338,6 +6933,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6338
6933
  lastActivityDetailKind: classifyActivityDetailForTrace(ap.lastActivityDetail),
6339
6934
  pendingMessages: ap.inbox.length,
6340
6935
  recovery: "terminate_for_queued_message",
6936
+ ...runtimeTraceCounterAttrs(ap),
6341
6937
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_stalled")
6342
6938
  });
6343
6939
  ap.expectedTerminationReason = "stalled_recovery";
@@ -6365,6 +6961,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6365
6961
  const ap = this.agents.get(agentId);
6366
6962
  if (ap) {
6367
6963
  const wasStalled = Boolean(ap.runtimeProgressStaleSince);
6964
+ this.noteRuntimeTraceCounter(ap, event);
6368
6965
  this.recordRuntimeTraceEvent(agentId, ap, "runtime.event.received", { kind: event.kind });
6369
6966
  if (wasStalled) {
6370
6967
  this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.observed", { afterStall: true });
@@ -6492,6 +7089,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6492
7089
  }
6493
7090
  this.endRuntimeTrace(ap, "ok", {
6494
7091
  outcome: "turn-completed",
7092
+ ...runtimeTraceCounterAttrs(ap),
6495
7093
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "turn_end")
6496
7094
  });
6497
7095
  if (ap.driver.terminateProcessOnTurnEnd) {
@@ -6533,10 +7131,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6533
7131
  `[Agent ${agentId}] Disabled Claude tool-boundary gated steering after thinking-block mutation error; lastFlushReason=${ap.gatedSteering.lastFlushReason || "none"}`
6534
7132
  );
6535
7133
  }
6536
- this.recordRuntimeTraceEvent(agentId, ap, "runtime.error", runtimeErrorDiagnostics.eventAttrs);
7134
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime.error", {
7135
+ ...runtimeErrorDiagnostics.eventAttrs,
7136
+ ...runtimeTraceCounterAttrs(ap)
7137
+ });
6537
7138
  this.endRuntimeTrace(ap, "error", {
6538
7139
  outcome: "runtime-error",
6539
7140
  ...runtimeErrorDiagnostics.spanAttrs,
7141
+ ...runtimeTraceCounterAttrs(ap),
6540
7142
  ...this.finalizeRuntimeProfileTurnControl(agentId, ap, "runtime_error")
6541
7143
  });
6542
7144
  if (ap.driver.supportsStdinNotification && classifyTerminalFailure(ap)) {
@@ -6559,6 +7161,29 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6559
7161
  sendAgentStatus(agentId, status, launchId) {
6560
7162
  this.sendToServer({ type: "agent:status", agentId, status, launchId: launchId || void 0 });
6561
7163
  }
7164
+ noteRuntimeTraceCounter(ap, event) {
7165
+ ap.runtimeTraceCounters.events++;
7166
+ switch (event.kind) {
7167
+ case "tool_call":
7168
+ ap.runtimeTraceCounters.toolCalls++;
7169
+ break;
7170
+ case "tool_output":
7171
+ ap.runtimeTraceCounters.toolOutputs++;
7172
+ break;
7173
+ case "compaction_started":
7174
+ ap.runtimeTraceCounters.compactionStarts++;
7175
+ break;
7176
+ case "compaction_finished":
7177
+ ap.runtimeTraceCounters.compactionFinishes++;
7178
+ break;
7179
+ case "text":
7180
+ ap.runtimeTraceCounters.textEvents++;
7181
+ break;
7182
+ case "thinking":
7183
+ ap.runtimeTraceCounters.thinkingEvents++;
7184
+ break;
7185
+ }
7186
+ }
6562
7187
  /** Send a batched notification to the agent via stdin about pending messages */
6563
7188
  sendStdinNotification(agentId) {
6564
7189
  const ap = this.agents.get(agentId);
@@ -6661,6 +7286,14 @@ ${messages.map((message) => formatIncomingMessage(message, ap.driver)).join("\n"
6661
7286
 
6662
7287
  Respond as appropriate. Complete all your work before stopping.
6663
7288
  ${RESPONSE_TARGET_HINT}`);
7289
+ const inputTraceAttrs = buildRuntimeInputTraceAttrs({
7290
+ source: `stdin_${mode}_delivery`,
7291
+ prompt,
7292
+ messages,
7293
+ sessionIdPresent: Boolean(ap.sessionId),
7294
+ nativeStandingPrompt: Boolean(ap.driver.supportsNativeStandingPrompt)
7295
+ });
7296
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime.input.prepared", inputTraceAttrs);
6664
7297
  const encoded = ap.driver.encodeStdinMessage(prompt, ap.sessionId, { mode });
6665
7298
  if (!encoded) {
6666
7299
  ap.inbox.unshift(...messages);
@@ -6672,6 +7305,7 @@ ${RESPONSE_TARGET_HINT}`);
6672
7305
  );
6673
7306
  this.recordDaemonTrace("daemon.agent.stdin_delivery", {
6674
7307
  ...traceAttrs,
7308
+ ...inputTraceAttrs,
6675
7309
  outcome: "encode_failed",
6676
7310
  requeued_messages_count: messages.length
6677
7311
  }, "error");
@@ -6688,6 +7322,7 @@ ${RESPONSE_TARGET_HINT}`);
6688
7322
  this.ackInjectedRuntimeProfileMessages(agentId, messages, ap.launchId);
6689
7323
  this.recordDaemonTrace("daemon.agent.stdin_delivery", {
6690
7324
  ...traceAttrs,
7325
+ ...inputTraceAttrs,
6691
7326
  outcome: "written",
6692
7327
  stdin_write_attempted: true
6693
7328
  });
@@ -6709,8 +7344,8 @@ ${RESPONSE_TARGET_HINT}`);
6709
7344
  const nodes = [];
6710
7345
  for (const entry of entries) {
6711
7346
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
6712
- const fullPath = path11.join(dir, entry.name);
6713
- const relativePath = path11.relative(rootDir, fullPath);
7347
+ const fullPath = path12.join(dir, entry.name);
7348
+ const relativePath = path12.relative(rootDir, fullPath);
6714
7349
  let info;
6715
7350
  try {
6716
7351
  info = await stat2(fullPath);
@@ -7013,9 +7648,9 @@ var ReminderCache = class {
7013
7648
 
7014
7649
  // src/machineLock.ts
7015
7650
  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";
7651
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync9 } from "fs";
7017
7652
  import os6 from "os";
7018
- import path12 from "path";
7653
+ import path13 from "path";
7019
7654
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
7020
7655
  var DaemonMachineLockConflictError = class extends Error {
7021
7656
  code = "DAEMON_MACHINE_LOCK_HELD";
@@ -7037,7 +7672,7 @@ function resolveDefaultMachineStateRoot() {
7037
7672
  return resolveSlockHomePath("machines");
7038
7673
  }
7039
7674
  function ownerPath(lockDir) {
7040
- return path12.join(lockDir, "owner.json");
7675
+ return path13.join(lockDir, "owner.json");
7041
7676
  }
7042
7677
  function readOwner(lockDir) {
7043
7678
  try {
@@ -7067,13 +7702,13 @@ function acquireDaemonMachineLock(options) {
7067
7702
  const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
7068
7703
  const fingerprint = apiKeyFingerprint(options.apiKey);
7069
7704
  const lockId = getDaemonMachineLockId(options.apiKey);
7070
- const machineDir = path12.join(rootDir, lockId);
7071
- const lockDir = path12.join(machineDir, "daemon.lock");
7705
+ const machineDir = path13.join(rootDir, lockId);
7706
+ const lockDir = path13.join(machineDir, "daemon.lock");
7072
7707
  const token = randomUUID2();
7073
- mkdirSync5(machineDir, { recursive: true });
7708
+ mkdirSync6(machineDir, { recursive: true });
7074
7709
  for (let attempt = 0; attempt < 2; attempt += 1) {
7075
7710
  try {
7076
- mkdirSync5(lockDir);
7711
+ mkdirSync6(lockDir);
7077
7712
  const owner = {
7078
7713
  pid: process.pid,
7079
7714
  token,
@@ -7083,7 +7718,7 @@ function acquireDaemonMachineLock(options) {
7083
7718
  apiKeyFingerprint: fingerprint.slice(0, 16)
7084
7719
  };
7085
7720
  try {
7086
- writeFileSync8(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
7721
+ writeFileSync9(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
7087
7722
  `, { mode: 384 });
7088
7723
  } catch (err) {
7089
7724
  rmSync2(lockDir, { recursive: true, force: true });
@@ -7120,8 +7755,8 @@ function acquireDaemonMachineLock(options) {
7120
7755
  }
7121
7756
 
7122
7757
  // 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";
7758
+ import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync4, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync10 } from "fs";
7759
+ import path14 from "path";
7125
7760
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
7126
7761
  var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
7127
7762
  var DEFAULT_MAX_FILES = 8;
@@ -7157,7 +7792,7 @@ var LocalRotatingTraceSink = class {
7157
7792
  currentSize = 0;
7158
7793
  sequence = 0;
7159
7794
  constructor(options) {
7160
- this.traceDir = path13.join(options.machineDir, "traces");
7795
+ this.traceDir = path14.join(options.machineDir, "traces");
7161
7796
  this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
7162
7797
  const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
7163
7798
  const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
@@ -7183,26 +7818,26 @@ var LocalRotatingTraceSink = class {
7183
7818
  return this.currentFile;
7184
7819
  }
7185
7820
  ensureFile(nextBytes) {
7186
- mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
7821
+ mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
7187
7822
  const nowMs = this.nowMsProvider();
7188
7823
  const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
7189
7824
  if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
7190
- this.currentFile = path13.join(
7825
+ this.currentFile = path14.join(
7191
7826
  this.traceDir,
7192
7827
  `daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
7193
7828
  );
7194
- writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
7829
+ writeFileSync10(this.currentFile, "", { flag: "a", mode: 384 });
7195
7830
  this.currentSize = statSync4(this.currentFile).size;
7196
7831
  this.currentFileOpenedAtMs = nowMs;
7197
7832
  this.pruneOldFiles();
7198
7833
  }
7199
7834
  }
7200
7835
  pruneOldFiles() {
7201
- const files = readdirSync3(this.traceDir).filter((name) => name.startsWith("daemon-trace-") && name.endsWith(".jsonl")).sort();
7836
+ const files = readdirSync4(this.traceDir).filter((name) => name.startsWith("daemon-trace-") && name.endsWith(".jsonl")).sort();
7202
7837
  const excess = files.length - this.maxFiles;
7203
7838
  if (excess <= 0) return;
7204
7839
  for (const file of files.slice(0, excess)) {
7205
- rmSync3(path13.join(this.traceDir, file), { force: true });
7840
+ rmSync3(path14.join(this.traceDir, file), { force: true });
7206
7841
  }
7207
7842
  }
7208
7843
  };
@@ -7289,11 +7924,11 @@ function isDiagnosticErrorAttr(key) {
7289
7924
  import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
7290
7925
  import { gzipSync } from "zlib";
7291
7926
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
7292
- import path14 from "path";
7927
+ import path15 from "path";
7293
7928
 
7294
7929
  // src/directUploadCapability.ts
7295
- function joinUrl(base, path16) {
7296
- return `${base.replace(/\/+$/, "")}${path16}`;
7930
+ function joinUrl(base, path17) {
7931
+ return `${base.replace(/\/+$/, "")}${path17}`;
7297
7932
  }
7298
7933
  function jsonHeaders(apiKey) {
7299
7934
  return {
@@ -7512,7 +8147,7 @@ var DaemonTraceBundleUploader = class {
7512
8147
  }, nextMs);
7513
8148
  }
7514
8149
  async findUploadCandidates() {
7515
- const traceDir = path14.join(this.options.machineDir, "traces");
8150
+ const traceDir = path15.join(this.options.machineDir, "traces");
7516
8151
  let names;
7517
8152
  try {
7518
8153
  names = await readdir3(traceDir);
@@ -7524,8 +8159,8 @@ var DaemonTraceBundleUploader = class {
7524
8159
  const currentFile = this.options.currentFileProvider?.();
7525
8160
  const candidates = [];
7526
8161
  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;
8162
+ const file = path15.join(traceDir, name);
8163
+ if (currentFile && path15.resolve(file) === path15.resolve(currentFile)) continue;
7529
8164
  if (await this.isUploaded(file)) continue;
7530
8165
  try {
7531
8166
  const info = await stat3(file);
@@ -7599,8 +8234,8 @@ var DaemonTraceBundleUploader = class {
7599
8234
  }
7600
8235
  }
7601
8236
  uploadStatePath(file) {
7602
- const stateDir = path14.join(this.options.machineDir, "trace-uploads");
7603
- return path14.join(stateDir, `${path14.basename(file)}.uploaded.json`);
8237
+ const stateDir = path15.join(this.options.machineDir, "trace-uploads");
8238
+ return path15.join(stateDir, `${path15.basename(file)}.uploaded.json`);
7604
8239
  }
7605
8240
  async isUploaded(file) {
7606
8241
  try {
@@ -7612,9 +8247,9 @@ var DaemonTraceBundleUploader = class {
7612
8247
  }
7613
8248
  async markUploaded(file, metadata) {
7614
8249
  const stateFile = this.uploadStatePath(file);
7615
- await mkdir2(path14.dirname(stateFile), { recursive: true, mode: 448 });
8250
+ await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
7616
8251
  await writeFile2(stateFile, `${JSON.stringify({
7617
- file: path14.basename(file),
8252
+ file: path15.basename(file),
7618
8253
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
7619
8254
  ...metadata
7620
8255
  }, null, 2)}
@@ -7633,10 +8268,10 @@ function readPositiveIntegerEnv2(name, fallback) {
7633
8268
 
7634
8269
  // src/core.ts
7635
8270
  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) {
8271
+ var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
8272
+ function parseDaemonCliArgs(args, env = {}) {
7638
8273
  let serverUrl = "";
7639
- let apiKey = "";
8274
+ let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
7640
8275
  for (let i = 0; i < args.length; i++) {
7641
8276
  if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
7642
8277
  if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
@@ -7653,23 +8288,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
7653
8288
  }
7654
8289
  }
7655
8290
  function resolveChatBridgePath(moduleUrl = import.meta.url) {
7656
- const dirname = path15.dirname(fileURLToPath(moduleUrl));
7657
- const jsPath = path15.resolve(dirname, "chat-bridge.js");
8291
+ const dirname = path16.dirname(fileURLToPath2(moduleUrl));
8292
+ const jsPath = path16.resolve(dirname, "chat-bridge.js");
7658
8293
  try {
7659
8294
  accessSync(jsPath);
7660
8295
  return jsPath;
7661
8296
  } catch {
7662
- return path15.resolve(dirname, "chat-bridge.ts");
8297
+ return path16.resolve(dirname, "chat-bridge.ts");
7663
8298
  }
7664
8299
  }
7665
8300
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
7666
- const thisDir = path15.dirname(fileURLToPath(moduleUrl));
7667
- const bundledDistPath = path15.resolve(thisDir, "cli", "index.js");
8301
+ const thisDir = path16.dirname(fileURLToPath2(moduleUrl));
8302
+ const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
7668
8303
  try {
7669
8304
  accessSync(bundledDistPath);
7670
8305
  return bundledDistPath;
7671
8306
  } catch {
7672
- const workspaceDistPath = path15.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
8307
+ const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
7673
8308
  accessSync(workspaceDistPath);
7674
8309
  return workspaceDistPath;
7675
8310
  }
@@ -7848,7 +8483,7 @@ var DaemonCore = class {
7848
8483
  }
7849
8484
  resolveMachineStateRoot() {
7850
8485
  if (this.options.machineStateDir) return this.options.machineStateDir;
7851
- if (this.options.dataDir) return path15.join(path15.dirname(this.options.dataDir), "machines");
8486
+ if (this.options.dataDir) return path16.join(path16.dirname(this.options.dataDir), "machines");
7852
8487
  return resolveDefaultMachineStateRoot();
7853
8488
  }
7854
8489
  shouldEnableLocalTrace() {
@@ -8231,6 +8866,8 @@ var DaemonCore = class {
8231
8866
  };
8232
8867
 
8233
8868
  export {
8869
+ DAEMON_API_KEY_ENV,
8870
+ scrubDaemonAuthEnv,
8234
8871
  resolveWorkspaceDirectoryPath,
8235
8872
  scanWorkspaceDirectories,
8236
8873
  deleteWorkspaceDirectory,