@love-moon/conductor-cli 0.17.2 → 0.18.0
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 +39 -0
- package/bin/conductor-chat-web.js +60 -0
- package/bin/conductor-config.js +8 -2
- package/bin/conductor-fire.js +11 -3
- package/bin/conductor.js +2 -0
- package/package.json +6 -6
- package/src/daemon.js +15 -3
- package/src/runtime-backends.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# @love-moon/conductor-cli
|
|
2
2
|
|
|
3
|
+
## 0.18.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 72324fd: Add a local multi-chat runtime with isolated Chromium workers, persistent profiles,
|
|
8
|
+
per-account concurrency, resumable events, request deduplication, and SDK/CLI access.
|
|
9
|
+
Add experimental Kimi and Claude webpage adapters and optional AI SDK socket transport.
|
|
10
|
+
|
|
11
|
+
Support Kimi Connect JSON envelopes and DeepSeek XHR SSE patches, with verified
|
|
12
|
+
parallel real-account text chats, independent cancellation, and restart recovery.
|
|
13
|
+
|
|
14
|
+
Add DeepSeek --deepthink / --no-deepthink controls and SDK options with persisted
|
|
15
|
+
chat defaults, per-turn overrides, verified UI state, and runtime capability checks.
|
|
16
|
+
|
|
17
|
+
Connect Conductor web-kimi and web-deepseek to managed isolated Chromium workers
|
|
18
|
+
by default. Add conductor chat-web login, account credential bootstrap, automatic
|
|
19
|
+
daemon startup and idle shutdown, stable cross-process resume IDs, and browser
|
|
20
|
+
release when tasks close. Preserve the shared-profile queue for other providers.
|
|
21
|
+
Retain cancelled conversation URLs and report confirmed stops as interruptions.
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- 31b61ea: Bound closed-chat and terminal-turn retention, reclaim browser profiles and orphan
|
|
26
|
+
directories, and keep streaming deltas in memory between durable checkpoints.
|
|
27
|
+
Defer saturated or busy chat startup to the turn queue, validate authentication at
|
|
28
|
+
allocation, and expire queued prompts before crash recovery or delayed submission.
|
|
29
|
+
Keep Gemini on its legacy transport with global runtime sockets and consume the
|
|
30
|
+
Conductor wrapper separator before forwarding a packaged CLI command.
|
|
31
|
+
- 924f51c: Time out stalled WebSocket handshakes so a fire can no longer wedge forever
|
|
32
|
+
mid-reconnect, and let an in-place restart replace a task's tmux fire that the
|
|
33
|
+
server sees as disconnected instead of refusing with "task already active".
|
|
34
|
+
- Updated dependencies [bdbda18]
|
|
35
|
+
- Updated dependencies [063b97c]
|
|
36
|
+
- Updated dependencies [31b61ea]
|
|
37
|
+
- Updated dependencies [924f51c]
|
|
38
|
+
- Updated dependencies [72324fd]
|
|
39
|
+
- @love-moon/ai-sdk@0.18.0
|
|
40
|
+
- @love-moon/conductor-sdk@0.18.0
|
|
41
|
+
|
|
3
42
|
## 0.17.2
|
|
4
43
|
|
|
5
44
|
### Patch Changes
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import yaml from "js-yaml";
|
|
7
|
+
import { resolveConductorConfigPath } from "../src/conductor-paths.js";
|
|
8
|
+
|
|
9
|
+
export async function runChatWebCommand(args = process.argv.slice(2), deps = {}) {
|
|
10
|
+
const log = deps.log ?? (text => process.stdout.write(text));
|
|
11
|
+
if (!args.length || args[0] === "--help" || args[0] === "-h") {
|
|
12
|
+
log("Usage: conductor chat-web login <kimi|deepseek> [--config-file PATH]\n conductor chat-web <serve|chat|turn|runtime> ...\n\nSign in once on the daemon host. web-kimi and web-deepseek start their runtime automatically.\n");
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
const env = deps.env ?? process.env;
|
|
16
|
+
const forwarded = [];
|
|
17
|
+
let configFile;
|
|
18
|
+
for (let i = 0; i < args.length; i++) {
|
|
19
|
+
if (args[i] === "--") { forwarded.push(...args.slice(i + (forwarded.length === 0 ? 1 : 0))); break; }
|
|
20
|
+
if (args[i] === "--config-file") {
|
|
21
|
+
if (!args[i + 1]) throw new Error("--config-file requires a path");
|
|
22
|
+
configFile = args[++i];
|
|
23
|
+
} else if (args[i].startsWith("--config-file=")) {
|
|
24
|
+
configFile = args[i].slice("--config-file=".length);
|
|
25
|
+
if (!configFile) throw new Error("--config-file requires a path");
|
|
26
|
+
} else forwarded.push(args[i]);
|
|
27
|
+
}
|
|
28
|
+
args = forwarded;
|
|
29
|
+
const configPath = resolveConductorConfigPath(configFile, env);
|
|
30
|
+
if (configFile && !fs.existsSync(configPath)) throw new Error("Conductor config not found: " + configPath);
|
|
31
|
+
if (fs.existsSync(configPath)) {
|
|
32
|
+
const config = yaml.load(fs.readFileSync(configPath, "utf8"));
|
|
33
|
+
for (const [key, value] of Object.entries(config?.envs ?? {})) {
|
|
34
|
+
if (/^(CHAT_WEB_|https?_proxy$|all_proxy$|no_proxy$)/i.test(key) && env[key] === undefined && value !== null && value !== undefined) env[key] = String(value);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const req = createRequire(import.meta.url);
|
|
38
|
+
let mod;
|
|
39
|
+
try { mod = deps.chatWebModule ?? await import("@love-moon/chat-web"); }
|
|
40
|
+
catch { throw new Error("Browser chat is unavailable. Install @love-moon/chat-web on this daemon host."); }
|
|
41
|
+
if (args[0] === "login") {
|
|
42
|
+
if (args.length !== 2 || !["kimi", "deepseek"].includes(args[1])) throw new Error("Usage: conductor chat-web login <kimi|deepseek>");
|
|
43
|
+
if (typeof mod.RuntimeChatSession?.open !== "function") throw new Error("Upgrade @love-moon/chat-web to use Conductor browser chat login");
|
|
44
|
+
mod.registerBuiltinProviders();
|
|
45
|
+
await mod.login(args[1], { autoExit: true });
|
|
46
|
+
log("Browser chat account saved. Select web-" + args[1] + " in Conductor.\n");
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
const entry = deps.cliEntry ?? path.join(path.dirname(req.resolve("@love-moon/chat-web/package.json")), "dist/cli.js");
|
|
50
|
+
// The packaged CLI handles socket management and prints structured results.
|
|
51
|
+
process.argv = [process.execPath, entry, ...args];
|
|
52
|
+
await (deps.importCli ?? (url => import(url)))(pathToFileURL(entry).href);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const entry = process.argv[1];
|
|
56
|
+
if (entry && fs.existsSync(entry) && fs.realpathSync(entry) === fs.realpathSync(new URL(import.meta.url))) {
|
|
57
|
+
runChatWebCommand().then(code => { if (typeof code === "number") process.exitCode = code; }, error => {
|
|
58
|
+
process.stderr.write(error.message + "\n"); process.exitCode = 1;
|
|
59
|
+
});
|
|
60
|
+
}
|
package/bin/conductor-config.js
CHANGED
|
@@ -68,6 +68,12 @@ const DEFAULT_CLIs = {
|
|
|
68
68
|
description: "Chat web (Kimi) via @love-moon/chat-web",
|
|
69
69
|
runtimeBackend: "chat-web"
|
|
70
70
|
},
|
|
71
|
+
"web-deepseek": {
|
|
72
|
+
command: "chat-web",
|
|
73
|
+
execArgs: "--model deepseek",
|
|
74
|
+
description: "Chat web (DeepSeek) via @love-moon/chat-web",
|
|
75
|
+
runtimeBackend: "chat-web"
|
|
76
|
+
},
|
|
71
77
|
"web-gemini": {
|
|
72
78
|
command: "chat-web",
|
|
73
79
|
execArgs: "--model gemini",
|
|
@@ -143,10 +149,10 @@ function buildConfigEntryLines(cli, info, { commented = false } = {}) {
|
|
|
143
149
|
lines.push(`${commentPrefix}opencode runs via ai-sdk server mode with permission=allow`);
|
|
144
150
|
}
|
|
145
151
|
if (cli === "web-chatgpt") {
|
|
146
|
-
// web-chatgpt / web-gemini / web-kimi resolve to the chat-web runtime backend
|
|
152
|
+
// web-chatgpt / web-gemini / web-kimi / web-deepseek resolve to the chat-web runtime backend
|
|
147
153
|
// (an in-process Chromium driver via @love-moon/chat-web). The command
|
|
148
154
|
// line is not executed; CLI parses --model from it.
|
|
149
|
-
lines.push(`${commentPrefix}web-chatgpt / web-gemini / web-kimi drive a real Chromium browser via @love-moon/chat-web`);
|
|
155
|
+
lines.push(`${commentPrefix}web-chatgpt / web-gemini / web-kimi / web-deepseek drive a real Chromium browser via @love-moon/chat-web`);
|
|
150
156
|
}
|
|
151
157
|
|
|
152
158
|
lines.push(`${entryPrefix}${cli}: ${fullCommand}`);
|
package/bin/conductor-fire.js
CHANGED
|
@@ -266,9 +266,17 @@ export function resolveAiSessionCommandLine(backend, allowCliList, env = process
|
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
export function resolveAiSessionOptions(backend, allowCliList, env = process.env, sessionBackend = backend) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
)
|
|
269
|
+
const commandLine = resolveAiSessionCommandLine(backend, allowCliList, env, sessionBackend);
|
|
270
|
+
const options = extractAiSessionOptionsFromCommandLine(commandLine);
|
|
271
|
+
if (normalizeRuntimeBackendName(sessionBackend) === "chat-web") {
|
|
272
|
+
const { parts } = parseCommandParts(commandLine);
|
|
273
|
+
for (const [positive, negative, key] of [["--headless", "--headed", "headless"], ["--deepthink", "--no-deepthink", "chatWebDeepThink"]]) {
|
|
274
|
+
if (parts.includes(positive) && parts.includes(negative)) throw new Error(`Choose ${positive} or ${negative}`);
|
|
275
|
+
if (parts.includes(positive)) options[key] = true;
|
|
276
|
+
else if (parts.includes(negative)) options[key] = false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return options;
|
|
272
280
|
}
|
|
273
281
|
|
|
274
282
|
const DEFAULT_POLL_INTERVAL_MS = parseInt(
|
package/bin/conductor.js
CHANGED
|
@@ -51,6 +51,7 @@ export function runConductorCli(args = argv, deps = {}) {
|
|
|
51
51
|
"send-file",
|
|
52
52
|
"channel",
|
|
53
53
|
"serve-ai",
|
|
54
|
+
"chat-web",
|
|
54
55
|
"project",
|
|
55
56
|
"issue",
|
|
56
57
|
"task",
|
|
@@ -140,6 +141,7 @@ Subcommands:
|
|
|
140
141
|
diagnose Diagnose a task and print likely root cause
|
|
141
142
|
send-file Upload a local file into a task session
|
|
142
143
|
channel Connect user-owned chat channel providers
|
|
144
|
+
chat-web Sign in and manage browser chat accounts and conversations
|
|
143
145
|
serve-ai Start an OpenAI-compatible local AI server
|
|
144
146
|
project Manage Conductor projects (list/show/create/update/delete/reorder/
|
|
145
147
|
agents/collab/labels/...)
|
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.18.0",
|
|
4
|
+
"gitCommitId": "825d90a",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/lovemoon-ai/conductor.git"
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
"provenance": true
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
|
-
"pretest": "pnpm --dir ../modules/ai-sdk build && pnpm --dir ../modules/conductor-sdk build",
|
|
23
|
+
"pretest": "pnpm --dir ../modules/chat-web build && pnpm --dir ../modules/ai-sdk build && pnpm --dir ../modules/conductor-sdk build",
|
|
24
24
|
"test": "node --test test/*.test.js"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@love-moon/ai-sdk": "0.
|
|
28
|
-
"@love-moon/conductor-sdk": "0.
|
|
27
|
+
"@love-moon/ai-sdk": "0.18.0",
|
|
28
|
+
"@love-moon/conductor-sdk": "0.18.0",
|
|
29
29
|
"@github/copilot-sdk": "^0.3.0",
|
|
30
30
|
"chrome-launcher": "^1.2.1",
|
|
31
31
|
"chrome-remote-interface": "^0.33.0",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"optionalDependencies": {
|
|
40
40
|
"@roamhq/wrtc": "^0.10.0",
|
|
41
|
-
"@love-moon/chat-web": "0.
|
|
41
|
+
"@love-moon/chat-web": "0.18.0"
|
|
42
42
|
},
|
|
43
43
|
"pnpm": {
|
|
44
44
|
"onlyBuiltDependencies": [
|
package/src/daemon.js
CHANGED
|
@@ -7081,7 +7081,7 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
7081
7081
|
// left is a still-exiting child or a tmux record the liveness reaper has not
|
|
7082
7082
|
// swept yet; a fire that is somehow still alive is stopped here. Its terminal
|
|
7083
7083
|
// status is suppressed because the task row now belongs to the new round.
|
|
7084
|
-
async function releaseReplacedFire(taskId) {
|
|
7084
|
+
async function releaseReplacedFire(taskId, reason = "persistent_new_round") {
|
|
7085
7085
|
const record = activeTaskProcesses.get(taskId);
|
|
7086
7086
|
if (!recordHasLiveFire(record)) {
|
|
7087
7087
|
return true;
|
|
@@ -7099,12 +7099,12 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
7099
7099
|
forgetFireSessionRecord(record);
|
|
7100
7100
|
return true;
|
|
7101
7101
|
}
|
|
7102
|
-
stopActiveTaskProcess(taskId, { reason
|
|
7102
|
+
stopActiveTaskProcess(taskId, { reason, suppressExitStatusReport: true });
|
|
7103
7103
|
// The tmux stop path retires the record synchronously and never consumes the flag.
|
|
7104
7104
|
suppressedExitStatusReports.delete(taskId);
|
|
7105
7105
|
return !activeTaskProcesses.has(taskId);
|
|
7106
7106
|
}
|
|
7107
|
-
stopActiveTaskProcess(taskId, { reason
|
|
7107
|
+
stopActiveTaskProcess(taskId, { reason, suppressExitStatusReport: true });
|
|
7108
7108
|
const stopped = await waitForTaskToStop(taskId);
|
|
7109
7109
|
if (!stopped) {
|
|
7110
7110
|
suppressedExitStatusReports.delete(taskId);
|
|
@@ -8278,6 +8278,18 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
8278
8278
|
}
|
|
8279
8279
|
}
|
|
8280
8280
|
|
|
8281
|
+
// The server saw no connected fire for this task, so a surviving session
|
|
8282
|
+
// is a wedged fire (e.g. stuck mid-reconnect): replace it, don't refuse.
|
|
8283
|
+
if (
|
|
8284
|
+
!isRefreshSessionInplace &&
|
|
8285
|
+
payload?.replace_existing_fire === true &&
|
|
8286
|
+
recordHasLiveFire(activeTarget)
|
|
8287
|
+
) {
|
|
8288
|
+
log(`Replacing disconnected fire for task ${normalizedTargetTaskId} before restart`);
|
|
8289
|
+
await releaseReplacedFire(normalizedTargetTaskId, "restart_replace_disconnected_fire");
|
|
8290
|
+
activeTarget = activeTaskProcesses.get(normalizedTargetTaskId);
|
|
8291
|
+
}
|
|
8292
|
+
|
|
8281
8293
|
// The probe above already cleared the record if the session was
|
|
8282
8294
|
// conclusively gone, so a surviving tmuxMode entry means a live session.
|
|
8283
8295
|
const targetHasLiveFire = recordHasLiveFire(activeTarget);
|
package/src/runtime-backends.js
CHANGED
|
@@ -19,7 +19,7 @@ const BUILT_IN_RUNTIME_BACKEND_SET = new Set(BUILT_IN_RUNTIME_BACKENDS);
|
|
|
19
19
|
//
|
|
20
20
|
// chat-web is intentionally NOT here even though it's also command-optional
|
|
21
21
|
// (it drives a Chromium browser, not a CLI). The reason: chat-web has
|
|
22
|
-
// configurable sub-providers (chatgpt / gemini / kimi) selected via
|
|
22
|
+
// configurable sub-providers (chatgpt / gemini / kimi / deepseek) selected via
|
|
23
23
|
// `--model`, and surfacing a bare `chat-web` backend alongside user
|
|
24
24
|
// aliases like `web-chatgpt` / `web-gemini` / `web-kimi` is just confusing — the alias
|
|
25
25
|
// IS the sub-provider choice. Users who want chat-web must declare an
|