@love-moon/conductor-cli 0.2.30 → 0.2.31

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.
@@ -257,7 +257,8 @@ async function main() {
257
257
  "# envs:",
258
258
  "# http_proxy: http://127.0.0.1:7890",
259
259
  "# https_proxy: http://127.0.0.1:7890",
260
- "# all_proxy: socks5://127.0.0.1:7890"
260
+ "# all_proxy: socks5://127.0.0.1:7890",
261
+ "# AISDK_PROVIDER_PATH: path-to-private-aisdk-provider.js"
261
262
  );
262
263
 
263
264
  fs.writeFileSync(CONFIG_FILE, lines.join("\n"), "utf-8");
@@ -26,7 +26,13 @@ import {
26
26
  resolveSessionRunDirectory as resolveCliSessionRunDirectory,
27
27
  resumeProviderForBackend as resumeProviderForCliBackend,
28
28
  } from "../src/fire/resume.js";
29
- import { filterRuntimeSupportedAllowCliList, normalizeRuntimeBackendName } from "../src/runtime-backends.js";
29
+ import {
30
+ filterRuntimeSupportedAllowCliList,
31
+ RUNTIME_SUPPORTED_BACKENDS,
32
+ listRuntimeSupportedBackends,
33
+ normalizeRuntimeBackendAlias,
34
+ normalizeRuntimeBackendName,
35
+ } from "../src/runtime-backends.js";
30
36
 
31
37
  const __filename = fileURLToPath(import.meta.url);
32
38
  const __dirname = path.dirname(__filename);
@@ -34,7 +40,19 @@ const PKG_ROOT = path.join(__dirname, "..");
34
40
  const INITIAL_CLI_PROJECT_PATH = process.cwd();
35
41
  const FIRE_LOG_PATH = path.join(INITIAL_CLI_PROJECT_PATH, "conductor.log");
36
42
  const FIRE_TASK_MARKER_PREFIX = "active-fire";
37
- const ENABLE_FIRE_LOCAL_LOG = !process.env.CONDUCTOR_CLI_COMMAND;
43
+
44
+ export function isLaunchedByDaemon(env = process.env) {
45
+ const daemonHostedValue =
46
+ typeof env?.CONDUCTOR_LAUNCHED_BY_DAEMON === "string" ? env.CONDUCTOR_LAUNCHED_BY_DAEMON.trim().toLowerCase() : "";
47
+ if (daemonHostedValue && daemonHostedValue !== "0" && daemonHostedValue !== "false" && daemonHostedValue !== "no") {
48
+ return true;
49
+ }
50
+ return Boolean(
51
+ typeof env?.CONDUCTOR_CLI_COMMAND === "string" && env.CONDUCTOR_CLI_COMMAND.trim(),
52
+ );
53
+ }
54
+
55
+ const ENABLE_FIRE_LOCAL_LOG = !isLaunchedByDaemon(process.env);
38
56
 
39
57
  const pkgJson = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, "package.json"), "utf-8"));
40
58
  const CLI_NAME = (process.env.CONDUCTOR_CLI_NAME || path.basename(process.argv[1] || "conductor-fire")).replace(
@@ -49,20 +67,21 @@ export function buildConductorConnectHeaders(version = pkgJson.version) {
49
67
  }
50
68
 
51
69
  // Load allow_cli_list from config file (no defaults - must be configured)
52
- function loadAllowCliList(configFilePath) {
70
+ async function loadAllowCliList(configFilePath) {
71
+ const home = os.homedir();
72
+ const configPath = configFilePath || process.env.CONDUCTOR_CONFIG || path.join(home, ".conductor", "config.yaml");
73
+ let parsed = null;
53
74
  try {
54
- const home = os.homedir();
55
- const configPath = configFilePath || process.env.CONDUCTOR_CONFIG || path.join(home, ".conductor", "config.yaml");
56
75
  if (fs.existsSync(configPath)) {
57
76
  const content = fs.readFileSync(configPath, "utf8");
58
- const parsed = yaml.load(content);
59
- if (parsed && typeof parsed === "object" && parsed.allow_cli_list) {
60
- return filterRuntimeSupportedAllowCliList(parsed.allow_cli_list);
61
- }
77
+ parsed = yaml.load(content);
62
78
  }
63
79
  } catch (error) {
64
80
  // ignore error
65
81
  }
82
+ if (parsed && typeof parsed === "object" && parsed.allow_cli_list) {
83
+ return await filterRuntimeSupportedAllowCliList(parsed.allow_cli_list, { configFilePath: configPath });
84
+ }
66
85
  return {};
67
86
  }
68
87
 
@@ -396,7 +415,7 @@ export class FireWatchdog {
396
415
  }
397
416
 
398
417
  async function main() {
399
- const cliArgs = parseCliArgs();
418
+ const cliArgs = await parseCliArgs();
400
419
  let runtimeProjectPath = process.cwd();
401
420
  let backendSession = null;
402
421
 
@@ -409,18 +428,28 @@ async function main() {
409
428
  return;
410
429
  }
411
430
 
412
- const allowCliList = loadAllowCliList(cliArgs.configFile);
431
+ const allowCliList = await loadAllowCliList(cliArgs.configFile);
413
432
  const supportedBackends = Object.keys(allowCliList);
433
+ const discoveredBackends = await listRuntimeSupportedBackends({ configFilePath: cliArgs.configFile });
434
+ const externalBackends = discoveredBackends.filter((backend) => !RUNTIME_SUPPORTED_BACKENDS.includes(backend));
414
435
 
415
436
  if (cliArgs.listBackends) {
416
- if (supportedBackends.length === 0) {
437
+ if (supportedBackends.length === 0 && externalBackends.length === 0) {
417
438
  process.stdout.write(`No supported backends configured.\n\nAdd allow_cli_list to your config file (~/.conductor/config.yaml):\n allow_cli_list:\n codex: codex --dangerously-bypass-approvals-and-sandbox\n claude: claude --dangerously-skip-permissions\n kimi: kimi\n opencode: opencode\n`);
418
439
  } else {
419
- process.stdout.write(`Supported backends (from config):\n`);
420
- for (const [name, command] of Object.entries(allowCliList)) {
421
- process.stdout.write(` ${name}: ${command}\n`);
440
+ if (supportedBackends.length > 0) {
441
+ process.stdout.write(`Supported backends (from config):\n`);
442
+ for (const [name, command] of Object.entries(allowCliList)) {
443
+ process.stdout.write(` ${name}: ${command}\n`);
444
+ }
445
+ }
446
+ if (externalBackends.length > 0) {
447
+ process.stdout.write(`${supportedBackends.length > 0 ? "\n" : ""}External backends (from AISDK_PROVIDER_PATH):\n`);
448
+ for (const name of externalBackends) {
449
+ process.stdout.write(` ${name}\n`);
450
+ }
422
451
  }
423
- process.stdout.write(`\nDefault: ${supportedBackends[0]}\n`);
452
+ process.stdout.write(`\nDefault: ${supportedBackends[0] || externalBackends[0]}\n`);
424
453
  }
425
454
  return;
426
455
  }
@@ -429,6 +458,7 @@ async function main() {
429
458
  if (cliArgs.resumeSessionId) {
430
459
  const bootstrap = await bootstrapResumeContextForFire({
431
460
  backend: cliArgs.backend,
461
+ configFile: cliArgs.configFile,
432
462
  resumeSessionId: cliArgs.resumeSessionId,
433
463
  });
434
464
  resumeContext = bootstrap.resumeContext;
@@ -436,7 +466,7 @@ async function main() {
436
466
  }
437
467
 
438
468
  const env = buildEnv();
439
- const launchedByDaemon = Boolean(process.env.CONDUCTOR_CLI_COMMAND);
469
+ const launchedByDaemon = isLaunchedByDaemon(process.env);
440
470
  let reconnectRunner = null;
441
471
  let reconnectTaskId = null;
442
472
  let pendingRemoteStopEvent = null;
@@ -815,7 +845,7 @@ function stripConductorArgsFromArgv(argv = []) {
815
845
  return backendArgs;
816
846
  }
817
847
 
818
- export function parseCliArgs(argvInput = process.argv) {
848
+ export async function parseCliArgs(argvInput = process.argv) {
819
849
  const rawArgv = Array.isArray(argvInput) ? argvInput : process.argv;
820
850
  const argv = hideBin(rawArgv);
821
851
  const separatorIndex = argv.indexOf("--");
@@ -833,8 +863,10 @@ export function parseCliArgs(argvInput = process.argv) {
833
863
  }
834
864
 
835
865
  const configFileFromArgs = extractConfigFileFromArgv(argv);
836
- const allowCliList = loadAllowCliList(configFileFromArgs);
866
+ const allowCliList = await loadAllowCliList(configFileFromArgs);
837
867
  const supportedBackends = Object.keys(allowCliList);
868
+ const discoveredBackends = await listRuntimeSupportedBackends({ configFilePath: configFileFromArgs });
869
+ const externalBackends = discoveredBackends.filter((backend) => !RUNTIME_SUPPORTED_BACKENDS.includes(backend));
838
870
 
839
871
  const conductorArgs = yargs(conductorArgv)
840
872
  .scriptName(CLI_NAME)
@@ -886,14 +918,14 @@ export function parseCliArgs(argvInput = process.argv) {
886
918
 
887
919
  // Handle help early
888
920
  if (helpWithoutSeparator) {
889
- const defaultBackend = supportedBackends[0] || "none";
921
+ const defaultBackend = supportedBackends[0] || externalBackends[0] || "none";
890
922
  process.stdout.write(`${CLI_NAME} - Conductor-aware AI coding agent runner
891
923
 
892
924
  Usage: ${CLI_NAME} [options] -- [backend options and prompt]
893
925
 
894
926
  Options:
895
- -b, --backend <name> Backend to use (from config: ${supportedBackends.join(", ") || "none configured"}) [default: ${defaultBackend}]
896
- --list-backends List available backends from config and exit
927
+ -b, --backend <name> Backend to use (from config or external providers: ${supportedBackends.join(", ") || externalBackends.join(", ") || "none configured"}) [default: ${defaultBackend}]
928
+ --list-backends List available configured and external backends
897
929
  --config-file <path> Path to Conductor config file
898
930
  --poll-interval <ms> Polling interval when waiting for Conductor messages
899
931
  -t, --title <text> Optional task title shown in the app task list
@@ -937,20 +969,21 @@ Environment:
937
969
  .parse();
938
970
 
939
971
  const backend = conductorArgs.backend
940
- ? normalizeRuntimeBackendName(conductorArgs.backend)
941
- : supportedBackends[0];
972
+ ? await normalizeRuntimeBackendAlias(conductorArgs.backend, { configFilePath: configFileFromArgs })
973
+ : supportedBackends[0] || externalBackends[0];
942
974
  const shouldRequireBackend =
943
975
  !Boolean(conductorArgs.listBackends) &&
944
976
  !listBackendsWithoutSeparator &&
945
977
  !Boolean(conductorArgs.version) &&
946
978
  !versionWithoutSeparator;
947
- if (backend && !supportedBackends.includes(backend) && shouldRequireBackend) {
979
+ const runtimeSupportedBackends = new Set(discoveredBackends);
980
+ if (backend && !runtimeSupportedBackends.has(backend) && shouldRequireBackend) {
948
981
  throw new Error(
949
- `Unsupported backend "${backend}". Supported backends: ${supportedBackends.join(", ") || "none configured"}.`,
982
+ `Unsupported backend "${backend}". Supported backends: ${[...runtimeSupportedBackends].join(", ") || "none configured"}.`,
950
983
  );
951
984
  }
952
985
  if (!backend && shouldRequireBackend) {
953
- throw new Error("No supported backends configured. Add codex, claude, kimi, or opencode to allow_cli_list.");
986
+ throw new Error("No supported backends configured. Add allow_cli_list entries or set AISDK_PROVIDER_PATH for external providers.");
954
987
  }
955
988
 
956
989
  const prompt = (backendArgs._ || []).map((part) => String(part)).join(" ").trim();
@@ -1225,6 +1258,7 @@ export async function resolveResumeContext(backend, sessionId, options = {}) {
1225
1258
 
1226
1259
  export async function bootstrapResumeContextForFire({
1227
1260
  backend,
1261
+ configFile,
1228
1262
  resumeSessionId,
1229
1263
  env = process.env,
1230
1264
  resolveResumeContextFn = resolveResumeContext,
@@ -1247,10 +1281,11 @@ export async function bootstrapResumeContextForFire({
1247
1281
  return { resumeContext, runtimeProjectPath };
1248
1282
  }
1249
1283
 
1250
- resumeContext = await resolveResumeContextFn(backend, resumeSessionId);
1251
- logger(
1252
- `Validated --resume ${resumeContext.sessionId} (${resumeContext.provider}) at ${resumeContext.sessionPath}`,
1253
- );
1284
+ resumeContext = await resolveResumeContextFn(backend, resumeSessionId, {
1285
+ configFilePath: configFile,
1286
+ });
1287
+ const sessionLocation = resumeContext.sessionPath ? ` at ${resumeContext.sessionPath}` : "";
1288
+ logger(`Validated --resume ${resumeContext.sessionId} (${resumeContext.provider})${sessionLocation}`);
1254
1289
  logger(`Resume will run backend from ${resumeContext.cwd}`);
1255
1290
  runtimeProjectPath = await applyWorkingDirectoryFn(resumeContext.cwd);
1256
1291
  logger(`Switched working directory to ${runtimeProjectPath} before Conductor connect`);
@@ -1459,10 +1494,24 @@ export class BridgeRunner {
1459
1494
  const fallbackSessionId = this.resumeSessionId;
1460
1495
  const sessionId = discoveredSessionId || fallbackSessionId;
1461
1496
  const sessionFilePath = sessionInfo?.sessionFilePath ? String(sessionInfo.sessionFilePath).trim() : "";
1497
+ const sessionModel =
1498
+ sessionInfo?.model && String(sessionInfo.model).trim()
1499
+ ? String(sessionInfo.model).trim()
1500
+ : this.backendSession.threadOptions?.model || this.backendName;
1501
+ const sessionModelProvider =
1502
+ sessionInfo?.modelProvider && String(sessionInfo.modelProvider).trim()
1503
+ ? String(sessionInfo.modelProvider).trim()
1504
+ : this.backendSession.threadOptions?.modelProvider || undefined;
1462
1505
  const hasRealSessionId = Boolean(sessionId);
1506
+ const messageSuffix = [
1507
+ sessionModel ? `model=${sessionModel}` : "",
1508
+ sessionModelProvider ? `provider=${sessionModelProvider}` : "",
1509
+ ]
1510
+ .filter(Boolean)
1511
+ .join(" ");
1463
1512
  const message = hasRealSessionId
1464
- ? `${this.backendName} session started: ${sessionId}`
1465
- : `${this.backendName} session started`;
1513
+ ? `${this.backendName} session started: ${sessionId}${messageSuffix ? ` (${messageSuffix})` : ""}`
1514
+ : `${this.backendName} session started${messageSuffix ? ` (${messageSuffix})` : ""}`;
1466
1515
  if (hasRealSessionId) {
1467
1516
  await this.persistTaskSessionBinding({
1468
1517
  sessionId,
@@ -1472,6 +1521,8 @@ export class BridgeRunner {
1472
1521
  try {
1473
1522
  await this.conductor.sendMessage(this.taskId, message, {
1474
1523
  backend: this.backendName,
1524
+ model: sessionModel || undefined,
1525
+ model_provider: sessionModelProvider || undefined,
1475
1526
  thread_id: hasRealSessionId ? sessionId : undefined,
1476
1527
  session_id: hasRealSessionId ? sessionId : undefined,
1477
1528
  session_file_path: sessionFilePath || undefined,
@@ -1897,7 +1948,7 @@ export class BridgeRunner {
1897
1948
  this.copilotLog(
1898
1949
  `runtime replyTo=${replyTo || "latest"} state=${runtime.state || ""} phase=${runtime.phase || ""} inProgress=${Boolean(
1899
1950
  runtime.reply_in_progress,
1900
- )} status="${sanitizeForLog(runtime.status_line || "", 120)}" done="${sanitizeForLog(runtime.status_done_line || "", 120)}" preview="${sanitizeForLog(runtime.reply_preview || "", 120)}"`,
1951
+ )} status="${sanitizeForLog(runtime.status_line || "", 120)}" done="${sanitizeForLog(runtime.status_done_line || "", 120)}" preview="${sanitizeForLog(runtime.reply_preview || "", 120)}" model="${sanitizeForLog(this.backendSession.threadOptions?.model || this.backendName, 80)}" provider="${sanitizeForLog(this.backendSession.threadOptions?.modelProvider || this.backendName, 80)}"`,
1901
1952
  );
1902
1953
 
1903
1954
  try {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@love-moon/conductor-cli",
3
- "version": "0.2.30",
4
- "gitCommitId": "e6a71ad",
3
+ "version": "0.2.31",
4
+ "gitCommitId": "7e0bd83",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "conductor": "bin/conductor.js"
@@ -18,8 +18,8 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@love-moon/ai-bridge": "0.1.4",
21
- "@love-moon/ai-sdk": "0.2.30",
22
- "@love-moon/conductor-sdk": "0.2.30",
21
+ "@love-moon/ai-sdk": "0.2.31",
22
+ "@love-moon/conductor-sdk": "0.2.31",
23
23
  "chrome-launcher": "^1.2.1",
24
24
  "chrome-remote-interface": "^0.33.0",
25
25
  "dotenv": "^16.4.5",