@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.
@@ -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
- // opencode: {
39
- // command: "opencode",
40
- // execArgs: "",
41
- // description: "OpenCode CLI"
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
- const fullCommand = info.execArgs
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).slice(0, 3).forEach(([key, info]) => {
182
- const fullCommand = info.execArgs
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)) {
@@ -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 copilot: copilot --allow-all-paths --allow-all-tools\n kimi: kimi\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
- copilot: copilot --allow-all-paths --allow-all-tools
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 copilot -- "fix the bug" # Use GitHub Copilot CLI 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 || supportedBackends[0];
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 && typeof this.conductor?.bindTaskSession === "function") {
1062
- try {
1063
- await this.conductor.bindTaskSession(this.taskId, {
1064
- session_id: sessionId,
1065
- session_file_path: sessionFilePath || undefined,
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))) {