@slock-ai/daemon 0.54.1 → 0.54.2-play.20260528162546

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.
@@ -8,11 +8,35 @@ import {
8
8
  } from "./chunk-VOZJ2ELH.js";
9
9
 
10
10
  // src/core.ts
11
- import path16 from "path";
12
- import os8 from "os";
11
+ import path17 from "path";
12
+ import os7 from "os";
13
13
  import { createRequire as createRequire2 } from "module";
14
14
  import { accessSync } from "fs";
15
- import { fileURLToPath } from "url";
15
+ import { fileURLToPath as fileURLToPath2 } from "url";
16
+
17
+ // ../shared/src/slockRefs.ts
18
+ var SLOCK_REF_CHANNEL_NAME_PATTERN = String.raw`[\p{L}\p{N}_-]+`;
19
+ var SLOCK_REF_USER_NAME_PATTERN = SLOCK_REF_CHANNEL_NAME_PATTERN;
20
+ var SLOCK_REF_DM_PEER_PATTERN = String.raw`[\w-]+`;
21
+ var SLOCK_REF_THREAD_SHORT_ID_PATTERN = String.raw`[\da-f]{6,8}`;
22
+ var SLOCK_REF_MESSAGE_ID_PATTERN = String.raw`[A-Za-z0-9][A-Za-z0-9-]{1,63}`;
23
+ var SLOCK_REF_TASK_NUMBER_PATTERN = String.raw`[1-9]\d*`;
24
+ var USER_RE = new RegExp(String.raw`^@(${SLOCK_REF_USER_NAME_PATTERN})$`, "u");
25
+ var CHANNEL_RE = new RegExp(String.raw`^#(${SLOCK_REF_CHANNEL_NAME_PATTERN})$`, "u");
26
+ var CHANNEL_THREAD_RE = new RegExp(
27
+ String.raw`^#(${SLOCK_REF_CHANNEL_NAME_PATTERN}):(${SLOCK_REF_THREAD_SHORT_ID_PATTERN})$`,
28
+ "iu"
29
+ );
30
+ var DM_RE = new RegExp(String.raw`^dm:@(${SLOCK_REF_DM_PEER_PATTERN})$`, "iu");
31
+ var DM_THREAD_RE = new RegExp(
32
+ String.raw`^dm:@(${SLOCK_REF_DM_PEER_PATTERN}):(${SLOCK_REF_THREAD_SHORT_ID_PATTERN})$`,
33
+ "iu"
34
+ );
35
+ var TASK_RE = new RegExp(String.raw`^task\s+#(${SLOCK_REF_TASK_NUMBER_PATTERN})$`, "iu");
36
+ var CHANNEL_MESSAGE_RE = new RegExp(
37
+ String.raw`^#(${SLOCK_REF_CHANNEL_NAME_PATTERN})(?::(${SLOCK_REF_THREAD_SHORT_ID_PATTERN}))?\s+msg=(${SLOCK_REF_MESSAGE_ID_PATTERN})$`,
38
+ "iu"
39
+ );
16
40
 
17
41
  // ../shared/src/tracing/index.ts
18
42
  var DEFAULT_TRACE_FLAGS = "00";
@@ -724,9 +748,11 @@ var SERVER_CAPABILITY_MATRIX = {
724
748
  };
725
749
 
726
750
  // ../shared/src/index.ts
751
+ var RUNTIME_CONFIG_VERSION = 1;
727
752
  var RUNTIMES = [
728
753
  { id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
729
754
  { id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
755
+ { id: "pi", displayName: "Pi", binary: "pi", supported: true },
730
756
  { id: "antigravity", displayName: "Antigravity CLI", binary: "agy", supported: true },
731
757
  { id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true },
732
758
  { id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
@@ -734,6 +760,171 @@ var RUNTIMES = [
734
760
  { id: "gemini", displayName: "Gemini CLI", binary: "gemini", supported: true },
735
761
  { id: "opencode", displayName: "OpenCode", binary: "opencode", supported: true }
736
762
  ];
763
+ var RUNTIME_MODELS = {
764
+ claude: [
765
+ { id: "opus", label: "Opus" },
766
+ { id: "sonnet", label: "Sonnet" },
767
+ { id: "haiku", label: "Haiku" }
768
+ ],
769
+ codex: [
770
+ { id: "gpt-5.5", label: "GPT-5.5" },
771
+ { id: "gpt-5.4", label: "GPT-5.4" },
772
+ { id: "gpt-5.3-codex", label: "GPT-5.3 Codex" },
773
+ { id: "gpt-5.3-codex-spark", label: "GPT-5.3 Codex Spark" },
774
+ { id: "gpt-5.2-codex", label: "GPT-5.2 Codex" },
775
+ { id: "gpt-5.2", label: "GPT-5.2" },
776
+ { id: "gpt-5.1-codex-max", label: "GPT-5.1 Codex Max" },
777
+ { id: "gpt-5.1-codex", label: "GPT-5.1 Codex" },
778
+ { id: "gpt-5-codex", label: "GPT-5 Codex" },
779
+ { id: "gpt-5", label: "GPT-5" }
780
+ ],
781
+ antigravity: [
782
+ { id: "default", label: "AGY configured default", verified: "suggestion_only" }
783
+ ],
784
+ copilot: [
785
+ { id: "gpt-5.4", label: "GPT-5.4" },
786
+ { id: "gpt-5.2", label: "GPT-5.2" },
787
+ { id: "claude-4-sonnet", label: "Claude 4 Sonnet" },
788
+ { id: "claude-4.5-sonnet", label: "Claude 4.5 Sonnet" }
789
+ ],
790
+ cursor: [
791
+ { id: "composer-2-fast", label: "Composer 2 Fast" },
792
+ { id: "composer-2", label: "Composer 2" },
793
+ { id: "auto", label: "Auto" }
794
+ ],
795
+ gemini: [
796
+ { id: "default", label: "Configured Default / Auto", verified: "suggestion_only" },
797
+ { id: "gemini-3.1-pro-preview", label: "Gemini 3.1 Pro (Preview)" },
798
+ { id: "gemini-3-flash-preview", label: "Gemini 3 Flash (Preview)" },
799
+ { id: "gemini-2.5-pro", label: "Gemini 2.5 Pro" },
800
+ { id: "gemini-2.5-flash", label: "Gemini 2.5 Flash" }
801
+ ],
802
+ opencode: [
803
+ { id: "default", label: "Configured Default / Auto", verified: "suggestion_only" },
804
+ { id: "deepseek/deepseek-v4-pro", label: "DeepSeek V4 Pro (OpenCode)", verified: "suggestion_only" },
805
+ { id: "openrouter/anthropic/claude-opus-4.5", label: "Claude Opus 4.5 via OpenRouter", verified: "suggestion_only" },
806
+ { id: "fusecode/opus[1m]", label: "Opus 1M via FuseCode", verified: "suggestion_only" }
807
+ ],
808
+ pi: [
809
+ { id: "deepseek/deepseek-v4-pro", label: "DeepSeek V4 Pro" },
810
+ { id: "deepseek/deepseek-v4-flash", label: "DeepSeek V4 Flash" },
811
+ { id: "kimi-coding/kimi-for-coding", label: "Kimi for Coding" },
812
+ { id: "zai/glm-5.1", label: "GLM-5.1" },
813
+ { id: "zai/glm-4.7", label: "GLM-4.7" }
814
+ ],
815
+ // Kimi CLI resolves model keys from each user's local config, so the safest
816
+ // built-in option is to defer to whatever default model the CLI already uses.
817
+ kimi: [
818
+ { id: "default", label: "Configured Default" }
819
+ ]
820
+ };
821
+ function getDefaultModel(runtimeId) {
822
+ const models = RUNTIME_MODELS[runtimeId];
823
+ return models?.[0]?.id ?? "sonnet";
824
+ }
825
+ var CONTROLLED_RUNTIME_ENV_KEYS = {
826
+ claude: ["ANTHROPIC_BASE_URL", "ANTHROPIC_API_KEY", "ANTHROPIC_CUSTOM_MODEL_OPTION"]
827
+ };
828
+ function isPlainRecord(value) {
829
+ return value !== null && typeof value === "object" && !Array.isArray(value);
830
+ }
831
+ function normalizeEnvVars(envVars) {
832
+ if (!isPlainRecord(envVars)) return null;
833
+ const normalized = {};
834
+ for (const [key, value] of Object.entries(envVars)) {
835
+ if (typeof key === "string" && typeof value === "string") {
836
+ normalized[key] = value;
837
+ }
838
+ }
839
+ return Object.keys(normalized).length > 0 ? normalized : null;
840
+ }
841
+ function getControlledRuntimeEnvKeys(runtime) {
842
+ return CONTROLLED_RUNTIME_ENV_KEYS[runtime] ?? [];
843
+ }
844
+ function stripControlledRuntimeEnvVars(runtime, envVars) {
845
+ const normalized = normalizeEnvVars(envVars);
846
+ if (!normalized) return null;
847
+ const controlled = new Set(getControlledRuntimeEnvKeys(runtime));
848
+ for (const key of controlled) {
849
+ delete normalized[key];
850
+ }
851
+ return Object.keys(normalized).length > 0 ? normalized : null;
852
+ }
853
+ function isPresetRuntimeModel(runtime, model) {
854
+ return (RUNTIME_MODELS[runtime] ?? []).some((candidate) => candidate.id === model);
855
+ }
856
+ function modelConfigFromLegacy(runtime, model) {
857
+ return isPresetRuntimeModel(runtime, model) ? { kind: "preset", id: model } : { kind: "custom", name: model };
858
+ }
859
+ function parseProviderConfig(runtime, value) {
860
+ if (runtime !== "claude") return void 0;
861
+ if (!isPlainRecord(value)) return { kind: "default" };
862
+ if (value.kind === "custom" && typeof value.apiUrl === "string" && value.apiUrl.trim() && typeof value.apiKey === "string" && value.apiKey.trim()) {
863
+ return { kind: "custom", apiUrl: value.apiUrl.trim(), apiKey: value.apiKey.trim() };
864
+ }
865
+ return { kind: "default" };
866
+ }
867
+ function parseModelConfig(runtime, value, fallback) {
868
+ if (!isPlainRecord(value)) return modelConfigFromLegacy(runtime, fallback);
869
+ if (value.kind === "custom" && typeof value.name === "string" && value.name.trim()) {
870
+ return { kind: "custom", name: value.name.trim() };
871
+ }
872
+ if (value.kind === "preset" && typeof value.id === "string" && value.id.trim()) {
873
+ return { kind: "preset", id: value.id.trim() };
874
+ }
875
+ return modelConfigFromLegacy(runtime, fallback);
876
+ }
877
+ function parseModeConfig(value) {
878
+ if (!isPlainRecord(value)) return { kind: "default" };
879
+ if (value.kind === "fast") return { kind: "fast" };
880
+ return { kind: "default" };
881
+ }
882
+ function hydrateRuntimeConfig(input) {
883
+ const stored = isPlainRecord(input.runtimeConfig) && input.runtimeConfig.version === RUNTIME_CONFIG_VERSION ? input.runtimeConfig : null;
884
+ const runtime = typeof stored?.runtime === "string" && stored.runtime.trim() || typeof input.runtime === "string" && input.runtime.trim() || "claude";
885
+ const fallbackModel = typeof input.model === "string" && input.model.trim() || getDefaultModel(runtime);
886
+ const legacyEnvVars = normalizeEnvVars(input.envVars);
887
+ const storedEnvVars = normalizeEnvVars(stored?.envVars);
888
+ const legacyClaudeApiUrl = runtime === "claude" ? legacyEnvVars?.ANTHROPIC_BASE_URL : void 0;
889
+ const legacyClaudeApiKey = runtime === "claude" ? legacyEnvVars?.ANTHROPIC_API_KEY : void 0;
890
+ const provider = stored ? parseProviderConfig(runtime, stored.provider) : runtime === "claude" && legacyClaudeApiUrl && legacyClaudeApiKey ? { kind: "custom", apiUrl: legacyClaudeApiUrl, apiKey: legacyClaudeApiKey } : runtime === "claude" ? { kind: "default" } : void 0;
891
+ return {
892
+ version: RUNTIME_CONFIG_VERSION,
893
+ runtime,
894
+ ...provider ? { provider } : {},
895
+ model: stored ? parseModelConfig(runtime, stored.model, fallbackModel) : modelConfigFromLegacy(runtime, fallbackModel),
896
+ mode: stored ? parseModeConfig(stored.mode) : { kind: "default" },
897
+ reasoningEffort: stored?.reasoningEffort ?? input.reasoningEffort ?? null,
898
+ envVars: stripControlledRuntimeEnvVars(runtime, stored ? storedEnvVars : legacyEnvVars)
899
+ };
900
+ }
901
+ function runtimeConfigModelValue(config) {
902
+ return config.model.kind === "custom" ? config.model.name : config.model.id;
903
+ }
904
+ function runtimeConfigToLaunchFields(config) {
905
+ const normalized = isPlainRecord(config) && config.version === RUNTIME_CONFIG_VERSION ? hydrateRuntimeConfig({ runtimeConfig: config }) : hydrateRuntimeConfig(config);
906
+ const generatedEnvVars = {};
907
+ if (normalized.runtime === "claude") {
908
+ if (normalized.provider?.kind === "custom") {
909
+ generatedEnvVars.ANTHROPIC_BASE_URL = normalized.provider.apiUrl;
910
+ generatedEnvVars.ANTHROPIC_API_KEY = normalized.provider.apiKey;
911
+ }
912
+ if (normalized.model.kind === "custom") {
913
+ generatedEnvVars.ANTHROPIC_CUSTOM_MODEL_OPTION = normalized.model.name;
914
+ }
915
+ }
916
+ const envVars = {
917
+ ...normalized.envVars ?? {},
918
+ ...generatedEnvVars
919
+ };
920
+ return {
921
+ runtime: normalized.runtime,
922
+ model: runtimeConfigModelValue(normalized),
923
+ mode: normalized.mode,
924
+ reasoningEffort: normalized.reasoningEffort ?? null,
925
+ envVars: Object.keys(envVars).length > 0 ? envVars : null
926
+ };
927
+ }
737
928
  var PLAN_CONFIG = {
738
929
  free: {
739
930
  displayName: "Hobby",
@@ -769,17 +960,14 @@ var DISPLAY_PLAN_CONFIG = {
769
960
  };
770
961
 
771
962
  // src/agentProcessManager.ts
772
- import { mkdirSync as mkdirSync4, readdirSync as readdirSync2, statSync as statSync2, writeFileSync as writeFileSync7 } from "fs";
963
+ import { mkdirSync as mkdirSync5, readdirSync, statSync, writeFileSync as writeFileSync8 } from "fs";
773
964
  import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
774
965
  import { createHash as createHash2 } from "crypto";
775
- import path12 from "path";
776
- import os6 from "os";
966
+ import path13 from "path";
967
+ import os5 from "os";
777
968
 
778
969
  // src/drivers/claude.ts
779
970
  import { spawn } from "child_process";
780
- import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
781
- import os2 from "os";
782
- import path4 from "path";
783
971
 
784
972
  // src/drivers/cliTransport.ts
785
973
  import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
@@ -1359,6 +1547,19 @@ function listLegacySlockStatePaths(slockHome = resolveSlockHome(), homeDir = os.
1359
1547
  return candidates.filter((candidate) => existsSync(candidate.path));
1360
1548
  }
1361
1549
 
1550
+ // src/authEnv.ts
1551
+ var DAEMON_API_KEY_ENV = "SLOCK_MACHINE_API_KEY";
1552
+ var SLOCK_AGENT_TOKEN_ENV = "SLOCK_AGENT_TOKEN";
1553
+ function scrubDaemonAuthEnv(env) {
1554
+ delete env[DAEMON_API_KEY_ENV];
1555
+ return env;
1556
+ }
1557
+ function scrubDaemonChildEnv(env) {
1558
+ delete env[DAEMON_API_KEY_ENV];
1559
+ delete env[SLOCK_AGENT_TOKEN_ENV];
1560
+ return env;
1561
+ }
1562
+
1362
1563
  // src/agentCredentialProxy.ts
1363
1564
  import { randomBytes } from "crypto";
1364
1565
  import http from "http";
@@ -1949,10 +2150,13 @@ function unregisterAgentCredentialProxyForLaunch(input) {
1949
2150
  // src/drivers/cliTransport.ts
1950
2151
  var shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
1951
2152
  var powershellSingleQuote = (value) => `'${value.replace(/'/g, "''")}'`;
1952
- var DEFAULT_ACTIVE_CAPABILITIES = "send,read,mentions,tasks,reactions,server,channels";
2153
+ var DEFAULT_ACTIVE_CAPABILITIES = "send,read,mentions,tasks,reactions,server,channels,knowledge";
2154
+ var LOOPBACK_NO_PROXY = "127.0.0.1,localhost";
1953
2155
  var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
1954
2156
  var RAW_CREDENTIAL_ENV_DENYLIST = [
1955
- "SLOCK_AGENT_CREDENTIAL_KEY"
2157
+ "SLOCK_AGENT_TOKEN",
2158
+ "SLOCK_AGENT_CREDENTIAL_KEY",
2159
+ "SLOCK_AGENT_CREDENTIAL_KEY_FILE"
1956
2160
  ];
1957
2161
  var cachedOpencliBinPath;
1958
2162
  function resolveOpencliBinPath() {
@@ -1996,6 +2200,34 @@ function windowsUtf8Env() {
1996
2200
  LC_ALL: "C.UTF-8"
1997
2201
  };
1998
2202
  }
2203
+ function posixLoopbackNoProxyPrelude() {
2204
+ return [
2205
+ `SLOCK_LOOPBACK_NO_PROXY=${shellSingleQuote(LOOPBACK_NO_PROXY)}`,
2206
+ `SLOCK_EXISTING_NO_PROXY="\${NO_PROXY:-}"`,
2207
+ `if [ -n "\${no_proxy:-}" ]; then SLOCK_EXISTING_NO_PROXY="\${SLOCK_EXISTING_NO_PROXY:+$SLOCK_EXISTING_NO_PROXY,}$no_proxy"; fi`,
2208
+ `NO_PROXY="\${SLOCK_LOOPBACK_NO_PROXY}\${SLOCK_EXISTING_NO_PROXY:+,$SLOCK_EXISTING_NO_PROXY}"`,
2209
+ `no_proxy="$NO_PROXY"`,
2210
+ "export NO_PROXY no_proxy"
2211
+ ].join("\n");
2212
+ }
2213
+ function cmdLoopbackNoProxyLines() {
2214
+ return [
2215
+ `set "SLOCK_LOOPBACK_NO_PROXY=${LOOPBACK_NO_PROXY}"`,
2216
+ `set "SLOCK_EXISTING_NO_PROXY=%NO_PROXY%"`,
2217
+ `if defined no_proxy (if defined SLOCK_EXISTING_NO_PROXY (set "SLOCK_EXISTING_NO_PROXY=%SLOCK_EXISTING_NO_PROXY%,%no_proxy%") else set "SLOCK_EXISTING_NO_PROXY=%no_proxy%")`,
2218
+ `if defined SLOCK_EXISTING_NO_PROXY (set "NO_PROXY=%SLOCK_LOOPBACK_NO_PROXY%,%SLOCK_EXISTING_NO_PROXY%") else set "NO_PROXY=%SLOCK_LOOPBACK_NO_PROXY%"`,
2219
+ `set "no_proxy=%NO_PROXY%"`
2220
+ ];
2221
+ }
2222
+ function powershellLoopbackNoProxyLines() {
2223
+ return [
2224
+ `$loopbackNoProxy = ${powershellSingleQuote(LOOPBACK_NO_PROXY)}`,
2225
+ "$existingNoProxy = @($env:NO_PROXY, $env:no_proxy) | Where-Object { $_ }",
2226
+ `if ($existingNoProxy.Count -gt 0) { $mergedNoProxy = "$loopbackNoProxy,$($existingNoProxy -join ',')" } else { $mergedNoProxy = $loopbackNoProxy }`,
2227
+ "$env:NO_PROXY = $mergedNoProxy",
2228
+ "$env:no_proxy = $mergedNoProxy"
2229
+ ];
2230
+ }
1999
2231
  function runtimeContextEnv(config) {
2000
2232
  const ctx = config.runtimeContext;
2001
2233
  if (!ctx) return {};
@@ -2045,6 +2277,7 @@ async function prepareCliTransport(ctx, extraEnv = {}, platform = process.platfo
2045
2277
  const posixWrapper = path2.join(slockDir, "slock");
2046
2278
  const posixCredentialPrefix = agentCredentialProxy ? `SLOCK_AGENT_PROXY_URL=${shellSingleQuote(agentCredentialProxy.proxyUrl)} SLOCK_AGENT_PROXY_TOKEN_FILE=${shellSingleQuote(agentCredentialProxyTokenFile)} SLOCK_AGENT_ACTIVE_CAPABILITIES=${shellSingleQuote(DEFAULT_ACTIVE_CAPABILITIES)} ` : "";
2047
2279
  const posixBody = `#!/usr/bin/env bash
2280
+ ${posixLoopbackNoProxyPrelude()}
2048
2281
  ${posixCredentialPrefix}exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)} "$@"
2049
2282
  `;
2050
2283
  writeFileSync(posixWrapper, posixBody, { mode: 493 });
@@ -2061,6 +2294,7 @@ set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
2061
2294
  "set LANG=C.UTF-8",
2062
2295
  "set LC_ALL=C.UTF-8",
2063
2296
  "chcp 65001 >NUL 2>NUL",
2297
+ ...cmdLoopbackNoProxyLines(),
2064
2298
  cmdCredentialLine.trimEnd(),
2065
2299
  `"${process.execPath}" "${ctx.slockCliPath}" %*`,
2066
2300
  ""
@@ -2081,6 +2315,7 @@ set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
2081
2315
  "$env:PYTHONUTF8 = '1'",
2082
2316
  "$env:LANG = 'C.UTF-8'",
2083
2317
  "$env:LC_ALL = 'C.UTF-8'",
2318
+ ...powershellLoopbackNoProxyLines(),
2084
2319
  ...psCredentialLines,
2085
2320
  `$node = ${powershellSingleQuote(process.execPath)}`,
2086
2321
  `$cli = ${powershellSingleQuote(ctx.slockCliPath)}`,
@@ -2120,10 +2355,11 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(opencliBinPath)} "
2120
2355
  }
2121
2356
  }
2122
2357
  const wrapperPath = platform === "win32" ? path2.join(slockDir, "slock.cmd") : posixWrapper;
2358
+ const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
2123
2359
  const spawnEnv = {
2124
2360
  ...process.env,
2125
2361
  FORCE_COLOR: "0",
2126
- ...ctx.config.envVars || {},
2362
+ ...launchRuntimeFields.envVars || {},
2127
2363
  ...extraEnv,
2128
2364
  ...platform === "win32" ? windowsUtf8Env() : {},
2129
2365
  ...runtimeContextEnv(ctx.config),
@@ -2134,7 +2370,7 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(opencliBinPath)} "
2134
2370
  ...agentCredentialProxy ? {} : { SLOCK_AGENT_TOKEN_FILE: tokenFile },
2135
2371
  PATH: `${slockDir}${path2.delimiter}${process.env.PATH ?? ""}`
2136
2372
  };
2137
- delete spawnEnv.SLOCK_AGENT_TOKEN;
2373
+ scrubDaemonChildEnv(spawnEnv);
2138
2374
  for (const key of RAW_CREDENTIAL_ENV_DENYLIST) {
2139
2375
  delete spawnEnv[key];
2140
2376
  }
@@ -2154,6 +2390,129 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(opencliBinPath)} "
2154
2390
  };
2155
2391
  }
2156
2392
 
2393
+ // src/drivers/claudeEventNormalizer.ts
2394
+ function collectResultErrorDetail(message, fallback) {
2395
+ const parts = [];
2396
+ if (Array.isArray(message.errors)) {
2397
+ for (const err of message.errors) {
2398
+ if (typeof err === "string" && err.trim()) parts.push(err.trim());
2399
+ }
2400
+ }
2401
+ if (typeof message.result === "string" && message.result.trim()) {
2402
+ parts.push(message.result.trim());
2403
+ }
2404
+ return parts.join(" | ") || fallback;
2405
+ }
2406
+ function isProviderApiFailureText(value, hasToolUse) {
2407
+ return !hasToolUse && /^\s*API Error:/i.test(value) && (/\b(?:ECONNRESET|EPIPE|ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN)\b/i.test(value) || /\bUnable to connect to API\b/i.test(value) || /\b(?:timed out|timeout)\b/i.test(value) || /\b5\d{2}\b/.test(value));
2408
+ }
2409
+ var ClaudeEventNormalizer = class {
2410
+ normalizeLine(line) {
2411
+ let event;
2412
+ try {
2413
+ event = JSON.parse(line);
2414
+ } catch {
2415
+ return [];
2416
+ }
2417
+ const events = [];
2418
+ const pushResultError = (message, fallback) => {
2419
+ events.push({ kind: "error", message: collectResultErrorDetail(message, fallback) });
2420
+ };
2421
+ switch (event.type) {
2422
+ case "system":
2423
+ if (event.subtype === "init" && event.session_id) {
2424
+ events.push({ kind: "session_init", sessionId: event.session_id });
2425
+ }
2426
+ if (event.subtype === "status" && event.status === "compacting") {
2427
+ events.push({ kind: "compaction_started" });
2428
+ }
2429
+ if (event.subtype === "status" && event.status === "requesting") {
2430
+ events.push({
2431
+ kind: "internal_progress",
2432
+ source: "claude_system_status",
2433
+ itemType: "requesting",
2434
+ payloadBytes: Buffer.byteLength(line, "utf8")
2435
+ });
2436
+ }
2437
+ if (event.subtype === "compact_boundary") {
2438
+ events.push({ kind: "compaction_finished" });
2439
+ }
2440
+ break;
2441
+ case "stream_event":
2442
+ events.push({
2443
+ kind: "internal_progress",
2444
+ source: "claude_stream_event",
2445
+ itemType: typeof event.event?.type === "string" && event.event.type.length > 0 ? event.event.type : "unknown",
2446
+ payloadBytes: Buffer.byteLength(line, "utf8")
2447
+ });
2448
+ break;
2449
+ case "assistant": {
2450
+ const content = event.message?.content;
2451
+ if (Array.isArray(content)) {
2452
+ const hasToolUse = content.some((block) => block?.type === "tool_use");
2453
+ for (const block of content) {
2454
+ if (block.type === "thinking" && block.thinking) {
2455
+ events.push({ kind: "thinking", text: block.thinking });
2456
+ } else if (block.type === "text" && block.text) {
2457
+ if (isProviderApiFailureText(block.text, hasToolUse)) {
2458
+ events.push({ kind: "error", message: block.text });
2459
+ } else {
2460
+ events.push({ kind: "text", text: block.text });
2461
+ }
2462
+ } else if (block.type === "tool_use") {
2463
+ events.push({ kind: "tool_call", name: block.name || "unknown_tool", input: block.input });
2464
+ }
2465
+ }
2466
+ }
2467
+ break;
2468
+ }
2469
+ case "user": {
2470
+ const content = event.message?.content;
2471
+ if (Array.isArray(content)) {
2472
+ for (const block of content) {
2473
+ if (block.type === "tool_result") {
2474
+ events.push({ kind: "tool_output", name: block.name || block.tool_use_id || "tool_result" });
2475
+ }
2476
+ }
2477
+ }
2478
+ break;
2479
+ }
2480
+ case "result": {
2481
+ const subtype = typeof event.subtype === "string" ? event.subtype : "success";
2482
+ const stopReason = typeof event.stop_reason === "string" ? event.stop_reason : null;
2483
+ switch (subtype) {
2484
+ case "success":
2485
+ if (event.is_error && stopReason !== "max_tokens") {
2486
+ pushResultError(event, "Execution failed");
2487
+ }
2488
+ break;
2489
+ case "error_during_execution":
2490
+ if (stopReason !== "max_tokens") {
2491
+ pushResultError(event, "Execution failed");
2492
+ }
2493
+ break;
2494
+ case "error_max_budget_usd":
2495
+ pushResultError(event, "Budget limit exceeded");
2496
+ break;
2497
+ case "error_max_turns":
2498
+ pushResultError(event, "Max turns exceeded");
2499
+ break;
2500
+ case "error_max_structured_output_retries":
2501
+ pushResultError(event, "Structured output retries exceeded");
2502
+ break;
2503
+ }
2504
+ events.push({ kind: "turn_end", sessionId: event.session_id });
2505
+ break;
2506
+ }
2507
+ }
2508
+ return events;
2509
+ }
2510
+ };
2511
+
2512
+ // src/drivers/claudeLaunch.ts
2513
+ import { writeFileSync as writeFileSync2 } from "fs";
2514
+ import path4 from "path";
2515
+
2157
2516
  // src/drivers/probe.ts
2158
2517
  import { execFileSync } from "child_process";
2159
2518
  import { existsSync as existsSync2 } from "fs";
@@ -2310,7 +2669,7 @@ function resolveCommandOnWindows(command, env, execFileSyncFn, existsSyncFn) {
2310
2669
  }
2311
2670
  function resolveCommandOnPath(command, deps = {}) {
2312
2671
  const platform = deps.platform ?? process.platform;
2313
- const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
2672
+ const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
2314
2673
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
2315
2674
  const existsSyncFn = deps.existsSyncFn ?? existsSync2;
2316
2675
  if (platform === "win32") {
@@ -2336,7 +2695,7 @@ function firstExistingPath(candidates, deps = {}) {
2336
2695
  return null;
2337
2696
  }
2338
2697
  function readCommandVersion(command, args = [], deps = {}) {
2339
- const env = withWindowsUserEnvironment(deps.env ?? process.env, deps);
2698
+ const env = scrubDaemonChildEnv({ ...withWindowsUserEnvironment(deps.env ?? process.env, deps) });
2340
2699
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
2341
2700
  try {
2342
2701
  const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
@@ -2354,12 +2713,10 @@ function resolveHomePath(relativePath, deps = {}) {
2354
2713
  return path3.join(homeDir, relativePath);
2355
2714
  }
2356
2715
 
2357
- // src/drivers/claude.ts
2716
+ // src/drivers/claudeLaunch.ts
2358
2717
  var CLAUDE_DESKTOP_CLI_RELATIVE_PATH = path4.join("Applications", "Claude Code URL Handler.app", "Contents", "MacOS", "claude");
2359
2718
  var CLAUDE_DESKTOP_CLI_SYSTEM_PATH = "/Applications/Claude Code URL Handler.app/Contents/MacOS/claude";
2360
2719
  var CLAUDE_SYSTEM_PROMPT_FILE = "claude-system-prompt.md";
2361
- var CLAUDE_MCP_CONFIG_FILE = "claude-mcp-config.json";
2362
- var SLOCK_RUNTIME_ACTIONS_MCP_SERVER_NAME = "chat";
2363
2720
  var CLAUDE_DISALLOWED_TOOLS = [
2364
2721
  "EnterPlanMode",
2365
2722
  "ExitPlanMode",
@@ -2385,81 +2742,54 @@ function probeClaude(deps = {}) {
2385
2742
  version: readCommandVersion(command, [], deps) ?? void 0
2386
2743
  };
2387
2744
  }
2388
- function isRecord(value) {
2389
- return value !== null && typeof value === "object" && !Array.isArray(value);
2390
- }
2391
- function expandClaudeMcpConfigVariables(raw, vars) {
2392
- let expanded = raw;
2393
- for (const [name, value] of Object.entries(vars)) {
2394
- const escapedValue = process.platform === "win32" ? value.replace(/\\/g, "\\\\") : value;
2395
- expanded = expanded.replaceAll(`\${${name}}`, escapedValue);
2745
+ function buildClaudeArgs(config, opts) {
2746
+ const launchRuntimeFields = runtimeConfigToLaunchFields(config);
2747
+ const args = [
2748
+ "--allow-dangerously-skip-permissions",
2749
+ "--dangerously-skip-permissions",
2750
+ "--verbose",
2751
+ "--permission-mode",
2752
+ "bypassPermissions",
2753
+ "--output-format",
2754
+ "stream-json",
2755
+ "--input-format",
2756
+ "stream-json",
2757
+ "--include-partial-messages",
2758
+ "--model",
2759
+ launchRuntimeFields.model || "sonnet",
2760
+ "--disallowed-tools",
2761
+ CLAUDE_DISALLOWED_TOOLS,
2762
+ "--append-system-prompt-file",
2763
+ opts.standingPromptFilePath
2764
+ ];
2765
+ if (launchRuntimeFields.reasoningEffort) {
2766
+ args.push("--effort", launchRuntimeFields.reasoningEffort);
2396
2767
  }
2397
- return expanded;
2398
- }
2399
- function readClaudeMcpServers(configPath, vars = {}) {
2400
- try {
2401
- const parsed = JSON.parse(
2402
- expandClaudeMcpConfigVariables(readFileSync2(configPath, "utf8"), vars)
2403
- );
2404
- if (!isRecord(parsed) || !isRecord(parsed.mcpServers)) return null;
2405
- return parsed.mcpServers;
2406
- } catch (err) {
2407
- logger.warn(
2408
- `[Claude] failed to read MCP config ${configPath}: ${err instanceof Error ? err.message : String(err)}`
2409
- );
2410
- return null;
2768
+ if (launchRuntimeFields.mode.kind === "fast") {
2769
+ args.push("--bare");
2411
2770
  }
2771
+ if (config.sessionId) {
2772
+ args.push("--resume", config.sessionId);
2773
+ }
2774
+ return args;
2412
2775
  }
2413
- function resolveClaudeConfigDir(ctx, home) {
2414
- const configured = ctx.config.envVars?.CLAUDE_CONFIG_DIR || process.env.CLAUDE_CONFIG_DIR;
2415
- return configured && path4.isAbsolute(configured) ? configured : path4.join(home, ".claude");
2776
+ function writeClaudeSystemPromptFile(standingPrompt, slockDir) {
2777
+ const systemPromptPath = path4.join(slockDir, CLAUDE_SYSTEM_PROMPT_FILE);
2778
+ writeFileSync2(systemPromptPath, standingPrompt, { mode: 384 });
2779
+ return systemPromptPath;
2416
2780
  }
2417
- function collectClaudeMcpConfigFiles(ctx, home) {
2418
- const files = [];
2419
- const pushIfFile = (candidate) => {
2420
- try {
2421
- if (existsSync3(candidate) && statSync(candidate).isFile()) {
2422
- files.push({ path: candidate });
2423
- }
2424
- } catch {
2425
- }
2781
+ function buildClaudeSpawnSpec(claudeCommand, platform = process.platform) {
2782
+ const lowerClaudeCommand = claudeCommand?.toLowerCase();
2783
+ const isBatchFile = Boolean(
2784
+ platform === "win32" && lowerClaudeCommand && (lowerClaudeCommand.endsWith(".cmd") || lowerClaudeCommand.endsWith(".bat"))
2785
+ );
2786
+ return {
2787
+ command: claudeCommand ?? "claude",
2788
+ shell: platform === "win32" && (!claudeCommand || isBatchFile)
2426
2789
  };
2427
- pushIfFile(path4.join(home, ".claude.json"));
2428
- pushIfFile(path4.join(ctx.workingDirectory, ".mcp.json"));
2429
- const pluginRoot = path4.join(resolveClaudeConfigDir(ctx, home), "plugins");
2430
- try {
2431
- for (const entry of readdirSync(pluginRoot)) {
2432
- const pluginPath = path4.join(pluginRoot, entry);
2433
- const configPath = path4.join(pluginPath, ".mcp.json");
2434
- try {
2435
- if (existsSync3(configPath) && statSync(configPath).isFile()) {
2436
- files.push({
2437
- path: configPath,
2438
- vars: { CLAUDE_PLUGIN_ROOT: pluginPath }
2439
- });
2440
- }
2441
- } catch {
2442
- }
2443
- }
2444
- } catch {
2445
- }
2446
- return files;
2447
- }
2448
- function buildClaudeUserMcpServers(ctx, home) {
2449
- const servers = /* @__PURE__ */ Object.create(null);
2450
- for (const configFile of collectClaudeMcpConfigFiles(ctx, home)) {
2451
- const mcpServers = readClaudeMcpServers(configFile.path, configFile.vars);
2452
- if (!mcpServers) continue;
2453
- for (const [name, server] of Object.entries(mcpServers)) {
2454
- if (!isRecord(server)) {
2455
- logger.warn(`[Claude] ignoring invalid MCP server "${name}" in ${configFile.path}`);
2456
- continue;
2457
- }
2458
- servers[name] = server;
2459
- }
2460
- }
2461
- return servers;
2462
2790
  }
2791
+
2792
+ // src/drivers/claude.ts
2463
2793
  var ClaudeDriver = class {
2464
2794
  id = "claude";
2465
2795
  lifecycle = {
@@ -2469,7 +2799,7 @@ var ClaudeDriver = class {
2469
2799
  };
2470
2800
  communication = {
2471
2801
  chat: "slock_cli",
2472
- runtimeControl: "mcp_runtime_actions"
2802
+ runtimeControl: "none"
2473
2803
  };
2474
2804
  session = {
2475
2805
  recovery: "resume_or_fresh"
@@ -2479,107 +2809,37 @@ var ClaudeDriver = class {
2479
2809
  toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
2480
2810
  };
2481
2811
  supportsStdinNotification = true;
2482
- mcpToolPrefix = "mcp__chat__";
2812
+ mcpToolPrefix = "";
2483
2813
  usesSlockCliForCommunication = true;
2484
2814
  // Claude Code supports same-turn steering, but raw stdin injection at an
2485
2815
  // arbitrary busy instant can collide with active signed thinking blocks. The
2486
2816
  // daemon therefore gates busy delivery on Claude stream-json boundaries.
2487
2817
  busyDeliveryMode = "gated";
2488
2818
  supportsNativeStandingPrompt = true;
2819
+ eventNormalizer = new ClaudeEventNormalizer();
2489
2820
  probe() {
2490
2821
  return probeClaude();
2491
2822
  }
2492
- buildClaudeArgs(config, standingPrompt, opts = {}) {
2493
- const args = [
2494
- "--allow-dangerously-skip-permissions",
2495
- "--dangerously-skip-permissions",
2496
- "--verbose",
2497
- "--permission-mode",
2498
- "bypassPermissions",
2499
- "--output-format",
2500
- "stream-json",
2501
- "--input-format",
2502
- "stream-json",
2503
- "--model",
2504
- config.model || "sonnet",
2505
- "--disallowed-tools",
2506
- CLAUDE_DISALLOWED_TOOLS
2507
- ];
2508
- if (opts.standingPromptFilePath) {
2509
- args.push("--append-system-prompt-file", opts.standingPromptFilePath);
2510
- } else {
2511
- args.push("--append-system-prompt", standingPrompt);
2512
- }
2513
- if (config.sessionId) {
2514
- args.push("--resume", config.sessionId);
2515
- }
2516
- return args;
2517
- }
2518
- buildRuntimeActionsMcpServer(ctx) {
2519
- const isTsSource = ctx.chatBridgePath.endsWith(".ts");
2520
- const command = isTsSource ? "npx" : "node";
2521
- const bridgeArgs = isTsSource ? ["tsx", ctx.chatBridgePath] : [ctx.chatBridgePath];
2522
- return {
2523
- command,
2524
- args: [
2525
- ...bridgeArgs,
2526
- "--agent-id",
2527
- ctx.agentId,
2528
- "--server-url",
2529
- ctx.config.serverUrl,
2530
- "--auth-token",
2531
- ctx.config.authToken || ctx.daemonApiKey,
2532
- "--runtime",
2533
- this.id,
2534
- ...ctx.launchId ? ["--launch-id", ctx.launchId] : [],
2535
- "--runtime-actions-only"
2536
- ]
2537
- };
2538
- }
2539
- buildRuntimeActionsMcpConfig(ctx, home = os2.homedir()) {
2540
- const userMcpServers = buildClaudeUserMcpServers(ctx, home);
2541
- if (Object.prototype.hasOwnProperty.call(userMcpServers, SLOCK_RUNTIME_ACTIONS_MCP_SERVER_NAME)) {
2542
- logger.warn(
2543
- `[Agent ${ctx.agentId}] Claude user MCP server "${SLOCK_RUNTIME_ACTIONS_MCP_SERVER_NAME}" is reserved by Slock runtime actions and will be ignored`
2544
- );
2545
- }
2546
- return JSON.stringify({
2547
- mcpServers: {
2548
- ...userMcpServers,
2549
- [SLOCK_RUNTIME_ACTIONS_MCP_SERVER_NAME]: this.buildRuntimeActionsMcpServer(ctx)
2550
- }
2551
- });
2552
- }
2553
- writeClaudeLaunchFiles(ctx, slockDir, home = os2.homedir()) {
2554
- const systemPromptPath = path4.join(slockDir, CLAUDE_SYSTEM_PROMPT_FILE);
2555
- const mcpConfigPath = path4.join(slockDir, CLAUDE_MCP_CONFIG_FILE);
2556
- writeFileSync2(systemPromptPath, ctx.standingPrompt, { mode: 384 });
2557
- writeFileSync2(mcpConfigPath, this.buildRuntimeActionsMcpConfig(ctx, home), { mode: 384 });
2558
- return { systemPromptPath, mcpConfigPath };
2823
+ buildClaudeArgs(config, opts) {
2824
+ return buildClaudeArgs(config, opts);
2559
2825
  }
2560
2826
  async spawn(ctx) {
2561
2827
  const { slockDir, tokenFile, spawnEnv } = await prepareCliTransport(ctx);
2562
- const { systemPromptPath, mcpConfigPath } = this.writeClaudeLaunchFiles(ctx, slockDir);
2563
- const args = this.buildClaudeArgs(ctx.config, ctx.standingPrompt, {
2828
+ const systemPromptPath = writeClaudeSystemPromptFile(ctx.standingPrompt, slockDir);
2829
+ const args = this.buildClaudeArgs(ctx.config, {
2564
2830
  standingPromptFilePath: systemPromptPath
2565
2831
  });
2566
- args.push("--mcp-config", mcpConfigPath, "--strict-mcp-config");
2567
2832
  delete spawnEnv.CLAUDECODE;
2568
2833
  logger.info(
2569
2834
  `[Agent ${ctx.agentId}] transport=cli cli=${ctx.slockCliPath} token_file=${tokenFile}`
2570
2835
  );
2571
2836
  const claudeCommand = resolveClaudeCommand();
2572
- const isWindows = process.platform === "win32";
2573
- const lowerClaudeCommand = claudeCommand?.toLowerCase();
2574
- const isBatchFile = Boolean(
2575
- isWindows && lowerClaudeCommand && (lowerClaudeCommand.endsWith(".cmd") || lowerClaudeCommand.endsWith(".bat"))
2576
- );
2577
- const useShell = isWindows && (!claudeCommand || isBatchFile);
2578
- const proc = spawn(claudeCommand ?? "claude", args, {
2837
+ const spawnSpec = buildClaudeSpawnSpec(claudeCommand);
2838
+ const proc = spawn(spawnSpec.command, args, {
2579
2839
  cwd: ctx.workingDirectory,
2580
2840
  stdio: ["pipe", "pipe", "pipe"],
2581
2841
  env: spawnEnv,
2582
- shell: useShell
2842
+ shell: spawnSpec.shell
2583
2843
  });
2584
2844
  const stdinMsg = JSON.stringify({
2585
2845
  type: "user",
@@ -2593,99 +2853,7 @@ var ClaudeDriver = class {
2593
2853
  return { process: proc };
2594
2854
  }
2595
2855
  parseLine(line) {
2596
- let event;
2597
- try {
2598
- event = JSON.parse(line);
2599
- } catch {
2600
- return [];
2601
- }
2602
- const events = [];
2603
- const pushResultError = (message, fallback) => {
2604
- const parts = [];
2605
- if (Array.isArray(message.errors)) {
2606
- for (const err of message.errors) {
2607
- if (typeof err === "string" && err.trim()) parts.push(err.trim());
2608
- }
2609
- }
2610
- if (typeof message.result === "string" && message.result.trim()) {
2611
- parts.push(message.result.trim());
2612
- }
2613
- const detail = parts.join(" | ") || fallback;
2614
- events.push({ kind: "error", message: detail });
2615
- };
2616
- const isProviderApiFailureText = (value, hasToolUse) => !hasToolUse && /^\s*API Error:/i.test(value) && (/\b(?:ECONNRESET|EPIPE|ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN)\b/i.test(value) || /\bUnable to connect to API\b/i.test(value) || /\b(?:timed out|timeout)\b/i.test(value) || /\b5\d{2}\b/.test(value));
2617
- switch (event.type) {
2618
- case "system":
2619
- if (event.subtype === "init" && event.session_id) {
2620
- events.push({ kind: "session_init", sessionId: event.session_id });
2621
- }
2622
- if (event.subtype === "status" && event.status === "compacting") {
2623
- events.push({ kind: "compaction_started" });
2624
- }
2625
- if (event.subtype === "compact_boundary") {
2626
- events.push({ kind: "compaction_finished" });
2627
- }
2628
- break;
2629
- case "assistant": {
2630
- const content = event.message?.content;
2631
- if (Array.isArray(content)) {
2632
- const hasToolUse = content.some((block) => block?.type === "tool_use");
2633
- for (const block of content) {
2634
- if (block.type === "thinking" && block.thinking) {
2635
- events.push({ kind: "thinking", text: block.thinking });
2636
- } else if (block.type === "text" && block.text) {
2637
- if (isProviderApiFailureText(block.text, hasToolUse)) {
2638
- events.push({ kind: "error", message: block.text });
2639
- } else {
2640
- events.push({ kind: "text", text: block.text });
2641
- }
2642
- } else if (block.type === "tool_use") {
2643
- events.push({ kind: "tool_call", name: block.name || "unknown_tool", input: block.input });
2644
- }
2645
- }
2646
- }
2647
- break;
2648
- }
2649
- case "user": {
2650
- const content = event.message?.content;
2651
- if (Array.isArray(content)) {
2652
- for (const block of content) {
2653
- if (block.type === "tool_result") {
2654
- events.push({ kind: "tool_output", name: block.name || block.tool_use_id || "tool_result" });
2655
- }
2656
- }
2657
- }
2658
- break;
2659
- }
2660
- case "result": {
2661
- const subtype = typeof event.subtype === "string" ? event.subtype : "success";
2662
- const stopReason = typeof event.stop_reason === "string" ? event.stop_reason : null;
2663
- switch (subtype) {
2664
- case "success":
2665
- if (event.is_error && stopReason !== "max_tokens") {
2666
- pushResultError(event, "Execution failed");
2667
- }
2668
- break;
2669
- case "error_during_execution":
2670
- if (stopReason !== "max_tokens") {
2671
- pushResultError(event, "Execution failed");
2672
- }
2673
- break;
2674
- case "error_max_budget_usd":
2675
- pushResultError(event, "Budget limit exceeded");
2676
- break;
2677
- case "error_max_turns":
2678
- pushResultError(event, "Max turns exceeded");
2679
- break;
2680
- case "error_max_structured_output_retries":
2681
- pushResultError(event, "Structured output retries exceeded");
2682
- break;
2683
- }
2684
- events.push({ kind: "turn_end", sessionId: event.session_id });
2685
- break;
2686
- }
2687
- }
2688
- return events;
2856
+ return this.eventNormalizer.normalizeLine(line);
2689
2857
  }
2690
2858
  encodeStdinMessage(text, sessionId, _opts) {
2691
2859
  return JSON.stringify({
@@ -2699,7 +2867,7 @@ var ClaudeDriver = class {
2699
2867
  }
2700
2868
  buildSystemPrompt(config, _agentId) {
2701
2869
  return buildCliTransportSystemPrompt(config, {
2702
- toolPrefix: "mcp__chat__",
2870
+ toolPrefix: "",
2703
2871
  extraCriticalRules: [],
2704
2872
  postStartupNotes: [
2705
2873
  "**Claude runtime note:** While you are busy, Slock batches inbox-count notifications instead of injecting message content. Use `slock message check` at natural breakpoints to pull the pending messages before side-effect actions that depend on current context."
@@ -2712,9 +2880,117 @@ var ClaudeDriver = class {
2712
2880
 
2713
2881
  // src/drivers/codex.ts
2714
2882
  import { spawn as spawn2, execFileSync as execFileSync2, execSync } from "child_process";
2715
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
2716
- import os3 from "os";
2883
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
2884
+ import os2 from "os";
2717
2885
  import path5 from "path";
2886
+
2887
+ // src/runtimeTurnState.ts
2888
+ var RuntimeTurnState = class {
2889
+ currentTurnId = null;
2890
+ /**
2891
+ * Post-tool window where the app-server may not yet accept stdin steering.
2892
+ * Gate busy-mode delivery until turn/completed or next progress.
2893
+ */
2894
+ steeringGateActive = false;
2895
+ reset() {
2896
+ this.currentTurnId = null;
2897
+ this.steeringGateActive = false;
2898
+ }
2899
+ get activeTurnId() {
2900
+ return this.currentTurnId;
2901
+ }
2902
+ get canSteerBusy() {
2903
+ return Boolean(this.currentTurnId && !this.steeringGateActive);
2904
+ }
2905
+ markTurnStarted(turnId) {
2906
+ if (turnId !== void 0 && turnId !== null) {
2907
+ this.currentTurnId = turnId;
2908
+ }
2909
+ this.steeringGateActive = false;
2910
+ }
2911
+ adoptTurnId(turnId) {
2912
+ this.currentTurnId = turnId;
2913
+ }
2914
+ markProgress() {
2915
+ this.steeringGateActive = false;
2916
+ }
2917
+ markToolBoundary() {
2918
+ this.steeringGateActive = true;
2919
+ }
2920
+ markTurnCompleted() {
2921
+ this.currentTurnId = null;
2922
+ this.steeringGateActive = false;
2923
+ }
2924
+ };
2925
+
2926
+ // src/drivers/codexTelemetrySidecar.ts
2927
+ function finiteNumber(value) {
2928
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2929
+ }
2930
+ function finiteString(value) {
2931
+ return typeof value === "string" && value.length > 0 ? value : void 0;
2932
+ }
2933
+ function ratio(numerator, denominator) {
2934
+ if (numerator === void 0 || denominator === void 0 || denominator <= 0) return void 0;
2935
+ return Number((numerator / denominator).toFixed(6));
2936
+ }
2937
+ function withDefined(attrs) {
2938
+ return Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== void 0));
2939
+ }
2940
+ function parseTokenUsageTelemetry(message) {
2941
+ const usage = message.params?.tokenUsage;
2942
+ const total = usage?.total;
2943
+ if (!total || typeof total !== "object") return null;
2944
+ const inputTokens = finiteNumber(total.inputTokens);
2945
+ const cachedInputTokens = finiteNumber(total.cachedInputTokens);
2946
+ const totalTokens = finiteNumber(total.totalTokens);
2947
+ const modelContextWindow = finiteNumber(usage.modelContextWindow);
2948
+ const attrs = withDefined({
2949
+ totalTokens,
2950
+ inputTokens,
2951
+ cachedInputTokens,
2952
+ outputTokens: finiteNumber(total.outputTokens),
2953
+ reasoningOutputTokens: finiteNumber(total.reasoningOutputTokens),
2954
+ modelContextWindow,
2955
+ cachedInputRatio: ratio(cachedInputTokens, inputTokens),
2956
+ contextUtilization: ratio(totalTokens, modelContextWindow)
2957
+ });
2958
+ if (Object.keys(attrs).length === 0) return null;
2959
+ return { kind: "telemetry", name: "token_usage", attrs };
2960
+ }
2961
+ function parseRateLimitTelemetry(message) {
2962
+ const rateLimits = message.params?.rateLimits;
2963
+ const primary = rateLimits?.primary;
2964
+ if (!rateLimits || typeof rateLimits !== "object" || !primary || typeof primary !== "object") return null;
2965
+ const attrs = withDefined({
2966
+ limitId: finiteString(rateLimits.limitId),
2967
+ planType: finiteString(rateLimits.planType),
2968
+ usedPercent: finiteNumber(primary.usedPercent),
2969
+ windowDurationMins: finiteNumber(primary.windowDurationMins),
2970
+ resetsAt: finiteNumber(primary.resetsAt)
2971
+ });
2972
+ if (Object.keys(attrs).length === 0) return null;
2973
+ return { kind: "telemetry", name: "rate_limits", attrs };
2974
+ }
2975
+ function parseCodexTelemetryEvent(message) {
2976
+ switch (message.method) {
2977
+ case "thread/tokenUsage/updated":
2978
+ return parseTokenUsageTelemetry(message);
2979
+ case "account/rateLimits/updated":
2980
+ return parseRateLimitTelemetry(message);
2981
+ default:
2982
+ return null;
2983
+ }
2984
+ }
2985
+
2986
+ // src/drivers/codexEventNormalizer.ts
2987
+ function parseCodexJsonRpcLine(line) {
2988
+ try {
2989
+ return JSON.parse(line);
2990
+ } catch {
2991
+ return null;
2992
+ }
2993
+ }
2718
2994
  function getCodexNotificationErrorMessage(params) {
2719
2995
  const topLevelMessage = params?.message;
2720
2996
  if (typeof topLevelMessage === "string" && topLevelMessage.trim()) {
@@ -2726,8 +3002,248 @@ function getCodexNotificationErrorMessage(params) {
2726
3002
  }
2727
3003
  return null;
2728
3004
  }
3005
+ function joinReasoningText(item) {
3006
+ const summary = Array.isArray(item.summary) ? item.summary.filter((entry) => typeof entry === "string") : [];
3007
+ const content = Array.isArray(item.content) ? item.content.filter((entry) => typeof entry === "string") : [];
3008
+ return [...summary, ...content].join("\n").trim();
3009
+ }
3010
+ function rawResponseItemProgressEvent(message) {
3011
+ if (message.method !== "rawResponseItem/completed") return null;
3012
+ const item = message.params?.item ?? message.params?.responseItem ?? message.params?.rawItem ?? message.params;
3013
+ if (!item || typeof item !== "object") return null;
3014
+ const itemType = typeof item.type === "string" ? item.type : void 0;
3015
+ let payloadBytes;
3016
+ try {
3017
+ payloadBytes = Buffer.byteLength(JSON.stringify(item), "utf8");
3018
+ } catch {
3019
+ payloadBytes = void 0;
3020
+ }
3021
+ return {
3022
+ kind: "internal_progress",
3023
+ source: "codex_raw_response_item",
3024
+ itemType,
3025
+ payloadBytes
3026
+ };
3027
+ }
3028
+ var CodexEventNormalizer = class {
3029
+ mcpToolPrefix;
3030
+ currentThreadId = null;
3031
+ sessionAnnounced = false;
3032
+ streamedAgentMessageIds = /* @__PURE__ */ new Set();
3033
+ streamedReasoningIds = /* @__PURE__ */ new Set();
3034
+ turnState = new RuntimeTurnState();
3035
+ constructor(opts) {
3036
+ this.mcpToolPrefix = opts.mcpToolPrefix;
3037
+ }
3038
+ reset(opts = {}) {
3039
+ this.currentThreadId = opts.threadId ?? null;
3040
+ this.turnState.reset();
3041
+ this.sessionAnnounced = false;
3042
+ this.streamedAgentMessageIds.clear();
3043
+ this.streamedReasoningIds.clear();
3044
+ }
3045
+ get threadId() {
3046
+ return this.currentThreadId;
3047
+ }
3048
+ get activeTurnId() {
3049
+ return this.turnState.activeTurnId;
3050
+ }
3051
+ get canSteerBusy() {
3052
+ return this.turnState.canSteerBusy;
3053
+ }
3054
+ adoptThreadId(threadId) {
3055
+ this.currentThreadId = threadId;
3056
+ }
3057
+ normalizeMessage(message) {
3058
+ const events = [];
3059
+ if (message.result) {
3060
+ const thread = message.result.thread;
3061
+ if (thread && typeof thread.id === "string") {
3062
+ return this.handleThreadReady(thread.id, events);
3063
+ }
3064
+ const turn = message.result.turn;
3065
+ if (turn && typeof turn.id === "string") {
3066
+ this.turnState.adoptTurnId(turn.id);
3067
+ return { events };
3068
+ }
3069
+ if (typeof message.result.turnId === "string") {
3070
+ this.turnState.adoptTurnId(message.result.turnId);
3071
+ return { events };
3072
+ }
3073
+ }
3074
+ if (message.error) {
3075
+ events.push({ kind: "error", message: message.error.message || "Codex app-server request failed" });
3076
+ return { events };
3077
+ }
3078
+ const telemetry = parseCodexTelemetryEvent(message);
3079
+ if (telemetry) {
3080
+ events.push(telemetry);
3081
+ return { events };
3082
+ }
3083
+ const rawProgress = rawResponseItemProgressEvent(message);
3084
+ if (rawProgress) {
3085
+ events.push(rawProgress);
3086
+ return { events };
3087
+ }
3088
+ switch (message.method) {
3089
+ case "thread/started": {
3090
+ const threadId = message.params?.thread?.id;
3091
+ if (typeof threadId === "string") {
3092
+ return this.handleThreadReady(threadId, events);
3093
+ }
3094
+ break;
3095
+ }
3096
+ case "turn/started": {
3097
+ const turnId = message.params?.turn?.id;
3098
+ this.turnState.markTurnStarted(typeof turnId === "string" ? turnId : null);
3099
+ events.push({ kind: "thinking", text: "" });
3100
+ break;
3101
+ }
3102
+ case "item/agentMessage/delta": {
3103
+ const delta = message.params?.delta;
3104
+ const itemId = message.params?.itemId;
3105
+ if (typeof itemId === "string") {
3106
+ this.streamedAgentMessageIds.add(itemId);
3107
+ }
3108
+ if (typeof delta === "string" && delta.length > 0) {
3109
+ this.turnState.markProgress();
3110
+ events.push({ kind: "text", text: delta });
3111
+ }
3112
+ break;
3113
+ }
3114
+ case "item/reasoning/summaryTextDelta":
3115
+ case "item/reasoning/textDelta": {
3116
+ const delta = message.params?.delta;
3117
+ const itemId = message.params?.itemId;
3118
+ if (typeof itemId === "string") {
3119
+ this.streamedReasoningIds.add(itemId);
3120
+ }
3121
+ if (typeof delta === "string" && delta.length > 0) {
3122
+ this.turnState.markProgress();
3123
+ events.push({ kind: "thinking", text: delta });
3124
+ }
3125
+ break;
3126
+ }
3127
+ case "item/started":
3128
+ case "item/completed": {
3129
+ const item = message.params?.item;
3130
+ if (!item || typeof item !== "object" || typeof item.type !== "string") break;
3131
+ const itemType = item.type;
3132
+ const isStarted = message.method === "item/started";
3133
+ const isCompleted = message.method === "item/completed";
3134
+ switch (itemType) {
3135
+ case "reasoning":
3136
+ if (isCompleted && typeof item.id === "string" && !this.streamedReasoningIds.has(item.id)) {
3137
+ const text = joinReasoningText(item);
3138
+ if (text) {
3139
+ this.turnState.markProgress();
3140
+ events.push({ kind: "thinking", text });
3141
+ }
3142
+ }
3143
+ if (isCompleted && typeof item.id === "string") {
3144
+ this.streamedReasoningIds.delete(item.id);
3145
+ }
3146
+ break;
3147
+ case "agentMessage":
3148
+ if (isCompleted && typeof item.id === "string" && !this.streamedAgentMessageIds.has(item.id) && typeof item.text === "string" && item.text.length > 0) {
3149
+ this.turnState.markProgress();
3150
+ events.push({ kind: "text", text: item.text });
3151
+ }
3152
+ if (isCompleted && typeof item.id === "string") {
3153
+ this.streamedAgentMessageIds.delete(item.id);
3154
+ }
3155
+ break;
3156
+ case "commandExecution":
3157
+ if (isStarted && typeof item.command === "string") {
3158
+ events.push({ kind: "tool_call", name: "shell", input: { command: item.command } });
3159
+ }
3160
+ if (isCompleted) {
3161
+ events.push({ kind: "tool_output", name: "shell" });
3162
+ this.turnState.markToolBoundary();
3163
+ }
3164
+ break;
3165
+ case "contextCompaction":
3166
+ if (isStarted) {
3167
+ events.push({ kind: "compaction_started" });
3168
+ }
3169
+ if (isCompleted) {
3170
+ events.push({ kind: "compaction_finished" });
3171
+ }
3172
+ break;
3173
+ case "fileChange":
3174
+ if (isStarted && Array.isArray(item.changes)) {
3175
+ for (const change of item.changes) {
3176
+ events.push({
3177
+ kind: "tool_call",
3178
+ name: "file_change",
3179
+ input: { path: change?.path, kind: change?.kind }
3180
+ });
3181
+ }
3182
+ }
3183
+ break;
3184
+ case "mcpToolCall":
3185
+ if (isStarted) {
3186
+ const toolName = item.server === "chat" ? `${this.mcpToolPrefix}${item.tool}` : `${this.mcpToolPrefix.replace(/_$/, "")}_${item.server}_${item.tool}`;
3187
+ events.push({ kind: "tool_call", name: toolName, input: item.arguments });
3188
+ }
3189
+ if (isCompleted) {
3190
+ const toolName = item.server === "chat" ? `${this.mcpToolPrefix}${item.tool}` : `${this.mcpToolPrefix.replace(/_$/, "")}_${item.server}_${item.tool}`;
3191
+ events.push({ kind: "tool_output", name: toolName });
3192
+ this.turnState.markToolBoundary();
3193
+ }
3194
+ break;
3195
+ case "collabAgentToolCall":
3196
+ if (isStarted) {
3197
+ events.push({ kind: "tool_call", name: "collab_tool_call", input: { tool: item.tool, prompt: item.prompt } });
3198
+ }
3199
+ if (isCompleted) {
3200
+ this.turnState.markToolBoundary();
3201
+ }
3202
+ break;
3203
+ case "webSearch":
3204
+ if (isStarted) {
3205
+ events.push({ kind: "tool_call", name: "web_search", input: { query: item.query } });
3206
+ }
3207
+ if (isCompleted) {
3208
+ this.turnState.markToolBoundary();
3209
+ }
3210
+ break;
3211
+ }
3212
+ break;
3213
+ }
3214
+ case "turn/completed": {
3215
+ const turn = message.params?.turn;
3216
+ if (turn?.status === "failed" && turn?.error?.message) {
3217
+ events.push({ kind: "error", message: turn.error.message });
3218
+ }
3219
+ this.turnState.markTurnCompleted();
3220
+ this.streamedAgentMessageIds.clear();
3221
+ this.streamedReasoningIds.clear();
3222
+ events.push({ kind: "turn_end", sessionId: this.currentThreadId || void 0 });
3223
+ break;
3224
+ }
3225
+ case "error":
3226
+ events.push({
3227
+ kind: "error",
3228
+ message: getCodexNotificationErrorMessage(message.params) || "Unknown Codex app-server error"
3229
+ });
3230
+ break;
3231
+ }
3232
+ return { events };
3233
+ }
3234
+ handleThreadReady(threadId, events) {
3235
+ this.currentThreadId = threadId;
3236
+ if (!this.sessionAnnounced) {
3237
+ events.push({ kind: "session_init", sessionId: threadId });
3238
+ this.sessionAnnounced = true;
3239
+ }
3240
+ return { events, threadReady: threadId };
3241
+ }
3242
+ };
3243
+
3244
+ // src/drivers/codex.ts
2729
3245
  function ensureGitRepoForCodex(workingDirectory, deps = {}) {
2730
- const existsSyncFn = deps.existsSyncFn ?? existsSync4;
3246
+ const existsSyncFn = deps.existsSyncFn ?? existsSync3;
2731
3247
  const execSyncFn = deps.execSyncFn ?? execSync;
2732
3248
  const gitDir = path5.join(workingDirectory, ".git");
2733
3249
  if (existsSyncFn(gitDir)) return;
@@ -2745,7 +3261,7 @@ function isWindowsSandboxRunner(commandPath) {
2745
3261
  return path5.basename(commandPath).toLowerCase().startsWith("codex-command-runner");
2746
3262
  }
2747
3263
  function resolveWindowsNpmCodexEntry(deps = {}) {
2748
- const existsSyncFn = deps.existsSyncFn ?? existsSync4;
3264
+ const existsSyncFn = deps.existsSyncFn ?? existsSync3;
2749
3265
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync2;
2750
3266
  const env = deps.env ?? process.env;
2751
3267
  const winPath = path5.win32;
@@ -2767,7 +3283,7 @@ function resolveWindowsNpmCodexEntry(deps = {}) {
2767
3283
  return null;
2768
3284
  }
2769
3285
  function resolveWindowsCodexDesktopEntry(deps = {}) {
2770
- const existsSyncFn = deps.existsSyncFn ?? existsSync4;
3286
+ const existsSyncFn = deps.existsSyncFn ?? existsSync3;
2771
3287
  const env = deps.env ?? process.env;
2772
3288
  const homeDir = deps.homeDir;
2773
3289
  const winPath = path5.win32;
@@ -2839,11 +3355,6 @@ function resolveCodexSpawn(commandArgs, deps = {}) {
2839
3355
  "Cannot resolve Codex CLI entry point on Windows. Install Codex Desktop or install @openai/codex globally via npm (npm i -g @openai/codex). Ignoring .codex/.sandbox-bin/codex-command-runner because it is a sandbox helper, not the Codex CLI."
2840
3356
  );
2841
3357
  }
2842
- function joinReasoningText(item) {
2843
- const summary = Array.isArray(item.summary) ? item.summary.filter((entry) => typeof entry === "string") : [];
2844
- const content = Array.isArray(item.content) ? item.content.filter((entry) => typeof entry === "string") : [];
2845
- return [...summary, ...content].join("\n").trim();
2846
- }
2847
3358
  var CodexDriver = class {
2848
3359
  id = "codex";
2849
3360
  lifecycle = {
@@ -2919,13 +3430,20 @@ var CodexDriver = class {
2919
3430
  cwd: ctx.workingDirectory,
2920
3431
  approvalPolicy: "never",
2921
3432
  sandbox: "danger-full-access",
2922
- developerInstructions: ctx.standingPrompt
3433
+ developerInstructions: ctx.standingPrompt,
3434
+ // Raw response items are used only as payload-free liveness signals in
3435
+ // the daemon. They replace the previous transcript-mtime heuristic.
3436
+ experimentalRawEvents: true
2923
3437
  };
2924
- if (ctx.config.model) {
2925
- threadParams.model = ctx.config.model;
3438
+ const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
3439
+ if (launchRuntimeFields.model) {
3440
+ threadParams.model = launchRuntimeFields.model;
2926
3441
  }
2927
- if (ctx.config.reasoningEffort) {
2928
- threadParams.config = { model_reasoning_effort: ctx.config.reasoningEffort };
3442
+ if (launchRuntimeFields.reasoningEffort) {
3443
+ threadParams.config = { model_reasoning_effort: launchRuntimeFields.reasoningEffort };
3444
+ }
3445
+ if (launchRuntimeFields.mode.kind === "fast") {
3446
+ threadParams.serviceTier = "priority";
2929
3447
  }
2930
3448
  if (ctx.config.sessionId) {
2931
3449
  return {
@@ -2943,35 +3461,21 @@ var CodexDriver = class {
2943
3461
  }
2944
3462
  process = null;
2945
3463
  requestId = 0;
2946
- threadId = null;
2947
- activeTurnId = null;
2948
3464
  pendingInitialPrompt = null;
2949
3465
  initializeRequestId = null;
2950
3466
  pendingThreadRequest = null;
2951
3467
  initialTurnStarted = false;
2952
- sessionAnnounced = false;
2953
- streamedAgentMessageIds = /* @__PURE__ */ new Set();
2954
- streamedReasoningIds = /* @__PURE__ */ new Set();
2955
- /**
2956
- * Post-tool window where the app-server may not yet accept stdin steering.
2957
- * Gate busy-mode delivery until turn/completed or next progress.
2958
- */
2959
- steeringGateActive = false;
3468
+ normalizer = new CodexEventNormalizer({ mcpToolPrefix: this.mcpToolPrefix });
2960
3469
  async spawn(ctx) {
2961
3470
  ensureGitRepoForCodex(ctx.workingDirectory);
2962
3471
  const { spawnEnv } = await prepareCliTransport(ctx, { NO_COLOR: "1" });
2963
3472
  this.process = null;
2964
3473
  this.requestId = 0;
2965
- this.threadId = ctx.config.sessionId || null;
2966
- this.activeTurnId = null;
2967
3474
  this.pendingInitialPrompt = ctx.prompt;
2968
3475
  this.initializeRequestId = null;
2969
3476
  this.pendingThreadRequest = null;
2970
3477
  this.initialTurnStarted = false;
2971
- this.sessionAnnounced = false;
2972
- this.streamedAgentMessageIds.clear();
2973
- this.streamedReasoningIds.clear();
2974
- this.steeringGateActive = false;
3478
+ this.normalizer.reset({ threadId: ctx.config.sessionId || null });
2975
3479
  const args = ["app-server", "--listen", "stdio://"];
2976
3480
  args.push(...this.buildRuntimeActionsConfigArgs(ctx));
2977
3481
  const { command, args: spawnArgs } = resolveCodexSpawn(args);
@@ -2992,10 +3496,8 @@ var CodexDriver = class {
2992
3496
  return { process: proc };
2993
3497
  }
2994
3498
  parseLine(line) {
2995
- let message;
2996
- try {
2997
- message = JSON.parse(line);
2998
- } catch {
3499
+ const message = parseCodexJsonRpcLine(line);
3500
+ if (!message) {
2999
3501
  return [];
3000
3502
  }
3001
3503
  const events = [];
@@ -3009,194 +3511,34 @@ var CodexDriver = class {
3009
3511
  }
3010
3512
  return events;
3011
3513
  }
3012
- const thread = message.result.thread;
3013
- if (thread && typeof thread.id === "string") {
3014
- this.handleThreadReady(thread.id, events);
3015
- return events;
3016
- }
3017
- const turn = message.result.turn;
3018
- if (turn && typeof turn.id === "string") {
3019
- this.activeTurnId = turn.id;
3020
- return events;
3021
- }
3022
- if (typeof message.result.turnId === "string") {
3023
- this.activeTurnId = message.result.turnId;
3024
- return events;
3025
- }
3026
3514
  }
3027
- if (message.error) {
3028
- if (message.id === this.initializeRequestId) {
3029
- this.initializeRequestId = null;
3030
- this.pendingThreadRequest = null;
3031
- }
3515
+ if (message.error && message.id === this.initializeRequestId) {
3516
+ this.initializeRequestId = null;
3517
+ this.pendingThreadRequest = null;
3032
3518
  events.push({ kind: "error", message: message.error.message || "Codex app-server request failed" });
3033
3519
  return events;
3034
3520
  }
3035
- switch (message.method) {
3036
- case "thread/started": {
3037
- const threadId = message.params?.thread?.id;
3038
- if (typeof threadId === "string") {
3039
- this.handleThreadReady(threadId, events);
3040
- }
3041
- break;
3042
- }
3043
- case "turn/started": {
3044
- const turnId = message.params?.turn?.id;
3045
- if (typeof turnId === "string") {
3046
- this.activeTurnId = turnId;
3047
- }
3048
- this.steeringGateActive = false;
3049
- events.push({ kind: "thinking", text: "" });
3050
- break;
3051
- }
3052
- case "item/agentMessage/delta": {
3053
- const delta = message.params?.delta;
3054
- const itemId = message.params?.itemId;
3055
- if (typeof itemId === "string") {
3056
- this.streamedAgentMessageIds.add(itemId);
3057
- }
3058
- if (typeof delta === "string" && delta.length > 0) {
3059
- this.steeringGateActive = false;
3060
- events.push({ kind: "text", text: delta });
3061
- }
3062
- break;
3063
- }
3064
- case "item/reasoning/summaryTextDelta":
3065
- case "item/reasoning/textDelta": {
3066
- const delta = message.params?.delta;
3067
- const itemId = message.params?.itemId;
3068
- if (typeof itemId === "string") {
3069
- this.streamedReasoningIds.add(itemId);
3070
- }
3071
- if (typeof delta === "string" && delta.length > 0) {
3072
- this.steeringGateActive = false;
3073
- events.push({ kind: "thinking", text: delta });
3074
- }
3075
- break;
3076
- }
3077
- case "item/started":
3078
- case "item/completed": {
3079
- const item = message.params?.item;
3080
- if (!item || typeof item !== "object" || typeof item.type !== "string") break;
3081
- const itemType = item.type;
3082
- const isStarted = message.method === "item/started";
3083
- const isCompleted = message.method === "item/completed";
3084
- switch (itemType) {
3085
- case "reasoning":
3086
- if (isCompleted && typeof item.id === "string" && !this.streamedReasoningIds.has(item.id)) {
3087
- const text = joinReasoningText(item);
3088
- if (text) {
3089
- this.steeringGateActive = false;
3090
- events.push({ kind: "thinking", text });
3091
- }
3092
- }
3093
- if (isCompleted && typeof item.id === "string") {
3094
- this.streamedReasoningIds.delete(item.id);
3095
- }
3096
- break;
3097
- case "agentMessage":
3098
- if (isCompleted && typeof item.id === "string" && !this.streamedAgentMessageIds.has(item.id) && typeof item.text === "string" && item.text.length > 0) {
3099
- this.steeringGateActive = false;
3100
- events.push({ kind: "text", text: item.text });
3101
- }
3102
- if (isCompleted && typeof item.id === "string") {
3103
- this.streamedAgentMessageIds.delete(item.id);
3104
- }
3105
- break;
3106
- case "commandExecution":
3107
- if (isStarted && typeof item.command === "string") {
3108
- events.push({ kind: "tool_call", name: "shell", input: { command: item.command } });
3109
- }
3110
- if (isCompleted) {
3111
- events.push({ kind: "tool_output", name: "shell" });
3112
- this.steeringGateActive = true;
3113
- }
3114
- break;
3115
- case "contextCompaction":
3116
- if (isStarted) {
3117
- events.push({ kind: "compaction_started" });
3118
- }
3119
- if (isCompleted) {
3120
- events.push({ kind: "compaction_finished" });
3121
- }
3122
- break;
3123
- case "fileChange":
3124
- if (isStarted && Array.isArray(item.changes)) {
3125
- for (const change of item.changes) {
3126
- events.push({
3127
- kind: "tool_call",
3128
- name: "file_change",
3129
- input: { path: change?.path, kind: change?.kind }
3130
- });
3131
- }
3132
- }
3133
- break;
3134
- case "mcpToolCall":
3135
- if (isStarted) {
3136
- const toolName = item.server === "chat" ? `${this.mcpToolPrefix}${item.tool}` : `${this.mcpToolPrefix.replace(/_$/, "")}_${item.server}_${item.tool}`;
3137
- events.push({ kind: "tool_call", name: toolName, input: item.arguments });
3138
- }
3139
- if (isCompleted) {
3140
- const toolName = item.server === "chat" ? `${this.mcpToolPrefix}${item.tool}` : `${this.mcpToolPrefix.replace(/_$/, "")}_${item.server}_${item.tool}`;
3141
- events.push({ kind: "tool_output", name: toolName });
3142
- this.steeringGateActive = true;
3143
- }
3144
- break;
3145
- case "collabAgentToolCall":
3146
- if (isStarted) {
3147
- events.push({ kind: "tool_call", name: "collab_tool_call", input: { tool: item.tool, prompt: item.prompt } });
3148
- }
3149
- if (isCompleted) {
3150
- this.steeringGateActive = true;
3151
- }
3152
- break;
3153
- case "webSearch":
3154
- if (isStarted) {
3155
- events.push({ kind: "tool_call", name: "web_search", input: { query: item.query } });
3156
- }
3157
- if (isCompleted) {
3158
- this.steeringGateActive = true;
3159
- }
3160
- break;
3161
- }
3162
- break;
3163
- }
3164
- case "turn/completed": {
3165
- const turn = message.params?.turn;
3166
- if (turn?.status === "failed" && turn?.error?.message) {
3167
- events.push({ kind: "error", message: turn.error.message });
3168
- }
3169
- this.activeTurnId = null;
3170
- this.streamedAgentMessageIds.clear();
3171
- this.streamedReasoningIds.clear();
3172
- this.steeringGateActive = false;
3173
- events.push({ kind: "turn_end", sessionId: this.threadId || void 0 });
3174
- break;
3175
- }
3176
- case "error":
3177
- events.push({
3178
- kind: "error",
3179
- message: getCodexNotificationErrorMessage(message.params) || "Unknown Codex app-server error"
3180
- });
3181
- break;
3521
+ const result = this.normalizer.normalizeMessage(message);
3522
+ if (result.threadReady) {
3523
+ this.startInitialTurn();
3182
3524
  }
3183
- return events;
3525
+ return result.events;
3184
3526
  }
3185
3527
  encodeStdinMessage(text, sessionId, opts) {
3186
- if (!this.threadId && sessionId) {
3187
- this.threadId = sessionId;
3528
+ if (!this.normalizer.threadId && sessionId) {
3529
+ this.normalizer.adoptThreadId(sessionId);
3188
3530
  }
3189
- if (!this.threadId) return null;
3531
+ if (!this.normalizer.threadId) return null;
3190
3532
  const mode = opts?.mode || "busy";
3191
3533
  if (mode === "busy") {
3192
- if (!this.activeTurnId || this.steeringGateActive) return null;
3534
+ if (!this.normalizer.canSteerBusy) return null;
3193
3535
  return JSON.stringify({
3194
3536
  jsonrpc: "2.0",
3195
3537
  id: this.nextRequestId(),
3196
3538
  method: "turn/steer",
3197
3539
  params: {
3198
- threadId: this.threadId,
3199
- expectedTurnId: this.activeTurnId,
3540
+ threadId: this.normalizer.threadId,
3541
+ expectedTurnId: this.normalizer.activeTurnId,
3200
3542
  input: [{ type: "text", text }]
3201
3543
  }
3202
3544
  });
@@ -3206,7 +3548,7 @@ var CodexDriver = class {
3206
3548
  id: this.nextRequestId(),
3207
3549
  method: "turn/start",
3208
3550
  params: {
3209
- threadId: this.threadId,
3551
+ threadId: this.normalizer.threadId,
3210
3552
  input: [{ type: "text", text }]
3211
3553
  }
3212
3554
  });
@@ -3226,21 +3568,13 @@ var CodexDriver = class {
3226
3568
  this.requestId += 1;
3227
3569
  return this.requestId;
3228
3570
  }
3229
- handleThreadReady(threadId, events) {
3230
- this.threadId = threadId;
3231
- if (!this.sessionAnnounced) {
3232
- events.push({ kind: "session_init", sessionId: threadId });
3233
- this.sessionAnnounced = true;
3234
- }
3235
- this.startInitialTurn();
3236
- }
3237
3571
  startInitialTurn() {
3238
- if (this.initialTurnStarted || !this.pendingInitialPrompt || !this.threadId) return;
3572
+ if (this.initialTurnStarted || !this.pendingInitialPrompt || !this.normalizer.threadId) return;
3239
3573
  this.initialTurnStarted = true;
3240
3574
  const prompt = this.pendingInitialPrompt;
3241
3575
  this.pendingInitialPrompt = null;
3242
3576
  this.sendRequest("turn/start", {
3243
- threadId: this.threadId,
3577
+ threadId: this.normalizer.threadId,
3244
3578
  input: [{ type: "text", text: prompt }]
3245
3579
  });
3246
3580
  }
@@ -3265,12 +3599,12 @@ var CodexDriver = class {
3265
3599
  return detectCodexModels();
3266
3600
  }
3267
3601
  };
3268
- function detectCodexModels(home = os3.homedir()) {
3602
+ function detectCodexModels(home = os2.homedir()) {
3269
3603
  const cachePath = path5.join(home, ".codex", "models_cache.json");
3270
3604
  const configPath = path5.join(home, ".codex", "config.toml");
3271
3605
  let models = [];
3272
3606
  try {
3273
- const raw = readFileSync3(cachePath, "utf8");
3607
+ const raw = readFileSync2(cachePath, "utf8");
3274
3608
  const parsed = JSON.parse(raw);
3275
3609
  const entries = Array.isArray(parsed?.models) ? parsed.models : [];
3276
3610
  for (const entry of entries) {
@@ -3287,7 +3621,7 @@ function detectCodexModels(home = os3.homedir()) {
3287
3621
  if (models.length === 0) return null;
3288
3622
  let defaultModel;
3289
3623
  try {
3290
- const raw = readFileSync3(configPath, "utf8");
3624
+ const raw = readFileSync2(configPath, "utf8");
3291
3625
  const match = raw.match(/^\s*model\s*=\s*"([^"]+)"/m);
3292
3626
  if (match) defaultModel = match[1];
3293
3627
  } catch {
@@ -3318,7 +3652,7 @@ function buildAntigravityArgs(ctx) {
3318
3652
  const args = [
3319
3653
  "--print",
3320
3654
  "--print-timeout",
3321
- ctx.config.envVars?.ANTIGRAVITY_PRINT_TIMEOUT || DEFAULT_PRINT_TIMEOUT,
3655
+ runtimeConfigToLaunchFields(ctx.config).envVars?.ANTIGRAVITY_PRINT_TIMEOUT || DEFAULT_PRINT_TIMEOUT,
3322
3656
  "--dangerously-skip-permissions"
3323
3657
  ];
3324
3658
  if (ctx.config.sessionId) {
@@ -3497,11 +3831,12 @@ var CopilotDriver = class {
3497
3831
  "-p",
3498
3832
  ctx.prompt
3499
3833
  ];
3500
- if (ctx.config.model && ctx.config.model !== "default") {
3501
- args.push("--model", ctx.config.model);
3834
+ const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
3835
+ if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
3836
+ args.push("--model", launchRuntimeFields.model);
3502
3837
  }
3503
- if (ctx.config.reasoningEffort) {
3504
- args.push("--effort", ctx.config.reasoningEffort);
3838
+ if (launchRuntimeFields.reasoningEffort) {
3839
+ args.push("--effort", launchRuntimeFields.reasoningEffort);
3505
3840
  }
3506
3841
  if (ctx.config.sessionId) {
3507
3842
  args.push(`--resume=${ctx.config.sessionId}`);
@@ -3601,7 +3936,7 @@ var CopilotDriver = class {
3601
3936
 
3602
3937
  // src/drivers/cursor.ts
3603
3938
  import { spawn as spawn5, spawnSync } from "child_process";
3604
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync5 } from "fs";
3939
+ import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
3605
3940
  import path7 from "path";
3606
3941
  async function buildCursorSpawnEnv(ctx, deps = {}) {
3607
3942
  const { spawnEnv } = await prepareCliTransport(ctx, { NO_COLOR: "1" });
@@ -3657,7 +3992,7 @@ var CursorDriver = class {
3657
3992
  }
3658
3993
  async spawn(ctx) {
3659
3994
  const cursorDir = path7.join(ctx.workingDirectory, ".cursor");
3660
- if (!existsSync5(cursorDir)) {
3995
+ if (!existsSync4(cursorDir)) {
3661
3996
  mkdirSync2(cursorDir, { recursive: true });
3662
3997
  }
3663
3998
  const mcpConfigPath = path7.join(cursorDir, "mcp.json");
@@ -3670,8 +4005,9 @@ var CursorDriver = class {
3670
4005
  "--approve-mcps",
3671
4006
  "--trust"
3672
4007
  ];
3673
- if (ctx.config.model && ctx.config.model !== "default") {
3674
- args.push("--model", ctx.config.model);
4008
+ const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
4009
+ if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
4010
+ args.push("--model", launchRuntimeFields.model);
3675
4011
  }
3676
4012
  if (ctx.config.sessionId) {
3677
4013
  args.push("--resume", ctx.config.sessionId);
@@ -3790,11 +4126,11 @@ function detectCursorModels(runCommand = runCursorModelsCommand) {
3790
4126
  return parseCursorModelsOutput(String(result.stdout || ""));
3791
4127
  }
3792
4128
  function buildCursorModelProbeEnv(deps = {}) {
3793
- return withWindowsUserEnvironment({
4129
+ return scrubDaemonChildEnv(withWindowsUserEnvironment({
3794
4130
  ...deps.env ?? process.env,
3795
4131
  FORCE_COLOR: "0",
3796
4132
  NO_COLOR: "1"
3797
- }, deps);
4133
+ }, deps));
3798
4134
  }
3799
4135
  function runCursorModelsCommand() {
3800
4136
  return spawnSync("cursor-agent", ["models"], {
@@ -3806,14 +4142,15 @@ function runCursorModelsCommand() {
3806
4142
 
3807
4143
  // src/drivers/gemini.ts
3808
4144
  import { execFileSync as execFileSync3, spawn as spawn6 } from "child_process";
3809
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
4145
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
3810
4146
  import path8 from "path";
3811
4147
  async function buildGeminiSpawnEnv(ctx, platform = process.platform) {
3812
4148
  const { spawnEnv } = await prepareCliTransport(ctx, { NO_COLOR: "1" }, platform);
3813
- if (!Object.prototype.hasOwnProperty.call(ctx.config.envVars ?? {}, "GEMINI_CLI_TRUST_WORKSPACE")) {
4149
+ const launchEnvVars = runtimeConfigToLaunchFields(ctx.config).envVars;
4150
+ if (!Object.prototype.hasOwnProperty.call(launchEnvVars ?? {}, "GEMINI_CLI_TRUST_WORKSPACE")) {
3814
4151
  spawnEnv.GEMINI_CLI_TRUST_WORKSPACE = "true";
3815
4152
  }
3816
- if (platform === "win32" && !Object.prototype.hasOwnProperty.call(ctx.config.envVars ?? {}, "GEMINI_PTY_INFO")) {
4153
+ if (platform === "win32" && !Object.prototype.hasOwnProperty.call(launchEnvVars ?? {}, "GEMINI_PTY_INFO")) {
3817
4154
  spawnEnv.GEMINI_PTY_INFO = "child_process";
3818
4155
  }
3819
4156
  return spawnEnv;
@@ -3833,8 +4170,9 @@ function buildGeminiArgs(config) {
3833
4170
  "-p",
3834
4171
  ""
3835
4172
  ];
3836
- if (config.model && config.model !== "default") {
3837
- args.push("--model", config.model);
4173
+ const launchRuntimeFields = runtimeConfigToLaunchFields(config);
4174
+ if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
4175
+ args.push("--model", launchRuntimeFields.model);
3838
4176
  }
3839
4177
  if (config.sessionId) {
3840
4178
  args.push("--resume", config.sessionId);
@@ -3847,8 +4185,8 @@ function resolveGeminiSpawn(commandArgs, deps = {}) {
3847
4185
  return { command: resolveCommandOnPath("gemini", deps) ?? "gemini", args: commandArgs };
3848
4186
  }
3849
4187
  const execFileSyncFn = deps.execFileSyncFn ?? execFileSync3;
3850
- const existsSyncFn = deps.existsSyncFn ?? existsSync6;
3851
- const env = deps.env ?? process.env;
4188
+ const existsSyncFn = deps.existsSyncFn ?? existsSync5;
4189
+ const env = scrubDaemonChildEnv({ ...deps.env ?? process.env });
3852
4190
  const winPath = path8.win32;
3853
4191
  let geminiEntry = null;
3854
4192
  try {
@@ -4020,13 +4358,16 @@ var GeminiDriver = class {
4020
4358
  // src/drivers/kimi.ts
4021
4359
  import { randomUUID as randomUUID2 } from "crypto";
4022
4360
  import { spawn as spawn7 } from "child_process";
4023
- import { existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "fs";
4024
- import os4 from "os";
4361
+ import { chmodSync, existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
4362
+ import os3 from "os";
4025
4363
  import path9 from "path";
4026
4364
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
4027
4365
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
4028
4366
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
4029
4367
  var KIMI_MCP_FILE = ".slock-kimi-mcp.json";
4368
+ var KIMI_GENERATED_CONFIG_FILE = ".slock-kimi-config.toml";
4369
+ var SLOCK_KIMI_CONFIG_CONTENT_ENV = "SLOCK_KIMI_CONFIG_CONTENT";
4370
+ var SLOCK_KIMI_CONFIG_FILE_ENV = "SLOCK_KIMI_CONFIG_FILE";
4030
4371
  function parseToolArguments(raw) {
4031
4372
  if (typeof raw !== "string") return raw;
4032
4373
  try {
@@ -4035,6 +4376,73 @@ function parseToolArguments(raw) {
4035
4376
  return raw;
4036
4377
  }
4037
4378
  }
4379
+ function readKimiConfigSource(home = os3.homedir(), env = process.env) {
4380
+ const inlineConfig = env[SLOCK_KIMI_CONFIG_CONTENT_ENV];
4381
+ if (inlineConfig && inlineConfig.trim()) {
4382
+ return {
4383
+ raw: inlineConfig,
4384
+ explicitPath: null,
4385
+ sourcePath: SLOCK_KIMI_CONFIG_CONTENT_ENV
4386
+ };
4387
+ }
4388
+ const explicitPath = env[SLOCK_KIMI_CONFIG_FILE_ENV];
4389
+ const configPath = explicitPath && explicitPath.trim() ? explicitPath : path9.join(home, ".kimi", "config.toml");
4390
+ try {
4391
+ return {
4392
+ raw: readFileSync3(configPath, "utf8"),
4393
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
4394
+ sourcePath: configPath
4395
+ };
4396
+ } catch {
4397
+ return {
4398
+ raw: null,
4399
+ explicitPath: explicitPath && explicitPath.trim() ? explicitPath : null,
4400
+ sourcePath: configPath
4401
+ };
4402
+ }
4403
+ }
4404
+ function buildKimiSpawnEnv(env = process.env) {
4405
+ const spawnEnv = { ...env, FORCE_COLOR: "0", NO_COLOR: "1" };
4406
+ delete spawnEnv[SLOCK_KIMI_CONFIG_CONTENT_ENV];
4407
+ delete spawnEnv[SLOCK_KIMI_CONFIG_FILE_ENV];
4408
+ return scrubDaemonChildEnv(spawnEnv);
4409
+ }
4410
+ function buildKimiEffectiveEnv(ctx, overrideEnv) {
4411
+ return {
4412
+ ...process.env,
4413
+ ...ctx.config.envVars || {},
4414
+ ...overrideEnv || {}
4415
+ };
4416
+ }
4417
+ function buildKimiLaunchOptions(ctx, opts = {}) {
4418
+ const env = buildKimiEffectiveEnv(ctx, opts.env);
4419
+ const source = readKimiConfigSource(opts.home ?? os3.homedir(), env);
4420
+ const args = [];
4421
+ let configFilePath = null;
4422
+ let configContent = null;
4423
+ if (source.explicitPath) {
4424
+ configFilePath = source.explicitPath;
4425
+ } else if (source.raw !== null && source.sourcePath === SLOCK_KIMI_CONFIG_CONTENT_ENV) {
4426
+ configFilePath = path9.join(ctx.workingDirectory, KIMI_GENERATED_CONFIG_FILE);
4427
+ configContent = source.raw;
4428
+ if (opts.writeGeneratedConfig !== false) {
4429
+ writeFileSync6(configFilePath, source.raw, { encoding: "utf8", mode: 384 });
4430
+ chmodSync(configFilePath, 384);
4431
+ }
4432
+ }
4433
+ if (configFilePath) {
4434
+ args.push("--config-file", configFilePath);
4435
+ }
4436
+ if (ctx.config.model && ctx.config.model !== "default") {
4437
+ args.push("--model", ctx.config.model);
4438
+ }
4439
+ return {
4440
+ args,
4441
+ env: buildKimiSpawnEnv(env),
4442
+ configFilePath,
4443
+ configContent
4444
+ };
4445
+ }
4038
4446
  function resolveKimiSpawn(commandArgs, deps = {}) {
4039
4447
  return {
4040
4448
  command: resolveCommandOnPath("kimi", deps) ?? "kimi",
@@ -4058,7 +4466,25 @@ var KimiDriver = class {
4058
4466
  };
4059
4467
  model = {
4060
4468
  detectedModelsVerifiedAs: "launchable",
4061
- toLaunchSpec: (modelId) => ({ args: ["--model", modelId] })
4469
+ toLaunchSpec: (modelId, ctx, opts) => {
4470
+ if (!ctx) return { args: ["--model", modelId] };
4471
+ const launchCtx = {
4472
+ ...ctx,
4473
+ config: {
4474
+ ...ctx.config,
4475
+ model: modelId
4476
+ }
4477
+ };
4478
+ const launch = buildKimiLaunchOptions(launchCtx, {
4479
+ home: opts?.home,
4480
+ writeGeneratedConfig: false
4481
+ });
4482
+ return {
4483
+ args: launch.args,
4484
+ env: launch.env,
4485
+ configFiles: launch.configFilePath ? [launch.configFilePath] : void 0
4486
+ };
4487
+ }
4062
4488
  };
4063
4489
  supportsStdinNotification = true;
4064
4490
  mcpToolPrefix = "";
@@ -4094,7 +4520,7 @@ var KimiDriver = class {
4094
4520
  const systemPromptPath = path9.join(ctx.workingDirectory, KIMI_SYSTEM_PROMPT_FILE);
4095
4521
  const agentFilePath = path9.join(ctx.workingDirectory, KIMI_AGENT_FILE);
4096
4522
  const mcpConfigPath = path9.join(ctx.workingDirectory, KIMI_MCP_FILE);
4097
- if (!isResume || !existsSync7(systemPromptPath)) {
4523
+ if (!isResume || !existsSync6(systemPromptPath)) {
4098
4524
  writeFileSync6(systemPromptPath, ctx.prompt, "utf8");
4099
4525
  }
4100
4526
  writeFileSync6(agentFilePath, [
@@ -4112,6 +4538,7 @@ var KimiDriver = class {
4112
4538
  }
4113
4539
  }
4114
4540
  }), "utf8");
4541
+ const launch = buildKimiLaunchOptions(ctx);
4115
4542
  const args = [
4116
4543
  "--wire",
4117
4544
  "--yolo",
@@ -4120,14 +4547,16 @@ var KimiDriver = class {
4120
4547
  "--mcp-config-file",
4121
4548
  mcpConfigPath,
4122
4549
  "--session",
4123
- this.sessionId
4550
+ this.sessionId,
4551
+ ...launch.args
4124
4552
  ];
4125
- if (ctx.config.model && ctx.config.model !== "default") {
4126
- args.push("--model", ctx.config.model);
4553
+ const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
4554
+ if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
4555
+ args.push("--model", launchRuntimeFields.model);
4127
4556
  }
4128
4557
  const spawnEnv = (await prepareCliTransport(ctx, { NO_COLOR: "1" })).spawnEnv;
4129
- const launch = resolveKimiSpawn(args);
4130
- const proc = spawn7(launch.command, launch.args, {
4558
+ const spawnTarget = resolveKimiSpawn(args);
4559
+ const proc = spawn7(spawnTarget.command, spawnTarget.args, {
4131
4560
  cwd: ctx.workingDirectory,
4132
4561
  stdio: ["pipe", "pipe", "pipe"],
4133
4562
  env: spawnEnv,
@@ -4135,7 +4564,7 @@ var KimiDriver = class {
4135
4564
  // and has an 8191-character command-line limit. Kimi's official
4136
4565
  // installer/uv entrypoint is an executable, so launch it directly and
4137
4566
  // keep prompts on stdin / files instead of routing through cmd.exe.
4138
- shell: launch.shell
4567
+ shell: spawnTarget.shell
4139
4568
  });
4140
4569
  proc.stdin?.write(JSON.stringify({
4141
4570
  jsonrpc: "2.0",
@@ -4249,14 +4678,9 @@ var KimiDriver = class {
4249
4678
  return detectKimiModels();
4250
4679
  }
4251
4680
  };
4252
- function detectKimiModels(home = os4.homedir()) {
4253
- const configPath = path9.join(home, ".kimi", "config.toml");
4254
- let raw;
4255
- try {
4256
- raw = readFileSync4(configPath, "utf8");
4257
- } catch {
4258
- return null;
4259
- }
4681
+ function detectKimiModels(home = os3.homedir(), opts = {}) {
4682
+ const raw = readKimiConfigSource(home, opts.env).raw;
4683
+ if (raw === null) return null;
4260
4684
  const models = [];
4261
4685
  const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
4262
4686
  const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
@@ -4277,8 +4701,8 @@ function detectKimiModels(home = os4.homedir()) {
4277
4701
 
4278
4702
  // src/drivers/opencode.ts
4279
4703
  import { spawn as spawn8, spawnSync as spawnSync2 } from "child_process";
4280
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
4281
- import os5 from "os";
4704
+ import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
4705
+ import os4 from "os";
4282
4706
  import path10 from "path";
4283
4707
  var CHAT_MCP_SERVER_NAME = "chat";
4284
4708
  var CHAT_MCP_TOOL_PREFIX = `${CHAT_MCP_SERVER_NAME}_`;
@@ -4323,13 +4747,13 @@ function parseOpenCodeConfigContent(raw) {
4323
4747
  return {};
4324
4748
  }
4325
4749
  function parseUserOpenCodeConfig(ctx) {
4326
- const raw = ctx.config.envVars?.OPENCODE_CONFIG_CONTENT;
4750
+ const raw = runtimeConfigToLaunchFields(ctx.config).envVars?.OPENCODE_CONFIG_CONTENT;
4327
4751
  return parseOpenCodeConfigContent(raw);
4328
4752
  }
4329
- function readLocalOpenCodeConfig(home = os5.homedir()) {
4753
+ function readLocalOpenCodeConfig(home = os4.homedir()) {
4330
4754
  const configPath = path10.join(home, ".config", "opencode", "opencode.json");
4331
4755
  try {
4332
- return parseOpenCodeConfigContent(readFileSync5(configPath, "utf8"));
4756
+ return parseOpenCodeConfigContent(readFileSync4(configPath, "utf8"));
4333
4757
  } catch {
4334
4758
  }
4335
4759
  return {};
@@ -4387,7 +4811,7 @@ function mergeOpenCodeConfigs(localConfig, envConfig) {
4387
4811
  }
4388
4812
  };
4389
4813
  }
4390
- function buildOpenCodeConfig(ctx, home = os5.homedir()) {
4814
+ function buildOpenCodeConfig(ctx, home = os4.homedir()) {
4391
4815
  const userConfig = mergeOpenCodeConfigs(readLocalOpenCodeConfig(home), parseUserOpenCodeConfig(ctx));
4392
4816
  const userAgents = recordField(userConfig.agent);
4393
4817
  const userSlockAgent = recordField(userAgents[SLOCK_AGENT_NAME]);
@@ -4412,7 +4836,7 @@ function buildOpenCodeConfig(ctx, home = os5.homedir()) {
4412
4836
  }
4413
4837
  };
4414
4838
  }
4415
- async function buildOpenCodeLaunchOptions(ctx, home = os5.homedir(), version = null) {
4839
+ async function buildOpenCodeLaunchOptions(ctx, home = os4.homedir(), version = null) {
4416
4840
  const slock = await prepareCliTransport(ctx, { NO_COLOR: "1" });
4417
4841
  const config = buildOpenCodeConfig(ctx, home);
4418
4842
  const env = {
@@ -4428,8 +4852,9 @@ async function buildOpenCodeLaunchOptions(ctx, home = os5.homedir(), version = n
4428
4852
  "--dir",
4429
4853
  ctx.workingDirectory
4430
4854
  ];
4431
- if (ctx.config.model && ctx.config.model !== "default") {
4432
- args.push("--model", ctx.config.model);
4855
+ const launchRuntimeFields = runtimeConfigToLaunchFields(ctx.config);
4856
+ if (launchRuntimeFields.model && launchRuntimeFields.model !== "default") {
4857
+ args.push("--model", launchRuntimeFields.model);
4433
4858
  }
4434
4859
  if (requiresAgentCliFlag(version)) {
4435
4860
  args.push("--agent", SLOCK_AGENT_NAME);
@@ -4510,7 +4935,7 @@ function formatOpenCodeLabelToken(token) {
4510
4935
  if (/^\d/.test(token)) return token;
4511
4936
  return normalized.charAt(0).toUpperCase() + normalized.slice(1);
4512
4937
  }
4513
- function detectOpenCodeModels(home = os5.homedir(), runCommand = runOpenCodeModelsCommand) {
4938
+ function detectOpenCodeModels(home = os4.homedir(), runCommand = runOpenCodeModelsCommand) {
4514
4939
  const commandResult = runCommand(home);
4515
4940
  if (commandResult.error || commandResult.status !== 0) return null;
4516
4941
  return parseOpenCodeModelsOutput(commandResult.stdout);
@@ -4519,7 +4944,7 @@ function runOpenCodeModelsCommand(home, deps = {}) {
4519
4944
  const platform = deps.platform ?? process.platform;
4520
4945
  const spawnSyncFn = deps.spawnSyncFn ?? spawnSync2;
4521
4946
  const result = spawnSyncFn("opencode", ["models"], {
4522
- env: { ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" },
4947
+ env: scrubDaemonChildEnv({ ...process.env, HOME: home, FORCE_COLOR: "0", NO_COLOR: "1" }),
4523
4948
  encoding: "utf8",
4524
4949
  timeout: 5e3,
4525
4950
  shell: platform === "win32"
@@ -4550,7 +4975,7 @@ function openCodeSpecForEntry(entry, commandArgs) {
4550
4975
  return { command: process.execPath, args: [entry, ...commandArgs], shell: false };
4551
4976
  }
4552
4977
  function resolveWindowsOpenCodePackageEntry(commandPath, deps = {}) {
4553
- const existsSyncFn = deps.existsSyncFn ?? existsSync8;
4978
+ const existsSyncFn = deps.existsSyncFn ?? existsSync7;
4554
4979
  const execFileSyncFn = deps.execFileSyncFn;
4555
4980
  const env = deps.env ?? process.env;
4556
4981
  const winPath = path10.win32;
@@ -4580,7 +5005,7 @@ function resolveWindowsOpenCodePackageEntry(commandPath, deps = {}) {
4580
5005
  }
4581
5006
  function extractWindowsShimTargets(commandPath, deps = {}) {
4582
5007
  if (!isWindowsCommandShim(commandPath)) return [];
4583
- const readFileSyncFn = deps.readFileSyncFn ?? readFileSync5;
5008
+ const readFileSyncFn = deps.readFileSyncFn ?? readFileSync4;
4584
5009
  const commandDir = path10.win32.dirname(commandPath);
4585
5010
  let raw;
4586
5011
  try {
@@ -4714,7 +5139,7 @@ var OpenCodeDriver = class {
4714
5139
  if (unsupportedMessage) {
4715
5140
  throw new Error(unsupportedMessage);
4716
5141
  }
4717
- const launch = await buildOpenCodeLaunchOptions(ctx, os5.homedir(), version);
5142
+ const launch = await buildOpenCodeLaunchOptions(ctx, os4.homedir(), version);
4718
5143
  const spawnSpec = resolveOpenCodeSpawn(launch.args);
4719
5144
  const proc = spawn8(spawnSpec.command, spawnSpec.args, {
4720
5145
  cwd: ctx.workingDirectory,
@@ -4778,6 +5203,297 @@ var OpenCodeDriver = class {
4778
5203
  }
4779
5204
  };
4780
5205
 
5206
+ // src/drivers/pi.ts
5207
+ import { spawn as spawn9 } from "child_process";
5208
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
5209
+ import path11 from "path";
5210
+ import { fileURLToPath } from "url";
5211
+ import { getAgentDir, VERSION as PI_SDK_VERSION } from "@earendil-works/pi-coding-agent";
5212
+ var CHAT_MCP_TOOL_PREFIX2 = "chat_";
5213
+ var NO_MESSAGE_PROMPT2 = "No new messages are pending. Stop now.";
5214
+ var FIRST_MESSAGE_TASK_PREFIX2 = "First message task (system-triggered):";
5215
+ var MIN_SUPPORTED_PI_VERSION = "0.74.0";
5216
+ function parseSemver2(version) {
5217
+ const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
5218
+ if (!match) return null;
5219
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
5220
+ }
5221
+ function isSupportedPiVersion(version) {
5222
+ if (!version) return true;
5223
+ const actual = parseSemver2(version);
5224
+ const minimum = parseSemver2(MIN_SUPPORTED_PI_VERSION);
5225
+ if (!actual || !minimum) return true;
5226
+ for (let i = 0; i < 3; i += 1) {
5227
+ if (actual[i] > minimum[i]) return true;
5228
+ if (actual[i] < minimum[i]) return false;
5229
+ }
5230
+ return true;
5231
+ }
5232
+ function unsupportedPiVersionMessage(version) {
5233
+ if (!version || isSupportedPiVersion(version)) return null;
5234
+ 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.`;
5235
+ }
5236
+ function probePi(version = PI_SDK_VERSION) {
5237
+ const unsupportedMessage = unsupportedPiVersionMessage(version);
5238
+ if (unsupportedMessage) {
5239
+ return {
5240
+ available: false,
5241
+ version: `${version} (requires @earendil-works/pi-coding-agent >= ${MIN_SUPPORTED_PI_VERSION})`
5242
+ };
5243
+ }
5244
+ return { available: true, version };
5245
+ }
5246
+ function resolvePiSdkRunnerPath(moduleUrl = import.meta.url) {
5247
+ const moduleDir = path11.dirname(fileURLToPath(moduleUrl));
5248
+ const sourceSibling = path11.join(moduleDir, "piSdkRunner.ts");
5249
+ if (existsSync8(sourceSibling)) return sourceSibling;
5250
+ const bundledEntry = path11.join(moduleDir, "drivers", "piSdkRunner.js");
5251
+ if (existsSync8(bundledEntry)) return bundledEntry;
5252
+ return path11.join(moduleDir, "piSdkRunner.js");
5253
+ }
5254
+ function buildPiSdkNodeArgs(runnerPath = resolvePiSdkRunnerPath()) {
5255
+ if (runnerPath.endsWith(".ts")) {
5256
+ return [...process.execArgv, runnerPath];
5257
+ }
5258
+ return [runnerPath];
5259
+ }
5260
+ async function buildPiLaunchOptions(ctx, opts = {}) {
5261
+ const command = opts.command ?? process.execPath;
5262
+ const piDir = path11.join(ctx.workingDirectory, ".slock", "pi");
5263
+ const sessionDir = path11.join(piDir, "sessions");
5264
+ mkdirSync4(sessionDir, { recursive: true });
5265
+ const slock = await prepareCliTransport(ctx, { NO_COLOR: "1" });
5266
+ const runnerPath = opts.runnerPath ?? resolvePiSdkRunnerPath();
5267
+ const agentDir = opts.agentDir ?? getAgentDir();
5268
+ const runnerConfigPath = path11.join(
5269
+ piDir,
5270
+ `sdk-run-${(ctx.launchId || "launch").replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`
5271
+ );
5272
+ const turnPrompt = ctx.prompt === ctx.standingPrompt ? NO_MESSAGE_PROMPT2 : ctx.prompt;
5273
+ const runnerConfig = {
5274
+ cwd: ctx.workingDirectory,
5275
+ agentDir,
5276
+ sessionDir,
5277
+ sessionId: ctx.config.sessionId || null,
5278
+ standingPrompt: ctx.standingPrompt,
5279
+ prompt: turnPrompt,
5280
+ model: ctx.config.model && ctx.config.model !== "default" ? ctx.config.model : null
5281
+ };
5282
+ writeFileSync7(runnerConfigPath, `${JSON.stringify(runnerConfig)}
5283
+ `, { encoding: "utf8", mode: 384 });
5284
+ const args = [
5285
+ ...buildPiSdkNodeArgs(runnerPath),
5286
+ "--config",
5287
+ runnerConfigPath
5288
+ ];
5289
+ return {
5290
+ command,
5291
+ args,
5292
+ env: slock.spawnEnv,
5293
+ sessionDir,
5294
+ agentDir,
5295
+ runnerConfigPath,
5296
+ sdkVersion: PI_SDK_VERSION
5297
+ };
5298
+ }
5299
+ function isSystemFirstMessageTask2(message) {
5300
+ return message.sender_id === "system" && message.channel_type === "channel" && message.channel_name === "all" && message.content.trimStart().startsWith(FIRST_MESSAGE_TASK_PREFIX2);
5301
+ }
5302
+ function buildPiSystemPrompt(config) {
5303
+ return buildCliTransportSystemPrompt(config, {
5304
+ toolPrefix: CHAT_MCP_TOOL_PREFIX2,
5305
+ extraCriticalRules: [
5306
+ "- 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."
5307
+ ],
5308
+ postStartupNotes: [
5309
+ "**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."
5310
+ ],
5311
+ includeStdinNotificationSection: false,
5312
+ messageNotificationStyle: "poll"
5313
+ });
5314
+ }
5315
+ function contentText(content) {
5316
+ if (!content) return "";
5317
+ const chunks = [];
5318
+ for (const item of content) {
5319
+ if (item.type === "text" && typeof item.text === "string") {
5320
+ chunks.push(item.text);
5321
+ }
5322
+ }
5323
+ return chunks.join("");
5324
+ }
5325
+ function apiKeyErrorMessage(line) {
5326
+ const trimmed = line.trim();
5327
+ if (!trimmed) return null;
5328
+ if (/no api key found/i.test(trimmed)) return trimmed;
5329
+ if (/api key.+required/i.test(trimmed)) return trimmed;
5330
+ if (/no models available/i.test(trimmed)) return trimmed;
5331
+ return null;
5332
+ }
5333
+ var PiDriver = class {
5334
+ id = "pi";
5335
+ lifecycle = {
5336
+ kind: "per_turn",
5337
+ start: "defer_until_concrete_message",
5338
+ exit: "terminate_on_turn_end",
5339
+ inFlightWake: "coalesce_into_pending"
5340
+ };
5341
+ communication = {
5342
+ chat: "slock_cli",
5343
+ runtimeControl: "none"
5344
+ };
5345
+ session = {
5346
+ recovery: "resume_or_fresh"
5347
+ };
5348
+ model = {
5349
+ detectedModelsVerifiedAs: "launchable",
5350
+ toLaunchSpec: async (modelId, ctx) => {
5351
+ if (!ctx) return modelId && modelId !== "default" ? { args: ["--model", modelId] } : { args: [] };
5352
+ const launchCtx = {
5353
+ ...ctx,
5354
+ config: {
5355
+ ...ctx.config,
5356
+ model: modelId
5357
+ }
5358
+ };
5359
+ const launch = await buildPiLaunchOptions(launchCtx);
5360
+ return {
5361
+ args: launch.args,
5362
+ env: launch.env,
5363
+ configFiles: [launch.runnerConfigPath],
5364
+ params: {
5365
+ agentDir: launch.agentDir,
5366
+ sessionDir: launch.sessionDir,
5367
+ sdkVersion: launch.sdkVersion,
5368
+ resources: "extensions/skills/prompt-templates/themes/context-files disabled by Slock policy"
5369
+ }
5370
+ };
5371
+ }
5372
+ };
5373
+ supportsStdinNotification = false;
5374
+ mcpToolPrefix = CHAT_MCP_TOOL_PREFIX2;
5375
+ busyDeliveryMode = "none";
5376
+ terminateProcessOnTurnEnd = true;
5377
+ deferSpawnUntilMessage = true;
5378
+ usesSlockCliForCommunication = true;
5379
+ sessionId = null;
5380
+ sessionAnnounced = false;
5381
+ apiKeyErrorAnnounced = false;
5382
+ turnEnded = false;
5383
+ assistantTextByMessageId = /* @__PURE__ */ new Map();
5384
+ shouldDeferWakeMessage(message) {
5385
+ return isSystemFirstMessageTask2(message);
5386
+ }
5387
+ probe() {
5388
+ return probePi();
5389
+ }
5390
+ async detectModels() {
5391
+ return null;
5392
+ }
5393
+ async spawn(ctx) {
5394
+ this.sessionId = ctx.config.sessionId || null;
5395
+ this.sessionAnnounced = false;
5396
+ this.apiKeyErrorAnnounced = false;
5397
+ this.turnEnded = false;
5398
+ this.assistantTextByMessageId.clear();
5399
+ const unsupportedMessage = unsupportedPiVersionMessage(PI_SDK_VERSION);
5400
+ if (unsupportedMessage) throw new Error(unsupportedMessage);
5401
+ const launch = await buildPiLaunchOptions(ctx);
5402
+ const proc = spawn9(launch.command, launch.args, {
5403
+ cwd: ctx.workingDirectory,
5404
+ stdio: ["pipe", "pipe", "pipe"],
5405
+ env: launch.env,
5406
+ shell: false
5407
+ });
5408
+ proc.stdin?.end();
5409
+ return { process: proc };
5410
+ }
5411
+ parseLine(line) {
5412
+ let event;
5413
+ try {
5414
+ event = JSON.parse(line);
5415
+ } catch {
5416
+ if (this.apiKeyErrorAnnounced) return [];
5417
+ const message = apiKeyErrorMessage(line);
5418
+ if (!message) return [];
5419
+ this.apiKeyErrorAnnounced = true;
5420
+ this.turnEnded = true;
5421
+ return [
5422
+ { kind: "error", message },
5423
+ { kind: "turn_end", sessionId: this.sessionId || void 0 }
5424
+ ];
5425
+ }
5426
+ const events = [];
5427
+ if (event.type === "session" && event.id) {
5428
+ this.sessionId = event.id;
5429
+ }
5430
+ if (!this.sessionAnnounced && this.sessionId) {
5431
+ events.push({ kind: "session_init", sessionId: this.sessionId });
5432
+ this.sessionAnnounced = true;
5433
+ }
5434
+ switch (event.type) {
5435
+ case "agent_start":
5436
+ case "turn_start":
5437
+ events.push({ kind: "thinking", text: "" });
5438
+ break;
5439
+ case "message_update":
5440
+ case "message_end":
5441
+ if (event.message?.role === "assistant") {
5442
+ const key = event.message.id || "current";
5443
+ const currentText = contentText(event.message.content);
5444
+ const previousText = this.assistantTextByMessageId.get(key) ?? "";
5445
+ if (currentText.length > previousText.length && currentText.startsWith(previousText)) {
5446
+ events.push({ kind: "text", text: currentText.slice(previousText.length) });
5447
+ } else if (currentText && currentText !== previousText) {
5448
+ events.push({ kind: "text", text: currentText });
5449
+ }
5450
+ this.assistantTextByMessageId.set(key, currentText);
5451
+ if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
5452
+ events.push({ kind: "error", message: event.message.errorMessage || `Request ${event.message.stopReason}` });
5453
+ }
5454
+ }
5455
+ break;
5456
+ case "tool_execution_start":
5457
+ events.push({
5458
+ kind: "tool_call",
5459
+ name: event.toolName || "unknown_tool",
5460
+ input: event.args
5461
+ });
5462
+ break;
5463
+ case "tool_execution_end":
5464
+ events.push({
5465
+ kind: "tool_output",
5466
+ name: event.toolName || "unknown_tool"
5467
+ });
5468
+ if (event.isError) {
5469
+ events.push({ kind: "error", message: `Pi tool ${event.toolName || "unknown_tool"} failed` });
5470
+ }
5471
+ break;
5472
+ case "compaction_start":
5473
+ events.push({ kind: "compaction_started" });
5474
+ break;
5475
+ case "compaction_end":
5476
+ events.push({ kind: "compaction_finished" });
5477
+ if (event.errorMessage) events.push({ kind: "error", message: event.errorMessage });
5478
+ break;
5479
+ case "turn_end":
5480
+ case "agent_end":
5481
+ if (!this.turnEnded) {
5482
+ events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
5483
+ this.turnEnded = true;
5484
+ }
5485
+ break;
5486
+ }
5487
+ return events;
5488
+ }
5489
+ encodeStdinMessage(_text, _sessionId, _opts) {
5490
+ return null;
5491
+ }
5492
+ buildSystemPrompt(config, _agentId) {
5493
+ return buildPiSystemPrompt(config);
5494
+ }
5495
+ };
5496
+
4781
5497
  // src/drivers/index.ts
4782
5498
  var driverFactories = {
4783
5499
  claude: () => new ClaudeDriver(),
@@ -4787,7 +5503,8 @@ var driverFactories = {
4787
5503
  cursor: () => new CursorDriver(),
4788
5504
  gemini: () => new GeminiDriver(),
4789
5505
  kimi: () => new KimiDriver(),
4790
- opencode: () => new OpenCodeDriver()
5506
+ opencode: () => new OpenCodeDriver(),
5507
+ pi: () => new PiDriver()
4791
5508
  };
4792
5509
  function getDriver(runtimeId) {
4793
5510
  const createDriver = driverFactories[runtimeId];
@@ -4800,7 +5517,7 @@ function getDriver(runtimeId) {
4800
5517
 
4801
5518
  // src/workspaces.ts
4802
5519
  import { readdir, rm, stat } from "fs/promises";
4803
- import path11 from "path";
5520
+ import path12 from "path";
4804
5521
  function isValidWorkspaceDirectoryName(directoryName) {
4805
5522
  return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
4806
5523
  }
@@ -4808,7 +5525,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
4808
5525
  if (!isValidWorkspaceDirectoryName(directoryName)) {
4809
5526
  return null;
4810
5527
  }
4811
- return path11.join(dataDir, directoryName);
5528
+ return path12.join(dataDir, directoryName);
4812
5529
  }
4813
5530
  function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
4814
5531
  return {
@@ -4857,7 +5574,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
4857
5574
  return summary;
4858
5575
  }
4859
5576
  const childSummaries = await Promise.all(
4860
- entries.map((entry) => summarizeWorkspaceEntry(path11.join(dirPath, entry.name), entry))
5577
+ entries.map((entry) => summarizeWorkspaceEntry(path12.join(dirPath, entry.name), entry))
4861
5578
  );
4862
5579
  for (const childSummary of childSummaries) {
4863
5580
  summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
@@ -4876,7 +5593,7 @@ async function scanWorkspaceDirectories(dataDir) {
4876
5593
  if (!entry.isDirectory()) {
4877
5594
  return null;
4878
5595
  }
4879
- const dirPath = path11.join(dataDir, entry.name);
5596
+ const dirPath = path12.join(dataDir, entry.name);
4880
5597
  try {
4881
5598
  const summary = await summarizeWorkspaceDirectory(dirPath);
4882
5599
  return {
@@ -5079,6 +5796,87 @@ function redactUrlQuery(value) {
5079
5796
  }
5080
5797
  }
5081
5798
 
5799
+ // src/runtimeProgressState.ts
5800
+ var RuntimeProgressState = class {
5801
+ lastEventAtMs;
5802
+ lastEventKindValue = null;
5803
+ staleSinceMs = null;
5804
+ constructor(nowMs = Date.now()) {
5805
+ this.lastEventAtMs = nowMs;
5806
+ }
5807
+ get lastEventAt() {
5808
+ return this.lastEventAtMs;
5809
+ }
5810
+ get lastEventKind() {
5811
+ return this.lastEventKindValue;
5812
+ }
5813
+ get staleSince() {
5814
+ return this.staleSinceMs;
5815
+ }
5816
+ get isStale() {
5817
+ return this.staleSinceMs !== null;
5818
+ }
5819
+ ageMs(nowMs = Date.now()) {
5820
+ return nowMs - this.lastEventAtMs;
5821
+ }
5822
+ noteRuntimeEvent(eventKind, nowMs = Date.now()) {
5823
+ this.lastEventAtMs = nowMs;
5824
+ this.lastEventKindValue = eventKind ?? null;
5825
+ this.staleSinceMs = null;
5826
+ }
5827
+ noteInternalProgress(observedAtMs = Date.now()) {
5828
+ this.lastEventAtMs = observedAtMs;
5829
+ this.staleSinceMs = null;
5830
+ }
5831
+ markStale(nowMs = Date.now()) {
5832
+ this.staleSinceMs ??= nowMs;
5833
+ return this.staleSinceMs;
5834
+ }
5835
+ };
5836
+
5837
+ // src/runtimeNotificationState.ts
5838
+ var RuntimeNotificationState = class {
5839
+ timerValue = null;
5840
+ pendingCountValue = 0;
5841
+ get pendingCount() {
5842
+ return this.pendingCountValue;
5843
+ }
5844
+ get timer() {
5845
+ return this.timerValue;
5846
+ }
5847
+ get hasTimer() {
5848
+ return this.timerValue !== null;
5849
+ }
5850
+ add(count = 1) {
5851
+ this.pendingCountValue += count;
5852
+ return this.pendingCountValue;
5853
+ }
5854
+ clearPending() {
5855
+ this.pendingCountValue = 0;
5856
+ }
5857
+ clearTimer() {
5858
+ if (this.timerValue) {
5859
+ clearTimeout(this.timerValue);
5860
+ this.timerValue = null;
5861
+ }
5862
+ }
5863
+ clear() {
5864
+ this.clearPending();
5865
+ this.clearTimer();
5866
+ }
5867
+ schedule(callback, delayMs) {
5868
+ if (this.timerValue) return false;
5869
+ this.timerValue = setTimeout(callback, delayMs);
5870
+ return true;
5871
+ }
5872
+ takePendingAndClearTimer() {
5873
+ const count = this.pendingCountValue;
5874
+ this.pendingCountValue = 0;
5875
+ this.clearTimer();
5876
+ return count;
5877
+ }
5878
+ };
5879
+
5082
5880
  // src/agentProcessManager.ts
5083
5881
  var DEFAULT_MAX_CONCURRENT_AGENT_STARTS = 5;
5084
5882
  var DEFAULT_AGENT_START_INTERVAL_MS = 500;
@@ -5225,19 +6023,19 @@ function findSessionJsonl(root, predicate) {
5225
6023
  if (depth < 0 || visited >= maxEntries) return null;
5226
6024
  let entries;
5227
6025
  try {
5228
- entries = readdirSync2(dir, { withFileTypes: true }).sort((a, b) => b.name.localeCompare(a.name));
6026
+ entries = readdirSync(dir, { withFileTypes: true }).sort((a, b) => b.name.localeCompare(a.name));
5229
6027
  } catch {
5230
6028
  return null;
5231
6029
  }
5232
6030
  for (const entry of entries) {
5233
6031
  if (++visited > maxEntries) return null;
5234
6032
  if (!entry.isFile() || !predicate(entry.name)) continue;
5235
- return path12.join(dir, entry.name);
6033
+ return path13.join(dir, entry.name);
5236
6034
  }
5237
6035
  for (const entry of entries) {
5238
6036
  if (++visited > maxEntries) return null;
5239
6037
  if (!entry.isDirectory()) continue;
5240
- const found = visit(path12.join(dir, entry.name), depth - 1);
6038
+ const found = visit(path13.join(dir, entry.name), depth - 1);
5241
6039
  if (found) return found;
5242
6040
  }
5243
6041
  return null;
@@ -5250,10 +6048,10 @@ function safeSessionFilename(value) {
5250
6048
  }
5251
6049
  function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
5252
6050
  try {
5253
- const dir = path12.join(fallbackDir, ".slock", "runtime-sessions");
5254
- mkdirSync4(dir, { recursive: true });
5255
- const filePath = path12.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
5256
- writeFileSync7(filePath, JSON.stringify({
6051
+ const dir = path13.join(fallbackDir, ".slock", "runtime-sessions");
6052
+ mkdirSync5(dir, { recursive: true });
6053
+ const filePath = path13.join(dir, `${runtime}-${safeSessionFilename(sessionId)}.jsonl`);
6054
+ writeFileSync8(filePath, JSON.stringify({
5257
6055
  type: "runtime_session_handoff",
5258
6056
  runtime,
5259
6057
  sessionId,
@@ -5271,17 +6069,17 @@ function writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir) {
5271
6069
  return null;
5272
6070
  }
5273
6071
  }
5274
- function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), fallbackDir) {
5275
- const directPath = path12.isAbsolute(sessionId) ? sessionId : null;
6072
+ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os5.homedir(), fallbackDir) {
6073
+ const directPath = path13.isAbsolute(sessionId) ? sessionId : null;
5276
6074
  if (directPath) {
5277
6075
  try {
5278
- if (statSync2(directPath).isFile()) {
6076
+ if (statSync(directPath).isFile()) {
5279
6077
  return { label: sessionId, path: directPath, runtime, reachable: true };
5280
6078
  }
5281
6079
  } catch {
5282
6080
  }
5283
6081
  }
5284
- 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;
6082
+ const resolvedPath = runtime === "claude" ? findSessionJsonl(path13.join(homeDir, ".claude", "projects"), (filename) => filename === `${sessionId}.jsonl`) : runtime === "codex" ? findSessionJsonl(path13.join(homeDir, ".codex", "sessions"), (filename) => filename.endsWith(".jsonl") && filename.includes(sessionId)) : null;
5285
6083
  if (!resolvedPath && fallbackDir) {
5286
6084
  const fallback = writeRuntimeSessionHandoff(runtime, sessionId, fallbackDir);
5287
6085
  if (fallback) return fallback;
@@ -5297,6 +6095,36 @@ function resolveRuntimeSessionRef(runtime, sessionId, homeDir = os6.homedir(), f
5297
6095
  }
5298
6096
  return ref;
5299
6097
  }
6098
+ function classifySpawnFailure(error) {
6099
+ const detail = error instanceof Error ? error.message : String(error);
6100
+ const lower = detail.toLowerCase();
6101
+ if (lower.includes("agent credential proxy") && lower.includes("failed to bind")) {
6102
+ return {
6103
+ reason: "agent_proxy_bind_failed",
6104
+ detail,
6105
+ userMessage: "Local agent proxy could not start. Check if another daemon or service is using the required local port."
6106
+ };
6107
+ }
6108
+ if (lower.includes("runner_credential_mint") || error instanceof RunnerCredentialMintError) {
6109
+ return {
6110
+ reason: "runner_credential_mint_failed",
6111
+ detail,
6112
+ userMessage: "Runner credential mint failed. Ensure the server is deployed and the daemon binary is compatible."
6113
+ };
6114
+ }
6115
+ if (lower.includes("enoent") || lower.includes("cannot resolve") || lower.includes("not found")) {
6116
+ return {
6117
+ reason: "runtime_not_found",
6118
+ detail,
6119
+ userMessage: "Runtime executable not found. Ensure the required CLI is installed and available on PATH."
6120
+ };
6121
+ }
6122
+ return {
6123
+ reason: "runtime_spawn_failed",
6124
+ detail,
6125
+ userMessage: `Start failed: ${detail}`
6126
+ };
6127
+ }
5300
6128
  function formatSenderHandle(message) {
5301
6129
  return message.sender_description ? `@${message.sender_name} \u2014 ${message.sender_description}` : `@${message.sender_name}`;
5302
6130
  }
@@ -5945,26 +6773,29 @@ function hasDirectStdinRecoveryEvidence(ap) {
5945
6773
  (text) => /write_stdin failed|stdin is closed|closed for this session|session.*closed/i.test(text)
5946
6774
  );
5947
6775
  }
5948
- function isMissingResumeSession(ap) {
5949
- if (!ap.sessionId) return false;
6776
+ function resumeSessionRecoveryReason(ap) {
6777
+ if (!ap.sessionId) return null;
5950
6778
  const candidates = [
5951
6779
  ap.lastRuntimeError,
5952
6780
  ...ap.recentStderr
5953
6781
  ].filter((value) => !!value);
5954
6782
  if (ap.driver.id === "claude") {
5955
- return candidates.some((text) => /No conversation found with session ID/i.test(text));
6783
+ return candidates.some((text) => /No conversation found with session ID/i.test(text)) ? "missing" : null;
5956
6784
  }
5957
6785
  if (ap.driver.id === "opencode") {
5958
- return candidates.some(
5959
- (text) => /Session not found/i.test(text) && text.includes(ap.sessionId)
5960
- );
6786
+ if (candidates.some((text) => /Session not found/i.test(text) && text.includes(ap.sessionId))) return "missing";
6787
+ if (candidates.some(isOpenCodeReplayRejectedByProvider)) return "provider_replay_rejected";
6788
+ return null;
5961
6789
  }
5962
6790
  if (ap.driver.id === "gemini") {
5963
6791
  return candidates.some(
5964
6792
  (text) => /Error resuming session:\s*Invalid session identifier/i.test(text) && text.includes(ap.sessionId)
5965
- );
6793
+ ) ? "missing" : null;
5966
6794
  }
5967
- return false;
6795
+ return null;
6796
+ }
6797
+ function isOpenCodeReplayRejectedByProvider(text) {
6798
+ return /Invalid request:\s*the message at position \d+ with role ['"]?assistant['"]? must not be empty/i.test(text);
5968
6799
  }
5969
6800
  function resumeSessionRuntimeLabel(runtimeId) {
5970
6801
  if (runtimeId === "opencode") return "OpenCode";
@@ -6021,7 +6852,7 @@ function buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes) {
6021
6852
  launchId: ap.launchId || void 0,
6022
6853
  sessionIdPresent: Boolean(ap.sessionId),
6023
6854
  inboxCount: ap.inbox.length,
6024
- pendingNotificationCount: ap.pendingNotificationCount,
6855
+ pendingNotificationCount: ap.notifications.pendingCount,
6025
6856
  processPidPresent: typeof ap.process.pid === "number",
6026
6857
  busyDeliveryMode: ap.driver.busyDeliveryMode,
6027
6858
  supportsStdinNotification: ap.driver.supportsStdinNotification,
@@ -6035,7 +6866,7 @@ function buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes) {
6035
6866
  };
6036
6867
  }
6037
6868
  function classifyRuntimeStallReason(ap) {
6038
- if (ap.lastRuntimeEventKind === "tool_output" && ap.gatedSteering.outstandingToolUses === 0) {
6869
+ if (ap.runtimeProgress.lastEventKind === "tool_output" && ap.gatedSteering.outstandingToolUses === 0) {
6039
6870
  return "harness_post_tool_silent_wedge";
6040
6871
  }
6041
6872
  return "no_runtime_events";
@@ -6196,7 +7027,7 @@ var AgentProcessManager = class _AgentProcessManager {
6196
7027
  this.daemonApiKey = daemonApiKey;
6197
7028
  this.serverUrl = opts.serverUrl;
6198
7029
  this.dataDir = opts.dataDir || resolveSlockHomePath("agents");
6199
- this.runtimeSessionHomeDir = opts.runtimeSessionHomeDir || os6.homedir();
7030
+ this.runtimeSessionHomeDir = opts.runtimeSessionHomeDir || os5.homedir();
6200
7031
  this.driverResolver = opts.driverResolver || getDriver;
6201
7032
  this.defaultAgentEnvVarsProvider = opts.defaultAgentEnvVarsProvider || null;
6202
7033
  this.tracer = opts.tracer ?? noopTracer;
@@ -6608,7 +7439,7 @@ var AgentProcessManager = class _AgentProcessManager {
6608
7439
  );
6609
7440
  wakeMessage = void 0;
6610
7441
  }
6611
- const agentDataDir = path12.join(this.dataDir, agentId);
7442
+ const agentDataDir = path13.join(this.dataDir, agentId);
6612
7443
  await mkdir(agentDataDir, { recursive: true });
6613
7444
  let runtimeConfig = withLocalRuntimeContext(config, agentId, agentDataDir);
6614
7445
  const legacyRuntimeProfileControl = runtimeConfig.runtimeProfileControl?.kind === "migration" ? runtimeConfig.runtimeProfileControl : null;
@@ -6622,23 +7453,23 @@ var AgentProcessManager = class _AgentProcessManager {
6622
7453
  );
6623
7454
  runtimeConfig = { ...runtimeConfig, runtimeProfileControl: null };
6624
7455
  }
6625
- const memoryMdPath = path12.join(agentDataDir, "MEMORY.md");
7456
+ const memoryMdPath = path13.join(agentDataDir, "MEMORY.md");
6626
7457
  try {
6627
7458
  await access(memoryMdPath);
6628
7459
  } catch {
6629
7460
  const initialMemoryMd = buildInitialMemoryMd(runtimeConfig);
6630
7461
  await writeFile(memoryMdPath, initialMemoryMd);
6631
7462
  }
6632
- const notesDir = path12.join(agentDataDir, "notes");
7463
+ const notesDir = path13.join(agentDataDir, "notes");
6633
7464
  await mkdir(notesDir, { recursive: true });
6634
7465
  if (getOnboardingSeedMode(config) === FIRST_CINDY_SEED_MODE) {
6635
7466
  const seedFiles = buildOnboardingSeedFiles();
6636
7467
  for (const { relativePath, content } of seedFiles) {
6637
- const fullPath = path12.join(agentDataDir, relativePath);
7468
+ const fullPath = path13.join(agentDataDir, relativePath);
6638
7469
  try {
6639
7470
  await access(fullPath);
6640
7471
  } catch {
6641
- await mkdir(path12.dirname(fullPath), { recursive: true });
7472
+ await mkdir(path13.dirname(fullPath), { recursive: true });
6642
7473
  await writeFile(fullPath, content);
6643
7474
  }
6644
7475
  }
@@ -6766,16 +7597,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6766
7597
  startupUnreadSummary: unreadSummary,
6767
7598
  startupResumePrompt: resumePrompt,
6768
7599
  isIdle: false,
6769
- notificationTimer: null,
6770
- pendingNotificationCount: 0,
7600
+ notifications: new RuntimeNotificationState(),
6771
7601
  activityHeartbeat: null,
6772
7602
  startupTimeoutTimer: null,
6773
7603
  startupTimedOut: false,
6774
7604
  compactionWatchdog: null,
6775
7605
  compactionStartedAt: null,
6776
- lastRuntimeEventAt: Date.now(),
6777
- lastRuntimeEventKind: null,
6778
- runtimeProgressStaleSince: null,
7606
+ runtimeProgress: new RuntimeProgressState(Date.now()),
6779
7607
  runtimeTraceSpan: null,
6780
7608
  runtimeTraceCounters: createRuntimeTraceCounters(),
6781
7609
  lastActivity: "",
@@ -6884,7 +7712,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6884
7712
  clean_exit: code === 0,
6885
7713
  runtime_trace_active: Boolean(current?.runtimeTraceSpan),
6886
7714
  inbox_count: current?.inbox.length ?? 0,
6887
- pending_notification_count: current?.pendingNotificationCount ?? 0,
7715
+ pending_notification_count: current?.notifications.pendingCount ?? 0,
6888
7716
  ...this.processExitTraceAttrs.get(proc)
6889
7717
  });
6890
7718
  logger.info(`[Agent ${agentId}] Process exited with code ${code}${signal ? ` (signal ${signal})` : ""}`);
@@ -6893,9 +7721,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6893
7721
  if (this.agents.has(agentId)) {
6894
7722
  const ap = this.agents.get(agentId);
6895
7723
  if (ap.process !== proc) return;
6896
- if (ap.notificationTimer) {
6897
- clearTimeout(ap.notificationTimer);
6898
- }
7724
+ ap.notifications.clearTimer();
6899
7725
  if (ap.pendingTrajectory?.timer) {
6900
7726
  clearTimeout(ap.pendingTrajectory.timer);
6901
7727
  }
@@ -6909,7 +7735,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6909
7735
  const expectedTermination = Boolean(ap.expectedTerminationReason) || ap.startupTimedOut;
6910
7736
  const processEndedCleanly = finalCode === 0 || expectedTermination && !ap.lastRuntimeError;
6911
7737
  const terminalFailureDetail = processEndedCleanly ? null : classifyTerminalFailure(ap);
6912
- const missingResumeSession = isMissingResumeSession(ap);
7738
+ const resumeRecoveryReason = resumeSessionRecoveryReason(ap);
7739
+ const shouldColdStartResumeSession = resumeRecoveryReason !== null;
6913
7740
  const summary = summarizeCrash(finalCode, finalSignal);
6914
7741
  this.endRuntimeTrace(ap, processEndedCleanly ? "ok" : "error", {
6915
7742
  outcome: processEndedCleanly ? "process-exit" : "process-crash",
@@ -6923,20 +7750,22 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
6923
7750
  cleanupAgentCredentialProxy(agentId, ap.launchId);
6924
7751
  this.revokeManagedRunnerCredential(agentId, ap.config, ap.launchId);
6925
7752
  this.agents.delete(agentId);
6926
- if (missingResumeSession) {
7753
+ if (shouldColdStartResumeSession) {
6927
7754
  const staleSessionId = ap.sessionId;
6928
7755
  const runtimeLabel = resumeSessionRuntimeLabel(ap.driver.id);
6929
7756
  const restartConfig = { ...stripManagedRunnerCredential(ap.config), sessionId: null };
7757
+ const reasonText = resumeRecoveryReason === "provider_replay_rejected" ? "was rejected by the provider during replay" : "is unavailable locally";
7758
+ const activityText = resumeRecoveryReason === "provider_replay_rejected" ? `Stored ${runtimeLabel} session replay rejected; cold-starting a new session\u2026` : `Stored ${runtimeLabel} session missing; cold-starting a new session\u2026`;
6930
7759
  logger.warn(
6931
- `[Agent ${agentId}] Stored ${runtimeLabel} session ${staleSessionId} is unavailable locally; falling back to cold start`
7760
+ `[Agent ${agentId}] Stored ${runtimeLabel} session ${staleSessionId} ${reasonText}; falling back to cold start`
6932
7761
  );
6933
7762
  this.broadcastActivity(
6934
7763
  agentId,
6935
7764
  "working",
6936
- `Stored ${runtimeLabel} session missing; cold-starting a new session\u2026`,
7765
+ activityText,
6937
7766
  [{
6938
7767
  kind: "text",
6939
- text: `Stored ${runtimeLabel} session ${staleSessionId} was not found locally. Falling back to a cold start; earlier runtime context may not be restored.`
7768
+ text: `Stored ${runtimeLabel} session ${staleSessionId} ${reasonText}. Falling back to a cold start; earlier runtime context may not be restored.`
6940
7769
  }]
6941
7770
  );
6942
7771
  this.startAgent(
@@ -7082,7 +7911,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7082
7911
  "X-Slock-Client": "daemon-server-session-worker"
7083
7912
  },
7084
7913
  body: JSON.stringify({
7085
- scopes: ["send", "read", "mentions", "tasks", "reactions", "server", "channels"],
7914
+ scopes: ["send", "read", "mentions", "tasks", "reactions", "server", "channels", "knowledge"],
7086
7915
  name: `runner:${config.runtime}:${agentId.slice(0, 8)}`
7087
7916
  })
7088
7917
  });
@@ -7205,15 +8034,15 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7205
8034
  enqueueRuntimeProfileNotification(agentId, ap, message, kind, key) {
7206
8035
  ap.inbox.push(message);
7207
8036
  if (ap.driver.supportsStdinNotification && ap.sessionId) {
7208
- ap.pendingNotificationCount++;
8037
+ ap.notifications.add();
7209
8038
  if (ap.driver.busyDeliveryMode === "gated") {
7210
8039
  this.recordGatedSteeringEvent(agentId, ap, "buffer", {
7211
8040
  reason: "runtime_profile",
7212
8041
  kind,
7213
8042
  pendingMessages: ap.inbox.length
7214
8043
  });
7215
- } else if (!ap.notificationTimer) {
7216
- ap.notificationTimer = setTimeout(() => {
8044
+ } else if (!ap.notifications.hasTimer) {
8045
+ ap.notifications.schedule(() => {
7217
8046
  this.sendStdinNotification(agentId);
7218
8047
  }, 3e3);
7219
8048
  }
@@ -7228,7 +8057,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7228
8057
  session_id_present: Boolean(ap.sessionId),
7229
8058
  launchId: ap.launchId || void 0,
7230
8059
  inbox_count: ap.inbox.length,
7231
- pending_notification_count: ap.pendingNotificationCount,
8060
+ pending_notification_count: ap.notifications.pendingCount,
7232
8061
  busy_delivery_mode: ap.driver.busyDeliveryMode,
7233
8062
  supports_stdin_notification: ap.driver.supportsStdinNotification
7234
8063
  });
@@ -7266,9 +8095,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7266
8095
  }
7267
8096
  return;
7268
8097
  }
7269
- if (ap.notificationTimer) {
7270
- clearTimeout(ap.notificationTimer);
7271
- }
8098
+ ap.notifications.clearTimer();
7272
8099
  if (ap.activityHeartbeat) {
7273
8100
  clearInterval(ap.activityHeartbeat);
7274
8101
  }
@@ -7449,11 +8276,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7449
8276
  return true;
7450
8277
  }
7451
8278
  if (ap.gatedSteering.compacting) {
7452
- ap.pendingNotificationCount++;
7453
- if (ap.notificationTimer) {
7454
- clearTimeout(ap.notificationTimer);
7455
- ap.notificationTimer = null;
7456
- }
8279
+ ap.notifications.add();
8280
+ ap.notifications.clearTimer();
7457
8281
  if (ap.driver.busyDeliveryMode === "gated") {
7458
8282
  this.recordGatedSteeringEvent(agentId, ap, "buffer", {
7459
8283
  reason: "compaction_boundary",
@@ -7461,7 +8285,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7461
8285
  });
7462
8286
  }
7463
8287
  this.recordRuntimeTraceEvent(agentId, ap, "runtime.compaction_boundary.delivery_buffered", {
7464
- pendingNotificationCount: ap.pendingNotificationCount,
8288
+ pendingNotificationCount: ap.notifications.pendingCount,
7465
8289
  pendingMessages: ap.inbox.length,
7466
8290
  busyDeliveryMode: ap.driver.busyDeliveryMode
7467
8291
  });
@@ -7473,16 +8297,16 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7473
8297
  session_id_present: true,
7474
8298
  launchId: ap.launchId || void 0,
7475
8299
  inbox_count: ap.inbox.length,
7476
- pending_notification_count: ap.pendingNotificationCount,
8300
+ pending_notification_count: ap.notifications.pendingCount,
7477
8301
  busy_delivery_mode: ap.driver.busyDeliveryMode,
7478
8302
  notification_timer_present: false
7479
8303
  }));
7480
8304
  return true;
7481
8305
  }
7482
8306
  if (ap.driver.busyDeliveryMode === "gated") {
7483
- ap.pendingNotificationCount++;
7484
- if (!ap.notificationTimer) {
7485
- ap.notificationTimer = setTimeout(() => {
8307
+ ap.notifications.add();
8308
+ if (!ap.notifications.hasTimer) {
8309
+ ap.notifications.schedule(() => {
7486
8310
  this.sendStdinNotification(agentId);
7487
8311
  }, 3e3);
7488
8312
  }
@@ -7498,14 +8322,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7498
8322
  session_id_present: true,
7499
8323
  launchId: ap.launchId || void 0,
7500
8324
  inbox_count: ap.inbox.length,
7501
- pending_notification_count: ap.pendingNotificationCount,
7502
- notification_timer_present: Boolean(ap.notificationTimer)
8325
+ pending_notification_count: ap.notifications.pendingCount,
8326
+ notification_timer_present: ap.notifications.hasTimer
7503
8327
  }));
7504
8328
  return true;
7505
8329
  }
7506
- ap.pendingNotificationCount++;
7507
- if (!ap.notificationTimer) {
7508
- ap.notificationTimer = setTimeout(() => {
8330
+ ap.notifications.add();
8331
+ if (!ap.notifications.hasTimer) {
8332
+ ap.notifications.schedule(() => {
7509
8333
  this.sendStdinNotification(agentId);
7510
8334
  }, 3e3);
7511
8335
  }
@@ -7516,13 +8340,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7516
8340
  runtime: ap.config.runtime,
7517
8341
  session_id_present: true,
7518
8342
  inbox_count: ap.inbox.length,
7519
- pending_notification_count: ap.pendingNotificationCount,
7520
- notification_timer_present: Boolean(ap.notificationTimer)
8343
+ pending_notification_count: ap.notifications.pendingCount,
8344
+ notification_timer_present: ap.notifications.hasTimer
7521
8345
  }));
7522
8346
  return true;
7523
8347
  }
7524
8348
  async resetWorkspace(agentId) {
7525
- const agentDataDir = path12.join(this.dataDir, agentId);
8349
+ const agentDataDir = path13.join(this.dataDir, agentId);
7526
8350
  try {
7527
8351
  await rm2(agentDataDir, { recursive: true, force: true });
7528
8352
  logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
@@ -7583,7 +8407,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7583
8407
  return result;
7584
8408
  }
7585
8409
  buildRuntimeProfileReport(agentId, config, sessionId, launchId) {
7586
- const workspacePath = path12.join(this.dataDir, agentId);
8410
+ const workspacePath = path13.join(this.dataDir, agentId);
7587
8411
  return {
7588
8412
  agentId,
7589
8413
  launchId,
@@ -7840,7 +8664,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7840
8664
  }
7841
8665
  // Workspace file browsing
7842
8666
  async getFileTree(agentId, dirPath) {
7843
- const agentDir = path12.join(this.dataDir, agentId);
8667
+ const agentDir = path13.join(this.dataDir, agentId);
7844
8668
  try {
7845
8669
  await stat2(agentDir);
7846
8670
  } catch {
@@ -7848,8 +8672,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7848
8672
  }
7849
8673
  let targetDir = agentDir;
7850
8674
  if (dirPath) {
7851
- const resolved = path12.resolve(agentDir, dirPath);
7852
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
8675
+ const resolved = path13.resolve(agentDir, dirPath);
8676
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
7853
8677
  return [];
7854
8678
  }
7855
8679
  targetDir = resolved;
@@ -7857,14 +8681,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7857
8681
  return this.listDirectoryChildren(targetDir, agentDir);
7858
8682
  }
7859
8683
  async readFile(agentId, filePath) {
7860
- const agentDir = path12.join(this.dataDir, agentId);
7861
- const resolved = path12.resolve(agentDir, filePath);
7862
- if (!resolved.startsWith(agentDir + path12.sep) && resolved !== agentDir) {
8684
+ const agentDir = path13.join(this.dataDir, agentId);
8685
+ const resolved = path13.resolve(agentDir, filePath);
8686
+ if (!resolved.startsWith(agentDir + path13.sep) && resolved !== agentDir) {
7863
8687
  throw new Error("Access denied");
7864
8688
  }
7865
8689
  const info = await stat2(resolved);
7866
8690
  if (info.isDirectory()) throw new Error("Cannot read a directory");
7867
- const ext = path12.extname(resolved).toLowerCase();
8691
+ const ext = path13.extname(resolved).toLowerCase();
7868
8692
  if (WORKSPACE_TEXT_EXTENSIONS.has(ext) || ext === "") {
7869
8693
  if (info.size > WORKSPACE_TEXT_FILE_MAX_BYTES) throw new Error("File too large");
7870
8694
  const content = await readFile(resolved, "utf-8");
@@ -7898,14 +8722,14 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7898
8722
  async listSkills(agentId, runtimeHint) {
7899
8723
  const agent = this.agents.get(agentId);
7900
8724
  const runtime = runtimeHint || agent?.config.runtime || "claude";
7901
- const home = os6.homedir();
7902
- const workspaceDir = path12.join(this.dataDir, agentId);
8725
+ const home = os5.homedir();
8726
+ const workspaceDir = path13.join(this.dataDir, agentId);
7903
8727
  const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
7904
8728
  const globalResults = await Promise.all(
7905
- paths.global.map((p) => this.scanSkillsDir(path12.join(home, p)))
8729
+ paths.global.map((p) => this.scanSkillsDir(path13.join(home, p)))
7906
8730
  );
7907
8731
  const workspaceResults = await Promise.all(
7908
- paths.workspace.map((p) => this.scanSkillsDir(path12.join(workspaceDir, p)))
8732
+ paths.workspace.map((p) => this.scanSkillsDir(path13.join(workspaceDir, p)))
7909
8733
  );
7910
8734
  const dedup = (skills) => {
7911
8735
  const seen = /* @__PURE__ */ new Set();
@@ -7934,7 +8758,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7934
8758
  const skills = [];
7935
8759
  for (const entry of entries) {
7936
8760
  if (entry.isDirectory() || entry.isSymbolicLink()) {
7937
- const skillMd = path12.join(dir, entry.name, "SKILL.md");
8761
+ const skillMd = path13.join(dir, entry.name, "SKILL.md");
7938
8762
  try {
7939
8763
  const content = await readFile(skillMd, "utf-8");
7940
8764
  const skill = this.parseSkillMd(entry.name, content);
@@ -7945,7 +8769,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
7945
8769
  } else if (entry.name.endsWith(".md")) {
7946
8770
  const cmdName = entry.name.replace(/\.md$/, "");
7947
8771
  try {
7948
- const content = await readFile(path12.join(dir, entry.name), "utf-8");
8772
+ const content = await readFile(path13.join(dir, entry.name), "utf-8");
7949
8773
  const skill = this.parseSkillMd(cmdName, content);
7950
8774
  skill.sourcePath = dir;
7951
8775
  skills.push(skill);
@@ -8312,40 +9136,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8312
9136
  this.startRuntimeTrace(agentId, ap, "runtime-progress").addEvent(name, attrs);
8313
9137
  }
8314
9138
  noteRuntimeProgress(ap, eventKind) {
8315
- ap.lastRuntimeEventAt = Date.now();
8316
- ap.lastRuntimeEventKind = eventKind ?? null;
8317
- ap.runtimeProgressStaleSince = null;
8318
- }
8319
- observeRuntimeTranscriptProgress(agentId, ap, staleForMs, source) {
8320
- if (ap.config.runtime !== "codex" || !ap.sessionId) return false;
8321
- const ref = resolveRuntimeSessionRef(ap.config.runtime, ap.sessionId, this.runtimeSessionHomeDir);
8322
- if (!ref.reachable || !ref.path) return false;
8323
- let mtimeMs;
8324
- try {
8325
- const stats = statSync2(ref.path);
8326
- if (!stats.isFile()) return false;
8327
- mtimeMs = stats.mtimeMs;
8328
- } catch {
8329
- return false;
8330
- }
8331
- if (mtimeMs <= ap.lastRuntimeEventAt) return false;
8332
- const now = Date.now();
8333
- const transcriptAgeMs = Math.max(0, now - mtimeMs);
8334
- if (transcriptAgeMs >= RUNTIME_PROGRESS_STALE_MS) return false;
8335
- ap.lastRuntimeEventAt = mtimeMs;
8336
- ap.runtimeProgressStaleSince = null;
8337
- this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.internal_observed", {
8338
- turn_outcome: "held",
8339
- turn_subtype: "runtime_progress",
8340
- turn_reason: "internal_activity_observed",
8341
- signal: "runtime_transcript_mtime",
8342
- source,
8343
- runtime: ap.config.runtime,
8344
- sessionRefReachable: true,
8345
- transcriptAgeMs,
8346
- previousStaleForMs: staleForMs
8347
- });
8348
- return true;
9139
+ ap.runtimeProgress.noteRuntimeEvent(eventKind);
8349
9140
  }
8350
9141
  recordGatedSteeringEvent(agentId, ap, event, attrs = {}) {
8351
9142
  if (ap.driver.busyDeliveryMode !== "gated") return;
@@ -8376,13 +9167,9 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8376
9167
  return this.sendStdinNotification(agentId);
8377
9168
  }
8378
9169
  const pendingMessages = ap.inbox.length;
8379
- const pendingNotificationCount = ap.pendingNotificationCount;
9170
+ const pendingNotificationCount = ap.notifications.pendingCount;
8380
9171
  const nextMessages = ap.inbox.splice(0, ap.inbox.length);
8381
- ap.pendingNotificationCount = 0;
8382
- if (ap.notificationTimer) {
8383
- clearTimeout(ap.notificationTimer);
8384
- ap.notificationTimer = null;
8385
- }
9172
+ ap.notifications.clear();
8386
9173
  ap.gatedSteering.lastFlushReason = reason;
8387
9174
  if (reason !== "turn_end") {
8388
9175
  ap.gatedSteering.inFlightBatch = {
@@ -8405,16 +9192,13 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8405
9192
  if (reason !== "turn_end") {
8406
9193
  ap.gatedSteering.inFlightBatch = null;
8407
9194
  }
8408
- ap.pendingNotificationCount += pendingNotificationCount || pendingMessages;
9195
+ ap.notifications.add(pendingNotificationCount || pendingMessages);
8409
9196
  return false;
8410
9197
  }
8411
9198
  flushCompactionBoundaryMessages(agentId, ap) {
8412
9199
  if (!ap.sessionId || !ap.driver.supportsStdinNotification || ap.inbox.length === 0) return false;
8413
- if (ap.pendingNotificationCount > 0) {
8414
- if (ap.notificationTimer) {
8415
- clearTimeout(ap.notificationTimer);
8416
- ap.notificationTimer = null;
8417
- }
9200
+ if (ap.notifications.pendingCount > 0) {
9201
+ ap.notifications.clearTimer();
8418
9202
  this.sendStdinNotification(agentId);
8419
9203
  return true;
8420
9204
  }
@@ -8442,7 +9226,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8442
9226
  handleRuntimeStartupTimeout(agentId, ap, timeoutMs) {
8443
9227
  const current = this.agents.get(agentId);
8444
9228
  if (current !== ap) return;
8445
- if (ap.lastRuntimeEventKind) {
9229
+ if (ap.runtimeProgress.lastEventKind) {
8446
9230
  this.clearRuntimeStartupTimeout(ap);
8447
9231
  return;
8448
9232
  }
@@ -8451,8 +9235,8 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8451
9235
  const detail = terminalFailureDetail?.detail ?? formatRuntimeStartTimeoutMessage(ap.driver.id);
8452
9236
  ap.startupTimedOut = true;
8453
9237
  ap.lastRuntimeError = detail;
8454
- ap.runtimeProgressStaleSince = Date.now();
8455
- const staleForMs = Math.max(timeoutMs, Date.now() - ap.lastRuntimeEventAt);
9238
+ ap.runtimeProgress.markStale();
9239
+ const staleForMs = Math.max(timeoutMs, ap.runtimeProgress.ageMs());
8456
9240
  const diagnostic = buildRuntimeStallDiagnostic(ap, staleForMs, Math.max(1, Math.floor(staleForMs / 6e4)));
8457
9241
  this.recordRuntimeTraceEvent(agentId, ap, "runtime.start.timeout", {
8458
9242
  turn_outcome: "failed",
@@ -8491,7 +9275,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8491
9275
  const batch = ap.gatedSteering.inFlightBatch;
8492
9276
  ap.gatedSteering.inFlightBatch = null;
8493
9277
  ap.inbox.unshift(...batch.messages);
8494
- ap.pendingNotificationCount += batch.messages.length;
9278
+ ap.notifications.add(batch.messages.length);
8495
9279
  this.recordGatedSteeringEvent(agentId, ap, "requeue", {
8496
9280
  reason,
8497
9281
  originalFlushReason: batch.reason,
@@ -8503,11 +9287,10 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8503
9287
  }
8504
9288
  markRuntimeProgressStaleIfNeeded(agentId, ap) {
8505
9289
  if (ap.lastActivity !== "working" && ap.lastActivity !== "thinking") return false;
8506
- if (ap.runtimeProgressStaleSince) return true;
8507
- const staleForMs = Date.now() - ap.lastRuntimeEventAt;
9290
+ if (ap.runtimeProgress.isStale) return true;
9291
+ const staleForMs = ap.runtimeProgress.ageMs();
8508
9292
  if (staleForMs < RUNTIME_PROGRESS_STALE_MS) return false;
8509
- if (this.observeRuntimeTranscriptProgress(agentId, ap, staleForMs, "activity_heartbeat")) return false;
8510
- ap.runtimeProgressStaleSince = Date.now();
9293
+ ap.runtimeProgress.markStale();
8511
9294
  const staleForMinutes = Math.max(1, Math.floor(staleForMs / 6e4));
8512
9295
  const diagnostic = buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes);
8513
9296
  const turnReason = classifyRuntimeStallReason(ap);
@@ -8540,11 +9323,10 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8540
9323
  const canRestartDirectStdinProcess = directStdinRuntime && Boolean(ap.sessionId) && (ap.gatedSteering.outstandingToolUses === 0 || hasDirectStdinRecoveryEvidence(ap));
8541
9324
  const canRestartStalledProcess = !ap.driver.supportsStdinNotification || canRestartDirectStdinProcess;
8542
9325
  if (!canRestartStalledProcess) return false;
8543
- const staleForMs = Date.now() - ap.lastRuntimeEventAt;
8544
- if (staleForMs < RUNTIME_PROGRESS_STALE_MS && !ap.runtimeProgressStaleSince) return false;
8545
- if (this.observeRuntimeTranscriptProgress(agentId, ap, staleForMs, "queued_recovery")) return false;
9326
+ const staleForMs = ap.runtimeProgress.ageMs();
9327
+ if (staleForMs < RUNTIME_PROGRESS_STALE_MS && !ap.runtimeProgress.isStale) return false;
8546
9328
  const staleForMinutes = Math.max(1, Math.floor(staleForMs / 6e4));
8547
- ap.runtimeProgressStaleSince ??= Date.now();
9329
+ ap.runtimeProgress.markStale();
8548
9330
  const diagnostic = buildRuntimeStallDiagnostic(ap, staleForMs, staleForMinutes);
8549
9331
  const turnReason = classifyRuntimeStallReason(ap);
8550
9332
  this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.stalled", {
@@ -8593,14 +9375,38 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8593
9375
  /** Handle a single ParsedEvent from any runtime driver */
8594
9376
  handleParsedEvent(agentId, event, driver) {
8595
9377
  const ap = this.agents.get(agentId);
9378
+ if (event.kind === "telemetry") {
9379
+ if (ap) this.recordRuntimeTelemetry(agentId, ap, event);
9380
+ return;
9381
+ }
8596
9382
  if (ap) {
8597
- const wasStalled = Boolean(ap.runtimeProgressStaleSince);
9383
+ const wasStalled = ap.runtimeProgress.isStale;
8598
9384
  this.clearRuntimeStartupTimeout(ap);
8599
9385
  this.noteRuntimeTraceCounter(ap, event);
8600
- this.recordRuntimeTraceEvent(agentId, ap, "runtime.event.received", { kind: event.kind });
9386
+ const eventAttrs = event.kind === "internal_progress" ? {
9387
+ kind: event.kind,
9388
+ source: event.source,
9389
+ itemType: event.itemType,
9390
+ payloadBytes: event.payloadBytes
9391
+ } : { kind: event.kind };
9392
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime.event.received", eventAttrs);
8601
9393
  if (wasStalled) {
8602
9394
  this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.observed", { afterStall: true });
8603
9395
  }
9396
+ if (event.kind === "internal_progress") {
9397
+ ap.runtimeProgress.noteInternalProgress();
9398
+ this.recordRuntimeTraceEvent(agentId, ap, "runtime.progress.internal_observed", {
9399
+ turn_outcome: "held",
9400
+ turn_subtype: "runtime_progress",
9401
+ turn_reason: "internal_activity_observed",
9402
+ signal: event.source,
9403
+ source: "runtime_event",
9404
+ runtime: ap.config.runtime,
9405
+ itemType: event.itemType,
9406
+ payloadBytes: event.payloadBytes
9407
+ });
9408
+ return;
9409
+ }
8604
9410
  this.noteRuntimeProgress(ap, event.kind);
8605
9411
  }
8606
9412
  switch (event.kind) {
@@ -8704,11 +9510,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8704
9510
  this.setGatedSteeringPhase(agentId, ap, "idle", { event: "turn_end" });
8705
9511
  if (ap.inbox.length > 0 && ap.driver.supportsStdinNotification && ap.sessionId) {
8706
9512
  const nextMessages = ap.inbox.splice(0, ap.inbox.length);
8707
- ap.pendingNotificationCount = 0;
8708
- if (ap.notificationTimer) {
8709
- clearTimeout(ap.notificationTimer);
8710
- ap.notificationTimer = null;
8711
- }
9513
+ ap.notifications.clear();
8712
9514
  if (ap.driver.busyDeliveryMode === "gated") {
8713
9515
  ap.gatedSteering.lastFlushReason = "turn_end";
8714
9516
  this.recordGatedSteeringEvent(agentId, ap, "flush", {
@@ -8802,11 +9604,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8802
9604
  }
8803
9605
  } else {
8804
9606
  ap.isIdle = true;
8805
- ap.pendingNotificationCount = 0;
8806
- if (ap.notificationTimer) {
8807
- clearTimeout(ap.notificationTimer);
8808
- ap.notificationTimer = null;
8809
- }
9607
+ ap.notifications.clear();
8810
9608
  logger.info(`[Agent ${agentId}] Marked ${ap.driver.id} wakeable after terminal runtime error`);
8811
9609
  }
8812
9610
  }
@@ -8818,6 +9616,18 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8818
9616
  }
8819
9617
  }
8820
9618
  }
9619
+ recordRuntimeTelemetry(agentId, ap, event) {
9620
+ const attrs = {
9621
+ agentId,
9622
+ launchId: ap.launchId || void 0,
9623
+ runtime: ap.config.runtime,
9624
+ model: ap.config.model,
9625
+ telemetry_name: event.name,
9626
+ ...event.attrs
9627
+ };
9628
+ ap.runtimeTraceSpan?.addEvent(`runtime.telemetry.${event.name}`, event.attrs);
9629
+ this.recordDaemonTrace(`daemon.runtime.telemetry.${event.name}`, attrs);
9630
+ }
8821
9631
  sendAgentStatus(agentId, status, launchId) {
8822
9632
  this.sendToServer({ type: "agent:status", agentId, status, launchId: launchId || void 0 });
8823
9633
  }
@@ -8871,12 +9681,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8871
9681
  sendStdinNotification(agentId) {
8872
9682
  const ap = this.agents.get(agentId);
8873
9683
  if (!ap) return false;
8874
- const count = ap.pendingNotificationCount;
8875
- ap.pendingNotificationCount = 0;
8876
- if (ap.notificationTimer) {
8877
- clearTimeout(ap.notificationTimer);
8878
- ap.notificationTimer = null;
8879
- }
9684
+ const count = ap.notifications.takePendingAndClearTimer();
8880
9685
  if (count === 0) return false;
8881
9686
  if (ap.isIdle) return false;
8882
9687
  if (!ap.sessionId) return false;
@@ -8886,7 +9691,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8886
9691
  pendingMessages: ap.inbox.length,
8887
9692
  busyDeliveryMode: ap.driver.busyDeliveryMode
8888
9693
  });
8889
- ap.pendingNotificationCount += count;
9694
+ ap.notifications.add(count);
8890
9695
  logger.info(
8891
9696
  `[Agent ${agentId}] Suppressing stdin delivery until context compaction finishes; pending=${ap.inbox.length}`
8892
9697
  );
@@ -8912,7 +9717,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8912
9717
  });
8913
9718
  return true;
8914
9719
  } else {
8915
- ap.pendingNotificationCount += count;
9720
+ ap.notifications.add(count);
8916
9721
  this.recordDaemonTrace("daemon.agent.stdin_notification", {
8917
9722
  agentId,
8918
9723
  runtime: ap.config.runtime,
@@ -8963,7 +9768,7 @@ Use ${communicationCommand(driver, "read_history")} to catch up on the channels
8963
9768
  messages_count: messages.length,
8964
9769
  session_id_present: Boolean(ap.sessionId),
8965
9770
  inbox_count: ap.inbox.length,
8966
- pending_notification_count: ap.pendingNotificationCount,
9771
+ pending_notification_count: ap.notifications.pendingCount,
8967
9772
  busy_delivery_mode: ap.driver.busyDeliveryMode,
8968
9773
  supports_stdin_notification: ap.driver.supportsStdinNotification,
8969
9774
  ...this.messagesTraceAttrs(messages)
@@ -9038,8 +9843,8 @@ ${RESPONSE_TARGET_HINT}`);
9038
9843
  const nodes = [];
9039
9844
  for (const entry of entries) {
9040
9845
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
9041
- const fullPath = path12.join(dir, entry.name);
9042
- const relativePath = path12.relative(rootDir, fullPath);
9846
+ const fullPath = path13.join(dir, entry.name);
9847
+ const relativePath = path13.relative(rootDir, fullPath);
9043
9848
  let info;
9044
9849
  try {
9045
9850
  info = await stat2(fullPath);
@@ -9344,9 +10149,9 @@ var ReminderCache = class {
9344
10149
 
9345
10150
  // src/machineLock.ts
9346
10151
  import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
9347
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
9348
- import os7 from "os";
9349
- import path13 from "path";
10152
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync2, writeFileSync as writeFileSync9 } from "fs";
10153
+ import os6 from "os";
10154
+ import path14 from "path";
9350
10155
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
9351
10156
  var DaemonMachineLockConflictError = class extends Error {
9352
10157
  code = "DAEMON_MACHINE_LOCK_HELD";
@@ -9368,18 +10173,18 @@ function resolveDefaultMachineStateRoot() {
9368
10173
  return resolveSlockHomePath("machines");
9369
10174
  }
9370
10175
  function ownerPath(lockDir) {
9371
- return path13.join(lockDir, "owner.json");
10176
+ return path14.join(lockDir, "owner.json");
9372
10177
  }
9373
10178
  function readOwner(lockDir) {
9374
10179
  try {
9375
- return JSON.parse(readFileSync6(ownerPath(lockDir), "utf8"));
10180
+ return JSON.parse(readFileSync5(ownerPath(lockDir), "utf8"));
9376
10181
  } catch {
9377
10182
  return null;
9378
10183
  }
9379
10184
  }
9380
10185
  function lockAgeMs(lockDir) {
9381
10186
  try {
9382
- return Date.now() - statSync3(lockDir).mtimeMs;
10187
+ return Date.now() - statSync2(lockDir).mtimeMs;
9383
10188
  } catch {
9384
10189
  return null;
9385
10190
  }
@@ -9398,23 +10203,23 @@ function acquireDaemonMachineLock(options) {
9398
10203
  const rootDir = options.rootDir ?? resolveDefaultMachineStateRoot();
9399
10204
  const fingerprint = apiKeyFingerprint(options.apiKey);
9400
10205
  const lockId = getDaemonMachineLockId(options.apiKey);
9401
- const machineDir = path13.join(rootDir, lockId);
9402
- const lockDir = path13.join(machineDir, "daemon.lock");
10206
+ const machineDir = path14.join(rootDir, lockId);
10207
+ const lockDir = path14.join(machineDir, "daemon.lock");
9403
10208
  const token = randomUUID3();
9404
- mkdirSync5(machineDir, { recursive: true });
10209
+ mkdirSync6(machineDir, { recursive: true });
9405
10210
  for (let attempt = 0; attempt < 2; attempt += 1) {
9406
10211
  try {
9407
- mkdirSync5(lockDir);
10212
+ mkdirSync6(lockDir);
9408
10213
  const owner = {
9409
10214
  pid: process.pid,
9410
10215
  token,
9411
- hostname: os7.hostname(),
10216
+ hostname: os6.hostname(),
9412
10217
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
9413
10218
  serverUrl: options.serverUrl,
9414
10219
  apiKeyFingerprint: fingerprint.slice(0, 16)
9415
10220
  };
9416
10221
  try {
9417
- writeFileSync8(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
10222
+ writeFileSync9(ownerPath(lockDir), `${JSON.stringify(owner, null, 2)}
9418
10223
  `, { mode: 384 });
9419
10224
  } catch (err) {
9420
10225
  rmSync3(lockDir, { recursive: true, force: true });
@@ -9451,8 +10256,8 @@ function acquireDaemonMachineLock(options) {
9451
10256
  }
9452
10257
 
9453
10258
  // src/localTraceSink.ts
9454
- import { appendFileSync, mkdirSync as mkdirSync6, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync9 } from "fs";
9455
- import path14 from "path";
10259
+ import { appendFileSync, mkdirSync as mkdirSync7, readdirSync as readdirSync2, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync10 } from "fs";
10260
+ import path15 from "path";
9456
10261
  var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
9457
10262
  var DEFAULT_MAX_FILE_AGE_MS = 5 * 60 * 1e3;
9458
10263
  var DEFAULT_MAX_FILES = 8;
@@ -9488,7 +10293,7 @@ var LocalRotatingTraceSink = class {
9488
10293
  currentSize = 0;
9489
10294
  sequence = 0;
9490
10295
  constructor(options) {
9491
- this.traceDir = path14.join(options.machineDir, "traces");
10296
+ this.traceDir = path15.join(options.machineDir, "traces");
9492
10297
  this.maxFileBytes = Math.max(1024, Math.floor(options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES));
9493
10298
  const baseAgeMs = Math.max(1e3, Math.floor(options.maxFileAgeMs ?? DEFAULT_MAX_FILE_AGE_MS));
9494
10299
  const ageJitterMs = Math.max(0, Math.floor(options.maxFileAgeJitterMs ?? 0));
@@ -9514,26 +10319,26 @@ var LocalRotatingTraceSink = class {
9514
10319
  return this.currentFile;
9515
10320
  }
9516
10321
  ensureFile(nextBytes) {
9517
- mkdirSync6(this.traceDir, { recursive: true, mode: 448 });
10322
+ mkdirSync7(this.traceDir, { recursive: true, mode: 448 });
9518
10323
  const nowMs = this.nowMsProvider();
9519
10324
  const shouldRotateForAge = this.currentFileOpenedAtMs !== null && nowMs - this.currentFileOpenedAtMs >= this.maxFileAgeMs;
9520
10325
  if (!this.currentFile || this.currentSize + nextBytes > this.maxFileBytes || shouldRotateForAge) {
9521
- this.currentFile = path14.join(
10326
+ this.currentFile = path15.join(
9522
10327
  this.traceDir,
9523
10328
  `daemon-trace-${safeTimestamp(nowMs)}-${process.pid}-${String(this.sequence++).padStart(4, "0")}.jsonl`
9524
10329
  );
9525
- writeFileSync9(this.currentFile, "", { flag: "a", mode: 384 });
9526
- this.currentSize = statSync4(this.currentFile).size;
10330
+ writeFileSync10(this.currentFile, "", { flag: "a", mode: 384 });
10331
+ this.currentSize = statSync3(this.currentFile).size;
9527
10332
  this.currentFileOpenedAtMs = nowMs;
9528
10333
  this.pruneOldFiles();
9529
10334
  }
9530
10335
  }
9531
10336
  pruneOldFiles() {
9532
- const files = readdirSync3(this.traceDir).filter((name) => name.startsWith("daemon-trace-") && name.endsWith(".jsonl")).sort();
10337
+ const files = readdirSync2(this.traceDir).filter((name) => name.startsWith("daemon-trace-") && name.endsWith(".jsonl")).sort();
9533
10338
  const excess = files.length - this.maxFiles;
9534
10339
  if (excess <= 0) return;
9535
10340
  for (const file of files.slice(0, excess)) {
9536
- rmSync4(path14.join(this.traceDir, file), { force: true });
10341
+ rmSync4(path15.join(this.traceDir, file), { force: true });
9537
10342
  }
9538
10343
  }
9539
10344
  };
@@ -9620,11 +10425,11 @@ function isDiagnosticErrorAttr(key) {
9620
10425
  import { createHash as createHash5, randomUUID as randomUUID4 } from "crypto";
9621
10426
  import { gzipSync } from "zlib";
9622
10427
  import { mkdir as mkdir2, readFile as readFile2, readdir as readdir3, stat as stat3, writeFile as writeFile2 } from "fs/promises";
9623
- import path15 from "path";
10428
+ import path16 from "path";
9624
10429
 
9625
10430
  // src/directUploadCapability.ts
9626
- function joinUrl(base, path17) {
9627
- return `${base.replace(/\/+$/, "")}${path17}`;
10431
+ function joinUrl(base, path18) {
10432
+ return `${base.replace(/\/+$/, "")}${path18}`;
9628
10433
  }
9629
10434
  function jsonHeaders(apiKey) {
9630
10435
  return {
@@ -9843,7 +10648,7 @@ var DaemonTraceBundleUploader = class {
9843
10648
  }, nextMs);
9844
10649
  }
9845
10650
  async findUploadCandidates() {
9846
- const traceDir = path15.join(this.options.machineDir, "traces");
10651
+ const traceDir = path16.join(this.options.machineDir, "traces");
9847
10652
  let names;
9848
10653
  try {
9849
10654
  names = await readdir3(traceDir);
@@ -9855,8 +10660,8 @@ var DaemonTraceBundleUploader = class {
9855
10660
  const currentFile = this.options.currentFileProvider?.();
9856
10661
  const candidates = [];
9857
10662
  for (const name of names.filter((entry) => entry.startsWith("daemon-trace-") && entry.endsWith(".jsonl")).sort()) {
9858
- const file = path15.join(traceDir, name);
9859
- if (currentFile && path15.resolve(file) === path15.resolve(currentFile)) continue;
10663
+ const file = path16.join(traceDir, name);
10664
+ if (currentFile && path16.resolve(file) === path16.resolve(currentFile)) continue;
9860
10665
  if (await this.isUploaded(file)) continue;
9861
10666
  try {
9862
10667
  const info = await stat3(file);
@@ -9930,8 +10735,8 @@ var DaemonTraceBundleUploader = class {
9930
10735
  }
9931
10736
  }
9932
10737
  uploadStatePath(file) {
9933
- const stateDir = path15.join(this.options.machineDir, "trace-uploads");
9934
- return path15.join(stateDir, `${path15.basename(file)}.uploaded.json`);
10738
+ const stateDir = path16.join(this.options.machineDir, "trace-uploads");
10739
+ return path16.join(stateDir, `${path16.basename(file)}.uploaded.json`);
9935
10740
  }
9936
10741
  async isUploaded(file) {
9937
10742
  try {
@@ -9943,9 +10748,9 @@ var DaemonTraceBundleUploader = class {
9943
10748
  }
9944
10749
  async markUploaded(file, metadata) {
9945
10750
  const stateFile = this.uploadStatePath(file);
9946
- await mkdir2(path15.dirname(stateFile), { recursive: true, mode: 448 });
10751
+ await mkdir2(path16.dirname(stateFile), { recursive: true, mode: 448 });
9947
10752
  await writeFile2(stateFile, `${JSON.stringify({
9948
- file: path15.basename(file),
10753
+ file: path16.basename(file),
9949
10754
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
9950
10755
  ...metadata
9951
10756
  }, null, 2)}
@@ -9964,10 +10769,10 @@ function readPositiveIntegerEnv2(name, fallback) {
9964
10769
 
9965
10770
  // src/core.ts
9966
10771
  var DEFAULT_TRACE_UPLOAD_URL = "https://slock-trace-upload.botiverse.dev";
9967
- var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels"];
10772
+ var RUNNER_CREDENTIAL_SCOPES = ["send", "read", "mentions", "tasks", "reactions", "server", "channels", "knowledge"];
9968
10773
  var RUNNER_CREDENTIAL_MINT_MAX_ATTEMPTS2 = 3;
9969
10774
  var RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2 = 250;
9970
- var DAEMON_CLI_USAGE = "Usage: slock-daemon --server-url <url> --api-key <key>";
10775
+ var DAEMON_CLI_USAGE = `Usage: slock-daemon --server-url <url> (--api-key <key> or ${DAEMON_API_KEY_ENV}=<key>)`;
9971
10776
  var RunnerCredentialMintError2 = class extends Error {
9972
10777
  code;
9973
10778
  retryable;
@@ -10003,9 +10808,9 @@ function runnerCredentialErrorDetail2(error) {
10003
10808
  async function waitForRunnerCredentialRetry2() {
10004
10809
  await new Promise((resolve) => setTimeout(resolve, RUNNER_CREDENTIAL_MINT_RETRY_DELAY_MS2));
10005
10810
  }
10006
- function parseDaemonCliArgs(args) {
10811
+ function parseDaemonCliArgs(args, env = {}) {
10007
10812
  let serverUrl = "";
10008
- let apiKey = "";
10813
+ let apiKey = env[DAEMON_API_KEY_ENV] ?? "";
10009
10814
  for (let i = 0; i < args.length; i++) {
10010
10815
  if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
10011
10816
  if (args[i] === "--api-key" && args[i + 1]) apiKey = args[++i];
@@ -10022,23 +10827,23 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
10022
10827
  }
10023
10828
  }
10024
10829
  function resolveChatBridgePath(moduleUrl = import.meta.url) {
10025
- const dirname = path16.dirname(fileURLToPath(moduleUrl));
10026
- const jsPath = path16.resolve(dirname, "chat-bridge.js");
10830
+ const dirname = path17.dirname(fileURLToPath2(moduleUrl));
10831
+ const jsPath = path17.resolve(dirname, "chat-bridge.js");
10027
10832
  try {
10028
10833
  accessSync(jsPath);
10029
10834
  return jsPath;
10030
10835
  } catch {
10031
- return path16.resolve(dirname, "chat-bridge.ts");
10836
+ return path17.resolve(dirname, "chat-bridge.ts");
10032
10837
  }
10033
10838
  }
10034
10839
  function resolveSlockCliPath(moduleUrl = import.meta.url) {
10035
- const thisDir = path16.dirname(fileURLToPath(moduleUrl));
10036
- const bundledDistPath = path16.resolve(thisDir, "cli", "index.js");
10840
+ const thisDir = path17.dirname(fileURLToPath2(moduleUrl));
10841
+ const bundledDistPath = path17.resolve(thisDir, "cli", "index.js");
10037
10842
  try {
10038
10843
  accessSync(bundledDistPath);
10039
10844
  return bundledDistPath;
10040
10845
  } catch {
10041
- const workspaceDistPath = path16.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
10846
+ const workspaceDistPath = path17.resolve(thisDir, "..", "..", "cli", "dist", "index.js");
10042
10847
  accessSync(workspaceDistPath);
10043
10848
  return workspaceDistPath;
10044
10849
  }
@@ -10217,7 +11022,7 @@ var DaemonCore = class {
10217
11022
  }
10218
11023
  resolveMachineStateRoot() {
10219
11024
  if (this.options.machineStateDir) return this.options.machineStateDir;
10220
- if (this.options.dataDir) return path16.join(path16.dirname(this.options.dataDir), "machines");
11025
+ if (this.options.dataDir) return path17.join(path17.dirname(this.options.dataDir), "machines");
10221
11026
  return resolveDefaultMachineStateRoot();
10222
11027
  }
10223
11028
  shouldEnableLocalTrace() {
@@ -10443,10 +11248,19 @@ var DaemonCore = class {
10443
11248
  case "agent:start":
10444
11249
  logger.info(`[Agent ${msg.agentId}] Start requested (runtime=${msg.config.runtime}, model=${msg.config.model}, session=${msg.config.sessionId || "new"}${msg.wakeMessage ? ", wake=true" : ""})`);
10445
11250
  this.startAgentFromMessage(msg).catch((err) => {
10446
- const reason = err instanceof Error ? err.message : String(err);
10447
- logger.error(`[Agent ${msg.agentId}] Start failed (${reason})`);
11251
+ const classification = classifySpawnFailure(err);
11252
+ logger.error(`[Agent ${msg.agentId}] Start failed (${classification.reason}): ${classification.detail}`);
11253
+ this.recordDaemonTrace("daemon.agent.spawn.failed", {
11254
+ agentId: msg.agentId,
11255
+ launchId: msg.launchId,
11256
+ runtime: msg.config.runtime,
11257
+ model: msg.config.model,
11258
+ failure_reason: classification.reason,
11259
+ failure_detail: classification.detail,
11260
+ session_id_present: Boolean(msg.config.sessionId)
11261
+ }, "error");
10448
11262
  this.connection.send({ type: "agent:status", agentId: msg.agentId, status: "inactive", launchId: msg.launchId });
10449
- this.connection.send({ type: "agent:activity", agentId: msg.agentId, activity: "offline", detail: `Start failed: ${reason}`, launchId: msg.launchId });
11263
+ this.connection.send({ type: "agent:activity", agentId: msg.agentId, activity: "offline", detail: classification.userMessage, launchId: msg.launchId });
10450
11264
  });
10451
11265
  break;
10452
11266
  case "agent:stop":
@@ -10656,8 +11470,8 @@ var DaemonCore = class {
10656
11470
  capabilities: ["agent:start", "agent:stop", "agent:deliver", "workspace:files"],
10657
11471
  runtimes,
10658
11472
  runningAgents: runningAgentIds,
10659
- hostname: this.options.hostname ?? os8.hostname(),
10660
- os: this.options.osDescription ?? `${os8.platform()} ${os8.arch()}`,
11473
+ hostname: this.options.hostname ?? os7.hostname(),
11474
+ os: this.options.osDescription ?? `${os7.platform()} ${os7.arch()}`,
10661
11475
  daemonVersion: this.daemonVersion
10662
11476
  });
10663
11477
  this.recordDaemonTrace("daemon.ready.sent", {
@@ -10719,6 +11533,8 @@ var DaemonCore = class {
10719
11533
  };
10720
11534
 
10721
11535
  export {
11536
+ DAEMON_API_KEY_ENV,
11537
+ scrubDaemonAuthEnv,
10722
11538
  resolveWorkspaceDirectoryPath,
10723
11539
  scanWorkspaceDirectories,
10724
11540
  deleteWorkspaceDirectory,