@love-moon/conductor-cli 0.2.38 → 0.2.39
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 +280 -8
- package/package.json +5 -5
- package/src/daemon.js +27 -6
- package/src/runtime-backends.js +44 -1
package/bin/conductor-fire.js
CHANGED
|
@@ -89,6 +89,13 @@ export function shouldRunReconnectRecovery({
|
|
|
89
89
|
return !runner.shouldSuppressReconnectRecovery();
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
export function shouldFireReportTaskStatus({ launchedByDaemon = false, phase } = {}) {
|
|
93
|
+
if (phase === "final") {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
return !launchedByDaemon;
|
|
97
|
+
}
|
|
98
|
+
|
|
92
99
|
// Load allow_cli_list from config file (no defaults - must be configured)
|
|
93
100
|
function loadFireConfigYaml(configFilePath) {
|
|
94
101
|
const home = os.homedir();
|
|
@@ -577,6 +584,39 @@ export class FireWatchdog {
|
|
|
577
584
|
}
|
|
578
585
|
}
|
|
579
586
|
|
|
587
|
+
export function createPendingRemoteInterruptQueue() {
|
|
588
|
+
const pending = [];
|
|
589
|
+
|
|
590
|
+
return {
|
|
591
|
+
enqueue(event) {
|
|
592
|
+
return new Promise((resolve) => {
|
|
593
|
+
pending.push({ event, resolve });
|
|
594
|
+
});
|
|
595
|
+
},
|
|
596
|
+
|
|
597
|
+
async flushWith(dispatch) {
|
|
598
|
+
while (pending.length > 0) {
|
|
599
|
+
const next = pending.shift();
|
|
600
|
+
if (!next) {
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
try {
|
|
604
|
+
next.resolve(await dispatch(next.event));
|
|
605
|
+
} catch {
|
|
606
|
+
next.resolve(false);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
|
|
611
|
+
rejectAll() {
|
|
612
|
+
while (pending.length > 0) {
|
|
613
|
+
const next = pending.shift();
|
|
614
|
+
next?.resolve(false);
|
|
615
|
+
}
|
|
616
|
+
},
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
580
620
|
async function main() {
|
|
581
621
|
syncPwdEnvWithProcessCwdForDaemonLaunch();
|
|
582
622
|
const cliArgs = await parseCliArgs();
|
|
@@ -637,6 +677,7 @@ async function main() {
|
|
|
637
677
|
let reconnectRunner = null;
|
|
638
678
|
let reconnectTaskId = null;
|
|
639
679
|
let pendingRemoteStopEvent = null;
|
|
680
|
+
const pendingRemoteInterruptQueue = createPendingRemoteInterruptQueue();
|
|
640
681
|
let conductor = null;
|
|
641
682
|
let reconnectResumeInFlight = false;
|
|
642
683
|
let fireShuttingDown = false;
|
|
@@ -676,7 +717,7 @@ async function main() {
|
|
|
676
717
|
source: "conductor-fire",
|
|
677
718
|
metadata: { reconnect: true },
|
|
678
719
|
});
|
|
679
|
-
if (
|
|
720
|
+
if (shouldFireReportTaskStatus({ launchedByDaemon, phase: "reconnect_running" })) {
|
|
680
721
|
await conductor.sendTaskStatus(reconnectTaskId, {
|
|
681
722
|
status: "RUNNING",
|
|
682
723
|
summary: "conductor fire reconnected",
|
|
@@ -706,6 +747,21 @@ async function main() {
|
|
|
706
747
|
pendingRemoteStopEvent = event;
|
|
707
748
|
};
|
|
708
749
|
|
|
750
|
+
const handleInterruptTurnCommand = async (event) => {
|
|
751
|
+
fireWatchdog.onInbound();
|
|
752
|
+
if (!event || typeof event !== "object") {
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
const taskId = typeof event.taskId === "string" ? event.taskId : "";
|
|
756
|
+
if (reconnectTaskId && taskId && taskId !== reconnectTaskId) {
|
|
757
|
+
return false;
|
|
758
|
+
}
|
|
759
|
+
if (reconnectRunner && typeof reconnectRunner.requestInterruptFromRemote === "function") {
|
|
760
|
+
return await reconnectRunner.requestInterruptFromRemote(event);
|
|
761
|
+
}
|
|
762
|
+
return await pendingRemoteInterruptQueue.enqueue(event);
|
|
763
|
+
};
|
|
764
|
+
|
|
709
765
|
if (cliArgs.configFile) {
|
|
710
766
|
env.CONDUCTOR_CONFIG = cliArgs.configFile;
|
|
711
767
|
}
|
|
@@ -756,6 +812,7 @@ async function main() {
|
|
|
756
812
|
fireWatchdog.onPong(event);
|
|
757
813
|
},
|
|
758
814
|
onStopTask: handleStopTaskCommand,
|
|
815
|
+
onInterruptTurn: handleInterruptTurnCommand,
|
|
759
816
|
});
|
|
760
817
|
|
|
761
818
|
const taskContext = await ensureTaskContext(conductor, {
|
|
@@ -853,6 +910,7 @@ async function main() {
|
|
|
853
910
|
await runner.requestStopFromRemote(pendingRemoteStopEvent);
|
|
854
911
|
pendingRemoteStopEvent = null;
|
|
855
912
|
}
|
|
913
|
+
await pendingRemoteInterruptQueue.flushWith((event) => runner.requestInterruptFromRemote(event));
|
|
856
914
|
|
|
857
915
|
const signals = new AbortController();
|
|
858
916
|
let shutdownSignal = null;
|
|
@@ -887,7 +945,7 @@ async function main() {
|
|
|
887
945
|
process.on("SIGINT", onSigint);
|
|
888
946
|
process.on("SIGTERM", onSigterm);
|
|
889
947
|
|
|
890
|
-
if (
|
|
948
|
+
if (shouldFireReportTaskStatus({ launchedByDaemon, phase: "running" })) {
|
|
891
949
|
try {
|
|
892
950
|
await conductor.sendTaskStatus(taskContext.taskId, {
|
|
893
951
|
status: "RUNNING",
|
|
@@ -911,7 +969,7 @@ async function main() {
|
|
|
911
969
|
} finally {
|
|
912
970
|
process.off("SIGINT", onSigint);
|
|
913
971
|
process.off("SIGTERM", onSigterm);
|
|
914
|
-
if (
|
|
972
|
+
if (shouldFireReportTaskStatus({ launchedByDaemon, phase: "final" })) {
|
|
915
973
|
const remoteStopReason = typeof runner.getRemoteStopReason === "function" ? runner.getRemoteStopReason() : null;
|
|
916
974
|
const remoteStopSummary = typeof runner.getRemoteStopSummary === "function" ? runner.getRemoteStopSummary() : null;
|
|
917
975
|
// When the task was deleted by the user, the DB record is already gone —
|
|
@@ -934,10 +992,10 @@ async function main() {
|
|
|
934
992
|
status: "KILLED",
|
|
935
993
|
summary: remoteStopSummary,
|
|
936
994
|
}
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
995
|
+
: {
|
|
996
|
+
status: "COMPLETED",
|
|
997
|
+
summary: "conductor fire exited",
|
|
998
|
+
};
|
|
941
999
|
if (!taskDeletedByUser) {
|
|
942
1000
|
try {
|
|
943
1001
|
const statusResult = await conductor.sendTaskStatus(taskContext.taskId, finalStatus);
|
|
@@ -966,6 +1024,7 @@ async function main() {
|
|
|
966
1024
|
}
|
|
967
1025
|
}
|
|
968
1026
|
} finally {
|
|
1027
|
+
pendingRemoteInterruptQueue.rejectAll();
|
|
969
1028
|
fireShuttingDown = true;
|
|
970
1029
|
fireWatchdog.stop();
|
|
971
1030
|
if (backendSession && typeof backendSession.close === "function") {
|
|
@@ -1731,6 +1790,9 @@ export class BridgeRunner {
|
|
|
1731
1790
|
os.hostname();
|
|
1732
1791
|
this.needsReconnectRecovery = false;
|
|
1733
1792
|
this.remoteStopInfo = null;
|
|
1793
|
+
this.remoteInterruptsByReplyTo = new Map();
|
|
1794
|
+
this.pendingInterruptRetryTimers = new Map();
|
|
1795
|
+
this.activeTurnReplyTo = "";
|
|
1734
1796
|
this.sessionAnnouncementSent = false;
|
|
1735
1797
|
this.boundSessionId = "";
|
|
1736
1798
|
this.errorLoop = null;
|
|
@@ -1990,6 +2052,200 @@ export class BridgeRunner {
|
|
|
1990
2052
|
}
|
|
1991
2053
|
}
|
|
1992
2054
|
|
|
2055
|
+
normalizeReplyTarget(replyTo) {
|
|
2056
|
+
return typeof replyTo === "string" ? replyTo.trim() : "";
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
isTurnInterruptedError(error) {
|
|
2060
|
+
const reason = typeof error?.reason === "string" ? error.reason.trim().toLowerCase() : "";
|
|
2061
|
+
if (reason === "turn_interrupted" || reason === "turn_cancelled") {
|
|
2062
|
+
return true;
|
|
2063
|
+
}
|
|
2064
|
+
const turnStatus = typeof error?.turnStatus === "string" ? error.turnStatus.trim().toLowerCase() : "";
|
|
2065
|
+
if (
|
|
2066
|
+
turnStatus === "interrupted" ||
|
|
2067
|
+
turnStatus === "cancelled" ||
|
|
2068
|
+
turnStatus === "canceled" ||
|
|
2069
|
+
turnStatus === "aborted"
|
|
2070
|
+
) {
|
|
2071
|
+
return true;
|
|
2072
|
+
}
|
|
2073
|
+
const name = typeof error?.name === "string" ? error.name.trim().toLowerCase() : "";
|
|
2074
|
+
if (name === "aborterror") {
|
|
2075
|
+
return true;
|
|
2076
|
+
}
|
|
2077
|
+
const message = String(error?.message || error || "").toLowerCase();
|
|
2078
|
+
return (
|
|
2079
|
+
message.includes(" interrupted") ||
|
|
2080
|
+
message.includes("interrupt ") ||
|
|
2081
|
+
message.includes("turn interrupted") ||
|
|
2082
|
+
message.includes("cancelled") ||
|
|
2083
|
+
message.includes("canceled") ||
|
|
2084
|
+
message.includes("aborted")
|
|
2085
|
+
);
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
async requestInterruptFromRemote(event = {}) {
|
|
2089
|
+
const taskId = typeof event.taskId === "string" ? event.taskId.trim() : "";
|
|
2090
|
+
if (taskId && taskId !== this.taskId) {
|
|
2091
|
+
return false;
|
|
2092
|
+
}
|
|
2093
|
+
const requestId = typeof event.requestId === "string" ? event.requestId.trim() : "";
|
|
2094
|
+
const reason = typeof event.reason === "string" ? event.reason.trim() : "";
|
|
2095
|
+
const targetReplyTo = this.normalizeReplyTarget(event.targetReplyTo);
|
|
2096
|
+
if (!targetReplyTo) {
|
|
2097
|
+
return false;
|
|
2098
|
+
}
|
|
2099
|
+
if (this.processedMessageIds.has(targetReplyTo)) {
|
|
2100
|
+
this.copilotLog(`ignore late interrupt_turn for processed replyTo=${targetReplyTo}`);
|
|
2101
|
+
return false;
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
const existing = this.remoteInterruptsByReplyTo.get(targetReplyTo) || {};
|
|
2105
|
+
const interruptInfo = {
|
|
2106
|
+
requestId: requestId || existing.requestId || null,
|
|
2107
|
+
reason: reason || existing.reason || "user_interrupt",
|
|
2108
|
+
issued: Boolean(existing.issued),
|
|
2109
|
+
};
|
|
2110
|
+
this.remoteInterruptsByReplyTo.set(targetReplyTo, interruptInfo);
|
|
2111
|
+
log(
|
|
2112
|
+
`Received interrupt_turn for ${this.taskId} replyTo=${targetReplyTo}${
|
|
2113
|
+
interruptInfo.reason ? ` (${interruptInfo.reason})` : ""
|
|
2114
|
+
}`,
|
|
2115
|
+
);
|
|
2116
|
+
return await this.issueInterruptForReplyTarget(targetReplyTo);
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
async issueInterruptForReplyTarget(replyTo) {
|
|
2120
|
+
const normalizedReplyTo = this.normalizeReplyTarget(replyTo);
|
|
2121
|
+
if (!normalizedReplyTo) {
|
|
2122
|
+
return false;
|
|
2123
|
+
}
|
|
2124
|
+
const interruptInfo = this.remoteInterruptsByReplyTo.get(normalizedReplyTo);
|
|
2125
|
+
if (!interruptInfo) {
|
|
2126
|
+
return false;
|
|
2127
|
+
}
|
|
2128
|
+
if (interruptInfo.issued) {
|
|
2129
|
+
return true;
|
|
2130
|
+
}
|
|
2131
|
+
const supportsTurnInterrupt = typeof this.backendSession?.interruptCurrentTurn === "function";
|
|
2132
|
+
const isActiveTarget = this.runningTurn && normalizedReplyTo === this.activeTurnReplyTo;
|
|
2133
|
+
const isInFlightTarget = this.inFlightMessageIds.has(normalizedReplyTo);
|
|
2134
|
+
|
|
2135
|
+
if (!isActiveTarget && isInFlightTarget) {
|
|
2136
|
+
this.copilotLog(`interrupt arrived after replyTo=${normalizedReplyTo} stopped being interruptible`);
|
|
2137
|
+
return false;
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
if (!isActiveTarget) {
|
|
2141
|
+
if (!supportsTurnInterrupt) {
|
|
2142
|
+
log(`Backend session for ${this.taskId} does not support turn interruption`);
|
|
2143
|
+
return false;
|
|
2144
|
+
}
|
|
2145
|
+
this.copilotLog(`queued interrupt request for future replyTo=${normalizedReplyTo}`);
|
|
2146
|
+
return true;
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
if (!supportsTurnInterrupt) {
|
|
2150
|
+
log(`Backend session for ${this.taskId} does not support turn interruption`);
|
|
2151
|
+
return false;
|
|
2152
|
+
}
|
|
2153
|
+
try {
|
|
2154
|
+
const interrupted = await this.backendSession.interruptCurrentTurn();
|
|
2155
|
+
if (interrupted === false) {
|
|
2156
|
+
interruptInfo.issued = false;
|
|
2157
|
+
this.remoteInterruptsByReplyTo.set(normalizedReplyTo, interruptInfo);
|
|
2158
|
+
if (
|
|
2159
|
+
this.runningTurn &&
|
|
2160
|
+
this.activeTurnReplyTo === normalizedReplyTo &&
|
|
2161
|
+
this.inFlightMessageIds.has(normalizedReplyTo)
|
|
2162
|
+
) {
|
|
2163
|
+
this.copilotLog(`backend interrupt not ready replyTo=${normalizedReplyTo}; retrying`);
|
|
2164
|
+
this.scheduleInterruptRetryForReplyTarget(normalizedReplyTo);
|
|
2165
|
+
return true;
|
|
2166
|
+
}
|
|
2167
|
+
return false;
|
|
2168
|
+
}
|
|
2169
|
+
interruptInfo.issued = true;
|
|
2170
|
+
this.remoteInterruptsByReplyTo.set(normalizedReplyTo, interruptInfo);
|
|
2171
|
+
this.copilotLog(`requested backend interrupt replyTo=${normalizedReplyTo}`);
|
|
2172
|
+
return true;
|
|
2173
|
+
} catch (error) {
|
|
2174
|
+
interruptInfo.issued = false;
|
|
2175
|
+
this.remoteInterruptsByReplyTo.set(normalizedReplyTo, interruptInfo);
|
|
2176
|
+
log(`Failed to interrupt replyTo=${normalizedReplyTo} for ${this.taskId}: ${error?.message || error}`);
|
|
2177
|
+
return false;
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
scheduleInterruptRetryForReplyTarget(replyTo) {
|
|
2182
|
+
const normalizedReplyTo = this.normalizeReplyTarget(replyTo);
|
|
2183
|
+
if (!normalizedReplyTo || this.pendingInterruptRetryTimers.has(normalizedReplyTo)) {
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
const timer = setTimeout(() => {
|
|
2188
|
+
this.pendingInterruptRetryTimers.delete(normalizedReplyTo);
|
|
2189
|
+
const interruptInfo = this.remoteInterruptsByReplyTo.get(normalizedReplyTo);
|
|
2190
|
+
if (
|
|
2191
|
+
!interruptInfo ||
|
|
2192
|
+
interruptInfo.issued ||
|
|
2193
|
+
this.processedMessageIds.has(normalizedReplyTo) ||
|
|
2194
|
+
!this.runningTurn ||
|
|
2195
|
+
this.activeTurnReplyTo !== normalizedReplyTo ||
|
|
2196
|
+
!this.inFlightMessageIds.has(normalizedReplyTo)
|
|
2197
|
+
) {
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
void this.issueInterruptForReplyTarget(normalizedReplyTo);
|
|
2201
|
+
}, 50);
|
|
2202
|
+
if (typeof timer.unref === "function") {
|
|
2203
|
+
timer.unref();
|
|
2204
|
+
}
|
|
2205
|
+
this.pendingInterruptRetryTimers.set(normalizedReplyTo, timer);
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
clearInterruptRetryForReplyTarget(replyTo) {
|
|
2209
|
+
const normalizedReplyTo = this.normalizeReplyTarget(replyTo);
|
|
2210
|
+
const timer = this.pendingInterruptRetryTimers.get(normalizedReplyTo);
|
|
2211
|
+
if (!timer) {
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
2214
|
+
clearTimeout(timer);
|
|
2215
|
+
this.pendingInterruptRetryTimers.delete(normalizedReplyTo);
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
async handleInterruptedTurn(replyTo, interruptInfo) {
|
|
2219
|
+
const normalizedReplyTo = this.normalizeReplyTarget(replyTo);
|
|
2220
|
+
this.clearInterruptRetryForReplyTarget(normalizedReplyTo);
|
|
2221
|
+
this.copilotLog(`turn interrupted replyTo=${normalizedReplyTo || "latest"}`);
|
|
2222
|
+
await this.reportRuntimeStatus(
|
|
2223
|
+
{
|
|
2224
|
+
phase: "interrupted",
|
|
2225
|
+
reply_in_progress: false,
|
|
2226
|
+
status_done_line: "Conversation interrupted",
|
|
2227
|
+
},
|
|
2228
|
+
normalizedReplyTo,
|
|
2229
|
+
);
|
|
2230
|
+
try {
|
|
2231
|
+
await this.conductor.sendMessage(this.taskId, "Conversation interrupted", {
|
|
2232
|
+
backend: this.backendName,
|
|
2233
|
+
reply_to: normalizedReplyTo || undefined,
|
|
2234
|
+
interrupted: true,
|
|
2235
|
+
interruption_request_id: interruptInfo?.requestId || undefined,
|
|
2236
|
+
reason: interruptInfo?.reason || undefined,
|
|
2237
|
+
cli_args: this.cliArgs,
|
|
2238
|
+
});
|
|
2239
|
+
} catch (error) {
|
|
2240
|
+
log(`Failed to send interrupt confirmation for ${this.taskId}: ${error?.message || error}`);
|
|
2241
|
+
}
|
|
2242
|
+
if (normalizedReplyTo) {
|
|
2243
|
+
this.processedMessageIds.add(normalizedReplyTo);
|
|
2244
|
+
this.remoteInterruptsByReplyTo.delete(normalizedReplyTo);
|
|
2245
|
+
}
|
|
2246
|
+
this.resetErrorLoop();
|
|
2247
|
+
}
|
|
2248
|
+
|
|
1993
2249
|
async recoverAfterReconnect() {
|
|
1994
2250
|
if (!this.needsReconnectRecovery) {
|
|
1995
2251
|
return;
|
|
@@ -2502,6 +2758,7 @@ export class BridgeRunner {
|
|
|
2502
2758
|
}
|
|
2503
2759
|
this.lastRuntimeStatusSignature = null;
|
|
2504
2760
|
this.runningTurn = true;
|
|
2761
|
+
this.activeTurnReplyTo = this.normalizeReplyTarget(replyTo);
|
|
2505
2762
|
const turnStartedAt = Date.now();
|
|
2506
2763
|
let turnWatchdog = null;
|
|
2507
2764
|
if (this.isCopilotBackend) {
|
|
@@ -2540,12 +2797,15 @@ export class BridgeRunner {
|
|
|
2540
2797
|
);
|
|
2541
2798
|
}
|
|
2542
2799
|
|
|
2543
|
-
const
|
|
2800
|
+
const turnPromise = this.backendSession.runTurn(content, {
|
|
2544
2801
|
useInitialImages,
|
|
2545
2802
|
onProgress: (payload) => {
|
|
2546
2803
|
void this.reportRuntimeStatus(payload, replyTo);
|
|
2547
2804
|
},
|
|
2548
2805
|
});
|
|
2806
|
+
await this.issueInterruptForReplyTarget(replyTo);
|
|
2807
|
+
const result = await turnPromise;
|
|
2808
|
+
this.activeTurnReplyTo = "";
|
|
2549
2809
|
this.copilotLog(
|
|
2550
2810
|
`runTurn completed replyTo=${replyTo || "latest"} elapsedMs=${Date.now() - turnStartedAt} answerLen=${String(
|
|
2551
2811
|
result.text || "",
|
|
@@ -2582,6 +2842,10 @@ export class BridgeRunner {
|
|
|
2582
2842
|
});
|
|
2583
2843
|
}
|
|
2584
2844
|
await this.syncBackendSessionBinding();
|
|
2845
|
+
if (replyTo) {
|
|
2846
|
+
this.clearInterruptRetryForReplyTarget(replyTo);
|
|
2847
|
+
this.remoteInterruptsByReplyTo.delete(replyTo);
|
|
2848
|
+
}
|
|
2585
2849
|
if (replyTo) {
|
|
2586
2850
|
this.processedMessageIds.add(replyTo);
|
|
2587
2851
|
}
|
|
@@ -2600,6 +2864,7 @@ export class BridgeRunner {
|
|
|
2600
2864
|
this.copilotLog(`sdk_message sent replyTo=${replyTo || "latest"} responseLen=${responseText.length}`);
|
|
2601
2865
|
}
|
|
2602
2866
|
} catch (error) {
|
|
2867
|
+
this.activeTurnReplyTo = "";
|
|
2603
2868
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2604
2869
|
if (this.stopped && (this.remoteStopInfo || isSessionClosedError(error))) {
|
|
2605
2870
|
this.copilotLog(
|
|
@@ -2607,6 +2872,11 @@ export class BridgeRunner {
|
|
|
2607
2872
|
);
|
|
2608
2873
|
return;
|
|
2609
2874
|
}
|
|
2875
|
+
const interruptInfo = replyTo ? this.remoteInterruptsByReplyTo.get(replyTo) : null;
|
|
2876
|
+
if (interruptInfo && this.isTurnInterruptedError(error)) {
|
|
2877
|
+
await this.handleInterruptedTurn(replyTo, interruptInfo);
|
|
2878
|
+
return;
|
|
2879
|
+
}
|
|
2610
2880
|
if (await this.settleCodexCheckpointUnavailableAfterStream(replyTo, errorMessage)) {
|
|
2611
2881
|
return;
|
|
2612
2882
|
}
|
|
@@ -2663,7 +2933,9 @@ export class BridgeRunner {
|
|
|
2663
2933
|
}
|
|
2664
2934
|
if (replyTo) {
|
|
2665
2935
|
this.inFlightMessageIds.delete(replyTo);
|
|
2936
|
+
this.clearInterruptRetryForReplyTarget(replyTo);
|
|
2666
2937
|
}
|
|
2938
|
+
this.activeTurnReplyTo = "";
|
|
2667
2939
|
this.copilotLog(
|
|
2668
2940
|
`turn end replyTo=${replyTo || "latest"} elapsedMs=${Date.now() - turnStartedAt} processedIds=${this.processedMessageIds.size}`,
|
|
2669
2941
|
);
|
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.39",
|
|
4
|
+
"gitCommitId": "30204c8",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"conductor": "bin/conductor.js"
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@love-moon/ai-bridge": "0.1.4",
|
|
21
|
-
"@love-moon/ai-manager": "0.2.
|
|
22
|
-
"@love-moon/ai-sdk": "0.2.
|
|
23
|
-
"@love-moon/conductor-sdk": "0.2.
|
|
21
|
+
"@love-moon/ai-manager": "0.2.39",
|
|
22
|
+
"@love-moon/ai-sdk": "0.2.39",
|
|
23
|
+
"@love-moon/conductor-sdk": "0.2.39",
|
|
24
24
|
"chrome-launcher": "^1.2.1",
|
|
25
25
|
"chrome-remote-interface": "^0.33.0",
|
|
26
26
|
"dotenv": "^16.4.5",
|
package/src/daemon.js
CHANGED
|
@@ -1589,15 +1589,27 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
1589
1589
|
}
|
|
1590
1590
|
}
|
|
1591
1591
|
|
|
1592
|
-
|
|
1592
|
+
const runMaintenanceTick = async () => {
|
|
1593
1593
|
void runDaemonWatchdog();
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1594
|
+
try {
|
|
1595
|
+
await checkForUpdate();
|
|
1596
|
+
} catch {
|
|
1597
|
+
// ignore non-critical version check failures
|
|
1598
|
+
}
|
|
1599
|
+
try {
|
|
1600
|
+
await tryAutoUpdate();
|
|
1601
|
+
} catch {
|
|
1602
|
+
// ignore non-critical auto-update failures
|
|
1603
|
+
}
|
|
1604
|
+
};
|
|
1605
|
+
|
|
1606
|
+
watchdogTimer = setInterval(() => {
|
|
1607
|
+
void runMaintenanceTick();
|
|
1597
1608
|
}, DAEMON_WATCHDOG_INTERVAL_MS);
|
|
1598
1609
|
if (typeof watchdogTimer?.unref === "function") {
|
|
1599
1610
|
watchdogTimer.unref();
|
|
1600
1611
|
}
|
|
1612
|
+
void runMaintenanceTick();
|
|
1601
1613
|
})();
|
|
1602
1614
|
|
|
1603
1615
|
function markBackendHttpSuccess(at = Date.now()) {
|
|
@@ -3824,6 +3836,10 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
3824
3836
|
return true;
|
|
3825
3837
|
}
|
|
3826
3838
|
|
|
3839
|
+
function shouldDaemonReportFireChildTerminalStatus(record) {
|
|
3840
|
+
return !Boolean(record?.managedByFireBridge);
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3827
3843
|
function handleStopTask(payload) {
|
|
3828
3844
|
const taskId = payload?.task_id;
|
|
3829
3845
|
if (!taskId) return;
|
|
@@ -4326,6 +4342,7 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
4326
4342
|
projectId,
|
|
4327
4343
|
logPath,
|
|
4328
4344
|
stopForceKillTimer: null,
|
|
4345
|
+
managedByFireBridge: true,
|
|
4329
4346
|
});
|
|
4330
4347
|
|
|
4331
4348
|
client
|
|
@@ -4395,7 +4412,7 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
4395
4412
|
? "completed"
|
|
4396
4413
|
: `exited with code ${code}`;
|
|
4397
4414
|
|
|
4398
|
-
if (!suppressExitStatusReport) {
|
|
4415
|
+
if (!suppressExitStatusReport && shouldDaemonReportFireChildTerminalStatus(active)) {
|
|
4399
4416
|
client
|
|
4400
4417
|
.sendJson({
|
|
4401
4418
|
type: "task_status_update",
|
|
@@ -4750,6 +4767,7 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
4750
4767
|
projectId: normalizedProjectId,
|
|
4751
4768
|
logPath,
|
|
4752
4769
|
stopForceKillTimer: null,
|
|
4770
|
+
managedByFireBridge: true,
|
|
4753
4771
|
});
|
|
4754
4772
|
|
|
4755
4773
|
client
|
|
@@ -4812,7 +4830,7 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
4812
4830
|
? "completed"
|
|
4813
4831
|
: `exited with code ${code}`;
|
|
4814
4832
|
|
|
4815
|
-
if (!suppressExitStatusReport) {
|
|
4833
|
+
if (!suppressExitStatusReport && shouldDaemonReportFireChildTerminalStatus(active)) {
|
|
4816
4834
|
client
|
|
4817
4835
|
.sendJson({
|
|
4818
4836
|
type: "task_status_update",
|
|
@@ -4852,6 +4870,9 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
4852
4870
|
await Promise.allSettled(
|
|
4853
4871
|
activeEntries.map(async ([taskId, record]) => {
|
|
4854
4872
|
suppressedExitStatusReports.add(taskId);
|
|
4873
|
+
if (!shouldDaemonReportFireChildTerminalStatus(record)) {
|
|
4874
|
+
return;
|
|
4875
|
+
}
|
|
4855
4876
|
try {
|
|
4856
4877
|
await withTimeout(
|
|
4857
4878
|
client.sendJson({
|
package/src/runtime-backends.js
CHANGED
|
@@ -28,7 +28,7 @@ function appendProviderModulePaths(parts, value) {
|
|
|
28
28
|
if (!raw) {
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
|
-
for (const item of raw
|
|
31
|
+
for (const item of splitProviderModulePathString(raw)) {
|
|
32
32
|
const normalized = item.trim();
|
|
33
33
|
if (normalized) {
|
|
34
34
|
parts.push(normalized);
|
|
@@ -36,6 +36,49 @@ function appendProviderModulePaths(parts, value) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
function looksLikeProviderModulePath(value) {
|
|
40
|
+
const normalized = String(value || "").trim();
|
|
41
|
+
if (!normalized) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return (
|
|
45
|
+
normalized.startsWith("/") ||
|
|
46
|
+
normalized.startsWith("./") ||
|
|
47
|
+
normalized.startsWith("../") ||
|
|
48
|
+
normalized.startsWith("~/") ||
|
|
49
|
+
normalized.startsWith("file:") ||
|
|
50
|
+
normalized.includes("/") ||
|
|
51
|
+
normalized.includes("\\") ||
|
|
52
|
+
/\.[cm]?[jt]sx?$/i.test(normalized) ||
|
|
53
|
+
/^[A-Za-z]:[\\/]/.test(normalized)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function splitProviderModulePathString(raw) {
|
|
58
|
+
const normalized = String(raw || "").trim();
|
|
59
|
+
if (!normalized) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const platformParts = normalized
|
|
64
|
+
.split(path.delimiter)
|
|
65
|
+
.map((item) => item.trim())
|
|
66
|
+
.filter(Boolean);
|
|
67
|
+
if (platformParts.length > 1 || !normalized.includes(",")) {
|
|
68
|
+
return platformParts;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const commaParts = normalized
|
|
72
|
+
.split(",")
|
|
73
|
+
.map((item) => item.trim())
|
|
74
|
+
.filter(Boolean);
|
|
75
|
+
if (commaParts.length > 1 && commaParts.every(looksLikeProviderModulePath)) {
|
|
76
|
+
return commaParts;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return platformParts;
|
|
80
|
+
}
|
|
81
|
+
|
|
39
82
|
function listProviderModulePaths(providerPathEnv) {
|
|
40
83
|
const parts = [];
|
|
41
84
|
appendProviderModulePaths(parts, providerPathEnv);
|