@slock-ai/daemon 0.35.0 → 0.36.1-alpha.0

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.
@@ -1,15 +1,37 @@
1
1
  import {
2
- buildWebSocketOptions
3
- } from "./chunk-GX2DVINN.js";
2
+ buildWebSocketOptions,
3
+ logger
4
+ } from "./chunk-E6OOH3IC.js";
4
5
 
5
6
  // src/core.ts
6
- import path9 from "path";
7
+ import path10 from "path";
7
8
  import os4 from "os";
8
9
  import { createRequire } from "module";
9
10
  import { execSync as execSync2 } from "child_process";
10
11
  import { accessSync } from "fs";
11
12
  import { fileURLToPath } from "url";
12
13
 
14
+ // ../shared/src/testing/failpoints.ts
15
+ var NoopFailpointRegistry = class {
16
+ get enabled() {
17
+ return false;
18
+ }
19
+ isEnabled() {
20
+ return false;
21
+ }
22
+ configure() {
23
+ }
24
+ clear() {
25
+ }
26
+ getTrace() {
27
+ return [];
28
+ }
29
+ hit(_key, _context, fallback) {
30
+ return fallback ? fallback() : void 0;
31
+ }
32
+ };
33
+ var noopFailpointRegistry = new NoopFailpointRegistry();
34
+
13
35
  // ../shared/src/serverPermissions.ts
14
36
  var EMPTY_SERVER_CAPABILITIES = Object.freeze({
15
37
  manageServer: false,
@@ -99,13 +121,13 @@ var DISPLAY_PLAN_CONFIG = {
99
121
 
100
122
  // src/agentProcessManager.ts
101
123
  import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
102
- import path8 from "path";
124
+ import path9 from "path";
103
125
  import os3 from "os";
104
126
 
105
127
  // src/drivers/claude.ts
106
128
  import { spawn } from "child_process";
107
129
  import { writeFileSync } from "fs";
108
- import path from "path";
130
+ import path2 from "path";
109
131
 
110
132
  // src/drivers/systemPrompt.ts
111
133
  function toolRef(prefix, name) {
@@ -297,6 +319,8 @@ Keep the user informed. They cannot see your internal reasoning, so:
297
319
 
298
320
  Use plain-text @mentions (e.g. \`@alice\`) and #channel references (e.g. \`#general\`, \`#1\`) \u2014 no HTML tags.
299
321
 
322
+ When referring to a task in message content, use the explicit form \`task #123\`. Do not use bare \`#123\` for task references \u2014 bare \`#123\` is ambiguous with PR, issue, and release references.
323
+
300
324
  When referencing a channel or mentioning someone, write them as plain text without backticks. Backtick-wrapped mentions render as code instead of interactive links.
301
325
 
302
326
  ### Formatting \u2014 URLs in non-English text
@@ -405,12 +429,83 @@ ${config.description}. This may evolve.`;
405
429
  return prompt;
406
430
  }
407
431
 
432
+ // src/drivers/probe.ts
433
+ import { execFileSync } from "child_process";
434
+ import { existsSync } from "fs";
435
+ import path from "path";
436
+ function normalizeExecOutput(raw) {
437
+ return Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw ?? "");
438
+ }
439
+ function resolveCommandOnPath(command, deps = {}) {
440
+ const platform = deps.platform ?? process.platform;
441
+ const env = deps.env ?? process.env;
442
+ const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
443
+ const locator = platform === "win32" ? "where" : "which";
444
+ try {
445
+ const output = normalizeExecOutput(execFileSyncFn(locator, [command], {
446
+ stdio: ["ignore", "pipe", "ignore"],
447
+ env
448
+ }));
449
+ const resolved = output.trim().split(/\r?\n/)[0];
450
+ return resolved || null;
451
+ } catch {
452
+ return null;
453
+ }
454
+ }
455
+ function firstExistingPath(candidates, deps = {}) {
456
+ const exists = deps.existsSyncFn ?? existsSync;
457
+ for (const candidate of candidates) {
458
+ if (exists(candidate)) return candidate;
459
+ }
460
+ return null;
461
+ }
462
+ function readCommandVersion(command, args = [], deps = {}) {
463
+ const env = deps.env ?? process.env;
464
+ const execFileSyncFn = deps.execFileSyncFn ?? execFileSync;
465
+ try {
466
+ const output = normalizeExecOutput(execFileSyncFn(command, [...args, "--version"], {
467
+ stdio: ["ignore", "pipe", "pipe"],
468
+ env,
469
+ timeout: 5e3
470
+ }));
471
+ return output.trim().split(/\r?\n/)[0] || null;
472
+ } catch {
473
+ return null;
474
+ }
475
+ }
476
+ function resolveHomePath(relativePath, deps = {}) {
477
+ const homeDir = deps.homeDir ?? deps.env?.HOME ?? process.env.HOME ?? "";
478
+ return path.join(homeDir, relativePath);
479
+ }
480
+
408
481
  // src/drivers/claude.ts
482
+ var CLAUDE_DESKTOP_CLI_RELATIVE_PATH = path2.join("Applications", "Claude Code URL Handler.app", "Contents", "MacOS", "claude");
483
+ var CLAUDE_DESKTOP_CLI_SYSTEM_PATH = "/Applications/Claude Code URL Handler.app/Contents/MacOS/claude";
484
+ function resolveClaudeCommand(deps = {}) {
485
+ const pathCommand = resolveCommandOnPath("claude", deps);
486
+ if (pathCommand) return pathCommand;
487
+ if ((deps.platform ?? process.platform) !== "darwin") return null;
488
+ return firstExistingPath([
489
+ resolveHomePath(CLAUDE_DESKTOP_CLI_RELATIVE_PATH, deps),
490
+ CLAUDE_DESKTOP_CLI_SYSTEM_PATH
491
+ ], deps);
492
+ }
493
+ function probeClaude(deps = {}) {
494
+ const command = resolveClaudeCommand(deps);
495
+ if (!command) return { available: false };
496
+ return {
497
+ available: true,
498
+ version: readCommandVersion(command, [], deps) ?? void 0
499
+ };
500
+ }
409
501
  var ClaudeDriver = class {
410
502
  id = "claude";
411
503
  supportsStdinNotification = true;
412
504
  mcpToolPrefix = "mcp__chat__";
413
505
  busyDeliveryMode = "notification";
506
+ probe() {
507
+ return probeClaude();
508
+ }
414
509
  spawn(ctx) {
415
510
  const mcpArgs = [
416
511
  ctx.chatBridgePath,
@@ -432,7 +527,7 @@ var ClaudeDriver = class {
432
527
  });
433
528
  let mcpConfigArg;
434
529
  if (process.platform === "win32") {
435
- const mcpConfigPath = path.join(ctx.workingDirectory, ".slock-claude-mcp.json");
530
+ const mcpConfigPath = path2.join(ctx.workingDirectory, ".slock-claude-mcp.json");
436
531
  writeFileSync(mcpConfigPath, mcpConfig, "utf8");
437
532
  mcpConfigArg = mcpConfigPath;
438
533
  } else {
@@ -458,7 +553,7 @@ var ClaudeDriver = class {
458
553
  }
459
554
  const spawnEnv = { ...process.env, FORCE_COLOR: "0", ...ctx.config.envVars || {} };
460
555
  delete spawnEnv.CLAUDECODE;
461
- const proc = spawn("claude", args, {
556
+ const proc = spawn(resolveClaudeCommand() ?? "claude", args, {
462
557
  cwd: ctx.workingDirectory,
463
558
  stdio: ["pipe", "pipe", "pipe"],
464
559
  env: spawnEnv,
@@ -633,9 +728,9 @@ var ClaudeDriver = class {
633
728
 
634
729
  // src/drivers/codex.ts
635
730
  import { spawn as spawn2, execSync } from "child_process";
636
- import { existsSync, readFileSync } from "fs";
731
+ import { existsSync as existsSync2, readFileSync } from "fs";
637
732
  import os from "os";
638
- import path2 from "path";
733
+ import path3 from "path";
639
734
  function getCodexNotificationErrorMessage(params) {
640
735
  const topLevelMessage = params?.message;
641
736
  if (typeof topLevelMessage === "string" && topLevelMessage.trim()) {
@@ -648,8 +743,8 @@ function getCodexNotificationErrorMessage(params) {
648
743
  return null;
649
744
  }
650
745
  function ensureGitRepo(workingDirectory) {
651
- const gitDir = path2.join(workingDirectory, ".git");
652
- if (existsSync(gitDir)) return;
746
+ const gitDir = path3.join(workingDirectory, ".git");
747
+ if (existsSync2(gitDir)) return;
653
748
  execSync("git init", { cwd: workingDirectory, stdio: "pipe" });
654
749
  execSync("git add -A && git commit --allow-empty -m 'init'", {
655
750
  cwd: workingDirectory,
@@ -663,22 +758,48 @@ function ensureGitRepo(workingDirectory) {
663
758
  }
664
759
  });
665
760
  }
666
- function resolveCodexSpawn(commandArgs) {
667
- if (process.platform !== "win32") {
668
- return { command: "codex", args: commandArgs };
761
+ var CODEX_DESKTOP_BUNDLE_PATH = "/Applications/Codex.app/Contents/Resources/codex";
762
+ function resolveCodexCommand(deps = {}) {
763
+ const pathCommand = resolveCommandOnPath("codex", deps);
764
+ if (pathCommand) return pathCommand;
765
+ if ((deps.platform ?? process.platform) !== "darwin") return null;
766
+ return firstExistingPath([CODEX_DESKTOP_BUNDLE_PATH], deps);
767
+ }
768
+ function probeCodex(deps = {}) {
769
+ if ((deps.platform ?? process.platform) === "win32") {
770
+ try {
771
+ const resolved = resolveCodexSpawn([], deps);
772
+ return {
773
+ available: true,
774
+ version: readCommandVersion(resolved.command, resolved.args, deps) ?? void 0
775
+ };
776
+ } catch {
777
+ return { available: false };
778
+ }
779
+ }
780
+ const command = resolveCodexCommand(deps);
781
+ if (!command) return { available: false };
782
+ return {
783
+ available: true,
784
+ version: readCommandVersion(command, [], deps) ?? void 0
785
+ };
786
+ }
787
+ function resolveCodexSpawn(commandArgs, deps = {}) {
788
+ if ((deps.platform ?? process.platform) !== "win32") {
789
+ return { command: resolveCodexCommand(deps) ?? "codex", args: commandArgs };
669
790
  }
670
791
  let codexEntry = null;
671
792
  try {
672
793
  const globalRoot = execSync("npm root -g", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
673
- const candidate = path2.join(globalRoot, "@openai", "codex", "bin", "codex.js");
674
- if (existsSync(candidate)) codexEntry = candidate;
794
+ const candidate = path3.join(globalRoot, "@openai", "codex", "bin", "codex.js");
795
+ if (existsSync2(candidate)) codexEntry = candidate;
675
796
  } catch {
676
797
  }
677
798
  if (!codexEntry) {
678
799
  try {
679
800
  const cmdPath = execSync("where codex", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim().split(/\r?\n/)[0];
680
- const candidate = path2.join(path2.dirname(cmdPath), "node_modules", "@openai", "codex", "bin", "codex.js");
681
- if (existsSync(candidate)) codexEntry = candidate;
801
+ const candidate = path3.join(path3.dirname(cmdPath), "node_modules", "@openai", "codex", "bin", "codex.js");
802
+ if (existsSync2(candidate)) codexEntry = candidate;
682
803
  } catch {
683
804
  }
684
805
  }
@@ -708,6 +829,9 @@ var CodexDriver = class {
708
829
  supportsStdinNotification = true;
709
830
  mcpToolPrefix = "mcp_chat_";
710
831
  busyDeliveryMode = "direct";
832
+ probe() {
833
+ return probeCodex();
834
+ }
711
835
  process = null;
712
836
  requestId = 0;
713
837
  threadId = null;
@@ -1095,8 +1219,8 @@ var CodexDriver = class {
1095
1219
  }
1096
1220
  };
1097
1221
  function detectCodexModels(home = os.homedir()) {
1098
- const cachePath = path2.join(home, ".codex", "models_cache.json");
1099
- const configPath = path2.join(home, ".codex", "config.toml");
1222
+ const cachePath = path3.join(home, ".codex", "models_cache.json");
1223
+ const configPath = path3.join(home, ".codex", "config.toml");
1100
1224
  let models = [];
1101
1225
  try {
1102
1226
  const raw = readFileSync(cachePath, "utf8");
@@ -1126,7 +1250,7 @@ function detectCodexModels(home = os.homedir()) {
1126
1250
 
1127
1251
  // src/drivers/copilot.ts
1128
1252
  import { spawn as spawn3 } from "child_process";
1129
- import path3 from "path";
1253
+ import path4 from "path";
1130
1254
  import { writeFileSync as writeFileSync2 } from "fs";
1131
1255
  var CopilotDriver = class {
1132
1256
  id = "copilot";
@@ -1141,7 +1265,7 @@ var CopilotDriver = class {
1141
1265
  const isTsSource = ctx.chatBridgePath.endsWith(".ts");
1142
1266
  const mcpCommand = isTsSource ? "npx" : "node";
1143
1267
  const mcpArgs = isTsSource ? ["tsx", ctx.chatBridgePath, "--agent-id", ctx.agentId, "--server-url", ctx.config.serverUrl, "--auth-token", ctx.config.authToken || ctx.daemonApiKey] : [ctx.chatBridgePath, "--agent-id", ctx.agentId, "--server-url", ctx.config.serverUrl, "--auth-token", ctx.config.authToken || ctx.daemonApiKey];
1144
- const mcpConfigPath = path3.join(ctx.workingDirectory, ".slock-copilot-mcp.json");
1268
+ const mcpConfigPath = path4.join(ctx.workingDirectory, ".slock-copilot-mcp.json");
1145
1269
  writeFileSync2(mcpConfigPath, JSON.stringify({
1146
1270
  mcpServers: {
1147
1271
  chat: {
@@ -1308,22 +1432,22 @@ var CopilotDriver = class {
1308
1432
 
1309
1433
  // src/drivers/cursor.ts
1310
1434
  import { spawn as spawn4 } from "child_process";
1311
- import { writeFileSync as writeFileSync3, mkdirSync, existsSync as existsSync2 } from "fs";
1312
- import path4 from "path";
1435
+ import { writeFileSync as writeFileSync3, mkdirSync, existsSync as existsSync3 } from "fs";
1436
+ import path5 from "path";
1313
1437
  var CursorDriver = class {
1314
1438
  id = "cursor";
1315
1439
  supportsStdinNotification = false;
1316
1440
  mcpToolPrefix = "mcp__chat__";
1317
1441
  busyDeliveryMode = "none";
1318
1442
  spawn(ctx) {
1319
- const cursorDir = path4.join(ctx.workingDirectory, ".cursor");
1320
- if (!existsSync2(cursorDir)) {
1443
+ const cursorDir = path5.join(ctx.workingDirectory, ".cursor");
1444
+ if (!existsSync3(cursorDir)) {
1321
1445
  mkdirSync(cursorDir, { recursive: true });
1322
1446
  }
1323
1447
  const isTsSource = ctx.chatBridgePath.endsWith(".ts");
1324
1448
  const mcpCommand = isTsSource ? "npx" : "node";
1325
1449
  const mcpArgs = isTsSource ? ["tsx", ctx.chatBridgePath, "--agent-id", ctx.agentId, "--server-url", ctx.config.serverUrl, "--auth-token", ctx.config.authToken || ctx.daemonApiKey] : [ctx.chatBridgePath, "--agent-id", ctx.agentId, "--server-url", ctx.config.serverUrl, "--auth-token", ctx.config.authToken || ctx.daemonApiKey];
1326
- const mcpConfigPath = path4.join(cursorDir, "mcp.json");
1450
+ const mcpConfigPath = path5.join(cursorDir, "mcp.json");
1327
1451
  writeFileSync3(mcpConfigPath, JSON.stringify({
1328
1452
  mcpServers: {
1329
1453
  chat: {
@@ -1485,8 +1609,8 @@ var CursorDriver = class {
1485
1609
 
1486
1610
  // src/drivers/gemini.ts
1487
1611
  import { spawn as spawn5 } from "child_process";
1488
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
1489
- import path5 from "path";
1612
+ import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
1613
+ import path6 from "path";
1490
1614
  var GeminiDriver = class {
1491
1615
  id = "gemini";
1492
1616
  supportsStdinNotification = false;
@@ -1497,14 +1621,14 @@ var GeminiDriver = class {
1497
1621
  spawn(ctx) {
1498
1622
  this.sessionId = ctx.config.sessionId || null;
1499
1623
  this.sessionAnnounced = false;
1500
- const geminiDir = path5.join(ctx.workingDirectory, ".gemini");
1501
- if (!existsSync3(geminiDir)) {
1624
+ const geminiDir = path6.join(ctx.workingDirectory, ".gemini");
1625
+ if (!existsSync4(geminiDir)) {
1502
1626
  mkdirSync2(geminiDir, { recursive: true });
1503
1627
  }
1504
1628
  const isTsSource = ctx.chatBridgePath.endsWith(".ts");
1505
1629
  const mcpCommand = isTsSource ? "npx" : "node";
1506
1630
  const mcpArgs = isTsSource ? ["tsx", ctx.chatBridgePath, "--agent-id", ctx.agentId, "--server-url", ctx.config.serverUrl, "--auth-token", ctx.config.authToken || ctx.daemonApiKey] : [ctx.chatBridgePath, "--agent-id", ctx.agentId, "--server-url", ctx.config.serverUrl, "--auth-token", ctx.config.authToken || ctx.daemonApiKey];
1507
- const settingsPath = path5.join(geminiDir, "settings.json");
1631
+ const settingsPath = path6.join(geminiDir, "settings.json");
1508
1632
  writeFileSync4(settingsPath, JSON.stringify({
1509
1633
  mcpServers: {
1510
1634
  chat: {
@@ -1645,9 +1769,9 @@ var GeminiDriver = class {
1645
1769
  // src/drivers/kimi.ts
1646
1770
  import { randomUUID } from "crypto";
1647
1771
  import { spawn as spawn6 } from "child_process";
1648
- import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync5 } from "fs";
1772
+ import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync5 } from "fs";
1649
1773
  import os2 from "os";
1650
- import path6 from "path";
1774
+ import path7 from "path";
1651
1775
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
1652
1776
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
1653
1777
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
@@ -1676,10 +1800,10 @@ var KimiDriver = class {
1676
1800
  const isTsSource = ctx.chatBridgePath.endsWith(".ts");
1677
1801
  const command = isTsSource ? "npx" : "node";
1678
1802
  const bridgeArgs = isTsSource ? ["tsx", ctx.chatBridgePath, "--agent-id", ctx.agentId, "--server-url", ctx.config.serverUrl, "--auth-token", ctx.config.authToken || ctx.daemonApiKey] : [ctx.chatBridgePath, "--agent-id", ctx.agentId, "--server-url", ctx.config.serverUrl, "--auth-token", ctx.config.authToken || ctx.daemonApiKey];
1679
- const systemPromptPath = path6.join(ctx.workingDirectory, KIMI_SYSTEM_PROMPT_FILE);
1680
- const agentFilePath = path6.join(ctx.workingDirectory, KIMI_AGENT_FILE);
1681
- const mcpConfigPath = path6.join(ctx.workingDirectory, KIMI_MCP_FILE);
1682
- if (!isResume || !existsSync4(systemPromptPath)) {
1803
+ const systemPromptPath = path7.join(ctx.workingDirectory, KIMI_SYSTEM_PROMPT_FILE);
1804
+ const agentFilePath = path7.join(ctx.workingDirectory, KIMI_AGENT_FILE);
1805
+ const mcpConfigPath = path7.join(ctx.workingDirectory, KIMI_MCP_FILE);
1806
+ if (!isResume || !existsSync5(systemPromptPath)) {
1683
1807
  writeFileSync5(systemPromptPath, ctx.prompt, "utf8");
1684
1808
  }
1685
1809
  writeFileSync5(agentFilePath, [
@@ -1871,7 +1995,7 @@ var KimiDriver = class {
1871
1995
  }
1872
1996
  };
1873
1997
  function detectKimiModels(home = os2.homedir()) {
1874
- const configPath = path6.join(home, ".kimi", "config.toml");
1998
+ const configPath = path7.join(home, ".kimi", "config.toml");
1875
1999
  let raw;
1876
2000
  try {
1877
2001
  raw = readFileSync2(configPath, "utf8");
@@ -1914,52 +2038,9 @@ function getDriver(runtimeId) {
1914
2038
  return driver;
1915
2039
  }
1916
2040
 
1917
- // src/logger.ts
1918
- var listeners = /* @__PURE__ */ new Set();
1919
- function timestamp() {
1920
- const d = /* @__PURE__ */ new Date();
1921
- const pad = (n) => String(n).padStart(2, "0");
1922
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
1923
- }
1924
- function format(level, msg) {
1925
- return `${timestamp()} [${level}] ${msg}`;
1926
- }
1927
- function emit(event) {
1928
- for (const listener of listeners) {
1929
- listener(event);
1930
- }
1931
- }
1932
- function subscribeDaemonLogs(listener) {
1933
- listeners.add(listener);
1934
- return () => {
1935
- listeners.delete(listener);
1936
- };
1937
- }
1938
- var logger = {
1939
- info(msg) {
1940
- const line = format("INFO", msg);
1941
- console.log(line);
1942
- emit({ level: "INFO", line, message: msg });
1943
- },
1944
- warn(msg) {
1945
- const line = format("WARN", msg);
1946
- console.warn(line);
1947
- emit({ level: "WARN", line, message: msg });
1948
- },
1949
- error(msg, err) {
1950
- const line = format("ERROR", msg);
1951
- if (err) {
1952
- console.error(line, err);
1953
- } else {
1954
- console.error(line);
1955
- }
1956
- emit({ level: "ERROR", line, message: msg, error: err });
1957
- }
1958
- };
1959
-
1960
2041
  // src/workspaces.ts
1961
2042
  import { readdir, rm, stat } from "fs/promises";
1962
- import path7 from "path";
2043
+ import path8 from "path";
1963
2044
  function isValidWorkspaceDirectoryName(directoryName) {
1964
2045
  return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
1965
2046
  }
@@ -1967,7 +2048,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
1967
2048
  if (!isValidWorkspaceDirectoryName(directoryName)) {
1968
2049
  return null;
1969
2050
  }
1970
- return path7.join(dataDir, directoryName);
2051
+ return path8.join(dataDir, directoryName);
1971
2052
  }
1972
2053
  function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
1973
2054
  return {
@@ -2016,7 +2097,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
2016
2097
  return summary;
2017
2098
  }
2018
2099
  const childSummaries = await Promise.all(
2019
- entries.map((entry) => summarizeWorkspaceEntry(path7.join(dirPath, entry.name), entry))
2100
+ entries.map((entry) => summarizeWorkspaceEntry(path8.join(dirPath, entry.name), entry))
2020
2101
  );
2021
2102
  for (const childSummary of childSummaries) {
2022
2103
  summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
@@ -2035,7 +2116,7 @@ async function scanWorkspaceDirectories(dataDir) {
2035
2116
  if (!entry.isDirectory()) {
2036
2117
  return null;
2037
2118
  }
2038
- const dirPath = path7.join(dataDir, entry.name);
2119
+ const dirPath = path8.join(dataDir, entry.name);
2039
2120
  try {
2040
2121
  const summary = await summarizeWorkspaceDirectory(dirPath);
2041
2122
  return {
@@ -2067,7 +2148,7 @@ async function deleteWorkspaceDirectory(dataDir, directoryName) {
2067
2148
  }
2068
2149
 
2069
2150
  // src/agentProcessManager.ts
2070
- var DATA_DIR = path8.join(os3.homedir(), ".slock", "agents");
2151
+ var DATA_DIR = path9.join(os3.homedir(), ".slock", "agents");
2071
2152
  function toLocalTime(iso) {
2072
2153
  const d = new Date(iso);
2073
2154
  if (isNaN(d.getTime())) return iso;
@@ -2193,6 +2274,19 @@ function summarizeCrash(code, signal) {
2193
2274
  if (typeof code === "number") return `exit code ${code}`;
2194
2275
  return "unknown exit";
2195
2276
  }
2277
+ function classifyTerminalFailure(ap) {
2278
+ const candidates = [
2279
+ ap.lastRuntimeError,
2280
+ ...ap.recentStderr
2281
+ ].filter((value) => !!value);
2282
+ for (const text of candidates) {
2283
+ const lower = text.toLowerCase();
2284
+ if (lower.includes("usage limit") || lower.includes("quota exceeded") || lower.includes("quota limit") || lower.includes("budget limit exceeded") || lower.includes("usage not included in your plan") || lower.includes("modelnotfounderror") || lower.includes("requested entity was not found") || lower.includes("model deprecated") || lower.includes("model not found")) {
2285
+ return text;
2286
+ }
2287
+ }
2288
+ return null;
2289
+ }
2196
2290
  function isMissingResumeSession(ap) {
2197
2291
  if (ap.driver.id !== "claude") return false;
2198
2292
  if (!ap.sessionId) return false;
@@ -2241,9 +2335,9 @@ var AgentProcessManager = class _AgentProcessManager {
2241
2335
  this.agentsStarting.add(agentId);
2242
2336
  try {
2243
2337
  const driver = this.driverResolver(config.runtime || "claude");
2244
- const agentDataDir = path8.join(this.dataDir, agentId);
2338
+ const agentDataDir = path9.join(this.dataDir, agentId);
2245
2339
  await mkdir(agentDataDir, { recursive: true });
2246
- const memoryMdPath = path8.join(agentDataDir, "MEMORY.md");
2340
+ const memoryMdPath = path9.join(agentDataDir, "MEMORY.md");
2247
2341
  try {
2248
2342
  await access(memoryMdPath);
2249
2343
  } catch {
@@ -2261,7 +2355,7 @@ ${config.description || "No role defined yet."}
2261
2355
  `;
2262
2356
  await writeFile(memoryMdPath, initialMemoryMd);
2263
2357
  }
2264
- await mkdir(path8.join(agentDataDir, "notes"), { recursive: true });
2358
+ await mkdir(path9.join(agentDataDir, "notes"), { recursive: true });
2265
2359
  const isResume = !!config.sessionId;
2266
2360
  let prompt;
2267
2361
  if (isResume && resumePrompt) {
@@ -2398,6 +2492,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2398
2492
  this.agents.delete(agentId);
2399
2493
  const finalCode = ap.exitCode ?? code;
2400
2494
  const finalSignal = ap.exitSignal ?? signal;
2495
+ const terminalFailureDetail = classifyTerminalFailure(ap);
2401
2496
  if (finalCode === 0) {
2402
2497
  const queuedWakeMessage = !ap.driver.supportsStdinNotification ? ap.inbox.shift() : void 0;
2403
2498
  const unreadSummary2 = queuedWakeMessage ? buildUnreadSummary(ap.inbox, formatChannelLabel(queuedWakeMessage)) : void 0;
@@ -2450,13 +2545,23 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2450
2545
  this.startAgent(agentId, restartConfig, void 0, void 0, void 0, ap.launchId || void 0).catch((err) => {
2451
2546
  logger.error(`[Agent ${agentId}] Cold start recovery failed`, err);
2452
2547
  this.sendAgentStatus(agentId, "inactive", ap.launchId);
2453
- this.broadcastActivity(agentId, "offline", `Crashed (${summary})`);
2548
+ this.broadcastActivity(agentId, "offline", `Crashed (${summary})`, [], ap.launchId);
2454
2549
  });
2455
2550
  return;
2456
2551
  }
2457
2552
  logger.error(`[Agent ${agentId}] Process crashed (${reason}) \u2014 marking inactive`);
2458
2553
  this.sendAgentStatus(agentId, "inactive", ap.launchId);
2459
- this.broadcastActivity(agentId, "offline", `Crashed (${summary})`);
2554
+ if (terminalFailureDetail) {
2555
+ this.broadcastActivity(
2556
+ agentId,
2557
+ "error",
2558
+ terminalFailureDetail,
2559
+ [{ kind: "text", text: `Error: ${terminalFailureDetail}` }],
2560
+ ap.launchId
2561
+ );
2562
+ } else {
2563
+ this.broadcastActivity(agentId, "offline", `Crashed (${summary})`, [], ap.launchId);
2564
+ }
2460
2565
  }
2461
2566
  }
2462
2567
  });
@@ -2590,7 +2695,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2590
2695
  }
2591
2696
  }
2592
2697
  async resetWorkspace(agentId) {
2593
- const agentDataDir = path8.join(this.dataDir, agentId);
2698
+ const agentDataDir = path9.join(this.dataDir, agentId);
2594
2699
  try {
2595
2700
  await rm2(agentDataDir, { recursive: true, force: true });
2596
2701
  logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
@@ -2628,7 +2733,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2628
2733
  }
2629
2734
  // Workspace file browsing
2630
2735
  async getFileTree(agentId, dirPath) {
2631
- const agentDir = path8.join(this.dataDir, agentId);
2736
+ const agentDir = path9.join(this.dataDir, agentId);
2632
2737
  try {
2633
2738
  await stat2(agentDir);
2634
2739
  } catch {
@@ -2636,8 +2741,8 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2636
2741
  }
2637
2742
  let targetDir = agentDir;
2638
2743
  if (dirPath) {
2639
- const resolved = path8.resolve(agentDir, dirPath);
2640
- if (!resolved.startsWith(agentDir + path8.sep) && resolved !== agentDir) {
2744
+ const resolved = path9.resolve(agentDir, dirPath);
2745
+ if (!resolved.startsWith(agentDir + path9.sep) && resolved !== agentDir) {
2641
2746
  return [];
2642
2747
  }
2643
2748
  targetDir = resolved;
@@ -2645,9 +2750,9 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2645
2750
  return this.listDirectoryChildren(targetDir, agentDir);
2646
2751
  }
2647
2752
  async readFile(agentId, filePath) {
2648
- const agentDir = path8.join(this.dataDir, agentId);
2649
- const resolved = path8.resolve(agentDir, filePath);
2650
- if (!resolved.startsWith(agentDir + path8.sep) && resolved !== agentDir) {
2753
+ const agentDir = path9.join(this.dataDir, agentId);
2754
+ const resolved = path9.resolve(agentDir, filePath);
2755
+ if (!resolved.startsWith(agentDir + path9.sep) && resolved !== agentDir) {
2651
2756
  throw new Error("Access denied");
2652
2757
  }
2653
2758
  const info = await stat2(resolved);
@@ -2671,7 +2776,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2671
2776
  ".sh",
2672
2777
  ".py"
2673
2778
  ]);
2674
- const ext = path8.extname(resolved).toLowerCase();
2779
+ const ext = path9.extname(resolved).toLowerCase();
2675
2780
  if (!TEXT_EXTENSIONS.has(ext) && ext !== "") {
2676
2781
  return { content: null, binary: true };
2677
2782
  }
@@ -2698,13 +2803,13 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2698
2803
  const agent = this.agents.get(agentId);
2699
2804
  const runtime = runtimeHint || agent?.config.runtime || "claude";
2700
2805
  const home = os3.homedir();
2701
- const workspaceDir = path8.join(this.dataDir, agentId);
2806
+ const workspaceDir = path9.join(this.dataDir, agentId);
2702
2807
  const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
2703
2808
  const globalResults = await Promise.all(
2704
- paths.global.map((p) => this.scanSkillsDir(path8.join(home, p)))
2809
+ paths.global.map((p) => this.scanSkillsDir(path9.join(home, p)))
2705
2810
  );
2706
2811
  const workspaceResults = await Promise.all(
2707
- paths.workspace.map((p) => this.scanSkillsDir(path8.join(workspaceDir, p)))
2812
+ paths.workspace.map((p) => this.scanSkillsDir(path9.join(workspaceDir, p)))
2708
2813
  );
2709
2814
  const dedup = (skills) => {
2710
2815
  const seen = /* @__PURE__ */ new Set();
@@ -2733,7 +2838,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2733
2838
  const skills = [];
2734
2839
  for (const entry of entries) {
2735
2840
  if (entry.isDirectory() || entry.isSymbolicLink()) {
2736
- const skillMd = path8.join(dir, entry.name, "SKILL.md");
2841
+ const skillMd = path9.join(dir, entry.name, "SKILL.md");
2737
2842
  try {
2738
2843
  const content = await readFile(skillMd, "utf-8");
2739
2844
  const skill = this.parseSkillMd(entry.name, content);
@@ -2744,7 +2849,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2744
2849
  } else if (entry.name.endsWith(".md")) {
2745
2850
  const cmdName = entry.name.replace(/\.md$/, "");
2746
2851
  try {
2747
- const content = await readFile(path8.join(dir, entry.name), "utf-8");
2852
+ const content = await readFile(path9.join(dir, entry.name), "utf-8");
2748
2853
  const skill = this.parseSkillMd(cmdName, content);
2749
2854
  skill.sourcePath = dir;
2750
2855
  skills.push(skill);
@@ -2780,14 +2885,21 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2780
2885
  * Broadcast an activity change — emits a single agent:activity event that carries
2781
2886
  * both the status (for the dot indicator) and trajectory entries (for the activity log).
2782
2887
  */
2783
- broadcastActivity(agentId, activity, detail, extraTrajectory = []) {
2888
+ broadcastActivity(agentId, activity, detail, extraTrajectory = [], launchIdOverride) {
2784
2889
  const ap = this.agents.get(agentId);
2785
2890
  const entries = [...extraTrajectory];
2786
2891
  const hasToolStart = entries.some((e) => e.kind === "tool_start");
2787
2892
  if (!hasToolStart) {
2788
2893
  entries.push({ kind: "status", activity, detail });
2789
2894
  }
2790
- this.sendToServer({ type: "agent:activity", agentId, activity, detail, entries, launchId: ap?.launchId || void 0 });
2895
+ this.sendToServer({
2896
+ type: "agent:activity",
2897
+ agentId,
2898
+ activity,
2899
+ detail,
2900
+ entries,
2901
+ launchId: launchIdOverride || ap?.launchId || void 0
2902
+ });
2791
2903
  if (ap) {
2792
2904
  ap.lastActivity = activity;
2793
2905
  ap.lastActivityDetail = detail;
@@ -2799,7 +2911,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2799
2911
  agentId,
2800
2912
  activity: ap.lastActivity,
2801
2913
  detail: ap.lastActivityDetail,
2802
- launchId: ap.launchId || void 0
2914
+ launchId: launchIdOverride || ap.launchId || void 0
2803
2915
  });
2804
2916
  }, ACTIVITY_HEARTBEAT_MS);
2805
2917
  }
@@ -2982,8 +3094,8 @@ Respond as appropriate. Complete all your work before stopping.`;
2982
3094
  const nodes = [];
2983
3095
  for (const entry of entries) {
2984
3096
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
2985
- const fullPath = path8.join(dir, entry.name);
2986
- const relativePath = path8.relative(rootDir, fullPath);
3097
+ const fullPath = path9.join(dir, entry.name);
3098
+ const relativePath = path9.relative(rootDir, fullPath);
2987
3099
  let info;
2988
3100
  try {
2989
3101
  info = await stat2(fullPath);
@@ -3154,13 +3266,13 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
3154
3266
  }
3155
3267
  }
3156
3268
  function resolveChatBridgePath(moduleUrl = import.meta.url) {
3157
- const dirname = path9.dirname(fileURLToPath(moduleUrl));
3158
- const jsPath = path9.resolve(dirname, "chat-bridge.js");
3269
+ const dirname = path10.dirname(fileURLToPath(moduleUrl));
3270
+ const jsPath = path10.resolve(dirname, "chat-bridge.js");
3159
3271
  try {
3160
3272
  accessSync(jsPath);
3161
3273
  return jsPath;
3162
3274
  } catch {
3163
- return path9.resolve(dirname, "chat-bridge.ts");
3275
+ return path10.resolve(dirname, "chat-bridge.ts");
3164
3276
  }
3165
3277
  }
3166
3278
  function detectRuntimes() {
@@ -3168,6 +3280,15 @@ function detectRuntimes() {
3168
3280
  const versions = {};
3169
3281
  const cmd = process.platform === "win32" ? "where" : "which";
3170
3282
  for (const runtime of RUNTIMES) {
3283
+ try {
3284
+ const probe = getDriver(runtime.id).probe?.();
3285
+ if (probe?.available) {
3286
+ ids.push(runtime.id);
3287
+ if (probe.version) versions[runtime.id] = probe.version;
3288
+ continue;
3289
+ }
3290
+ } catch {
3291
+ }
3171
3292
  try {
3172
3293
  execSync2(`${cmd} ${runtime.binary}`, { stdio: "pipe" });
3173
3294
  ids.push(runtime.id);
@@ -3374,7 +3495,6 @@ var DaemonCore = class {
3374
3495
  };
3375
3496
 
3376
3497
  export {
3377
- subscribeDaemonLogs,
3378
3498
  resolveWorkspaceDirectoryPath,
3379
3499
  scanWorkspaceDirectories,
3380
3500
  deleteWorkspaceDirectory,