@love-moon/conductor-cli 0.4.2 → 0.5.1
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/CHANGELOG.md +31 -0
- package/bin/conductor-fire.js +55 -1
- package/package.json +5 -5
- package/src/daemon.js +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# @love-moon/conductor-cli
|
|
2
2
|
|
|
3
|
+
## 0.5.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 39a49fc: fix: reclaim orphaned chat-web browser and cap chat-web task lifetime
|
|
8
|
+
|
|
9
|
+
chat-web persists one Chromium profile per provider, guarded by a per-profile
|
|
10
|
+
SingletonLock. A task whose browser was not cleaned up (e.g. the ai-sdk worker
|
|
11
|
+
was SIGKILLed) left an orphaned Chromium holding that lock, so the next task for
|
|
12
|
+
the same provider failed to launch with `Opening in existing browser session`.
|
|
13
|
+
|
|
14
|
+
- chat-web now reclaims stale/orphaned profile locks before launching (kills an
|
|
15
|
+
orphan whose owner process is gone, clears dead locks) and refuses with a
|
|
16
|
+
clear `ProfileLockedError` when a genuine live chat still holds the profile.
|
|
17
|
+
- The ai-sdk worker now closes its session (and browser) on SIGTERM/SIGINT and
|
|
18
|
+
bounds the close so it can't hang, preventing browser leaks on shutdown.
|
|
19
|
+
- conductor fire caps a chat-web task's active lifetime (default 24h,
|
|
20
|
+
`CONDUCTOR_CHATWEB_MAX_ACTIVE_MS`) and auto-stops it as
|
|
21
|
+
`KILLED / max_active_duration`; chat history is preserved.
|
|
22
|
+
|
|
23
|
+
- Updated dependencies [39a49fc]
|
|
24
|
+
- @love-moon/ai-sdk@0.5.1
|
|
25
|
+
- @love-moon/conductor-sdk@0.5.1
|
|
26
|
+
|
|
27
|
+
## 0.5.0
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- @love-moon/conductor-sdk@0.5.0
|
|
32
|
+
- @love-moon/ai-sdk@0.5.0
|
|
33
|
+
|
|
3
34
|
## 0.4.2
|
|
4
35
|
|
|
5
36
|
### Patch Changes
|
package/bin/conductor-fire.js
CHANGED
|
@@ -246,6 +246,18 @@ const DEFAULT_POLL_INTERVAL_MS = parseInt(
|
|
|
246
246
|
const DEFAULT_ERROR_LOOP_WINDOW_MS = 2 * 60 * 1000;
|
|
247
247
|
const DEFAULT_ERROR_LOOP_BACKOFF_MS = 3 * 60 * 1000;
|
|
248
248
|
const DEFAULT_ERROR_LOOP_THRESHOLD = 3;
|
|
249
|
+
// Runtime backend tokens (first word of the resolved command line) that mean
|
|
250
|
+
// "this task drives a chat-web Chromium browser". Mirrors ai-sdk's chat-web
|
|
251
|
+
// aliases; user-facing aliases like `web-chatgpt` resolve to one of these.
|
|
252
|
+
const CHAT_WEB_RUNTIME_BACKEND_TOKENS = new Set(["chat-web", "chatweb", "chat_web", "web-chat"]);
|
|
253
|
+
// Max active lifetime for a chat-web task before it is auto-stopped (default
|
|
254
|
+
// 24h). Bounded to [1min, 7d]; override via CONDUCTOR_CHATWEB_MAX_ACTIVE_MS.
|
|
255
|
+
const CHAT_WEB_MAX_ACTIVE_MS = getBoundedEnvInt(
|
|
256
|
+
"CONDUCTOR_CHATWEB_MAX_ACTIVE_MS",
|
|
257
|
+
24 * 60 * 60 * 1000,
|
|
258
|
+
60_000,
|
|
259
|
+
7 * 24 * 60 * 60 * 1000,
|
|
260
|
+
);
|
|
249
261
|
const SESSION_BOOTSTRAP_LOCK_TIMEOUT_MS = 15_000;
|
|
250
262
|
const SESSION_BOOTSTRAP_LOCK_RETRY_MS = 50;
|
|
251
263
|
const FIRE_WATCHDOG_INTERVAL_MS = getBoundedEnvInt(
|
|
@@ -866,6 +878,18 @@ async function main() {
|
|
|
866
878
|
env: process.env,
|
|
867
879
|
});
|
|
868
880
|
|
|
881
|
+
// chat-web tasks drive a real Chromium browser that holds a per-profile
|
|
882
|
+
// singleton lock for as long as the task lives. A task that never ends
|
|
883
|
+
// pins that browser indefinitely (and blocks other chats for the same
|
|
884
|
+
// provider), so we cap its active lifetime and auto-stop it when exceeded.
|
|
885
|
+
// The conversation itself is preserved (it lives in the provider account
|
|
886
|
+
// and the persisted profile; closing the browser does not delete it).
|
|
887
|
+
const runtimeBackendToken = String(sessionCommandLine || "")
|
|
888
|
+
.trim()
|
|
889
|
+
.split(/\s+/)[0]
|
|
890
|
+
?.toLowerCase() || "";
|
|
891
|
+
const isChatWebTask = CHAT_WEB_RUNTIME_BACKEND_TOKENS.has(runtimeBackendToken);
|
|
892
|
+
|
|
869
893
|
log(`Using backend: ${cliArgs.backend}`);
|
|
870
894
|
|
|
871
895
|
try {
|
|
@@ -880,6 +904,7 @@ async function main() {
|
|
|
880
904
|
|
|
881
905
|
const signals = new AbortController();
|
|
882
906
|
let shutdownSignal = null;
|
|
907
|
+
let autoStopReason = null;
|
|
883
908
|
let backendShutdownRequested = false;
|
|
884
909
|
const requestBackendShutdown = (source) => {
|
|
885
910
|
if (backendShutdownRequested) {
|
|
@@ -911,6 +936,27 @@ async function main() {
|
|
|
911
936
|
process.on("SIGINT", onSigint);
|
|
912
937
|
process.on("SIGTERM", onSigterm);
|
|
913
938
|
|
|
939
|
+
// Cap the active lifetime of chat-web tasks (default 24h). On expiry we
|
|
940
|
+
// stop the task gracefully — abort the runner and close the backend
|
|
941
|
+
// session so the Chromium browser is released — and mark it KILLED with a
|
|
942
|
+
// max_active_duration reason. Chat history is retained on the provider side.
|
|
943
|
+
let maxActiveTimer = null;
|
|
944
|
+
if (isChatWebTask) {
|
|
945
|
+
maxActiveTimer = setTimeout(() => {
|
|
946
|
+
log(
|
|
947
|
+
`chat-web task exceeded max active duration (${CHAT_WEB_MAX_ACTIVE_MS}ms); ` +
|
|
948
|
+
`auto-stopping task (chat history is preserved).`,
|
|
949
|
+
);
|
|
950
|
+
autoStopReason = "max_active_duration";
|
|
951
|
+
fireShuttingDown = true;
|
|
952
|
+
signals.abort();
|
|
953
|
+
requestBackendShutdown("max_active_duration");
|
|
954
|
+
}, CHAT_WEB_MAX_ACTIVE_MS);
|
|
955
|
+
if (typeof maxActiveTimer.unref === "function") {
|
|
956
|
+
maxActiveTimer.unref();
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
914
960
|
if (shouldFireReportTaskStatus({ launchedByDaemon, phase: "running" })) {
|
|
915
961
|
try {
|
|
916
962
|
await conductor.sendTaskStatus(taskContext.taskId, {
|
|
@@ -1030,7 +1076,12 @@ async function main() {
|
|
|
1030
1076
|
// SDK durable outbox would retry forever, preventing the process from
|
|
1031
1077
|
// exiting.
|
|
1032
1078
|
const taskDeletedByUser = remoteStopReason === "deleted_by_user";
|
|
1033
|
-
const finalStatus =
|
|
1079
|
+
const finalStatus = autoStopReason
|
|
1080
|
+
? {
|
|
1081
|
+
status: "KILLED",
|
|
1082
|
+
summary: `${autoStopReason}: chat-web task exceeded its max active duration`,
|
|
1083
|
+
}
|
|
1084
|
+
: shutdownSignal
|
|
1034
1085
|
? {
|
|
1035
1086
|
status: "KILLED",
|
|
1036
1087
|
summary: `terminated by ${shutdownSignal}`,
|
|
@@ -1079,6 +1130,9 @@ async function main() {
|
|
|
1079
1130
|
} finally {
|
|
1080
1131
|
process.off("SIGINT", onSigint);
|
|
1081
1132
|
process.off("SIGTERM", onSigterm);
|
|
1133
|
+
if (maxActiveTimer) {
|
|
1134
|
+
clearTimeout(maxActiveTimer);
|
|
1135
|
+
}
|
|
1082
1136
|
if (shutdownSignal === "SIGINT") {
|
|
1083
1137
|
process.exitCode = 130;
|
|
1084
1138
|
} else if (shutdownSignal === "SIGTERM") {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@love-moon/conductor-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"gitCommitId": "
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"gitCommitId": "119eab6",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/lovemoon-ai/conductor.git"
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"test": "node --test test/*.test.js"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@love-moon/ai-sdk": "0.
|
|
27
|
-
"@love-moon/conductor-sdk": "0.
|
|
26
|
+
"@love-moon/ai-sdk": "0.5.1",
|
|
27
|
+
"@love-moon/conductor-sdk": "0.5.1",
|
|
28
28
|
"@github/copilot-sdk": "^0.3.0",
|
|
29
29
|
"chrome-launcher": "^1.2.1",
|
|
30
30
|
"chrome-remote-interface": "^0.33.0",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"optionalDependencies": {
|
|
39
39
|
"@roamhq/wrtc": "^0.10.0",
|
|
40
|
-
"@love-moon/chat-web": "0.
|
|
40
|
+
"@love-moon/chat-web": "0.5.1"
|
|
41
41
|
},
|
|
42
42
|
"pnpm": {
|
|
43
43
|
"onlyBuiltDependencies": [
|
package/src/daemon.js
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
listAdvertisedBackends,
|
|
29
29
|
resolveConfiguredRuntimeBackend,
|
|
30
30
|
isBuiltInRuntimeBackend,
|
|
31
|
+
isCommandOptionalBuiltInRuntimeBackend,
|
|
31
32
|
isRuntimeSupportedBackend,
|
|
32
33
|
normalizeRuntimeBackendAlias,
|
|
33
34
|
normalizeRuntimeBackendName,
|
|
@@ -5055,7 +5056,8 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
5055
5056
|
const isAllowedExternalBackend =
|
|
5056
5057
|
!isBuiltInRuntimeBackend(effectiveBackend) &&
|
|
5057
5058
|
await isRuntimeSupportedBackend(effectiveBackend, { configFilePath: config.CONFIG_FILE });
|
|
5058
|
-
|
|
5059
|
+
const isCommandOptionalBuiltIn = isCommandOptionalBuiltInRuntimeBackend(effectiveBackend);
|
|
5060
|
+
if (!isAdvertisedBackend || (!hasConfiguredEntry && !isAllowedExternalBackend && !isCommandOptionalBuiltIn)) {
|
|
5059
5061
|
logError(`Unsupported backend: ${selectedBackend}. Supported: ${SUPPORTED_BACKENDS.join(", ")}`);
|
|
5060
5062
|
sendAgentCommandAck({
|
|
5061
5063
|
requestId,
|
|
@@ -5526,7 +5528,8 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
5526
5528
|
const isAllowedExternalBackend =
|
|
5527
5529
|
!isBuiltInRuntimeBackend(effectiveBackend) &&
|
|
5528
5530
|
await isRuntimeSupportedBackend(effectiveBackend, { configFilePath: config.CONFIG_FILE });
|
|
5529
|
-
|
|
5531
|
+
const isCommandOptionalBuiltIn = isCommandOptionalBuiltInRuntimeBackend(effectiveBackend);
|
|
5532
|
+
if (!isAdvertisedBackend || (!hasConfiguredEntry && !isAllowedExternalBackend && !isCommandOptionalBuiltIn)) {
|
|
5530
5533
|
reportRestartFailure({
|
|
5531
5534
|
taskId: normalizedTargetTaskId,
|
|
5532
5535
|
projectId: normalizedProjectId,
|