@love-moon/conductor-cli 0.17.1 → 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 +65 -0
- package/bin/conductor-chat-web.js +60 -0
- package/bin/conductor-config.js +15 -3
- package/bin/conductor-fire.js +15 -7
- package/bin/conductor.js +2 -0
- package/package.json +6 -6
- package/src/daemon.js +18 -3
- package/src/remote/client.js +5 -1
- package/src/remote/mcp-launch.js +1 -0
- package/src/runtime-backends.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,70 @@
|
|
|
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
|
+
|
|
42
|
+
## 0.17.2
|
|
43
|
+
|
|
44
|
+
### Patch Changes
|
|
45
|
+
|
|
46
|
+
- 0a9488e: Add Kimi website text conversations with persistent login, multi-turn context,
|
|
47
|
+
reply progress, new chats and browser diagnostics. Expose the provider through
|
|
48
|
+
ai-sdk's chat-web backend and the web-kimi Conductor configuration alias.
|
|
49
|
+
|
|
50
|
+
Explicit unknown chat-web providers now fail with a clear error instead of
|
|
51
|
+
silently opening ChatGPT. The existing kimi CLI backend is unchanged.
|
|
52
|
+
|
|
53
|
+
- b16c1ae: Allow a shared daemon to run the AI side of a Global AI Backend task while code
|
|
54
|
+
stays on one of the user's own daemons. Advertise
|
|
55
|
+
`shared_ai_global_backend_v1` and forward the task id on remote exec, file, and
|
|
56
|
+
MCP requests so the backend can authorize only that task's remote binding.
|
|
57
|
+
|
|
58
|
+
Keep remote grants tied to active tasks: shared credentials cannot mint remote
|
|
59
|
+
bindings, renew them through restarts or persistent rounds, or reactivate ended
|
|
60
|
+
tasks through status updates. Check shared AI compatibility when restarting or
|
|
61
|
+
starting later rounds of an existing remote task too.
|
|
62
|
+
|
|
63
|
+
- Updated dependencies [b124565]
|
|
64
|
+
- Updated dependencies [0a9488e]
|
|
65
|
+
- @love-moon/ai-sdk@0.17.2
|
|
66
|
+
- @love-moon/conductor-sdk@0.17.2
|
|
67
|
+
|
|
3
68
|
## 0.17.1
|
|
4
69
|
|
|
5
70
|
### 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
|
@@ -55,13 +55,25 @@ const DEFAULT_CLIs = {
|
|
|
55
55
|
// chat-web is the runtime backend (an in-process Chromium driver, not a CLI
|
|
56
56
|
// binary). It has multiple sub-providers selected via --model. Each user-
|
|
57
57
|
// facing alias below resolves to the chat-web runtime; advertising the bare
|
|
58
|
-
// `chat-web` backend would be ambiguous, so we emit
|
|
58
|
+
// `chat-web` backend would be ambiguous, so we emit explicit aliases.
|
|
59
59
|
"web-chatgpt": {
|
|
60
60
|
command: "chat-web",
|
|
61
61
|
execArgs: "--model chatgpt",
|
|
62
62
|
description: "Chat web (ChatGPT) via @love-moon/chat-web",
|
|
63
63
|
runtimeBackend: "chat-web"
|
|
64
64
|
},
|
|
65
|
+
"web-kimi": {
|
|
66
|
+
command: "chat-web",
|
|
67
|
+
execArgs: "--model kimi",
|
|
68
|
+
description: "Chat web (Kimi) via @love-moon/chat-web",
|
|
69
|
+
runtimeBackend: "chat-web"
|
|
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
|
+
},
|
|
65
77
|
"web-gemini": {
|
|
66
78
|
command: "chat-web",
|
|
67
79
|
execArgs: "--model gemini",
|
|
@@ -137,10 +149,10 @@ function buildConfigEntryLines(cli, info, { commented = false } = {}) {
|
|
|
137
149
|
lines.push(`${commentPrefix}opencode runs via ai-sdk server mode with permission=allow`);
|
|
138
150
|
}
|
|
139
151
|
if (cli === "web-chatgpt") {
|
|
140
|
-
// web-chatgpt / web-gemini
|
|
152
|
+
// web-chatgpt / web-gemini / web-kimi / web-deepseek resolve to the chat-web runtime backend
|
|
141
153
|
// (an in-process Chromium driver via @love-moon/chat-web). The command
|
|
142
154
|
// line is not executed; CLI parses --model from it.
|
|
143
|
-
lines.push(`${commentPrefix}web-chatgpt / web-gemini 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`);
|
|
144
156
|
}
|
|
145
157
|
|
|
146
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(
|
|
@@ -940,10 +948,10 @@ async function main() {
|
|
|
940
948
|
env: process.env,
|
|
941
949
|
});
|
|
942
950
|
|
|
943
|
-
// chat-web tasks
|
|
944
|
-
//
|
|
945
|
-
//
|
|
946
|
-
//
|
|
951
|
+
// chat-web tasks retain a tab in the shared profile browser while alive.
|
|
952
|
+
// New runtimes schedule turns across tasks; older runtimes exclusively
|
|
953
|
+
// own the profile. Cap task lifetime so abandoned sessions release their
|
|
954
|
+
// browser resources in either runtime.
|
|
947
955
|
// The conversation itself is preserved (it lives in the provider account
|
|
948
956
|
// and the persisted profile; closing the browser does not delete it).
|
|
949
957
|
const runtimeBackendToken = String(sessionCommandLine || "")
|
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
|
@@ -3928,6 +3928,9 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
3928
3928
|
// another daemon over `conductor remote` and binds the remote_* MCP tools
|
|
3929
3929
|
// to a remoteWorkspace as well as a remoteWorktree.
|
|
3930
3930
|
"global_backend_v1",
|
|
3931
|
+
// A shared AI also sends CONDUCTOR_TASK_ID on every remote request so the
|
|
3932
|
+
// backend can grant only that task's A → B remote permission.
|
|
3933
|
+
"shared_ai_global_backend_v1",
|
|
3931
3934
|
"backend_session_list",
|
|
3932
3935
|
CUSTOM_COMMANDS_CAPABILITY,
|
|
3933
3936
|
UPDATE_DAEMON_CAPABILITY,
|
|
@@ -7078,7 +7081,7 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
7078
7081
|
// left is a still-exiting child or a tmux record the liveness reaper has not
|
|
7079
7082
|
// swept yet; a fire that is somehow still alive is stopped here. Its terminal
|
|
7080
7083
|
// status is suppressed because the task row now belongs to the new round.
|
|
7081
|
-
async function releaseReplacedFire(taskId) {
|
|
7084
|
+
async function releaseReplacedFire(taskId, reason = "persistent_new_round") {
|
|
7082
7085
|
const record = activeTaskProcesses.get(taskId);
|
|
7083
7086
|
if (!recordHasLiveFire(record)) {
|
|
7084
7087
|
return true;
|
|
@@ -7096,12 +7099,12 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
7096
7099
|
forgetFireSessionRecord(record);
|
|
7097
7100
|
return true;
|
|
7098
7101
|
}
|
|
7099
|
-
stopActiveTaskProcess(taskId, { reason
|
|
7102
|
+
stopActiveTaskProcess(taskId, { reason, suppressExitStatusReport: true });
|
|
7100
7103
|
// The tmux stop path retires the record synchronously and never consumes the flag.
|
|
7101
7104
|
suppressedExitStatusReports.delete(taskId);
|
|
7102
7105
|
return !activeTaskProcesses.has(taskId);
|
|
7103
7106
|
}
|
|
7104
|
-
stopActiveTaskProcess(taskId, { reason
|
|
7107
|
+
stopActiveTaskProcess(taskId, { reason, suppressExitStatusReport: true });
|
|
7105
7108
|
const stopped = await waitForTaskToStop(taskId);
|
|
7106
7109
|
if (!stopped) {
|
|
7107
7110
|
suppressedExitStatusReports.delete(taskId);
|
|
@@ -8275,6 +8278,18 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
8275
8278
|
}
|
|
8276
8279
|
}
|
|
8277
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
|
+
|
|
8278
8293
|
// The probe above already cleared the record if the session was
|
|
8279
8294
|
// conclusively gone, so a surviving tmuxMode entry means a live session.
|
|
8280
8295
|
const targetHasLiveFire = recordHasLiveFire(activeTarget);
|
package/src/remote/client.js
CHANGED
|
@@ -62,7 +62,11 @@ export function loadCliConfig(configFile, env = process.env) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
function authHeaders(config) {
|
|
65
|
-
|
|
65
|
+
const taskId = process.env.CONDUCTOR_TASK_ID?.trim();
|
|
66
|
+
return {
|
|
67
|
+
Authorization: `Bearer ${config.agentToken}`,
|
|
68
|
+
...(taskId ? { "X-Conductor-Task-Id": taskId } : {}),
|
|
69
|
+
};
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
async function toApiError(response) {
|
package/src/remote/mcp-launch.js
CHANGED
package/src/runtime-backends.js
CHANGED
|
@@ -19,9 +19,9 @@ 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 / deepseek) 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
|
-
// aliases like `web-chatgpt` / `web-gemini` is just confusing — the alias
|
|
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
|
|
26
26
|
// explicit allow_cli_list entry (which is also where `--model` lives).
|
|
27
27
|
const COMMAND_OPTIONAL_BUILT_IN_RUNTIME_BACKENDS = ["copilot", "dsh"];
|