@love-moon/conductor-cli 0.2.17 → 0.2.19
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-config.js +28 -29
- package/bin/conductor-fire.js +210 -18
- package/bin/conductor-send-file.js +290 -0
- package/bin/conductor.js +5 -1
- package/package.json +6 -5
- package/src/daemon.js +975 -29
- package/src/runtime-backends.js +31 -0
package/bin/conductor-config.js
CHANGED
|
@@ -8,6 +8,7 @@ import readline from "node:readline/promises";
|
|
|
8
8
|
import { execSync } from "node:child_process";
|
|
9
9
|
import yargs from "yargs/yargs";
|
|
10
10
|
import { hideBin } from "yargs/helpers";
|
|
11
|
+
import { RUNTIME_SUPPORTED_BACKENDS } from "../src/runtime-backends.js";
|
|
11
12
|
|
|
12
13
|
const CONFIG_DIR = path.join(os.homedir(), ".conductor");
|
|
13
14
|
const CONFIG_FILE = path.join(CONFIG_DIR, "config.yaml");
|
|
@@ -25,21 +26,16 @@ const DEFAULT_CLIs = {
|
|
|
25
26
|
execArgs: "exec --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check",
|
|
26
27
|
description: "OpenAI Codex CLI"
|
|
27
28
|
},
|
|
28
|
-
copilot: {
|
|
29
|
-
command: "copilot",
|
|
30
|
-
execArgs: "--allow-all-paths --allow-all-tools",
|
|
31
|
-
description: "GitHub Copilot CLI"
|
|
32
|
-
},
|
|
33
29
|
// gemini: {
|
|
34
30
|
// command: "gemini",
|
|
35
31
|
// execArgs: "",
|
|
36
32
|
// description: "Google Gemini CLI"
|
|
37
33
|
// },
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
opencode: {
|
|
35
|
+
command: "opencode",
|
|
36
|
+
execArgs: "",
|
|
37
|
+
description: "OpenCode CLI (Conductor runs opencode serve with permission=allow)"
|
|
38
|
+
},
|
|
43
39
|
// kimi: {
|
|
44
40
|
// command: "kimi",
|
|
45
41
|
// execArgs: "--yolo --print --prompt",
|
|
@@ -66,6 +62,22 @@ function colorize(text, color) {
|
|
|
66
62
|
return `${COLORS[color] || ""}${text}${COLORS.reset}`;
|
|
67
63
|
}
|
|
68
64
|
|
|
65
|
+
function buildConfigEntryLines(cli, info, { commented = false } = {}) {
|
|
66
|
+
const fullCommand = info.execArgs
|
|
67
|
+
? `${info.command} ${info.execArgs}`
|
|
68
|
+
: info.command;
|
|
69
|
+
const entryPrefix = commented ? " # " : " ";
|
|
70
|
+
const commentPrefix = commented ? " # " : " # ";
|
|
71
|
+
const lines = [];
|
|
72
|
+
|
|
73
|
+
if (cli === "opencode") {
|
|
74
|
+
lines.push(`${commentPrefix}opencode runs via ai-sdk server mode with permission=allow`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
lines.push(`${entryPrefix}${cli}: ${fullCommand}`);
|
|
78
|
+
return lines;
|
|
79
|
+
}
|
|
80
|
+
|
|
69
81
|
async function main() {
|
|
70
82
|
// 解析命令行参数
|
|
71
83
|
const argv = yargs(hideBin(process.argv))
|
|
@@ -170,19 +182,13 @@ async function main() {
|
|
|
170
182
|
if (detectedCLIs.length > 0) {
|
|
171
183
|
detectedCLIs.forEach(cli => {
|
|
172
184
|
const info = DEFAULT_CLIs[cli];
|
|
173
|
-
|
|
174
|
-
? `${info.command} ${info.execArgs}`
|
|
175
|
-
: info.command;
|
|
176
|
-
lines.push(` ${cli}: ${fullCommand}`);
|
|
185
|
+
lines.push(...buildConfigEntryLines(cli, info));
|
|
177
186
|
});
|
|
178
187
|
} else {
|
|
179
188
|
// 如果没有检测到任何 CLI,添加示例注释
|
|
180
189
|
lines.push(" # No CLI detected. Add your installed CLI here:");
|
|
181
|
-
Object.entries(DEFAULT_CLIs).
|
|
182
|
-
|
|
183
|
-
? `${info.command} ${info.execArgs}`
|
|
184
|
-
: info.command;
|
|
185
|
-
lines.push(` # ${key}: ${fullCommand}`);
|
|
190
|
+
Object.entries(DEFAULT_CLIs).forEach(([key, info]) => {
|
|
191
|
+
lines.push(...buildConfigEntryLines(key, info, { commented: true }));
|
|
186
192
|
});
|
|
187
193
|
}
|
|
188
194
|
|
|
@@ -213,6 +219,9 @@ function detectInstalledCLIs() {
|
|
|
213
219
|
const detected = [];
|
|
214
220
|
|
|
215
221
|
for (const [key, info] of Object.entries(DEFAULT_CLIs)) {
|
|
222
|
+
if (!RUNTIME_SUPPORTED_BACKENDS.includes(key)) {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
216
225
|
if (isCommandAvailable(info.command)) {
|
|
217
226
|
detected.push(key);
|
|
218
227
|
}
|
|
@@ -282,16 +291,6 @@ function checkAlternativeInstallations(command) {
|
|
|
282
291
|
);
|
|
283
292
|
}
|
|
284
293
|
|
|
285
|
-
// 特殊检查:Copilot CLI 可能是 gh copilot 扩展
|
|
286
|
-
if (command === "copilot" || command === "copilot-chat") {
|
|
287
|
-
try {
|
|
288
|
-
execSync("gh copilot --help", { stdio: "pipe", timeout: 5000 });
|
|
289
|
-
return true;
|
|
290
|
-
} catch {
|
|
291
|
-
// gh copilot 未安装
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
294
|
// 检查文件是否存在
|
|
296
295
|
for (const checkPath of commonPaths) {
|
|
297
296
|
if (fs.existsSync(checkPath)) {
|
package/bin/conductor-fire.js
CHANGED
|
@@ -26,12 +26,14 @@ 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);
|
|
32
33
|
const PKG_ROOT = path.join(__dirname, "..");
|
|
33
34
|
const INITIAL_CLI_PROJECT_PATH = process.cwd();
|
|
34
35
|
const FIRE_LOG_PATH = path.join(INITIAL_CLI_PROJECT_PATH, "conductor.log");
|
|
36
|
+
const FIRE_TASK_MARKER_PREFIX = "active-fire";
|
|
35
37
|
const ENABLE_FIRE_LOCAL_LOG = !process.env.CONDUCTOR_CLI_COMMAND;
|
|
36
38
|
|
|
37
39
|
const pkgJson = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, "package.json"), "utf-8"));
|
|
@@ -49,7 +51,7 @@ function loadAllowCliList(configFilePath) {
|
|
|
49
51
|
const content = fs.readFileSync(configPath, "utf8");
|
|
50
52
|
const parsed = yaml.load(content);
|
|
51
53
|
if (parsed && typeof parsed === "object" && parsed.allow_cli_list) {
|
|
52
|
-
return parsed.allow_cli_list;
|
|
54
|
+
return filterRuntimeSupportedAllowCliList(parsed.allow_cli_list);
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
} catch (error) {
|
|
@@ -58,6 +60,35 @@ function loadAllowCliList(configFilePath) {
|
|
|
58
60
|
return {};
|
|
59
61
|
}
|
|
60
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
|
+
|
|
61
92
|
const DEFAULT_POLL_INTERVAL_MS = parseInt(
|
|
62
93
|
process.env.CONDUCTOR_CLI_POLL_INTERVAL_MS || process.env.CCODEX_POLL_INTERVAL_MS || "2000",
|
|
63
94
|
10,
|
|
@@ -179,7 +210,7 @@ async function main() {
|
|
|
179
210
|
|
|
180
211
|
if (cliArgs.listBackends) {
|
|
181
212
|
if (supportedBackends.length === 0) {
|
|
182
|
-
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
|
|
213
|
+
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`);
|
|
183
214
|
} else {
|
|
184
215
|
process.stdout.write(`Supported backends (from config):\n`);
|
|
185
216
|
for (const [name, command] of Object.entries(allowCliList)) {
|
|
@@ -303,6 +334,13 @@ async function main() {
|
|
|
303
334
|
backend: cliArgs.backend,
|
|
304
335
|
daemonName: configuredDaemonName,
|
|
305
336
|
});
|
|
337
|
+
injectResolvedTaskId(taskContext.taskId);
|
|
338
|
+
injectResolvedTaskId(taskContext.taskId, env);
|
|
339
|
+
try {
|
|
340
|
+
writeFireTaskMarker(taskContext.taskId, runtimeProjectPath);
|
|
341
|
+
} catch (error) {
|
|
342
|
+
log(`Note: Could not persist local task marker: ${error.message}`);
|
|
343
|
+
}
|
|
306
344
|
|
|
307
345
|
log(
|
|
308
346
|
`Attached to Conductor task ${taskContext.taskId}${
|
|
@@ -325,11 +363,14 @@ async function main() {
|
|
|
325
363
|
|
|
326
364
|
const resolvedResumeSessionId = cliArgs.resumeSessionId;
|
|
327
365
|
|
|
366
|
+
const sessionCommandLine = resolveAiSessionCommandLine(cliArgs.backend, allowCliList, process.env);
|
|
367
|
+
|
|
328
368
|
backendSession = createAiSession(cliArgs.backend, {
|
|
329
369
|
initialImages: cliArgs.initialImages,
|
|
330
370
|
cwd: runtimeProjectPath,
|
|
331
371
|
resumeSessionId: resolvedResumeSessionId,
|
|
332
372
|
configFile: cliArgs.configFile,
|
|
373
|
+
...(sessionCommandLine ? { commandLine: sessionCommandLine } : {}),
|
|
333
374
|
logger: { log },
|
|
334
375
|
});
|
|
335
376
|
|
|
@@ -351,6 +392,7 @@ async function main() {
|
|
|
351
392
|
taskId: taskContext.taskId,
|
|
352
393
|
pollIntervalMs: Math.max(cliArgs.pollIntervalMs, 500),
|
|
353
394
|
initialPrompt: taskContext.shouldProcessInitialPrompt ? cliArgs.initialPrompt : "",
|
|
395
|
+
initialPromptDelivery: taskContext.initialPromptDelivery || "none",
|
|
354
396
|
includeInitialImages: Boolean(cliArgs.initialPrompt && cliArgs.initialImages.length),
|
|
355
397
|
cliArgs: cliArgs.rawBackendArgs,
|
|
356
398
|
backendName: cliArgs.backend,
|
|
@@ -570,7 +612,6 @@ export function parseCliArgs(argvInput = process.argv) {
|
|
|
570
612
|
alias: "b",
|
|
571
613
|
type: "string",
|
|
572
614
|
describe: `Backend to use (loaded from config: ${supportedBackends.join(", ") || "none configured"})`,
|
|
573
|
-
...(supportedBackends.length > 0 ? { choices: supportedBackends } : {}),
|
|
574
615
|
})
|
|
575
616
|
.option("list-backends", {
|
|
576
617
|
type: "boolean",
|
|
@@ -632,12 +673,12 @@ Config file format (~/.conductor/config.yaml):
|
|
|
632
673
|
allow_cli_list:
|
|
633
674
|
codex: codex --dangerously-bypass-approvals-and-sandbox
|
|
634
675
|
claude: claude --dangerously-skip-permissions
|
|
635
|
-
|
|
676
|
+
opencode: opencode
|
|
636
677
|
|
|
637
678
|
Examples:
|
|
638
679
|
${CLI_NAME} -- "fix the bug" # Use default backend
|
|
639
680
|
${CLI_NAME} --backend claude -- "fix the bug" # Use Claude CLI backend
|
|
640
|
-
${CLI_NAME} --backend
|
|
681
|
+
${CLI_NAME} --backend opencode -- "fix the bug" # Use OpenCode backend
|
|
641
682
|
${CLI_NAME} --backend codex --resume <id> # Resume Codex session
|
|
642
683
|
${CLI_NAME} --list-backends # Show configured backends
|
|
643
684
|
${CLI_NAME} --config-file ~/.conductor/config.yaml -- "fix the bug"
|
|
@@ -660,7 +701,22 @@ Environment:
|
|
|
660
701
|
})
|
|
661
702
|
.parse();
|
|
662
703
|
|
|
663
|
-
const backend = conductorArgs.backend
|
|
704
|
+
const backend = conductorArgs.backend
|
|
705
|
+
? normalizeRuntimeBackendName(conductorArgs.backend)
|
|
706
|
+
: supportedBackends[0];
|
|
707
|
+
const shouldRequireBackend =
|
|
708
|
+
!Boolean(conductorArgs.listBackends) &&
|
|
709
|
+
!listBackendsWithoutSeparator &&
|
|
710
|
+
!Boolean(conductorArgs.version) &&
|
|
711
|
+
!versionWithoutSeparator;
|
|
712
|
+
if (backend && !supportedBackends.includes(backend) && shouldRequireBackend) {
|
|
713
|
+
throw new Error(
|
|
714
|
+
`Unsupported backend "${backend}". Supported backends: ${supportedBackends.join(", ") || "none configured"}.`,
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
if (!backend && shouldRequireBackend) {
|
|
718
|
+
throw new Error("No supported backends configured. Add codex, claude, or opencode to allow_cli_list.");
|
|
719
|
+
}
|
|
664
720
|
|
|
665
721
|
const prompt = (backendArgs._ || []).map((part) => String(part)).join(" ").trim();
|
|
666
722
|
const initialImages = normalizeArray(backendArgs.image || backendArgs.i).map((img) => String(img));
|
|
@@ -711,6 +767,73 @@ export function resolveRequestedTaskTitle({
|
|
|
711
767
|
return resolveTaskTitle(explicit, runtimeProjectPath);
|
|
712
768
|
}
|
|
713
769
|
|
|
770
|
+
function normalizeTaskId(value) {
|
|
771
|
+
if (typeof value !== "string") {
|
|
772
|
+
return "";
|
|
773
|
+
}
|
|
774
|
+
return value.trim();
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function resolveFireStateDir(workingDirectory = process.cwd()) {
|
|
778
|
+
const baseDir =
|
|
779
|
+
typeof workingDirectory === "string" && workingDirectory.trim()
|
|
780
|
+
? path.resolve(workingDirectory.trim())
|
|
781
|
+
: process.cwd();
|
|
782
|
+
return path.join(baseDir, ".conductor", "state");
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
export function injectResolvedTaskId(taskId, env = process.env) {
|
|
786
|
+
const normalizedTaskId = normalizeTaskId(taskId);
|
|
787
|
+
if (!normalizedTaskId) {
|
|
788
|
+
return "";
|
|
789
|
+
}
|
|
790
|
+
env.CONDUCTOR_TASK_ID = normalizedTaskId;
|
|
791
|
+
return normalizedTaskId;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
export function writeFireTaskMarker(taskId, workingDirectory = process.cwd()) {
|
|
795
|
+
const normalizedTaskId = normalizeTaskId(taskId);
|
|
796
|
+
if (!normalizedTaskId) {
|
|
797
|
+
return "";
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
const stateDir = resolveFireStateDir(workingDirectory);
|
|
801
|
+
const markerFileName = `${FIRE_TASK_MARKER_PREFIX}.task_${normalizedTaskId}.json`;
|
|
802
|
+
const markerPath = path.join(stateDir, markerFileName);
|
|
803
|
+
|
|
804
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
805
|
+
fs.writeFileSync(
|
|
806
|
+
markerPath,
|
|
807
|
+
`${JSON.stringify(
|
|
808
|
+
{
|
|
809
|
+
source: "conductor-fire",
|
|
810
|
+
taskId: normalizedTaskId,
|
|
811
|
+
cwd: path.resolve(workingDirectory),
|
|
812
|
+
updatedAt: new Date().toISOString(),
|
|
813
|
+
},
|
|
814
|
+
null,
|
|
815
|
+
2,
|
|
816
|
+
)}\n`,
|
|
817
|
+
"utf8",
|
|
818
|
+
);
|
|
819
|
+
|
|
820
|
+
for (const entry of fs.readdirSync(stateDir)) {
|
|
821
|
+
if (entry === markerFileName) {
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
if (!entry.startsWith(`${FIRE_TASK_MARKER_PREFIX}.task_`) || !entry.endsWith(".json")) {
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
try {
|
|
828
|
+
fs.unlinkSync(path.join(stateDir, entry));
|
|
829
|
+
} catch {
|
|
830
|
+
// ignore stale marker cleanup failures
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
return markerPath;
|
|
835
|
+
}
|
|
836
|
+
|
|
714
837
|
function normalizeArray(value) {
|
|
715
838
|
if (!value) {
|
|
716
839
|
return [];
|
|
@@ -731,6 +854,7 @@ async function ensureTaskContext(conductor, opts) {
|
|
|
731
854
|
taskId: opts.providedTaskId,
|
|
732
855
|
appUrl: null,
|
|
733
856
|
shouldProcessInitialPrompt: Boolean(opts.initialPrompt),
|
|
857
|
+
initialPromptDelivery: opts.initialPrompt ? "synthetic" : "none",
|
|
734
858
|
};
|
|
735
859
|
}
|
|
736
860
|
|
|
@@ -762,6 +886,7 @@ async function ensureTaskContext(conductor, opts) {
|
|
|
762
886
|
taskId: session.task_id,
|
|
763
887
|
appUrl: session.app_url || null,
|
|
764
888
|
shouldProcessInitialPrompt: Boolean(opts.initialPrompt),
|
|
889
|
+
initialPromptDelivery: opts.initialPrompt ? "queued" : "none",
|
|
765
890
|
};
|
|
766
891
|
}
|
|
767
892
|
|
|
@@ -941,6 +1066,7 @@ export class BridgeRunner {
|
|
|
941
1066
|
taskId,
|
|
942
1067
|
pollIntervalMs,
|
|
943
1068
|
initialPrompt,
|
|
1069
|
+
initialPromptDelivery,
|
|
944
1070
|
includeInitialImages,
|
|
945
1071
|
cliArgs,
|
|
946
1072
|
backendName,
|
|
@@ -952,6 +1078,10 @@ export class BridgeRunner {
|
|
|
952
1078
|
this.taskId = taskId;
|
|
953
1079
|
this.pollIntervalMs = pollIntervalMs;
|
|
954
1080
|
this.initialPrompt = initialPrompt;
|
|
1081
|
+
this.initialPromptDelivery =
|
|
1082
|
+
typeof initialPromptDelivery === "string" && initialPromptDelivery.trim()
|
|
1083
|
+
? initialPromptDelivery.trim()
|
|
1084
|
+
: "none";
|
|
955
1085
|
this.includeInitialImages = includeInitialImages;
|
|
956
1086
|
this.cliArgs = cliArgs;
|
|
957
1087
|
this.backendName = backendName || "codex";
|
|
@@ -967,6 +1097,10 @@ export class BridgeRunner {
|
|
|
967
1097
|
this.runningTurn = false;
|
|
968
1098
|
this.processedMessageIds = new Set();
|
|
969
1099
|
this.inFlightMessageIds = new Set();
|
|
1100
|
+
this.pendingInitialPrompt =
|
|
1101
|
+
this.initialPromptDelivery === "queued" && typeof initialPrompt === "string" && initialPrompt.trim()
|
|
1102
|
+
? initialPrompt.trim()
|
|
1103
|
+
: "";
|
|
970
1104
|
this.sessionStreamReplyCounts = new Map();
|
|
971
1105
|
this.lastRuntimeStatusSignature = null;
|
|
972
1106
|
this.lastRuntimeStatusPayload = null;
|
|
@@ -992,6 +1126,7 @@ export class BridgeRunner {
|
|
|
992
1126
|
this.needsReconnectRecovery = false;
|
|
993
1127
|
this.remoteStopInfo = null;
|
|
994
1128
|
this.sessionAnnouncementSent = false;
|
|
1129
|
+
this.boundSessionId = "";
|
|
995
1130
|
this.errorLoop = null;
|
|
996
1131
|
this.errorLoopWindowMs = getBoundedEnvInt(
|
|
997
1132
|
"CONDUCTOR_ERROR_LOOP_WINDOW_MS",
|
|
@@ -1058,16 +1193,11 @@ export class BridgeRunner {
|
|
|
1058
1193
|
const message = hasRealSessionId
|
|
1059
1194
|
? `${this.backendName} session started: ${sessionId}`
|
|
1060
1195
|
: `${this.backendName} session started`;
|
|
1061
|
-
if (hasRealSessionId
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
backend_type: this.backendName,
|
|
1067
|
-
});
|
|
1068
|
-
} catch (error) {
|
|
1069
|
-
log(`Failed to persist task session binding for ${this.taskId}: ${error?.message || error}`);
|
|
1070
|
-
}
|
|
1196
|
+
if (hasRealSessionId) {
|
|
1197
|
+
await this.persistTaskSessionBinding({
|
|
1198
|
+
sessionId,
|
|
1199
|
+
sessionFilePath,
|
|
1200
|
+
});
|
|
1071
1201
|
}
|
|
1072
1202
|
try {
|
|
1073
1203
|
await this.conductor.sendMessage(this.taskId, message, {
|
|
@@ -1101,6 +1231,51 @@ export class BridgeRunner {
|
|
|
1101
1231
|
}
|
|
1102
1232
|
}
|
|
1103
1233
|
|
|
1234
|
+
async persistTaskSessionBinding({ sessionId, sessionFilePath } = {}) {
|
|
1235
|
+
const normalizedSessionId = typeof sessionId === "string" ? sessionId.trim() : "";
|
|
1236
|
+
if (!normalizedSessionId || normalizedSessionId === this.boundSessionId) {
|
|
1237
|
+
return false;
|
|
1238
|
+
}
|
|
1239
|
+
if (typeof this.conductor?.bindTaskSession !== "function") {
|
|
1240
|
+
return false;
|
|
1241
|
+
}
|
|
1242
|
+
try {
|
|
1243
|
+
await this.conductor.bindTaskSession(this.taskId, {
|
|
1244
|
+
session_id: normalizedSessionId,
|
|
1245
|
+
session_file_path:
|
|
1246
|
+
typeof sessionFilePath === "string" && sessionFilePath.trim() ? sessionFilePath.trim() : undefined,
|
|
1247
|
+
backend_type: this.backendName,
|
|
1248
|
+
});
|
|
1249
|
+
this.boundSessionId = normalizedSessionId;
|
|
1250
|
+
return true;
|
|
1251
|
+
} catch (error) {
|
|
1252
|
+
log(`Failed to persist task session binding for ${this.taskId}: ${error?.message || error}`);
|
|
1253
|
+
return false;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
async syncBackendSessionBinding() {
|
|
1258
|
+
if (!this.backendSession) {
|
|
1259
|
+
return false;
|
|
1260
|
+
}
|
|
1261
|
+
let sessionInfo = null;
|
|
1262
|
+
try {
|
|
1263
|
+
if (typeof this.backendSession.getSessionInfo === "function") {
|
|
1264
|
+
sessionInfo = this.backendSession.getSessionInfo();
|
|
1265
|
+
}
|
|
1266
|
+
if (!sessionInfo && typeof this.backendSession.ensureSessionInfo === "function") {
|
|
1267
|
+
sessionInfo = await this.backendSession.ensureSessionInfo();
|
|
1268
|
+
}
|
|
1269
|
+
} catch (error) {
|
|
1270
|
+
this.copilotLog(`session binding sync skipped: ${sanitizeForLog(error?.message || error, 160)}`);
|
|
1271
|
+
return false;
|
|
1272
|
+
}
|
|
1273
|
+
return this.persistTaskSessionBinding({
|
|
1274
|
+
sessionId: sessionInfo?.sessionId,
|
|
1275
|
+
sessionFilePath: sessionInfo?.sessionFilePath,
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1104
1279
|
async start(abortSignal) {
|
|
1105
1280
|
abortSignal?.addEventListener("abort", () => {
|
|
1106
1281
|
this.stopped = true;
|
|
@@ -1114,8 +1289,8 @@ export class BridgeRunner {
|
|
|
1114
1289
|
return;
|
|
1115
1290
|
}
|
|
1116
1291
|
|
|
1117
|
-
if (this.initialPrompt) {
|
|
1118
|
-
this.copilotLog("processing initial prompt");
|
|
1292
|
+
if (this.initialPrompt && this.initialPromptDelivery === "synthetic") {
|
|
1293
|
+
this.copilotLog("processing initial prompt via synthetic attach flow");
|
|
1119
1294
|
await this.handleSyntheticMessage(this.initialPrompt, {
|
|
1120
1295
|
includeImages: this.includeInitialImages,
|
|
1121
1296
|
});
|
|
@@ -1123,6 +1298,7 @@ export class BridgeRunner {
|
|
|
1123
1298
|
if (this.stopped) {
|
|
1124
1299
|
return;
|
|
1125
1300
|
}
|
|
1301
|
+
|
|
1126
1302
|
while (!this.stopped) {
|
|
1127
1303
|
if (this.needsReconnectRecovery && !this.runningTurn) {
|
|
1128
1304
|
await this.recoverAfterReconnect();
|
|
@@ -1505,6 +1681,11 @@ export class BridgeRunner {
|
|
|
1505
1681
|
return;
|
|
1506
1682
|
}
|
|
1507
1683
|
|
|
1684
|
+
await this.persistTaskSessionBinding({
|
|
1685
|
+
sessionId,
|
|
1686
|
+
sessionFilePath,
|
|
1687
|
+
});
|
|
1688
|
+
|
|
1508
1689
|
logBackendReply(this.backendName, normalizedText, {
|
|
1509
1690
|
usage: null,
|
|
1510
1691
|
replyTo: replyTo || "latest",
|
|
@@ -1662,6 +1843,11 @@ export class BridgeRunner {
|
|
|
1662
1843
|
if (replyTo) {
|
|
1663
1844
|
this.inFlightMessageIds.add(replyTo);
|
|
1664
1845
|
}
|
|
1846
|
+
const isQueuedInitialPromptMessage =
|
|
1847
|
+
Boolean(this.pendingInitialPrompt) &&
|
|
1848
|
+
String(message.role || "").toLowerCase() === "user" &&
|
|
1849
|
+
content === this.pendingInitialPrompt;
|
|
1850
|
+
const useInitialImages = isQueuedInitialPromptMessage && this.includeInitialImages;
|
|
1665
1851
|
if (
|
|
1666
1852
|
this.useSessionFileReplyStream &&
|
|
1667
1853
|
typeof this.backendSession?.setSessionReplyTarget === "function"
|
|
@@ -1709,6 +1895,7 @@ export class BridgeRunner {
|
|
|
1709
1895
|
}
|
|
1710
1896
|
|
|
1711
1897
|
const result = await this.backendSession.runTurn(content, {
|
|
1898
|
+
useInitialImages,
|
|
1712
1899
|
onProgress: (payload) => {
|
|
1713
1900
|
void this.reportRuntimeStatus(payload, replyTo);
|
|
1714
1901
|
},
|
|
@@ -1748,9 +1935,13 @@ export class BridgeRunner {
|
|
|
1748
1935
|
cli_args: this.cliArgs,
|
|
1749
1936
|
});
|
|
1750
1937
|
}
|
|
1938
|
+
await this.syncBackendSessionBinding();
|
|
1751
1939
|
if (replyTo) {
|
|
1752
1940
|
this.processedMessageIds.add(replyTo);
|
|
1753
1941
|
}
|
|
1942
|
+
if (isQueuedInitialPromptMessage) {
|
|
1943
|
+
this.pendingInitialPrompt = "";
|
|
1944
|
+
}
|
|
1754
1945
|
this.resetErrorLoop();
|
|
1755
1946
|
if (this.useSessionFileReplyStream) {
|
|
1756
1947
|
this.copilotLog(`session_file turn settled replyTo=${replyTo || "latest"}`);
|
|
@@ -1877,6 +2068,7 @@ export class BridgeRunner {
|
|
|
1877
2068
|
} else {
|
|
1878
2069
|
this.copilotLog("synthetic session_file turn settled");
|
|
1879
2070
|
}
|
|
2071
|
+
await this.syncBackendSessionBinding();
|
|
1880
2072
|
} catch (error) {
|
|
1881
2073
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1882
2074
|
if (this.stopped && (this.remoteStopInfo || isSessionClosedError(error))) {
|