@love-moon/conductor-cli 0.2.30 → 0.2.32
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.
- package/bin/conductor-config.js +2 -1
- package/bin/conductor-fire.js +117 -36
- package/package.json +4 -4
- package/src/daemon.js +415 -245
- package/src/fire/resume.js +101 -1
- package/src/runtime-backends.js +227 -9
package/bin/conductor-config.js
CHANGED
|
@@ -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");
|
package/bin/conductor-fire.js
CHANGED
|
@@ -26,7 +26,13 @@ import {
|
|
|
26
26
|
resolveSessionRunDirectory as resolveCliSessionRunDirectory,
|
|
27
27
|
resumeProviderForBackend as resumeProviderForCliBackend,
|
|
28
28
|
} from "../src/fire/resume.js";
|
|
29
|
-
import {
|
|
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
|
-
|
|
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(
|
|
@@ -48,21 +66,36 @@ export function buildConductorConnectHeaders(version = pkgJson.version) {
|
|
|
48
66
|
};
|
|
49
67
|
}
|
|
50
68
|
|
|
69
|
+
export function shouldRunReconnectRecovery({
|
|
70
|
+
isReconnect,
|
|
71
|
+
fireShuttingDown = false,
|
|
72
|
+
runner = null,
|
|
73
|
+
} = {}) {
|
|
74
|
+
if (!isReconnect || fireShuttingDown) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
if (!runner || typeof runner.shouldSuppressReconnectRecovery !== "function") {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
return !runner.shouldSuppressReconnectRecovery();
|
|
81
|
+
}
|
|
82
|
+
|
|
51
83
|
// Load allow_cli_list from config file (no defaults - must be configured)
|
|
52
|
-
function loadAllowCliList(configFilePath) {
|
|
84
|
+
async function loadAllowCliList(configFilePath) {
|
|
85
|
+
const home = os.homedir();
|
|
86
|
+
const configPath = configFilePath || process.env.CONDUCTOR_CONFIG || path.join(home, ".conductor", "config.yaml");
|
|
87
|
+
let parsed = null;
|
|
53
88
|
try {
|
|
54
|
-
const home = os.homedir();
|
|
55
|
-
const configPath = configFilePath || process.env.CONDUCTOR_CONFIG || path.join(home, ".conductor", "config.yaml");
|
|
56
89
|
if (fs.existsSync(configPath)) {
|
|
57
90
|
const content = fs.readFileSync(configPath, "utf8");
|
|
58
|
-
|
|
59
|
-
if (parsed && typeof parsed === "object" && parsed.allow_cli_list) {
|
|
60
|
-
return filterRuntimeSupportedAllowCliList(parsed.allow_cli_list);
|
|
61
|
-
}
|
|
91
|
+
parsed = yaml.load(content);
|
|
62
92
|
}
|
|
63
93
|
} catch (error) {
|
|
64
94
|
// ignore error
|
|
65
95
|
}
|
|
96
|
+
if (parsed && typeof parsed === "object" && parsed.allow_cli_list) {
|
|
97
|
+
return await filterRuntimeSupportedAllowCliList(parsed.allow_cli_list, { configFilePath: configPath });
|
|
98
|
+
}
|
|
66
99
|
return {};
|
|
67
100
|
}
|
|
68
101
|
|
|
@@ -396,7 +429,7 @@ export class FireWatchdog {
|
|
|
396
429
|
}
|
|
397
430
|
|
|
398
431
|
async function main() {
|
|
399
|
-
const cliArgs = parseCliArgs();
|
|
432
|
+
const cliArgs = await parseCliArgs();
|
|
400
433
|
let runtimeProjectPath = process.cwd();
|
|
401
434
|
let backendSession = null;
|
|
402
435
|
|
|
@@ -409,18 +442,28 @@ async function main() {
|
|
|
409
442
|
return;
|
|
410
443
|
}
|
|
411
444
|
|
|
412
|
-
const allowCliList = loadAllowCliList(cliArgs.configFile);
|
|
445
|
+
const allowCliList = await loadAllowCliList(cliArgs.configFile);
|
|
413
446
|
const supportedBackends = Object.keys(allowCliList);
|
|
447
|
+
const discoveredBackends = await listRuntimeSupportedBackends({ configFilePath: cliArgs.configFile });
|
|
448
|
+
const externalBackends = discoveredBackends.filter((backend) => !RUNTIME_SUPPORTED_BACKENDS.includes(backend));
|
|
414
449
|
|
|
415
450
|
if (cliArgs.listBackends) {
|
|
416
|
-
if (supportedBackends.length === 0) {
|
|
451
|
+
if (supportedBackends.length === 0 && externalBackends.length === 0) {
|
|
417
452
|
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
453
|
} else {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
454
|
+
if (supportedBackends.length > 0) {
|
|
455
|
+
process.stdout.write(`Supported backends (from config):\n`);
|
|
456
|
+
for (const [name, command] of Object.entries(allowCliList)) {
|
|
457
|
+
process.stdout.write(` ${name}: ${command}\n`);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (externalBackends.length > 0) {
|
|
461
|
+
process.stdout.write(`${supportedBackends.length > 0 ? "\n" : ""}External backends (from AISDK_PROVIDER_PATH):\n`);
|
|
462
|
+
for (const name of externalBackends) {
|
|
463
|
+
process.stdout.write(` ${name}\n`);
|
|
464
|
+
}
|
|
422
465
|
}
|
|
423
|
-
process.stdout.write(`\nDefault: ${supportedBackends[0]}\n`);
|
|
466
|
+
process.stdout.write(`\nDefault: ${supportedBackends[0] || externalBackends[0]}\n`);
|
|
424
467
|
}
|
|
425
468
|
return;
|
|
426
469
|
}
|
|
@@ -429,6 +472,7 @@ async function main() {
|
|
|
429
472
|
if (cliArgs.resumeSessionId) {
|
|
430
473
|
const bootstrap = await bootstrapResumeContextForFire({
|
|
431
474
|
backend: cliArgs.backend,
|
|
475
|
+
configFile: cliArgs.configFile,
|
|
432
476
|
resumeSessionId: cliArgs.resumeSessionId,
|
|
433
477
|
});
|
|
434
478
|
resumeContext = bootstrap.resumeContext;
|
|
@@ -436,7 +480,7 @@ async function main() {
|
|
|
436
480
|
}
|
|
437
481
|
|
|
438
482
|
const env = buildEnv();
|
|
439
|
-
const launchedByDaemon =
|
|
483
|
+
const launchedByDaemon = isLaunchedByDaemon(process.env);
|
|
440
484
|
let reconnectRunner = null;
|
|
441
485
|
let reconnectTaskId = null;
|
|
442
486
|
let pendingRemoteStopEvent = null;
|
|
@@ -455,7 +499,13 @@ async function main() {
|
|
|
455
499
|
fireWatchdog.start();
|
|
456
500
|
|
|
457
501
|
const scheduleReconnectRecovery = ({ isReconnect }) => {
|
|
458
|
-
if (
|
|
502
|
+
if (
|
|
503
|
+
!shouldRunReconnectRecovery({
|
|
504
|
+
isReconnect,
|
|
505
|
+
fireShuttingDown,
|
|
506
|
+
runner: reconnectRunner,
|
|
507
|
+
})
|
|
508
|
+
) {
|
|
459
509
|
return;
|
|
460
510
|
}
|
|
461
511
|
log("Conductor connection restored");
|
|
@@ -712,7 +762,13 @@ async function main() {
|
|
|
712
762
|
summary: "conductor fire exited",
|
|
713
763
|
};
|
|
714
764
|
try {
|
|
715
|
-
await conductor.sendTaskStatus(taskContext.taskId, finalStatus);
|
|
765
|
+
const statusResult = await conductor.sendTaskStatus(taskContext.taskId, finalStatus);
|
|
766
|
+
if (statusResult?.pending && typeof conductor.flushPendingUpstreamEvents === "function") {
|
|
767
|
+
await conductor.flushPendingUpstreamEvents({
|
|
768
|
+
timeoutMs: 5_000,
|
|
769
|
+
retryIntervalMs: 250,
|
|
770
|
+
});
|
|
771
|
+
}
|
|
716
772
|
} catch (error) {
|
|
717
773
|
log(`Failed to report task status (${finalStatus.status}): ${error?.message || error}`);
|
|
718
774
|
}
|
|
@@ -815,7 +871,7 @@ function stripConductorArgsFromArgv(argv = []) {
|
|
|
815
871
|
return backendArgs;
|
|
816
872
|
}
|
|
817
873
|
|
|
818
|
-
export function parseCliArgs(argvInput = process.argv) {
|
|
874
|
+
export async function parseCliArgs(argvInput = process.argv) {
|
|
819
875
|
const rawArgv = Array.isArray(argvInput) ? argvInput : process.argv;
|
|
820
876
|
const argv = hideBin(rawArgv);
|
|
821
877
|
const separatorIndex = argv.indexOf("--");
|
|
@@ -833,8 +889,10 @@ export function parseCliArgs(argvInput = process.argv) {
|
|
|
833
889
|
}
|
|
834
890
|
|
|
835
891
|
const configFileFromArgs = extractConfigFileFromArgv(argv);
|
|
836
|
-
const allowCliList = loadAllowCliList(configFileFromArgs);
|
|
892
|
+
const allowCliList = await loadAllowCliList(configFileFromArgs);
|
|
837
893
|
const supportedBackends = Object.keys(allowCliList);
|
|
894
|
+
const discoveredBackends = await listRuntimeSupportedBackends({ configFilePath: configFileFromArgs });
|
|
895
|
+
const externalBackends = discoveredBackends.filter((backend) => !RUNTIME_SUPPORTED_BACKENDS.includes(backend));
|
|
838
896
|
|
|
839
897
|
const conductorArgs = yargs(conductorArgv)
|
|
840
898
|
.scriptName(CLI_NAME)
|
|
@@ -886,14 +944,14 @@ export function parseCliArgs(argvInput = process.argv) {
|
|
|
886
944
|
|
|
887
945
|
// Handle help early
|
|
888
946
|
if (helpWithoutSeparator) {
|
|
889
|
-
const defaultBackend = supportedBackends[0] || "none";
|
|
947
|
+
const defaultBackend = supportedBackends[0] || externalBackends[0] || "none";
|
|
890
948
|
process.stdout.write(`${CLI_NAME} - Conductor-aware AI coding agent runner
|
|
891
949
|
|
|
892
950
|
Usage: ${CLI_NAME} [options] -- [backend options and prompt]
|
|
893
951
|
|
|
894
952
|
Options:
|
|
895
|
-
-b, --backend <name> Backend to use (from config: ${supportedBackends.join(", ") || "none configured"}) [default: ${defaultBackend}]
|
|
896
|
-
--list-backends List available
|
|
953
|
+
-b, --backend <name> Backend to use (from config or external providers: ${supportedBackends.join(", ") || externalBackends.join(", ") || "none configured"}) [default: ${defaultBackend}]
|
|
954
|
+
--list-backends List available configured and external backends
|
|
897
955
|
--config-file <path> Path to Conductor config file
|
|
898
956
|
--poll-interval <ms> Polling interval when waiting for Conductor messages
|
|
899
957
|
-t, --title <text> Optional task title shown in the app task list
|
|
@@ -937,20 +995,21 @@ Environment:
|
|
|
937
995
|
.parse();
|
|
938
996
|
|
|
939
997
|
const backend = conductorArgs.backend
|
|
940
|
-
?
|
|
941
|
-
: supportedBackends[0];
|
|
998
|
+
? await normalizeRuntimeBackendAlias(conductorArgs.backend, { configFilePath: configFileFromArgs })
|
|
999
|
+
: supportedBackends[0] || externalBackends[0];
|
|
942
1000
|
const shouldRequireBackend =
|
|
943
1001
|
!Boolean(conductorArgs.listBackends) &&
|
|
944
1002
|
!listBackendsWithoutSeparator &&
|
|
945
1003
|
!Boolean(conductorArgs.version) &&
|
|
946
1004
|
!versionWithoutSeparator;
|
|
947
|
-
|
|
1005
|
+
const runtimeSupportedBackends = new Set(discoveredBackends);
|
|
1006
|
+
if (backend && !runtimeSupportedBackends.has(backend) && shouldRequireBackend) {
|
|
948
1007
|
throw new Error(
|
|
949
|
-
`Unsupported backend "${backend}". Supported backends: ${
|
|
1008
|
+
`Unsupported backend "${backend}". Supported backends: ${[...runtimeSupportedBackends].join(", ") || "none configured"}.`,
|
|
950
1009
|
);
|
|
951
1010
|
}
|
|
952
1011
|
if (!backend && shouldRequireBackend) {
|
|
953
|
-
throw new Error("No supported backends configured. Add
|
|
1012
|
+
throw new Error("No supported backends configured. Add allow_cli_list entries or set AISDK_PROVIDER_PATH for external providers.");
|
|
954
1013
|
}
|
|
955
1014
|
|
|
956
1015
|
const prompt = (backendArgs._ || []).map((part) => String(part)).join(" ").trim();
|
|
@@ -1225,6 +1284,7 @@ export async function resolveResumeContext(backend, sessionId, options = {}) {
|
|
|
1225
1284
|
|
|
1226
1285
|
export async function bootstrapResumeContextForFire({
|
|
1227
1286
|
backend,
|
|
1287
|
+
configFile,
|
|
1228
1288
|
resumeSessionId,
|
|
1229
1289
|
env = process.env,
|
|
1230
1290
|
resolveResumeContextFn = resolveResumeContext,
|
|
@@ -1247,10 +1307,11 @@ export async function bootstrapResumeContextForFire({
|
|
|
1247
1307
|
return { resumeContext, runtimeProjectPath };
|
|
1248
1308
|
}
|
|
1249
1309
|
|
|
1250
|
-
resumeContext = await resolveResumeContextFn(backend, resumeSessionId
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1310
|
+
resumeContext = await resolveResumeContextFn(backend, resumeSessionId, {
|
|
1311
|
+
configFilePath: configFile,
|
|
1312
|
+
});
|
|
1313
|
+
const sessionLocation = resumeContext.sessionPath ? ` at ${resumeContext.sessionPath}` : "";
|
|
1314
|
+
logger(`Validated --resume ${resumeContext.sessionId} (${resumeContext.provider})${sessionLocation}`);
|
|
1254
1315
|
logger(`Resume will run backend from ${resumeContext.cwd}`);
|
|
1255
1316
|
runtimeProjectPath = await applyWorkingDirectoryFn(resumeContext.cwd);
|
|
1256
1317
|
logger(`Switched working directory to ${runtimeProjectPath} before Conductor connect`);
|
|
@@ -1459,10 +1520,24 @@ export class BridgeRunner {
|
|
|
1459
1520
|
const fallbackSessionId = this.resumeSessionId;
|
|
1460
1521
|
const sessionId = discoveredSessionId || fallbackSessionId;
|
|
1461
1522
|
const sessionFilePath = sessionInfo?.sessionFilePath ? String(sessionInfo.sessionFilePath).trim() : "";
|
|
1523
|
+
const sessionModel =
|
|
1524
|
+
sessionInfo?.model && String(sessionInfo.model).trim()
|
|
1525
|
+
? String(sessionInfo.model).trim()
|
|
1526
|
+
: this.backendSession.threadOptions?.model || this.backendName;
|
|
1527
|
+
const sessionModelProvider =
|
|
1528
|
+
sessionInfo?.modelProvider && String(sessionInfo.modelProvider).trim()
|
|
1529
|
+
? String(sessionInfo.modelProvider).trim()
|
|
1530
|
+
: this.backendSession.threadOptions?.modelProvider || undefined;
|
|
1462
1531
|
const hasRealSessionId = Boolean(sessionId);
|
|
1532
|
+
const messageSuffix = [
|
|
1533
|
+
sessionModel ? `model=${sessionModel}` : "",
|
|
1534
|
+
sessionModelProvider ? `provider=${sessionModelProvider}` : "",
|
|
1535
|
+
]
|
|
1536
|
+
.filter(Boolean)
|
|
1537
|
+
.join(" ");
|
|
1463
1538
|
const message = hasRealSessionId
|
|
1464
|
-
? `${this.backendName} session started: ${sessionId}`
|
|
1465
|
-
: `${this.backendName} session started`;
|
|
1539
|
+
? `${this.backendName} session started: ${sessionId}${messageSuffix ? ` (${messageSuffix})` : ""}`
|
|
1540
|
+
: `${this.backendName} session started${messageSuffix ? ` (${messageSuffix})` : ""}`;
|
|
1466
1541
|
if (hasRealSessionId) {
|
|
1467
1542
|
await this.persistTaskSessionBinding({
|
|
1468
1543
|
sessionId,
|
|
@@ -1472,6 +1547,8 @@ export class BridgeRunner {
|
|
|
1472
1547
|
try {
|
|
1473
1548
|
await this.conductor.sendMessage(this.taskId, message, {
|
|
1474
1549
|
backend: this.backendName,
|
|
1550
|
+
model: sessionModel || undefined,
|
|
1551
|
+
model_provider: sessionModelProvider || undefined,
|
|
1475
1552
|
thread_id: hasRealSessionId ? sessionId : undefined,
|
|
1476
1553
|
session_id: hasRealSessionId ? sessionId : undefined,
|
|
1477
1554
|
session_file_path: sessionFilePath || undefined,
|
|
@@ -1593,6 +1670,10 @@ export class BridgeRunner {
|
|
|
1593
1670
|
this.needsReconnectRecovery = true;
|
|
1594
1671
|
}
|
|
1595
1672
|
|
|
1673
|
+
shouldSuppressReconnectRecovery() {
|
|
1674
|
+
return this.stopped || Boolean(this.remoteStopInfo);
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1596
1677
|
getRemoteStopSummary() {
|
|
1597
1678
|
if (!this.remoteStopInfo) {
|
|
1598
1679
|
return null;
|
|
@@ -1897,7 +1978,7 @@ export class BridgeRunner {
|
|
|
1897
1978
|
this.copilotLog(
|
|
1898
1979
|
`runtime replyTo=${replyTo || "latest"} state=${runtime.state || ""} phase=${runtime.phase || ""} inProgress=${Boolean(
|
|
1899
1980
|
runtime.reply_in_progress,
|
|
1900
|
-
)} status="${sanitizeForLog(runtime.status_line || "", 120)}" done="${sanitizeForLog(runtime.status_done_line || "", 120)}" preview="${sanitizeForLog(runtime.reply_preview || "", 120)}"`,
|
|
1981
|
+
)} 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
1982
|
);
|
|
1902
1983
|
|
|
1903
1984
|
try {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@love-moon/conductor-cli",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"gitCommitId": "
|
|
3
|
+
"version": "0.2.32",
|
|
4
|
+
"gitCommitId": "c749d4b",
|
|
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.
|
|
22
|
-
"@love-moon/conductor-sdk": "0.2.
|
|
21
|
+
"@love-moon/ai-sdk": "0.2.32",
|
|
22
|
+
"@love-moon/conductor-sdk": "0.2.32",
|
|
23
23
|
"chrome-launcher": "^1.2.1",
|
|
24
24
|
"chrome-remote-interface": "^0.33.0",
|
|
25
25
|
"dotenv": "^16.4.5",
|