@love-moon/conductor-cli 0.2.32 → 0.2.34
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-fire.js +199 -79
- package/package.json +4 -4
- package/src/daemon.js +772 -52
- package/src/fire/resume.js +67 -7
- package/src/runtime-backends.js +306 -3
package/bin/conductor-fire.js
CHANGED
|
@@ -19,7 +19,7 @@ import yargs from "yargs/yargs";
|
|
|
19
19
|
import { hideBin } from "yargs/helpers";
|
|
20
20
|
import yaml from "js-yaml";
|
|
21
21
|
import { createAiSession } from "@love-moon/ai-sdk";
|
|
22
|
-
import { ConductorClient, loadConfig } from "@love-moon/conductor-sdk";
|
|
22
|
+
import { ConductorClient, ProjectContext, loadConfig } from "@love-moon/conductor-sdk";
|
|
23
23
|
import {
|
|
24
24
|
buildResumeArgsForBackend as buildCliResumeArgsForBackend,
|
|
25
25
|
resolveResumeContext as resolveCliResumeContext,
|
|
@@ -28,8 +28,9 @@ import {
|
|
|
28
28
|
} from "../src/fire/resume.js";
|
|
29
29
|
import {
|
|
30
30
|
filterRuntimeSupportedAllowCliList,
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
isBuiltInRuntimeBackend,
|
|
32
|
+
listAdvertisedBackends,
|
|
33
|
+
resolveConfiguredRuntimeBackend,
|
|
33
34
|
normalizeRuntimeBackendAlias,
|
|
34
35
|
normalizeRuntimeBackendName,
|
|
35
36
|
} from "../src/runtime-backends.js";
|
|
@@ -99,37 +100,46 @@ async function loadAllowCliList(configFilePath) {
|
|
|
99
100
|
return {};
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
export function resolveAiSessionCommandLine(backend, allowCliList, env = process.env) {
|
|
103
|
+
export function resolveAiSessionCommandLine(backend, allowCliList, env = process.env, sessionBackend = backend) {
|
|
103
104
|
const normalizedBackend = normalizeRuntimeBackendName(backend);
|
|
105
|
+
const normalizedSessionBackend = normalizeRuntimeBackendName(sessionBackend);
|
|
104
106
|
const envKeyByBackend = {
|
|
107
|
+
codex: "CONDUCTOR_CODEX_APP_SERVER_COMMAND",
|
|
105
108
|
opencode: "CONDUCTOR_OPENCODE_COMMAND",
|
|
106
109
|
kimi: "CONDUCTOR_KIMI_COMMAND",
|
|
107
110
|
};
|
|
108
|
-
const envKey = envKeyByBackend[
|
|
109
|
-
if (!envKey) {
|
|
110
|
-
return "";
|
|
111
|
-
}
|
|
111
|
+
const envKey = envKeyByBackend[normalizedSessionBackend];
|
|
112
112
|
|
|
113
|
-
const preferredEnvCommand = typeof env?.[envKey] === "string" ? env[envKey].trim() : "";
|
|
113
|
+
const preferredEnvCommand = envKey && typeof env?.[envKey] === "string" ? env[envKey].trim() : "";
|
|
114
114
|
if (preferredEnvCommand) {
|
|
115
115
|
return preferredEnvCommand;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
const configuredCommand =
|
|
119
|
-
allowCliList && typeof allowCliList === "object"
|
|
120
|
-
? allowCliList[normalizedBackend]
|
|
119
|
+
allowCliList && typeof allowCliList === "object"
|
|
120
|
+
? typeof allowCliList[normalizedBackend] === "string"
|
|
121
|
+
? allowCliList[normalizedBackend].trim()
|
|
122
|
+
: typeof allowCliList[normalizedSessionBackend] === "string"
|
|
123
|
+
? allowCliList[normalizedSessionBackend].trim()
|
|
124
|
+
: ""
|
|
121
125
|
: "";
|
|
122
|
-
if (configuredCommand) {
|
|
123
|
-
return configuredCommand;
|
|
124
|
-
}
|
|
125
126
|
|
|
126
127
|
const daemonCommand =
|
|
127
128
|
typeof env?.CONDUCTOR_CLI_COMMAND === "string" ? env.CONDUCTOR_CLI_COMMAND.trim() : "";
|
|
128
|
-
|
|
129
|
-
|
|
129
|
+
|
|
130
|
+
const resolvedCommand = configuredCommand || daemonCommand;
|
|
131
|
+
if (!resolvedCommand) {
|
|
132
|
+
return "";
|
|
130
133
|
}
|
|
131
134
|
|
|
132
|
-
|
|
135
|
+
if (normalizedSessionBackend === "codex") {
|
|
136
|
+
if (/\bapp-server\b/.test(resolvedCommand)) {
|
|
137
|
+
return resolvedCommand;
|
|
138
|
+
}
|
|
139
|
+
return `${resolvedCommand} app-server --listen stdio://`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return resolvedCommand;
|
|
133
143
|
}
|
|
134
144
|
|
|
135
145
|
const DEFAULT_POLL_INTERVAL_MS = parseInt(
|
|
@@ -443,12 +453,15 @@ async function main() {
|
|
|
443
453
|
}
|
|
444
454
|
|
|
445
455
|
const allowCliList = await loadAllowCliList(cliArgs.configFile);
|
|
446
|
-
const supportedBackends =
|
|
447
|
-
|
|
448
|
-
|
|
456
|
+
const { supportedBackends, externalBackends, discoveryError } = await listAdvertisedBackends(allowCliList, {
|
|
457
|
+
configFilePath: cliArgs.configFile,
|
|
458
|
+
});
|
|
449
459
|
|
|
450
460
|
if (cliArgs.listBackends) {
|
|
451
461
|
if (supportedBackends.length === 0 && externalBackends.length === 0) {
|
|
462
|
+
if (discoveryError) {
|
|
463
|
+
throw discoveryError;
|
|
464
|
+
}
|
|
452
465
|
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`);
|
|
453
466
|
} else {
|
|
454
467
|
if (supportedBackends.length > 0) {
|
|
@@ -471,7 +484,7 @@ async function main() {
|
|
|
471
484
|
let resumeContext = null;
|
|
472
485
|
if (cliArgs.resumeSessionId) {
|
|
473
486
|
const bootstrap = await bootstrapResumeContextForFire({
|
|
474
|
-
backend: cliArgs.backend,
|
|
487
|
+
backend: cliArgs.sessionBackend || cliArgs.backend,
|
|
475
488
|
configFile: cliArgs.configFile,
|
|
476
489
|
resumeSessionId: cliArgs.resumeSessionId,
|
|
477
490
|
});
|
|
@@ -611,6 +624,7 @@ async function main() {
|
|
|
611
624
|
requestedTitle: requestedTaskTitle,
|
|
612
625
|
backend: cliArgs.backend,
|
|
613
626
|
daemonName: configuredDaemonName,
|
|
627
|
+
projectPath: runtimeProjectPath,
|
|
614
628
|
});
|
|
615
629
|
injectResolvedTaskId(taskContext.taskId);
|
|
616
630
|
injectResolvedTaskId(taskContext.taskId, env);
|
|
@@ -641,9 +655,14 @@ async function main() {
|
|
|
641
655
|
|
|
642
656
|
const resolvedResumeSessionId = cliArgs.resumeSessionId;
|
|
643
657
|
|
|
644
|
-
const sessionCommandLine = resolveAiSessionCommandLine(
|
|
658
|
+
const sessionCommandLine = resolveAiSessionCommandLine(
|
|
659
|
+
cliArgs.backend,
|
|
660
|
+
allowCliList,
|
|
661
|
+
process.env,
|
|
662
|
+
cliArgs.sessionBackend,
|
|
663
|
+
);
|
|
645
664
|
|
|
646
|
-
backendSession = createAiSession(cliArgs.backend, {
|
|
665
|
+
backendSession = createAiSession(cliArgs.sessionBackend || cliArgs.backend, {
|
|
647
666
|
initialImages: cliArgs.initialImages,
|
|
648
667
|
cwd: runtimeProjectPath,
|
|
649
668
|
resumeSessionId: resolvedResumeSessionId,
|
|
@@ -728,8 +747,8 @@ async function main() {
|
|
|
728
747
|
|
|
729
748
|
let runnerError = null;
|
|
730
749
|
try {
|
|
731
|
-
if (!resolvedResumeSessionId && String(cliArgs.backend).trim().toLowerCase() === "codex") {
|
|
732
|
-
await withFreshSessionBootstrapLock(cliArgs.backend, runtimeProjectPath, async () => {
|
|
750
|
+
if (!resolvedResumeSessionId && String(cliArgs.sessionBackend || cliArgs.backend).trim().toLowerCase() === "codex") {
|
|
751
|
+
await withFreshSessionBootstrapLock(cliArgs.sessionBackend || cliArgs.backend, runtimeProjectPath, async () => {
|
|
733
752
|
await runner.announceBackendSession();
|
|
734
753
|
});
|
|
735
754
|
}
|
|
@@ -741,7 +760,13 @@ async function main() {
|
|
|
741
760
|
process.off("SIGINT", onSigint);
|
|
742
761
|
process.off("SIGTERM", onSigterm);
|
|
743
762
|
if (!launchedByDaemon) {
|
|
763
|
+
const remoteStopReason = typeof runner.getRemoteStopReason === "function" ? runner.getRemoteStopReason() : null;
|
|
744
764
|
const remoteStopSummary = typeof runner.getRemoteStopSummary === "function" ? runner.getRemoteStopSummary() : null;
|
|
765
|
+
// When the task was deleted by the user, the DB record is already gone —
|
|
766
|
+
// attempting to send a final status update would fail with 500 and the
|
|
767
|
+
// SDK durable outbox would retry forever, preventing the process from
|
|
768
|
+
// exiting.
|
|
769
|
+
const taskDeletedByUser = remoteStopReason === "deleted_by_user";
|
|
745
770
|
const finalStatus = shutdownSignal
|
|
746
771
|
? {
|
|
747
772
|
status: "KILLED",
|
|
@@ -761,16 +786,25 @@ async function main() {
|
|
|
761
786
|
status: "COMPLETED",
|
|
762
787
|
summary: "conductor fire exited",
|
|
763
788
|
};
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
789
|
+
if (!taskDeletedByUser) {
|
|
790
|
+
try {
|
|
791
|
+
const statusResult = await conductor.sendTaskStatus(taskContext.taskId, finalStatus);
|
|
792
|
+
if (statusResult?.pending && typeof conductor.flushPendingUpstreamEvents === "function") {
|
|
793
|
+
await conductor.flushPendingUpstreamEvents({
|
|
794
|
+
timeoutMs: 5_000,
|
|
795
|
+
retryIntervalMs: 250,
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
} catch (error) {
|
|
799
|
+
log(`Failed to report task status (${finalStatus.status}): ${error?.message || error}`);
|
|
800
|
+
}
|
|
801
|
+
} else {
|
|
802
|
+
log(`Skipping final status report: task was deleted by user`);
|
|
803
|
+
// Also clear any pending durable outbox retries (e.g. task_stop_ack)
|
|
804
|
+
// that would keep failing against the deleted task.
|
|
805
|
+
if (typeof conductor.clearDurableOutboxTimer === "function") {
|
|
806
|
+
conductor.clearDurableOutboxTimer();
|
|
771
807
|
}
|
|
772
|
-
} catch (error) {
|
|
773
|
-
log(`Failed to report task status (${finalStatus.status}): ${error?.message || error}`);
|
|
774
808
|
}
|
|
775
809
|
}
|
|
776
810
|
if (shutdownSignal === "SIGINT") {
|
|
@@ -890,9 +924,9 @@ export async function parseCliArgs(argvInput = process.argv) {
|
|
|
890
924
|
|
|
891
925
|
const configFileFromArgs = extractConfigFileFromArgv(argv);
|
|
892
926
|
const allowCliList = await loadAllowCliList(configFileFromArgs);
|
|
893
|
-
const supportedBackends =
|
|
894
|
-
|
|
895
|
-
|
|
927
|
+
const { supportedBackends, externalBackends, discoveryError } = await listAdvertisedBackends(allowCliList, {
|
|
928
|
+
configFilePath: configFileFromArgs,
|
|
929
|
+
});
|
|
896
930
|
|
|
897
931
|
const conductorArgs = yargs(conductorArgv)
|
|
898
932
|
.scriptName(CLI_NAME)
|
|
@@ -994,21 +1028,36 @@ Environment:
|
|
|
994
1028
|
})
|
|
995
1029
|
.parse();
|
|
996
1030
|
|
|
997
|
-
const
|
|
998
|
-
?
|
|
1031
|
+
const requestedBackend = conductorArgs.backend
|
|
1032
|
+
? normalizeRuntimeBackendName(conductorArgs.backend)
|
|
999
1033
|
: supportedBackends[0] || externalBackends[0];
|
|
1034
|
+
const configuredBackend = await resolveConfiguredRuntimeBackend(requestedBackend, allowCliList, {
|
|
1035
|
+
configFilePath: configFileFromArgs,
|
|
1036
|
+
});
|
|
1037
|
+
const backend = configuredBackend?.requestedBackend || requestedBackend;
|
|
1038
|
+
const sessionBackend =
|
|
1039
|
+
configuredBackend?.runtimeBackend ||
|
|
1040
|
+
(backend ? await normalizeRuntimeBackendAlias(backend, { configFilePath: configFileFromArgs }) : "");
|
|
1000
1041
|
const shouldRequireBackend =
|
|
1001
1042
|
!Boolean(conductorArgs.listBackends) &&
|
|
1002
1043
|
!listBackendsWithoutSeparator &&
|
|
1003
1044
|
!Boolean(conductorArgs.version) &&
|
|
1004
1045
|
!versionWithoutSeparator;
|
|
1005
|
-
const runtimeSupportedBackends = new Set(
|
|
1006
|
-
|
|
1046
|
+
const runtimeSupportedBackends = new Set(supportedBackends);
|
|
1047
|
+
const advertisedExternalBackends = new Set(externalBackends);
|
|
1048
|
+
const hasConfiguredEntry = Boolean(configuredBackend?.commandLine);
|
|
1049
|
+
const isAllowedExternalBackend =
|
|
1050
|
+
!isBuiltInRuntimeBackend(sessionBackend) &&
|
|
1051
|
+
advertisedExternalBackends.has(sessionBackend);
|
|
1052
|
+
if (backend && shouldRequireBackend && !hasConfiguredEntry && !isAllowedExternalBackend) {
|
|
1007
1053
|
throw new Error(
|
|
1008
1054
|
`Unsupported backend "${backend}". Supported backends: ${[...runtimeSupportedBackends].join(", ") || "none configured"}.`,
|
|
1009
1055
|
);
|
|
1010
1056
|
}
|
|
1011
1057
|
if (!backend && shouldRequireBackend) {
|
|
1058
|
+
if (discoveryError) {
|
|
1059
|
+
throw discoveryError;
|
|
1060
|
+
}
|
|
1012
1061
|
throw new Error("No supported backends configured. Add allow_cli_list entries or set AISDK_PROVIDER_PATH for external providers.");
|
|
1013
1062
|
}
|
|
1014
1063
|
|
|
@@ -1036,6 +1085,7 @@ Environment:
|
|
|
1036
1085
|
taskTitle: typeof conductorArgs.title === "string" ? conductorArgs.title.trim() : "",
|
|
1037
1086
|
hasExplicitTaskTitle: typeof conductorArgs.title === "string" && Boolean(conductorArgs.title.trim()),
|
|
1038
1087
|
configFile: conductorArgs.configFile,
|
|
1088
|
+
sessionBackend,
|
|
1039
1089
|
resumeSessionId,
|
|
1040
1090
|
showVersion: Boolean(conductorArgs.version) || versionWithoutSeparator,
|
|
1041
1091
|
listBackends: Boolean(conductorArgs.listBackends) || listBackendsWithoutSeparator,
|
|
@@ -1152,7 +1202,10 @@ async function ensureTaskContext(conductor, opts) {
|
|
|
1152
1202
|
};
|
|
1153
1203
|
}
|
|
1154
1204
|
|
|
1155
|
-
const projectId = await resolveProjectId(conductor, opts.requestedProjectId
|
|
1205
|
+
const projectId = await resolveProjectId(conductor, opts.requestedProjectId, {
|
|
1206
|
+
daemonName: opts.daemonName,
|
|
1207
|
+
projectPath: opts.projectPath,
|
|
1208
|
+
});
|
|
1156
1209
|
const payload = {
|
|
1157
1210
|
project_id: projectId,
|
|
1158
1211
|
task_title: deriveTaskTitle(opts.initialPrompt, opts.requestedTitle, opts.backend),
|
|
@@ -1167,15 +1220,6 @@ async function ensureTaskContext(conductor, opts) {
|
|
|
1167
1220
|
|
|
1168
1221
|
const session = await conductor.createTaskSession(payload);
|
|
1169
1222
|
|
|
1170
|
-
// Auto-bind current path to project if not already bound
|
|
1171
|
-
try {
|
|
1172
|
-
await conductor.bindProjectPath(projectId);
|
|
1173
|
-
log(`Bound current path to project ${projectId}`);
|
|
1174
|
-
} catch (error) {
|
|
1175
|
-
// Ignore binding errors - it's not critical
|
|
1176
|
-
log(`Note: Could not bind path to project: ${error.message}`);
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
1223
|
return {
|
|
1180
1224
|
taskId: session.task_id,
|
|
1181
1225
|
appUrl: session.app_url || null,
|
|
@@ -1184,51 +1228,123 @@ async function ensureTaskContext(conductor, opts) {
|
|
|
1184
1228
|
};
|
|
1185
1229
|
}
|
|
1186
1230
|
|
|
1187
|
-
async function resolveProjectId(conductor, explicit) {
|
|
1231
|
+
export async function resolveProjectId(conductor, explicit, opts = {}) {
|
|
1188
1232
|
if (explicit) {
|
|
1189
1233
|
return explicit;
|
|
1190
1234
|
}
|
|
1191
1235
|
|
|
1192
|
-
|
|
1236
|
+
const daemonHost = resolveDaemonHost(opts.daemonName);
|
|
1237
|
+
const projectPath = typeof opts.projectPath === "string" && opts.projectPath.trim() ? opts.projectPath.trim() : process.cwd();
|
|
1238
|
+
|
|
1239
|
+
if (!daemonHost) {
|
|
1240
|
+
return resolveDefaultProjectId(conductor);
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
const exists = await isExistingDirectory(projectPath);
|
|
1244
|
+
if (!exists) {
|
|
1245
|
+
throw new Error(`Workspace path does not exist: ${projectPath}`);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
const snapshot = resolveWorkspaceSnapshot(projectPath);
|
|
1249
|
+
const projectName = deriveProjectName(snapshot);
|
|
1250
|
+
|
|
1193
1251
|
try {
|
|
1194
|
-
const matchResult = await conductor.matchProjectByPath(
|
|
1252
|
+
const matchResult = await conductor.matchProjectByPath({
|
|
1253
|
+
daemon_host: daemonHost,
|
|
1254
|
+
project_path: snapshot.projectRoot,
|
|
1255
|
+
});
|
|
1195
1256
|
if (matchResult?.project_id) {
|
|
1196
1257
|
log(`Matched project ${matchResult.project_name || matchResult.project_id} by path ${matchResult.matched_path}`);
|
|
1197
|
-
|
|
1258
|
+
let resolvedProjectId = matchResult.project_id;
|
|
1259
|
+
try {
|
|
1260
|
+
const bindResult = await conductor.bindProjectPath(matchResult.project_id, {
|
|
1261
|
+
daemon_host: daemonHost,
|
|
1262
|
+
project_path: snapshot.projectRoot,
|
|
1263
|
+
});
|
|
1264
|
+
if (typeof bindResult?.project_id === "string" && bindResult.project_id.trim()) {
|
|
1265
|
+
resolvedProjectId = bindResult.project_id.trim();
|
|
1266
|
+
}
|
|
1267
|
+
} catch (error) {
|
|
1268
|
+
log(`Unable to backfill bound workspace path: ${error.message}`);
|
|
1269
|
+
try {
|
|
1270
|
+
const rebound = await conductor.matchProjectByPath({
|
|
1271
|
+
daemon_host: daemonHost,
|
|
1272
|
+
project_path: snapshot.projectRoot,
|
|
1273
|
+
});
|
|
1274
|
+
if (rebound?.project_id) {
|
|
1275
|
+
resolvedProjectId = rebound.project_id;
|
|
1276
|
+
}
|
|
1277
|
+
} catch {
|
|
1278
|
+
// ignore retry match failures
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
return resolvedProjectId;
|
|
1198
1282
|
}
|
|
1199
1283
|
} catch (error) {
|
|
1200
1284
|
log(`Unable to match project by path: ${error.message}`);
|
|
1201
1285
|
}
|
|
1202
1286
|
|
|
1287
|
+
log(`No matching project found for ${daemonHost}:${snapshot.projectRoot}, falling back to default`);
|
|
1288
|
+
return resolveDefaultProjectId(conductor);
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
function resolveDaemonHost(daemonName) {
|
|
1292
|
+
if (typeof daemonName === "string" && daemonName.trim()) {
|
|
1293
|
+
return daemonName.trim();
|
|
1294
|
+
}
|
|
1295
|
+
const fromEnv = typeof process.env.CONDUCTOR_DAEMON_NAME === "string" ? process.env.CONDUCTOR_DAEMON_NAME.trim() : "";
|
|
1296
|
+
if (fromEnv) {
|
|
1297
|
+
return fromEnv;
|
|
1298
|
+
}
|
|
1299
|
+
const fromAgent = typeof process.env.CONDUCTOR_AGENT_NAME === "string" ? process.env.CONDUCTOR_AGENT_NAME.trim() : "";
|
|
1300
|
+
if (fromAgent) {
|
|
1301
|
+
return fromAgent;
|
|
1302
|
+
}
|
|
1203
1303
|
try {
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
const listing = await conductor.listProjects();
|
|
1208
|
-
const exists = Array.isArray(listing?.projects)
|
|
1209
|
-
? listing.projects.some((project) => String(project?.id || "") === String(record.project_id))
|
|
1210
|
-
: false;
|
|
1211
|
-
if (exists) {
|
|
1212
|
-
return record.project_id;
|
|
1213
|
-
}
|
|
1214
|
-
log(`Local session project ${record.project_id} no longer exists; falling back to server project list`);
|
|
1215
|
-
} catch (verifyError) {
|
|
1216
|
-
log(`Unable to verify local project record; using cached project id: ${verifyError.message}`);
|
|
1217
|
-
return record.project_id;
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
} catch (error) {
|
|
1221
|
-
log(`Unable to resolve project via local session: ${error.message}`);
|
|
1304
|
+
return os.hostname();
|
|
1305
|
+
} catch {
|
|
1306
|
+
return "";
|
|
1222
1307
|
}
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
function resolveWorkspaceSnapshot(projectPath) {
|
|
1311
|
+
try {
|
|
1312
|
+
const context = new ProjectContext(projectPath);
|
|
1313
|
+
return context.snapshot();
|
|
1314
|
+
} catch {
|
|
1315
|
+
return {
|
|
1316
|
+
projectRoot: path.resolve(projectPath),
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1223
1320
|
|
|
1224
|
-
|
|
1225
|
-
const
|
|
1226
|
-
|
|
1227
|
-
|
|
1321
|
+
function deriveProjectName(snapshot) {
|
|
1322
|
+
const basePath = snapshot.repoRoot || snapshot.projectRoot;
|
|
1323
|
+
const name = basePath ? path.basename(basePath) : "";
|
|
1324
|
+
const baseName = name || "New Project";
|
|
1325
|
+
const digest = createHash("sha1").update(basePath || baseName).digest("hex").slice(0, 8);
|
|
1326
|
+
return `${baseName}-${digest}`;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
async function resolveDefaultProjectId(conductor) {
|
|
1330
|
+
try {
|
|
1331
|
+
const listing = await conductor.listProjects();
|
|
1332
|
+
const defaultProject = Array.isArray(listing?.projects)
|
|
1333
|
+
? listing.projects.find((project) => Boolean(project?.isDefault))
|
|
1334
|
+
: null;
|
|
1335
|
+
if (defaultProject?.id) {
|
|
1336
|
+
return defaultProject.id;
|
|
1337
|
+
}
|
|
1338
|
+
} catch {
|
|
1339
|
+
// ignore list failures
|
|
1228
1340
|
}
|
|
1229
|
-
|
|
1341
|
+
|
|
1342
|
+
log("No bound daemon available; creating default project...");
|
|
1230
1343
|
try {
|
|
1231
|
-
const created = await conductor.createProject(
|
|
1344
|
+
const created = await conductor.createProject({
|
|
1345
|
+
name: "Default Project",
|
|
1346
|
+
isDefault: true,
|
|
1347
|
+
});
|
|
1232
1348
|
if (created?.id) {
|
|
1233
1349
|
log(`Created default project ${created.id}`);
|
|
1234
1350
|
return created.id;
|
|
@@ -1674,6 +1790,10 @@ export class BridgeRunner {
|
|
|
1674
1790
|
return this.stopped || Boolean(this.remoteStopInfo);
|
|
1675
1791
|
}
|
|
1676
1792
|
|
|
1793
|
+
getRemoteStopReason() {
|
|
1794
|
+
return this.remoteStopInfo?.reason || null;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1677
1797
|
getRemoteStopSummary() {
|
|
1678
1798
|
if (!this.remoteStopInfo) {
|
|
1679
1799
|
return null;
|
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.34",
|
|
4
|
+
"gitCommitId": "c3c936c",
|
|
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.34",
|
|
22
|
+
"@love-moon/conductor-sdk": "0.2.34",
|
|
23
23
|
"chrome-launcher": "^1.2.1",
|
|
24
24
|
"chrome-remote-interface": "^0.33.0",
|
|
25
25
|
"dotenv": "^16.4.5",
|