@love-moon/conductor-cli 0.2.18 → 0.2.20
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-channel.js +130 -0
- package/bin/conductor-config.js +29 -30
- package/bin/conductor-diagnose.js +25 -0
- package/bin/conductor-fire.js +300 -9
- package/bin/conductor.js +5 -1
- package/package.json +12 -4
- package/src/daemon.js +1112 -27
- package/src/runtime-backends.js +31 -0
package/bin/conductor-fire.js
CHANGED
|
@@ -26,6 +26,7 @@ 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
30
|
|
|
30
31
|
const __filename = fileURLToPath(import.meta.url);
|
|
31
32
|
const __dirname = path.dirname(__filename);
|
|
@@ -50,7 +51,7 @@ function loadAllowCliList(configFilePath) {
|
|
|
50
51
|
const content = fs.readFileSync(configPath, "utf8");
|
|
51
52
|
const parsed = yaml.load(content);
|
|
52
53
|
if (parsed && typeof parsed === "object" && parsed.allow_cli_list) {
|
|
53
|
-
return parsed.allow_cli_list;
|
|
54
|
+
return filterRuntimeSupportedAllowCliList(parsed.allow_cli_list);
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
} catch (error) {
|
|
@@ -59,6 +60,35 @@ function loadAllowCliList(configFilePath) {
|
|
|
59
60
|
return {};
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
export function resolveAiSessionCommandLine(backend, allowCliList, env = process.env) {
|
|
64
|
+
const normalizedBackend = normalizeRuntimeBackendName(backend);
|
|
65
|
+
if (normalizedBackend !== "opencode") {
|
|
66
|
+
return "";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const opencodeEnvCommand =
|
|
70
|
+
typeof env?.CONDUCTOR_OPENCODE_COMMAND === "string" ? env.CONDUCTOR_OPENCODE_COMMAND.trim() : "";
|
|
71
|
+
if (opencodeEnvCommand) {
|
|
72
|
+
return opencodeEnvCommand;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const configuredCommand =
|
|
76
|
+
allowCliList && typeof allowCliList === "object" && typeof allowCliList.opencode === "string"
|
|
77
|
+
? allowCliList.opencode.trim()
|
|
78
|
+
: "";
|
|
79
|
+
if (configuredCommand) {
|
|
80
|
+
return configuredCommand;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const daemonCommand =
|
|
84
|
+
typeof env?.CONDUCTOR_CLI_COMMAND === "string" ? env.CONDUCTOR_CLI_COMMAND.trim() : "";
|
|
85
|
+
if (daemonCommand) {
|
|
86
|
+
return daemonCommand;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return "";
|
|
90
|
+
}
|
|
91
|
+
|
|
62
92
|
const DEFAULT_POLL_INTERVAL_MS = parseInt(
|
|
63
93
|
process.env.CONDUCTOR_CLI_POLL_INTERVAL_MS || process.env.CCODEX_POLL_INTERVAL_MS || "2000",
|
|
64
94
|
10,
|
|
@@ -68,6 +98,30 @@ const DEFAULT_ERROR_LOOP_BACKOFF_MS = 3 * 60 * 1000;
|
|
|
68
98
|
const DEFAULT_ERROR_LOOP_THRESHOLD = 3;
|
|
69
99
|
const SESSION_BOOTSTRAP_LOCK_TIMEOUT_MS = 15_000;
|
|
70
100
|
const SESSION_BOOTSTRAP_LOCK_RETRY_MS = 50;
|
|
101
|
+
const FIRE_WATCHDOG_INTERVAL_MS = getBoundedEnvInt(
|
|
102
|
+
"CONDUCTOR_FIRE_WATCHDOG_INTERVAL_MS",
|
|
103
|
+
10_000,
|
|
104
|
+
1_000,
|
|
105
|
+
60_000,
|
|
106
|
+
);
|
|
107
|
+
const FIRE_WATCHDOG_STALE_WS_MS = getBoundedEnvInt(
|
|
108
|
+
"CONDUCTOR_FIRE_WATCHDOG_STALE_WS_MS",
|
|
109
|
+
45_000,
|
|
110
|
+
5_000,
|
|
111
|
+
5 * 60_000,
|
|
112
|
+
);
|
|
113
|
+
const FIRE_WATCHDOG_CONNECT_GRACE_MS = getBoundedEnvInt(
|
|
114
|
+
"CONDUCTOR_FIRE_WATCHDOG_CONNECT_GRACE_MS",
|
|
115
|
+
15_000,
|
|
116
|
+
1_000,
|
|
117
|
+
60_000,
|
|
118
|
+
);
|
|
119
|
+
const FIRE_WATCHDOG_RECONNECT_COOLDOWN_MS = getBoundedEnvInt(
|
|
120
|
+
"CONDUCTOR_FIRE_WATCHDOG_RECONNECT_COOLDOWN_MS",
|
|
121
|
+
15_000,
|
|
122
|
+
1_000,
|
|
123
|
+
2 * 60_000,
|
|
124
|
+
);
|
|
71
125
|
|
|
72
126
|
function sleepSync(ms) {
|
|
73
127
|
if (!Number.isFinite(ms) || ms <= 0) {
|
|
@@ -161,6 +215,176 @@ function appendFireLocalLog(line) {
|
|
|
161
215
|
}
|
|
162
216
|
}
|
|
163
217
|
|
|
218
|
+
function formatFireDisconnectDiagnostics(event = {}) {
|
|
219
|
+
const parts = [];
|
|
220
|
+
if (event.reason) {
|
|
221
|
+
parts.push(`reason=${event.reason}`);
|
|
222
|
+
}
|
|
223
|
+
if (typeof event.closeCode === "number") {
|
|
224
|
+
parts.push(`close_code=${event.closeCode}`);
|
|
225
|
+
}
|
|
226
|
+
if (event.closeReason) {
|
|
227
|
+
parts.push(`close_reason=${sanitizeForLog(event.closeReason, 120)}`);
|
|
228
|
+
}
|
|
229
|
+
if (event.socketError) {
|
|
230
|
+
parts.push(`socket_error=${sanitizeForLog(event.socketError, 120)}`);
|
|
231
|
+
}
|
|
232
|
+
if (typeof event.missedPongs === "number" && event.missedPongs > 0) {
|
|
233
|
+
parts.push(`missed_pongs=${event.missedPongs}`);
|
|
234
|
+
}
|
|
235
|
+
return parts.join(" ") || "reason=connection_lost";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function formatIsoTimestamp(value) {
|
|
239
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
240
|
+
return "n/a";
|
|
241
|
+
}
|
|
242
|
+
return new Date(value).toISOString();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function formatFireWatchdogState({ connectedAt, lastPongAt, lastInboundAt }) {
|
|
246
|
+
return [
|
|
247
|
+
`connected_at=${formatIsoTimestamp(connectedAt)}`,
|
|
248
|
+
`last_pong_at=${formatIsoTimestamp(lastPongAt)}`,
|
|
249
|
+
`last_inbound_at=${formatIsoTimestamp(lastInboundAt)}`,
|
|
250
|
+
].join(" ");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export class FireWatchdog {
|
|
254
|
+
constructor({
|
|
255
|
+
intervalMs = FIRE_WATCHDOG_INTERVAL_MS,
|
|
256
|
+
staleWsMs = FIRE_WATCHDOG_STALE_WS_MS,
|
|
257
|
+
connectGraceMs = FIRE_WATCHDOG_CONNECT_GRACE_MS,
|
|
258
|
+
reconnectCooldownMs = FIRE_WATCHDOG_RECONNECT_COOLDOWN_MS,
|
|
259
|
+
onForceReconnect,
|
|
260
|
+
logger = () => {},
|
|
261
|
+
now = () => Date.now(),
|
|
262
|
+
} = {}) {
|
|
263
|
+
this.intervalMs = intervalMs;
|
|
264
|
+
this.staleWsMs = staleWsMs;
|
|
265
|
+
this.connectGraceMs = connectGraceMs;
|
|
266
|
+
this.reconnectCooldownMs = reconnectCooldownMs;
|
|
267
|
+
this.onForceReconnect = onForceReconnect;
|
|
268
|
+
this.logger = logger;
|
|
269
|
+
this.now = now;
|
|
270
|
+
this.wsConnected = false;
|
|
271
|
+
this.lastConnectedAt = null;
|
|
272
|
+
this.lastPongAt = null;
|
|
273
|
+
this.lastInboundAt = null;
|
|
274
|
+
this.lastHealAt = 0;
|
|
275
|
+
this.healAttempts = 0;
|
|
276
|
+
this.awaitingHealthySignalAt = null;
|
|
277
|
+
this.timer = null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
start() {
|
|
281
|
+
if (this.timer) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
this.timer = setInterval(() => {
|
|
285
|
+
void this.runOnce();
|
|
286
|
+
}, this.intervalMs);
|
|
287
|
+
if (typeof this.timer.unref === "function") {
|
|
288
|
+
this.timer.unref();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
stop() {
|
|
293
|
+
if (!this.timer) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
clearInterval(this.timer);
|
|
297
|
+
this.timer = null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
onConnected({ isReconnect = false, connectedAt = this.now() } = {}) {
|
|
301
|
+
this.wsConnected = true;
|
|
302
|
+
this.lastConnectedAt = connectedAt;
|
|
303
|
+
this.lastPongAt =
|
|
304
|
+
Number.isFinite(this.lastPongAt) && this.lastPongAt > connectedAt ? this.lastPongAt : connectedAt;
|
|
305
|
+
if (isReconnect && this.healAttempts > 0) {
|
|
306
|
+
this.awaitingHealthySignalAt = connectedAt;
|
|
307
|
+
} else if (!isReconnect) {
|
|
308
|
+
this.awaitingHealthySignalAt = null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
onDisconnected() {
|
|
313
|
+
this.wsConnected = false;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
onPong({ at = this.now() } = {}) {
|
|
317
|
+
this.lastPongAt = at;
|
|
318
|
+
this.markHealthy("pong", at);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
onInbound(at = this.now()) {
|
|
322
|
+
this.lastInboundAt = at;
|
|
323
|
+
this.markHealthy("inbound", at);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
markHealthy(signal, at = this.now()) {
|
|
327
|
+
if (!this.awaitingHealthySignalAt || this.healAttempts === 0) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (at < this.awaitingHealthySignalAt) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
this.logger(
|
|
334
|
+
`[watchdog] Backend websocket healthy again after self-heal via ${signal} (${formatFireWatchdogState({
|
|
335
|
+
connectedAt: this.lastConnectedAt,
|
|
336
|
+
lastPongAt: this.lastPongAt,
|
|
337
|
+
lastInboundAt: this.lastInboundAt,
|
|
338
|
+
})})`,
|
|
339
|
+
);
|
|
340
|
+
this.awaitingHealthySignalAt = null;
|
|
341
|
+
this.healAttempts = 0;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async runOnce() {
|
|
345
|
+
if (!this.wsConnected || typeof this.onForceReconnect !== "function") {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
const now = this.now();
|
|
349
|
+
if (!Number.isFinite(this.lastConnectedAt) || now - this.lastConnectedAt < this.connectGraceMs) {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
const lastWsHealthAt = Math.max(this.lastPongAt || 0, this.lastInboundAt || 0, this.lastConnectedAt || 0);
|
|
353
|
+
if (lastWsHealthAt && now - lastWsHealthAt <= this.staleWsMs) {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
if (this.lastHealAt && now - this.lastHealAt < this.reconnectCooldownMs) {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
this.lastHealAt = now;
|
|
361
|
+
this.healAttempts += 1;
|
|
362
|
+
this.awaitingHealthySignalAt = null;
|
|
363
|
+
this.wsConnected = false;
|
|
364
|
+
this.logger(
|
|
365
|
+
`[watchdog] stale_ws_health; restarting fire websocket (${this.healAttempts}) (${formatFireWatchdogState({
|
|
366
|
+
connectedAt: this.lastConnectedAt,
|
|
367
|
+
lastPongAt: this.lastPongAt,
|
|
368
|
+
lastInboundAt: this.lastInboundAt,
|
|
369
|
+
})})`,
|
|
370
|
+
);
|
|
371
|
+
await this.onForceReconnect("watchdog:stale_ws_health");
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
getDebugState() {
|
|
376
|
+
return {
|
|
377
|
+
wsConnected: this.wsConnected,
|
|
378
|
+
lastConnectedAt: this.lastConnectedAt,
|
|
379
|
+
lastPongAt: this.lastPongAt,
|
|
380
|
+
lastInboundAt: this.lastInboundAt,
|
|
381
|
+
lastHealAt: this.lastHealAt,
|
|
382
|
+
healAttempts: this.healAttempts,
|
|
383
|
+
awaitingHealthySignalAt: this.awaitingHealthySignalAt,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
164
388
|
async function main() {
|
|
165
389
|
const cliArgs = parseCliArgs();
|
|
166
390
|
let runtimeProjectPath = process.cwd();
|
|
@@ -180,7 +404,7 @@ async function main() {
|
|
|
180
404
|
|
|
181
405
|
if (cliArgs.listBackends) {
|
|
182
406
|
if (supportedBackends.length === 0) {
|
|
183
|
-
process.stdout.write(`No 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
|
|
407
|
+
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 opencode: opencode\n`);
|
|
184
408
|
} else {
|
|
185
409
|
process.stdout.write(`Supported backends (from config):\n`);
|
|
186
410
|
for (const [name, command] of Object.entries(allowCliList)) {
|
|
@@ -209,6 +433,17 @@ async function main() {
|
|
|
209
433
|
let pendingRemoteStopEvent = null;
|
|
210
434
|
let conductor = null;
|
|
211
435
|
let reconnectResumeInFlight = false;
|
|
436
|
+
let fireShuttingDown = false;
|
|
437
|
+
const fireWatchdog = new FireWatchdog({
|
|
438
|
+
onForceReconnect: async (reason) => {
|
|
439
|
+
if (!conductor || typeof conductor.forceReconnect !== "function") {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
await conductor.forceReconnect(reason);
|
|
443
|
+
},
|
|
444
|
+
logger: log,
|
|
445
|
+
});
|
|
446
|
+
fireWatchdog.start();
|
|
212
447
|
|
|
213
448
|
const scheduleReconnectRecovery = ({ isReconnect }) => {
|
|
214
449
|
if (!isReconnect) {
|
|
@@ -244,6 +479,7 @@ async function main() {
|
|
|
244
479
|
};
|
|
245
480
|
|
|
246
481
|
const handleStopTaskCommand = async (event) => {
|
|
482
|
+
fireWatchdog.onInbound();
|
|
247
483
|
if (!event || typeof event !== "object") {
|
|
248
484
|
return;
|
|
249
485
|
}
|
|
@@ -292,7 +528,19 @@ async function main() {
|
|
|
292
528
|
projectPath: runtimeProjectPath,
|
|
293
529
|
extraEnv: env,
|
|
294
530
|
configFile: cliArgs.configFile,
|
|
295
|
-
onConnected:
|
|
531
|
+
onConnected: (event) => {
|
|
532
|
+
fireWatchdog.onConnected(event);
|
|
533
|
+
scheduleReconnectRecovery(event);
|
|
534
|
+
},
|
|
535
|
+
onDisconnected: (event) => {
|
|
536
|
+
fireWatchdog.onDisconnected();
|
|
537
|
+
if (!fireShuttingDown) {
|
|
538
|
+
log(`[fire-ws] Disconnected from backend: ${formatFireDisconnectDiagnostics(event)}`);
|
|
539
|
+
}
|
|
540
|
+
},
|
|
541
|
+
onPong: (event) => {
|
|
542
|
+
fireWatchdog.onPong(event);
|
|
543
|
+
},
|
|
296
544
|
onStopTask: handleStopTaskCommand,
|
|
297
545
|
});
|
|
298
546
|
|
|
@@ -333,11 +581,14 @@ async function main() {
|
|
|
333
581
|
|
|
334
582
|
const resolvedResumeSessionId = cliArgs.resumeSessionId;
|
|
335
583
|
|
|
584
|
+
const sessionCommandLine = resolveAiSessionCommandLine(cliArgs.backend, allowCliList, process.env);
|
|
585
|
+
|
|
336
586
|
backendSession = createAiSession(cliArgs.backend, {
|
|
337
587
|
initialImages: cliArgs.initialImages,
|
|
338
588
|
cwd: runtimeProjectPath,
|
|
339
589
|
resumeSessionId: resolvedResumeSessionId,
|
|
340
590
|
configFile: cliArgs.configFile,
|
|
591
|
+
...(sessionCommandLine ? { commandLine: sessionCommandLine } : {}),
|
|
341
592
|
logger: { log },
|
|
342
593
|
});
|
|
343
594
|
|
|
@@ -359,6 +610,7 @@ async function main() {
|
|
|
359
610
|
taskId: taskContext.taskId,
|
|
360
611
|
pollIntervalMs: Math.max(cliArgs.pollIntervalMs, 500),
|
|
361
612
|
initialPrompt: taskContext.shouldProcessInitialPrompt ? cliArgs.initialPrompt : "",
|
|
613
|
+
initialPromptDelivery: taskContext.initialPromptDelivery || "none",
|
|
362
614
|
includeInitialImages: Boolean(cliArgs.initialPrompt && cliArgs.initialImages.length),
|
|
363
615
|
cliArgs: cliArgs.rawBackendArgs,
|
|
364
616
|
backendName: cliArgs.backend,
|
|
@@ -391,11 +643,13 @@ async function main() {
|
|
|
391
643
|
};
|
|
392
644
|
const onSigint = () => {
|
|
393
645
|
shutdownSignal = shutdownSignal || "SIGINT";
|
|
646
|
+
fireShuttingDown = true;
|
|
394
647
|
signals.abort();
|
|
395
648
|
requestBackendShutdown("SIGINT");
|
|
396
649
|
};
|
|
397
650
|
const onSigterm = () => {
|
|
398
651
|
shutdownSignal = shutdownSignal || "SIGTERM";
|
|
652
|
+
fireShuttingDown = true;
|
|
399
653
|
signals.abort();
|
|
400
654
|
requestBackendShutdown("SIGTERM");
|
|
401
655
|
};
|
|
@@ -460,6 +714,8 @@ async function main() {
|
|
|
460
714
|
}
|
|
461
715
|
}
|
|
462
716
|
} finally {
|
|
717
|
+
fireShuttingDown = true;
|
|
718
|
+
fireWatchdog.stop();
|
|
463
719
|
if (backendSession && typeof backendSession.close === "function") {
|
|
464
720
|
try {
|
|
465
721
|
await backendSession.close();
|
|
@@ -578,7 +834,6 @@ export function parseCliArgs(argvInput = process.argv) {
|
|
|
578
834
|
alias: "b",
|
|
579
835
|
type: "string",
|
|
580
836
|
describe: `Backend to use (loaded from config: ${supportedBackends.join(", ") || "none configured"})`,
|
|
581
|
-
...(supportedBackends.length > 0 ? { choices: supportedBackends } : {}),
|
|
582
837
|
})
|
|
583
838
|
.option("list-backends", {
|
|
584
839
|
type: "boolean",
|
|
@@ -640,12 +895,12 @@ Config file format (~/.conductor/config.yaml):
|
|
|
640
895
|
allow_cli_list:
|
|
641
896
|
codex: codex --dangerously-bypass-approvals-and-sandbox
|
|
642
897
|
claude: claude --dangerously-skip-permissions
|
|
643
|
-
|
|
898
|
+
opencode: opencode
|
|
644
899
|
|
|
645
900
|
Examples:
|
|
646
901
|
${CLI_NAME} -- "fix the bug" # Use default backend
|
|
647
902
|
${CLI_NAME} --backend claude -- "fix the bug" # Use Claude CLI backend
|
|
648
|
-
${CLI_NAME} --backend
|
|
903
|
+
${CLI_NAME} --backend opencode -- "fix the bug" # Use OpenCode backend
|
|
649
904
|
${CLI_NAME} --backend codex --resume <id> # Resume Codex session
|
|
650
905
|
${CLI_NAME} --list-backends # Show configured backends
|
|
651
906
|
${CLI_NAME} --config-file ~/.conductor/config.yaml -- "fix the bug"
|
|
@@ -668,7 +923,22 @@ Environment:
|
|
|
668
923
|
})
|
|
669
924
|
.parse();
|
|
670
925
|
|
|
671
|
-
const backend = conductorArgs.backend
|
|
926
|
+
const backend = conductorArgs.backend
|
|
927
|
+
? normalizeRuntimeBackendName(conductorArgs.backend)
|
|
928
|
+
: supportedBackends[0];
|
|
929
|
+
const shouldRequireBackend =
|
|
930
|
+
!Boolean(conductorArgs.listBackends) &&
|
|
931
|
+
!listBackendsWithoutSeparator &&
|
|
932
|
+
!Boolean(conductorArgs.version) &&
|
|
933
|
+
!versionWithoutSeparator;
|
|
934
|
+
if (backend && !supportedBackends.includes(backend) && shouldRequireBackend) {
|
|
935
|
+
throw new Error(
|
|
936
|
+
`Unsupported backend "${backend}". Supported backends: ${supportedBackends.join(", ") || "none configured"}.`,
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
if (!backend && shouldRequireBackend) {
|
|
940
|
+
throw new Error("No supported backends configured. Add codex, claude, or opencode to allow_cli_list.");
|
|
941
|
+
}
|
|
672
942
|
|
|
673
943
|
const prompt = (backendArgs._ || []).map((part) => String(part)).join(" ").trim();
|
|
674
944
|
const initialImages = normalizeArray(backendArgs.image || backendArgs.i).map((img) => String(img));
|
|
@@ -806,6 +1076,7 @@ async function ensureTaskContext(conductor, opts) {
|
|
|
806
1076
|
taskId: opts.providedTaskId,
|
|
807
1077
|
appUrl: null,
|
|
808
1078
|
shouldProcessInitialPrompt: Boolean(opts.initialPrompt),
|
|
1079
|
+
initialPromptDelivery: opts.initialPrompt ? "synthetic" : "none",
|
|
809
1080
|
};
|
|
810
1081
|
}
|
|
811
1082
|
|
|
@@ -837,6 +1108,7 @@ async function ensureTaskContext(conductor, opts) {
|
|
|
837
1108
|
taskId: session.task_id,
|
|
838
1109
|
appUrl: session.app_url || null,
|
|
839
1110
|
shouldProcessInitialPrompt: Boolean(opts.initialPrompt),
|
|
1111
|
+
initialPromptDelivery: opts.initialPrompt ? "queued" : "none",
|
|
840
1112
|
};
|
|
841
1113
|
}
|
|
842
1114
|
|
|
@@ -1016,6 +1288,7 @@ export class BridgeRunner {
|
|
|
1016
1288
|
taskId,
|
|
1017
1289
|
pollIntervalMs,
|
|
1018
1290
|
initialPrompt,
|
|
1291
|
+
initialPromptDelivery,
|
|
1019
1292
|
includeInitialImages,
|
|
1020
1293
|
cliArgs,
|
|
1021
1294
|
backendName,
|
|
@@ -1027,6 +1300,10 @@ export class BridgeRunner {
|
|
|
1027
1300
|
this.taskId = taskId;
|
|
1028
1301
|
this.pollIntervalMs = pollIntervalMs;
|
|
1029
1302
|
this.initialPrompt = initialPrompt;
|
|
1303
|
+
this.initialPromptDelivery =
|
|
1304
|
+
typeof initialPromptDelivery === "string" && initialPromptDelivery.trim()
|
|
1305
|
+
? initialPromptDelivery.trim()
|
|
1306
|
+
: "none";
|
|
1030
1307
|
this.includeInitialImages = includeInitialImages;
|
|
1031
1308
|
this.cliArgs = cliArgs;
|
|
1032
1309
|
this.backendName = backendName || "codex";
|
|
@@ -1042,6 +1319,10 @@ export class BridgeRunner {
|
|
|
1042
1319
|
this.runningTurn = false;
|
|
1043
1320
|
this.processedMessageIds = new Set();
|
|
1044
1321
|
this.inFlightMessageIds = new Set();
|
|
1322
|
+
this.pendingInitialPrompt =
|
|
1323
|
+
this.initialPromptDelivery === "queued" && typeof initialPrompt === "string" && initialPrompt.trim()
|
|
1324
|
+
? initialPrompt.trim()
|
|
1325
|
+
: "";
|
|
1045
1326
|
this.sessionStreamReplyCounts = new Map();
|
|
1046
1327
|
this.lastRuntimeStatusSignature = null;
|
|
1047
1328
|
this.lastRuntimeStatusPayload = null;
|
|
@@ -1230,8 +1511,8 @@ export class BridgeRunner {
|
|
|
1230
1511
|
return;
|
|
1231
1512
|
}
|
|
1232
1513
|
|
|
1233
|
-
if (this.initialPrompt) {
|
|
1234
|
-
this.copilotLog("processing initial prompt");
|
|
1514
|
+
if (this.initialPrompt && this.initialPromptDelivery === "synthetic") {
|
|
1515
|
+
this.copilotLog("processing initial prompt via synthetic attach flow");
|
|
1235
1516
|
await this.handleSyntheticMessage(this.initialPrompt, {
|
|
1236
1517
|
includeImages: this.includeInitialImages,
|
|
1237
1518
|
});
|
|
@@ -1239,6 +1520,7 @@ export class BridgeRunner {
|
|
|
1239
1520
|
if (this.stopped) {
|
|
1240
1521
|
return;
|
|
1241
1522
|
}
|
|
1523
|
+
|
|
1242
1524
|
while (!this.stopped) {
|
|
1243
1525
|
if (this.needsReconnectRecovery && !this.runningTurn) {
|
|
1244
1526
|
await this.recoverAfterReconnect();
|
|
@@ -1783,6 +2065,11 @@ export class BridgeRunner {
|
|
|
1783
2065
|
if (replyTo) {
|
|
1784
2066
|
this.inFlightMessageIds.add(replyTo);
|
|
1785
2067
|
}
|
|
2068
|
+
const isQueuedInitialPromptMessage =
|
|
2069
|
+
Boolean(this.pendingInitialPrompt) &&
|
|
2070
|
+
String(message.role || "").toLowerCase() === "user" &&
|
|
2071
|
+
content === this.pendingInitialPrompt;
|
|
2072
|
+
const useInitialImages = isQueuedInitialPromptMessage && this.includeInitialImages;
|
|
1786
2073
|
if (
|
|
1787
2074
|
this.useSessionFileReplyStream &&
|
|
1788
2075
|
typeof this.backendSession?.setSessionReplyTarget === "function"
|
|
@@ -1830,6 +2117,7 @@ export class BridgeRunner {
|
|
|
1830
2117
|
}
|
|
1831
2118
|
|
|
1832
2119
|
const result = await this.backendSession.runTurn(content, {
|
|
2120
|
+
useInitialImages,
|
|
1833
2121
|
onProgress: (payload) => {
|
|
1834
2122
|
void this.reportRuntimeStatus(payload, replyTo);
|
|
1835
2123
|
},
|
|
@@ -1873,6 +2161,9 @@ export class BridgeRunner {
|
|
|
1873
2161
|
if (replyTo) {
|
|
1874
2162
|
this.processedMessageIds.add(replyTo);
|
|
1875
2163
|
}
|
|
2164
|
+
if (isQueuedInitialPromptMessage) {
|
|
2165
|
+
this.pendingInitialPrompt = "";
|
|
2166
|
+
}
|
|
1876
2167
|
this.resetErrorLoop();
|
|
1877
2168
|
if (this.useSessionFileReplyStream) {
|
|
1878
2169
|
this.copilotLog(`session_file turn settled replyTo=${replyTo || "latest"}`);
|
package/bin/conductor.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* update - Update the CLI to the latest version
|
|
11
11
|
* diagnose - Diagnose a task in production/backend
|
|
12
12
|
* send-file - Upload a local file into a task session
|
|
13
|
+
* channel - Connect user-owned chat channel providers
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
import { fileURLToPath } from "node:url";
|
|
@@ -46,7 +47,7 @@ if (argv[0] === "--version" || argv[0] === "-v") {
|
|
|
46
47
|
const subcommand = argv[0];
|
|
47
48
|
|
|
48
49
|
// Valid subcommands
|
|
49
|
-
const validSubcommands = ["fire", "daemon", "config", "update", "diagnose", "send-file"];
|
|
50
|
+
const validSubcommands = ["fire", "daemon", "config", "update", "diagnose", "send-file", "channel"];
|
|
50
51
|
|
|
51
52
|
if (!validSubcommands.includes(subcommand)) {
|
|
52
53
|
console.error(`Error: Unknown subcommand '${subcommand}'`);
|
|
@@ -90,6 +91,7 @@ Subcommands:
|
|
|
90
91
|
update Update the CLI to the latest version
|
|
91
92
|
diagnose Diagnose a task and print likely root cause
|
|
92
93
|
send-file Upload a local file into a task session
|
|
94
|
+
channel Connect user-owned chat channel providers
|
|
93
95
|
|
|
94
96
|
Options:
|
|
95
97
|
-h, --help Show this help message
|
|
@@ -101,6 +103,7 @@ Examples:
|
|
|
101
103
|
conductor daemon --config-file ~/.conductor/config.yaml
|
|
102
104
|
conductor diagnose <task-id>
|
|
103
105
|
conductor send-file ./screenshot.png
|
|
106
|
+
conductor channel connect feishu
|
|
104
107
|
conductor config
|
|
105
108
|
conductor update
|
|
106
109
|
|
|
@@ -111,6 +114,7 @@ For subcommand-specific help:
|
|
|
111
114
|
conductor update --help
|
|
112
115
|
conductor diagnose --help
|
|
113
116
|
conductor send-file --help
|
|
117
|
+
conductor channel --help
|
|
114
118
|
|
|
115
119
|
Version: ${pkgJson.version}
|
|
116
120
|
`);
|
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.20",
|
|
4
|
+
"gitCommitId": "d622756",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"conductor": "bin/conductor.js"
|
|
@@ -17,17 +17,25 @@
|
|
|
17
17
|
"test": "node --test test/*.test.js"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@love-moon/ai-sdk": "0.2.
|
|
21
|
-
"@love-moon/conductor-sdk": "0.2.
|
|
20
|
+
"@love-moon/ai-sdk": "0.2.20",
|
|
21
|
+
"@love-moon/conductor-sdk": "0.2.20",
|
|
22
22
|
"dotenv": "^16.4.5",
|
|
23
23
|
"enquirer": "^2.4.1",
|
|
24
24
|
"js-yaml": "^4.1.1",
|
|
25
|
+
"node-pty": "^1.0.0",
|
|
25
26
|
"ws": "^8.18.0",
|
|
26
27
|
"yargs": "^17.7.2",
|
|
27
28
|
"chrome-launcher": "^1.2.1",
|
|
28
29
|
"chrome-remote-interface": "^0.33.0"
|
|
29
30
|
},
|
|
31
|
+
"optionalDependencies": {
|
|
32
|
+
"@roamhq/wrtc": "^0.10.0"
|
|
33
|
+
},
|
|
30
34
|
"pnpm": {
|
|
35
|
+
"onlyBuiltDependencies": [
|
|
36
|
+
"node-pty",
|
|
37
|
+
"@roamhq/wrtc"
|
|
38
|
+
],
|
|
31
39
|
"overrides": {
|
|
32
40
|
"@love-moon/ai-sdk": "file:../modules/ai-sdk",
|
|
33
41
|
"@love-moon/conductor-sdk": "file:../modules/conductor-sdk"
|