@slock-ai/daemon 0.34.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 path6 from "path";
7
- import os2 from "os";
7
+ import path10 from "path";
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,
@@ -38,7 +60,7 @@ var SERVER_CAPABILITY_MATRIX = {
38
60
  manageAgents: true,
39
61
  manageMachines: true,
40
62
  manageMembers: true,
41
- changeMemberRoles: false,
63
+ changeMemberRoles: true,
42
64
  manageBilling: false,
43
65
  joinPublicChannels: true
44
66
  }),
@@ -58,8 +80,10 @@ var SERVER_CAPABILITY_MATRIX = {
58
80
  var RUNTIMES = [
59
81
  { id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
60
82
  { id: "codex", displayName: "Codex CLI", binary: "codex", supported: true },
61
- { id: "gemini", displayName: "Gemini CLI", binary: "gemini", supported: false },
62
- { id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true }
83
+ { id: "kimi", displayName: "Kimi CLI", binary: "kimi", supported: true },
84
+ { id: "copilot", displayName: "Copilot CLI", binary: "copilot", supported: true },
85
+ { id: "cursor", displayName: "Cursor CLI", binary: "agent", supported: true },
86
+ { id: "gemini", displayName: "Gemini CLI", binary: "gemini", supported: true }
63
87
  ];
64
88
  var PLAN_CONFIG = {
65
89
  free: {
@@ -97,13 +121,13 @@ var DISPLAY_PLAN_CONFIG = {
97
121
 
98
122
  // src/agentProcessManager.ts
99
123
  import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
100
- import path5 from "path";
101
- import os from "os";
124
+ import path9 from "path";
125
+ import os3 from "os";
102
126
 
103
127
  // src/drivers/claude.ts
104
128
  import { spawn } from "child_process";
105
129
  import { writeFileSync } from "fs";
106
- import path from "path";
130
+ import path2 from "path";
107
131
 
108
132
  // src/drivers/systemPrompt.ts
109
133
  function toolRef(prefix, name) {
@@ -145,8 +169,8 @@ You have MCP tools from the "chat" server. Use ONLY these for communication:
145
169
  8. **${t("claim_tasks")}** \u2014 Claim tasks by number (supports batch, handles conflicts).
146
170
  9. **${t("unclaim_task")}** \u2014 Release your claim on a task.
147
171
  10. **${t("update_task_status")}** \u2014 Change a task's status (e.g. to in_review or done).
148
- 11. **${t("upload_file")}** \u2014 Upload an image file to attach to a message. Returns an attachment ID to pass to send_message.
149
- 12. **${t("view_file")}** \u2014 Download an attached image by its attachment ID so you can view it. Use when messages contain image attachments.
172
+ 11. **${t("upload_file")}** \u2014 Upload a file to attach to a message. Returns an attachment ID to pass to send_message.
173
+ 12. **${t("view_file")}** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
150
174
 
151
175
  CRITICAL RULES:
152
176
  ${criticalRules.join("\n")}
@@ -166,18 +190,18 @@ ${opts.postStartupNotes.join("\n")}`;
166
190
  Messages you receive have a single RFC 5424-style structured data header followed by the sender and content:
167
191
 
168
192
  \`\`\`
169
- [target=#general msg=a1b2c3d4 time=2026-03-15T01:00:00] @richard: hello everyone
193
+ [target=#general msg=a1b2c3d4 time=2026-03-15T01:00:00 type=human] @richard: hello everyone
170
194
  [target=#general msg=e5f6a7b8 time=2026-03-15T01:00:01 type=agent] @Alice: hi there
171
- [target=dm:@richard msg=c9d0e1f2 time=2026-03-15T01:00:02] @richard: hey, can you help?
172
- [target=#general:a1b2c3d4 msg=f3a4b5c6 time=2026-03-15T01:00:03] @richard: thread reply
173
- [target=dm:@richard:x9y8z7a0 msg=d7e8f9a0 time=2026-03-15T01:00:04] @richard: DM thread reply
195
+ [target=dm:@richard msg=c9d0e1f2 time=2026-03-15T01:00:02 type=human] @richard: hey, can you help?
196
+ [target=#general:a1b2c3d4 msg=f3a4b5c6 time=2026-03-15T01:00:03 type=human] @richard: thread reply
197
+ [target=dm:@richard:x9y8z7a0 msg=d7e8f9a0 time=2026-03-15T01:00:04 type=human] @richard: DM thread reply
174
198
  \`\`\`
175
199
 
176
200
  Header fields:
177
201
  - \`target=\` \u2014 where the message came from. Reuse as the \`target\` parameter when replying.
178
202
  - \`msg=\` \u2014 message short ID (first 8 chars of UUID). Use as thread suffix to start/reply in a thread.
179
203
  - \`time=\` \u2014 timestamp.
180
- - \`type=agent\` \u2014 present only if the sender is an agent.
204
+ - \`type=\` \u2014 sender kind. Values are \`human\`, \`agent\`, or \`system\`.
181
205
 
182
206
  ### Sending messages
183
207
 
@@ -295,6 +319,8 @@ Keep the user informed. They cannot see your internal reasoning, so:
295
319
 
296
320
  Use plain-text @mentions (e.g. \`@alice\`) and #channel references (e.g. \`#general\`, \`#1\`) \u2014 no HTML tags.
297
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
+
298
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.
299
325
 
300
326
  ### Formatting \u2014 URLs in non-English text
@@ -403,11 +429,83 @@ ${config.description}. This may evolve.`;
403
429
  return prompt;
404
430
  }
405
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
+
406
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
+ }
407
501
  var ClaudeDriver = class {
408
502
  id = "claude";
409
503
  supportsStdinNotification = true;
410
504
  mcpToolPrefix = "mcp__chat__";
505
+ busyDeliveryMode = "notification";
506
+ probe() {
507
+ return probeClaude();
508
+ }
411
509
  spawn(ctx) {
412
510
  const mcpArgs = [
413
511
  ctx.chatBridgePath,
@@ -429,7 +527,7 @@ var ClaudeDriver = class {
429
527
  });
430
528
  let mcpConfigArg;
431
529
  if (process.platform === "win32") {
432
- const mcpConfigPath = path.join(ctx.workingDirectory, ".slock-claude-mcp.json");
530
+ const mcpConfigPath = path2.join(ctx.workingDirectory, ".slock-claude-mcp.json");
433
531
  writeFileSync(mcpConfigPath, mcpConfig, "utf8");
434
532
  mcpConfigArg = mcpConfigPath;
435
533
  } else {
@@ -455,7 +553,7 @@ var ClaudeDriver = class {
455
553
  }
456
554
  const spawnEnv = { ...process.env, FORCE_COLOR: "0", ...ctx.config.envVars || {} };
457
555
  delete spawnEnv.CLAUDECODE;
458
- const proc = spawn("claude", args, {
556
+ const proc = spawn(resolveClaudeCommand() ?? "claude", args, {
459
557
  cwd: ctx.workingDirectory,
460
558
  stdio: ["pipe", "pipe", "pipe"],
461
559
  env: spawnEnv,
@@ -630,11 +728,23 @@ var ClaudeDriver = class {
630
728
 
631
729
  // src/drivers/codex.ts
632
730
  import { spawn as spawn2, execSync } from "child_process";
633
- import { existsSync } from "fs";
634
- import path2 from "path";
731
+ import { existsSync as existsSync2, readFileSync } from "fs";
732
+ import os from "os";
733
+ import path3 from "path";
734
+ function getCodexNotificationErrorMessage(params) {
735
+ const topLevelMessage = params?.message;
736
+ if (typeof topLevelMessage === "string" && topLevelMessage.trim()) {
737
+ return topLevelMessage;
738
+ }
739
+ const nestedMessage = params?.error?.message;
740
+ if (typeof nestedMessage === "string" && nestedMessage.trim()) {
741
+ return nestedMessage;
742
+ }
743
+ return null;
744
+ }
635
745
  function ensureGitRepo(workingDirectory) {
636
- const gitDir = path2.join(workingDirectory, ".git");
637
- if (existsSync(gitDir)) return;
746
+ const gitDir = path3.join(workingDirectory, ".git");
747
+ if (existsSync2(gitDir)) return;
638
748
  execSync("git init", { cwd: workingDirectory, stdio: "pipe" });
639
749
  execSync("git add -A && git commit --allow-empty -m 'init'", {
640
750
  cwd: workingDirectory,
@@ -648,22 +758,48 @@ function ensureGitRepo(workingDirectory) {
648
758
  }
649
759
  });
650
760
  }
651
- function resolveCodexSpawn(commandArgs) {
652
- if (process.platform !== "win32") {
653
- 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 };
654
790
  }
655
791
  let codexEntry = null;
656
792
  try {
657
793
  const globalRoot = execSync("npm root -g", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
658
- const candidate = path2.join(globalRoot, "@openai", "codex", "bin", "codex.js");
659
- if (existsSync(candidate)) codexEntry = candidate;
794
+ const candidate = path3.join(globalRoot, "@openai", "codex", "bin", "codex.js");
795
+ if (existsSync2(candidate)) codexEntry = candidate;
660
796
  } catch {
661
797
  }
662
798
  if (!codexEntry) {
663
799
  try {
664
800
  const cmdPath = execSync("where codex", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim().split(/\r?\n/)[0];
665
- const candidate = path2.join(path2.dirname(cmdPath), "node_modules", "@openai", "codex", "bin", "codex.js");
666
- 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;
667
803
  } catch {
668
804
  }
669
805
  }
@@ -692,7 +828,10 @@ var CodexDriver = class {
692
828
  id = "codex";
693
829
  supportsStdinNotification = true;
694
830
  mcpToolPrefix = "mcp_chat_";
695
- deliverMessageDirectlyWhileBusy = true;
831
+ busyDeliveryMode = "direct";
832
+ probe() {
833
+ return probeCodex();
834
+ }
696
835
  process = null;
697
836
  requestId = 0;
698
837
  threadId = null;
@@ -931,7 +1070,10 @@ var CodexDriver = class {
931
1070
  break;
932
1071
  }
933
1072
  case "error":
934
- events.push({ kind: "error", message: message.params?.message || "Unknown Codex app-server error" });
1073
+ events.push({
1074
+ kind: "error",
1075
+ message: getCodexNotificationErrorMessage(message.params) || "Unknown Codex app-server error"
1076
+ });
935
1077
  break;
936
1078
  }
937
1079
  return events;
@@ -1072,13 +1214,564 @@ var CodexDriver = class {
1072
1214
  params
1073
1215
  }) + "\n");
1074
1216
  }
1217
+ async detectModels() {
1218
+ return detectCodexModels();
1219
+ }
1220
+ };
1221
+ function detectCodexModels(home = os.homedir()) {
1222
+ const cachePath = path3.join(home, ".codex", "models_cache.json");
1223
+ const configPath = path3.join(home, ".codex", "config.toml");
1224
+ let models = [];
1225
+ try {
1226
+ const raw = readFileSync(cachePath, "utf8");
1227
+ const parsed = JSON.parse(raw);
1228
+ const entries = Array.isArray(parsed?.models) ? parsed.models : [];
1229
+ for (const entry of entries) {
1230
+ const slug = typeof entry?.slug === "string" ? entry.slug : null;
1231
+ if (!slug) continue;
1232
+ if (entry?.visibility && entry.visibility !== "public") continue;
1233
+ if (entry?.supported_in_api === false) continue;
1234
+ const label = typeof entry?.display_name === "string" && entry.display_name.length > 0 ? entry.display_name : slug;
1235
+ models.push({ id: slug, label });
1236
+ }
1237
+ } catch {
1238
+ return null;
1239
+ }
1240
+ if (models.length === 0) return null;
1241
+ let defaultModel;
1242
+ try {
1243
+ const raw = readFileSync(configPath, "utf8");
1244
+ const match = raw.match(/^\s*model\s*=\s*"([^"]+)"/m);
1245
+ if (match) defaultModel = match[1];
1246
+ } catch {
1247
+ }
1248
+ return { models, default: defaultModel };
1249
+ }
1250
+
1251
+ // src/drivers/copilot.ts
1252
+ import { spawn as spawn3 } from "child_process";
1253
+ import path4 from "path";
1254
+ import { writeFileSync as writeFileSync2 } from "fs";
1255
+ var CopilotDriver = class {
1256
+ id = "copilot";
1257
+ supportsStdinNotification = false;
1258
+ mcpToolPrefix = "";
1259
+ busyDeliveryMode = "none";
1260
+ sessionId = null;
1261
+ sessionAnnounced = false;
1262
+ spawn(ctx) {
1263
+ this.sessionId = ctx.config.sessionId || null;
1264
+ this.sessionAnnounced = false;
1265
+ const isTsSource = ctx.chatBridgePath.endsWith(".ts");
1266
+ const mcpCommand = isTsSource ? "npx" : "node";
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];
1268
+ const mcpConfigPath = path4.join(ctx.workingDirectory, ".slock-copilot-mcp.json");
1269
+ writeFileSync2(mcpConfigPath, JSON.stringify({
1270
+ mcpServers: {
1271
+ chat: {
1272
+ command: mcpCommand,
1273
+ args: mcpArgs
1274
+ }
1275
+ }
1276
+ }), "utf8");
1277
+ const args = [
1278
+ "--output-format",
1279
+ "json",
1280
+ "--allow-all-tools",
1281
+ "--allow-all-paths",
1282
+ "--additional-mcp-config",
1283
+ `@${mcpConfigPath}`,
1284
+ "-p",
1285
+ ctx.prompt
1286
+ ];
1287
+ if (ctx.config.model && ctx.config.model !== "default") {
1288
+ args.push("--model", ctx.config.model);
1289
+ }
1290
+ if (ctx.config.reasoningEffort) {
1291
+ args.push("--effort", ctx.config.reasoningEffort);
1292
+ }
1293
+ if (ctx.config.sessionId) {
1294
+ args.push(`--resume=${ctx.config.sessionId}`);
1295
+ }
1296
+ const spawnEnv = { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1", ...ctx.config.envVars || {} };
1297
+ const proc = spawn3("copilot", args, {
1298
+ cwd: ctx.workingDirectory,
1299
+ stdio: ["pipe", "pipe", "pipe"],
1300
+ env: spawnEnv,
1301
+ shell: process.platform === "win32"
1302
+ });
1303
+ return { process: proc };
1304
+ }
1305
+ parseLine(line) {
1306
+ let event;
1307
+ try {
1308
+ event = JSON.parse(line);
1309
+ } catch {
1310
+ return [];
1311
+ }
1312
+ const events = [];
1313
+ const eventType = event.type;
1314
+ const data = event.data || {};
1315
+ if (event.ephemeral && eventType?.startsWith("session.")) {
1316
+ return [];
1317
+ }
1318
+ switch (eventType) {
1319
+ case "assistant.turn_start":
1320
+ if (!this.sessionAnnounced && data.sessionId) {
1321
+ this.sessionId = data.sessionId;
1322
+ events.push({ kind: "session_init", sessionId: data.sessionId });
1323
+ this.sessionAnnounced = true;
1324
+ }
1325
+ events.push({ kind: "thinking", text: "" });
1326
+ break;
1327
+ case "assistant.reasoning":
1328
+ if (data.content) {
1329
+ events.push({ kind: "thinking", text: data.content });
1330
+ }
1331
+ break;
1332
+ case "assistant.message_delta":
1333
+ if (data.deltaContent) {
1334
+ events.push({ kind: "text", text: data.deltaContent });
1335
+ }
1336
+ break;
1337
+ case "assistant.message": {
1338
+ if (Array.isArray(data.toolRequests)) {
1339
+ for (const req of data.toolRequests) {
1340
+ events.push({
1341
+ kind: "tool_call",
1342
+ name: req.name || req.toolName || "unknown_tool",
1343
+ input: req.arguments || req.parameters || req.input || {}
1344
+ });
1345
+ }
1346
+ }
1347
+ if (!event.ephemeral && data.content && typeof data.content === "string") {
1348
+ if (!Array.isArray(data.toolRequests) || data.toolRequests.length === 0) {
1349
+ }
1350
+ }
1351
+ break;
1352
+ }
1353
+ case "assistant.turn_end":
1354
+ events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
1355
+ break;
1356
+ case "result": {
1357
+ const resultSessionId = event.sessionId || data.sessionId;
1358
+ const exitCode = event.exitCode ?? data.exitCode;
1359
+ if (!this.sessionAnnounced && resultSessionId) {
1360
+ this.sessionId = resultSessionId;
1361
+ events.push({ kind: "session_init", sessionId: resultSessionId });
1362
+ this.sessionAnnounced = true;
1363
+ }
1364
+ if (exitCode && exitCode !== 0) {
1365
+ events.push({ kind: "error", message: `Copilot exited with code ${exitCode}` });
1366
+ }
1367
+ events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
1368
+ break;
1369
+ }
1370
+ }
1371
+ return events;
1372
+ }
1373
+ encodeStdinMessage(_text, _sessionId, _opts) {
1374
+ return null;
1375
+ }
1376
+ buildSystemPrompt(config, _agentId) {
1377
+ return buildBaseSystemPrompt(config, {
1378
+ toolPrefix: "",
1379
+ extraCriticalRules: [
1380
+ "- Do NOT use shell commands to send or receive messages. The MCP tools handle everything."
1381
+ ],
1382
+ postStartupNotes: [],
1383
+ includeStdinNotificationSection: false,
1384
+ messageNotificationStyle: "poll"
1385
+ });
1386
+ }
1387
+ toolDisplayName(name) {
1388
+ if (name === "list_tasks") return "Viewing task board\u2026";
1389
+ if (name === "create_tasks") return "Creating tasks\u2026";
1390
+ if (name === "claim_tasks") return "Claiming tasks\u2026";
1391
+ if (name === "unclaim_task") return "Unclaiming task\u2026";
1392
+ if (name === "update_task_status") return "Updating task\u2026";
1393
+ if (name === "send_message" || name === "receive_message" || name === "read_history" || name === "list_server") return "";
1394
+ if (name === "shell" || name === "Shell") return "Running command\u2026";
1395
+ if (name === "read_file" || name === "ReadFile") return "Reading file\u2026";
1396
+ if (name === "write_file" || name === "WriteFile" || name === "edit_file" || name === "EditFile") return "Editing file\u2026";
1397
+ if (name === "search_files" || name === "Glob" || name === "Grep") return "Searching code\u2026";
1398
+ if (name === "web_search" || name === "SearchWeb") return "Searching web\u2026";
1399
+ if (name === "fetch_url" || name === "FetchURL") return "Fetching web\u2026";
1400
+ return `Using ${name.length > 20 ? name.slice(0, 20) + "\u2026" : name}\u2026`;
1401
+ }
1402
+ summarizeToolInput(name, input) {
1403
+ if (!input || typeof input !== "object") return "";
1404
+ try {
1405
+ if (name === "shell" || name === "Shell") {
1406
+ const cmd = input.command || "";
1407
+ return cmd.length > 100 ? cmd.slice(0, 100) + "\u2026" : cmd;
1408
+ }
1409
+ if (name === "read_file" || name === "ReadFile" || name === "write_file" || name === "WriteFile" || name === "edit_file" || name === "EditFile") {
1410
+ return input.path || input.file_path || "";
1411
+ }
1412
+ if (name === "Glob" || name === "Grep" || name === "search_files") return input.pattern || input.query || "";
1413
+ if (name === "web_search" || name === "SearchWeb") return input.query || "";
1414
+ if (name === "fetch_url" || name === "FetchURL") return input.url || "";
1415
+ if (name === "send_message") return input.target || input.channel || "";
1416
+ if (name === "read_history") return input.target || input.channel || "";
1417
+ if (name === "list_tasks") return input.channel || "";
1418
+ if (name === "create_tasks") return input.channel || "";
1419
+ if (name === "claim_tasks") {
1420
+ const nums = input.task_numbers;
1421
+ return input.channel ? `${input.channel} #t${Array.isArray(nums) ? nums.join(",#t") : nums}` : "";
1422
+ }
1423
+ if (name === "unclaim_task" || name === "update_task_status") {
1424
+ return input.channel ? `${input.channel} #t${input.task_number}` : "";
1425
+ }
1426
+ return "";
1427
+ } catch {
1428
+ return "";
1429
+ }
1430
+ }
1431
+ };
1432
+
1433
+ // src/drivers/cursor.ts
1434
+ import { spawn as spawn4 } from "child_process";
1435
+ import { writeFileSync as writeFileSync3, mkdirSync, existsSync as existsSync3 } from "fs";
1436
+ import path5 from "path";
1437
+ var CursorDriver = class {
1438
+ id = "cursor";
1439
+ supportsStdinNotification = false;
1440
+ mcpToolPrefix = "mcp__chat__";
1441
+ busyDeliveryMode = "none";
1442
+ spawn(ctx) {
1443
+ const cursorDir = path5.join(ctx.workingDirectory, ".cursor");
1444
+ if (!existsSync3(cursorDir)) {
1445
+ mkdirSync(cursorDir, { recursive: true });
1446
+ }
1447
+ const isTsSource = ctx.chatBridgePath.endsWith(".ts");
1448
+ const mcpCommand = isTsSource ? "npx" : "node";
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];
1450
+ const mcpConfigPath = path5.join(cursorDir, "mcp.json");
1451
+ writeFileSync3(mcpConfigPath, JSON.stringify({
1452
+ mcpServers: {
1453
+ chat: {
1454
+ command: mcpCommand,
1455
+ args: mcpArgs
1456
+ }
1457
+ }
1458
+ }), "utf8");
1459
+ const args = [
1460
+ "--print",
1461
+ "--output-format",
1462
+ "stream-json",
1463
+ "--yolo",
1464
+ "--approve-mcps",
1465
+ "--trust"
1466
+ ];
1467
+ if (ctx.config.model && ctx.config.model !== "default") {
1468
+ args.push("--model", ctx.config.model);
1469
+ }
1470
+ if (ctx.config.sessionId) {
1471
+ args.push("--resume", ctx.config.sessionId);
1472
+ }
1473
+ args.push(ctx.prompt);
1474
+ const spawnEnv = { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1", ...ctx.config.envVars || {} };
1475
+ const proc = spawn4("agent", args, {
1476
+ cwd: ctx.workingDirectory,
1477
+ stdio: ["pipe", "pipe", "pipe"],
1478
+ env: spawnEnv,
1479
+ shell: process.platform === "win32"
1480
+ });
1481
+ return { process: proc };
1482
+ }
1483
+ parseLine(line) {
1484
+ let event;
1485
+ try {
1486
+ event = JSON.parse(line);
1487
+ } catch {
1488
+ return [];
1489
+ }
1490
+ const events = [];
1491
+ switch (event.type) {
1492
+ case "system":
1493
+ if (event.subtype === "init" && event.session_id) {
1494
+ events.push({ kind: "session_init", sessionId: event.session_id });
1495
+ }
1496
+ break;
1497
+ case "assistant": {
1498
+ const content = event.message?.content;
1499
+ if (Array.isArray(content)) {
1500
+ for (const block of content) {
1501
+ if (block.type === "thinking" && block.thinking) {
1502
+ events.push({ kind: "thinking", text: block.thinking });
1503
+ } else if (block.type === "text" && block.text) {
1504
+ events.push({ kind: "text", text: block.text });
1505
+ } else if (block.type === "tool_use") {
1506
+ events.push({ kind: "tool_call", name: block.name || "unknown_tool", input: block.input });
1507
+ }
1508
+ }
1509
+ }
1510
+ break;
1511
+ }
1512
+ case "result": {
1513
+ const subtype = typeof event.subtype === "string" ? event.subtype : "success";
1514
+ if (subtype !== "success" || event.is_error) {
1515
+ const parts = [];
1516
+ if (Array.isArray(event.errors)) {
1517
+ for (const err of event.errors) {
1518
+ if (typeof err === "string" && err.trim()) parts.push(err.trim());
1519
+ }
1520
+ }
1521
+ if (typeof event.result === "string" && event.result.trim()) {
1522
+ parts.push(event.result.trim());
1523
+ }
1524
+ const detail = parts.join(" | ") || "Execution failed";
1525
+ events.push({ kind: "error", message: detail });
1526
+ }
1527
+ events.push({ kind: "turn_end", sessionId: event.session_id });
1528
+ break;
1529
+ }
1530
+ }
1531
+ return events;
1532
+ }
1533
+ encodeStdinMessage(_text, _sessionId, _opts) {
1534
+ return null;
1535
+ }
1536
+ buildSystemPrompt(config, _agentId) {
1537
+ return buildBaseSystemPrompt(config, {
1538
+ toolPrefix: "mcp__chat__",
1539
+ extraCriticalRules: [
1540
+ "- Do NOT use bash/curl/sqlite to send or receive messages. The MCP tools handle everything."
1541
+ ],
1542
+ postStartupNotes: [],
1543
+ includeStdinNotificationSection: false,
1544
+ messageNotificationStyle: "poll"
1545
+ });
1546
+ }
1547
+ toolDisplayName(name) {
1548
+ if (name === "mcp__chat__upload_file") return "Uploading file\u2026";
1549
+ if (name === "mcp__chat__view_file") return "Viewing file\u2026";
1550
+ if (name === "mcp__chat__list_tasks") return "Listing tasks\u2026";
1551
+ if (name === "mcp__chat__create_tasks") return "Creating tasks\u2026";
1552
+ if (name === "mcp__chat__claim_tasks") return "Claiming tasks\u2026";
1553
+ if (name === "mcp__chat__unclaim_task") return "Unclaiming task\u2026";
1554
+ if (name === "mcp__chat__update_task_status") return "Updating task status\u2026";
1555
+ if (name === "mcp__chat__list_server") return "Listing server\u2026";
1556
+ if (name === "mcp__chat__read_history") return "Reading history\u2026";
1557
+ if (name === "mcp__chat__search_messages") return "Searching messages\u2026";
1558
+ if (name === "mcp__chat__check_messages") return "Checking messages\u2026";
1559
+ if (name.startsWith("mcp__chat__")) return "";
1560
+ if (name === "Read" || name === "read_file") return "Reading file\u2026";
1561
+ if (name === "Write" || name === "write_file") return "Writing file\u2026";
1562
+ if (name === "Edit" || name === "edit_file") return "Editing file\u2026";
1563
+ if (name === "Bash" || name === "bash") return "Running command\u2026";
1564
+ if (name === "Glob" || name === "glob") return "Searching files\u2026";
1565
+ if (name === "Grep" || name === "grep") return "Searching code\u2026";
1566
+ if (name === "WebFetch" || name === "web_fetch") return "Fetching web\u2026";
1567
+ if (name === "WebSearch" || name === "web_search") return "Searching web\u2026";
1568
+ if (name === "TodoWrite") return "Updating tasks\u2026";
1569
+ return `Using ${name.length > 20 ? name.slice(0, 20) + "\u2026" : name}\u2026`;
1570
+ }
1571
+ summarizeToolInput(name, input) {
1572
+ if (!input || typeof input !== "object") return "";
1573
+ try {
1574
+ if (name === "Read" || name === "read_file") return input.file_path || input.path || "";
1575
+ if (name === "Write" || name === "write_file") return input.file_path || input.path || "";
1576
+ if (name === "Edit" || name === "edit_file") return input.file_path || input.path || "";
1577
+ if (name === "Bash" || name === "bash") {
1578
+ const cmd = input.command || "";
1579
+ return cmd.length > 100 ? cmd.slice(0, 100) + "\u2026" : cmd;
1580
+ }
1581
+ if (name === "Glob" || name === "glob") return input.pattern || "";
1582
+ if (name === "Grep" || name === "grep") return input.pattern || "";
1583
+ if (name === "WebFetch" || name === "web_fetch") return input.url || "";
1584
+ if (name === "WebSearch" || name === "web_search") return input.query || "";
1585
+ if (name === "mcp__chat__send_message") {
1586
+ return input.target || input.channel || (input.dm_to ? `DM:@${input.dm_to}` : "");
1587
+ }
1588
+ if (name === "mcp__chat__read_history") return input.target || input.channel || "";
1589
+ if (name === "mcp__chat__search_messages") return input.query || "";
1590
+ if (name === "mcp__chat__list_tasks") return input.channel || "";
1591
+ if (name === "mcp__chat__create_tasks") return input.channel || "";
1592
+ if (name === "mcp__chat__claim_tasks") {
1593
+ const nums = input.task_numbers;
1594
+ return input.channel ? `${input.channel} #${Array.isArray(nums) ? nums.join(",#t") : nums}` : "";
1595
+ }
1596
+ if (name === "mcp__chat__unclaim_task") {
1597
+ return input.channel ? `${input.channel} #${input.task_number}` : "";
1598
+ }
1599
+ if (name === "mcp__chat__update_task_status") {
1600
+ return input.channel ? `${input.channel} #${input.task_number}` : "";
1601
+ }
1602
+ if (name === "mcp__chat__upload_file") return input.file_path || "";
1603
+ return "";
1604
+ } catch {
1605
+ return "";
1606
+ }
1607
+ }
1608
+ };
1609
+
1610
+ // src/drivers/gemini.ts
1611
+ import { spawn as spawn5 } from "child_process";
1612
+ import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
1613
+ import path6 from "path";
1614
+ var GeminiDriver = class {
1615
+ id = "gemini";
1616
+ supportsStdinNotification = false;
1617
+ mcpToolPrefix = "";
1618
+ busyDeliveryMode = "none";
1619
+ sessionId = null;
1620
+ sessionAnnounced = false;
1621
+ spawn(ctx) {
1622
+ this.sessionId = ctx.config.sessionId || null;
1623
+ this.sessionAnnounced = false;
1624
+ const geminiDir = path6.join(ctx.workingDirectory, ".gemini");
1625
+ if (!existsSync4(geminiDir)) {
1626
+ mkdirSync2(geminiDir, { recursive: true });
1627
+ }
1628
+ const isTsSource = ctx.chatBridgePath.endsWith(".ts");
1629
+ const mcpCommand = isTsSource ? "npx" : "node";
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];
1631
+ const settingsPath = path6.join(geminiDir, "settings.json");
1632
+ writeFileSync4(settingsPath, JSON.stringify({
1633
+ mcpServers: {
1634
+ chat: {
1635
+ command: mcpCommand,
1636
+ args: mcpArgs
1637
+ }
1638
+ }
1639
+ }), "utf8");
1640
+ const args = [
1641
+ "--output-format",
1642
+ "stream-json",
1643
+ "--yolo",
1644
+ "-p",
1645
+ ctx.prompt
1646
+ ];
1647
+ if (ctx.config.model && ctx.config.model !== "default") {
1648
+ args.push("--model", ctx.config.model);
1649
+ }
1650
+ if (ctx.config.sessionId) {
1651
+ args.push("--resume", ctx.config.sessionId);
1652
+ }
1653
+ const spawnEnv = { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1", ...ctx.config.envVars || {} };
1654
+ const proc = spawn5("gemini", args, {
1655
+ cwd: ctx.workingDirectory,
1656
+ stdio: ["pipe", "pipe", "pipe"],
1657
+ env: spawnEnv,
1658
+ shell: process.platform === "win32"
1659
+ });
1660
+ return { process: proc };
1661
+ }
1662
+ parseLine(line) {
1663
+ let event;
1664
+ try {
1665
+ event = JSON.parse(line);
1666
+ } catch {
1667
+ return [];
1668
+ }
1669
+ const events = [];
1670
+ switch (event.type) {
1671
+ case "init":
1672
+ if (event.session_id) {
1673
+ this.sessionId = event.session_id;
1674
+ events.push({ kind: "session_init", sessionId: event.session_id });
1675
+ this.sessionAnnounced = true;
1676
+ }
1677
+ break;
1678
+ case "message":
1679
+ if (event.role === "assistant" && event.content) {
1680
+ if (event.delta) {
1681
+ events.push({ kind: "text", text: event.content });
1682
+ } else {
1683
+ events.push({ kind: "text", text: event.content });
1684
+ }
1685
+ }
1686
+ break;
1687
+ case "tool_use":
1688
+ events.push({
1689
+ kind: "tool_call",
1690
+ name: event.tool_name || "unknown_tool",
1691
+ input: event.parameters
1692
+ });
1693
+ break;
1694
+ case "error":
1695
+ events.push({ kind: "error", message: event.message || "Unknown Gemini error" });
1696
+ break;
1697
+ case "result":
1698
+ if (event.status !== "success") {
1699
+ const raw = event.error_message || event.message || event.error || "";
1700
+ const detail = typeof raw === "string" ? raw : raw?.message || JSON.stringify(raw);
1701
+ const msg = detail ? `Gemini error: ${detail}` : `Gemini session ended with status: ${event.status}`;
1702
+ events.push({ kind: "error", message: msg });
1703
+ }
1704
+ events.push({ kind: "turn_end", sessionId: this.sessionId || void 0 });
1705
+ break;
1706
+ }
1707
+ return events;
1708
+ }
1709
+ encodeStdinMessage(_text, _sessionId, _opts) {
1710
+ return null;
1711
+ }
1712
+ buildSystemPrompt(config, _agentId) {
1713
+ return buildBaseSystemPrompt(config, {
1714
+ toolPrefix: "",
1715
+ extraCriticalRules: [
1716
+ "- Do NOT use shell commands to send or receive messages. The MCP tools handle everything."
1717
+ ],
1718
+ postStartupNotes: [],
1719
+ includeStdinNotificationSection: false,
1720
+ messageNotificationStyle: "poll"
1721
+ });
1722
+ }
1723
+ toolDisplayName(name) {
1724
+ if (name === "list_tasks") return "Viewing task board\u2026";
1725
+ if (name === "create_tasks") return "Creating tasks\u2026";
1726
+ if (name === "claim_tasks") return "Claiming tasks\u2026";
1727
+ if (name === "unclaim_task") return "Unclaiming task\u2026";
1728
+ if (name === "update_task_status") return "Updating task\u2026";
1729
+ if (name === "send_message" || name === "receive_message" || name === "read_history" || name === "list_server") return "";
1730
+ if (name === "shell" || name === "Shell") return "Running command\u2026";
1731
+ if (name === "read_file" || name === "ReadFile") return "Reading file\u2026";
1732
+ if (name === "write_file" || name === "WriteFile" || name === "edit_file" || name === "EditFile") return "Editing file\u2026";
1733
+ if (name === "search_files" || name === "Glob" || name === "Grep") return "Searching code\u2026";
1734
+ if (name === "web_search" || name === "SearchWeb") return "Searching web\u2026";
1735
+ if (name === "fetch_url" || name === "FetchURL") return "Fetching web\u2026";
1736
+ return `Using ${name.length > 20 ? name.slice(0, 20) + "\u2026" : name}\u2026`;
1737
+ }
1738
+ summarizeToolInput(name, input) {
1739
+ if (!input || typeof input !== "object") return "";
1740
+ try {
1741
+ if (name === "shell" || name === "Shell") {
1742
+ const cmd = input.command || "";
1743
+ return cmd.length > 100 ? cmd.slice(0, 100) + "\u2026" : cmd;
1744
+ }
1745
+ if (name === "read_file" || name === "ReadFile" || name === "write_file" || name === "WriteFile" || name === "edit_file" || name === "EditFile") {
1746
+ return input.path || input.file_path || "";
1747
+ }
1748
+ if (name === "Glob" || name === "Grep" || name === "search_files") return input.pattern || input.query || "";
1749
+ if (name === "web_search" || name === "SearchWeb") return input.query || "";
1750
+ if (name === "fetch_url" || name === "FetchURL") return input.url || "";
1751
+ if (name === "send_message") return input.target || input.channel || "";
1752
+ if (name === "read_history") return input.target || input.channel || "";
1753
+ if (name === "list_tasks") return input.channel || "";
1754
+ if (name === "create_tasks") return input.channel || "";
1755
+ if (name === "claim_tasks") {
1756
+ const nums = input.task_numbers;
1757
+ return input.channel ? `${input.channel} #t${Array.isArray(nums) ? nums.join(",#t") : nums}` : "";
1758
+ }
1759
+ if (name === "unclaim_task" || name === "update_task_status") {
1760
+ return input.channel ? `${input.channel} #t${input.task_number}` : "";
1761
+ }
1762
+ return "";
1763
+ } catch {
1764
+ return "";
1765
+ }
1766
+ }
1075
1767
  };
1076
1768
 
1077
1769
  // src/drivers/kimi.ts
1078
1770
  import { randomUUID } from "crypto";
1079
- import { spawn as spawn3 } from "child_process";
1080
- import { existsSync as existsSync2, writeFileSync as writeFileSync2 } from "fs";
1081
- import path3 from "path";
1771
+ import { spawn as spawn6 } from "child_process";
1772
+ import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync as writeFileSync5 } from "fs";
1773
+ import os2 from "os";
1774
+ import path7 from "path";
1082
1775
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
1083
1776
  var KIMI_SYSTEM_PROMPT_FILE = ".slock-kimi-system.md";
1084
1777
  var KIMI_AGENT_FILE = ".slock-kimi-agent.yaml";
@@ -1095,7 +1788,7 @@ var KimiDriver = class {
1095
1788
  id = "kimi";
1096
1789
  supportsStdinNotification = true;
1097
1790
  mcpToolPrefix = "";
1098
- deliverMessageDirectlyWhileBusy = true;
1791
+ busyDeliveryMode = "direct";
1099
1792
  sessionId = null;
1100
1793
  sessionAnnounced = false;
1101
1794
  promptRequestId = null;
@@ -1107,20 +1800,20 @@ var KimiDriver = class {
1107
1800
  const isTsSource = ctx.chatBridgePath.endsWith(".ts");
1108
1801
  const command = isTsSource ? "npx" : "node";
1109
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];
1110
- const systemPromptPath = path3.join(ctx.workingDirectory, KIMI_SYSTEM_PROMPT_FILE);
1111
- const agentFilePath = path3.join(ctx.workingDirectory, KIMI_AGENT_FILE);
1112
- const mcpConfigPath = path3.join(ctx.workingDirectory, KIMI_MCP_FILE);
1113
- if (!isResume || !existsSync2(systemPromptPath)) {
1114
- writeFileSync2(systemPromptPath, ctx.prompt, "utf8");
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)) {
1807
+ writeFileSync5(systemPromptPath, ctx.prompt, "utf8");
1115
1808
  }
1116
- writeFileSync2(agentFilePath, [
1809
+ writeFileSync5(agentFilePath, [
1117
1810
  "version: 1",
1118
1811
  "agent:",
1119
1812
  " extend: default",
1120
1813
  ` system_prompt_path: ./${KIMI_SYSTEM_PROMPT_FILE}`,
1121
1814
  ""
1122
1815
  ].join("\n"), "utf8");
1123
- writeFileSync2(mcpConfigPath, JSON.stringify({
1816
+ writeFileSync5(mcpConfigPath, JSON.stringify({
1124
1817
  mcpServers: {
1125
1818
  chat: {
1126
1819
  command,
@@ -1142,7 +1835,7 @@ var KimiDriver = class {
1142
1835
  args.push("--model", ctx.config.model);
1143
1836
  }
1144
1837
  const spawnEnv = { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" };
1145
- const proc = spawn3("kimi", args, {
1838
+ const proc = spawn6("kimi", args, {
1146
1839
  cwd: ctx.workingDirectory,
1147
1840
  stdio: ["pipe", "pipe", "pipe"],
1148
1841
  env: spawnEnv,
@@ -1297,12 +1990,43 @@ var KimiDriver = class {
1297
1990
  return "";
1298
1991
  }
1299
1992
  }
1993
+ async detectModels() {
1994
+ return detectKimiModels();
1995
+ }
1300
1996
  };
1997
+ function detectKimiModels(home = os2.homedir()) {
1998
+ const configPath = path7.join(home, ".kimi", "config.toml");
1999
+ let raw;
2000
+ try {
2001
+ raw = readFileSync2(configPath, "utf8");
2002
+ } catch {
2003
+ return null;
2004
+ }
2005
+ const models = [];
2006
+ const sectionRe = /^\s*\[models(?:\.([^\]]+)|"\.[^"]+"|\."[^"]+")\s*\]\s*$/gm;
2007
+ const lineRe = /^\s*\[models\.(.+?)\s*\]\s*$/gm;
2008
+ let match;
2009
+ while ((match = lineRe.exec(raw)) !== null) {
2010
+ let key = match[1].trim();
2011
+ if (key.startsWith('"') && key.endsWith('"')) key = key.slice(1, -1);
2012
+ if (!key) continue;
2013
+ models.push({ id: key, label: key });
2014
+ }
2015
+ void sectionRe;
2016
+ if (models.length === 0) return null;
2017
+ let defaultModel;
2018
+ const defaultMatch = raw.match(/^\s*default_model\s*=\s*"([^"]+)"/m);
2019
+ if (defaultMatch) defaultModel = defaultMatch[1];
2020
+ return { models, default: defaultModel };
2021
+ }
1301
2022
 
1302
2023
  // src/drivers/index.ts
1303
2024
  var driverFactories = {
1304
2025
  claude: () => new ClaudeDriver(),
1305
2026
  codex: () => new CodexDriver(),
2027
+ copilot: () => new CopilotDriver(),
2028
+ cursor: () => new CursorDriver(),
2029
+ gemini: () => new GeminiDriver(),
1306
2030
  kimi: () => new KimiDriver()
1307
2031
  };
1308
2032
  function getDriver(runtimeId) {
@@ -1314,52 +2038,9 @@ function getDriver(runtimeId) {
1314
2038
  return driver;
1315
2039
  }
1316
2040
 
1317
- // src/logger.ts
1318
- var listeners = /* @__PURE__ */ new Set();
1319
- function timestamp() {
1320
- const d = /* @__PURE__ */ new Date();
1321
- const pad = (n) => String(n).padStart(2, "0");
1322
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
1323
- }
1324
- function format(level, msg) {
1325
- return `${timestamp()} [${level}] ${msg}`;
1326
- }
1327
- function emit(event) {
1328
- for (const listener of listeners) {
1329
- listener(event);
1330
- }
1331
- }
1332
- function subscribeDaemonLogs(listener) {
1333
- listeners.add(listener);
1334
- return () => {
1335
- listeners.delete(listener);
1336
- };
1337
- }
1338
- var logger = {
1339
- info(msg) {
1340
- const line = format("INFO", msg);
1341
- console.log(line);
1342
- emit({ level: "INFO", line, message: msg });
1343
- },
1344
- warn(msg) {
1345
- const line = format("WARN", msg);
1346
- console.warn(line);
1347
- emit({ level: "WARN", line, message: msg });
1348
- },
1349
- error(msg, err) {
1350
- const line = format("ERROR", msg);
1351
- if (err) {
1352
- console.error(line, err);
1353
- } else {
1354
- console.error(line);
1355
- }
1356
- emit({ level: "ERROR", line, message: msg, error: err });
1357
- }
1358
- };
1359
-
1360
2041
  // src/workspaces.ts
1361
2042
  import { readdir, rm, stat } from "fs/promises";
1362
- import path4 from "path";
2043
+ import path8 from "path";
1363
2044
  function isValidWorkspaceDirectoryName(directoryName) {
1364
2045
  return !directoryName.includes("/") && !directoryName.includes("\\") && !directoryName.includes("..");
1365
2046
  }
@@ -1367,7 +2048,7 @@ function resolveWorkspaceDirectoryPath(dataDir, directoryName) {
1367
2048
  if (!isValidWorkspaceDirectoryName(directoryName)) {
1368
2049
  return null;
1369
2050
  }
1370
- return path4.join(dataDir, directoryName);
2051
+ return path8.join(dataDir, directoryName);
1371
2052
  }
1372
2053
  function emptyWorkspaceDirectorySummary(latestMtime = /* @__PURE__ */ new Date(0)) {
1373
2054
  return {
@@ -1416,7 +2097,7 @@ async function summarizeWorkspaceDirectory(dirPath) {
1416
2097
  return summary;
1417
2098
  }
1418
2099
  const childSummaries = await Promise.all(
1419
- entries.map((entry) => summarizeWorkspaceEntry(path4.join(dirPath, entry.name), entry))
2100
+ entries.map((entry) => summarizeWorkspaceEntry(path8.join(dirPath, entry.name), entry))
1420
2101
  );
1421
2102
  for (const childSummary of childSummaries) {
1422
2103
  summary = mergeWorkspaceDirectorySummaries(summary, childSummary);
@@ -1435,7 +2116,7 @@ async function scanWorkspaceDirectories(dataDir) {
1435
2116
  if (!entry.isDirectory()) {
1436
2117
  return null;
1437
2118
  }
1438
- const dirPath = path4.join(dataDir, entry.name);
2119
+ const dirPath = path8.join(dataDir, entry.name);
1439
2120
  try {
1440
2121
  const summary = await summarizeWorkspaceDirectory(dirPath);
1441
2122
  return {
@@ -1467,7 +2148,7 @@ async function deleteWorkspaceDirectory(dataDir, directoryName) {
1467
2148
  }
1468
2149
 
1469
2150
  // src/agentProcessManager.ts
1470
- var DATA_DIR = path5.join(os.homedir(), ".slock", "agents");
2151
+ var DATA_DIR = path9.join(os3.homedir(), ".slock", "agents");
1471
2152
  function toLocalTime(iso) {
1472
2153
  const d = new Date(iso);
1473
2154
  if (isNaN(d.getTime())) return iso;
@@ -1479,7 +2160,7 @@ function formatChannelLabel(message) {
1479
2160
  }
1480
2161
  function formatMessageTarget(message) {
1481
2162
  if (message.channel_type === "thread" && message.parent_channel_name) {
1482
- const shortId = message.channel_name.startsWith("thread-") ? message.channel_name.slice(7) : message.channel_name;
2163
+ const shortId = getMessageShortId(message.channel_name);
1483
2164
  if (message.parent_channel_type === "dm") {
1484
2165
  return `dm:@${message.parent_channel_name}:${shortId}`;
1485
2166
  }
@@ -1490,15 +2171,47 @@ function formatMessageTarget(message) {
1490
2171
  }
1491
2172
  return `#${message.channel_name}`;
1492
2173
  }
2174
+ function getMessageShortId(messageId) {
2175
+ return messageId.startsWith("thread-") ? messageId.slice(7) : messageId.slice(0, 8);
2176
+ }
2177
+ function formatSenderHandle(message) {
2178
+ return message.sender_description ? `@${message.sender_name} \u2014 ${message.sender_description}` : `@${message.sender_name}`;
2179
+ }
2180
+ function formatVisibleActorType(type) {
2181
+ return ` type=${type}`;
2182
+ }
2183
+ function formatTaskAssigneeType(type) {
2184
+ return type ?? null;
2185
+ }
2186
+ function formatThreadContextMessage(message) {
2187
+ const msgId = message.message_id ? getMessageShortId(message.message_id) : "-";
2188
+ const time = message.timestamp ? toLocalTime(message.timestamp) : "-";
2189
+ const senderType = formatVisibleActorType(message.sender_type);
2190
+ return `- [msg=${msgId} time=${time}${senderType}] ${formatSenderHandle(message)}: ${message.content}`;
2191
+ }
1493
2192
  function formatIncomingMessage(message) {
2193
+ const threadJoinPrefix = message.thread_join_context ? [
2194
+ `[System: You were added to a new thread via @mention. Read this context before replying.]`,
2195
+ `parent: ${message.thread_join_context.parent_target}`,
2196
+ `thread: ${message.thread_join_context.thread_target}`,
2197
+ `suggested next step: read_history(channel="${message.thread_join_context.suggested_read_history_target}")`,
2198
+ "",
2199
+ "Parent message:",
2200
+ formatThreadContextMessage(message.thread_join_context.parent_message),
2201
+ "",
2202
+ `Recent thread context${message.thread_join_context.history_truncated ? " (truncated)" : ""}:`,
2203
+ message.thread_join_context.recent_messages.length > 0 ? message.thread_join_context.recent_messages.map(formatThreadContextMessage).join("\n") : "- (no earlier thread replies)",
2204
+ ""
2205
+ ].join("\n") : "";
1494
2206
  const target = formatMessageTarget(message);
1495
- const msgId = message.message_id ? message.message_id.slice(0, 8) : "-";
2207
+ const msgId = message.message_id ? getMessageShortId(message.message_id) : "-";
1496
2208
  const time = message.timestamp ? toLocalTime(message.timestamp) : "-";
1497
- const senderType = message.sender_type === "agent" ? " type=agent" : "";
1498
- const renderedContent = message.content;
1499
- const attachSuffix = message.attachments?.length ? ` [${message.attachments.length} image${message.attachments.length > 1 ? "s" : ""}: ${message.attachments.map((a) => `${a.filename} (id:${a.id})`).join(", ")} \u2014 use view_file to see]` : "";
1500
- const taskSuffix = message.task_status ? ` [task #${message.task_number} status=${message.task_status}${message.task_assignee_id ? ` assignee=${message.task_assignee_type}:${message.task_assignee_id}` : ""}]` : "";
1501
- return `[target=${target} msg=${msgId} time=${time}${senderType}] @${message.sender_name}: ${renderedContent}${attachSuffix}${taskSuffix}`;
2209
+ const senderType = formatVisibleActorType(message.sender_type);
2210
+ const attachSuffix = message.attachments?.length ? ` [${message.attachments.length} attachment${message.attachments.length > 1 ? "s" : ""}: ${message.attachments.map((a) => `${a.filename} (id:${a.id})`).join(", ")} \u2014 use view_file to download]` : "";
2211
+ const taskSuffix = message.task_status ? ` [task #${message.task_number} status=${message.task_status}${message.task_assignee_id ? ` assignee=${formatTaskAssigneeType(message.task_assignee_type)}:${message.task_assignee_id}` : ""}]` : "";
2212
+ const body = `[target=${target} msg=${msgId} time=${time}${senderType}] ${formatSenderHandle(message)}: ${message.content}${attachSuffix}${taskSuffix}`;
2213
+ return threadJoinPrefix ? `${threadJoinPrefix}
2214
+ ${body}` : body;
1502
2215
  }
1503
2216
  function buildUnreadSummary(messages, excludeChannel) {
1504
2217
  const summary = /* @__PURE__ */ new Map();
@@ -1561,6 +2274,19 @@ function summarizeCrash(code, signal) {
1561
2274
  if (typeof code === "number") return `exit code ${code}`;
1562
2275
  return "unknown exit";
1563
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
+ }
1564
2290
  function isMissingResumeSession(ap) {
1565
2291
  if (ap.driver.id !== "claude") return false;
1566
2292
  if (!ap.sessionId) return false;
@@ -1571,7 +2297,7 @@ function getMessageDeliveryText(driver) {
1571
2297
  }
1572
2298
  function getBusyDeliveryNote(driver) {
1573
2299
  if (!driver.supportsStdinNotification) return "";
1574
- if (driver.deliverMessageDirectlyWhileBusy) {
2300
+ if (driver.busyDeliveryMode === "direct") {
1575
2301
  return "\n\nNote: While you are busy, new messages may be delivered directly into your active turn. Handle them when appropriate and keep working.";
1576
2302
  }
1577
2303
  return "\n\nNote: While you are busy, you may receive [System notification: ...] messages. Finish your current step, then call check_messages to check for messages.";
@@ -1609,9 +2335,9 @@ var AgentProcessManager = class _AgentProcessManager {
1609
2335
  this.agentsStarting.add(agentId);
1610
2336
  try {
1611
2337
  const driver = this.driverResolver(config.runtime || "claude");
1612
- const agentDataDir = path5.join(this.dataDir, agentId);
2338
+ const agentDataDir = path9.join(this.dataDir, agentId);
1613
2339
  await mkdir(agentDataDir, { recursive: true });
1614
- const memoryMdPath = path5.join(agentDataDir, "MEMORY.md");
2340
+ const memoryMdPath = path9.join(agentDataDir, "MEMORY.md");
1615
2341
  try {
1616
2342
  await access(memoryMdPath);
1617
2343
  } catch {
@@ -1629,7 +2355,7 @@ ${config.description || "No role defined yet."}
1629
2355
  `;
1630
2356
  await writeFile(memoryMdPath, initialMemoryMd);
1631
2357
  }
1632
- await mkdir(path5.join(agentDataDir, "notes"), { recursive: true });
2358
+ await mkdir(path9.join(agentDataDir, "notes"), { recursive: true });
1633
2359
  const isResume = !!config.sessionId;
1634
2360
  let prompt;
1635
2361
  if (isResume && resumePrompt) {
@@ -1766,6 +2492,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
1766
2492
  this.agents.delete(agentId);
1767
2493
  const finalCode = ap.exitCode ?? code;
1768
2494
  const finalSignal = ap.exitSignal ?? signal;
2495
+ const terminalFailureDetail = classifyTerminalFailure(ap);
1769
2496
  if (finalCode === 0) {
1770
2497
  const queuedWakeMessage = !ap.driver.supportsStdinNotification ? ap.inbox.shift() : void 0;
1771
2498
  const unreadSummary2 = queuedWakeMessage ? buildUnreadSummary(ap.inbox, formatChannelLabel(queuedWakeMessage)) : void 0;
@@ -1818,13 +2545,23 @@ Use read_history to catch up on the channels listed above, then stop. Read each
1818
2545
  this.startAgent(agentId, restartConfig, void 0, void 0, void 0, ap.launchId || void 0).catch((err) => {
1819
2546
  logger.error(`[Agent ${agentId}] Cold start recovery failed`, err);
1820
2547
  this.sendAgentStatus(agentId, "inactive", ap.launchId);
1821
- this.broadcastActivity(agentId, "offline", `Crashed (${summary})`);
2548
+ this.broadcastActivity(agentId, "offline", `Crashed (${summary})`, [], ap.launchId);
1822
2549
  });
1823
2550
  return;
1824
2551
  }
1825
2552
  logger.error(`[Agent ${agentId}] Process crashed (${reason}) \u2014 marking inactive`);
1826
2553
  this.sendAgentStatus(agentId, "inactive", ap.launchId);
1827
- 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
+ }
1828
2565
  }
1829
2566
  }
1830
2567
  });
@@ -1958,7 +2695,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
1958
2695
  }
1959
2696
  }
1960
2697
  async resetWorkspace(agentId) {
1961
- const agentDataDir = path5.join(this.dataDir, agentId);
2698
+ const agentDataDir = path9.join(this.dataDir, agentId);
1962
2699
  try {
1963
2700
  await rm2(agentDataDir, { recursive: true, force: true });
1964
2701
  logger.info(`[Agent ${agentId}] Workspace reset complete (${agentDataDir})`);
@@ -1996,7 +2733,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
1996
2733
  }
1997
2734
  // Workspace file browsing
1998
2735
  async getFileTree(agentId, dirPath) {
1999
- const agentDir = path5.join(this.dataDir, agentId);
2736
+ const agentDir = path9.join(this.dataDir, agentId);
2000
2737
  try {
2001
2738
  await stat2(agentDir);
2002
2739
  } catch {
@@ -2004,8 +2741,8 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2004
2741
  }
2005
2742
  let targetDir = agentDir;
2006
2743
  if (dirPath) {
2007
- const resolved = path5.resolve(agentDir, dirPath);
2008
- if (!resolved.startsWith(agentDir + path5.sep) && resolved !== agentDir) {
2744
+ const resolved = path9.resolve(agentDir, dirPath);
2745
+ if (!resolved.startsWith(agentDir + path9.sep) && resolved !== agentDir) {
2009
2746
  return [];
2010
2747
  }
2011
2748
  targetDir = resolved;
@@ -2013,9 +2750,9 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2013
2750
  return this.listDirectoryChildren(targetDir, agentDir);
2014
2751
  }
2015
2752
  async readFile(agentId, filePath) {
2016
- const agentDir = path5.join(this.dataDir, agentId);
2017
- const resolved = path5.resolve(agentDir, filePath);
2018
- if (!resolved.startsWith(agentDir + path5.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) {
2019
2756
  throw new Error("Access denied");
2020
2757
  }
2021
2758
  const info = await stat2(resolved);
@@ -2039,7 +2776,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2039
2776
  ".sh",
2040
2777
  ".py"
2041
2778
  ]);
2042
- const ext = path5.extname(resolved).toLowerCase();
2779
+ const ext = path9.extname(resolved).toLowerCase();
2043
2780
  if (!TEXT_EXTENSIONS.has(ext) && ext !== "") {
2044
2781
  return { content: null, binary: true };
2045
2782
  }
@@ -2065,14 +2802,14 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2065
2802
  async listSkills(agentId, runtimeHint) {
2066
2803
  const agent = this.agents.get(agentId);
2067
2804
  const runtime = runtimeHint || agent?.config.runtime || "claude";
2068
- const home = os.homedir();
2069
- const workspaceDir = path5.join(this.dataDir, agentId);
2805
+ const home = os3.homedir();
2806
+ const workspaceDir = path9.join(this.dataDir, agentId);
2070
2807
  const paths = _AgentProcessManager.SKILL_PATHS[runtime] || _AgentProcessManager.SKILL_PATHS.claude;
2071
2808
  const globalResults = await Promise.all(
2072
- paths.global.map((p) => this.scanSkillsDir(path5.join(home, p)))
2809
+ paths.global.map((p) => this.scanSkillsDir(path9.join(home, p)))
2073
2810
  );
2074
2811
  const workspaceResults = await Promise.all(
2075
- paths.workspace.map((p) => this.scanSkillsDir(path5.join(workspaceDir, p)))
2812
+ paths.workspace.map((p) => this.scanSkillsDir(path9.join(workspaceDir, p)))
2076
2813
  );
2077
2814
  const dedup = (skills) => {
2078
2815
  const seen = /* @__PURE__ */ new Set();
@@ -2101,7 +2838,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2101
2838
  const skills = [];
2102
2839
  for (const entry of entries) {
2103
2840
  if (entry.isDirectory() || entry.isSymbolicLink()) {
2104
- const skillMd = path5.join(dir, entry.name, "SKILL.md");
2841
+ const skillMd = path9.join(dir, entry.name, "SKILL.md");
2105
2842
  try {
2106
2843
  const content = await readFile(skillMd, "utf-8");
2107
2844
  const skill = this.parseSkillMd(entry.name, content);
@@ -2112,7 +2849,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2112
2849
  } else if (entry.name.endsWith(".md")) {
2113
2850
  const cmdName = entry.name.replace(/\.md$/, "");
2114
2851
  try {
2115
- const content = await readFile(path5.join(dir, entry.name), "utf-8");
2852
+ const content = await readFile(path9.join(dir, entry.name), "utf-8");
2116
2853
  const skill = this.parseSkillMd(cmdName, content);
2117
2854
  skill.sourcePath = dir;
2118
2855
  skills.push(skill);
@@ -2148,14 +2885,21 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2148
2885
  * Broadcast an activity change — emits a single agent:activity event that carries
2149
2886
  * both the status (for the dot indicator) and trajectory entries (for the activity log).
2150
2887
  */
2151
- broadcastActivity(agentId, activity, detail, extraTrajectory = []) {
2888
+ broadcastActivity(agentId, activity, detail, extraTrajectory = [], launchIdOverride) {
2152
2889
  const ap = this.agents.get(agentId);
2153
2890
  const entries = [...extraTrajectory];
2154
2891
  const hasToolStart = entries.some((e) => e.kind === "tool_start");
2155
2892
  if (!hasToolStart) {
2156
2893
  entries.push({ kind: "status", activity, detail });
2157
2894
  }
2158
- 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
+ });
2159
2903
  if (ap) {
2160
2904
  ap.lastActivity = activity;
2161
2905
  ap.lastActivityDetail = detail;
@@ -2167,7 +2911,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2167
2911
  agentId,
2168
2912
  activity: ap.lastActivity,
2169
2913
  detail: ap.lastActivityDetail,
2170
- launchId: ap.launchId || void 0
2914
+ launchId: launchIdOverride || ap.launchId || void 0
2171
2915
  });
2172
2916
  }, ACTIVITY_HEARTBEAT_MS);
2173
2917
  }
@@ -2287,7 +3031,7 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2287
3031
  if (count === 0) return;
2288
3032
  if (ap.isIdle) return;
2289
3033
  if (!ap.sessionId) return;
2290
- if (ap.driver.deliverMessageDirectlyWhileBusy && ap.inbox.length > 0) {
3034
+ if (ap.driver.busyDeliveryMode === "direct" && ap.inbox.length > 0) {
2291
3035
  const queuedMessages = ap.inbox.splice(0, ap.inbox.length);
2292
3036
  console.log(`[Agent ${agentId}] Delivering queued message via stdin while busy`);
2293
3037
  this.broadcastActivity(agentId, "working", "Message received");
@@ -2350,8 +3094,8 @@ Respond as appropriate. Complete all your work before stopping.`;
2350
3094
  const nodes = [];
2351
3095
  for (const entry of entries) {
2352
3096
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
2353
- const fullPath = path5.join(dir, entry.name);
2354
- const relativePath = path5.relative(rootDir, fullPath);
3097
+ const fullPath = path9.join(dir, entry.name);
3098
+ const relativePath = path9.relative(rootDir, fullPath);
2355
3099
  let info;
2356
3100
  try {
2357
3101
  info = await stat2(fullPath);
@@ -2522,13 +3266,13 @@ function readDaemonVersion(moduleUrl = import.meta.url) {
2522
3266
  }
2523
3267
  }
2524
3268
  function resolveChatBridgePath(moduleUrl = import.meta.url) {
2525
- const dirname = path6.dirname(fileURLToPath(moduleUrl));
2526
- const jsPath = path6.resolve(dirname, "chat-bridge.js");
3269
+ const dirname = path10.dirname(fileURLToPath(moduleUrl));
3270
+ const jsPath = path10.resolve(dirname, "chat-bridge.js");
2527
3271
  try {
2528
3272
  accessSync(jsPath);
2529
3273
  return jsPath;
2530
3274
  } catch {
2531
- return path6.resolve(dirname, "chat-bridge.ts");
3275
+ return path10.resolve(dirname, "chat-bridge.ts");
2532
3276
  }
2533
3277
  }
2534
3278
  function detectRuntimes() {
@@ -2536,6 +3280,15 @@ function detectRuntimes() {
2536
3280
  const versions = {};
2537
3281
  const cmd = process.platform === "win32" ? "where" : "which";
2538
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
+ }
2539
3292
  try {
2540
3293
  execSync2(`${cmd} ${runtime.binary}`, { stdio: "pipe" });
2541
3294
  ids.push(runtime.id);
@@ -2570,6 +3323,8 @@ function summarizeIncomingMessage(msg) {
2570
3323
  return `(agent=${msg.agentId}, runtime=${msg.runtime || "auto"})`;
2571
3324
  case "machine:workspace:delete":
2572
3325
  return `(directory=${msg.directoryName})`;
3326
+ case "machine:runtime_models:detect":
3327
+ return `(runtime=${msg.runtime}, req=${msg.requestId})`;
2573
3328
  default:
2574
3329
  return "";
2575
3330
  }
@@ -2688,6 +3443,21 @@ var DaemonCore = class {
2688
3443
  this.connection.send({ type: "machine:workspace:delete_result", directoryName: msg.directoryName, success });
2689
3444
  });
2690
3445
  break;
3446
+ case "machine:runtime_models:detect": {
3447
+ const driver = getDriver(msg.runtime);
3448
+ const detect = typeof driver?.detectModels === "function" ? driver.detectModels() : Promise.resolve(null);
3449
+ Promise.resolve(detect).then((result) => {
3450
+ if (result) {
3451
+ this.connection.send({ type: "machine:runtime_models:result", requestId: msg.requestId, models: result.models, default: result.default });
3452
+ } else {
3453
+ this.connection.send({ type: "machine:runtime_models:result", requestId: msg.requestId, error: "unsupported" });
3454
+ }
3455
+ }).catch((err) => {
3456
+ const reason = err instanceof Error ? err.message : String(err);
3457
+ this.connection.send({ type: "machine:runtime_models:result", requestId: msg.requestId, error: reason });
3458
+ });
3459
+ break;
3460
+ }
2691
3461
  case "ping":
2692
3462
  this.connection.send({ type: "pong" });
2693
3463
  break;
@@ -2702,8 +3472,8 @@ var DaemonCore = class {
2702
3472
  capabilities: ["agent:start", "agent:stop", "agent:deliver", "workspace:files"],
2703
3473
  runtimes,
2704
3474
  runningAgents: this.agentManager.getRunningAgentIds(),
2705
- hostname: this.options.hostname ?? os2.hostname(),
2706
- os: this.options.osDescription ?? `${os2.platform()} ${os2.arch()}`,
3475
+ hostname: this.options.hostname ?? os4.hostname(),
3476
+ os: this.options.osDescription ?? `${os4.platform()} ${os4.arch()}`,
2707
3477
  daemonVersion: this.daemonVersion
2708
3478
  });
2709
3479
  for (const agentId of this.agentManager.getRunningAgentIds()) {
@@ -2725,7 +3495,6 @@ var DaemonCore = class {
2725
3495
  };
2726
3496
 
2727
3497
  export {
2728
- subscribeDaemonLogs,
2729
3498
  resolveWorkspaceDirectoryPath,
2730
3499
  scanWorkspaceDirectories,
2731
3500
  deleteWorkspaceDirectory,