@love-moon/conductor-cli 0.2.31 → 0.2.33
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-fire.js +233 -70
- package/package.json +4 -4
- package/src/daemon.js +837 -56
- package/src/fire/resume.js +67 -7
- package/src/runtime-backends.js +315 -4
package/src/daemon.js
CHANGED
|
@@ -8,13 +8,21 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
8
8
|
import dotenv from "dotenv";
|
|
9
9
|
import yaml from "js-yaml";
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
ConductorWebSocketClient,
|
|
13
|
+
ConductorConfig,
|
|
14
|
+
loadConfig,
|
|
15
|
+
ConfigFileNotFound,
|
|
16
|
+
ProjectContext,
|
|
17
|
+
} from "@love-moon/conductor-sdk";
|
|
12
18
|
import { DaemonLogCollector } from "./log-collector.js";
|
|
13
19
|
import { resolveResumeContext } from "./fire/resume.js";
|
|
14
20
|
import {
|
|
15
|
-
|
|
21
|
+
filterRuntimeSupportedAllowCliList,
|
|
22
|
+
listAdvertisedBackends,
|
|
23
|
+
resolveConfiguredRuntimeBackend,
|
|
24
|
+
isBuiltInRuntimeBackend,
|
|
16
25
|
isRuntimeSupportedBackend,
|
|
17
|
-
listRuntimeSupportedBackends,
|
|
18
26
|
normalizeRuntimeBackendAlias,
|
|
19
27
|
normalizeRuntimeBackendName,
|
|
20
28
|
} from "./runtime-backends.js";
|
|
@@ -59,6 +67,7 @@ const DEFAULT_TERMINAL_ROWS = 40;
|
|
|
59
67
|
const DEFAULT_TERMINAL_RING_BUFFER_MAX_BYTES = 2 * 1024 * 1024;
|
|
60
68
|
const DEFAULT_TERMINAL_RESUME_SNAPSHOT_MAX_BYTES = 128 * 1024;
|
|
61
69
|
const DEFAULT_RTC_MODULE_CANDIDATES = ["@roamhq/wrtc", "wrtc"];
|
|
70
|
+
const BLOCKING_SLEEP_BUFFER = new Int32Array(new SharedArrayBuffer(4));
|
|
62
71
|
let nodePtySpawnPromise = null;
|
|
63
72
|
|
|
64
73
|
function resolveNodePtySpawnExport(mod) {
|
|
@@ -117,6 +126,20 @@ function logError(message) {
|
|
|
117
126
|
appendDaemonLog(line);
|
|
118
127
|
}
|
|
119
128
|
|
|
129
|
+
function sleepSync(ms) {
|
|
130
|
+
if (!Number.isFinite(ms) || ms <= 0) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
Atomics.wait(BLOCKING_SLEEP_BUFFER, 0, 0, ms);
|
|
135
|
+
} catch {
|
|
136
|
+
const deadline = Date.now() + ms;
|
|
137
|
+
while (Date.now() < deadline) {
|
|
138
|
+
// best-effort fallback for runtimes that disallow Atomics.wait
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
120
143
|
function getUserConfig(configFilePath) {
|
|
121
144
|
try {
|
|
122
145
|
const home = os.homedir();
|
|
@@ -192,14 +215,12 @@ function filterConfiguredAllowCliList(allowCliList) {
|
|
|
192
215
|
if (!allowCliList || typeof allowCliList !== "object") {
|
|
193
216
|
return {};
|
|
194
217
|
}
|
|
195
|
-
const builtInBackends = new Set(RUNTIME_SUPPORTED_BACKENDS);
|
|
196
218
|
const filtered = {};
|
|
197
219
|
for (const [backend, command] of Object.entries(allowCliList)) {
|
|
198
220
|
const normalizedBackend = normalizeRuntimeBackendName(backend);
|
|
199
221
|
if (
|
|
200
222
|
!normalizedBackend ||
|
|
201
|
-
LEGACY_RUNTIME_BACKEND_ALIASES.has(normalizedBackend)
|
|
202
|
-
!builtInBackends.has(normalizedBackend)
|
|
223
|
+
LEGACY_RUNTIME_BACKEND_ALIASES.has(normalizedBackend)
|
|
203
224
|
) {
|
|
204
225
|
continue;
|
|
205
226
|
}
|
|
@@ -222,10 +243,28 @@ function getAllowCliList(userConfig) {
|
|
|
222
243
|
return DEFAULT_CLI_LIST;
|
|
223
244
|
}
|
|
224
245
|
|
|
246
|
+
function getRawAllowCliList(userConfig) {
|
|
247
|
+
if (userConfig.allow_cli_list && typeof userConfig.allow_cli_list === "object") {
|
|
248
|
+
return userConfig.allow_cli_list;
|
|
249
|
+
}
|
|
250
|
+
return DEFAULT_CLI_LIST;
|
|
251
|
+
}
|
|
252
|
+
|
|
225
253
|
function formatBackendLaunchCommand(cliCommand) {
|
|
226
254
|
return typeof cliCommand === "string" && cliCommand.trim() ? cliCommand.trim() : "ai-sdk-managed";
|
|
227
255
|
}
|
|
228
256
|
|
|
257
|
+
function serializeRuntimeBackendMap(runtimeBackendMap) {
|
|
258
|
+
if (!runtimeBackendMap || typeof runtimeBackendMap !== "object") {
|
|
259
|
+
return "";
|
|
260
|
+
}
|
|
261
|
+
return Object.entries(runtimeBackendMap)
|
|
262
|
+
.filter(([backend, runtimeBackend]) => backend && runtimeBackend)
|
|
263
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
264
|
+
.map(([backend, runtimeBackend]) => `${backend}=${runtimeBackend}`)
|
|
265
|
+
.join(",");
|
|
266
|
+
}
|
|
267
|
+
|
|
229
268
|
async function defaultCreatePty(command, args, options) {
|
|
230
269
|
if (!nodePtySpawnPromise) {
|
|
231
270
|
const spawnHelperInfo = ensureNodePtySpawnHelperExecutable();
|
|
@@ -393,6 +432,64 @@ function normalizeLaunchConfig(value) {
|
|
|
393
432
|
return value;
|
|
394
433
|
}
|
|
395
434
|
|
|
435
|
+
function normalizeBooleanFlag(value) {
|
|
436
|
+
if (typeof value === "boolean") {
|
|
437
|
+
return value;
|
|
438
|
+
}
|
|
439
|
+
if (typeof value === "number") {
|
|
440
|
+
return value === 1;
|
|
441
|
+
}
|
|
442
|
+
if (typeof value !== "string") {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
const normalized = value.trim().toLowerCase();
|
|
446
|
+
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function parseTaskWorktreeLaunchConfig(launchConfig) {
|
|
450
|
+
const normalizedLaunchConfig = normalizeLaunchConfig(launchConfig);
|
|
451
|
+
const worktreeEnabled = normalizeBooleanFlag(
|
|
452
|
+
normalizedLaunchConfig.worktree ??
|
|
453
|
+
normalizedLaunchConfig.createWorktree ??
|
|
454
|
+
normalizedLaunchConfig.create_worktree,
|
|
455
|
+
);
|
|
456
|
+
if (!worktreeEnabled) {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const worktreeId =
|
|
461
|
+
normalizeOptionalString(normalizedLaunchConfig.worktreeId) ||
|
|
462
|
+
normalizeOptionalString(normalizedLaunchConfig.worktree_id);
|
|
463
|
+
const worktreeBranch =
|
|
464
|
+
normalizeOptionalString(normalizedLaunchConfig.worktreeBranch) ||
|
|
465
|
+
normalizeOptionalString(normalizedLaunchConfig.worktree_branch);
|
|
466
|
+
const projectRepoRoot =
|
|
467
|
+
normalizeOptionalString(normalizedLaunchConfig.projectRepoRoot) ||
|
|
468
|
+
normalizeOptionalString(normalizedLaunchConfig.project_repo_root);
|
|
469
|
+
const projectWorkspacePath =
|
|
470
|
+
normalizeOptionalString(normalizedLaunchConfig.projectWorkspacePath) ||
|
|
471
|
+
normalizeOptionalString(normalizedLaunchConfig.project_workspace_path);
|
|
472
|
+
const projectRelativePath =
|
|
473
|
+
normalizeOptionalString(normalizedLaunchConfig.projectRelativePath) ||
|
|
474
|
+
normalizeOptionalString(normalizedLaunchConfig.project_relative_path) ||
|
|
475
|
+
".";
|
|
476
|
+
if (!worktreeId || !worktreeBranch || !projectRepoRoot || !projectWorkspacePath) {
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return {
|
|
481
|
+
worktreeId,
|
|
482
|
+
worktreeBranch,
|
|
483
|
+
worktreeBaseRef:
|
|
484
|
+
normalizeOptionalString(normalizedLaunchConfig.worktreeBaseRef) ||
|
|
485
|
+
normalizeOptionalString(normalizedLaunchConfig.worktree_base_ref) ||
|
|
486
|
+
"HEAD",
|
|
487
|
+
projectRepoRoot,
|
|
488
|
+
projectWorkspacePath,
|
|
489
|
+
projectRelativePath,
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
396
493
|
function normalizeTerminalEnv(value) {
|
|
397
494
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
398
495
|
return {};
|
|
@@ -410,6 +507,36 @@ function normalizeTerminalEnv(value) {
|
|
|
410
507
|
return env;
|
|
411
508
|
}
|
|
412
509
|
|
|
510
|
+
const PTY_TASK_SCOPED_ENV_KEYS = [
|
|
511
|
+
"CONDUCTOR_PROJECT_ID",
|
|
512
|
+
"CONDUCTOR_TASK_ID",
|
|
513
|
+
"CONDUCTOR_PTY_SESSION_ID",
|
|
514
|
+
"CONDUCTOR_LAUNCHED_BY_DAEMON",
|
|
515
|
+
"CONDUCTOR_RESUME_CWD",
|
|
516
|
+
];
|
|
517
|
+
|
|
518
|
+
function stripPtyTaskScopedEnv(source) {
|
|
519
|
+
const env = {
|
|
520
|
+
...(source && typeof source === "object" ? source : {}),
|
|
521
|
+
};
|
|
522
|
+
for (const key of PTY_TASK_SCOPED_ENV_KEYS) {
|
|
523
|
+
delete env[key];
|
|
524
|
+
}
|
|
525
|
+
return env;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function buildPtyTaskEnv(baseEnv = process.env, launchEnv = {}) {
|
|
529
|
+
const parentEnv = stripPtyTaskScopedEnv(baseEnv);
|
|
530
|
+
const taskLaunchEnv = stripPtyTaskScopedEnv(launchEnv);
|
|
531
|
+
const env = {
|
|
532
|
+
...parentEnv,
|
|
533
|
+
};
|
|
534
|
+
return {
|
|
535
|
+
...env,
|
|
536
|
+
...taskLaunchEnv,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
413
540
|
export function startDaemon(config = {}, deps = {}) {
|
|
414
541
|
const exitFn = deps.exit || process.exit;
|
|
415
542
|
const killFn = deps.kill || process.kill;
|
|
@@ -495,8 +622,10 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
495
622
|
const CLI_PATH_VAL = config.CLI_PATH || CLI_PATH;
|
|
496
623
|
|
|
497
624
|
// Get allow_cli_list from config
|
|
498
|
-
const
|
|
499
|
-
let
|
|
625
|
+
const RAW_ALLOW_CLI_LIST = getRawAllowCliList(userConfig);
|
|
626
|
+
let ALLOW_CLI_LIST = {};
|
|
627
|
+
let SUPPORTED_BACKENDS = [];
|
|
628
|
+
let SUPPORTED_BACKEND_RUNTIME_MAP = {};
|
|
500
629
|
const fetchLatestVersionFn = deps.fetchLatestVersion || fetchLatestVersion;
|
|
501
630
|
const isNewerVersionFn = deps.isNewerVersion || isNewerVersion;
|
|
502
631
|
const detectPackageManagerFn = deps.detectPackageManager || detectPackageManager;
|
|
@@ -539,7 +668,11 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
539
668
|
const mkdirSyncFn = deps.mkdirSync || fs.mkdirSync;
|
|
540
669
|
const writeFileSyncFn = deps.writeFileSync || fs.writeFileSync;
|
|
541
670
|
const existsSyncFn = deps.existsSync || fs.existsSync;
|
|
671
|
+
const statSyncFn = deps.statSync || fs.statSync;
|
|
672
|
+
const lstatSyncFn = deps.lstatSync || fs.lstatSync;
|
|
542
673
|
const readFileSyncFn = deps.readFileSync || fs.readFileSync;
|
|
674
|
+
const readlinkSyncFn = deps.readlinkSync || fs.readlinkSync;
|
|
675
|
+
const symlinkSyncFn = deps.symlinkSync || fs.symlinkSync;
|
|
543
676
|
const unlinkSyncFn = deps.unlinkSync || fs.unlinkSync;
|
|
544
677
|
const renameSyncFn = deps.renameSync || fs.renameSync;
|
|
545
678
|
const createWriteStreamFn = deps.createWriteStream || fs.createWriteStream;
|
|
@@ -550,6 +683,263 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
550
683
|
deps.createWebSocketClient ||
|
|
551
684
|
((clientConfig, options) => new ConductorWebSocketClient(clientConfig, options));
|
|
552
685
|
const createLogCollector = deps.createLogCollector || ((backendUrl) => new DaemonLogCollector(backendUrl));
|
|
686
|
+
const resolveProjectSnapshotFn =
|
|
687
|
+
deps.resolveProjectSnapshot || ((projectPath) => new ProjectContext(projectPath).snapshot());
|
|
688
|
+
|
|
689
|
+
function buildTaskWorktreeRoot(projectWorkspacePath, worktreeId) {
|
|
690
|
+
const sanitized = String(worktreeId).replace(/[/\\]/g, "_").replace(/\.\./g, "_");
|
|
691
|
+
return path.join(projectWorkspacePath, ".conductor", "worktrees", sanitized);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function resolveTaskWorktreeCwd(worktreeRoot, projectRelativePath) {
|
|
695
|
+
return projectRelativePath && projectRelativePath !== "."
|
|
696
|
+
? path.join(worktreeRoot, projectRelativePath)
|
|
697
|
+
: worktreeRoot;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function normalizeConfiguredPathList(value, projectWorkspacePath = "") {
|
|
701
|
+
const rawList = typeof value === "string"
|
|
702
|
+
? [value]
|
|
703
|
+
: Array.isArray(value)
|
|
704
|
+
? value
|
|
705
|
+
: [];
|
|
706
|
+
const deduped = [];
|
|
707
|
+
for (const entry of rawList) {
|
|
708
|
+
const normalizedEntry = normalizeOptionalString(entry);
|
|
709
|
+
if (!normalizedEntry) continue;
|
|
710
|
+
const exactConfiguredPathExists =
|
|
711
|
+
projectWorkspacePath &&
|
|
712
|
+
existsSyncFn(path.resolve(projectWorkspacePath, normalizedEntry));
|
|
713
|
+
const normalizedEntries =
|
|
714
|
+
!exactConfiguredPathExists && /\s/.test(normalizedEntry)
|
|
715
|
+
? normalizedEntry.split(/\s+/).map((part) => normalizeOptionalString(part)).filter(Boolean)
|
|
716
|
+
: [normalizedEntry];
|
|
717
|
+
for (const candidate of normalizedEntries) {
|
|
718
|
+
if (deduped.includes(candidate)) {
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
deduped.push(candidate);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return deduped;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function resolveProjectScopedPath(basePath, configuredPath, label) {
|
|
728
|
+
const resolvedPath = path.resolve(basePath, configuredPath);
|
|
729
|
+
const relativePath = path.relative(basePath, resolvedPath);
|
|
730
|
+
if (
|
|
731
|
+
relativePath === "" ||
|
|
732
|
+
relativePath === "." ||
|
|
733
|
+
relativePath.startsWith("..") ||
|
|
734
|
+
path.isAbsolute(relativePath)
|
|
735
|
+
) {
|
|
736
|
+
throw new Error(`${label} must stay within ${basePath}`);
|
|
737
|
+
}
|
|
738
|
+
return resolvedPath;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function readProjectWorktreeSettings(projectWorkspacePath) {
|
|
742
|
+
const settingsCandidates = [
|
|
743
|
+
path.join(projectWorkspacePath, ".conductor", "settings.yaml"),
|
|
744
|
+
path.join(projectWorkspacePath, ".conductor", "settings.yml"),
|
|
745
|
+
path.join(projectWorkspacePath, ".conductor", "setttings.yaml"),
|
|
746
|
+
path.join(projectWorkspacePath, ".conductor", "setttings.yml"),
|
|
747
|
+
];
|
|
748
|
+
|
|
749
|
+
for (const settingsPath of settingsCandidates) {
|
|
750
|
+
if (!existsSyncFn(settingsPath)) {
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
try {
|
|
754
|
+
const parsed = yaml.load(readFileSyncFn(settingsPath, "utf8"));
|
|
755
|
+
const worktreeSettings =
|
|
756
|
+
parsed && typeof parsed === "object" && !Array.isArray(parsed) &&
|
|
757
|
+
parsed.worktree && typeof parsed.worktree === "object" && !Array.isArray(parsed.worktree)
|
|
758
|
+
? parsed.worktree
|
|
759
|
+
: {};
|
|
760
|
+
return {
|
|
761
|
+
symlinkPaths: normalizeConfiguredPathList(worktreeSettings.symlink, projectWorkspacePath),
|
|
762
|
+
settingsPath,
|
|
763
|
+
};
|
|
764
|
+
} catch (error) {
|
|
765
|
+
throw new Error(`Failed to read ${settingsPath}: ${error?.message || error}`);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
return {
|
|
770
|
+
symlinkPaths: [],
|
|
771
|
+
settingsPath: null,
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function ensureTaskWorktreeSymlinks({ projectWorkspacePath, finalCwd }) {
|
|
776
|
+
const { symlinkPaths } = readProjectWorktreeSettings(projectWorkspacePath);
|
|
777
|
+
for (const configuredPath of symlinkPaths) {
|
|
778
|
+
const sourcePath = resolveProjectScopedPath(
|
|
779
|
+
projectWorkspacePath,
|
|
780
|
+
configuredPath,
|
|
781
|
+
`worktree.symlink entry ${configuredPath}`,
|
|
782
|
+
);
|
|
783
|
+
const linkPath = resolveProjectScopedPath(
|
|
784
|
+
finalCwd,
|
|
785
|
+
configuredPath,
|
|
786
|
+
`worktree.symlink destination ${configuredPath}`,
|
|
787
|
+
);
|
|
788
|
+
mkdirSyncFn(path.dirname(linkPath), { recursive: true });
|
|
789
|
+
|
|
790
|
+
if (existsSyncFn(linkPath)) {
|
|
791
|
+
try {
|
|
792
|
+
const stat = lstatSyncFn(linkPath);
|
|
793
|
+
if (!stat.isSymbolicLink()) {
|
|
794
|
+
throw new Error(`worktree symlink destination already exists: ${linkPath}`);
|
|
795
|
+
}
|
|
796
|
+
const currentTarget = readlinkSyncFn(linkPath);
|
|
797
|
+
const currentResolvedTarget = path.resolve(path.dirname(linkPath), currentTarget);
|
|
798
|
+
if (currentResolvedTarget === sourcePath) {
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
throw new Error(`worktree symlink destination already points elsewhere: ${linkPath}`);
|
|
802
|
+
} catch (error) {
|
|
803
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
const relativeTarget = path.relative(path.dirname(linkPath), sourcePath) || ".";
|
|
808
|
+
symlinkSyncFn(relativeTarget, linkPath);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
async function runSpawnProcess(command, args, options = {}) {
|
|
813
|
+
let child;
|
|
814
|
+
try {
|
|
815
|
+
child = spawnFn(command, args, {
|
|
816
|
+
...options,
|
|
817
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
818
|
+
});
|
|
819
|
+
} catch (error) {
|
|
820
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
return await new Promise((resolve, reject) => {
|
|
824
|
+
let stdout = "";
|
|
825
|
+
let stderr = "";
|
|
826
|
+
let settled = false;
|
|
827
|
+
|
|
828
|
+
const finishResolve = () => {
|
|
829
|
+
if (settled) {
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
settled = true;
|
|
833
|
+
resolve({ stdout, stderr });
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
const finishReject = (error) => {
|
|
837
|
+
if (settled) {
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
settled = true;
|
|
841
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
if (child.stdout && typeof child.stdout.on === "function") {
|
|
845
|
+
child.stdout.on("data", (chunk) => {
|
|
846
|
+
stdout += String(chunk ?? "");
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
if (child.stderr && typeof child.stderr.on === "function") {
|
|
850
|
+
child.stderr.on("data", (chunk) => {
|
|
851
|
+
stderr += String(chunk ?? "");
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
if (typeof child.on === "function") {
|
|
855
|
+
child.on("error", (error) => {
|
|
856
|
+
finishReject(error);
|
|
857
|
+
});
|
|
858
|
+
child.on("close", (code, signal) => {
|
|
859
|
+
if (code === 0) {
|
|
860
|
+
finishResolve();
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
const detail = (stderr || stdout).trim();
|
|
864
|
+
finishReject(
|
|
865
|
+
new Error(
|
|
866
|
+
`${command} ${args.join(" ")} failed` +
|
|
867
|
+
(signal ? ` (signal ${signal})` : ` (exit ${code ?? "unknown"})`) +
|
|
868
|
+
(detail ? `: ${detail}` : ""),
|
|
869
|
+
),
|
|
870
|
+
);
|
|
871
|
+
});
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
finishResolve();
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
async function ensureTaskWorktree({ taskId, projectId, launchConfig }) {
|
|
879
|
+
const worktreeConfig = parseTaskWorktreeLaunchConfig(launchConfig);
|
|
880
|
+
if (!worktreeConfig) {
|
|
881
|
+
return null;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
const worktreeRoot = buildTaskWorktreeRoot(
|
|
885
|
+
worktreeConfig.projectWorkspacePath,
|
|
886
|
+
worktreeConfig.worktreeId,
|
|
887
|
+
);
|
|
888
|
+
const finalCwd = resolveTaskWorktreeCwd(worktreeRoot, worktreeConfig.projectRelativePath);
|
|
889
|
+
const gitMarkerPath = path.join(worktreeRoot, ".git");
|
|
890
|
+
if (!existsSyncFn(gitMarkerPath)) {
|
|
891
|
+
mkdirSyncFn(path.dirname(worktreeRoot), { recursive: true });
|
|
892
|
+
try {
|
|
893
|
+
await runSpawnProcess(
|
|
894
|
+
"git",
|
|
895
|
+
[
|
|
896
|
+
"-C",
|
|
897
|
+
worktreeConfig.projectRepoRoot,
|
|
898
|
+
"worktree",
|
|
899
|
+
"add",
|
|
900
|
+
"-b",
|
|
901
|
+
worktreeConfig.worktreeBranch,
|
|
902
|
+
worktreeRoot,
|
|
903
|
+
worktreeConfig.worktreeBaseRef,
|
|
904
|
+
],
|
|
905
|
+
{
|
|
906
|
+
cwd: worktreeConfig.projectRepoRoot,
|
|
907
|
+
},
|
|
908
|
+
);
|
|
909
|
+
} catch (primaryError) {
|
|
910
|
+
if (!existsSyncFn(gitMarkerPath)) {
|
|
911
|
+
try {
|
|
912
|
+
await runSpawnProcess(
|
|
913
|
+
"git",
|
|
914
|
+
[
|
|
915
|
+
"-C",
|
|
916
|
+
worktreeConfig.projectRepoRoot,
|
|
917
|
+
"worktree",
|
|
918
|
+
"add",
|
|
919
|
+
worktreeRoot,
|
|
920
|
+
worktreeConfig.worktreeBranch,
|
|
921
|
+
],
|
|
922
|
+
{
|
|
923
|
+
cwd: worktreeConfig.projectRepoRoot,
|
|
924
|
+
},
|
|
925
|
+
);
|
|
926
|
+
} catch (reuseError) {
|
|
927
|
+
throw new Error(
|
|
928
|
+
`Failed to prepare git worktree for ${taskId}: ${reuseError?.message || primaryError?.message || primaryError}`,
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
mkdirSyncFn(finalCwd, { recursive: true });
|
|
936
|
+
ensureTaskWorktreeSymlinks({
|
|
937
|
+
projectWorkspacePath: worktreeConfig.projectWorkspacePath,
|
|
938
|
+
finalCwd,
|
|
939
|
+
});
|
|
940
|
+
return finalCwd;
|
|
941
|
+
}
|
|
942
|
+
|
|
553
943
|
const RTC_MODULE_CANDIDATES = resolveRtcModuleCandidates(process.env.CONDUCTOR_PTY_RTC_MODULES);
|
|
554
944
|
const RTC_DIRECT_DISABLED = parseBooleanEnv(process.env.CONDUCTOR_DISABLE_PTY_DIRECT_RTC);
|
|
555
945
|
const PROJECT_PATH_LOOKUP_TIMEOUT_MS = parsePositiveInt(
|
|
@@ -560,6 +950,18 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
560
950
|
process.env.CONDUCTOR_STOP_FORCE_KILL_TIMEOUT_MS,
|
|
561
951
|
5000,
|
|
562
952
|
);
|
|
953
|
+
const DAEMON_FORCE_STOP_GRACE_MS = parsePositiveInt(
|
|
954
|
+
process.env.CONDUCTOR_DAEMON_FORCE_STOP_GRACE_MS,
|
|
955
|
+
15_000,
|
|
956
|
+
);
|
|
957
|
+
const DAEMON_FORCE_STOP_POLL_INTERVAL_MS = parsePositiveInt(
|
|
958
|
+
process.env.CONDUCTOR_DAEMON_FORCE_STOP_POLL_INTERVAL_MS,
|
|
959
|
+
100,
|
|
960
|
+
);
|
|
961
|
+
const DAEMON_FORCE_KILL_WAIT_MS = parsePositiveInt(
|
|
962
|
+
process.env.CONDUCTOR_DAEMON_FORCE_KILL_WAIT_MS,
|
|
963
|
+
2_000,
|
|
964
|
+
);
|
|
563
965
|
const SHUTDOWN_STATUS_REPORT_TIMEOUT_MS = parsePositiveInt(
|
|
564
966
|
process.env.CONDUCTOR_SHUTDOWN_STATUS_REPORT_TIMEOUT_MS,
|
|
565
967
|
1000,
|
|
@@ -648,6 +1050,20 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
648
1050
|
return true;
|
|
649
1051
|
};
|
|
650
1052
|
|
|
1053
|
+
const waitForProcessExitSync = (pid, timeoutMs) => {
|
|
1054
|
+
const deadline = Date.now() + timeoutMs;
|
|
1055
|
+
while (true) {
|
|
1056
|
+
if (!isProcessAlive(pid)) {
|
|
1057
|
+
return true;
|
|
1058
|
+
}
|
|
1059
|
+
const remainingMs = deadline - Date.now();
|
|
1060
|
+
if (remainingMs <= 0) {
|
|
1061
|
+
return false;
|
|
1062
|
+
}
|
|
1063
|
+
sleepSync(Math.min(DAEMON_FORCE_STOP_POLL_INTERVAL_MS, remainingMs));
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
|
|
651
1067
|
try {
|
|
652
1068
|
mkdirSyncFn(WORKSPACE_ROOT, { recursive: true });
|
|
653
1069
|
} catch (err) {
|
|
@@ -670,17 +1086,35 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
670
1086
|
if (alive) {
|
|
671
1087
|
if (config.FORCE) {
|
|
672
1088
|
log(`Force enabled: stopping existing daemon PID ${pid}`);
|
|
1089
|
+
let alreadyExited = false;
|
|
673
1090
|
try {
|
|
674
1091
|
killFn(pid, "SIGTERM");
|
|
675
1092
|
} catch (killErr) {
|
|
676
|
-
if (
|
|
1093
|
+
if (killErr?.code === "ESRCH") {
|
|
1094
|
+
alreadyExited = true;
|
|
1095
|
+
} else {
|
|
677
1096
|
logError(`Failed to stop existing daemon PID ${pid}: ${killErr.message}`);
|
|
678
1097
|
return exitAndReturn(1);
|
|
679
1098
|
}
|
|
680
1099
|
}
|
|
681
1100
|
try {
|
|
682
|
-
|
|
683
|
-
|
|
1101
|
+
let exited = alreadyExited || waitForProcessExitSync(pid, DAEMON_FORCE_STOP_GRACE_MS);
|
|
1102
|
+
if (!exited) {
|
|
1103
|
+
log(
|
|
1104
|
+
`Existing daemon PID ${pid} did not exit within ${DAEMON_FORCE_STOP_GRACE_MS}ms; sending SIGKILL`,
|
|
1105
|
+
);
|
|
1106
|
+
try {
|
|
1107
|
+
killFn(pid, "SIGKILL");
|
|
1108
|
+
} catch (killErr) {
|
|
1109
|
+
if (killErr?.code !== "ESRCH") {
|
|
1110
|
+
logError(`Failed to force kill existing daemon PID ${pid}: ${killErr.message}`);
|
|
1111
|
+
return exitAndReturn(1);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
exited = waitForProcessExitSync(pid, DAEMON_FORCE_KILL_WAIT_MS);
|
|
1115
|
+
}
|
|
1116
|
+
if (!exited) {
|
|
1117
|
+
logError(`Existing daemon PID ${pid} is still running after force restart; please stop it manually.`);
|
|
684
1118
|
return exitAndReturn(1);
|
|
685
1119
|
}
|
|
686
1120
|
} catch (checkErr) {
|
|
@@ -688,7 +1122,9 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
688
1122
|
return exitAndReturn(1);
|
|
689
1123
|
}
|
|
690
1124
|
log("Removing lock file after force stop");
|
|
691
|
-
|
|
1125
|
+
if (existsSyncFn(LOCK_FILE)) {
|
|
1126
|
+
unlinkSyncFn(LOCK_FILE);
|
|
1127
|
+
}
|
|
692
1128
|
} else {
|
|
693
1129
|
logError(`Daemon already running with PID ${pid}`);
|
|
694
1130
|
return exitAndReturn(1);
|
|
@@ -878,8 +1314,12 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
878
1314
|
"x-conductor-backends": SUPPORTED_BACKENDS.join(","),
|
|
879
1315
|
"x-conductor-version": cliVersion,
|
|
880
1316
|
};
|
|
1317
|
+
const advertisedCapabilities = ["project_path_validation"];
|
|
881
1318
|
if (ptyTaskCapabilityEnabled) {
|
|
882
|
-
|
|
1319
|
+
advertisedCapabilities.push("pty_task", "terminal_snapshot");
|
|
1320
|
+
}
|
|
1321
|
+
if (advertisedCapabilities.length > 0) {
|
|
1322
|
+
extraHeaders["x-conductor-capabilities"] = advertisedCapabilities.join(",");
|
|
883
1323
|
}
|
|
884
1324
|
const client = createWebSocketClient(sdkConfig, {
|
|
885
1325
|
extraHeaders,
|
|
@@ -937,14 +1377,31 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
937
1377
|
|
|
938
1378
|
void (async () => {
|
|
939
1379
|
try {
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1380
|
+
ALLOW_CLI_LIST = await filterRuntimeSupportedAllowCliList(RAW_ALLOW_CLI_LIST, {
|
|
1381
|
+
configFilePath: config.CONFIG_FILE,
|
|
1382
|
+
});
|
|
943
1383
|
} catch (error) {
|
|
944
|
-
|
|
1384
|
+
ALLOW_CLI_LIST = {};
|
|
1385
|
+
logError(`Failed to filter configured backends: ${error?.message || error}`);
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
try {
|
|
1389
|
+
const advertisedBackends = await listAdvertisedBackends(ALLOW_CLI_LIST, {
|
|
1390
|
+
configFilePath: config.CONFIG_FILE,
|
|
1391
|
+
});
|
|
1392
|
+
SUPPORTED_BACKENDS = advertisedBackends.supportedBackends;
|
|
1393
|
+
SUPPORTED_BACKEND_RUNTIME_MAP = advertisedBackends.runtimeBackendMap;
|
|
1394
|
+
if (advertisedBackends.discoveryError) {
|
|
1395
|
+
logError(`Failed to discover external backends: ${advertisedBackends.discoveryError?.message || advertisedBackends.discoveryError}`);
|
|
1396
|
+
}
|
|
1397
|
+
} catch (error) {
|
|
1398
|
+
SUPPORTED_BACKENDS = [];
|
|
1399
|
+
SUPPORTED_BACKEND_RUNTIME_MAP = {};
|
|
1400
|
+
logError(`Failed to resolve advertised backends: ${error?.message || error}`);
|
|
945
1401
|
}
|
|
946
1402
|
|
|
947
1403
|
extraHeaders["x-conductor-backends"] = SUPPORTED_BACKENDS.join(",");
|
|
1404
|
+
extraHeaders["x-conductor-backend-runtime-map"] = serializeRuntimeBackendMap(SUPPORTED_BACKEND_RUNTIME_MAP);
|
|
948
1405
|
if (typeof client?.setExtraHeaders === "function") {
|
|
949
1406
|
client.setExtraHeaders(extraHeaders);
|
|
950
1407
|
}
|
|
@@ -1581,6 +2038,30 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
1581
2038
|
});
|
|
1582
2039
|
}
|
|
1583
2040
|
|
|
2041
|
+
function reportTaskWorktreeCleanupResult({
|
|
2042
|
+
requestId,
|
|
2043
|
+
taskId,
|
|
2044
|
+
worktreeBranch = null,
|
|
2045
|
+
removedPath = null,
|
|
2046
|
+
cleaned = true,
|
|
2047
|
+
error = null,
|
|
2048
|
+
}) {
|
|
2049
|
+
if (!requestId || !taskId) return Promise.resolve();
|
|
2050
|
+
return client.sendJson({
|
|
2051
|
+
type: "task_worktree_cleanup_result",
|
|
2052
|
+
payload: {
|
|
2053
|
+
request_id: String(requestId),
|
|
2054
|
+
task_id: String(taskId),
|
|
2055
|
+
daemon_host: AGENT_NAME || os.hostname(),
|
|
2056
|
+
worktree_branch: worktreeBranch || undefined,
|
|
2057
|
+
removed_path: removedPath || undefined,
|
|
2058
|
+
cleaned: Boolean(cleaned),
|
|
2059
|
+
error: error ? String(error) : null,
|
|
2060
|
+
cleaned_at: new Date().toISOString(),
|
|
2061
|
+
},
|
|
2062
|
+
});
|
|
2063
|
+
}
|
|
2064
|
+
|
|
1584
2065
|
function sendPtyTransportStatus(payload) {
|
|
1585
2066
|
return client.sendJson({
|
|
1586
2067
|
type: "pty_transport_status",
|
|
@@ -2202,7 +2683,9 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
2202
2683
|
rejectCreatePtyTaskDuringShutdown(payload);
|
|
2203
2684
|
return;
|
|
2204
2685
|
}
|
|
2205
|
-
let taskDir =
|
|
2686
|
+
let taskDir =
|
|
2687
|
+
normalizeOptionalString(launchConfig.cwd) ||
|
|
2688
|
+
boundPath;
|
|
2206
2689
|
if (!taskDir) {
|
|
2207
2690
|
const now = new Date();
|
|
2208
2691
|
const dayDir = path.join(WORKSPACE_ROOT, formatWorkspaceDate(now));
|
|
@@ -2255,22 +2738,7 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
2255
2738
|
logError(`Failed to report agent_command_ack(create_pty_task) for ${taskId}: ${err?.message || err}`);
|
|
2256
2739
|
});
|
|
2257
2740
|
|
|
2258
|
-
const env =
|
|
2259
|
-
...process.env,
|
|
2260
|
-
...launchSpec.env,
|
|
2261
|
-
CONDUCTOR_PROJECT_ID: projectId,
|
|
2262
|
-
CONDUCTOR_TASK_ID: taskId,
|
|
2263
|
-
CONDUCTOR_PTY_SESSION_ID: ptySessionId,
|
|
2264
|
-
};
|
|
2265
|
-
if (config.CONFIG_FILE) {
|
|
2266
|
-
env.CONDUCTOR_CONFIG = config.CONFIG_FILE;
|
|
2267
|
-
}
|
|
2268
|
-
if (AGENT_TOKEN) {
|
|
2269
|
-
env.CONDUCTOR_AGENT_TOKEN = AGENT_TOKEN;
|
|
2270
|
-
}
|
|
2271
|
-
if (BACKEND_HTTP) {
|
|
2272
|
-
env.CONDUCTOR_BACKEND_URL = BACKEND_HTTP;
|
|
2273
|
-
}
|
|
2741
|
+
const env = buildPtyTaskEnv(process.env, launchSpec.env);
|
|
2274
2742
|
|
|
2275
2743
|
const logPath = path.join(launchSpec.cwd, "conductor-terminal.log");
|
|
2276
2744
|
let logStream;
|
|
@@ -2360,6 +2828,119 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
2360
2828
|
}
|
|
2361
2829
|
}
|
|
2362
2830
|
|
|
2831
|
+
async function handleCleanupTaskWorktree(payload) {
|
|
2832
|
+
const taskId = payload?.task_id ? String(payload.task_id) : "";
|
|
2833
|
+
const projectId = payload?.project_id ? String(payload.project_id) : "";
|
|
2834
|
+
const requestId = payload?.request_id ? String(payload.request_id) : "";
|
|
2835
|
+
const forceCleanup = payload?.force === true;
|
|
2836
|
+
const launchConfig = normalizeLaunchConfig(payload?.launch_config);
|
|
2837
|
+
|
|
2838
|
+
if (!taskId || !projectId || !requestId) {
|
|
2839
|
+
logError(`Invalid cleanup_task_worktree payload: ${JSON.stringify(payload)}`);
|
|
2840
|
+
sendAgentCommandAck({
|
|
2841
|
+
requestId,
|
|
2842
|
+
taskId,
|
|
2843
|
+
eventType: "cleanup_task_worktree",
|
|
2844
|
+
accepted: false,
|
|
2845
|
+
}).catch(() => {});
|
|
2846
|
+
return;
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
const worktreeConfig = parseTaskWorktreeLaunchConfig(launchConfig);
|
|
2850
|
+
if (!worktreeConfig) {
|
|
2851
|
+
sendAgentCommandAck({
|
|
2852
|
+
requestId,
|
|
2853
|
+
taskId,
|
|
2854
|
+
eventType: "cleanup_task_worktree",
|
|
2855
|
+
accepted: false,
|
|
2856
|
+
}).catch(() => {});
|
|
2857
|
+
await reportTaskWorktreeCleanupResult({
|
|
2858
|
+
requestId,
|
|
2859
|
+
taskId,
|
|
2860
|
+
cleaned: false,
|
|
2861
|
+
error: "Task does not use an isolated worktree",
|
|
2862
|
+
}).catch((error) => {
|
|
2863
|
+
logError(`Failed to report task_worktree_cleanup_result for ${taskId}: ${error?.message || error}`);
|
|
2864
|
+
});
|
|
2865
|
+
return;
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
sendAgentCommandAck({
|
|
2869
|
+
requestId,
|
|
2870
|
+
taskId,
|
|
2871
|
+
eventType: "cleanup_task_worktree",
|
|
2872
|
+
accepted: true,
|
|
2873
|
+
}).catch((error) => {
|
|
2874
|
+
logError(`Failed to report agent_command_ack(cleanup_task_worktree) for ${taskId}: ${error?.message || error}`);
|
|
2875
|
+
});
|
|
2876
|
+
|
|
2877
|
+
if (activeTaskProcesses.has(taskId) || activePtySessions.has(taskId)) {
|
|
2878
|
+
await reportTaskWorktreeCleanupResult({
|
|
2879
|
+
requestId,
|
|
2880
|
+
taskId,
|
|
2881
|
+
worktreeBranch: worktreeConfig.worktreeBranch,
|
|
2882
|
+
cleaned: false,
|
|
2883
|
+
error: "Task is still active",
|
|
2884
|
+
}).catch((error) => {
|
|
2885
|
+
logError(`Failed to report task_worktree_cleanup_result for ${taskId}: ${error?.message || error}`);
|
|
2886
|
+
});
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
const worktreeRoot = buildTaskWorktreeRoot(
|
|
2891
|
+
worktreeConfig.projectWorkspacePath,
|
|
2892
|
+
worktreeConfig.worktreeId,
|
|
2893
|
+
);
|
|
2894
|
+
const worktreeCwd = resolveTaskWorktreeCwd(worktreeRoot, worktreeConfig.projectRelativePath);
|
|
2895
|
+
const statusCwd = existsSyncFn(worktreeCwd) ? worktreeCwd : worktreeRoot;
|
|
2896
|
+
|
|
2897
|
+
try {
|
|
2898
|
+
if (existsSyncFn(worktreeRoot)) {
|
|
2899
|
+
if (!forceCleanup) {
|
|
2900
|
+
const { stdout } = await runSpawnProcess(
|
|
2901
|
+
"git",
|
|
2902
|
+
["-C", statusCwd, "status", "--porcelain"],
|
|
2903
|
+
{ cwd: statusCwd },
|
|
2904
|
+
);
|
|
2905
|
+
if (stdout.trim()) {
|
|
2906
|
+
throw new Error("Worktree has uncommitted changes");
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
await runSpawnProcess(
|
|
2910
|
+
"git",
|
|
2911
|
+
[
|
|
2912
|
+
"-C",
|
|
2913
|
+
worktreeConfig.projectRepoRoot,
|
|
2914
|
+
"worktree",
|
|
2915
|
+
"remove",
|
|
2916
|
+
...(forceCleanup ? ["--force"] : []),
|
|
2917
|
+
worktreeRoot,
|
|
2918
|
+
],
|
|
2919
|
+
{ cwd: worktreeConfig.projectRepoRoot },
|
|
2920
|
+
);
|
|
2921
|
+
}
|
|
2922
|
+
|
|
2923
|
+
await reportTaskWorktreeCleanupResult({
|
|
2924
|
+
requestId,
|
|
2925
|
+
taskId,
|
|
2926
|
+
worktreeBranch: worktreeConfig.worktreeBranch,
|
|
2927
|
+
removedPath: worktreeRoot,
|
|
2928
|
+
cleaned: true,
|
|
2929
|
+
});
|
|
2930
|
+
} catch (error) {
|
|
2931
|
+
await reportTaskWorktreeCleanupResult({
|
|
2932
|
+
requestId,
|
|
2933
|
+
taskId,
|
|
2934
|
+
worktreeBranch: worktreeConfig.worktreeBranch,
|
|
2935
|
+
removedPath: worktreeRoot,
|
|
2936
|
+
cleaned: false,
|
|
2937
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2938
|
+
}).catch((reportError) => {
|
|
2939
|
+
logError(`Failed to report task_worktree_cleanup_result for ${taskId}: ${reportError?.message || reportError}`);
|
|
2940
|
+
});
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2363
2944
|
async function handleTerminalAttach(payload) {
|
|
2364
2945
|
const taskId = payload?.task_id ? String(payload.task_id) : "";
|
|
2365
2946
|
if (!taskId) return;
|
|
@@ -2610,6 +3191,23 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
2610
3191
|
rejectCreatePtyTaskDuringShutdown(event.payload);
|
|
2611
3192
|
return;
|
|
2612
3193
|
}
|
|
3194
|
+
if (event.type === "cleanup_task_worktree") {
|
|
3195
|
+
const requestId = event?.payload?.request_id ? String(event.payload.request_id) : "";
|
|
3196
|
+
const taskId = event?.payload?.task_id ? String(event.payload.task_id) : "";
|
|
3197
|
+
sendAgentCommandAck({
|
|
3198
|
+
requestId,
|
|
3199
|
+
taskId,
|
|
3200
|
+
eventType: "cleanup_task_worktree",
|
|
3201
|
+
accepted: false,
|
|
3202
|
+
}).catch(() => {});
|
|
3203
|
+
void reportTaskWorktreeCleanupResult({
|
|
3204
|
+
requestId,
|
|
3205
|
+
taskId,
|
|
3206
|
+
cleaned: false,
|
|
3207
|
+
error: "daemon shutting down",
|
|
3208
|
+
});
|
|
3209
|
+
return;
|
|
3210
|
+
}
|
|
2613
3211
|
}
|
|
2614
3212
|
|
|
2615
3213
|
if (event.type === "create_task") {
|
|
@@ -2626,6 +3224,10 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
2626
3224
|
void handleCreatePtyTask(event.payload);
|
|
2627
3225
|
return;
|
|
2628
3226
|
}
|
|
3227
|
+
if (event.type === "cleanup_task_worktree") {
|
|
3228
|
+
void handleCleanupTaskWorktree(event.payload);
|
|
3229
|
+
return;
|
|
3230
|
+
}
|
|
2629
3231
|
if (event.type === "stop_task") {
|
|
2630
3232
|
handleStopTask(event.payload);
|
|
2631
3233
|
return;
|
|
@@ -2652,6 +3254,10 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
2652
3254
|
}
|
|
2653
3255
|
if (event.type === "collect_logs") {
|
|
2654
3256
|
void handleCollectLogs(event.payload);
|
|
3257
|
+
return;
|
|
3258
|
+
}
|
|
3259
|
+
if (event.type === "validate_project_path") {
|
|
3260
|
+
void handleValidateProjectPath(event.payload);
|
|
2655
3261
|
}
|
|
2656
3262
|
}
|
|
2657
3263
|
|
|
@@ -2724,6 +3330,96 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
2724
3330
|
}
|
|
2725
3331
|
}
|
|
2726
3332
|
|
|
3333
|
+
async function handleValidateProjectPath(payload) {
|
|
3334
|
+
const requestId = payload?.request_id ? String(payload.request_id).trim() : "";
|
|
3335
|
+
const rawWorkspacePath = payload?.workspace_path ? String(payload.workspace_path).trim() : "";
|
|
3336
|
+
const validatedAt = new Date().toISOString();
|
|
3337
|
+
|
|
3338
|
+
if (!requestId || !rawWorkspacePath) {
|
|
3339
|
+
logError(`Invalid validate_project_path payload: ${JSON.stringify(payload)}`);
|
|
3340
|
+
return;
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
let result = {
|
|
3344
|
+
workspacePath: null,
|
|
3345
|
+
repoRoot: null,
|
|
3346
|
+
worktreeBranch: null,
|
|
3347
|
+
lastCommit: null,
|
|
3348
|
+
fileCount: null,
|
|
3349
|
+
error: null,
|
|
3350
|
+
errorCode: null,
|
|
3351
|
+
validatedAt,
|
|
3352
|
+
};
|
|
3353
|
+
|
|
3354
|
+
try {
|
|
3355
|
+
const resolvedPath = path.resolve(rawWorkspacePath);
|
|
3356
|
+
if (!existsSyncFn(resolvedPath)) {
|
|
3357
|
+
result = {
|
|
3358
|
+
...result,
|
|
3359
|
+
error: `Workspace path does not exist on daemon ${AGENT_NAME}: ${rawWorkspacePath}`,
|
|
3360
|
+
errorCode: "workspace_not_found",
|
|
3361
|
+
};
|
|
3362
|
+
} else if (!statSyncFn(resolvedPath).isDirectory()) {
|
|
3363
|
+
result = {
|
|
3364
|
+
...result,
|
|
3365
|
+
error: `Workspace path is not a directory on daemon ${AGENT_NAME}: ${rawWorkspacePath}`,
|
|
3366
|
+
errorCode: "workspace_not_directory",
|
|
3367
|
+
};
|
|
3368
|
+
} else {
|
|
3369
|
+
const snapshot = await Promise.resolve(resolveProjectSnapshotFn(resolvedPath));
|
|
3370
|
+
result = {
|
|
3371
|
+
...result,
|
|
3372
|
+
workspacePath:
|
|
3373
|
+
typeof snapshot?.projectRoot === "string" && snapshot.projectRoot.trim()
|
|
3374
|
+
? snapshot.projectRoot.trim()
|
|
3375
|
+
: resolvedPath,
|
|
3376
|
+
repoRoot:
|
|
3377
|
+
typeof snapshot?.repoRoot === "string" && snapshot.repoRoot.trim()
|
|
3378
|
+
? snapshot.repoRoot.trim()
|
|
3379
|
+
: null,
|
|
3380
|
+
worktreeBranch:
|
|
3381
|
+
typeof snapshot?.worktreeBranch === "string" && snapshot.worktreeBranch.trim()
|
|
3382
|
+
? snapshot.worktreeBranch.trim()
|
|
3383
|
+
: null,
|
|
3384
|
+
lastCommit:
|
|
3385
|
+
typeof snapshot?.lastCommit === "string" && snapshot.lastCommit.trim()
|
|
3386
|
+
? snapshot.lastCommit.trim()
|
|
3387
|
+
: null,
|
|
3388
|
+
fileCount:
|
|
3389
|
+
typeof snapshot?.fileCount === "number" && Number.isInteger(snapshot.fileCount)
|
|
3390
|
+
? snapshot.fileCount
|
|
3391
|
+
: null,
|
|
3392
|
+
};
|
|
3393
|
+
}
|
|
3394
|
+
} catch (error) {
|
|
3395
|
+
result = {
|
|
3396
|
+
...result,
|
|
3397
|
+
error: `Failed to validate workspace path on daemon ${AGENT_NAME}: ${error?.message || error}`,
|
|
3398
|
+
errorCode: "workspace_validation_failed",
|
|
3399
|
+
};
|
|
3400
|
+
}
|
|
3401
|
+
|
|
3402
|
+
try {
|
|
3403
|
+
await client.sendJson({
|
|
3404
|
+
type: "project_path_validated",
|
|
3405
|
+
payload: {
|
|
3406
|
+
request_id: requestId,
|
|
3407
|
+
daemon_host: AGENT_NAME,
|
|
3408
|
+
workspace_path: result.workspacePath,
|
|
3409
|
+
repo_root: result.repoRoot,
|
|
3410
|
+
worktree_branch: result.worktreeBranch,
|
|
3411
|
+
last_commit: result.lastCommit,
|
|
3412
|
+
file_count: result.fileCount,
|
|
3413
|
+
error: result.error,
|
|
3414
|
+
error_code: result.errorCode,
|
|
3415
|
+
validated_at: result.validatedAt,
|
|
3416
|
+
},
|
|
3417
|
+
});
|
|
3418
|
+
} catch (error) {
|
|
3419
|
+
logError(`Failed to report project_path_validated for ${rawWorkspacePath}: ${error?.message || error}`);
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
|
|
2727
3423
|
function handleStopTask(payload) {
|
|
2728
3424
|
const taskId = payload?.task_id;
|
|
2729
3425
|
if (!taskId) return;
|
|
@@ -2852,6 +3548,20 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
2852
3548
|
return null;
|
|
2853
3549
|
}
|
|
2854
3550
|
const project = await response.json();
|
|
3551
|
+
const daemonHost =
|
|
3552
|
+
(typeof project.daemon_host === "string" && project.daemon_host.trim()) ||
|
|
3553
|
+
(typeof project.daemonHost === "string" && project.daemonHost.trim()) ||
|
|
3554
|
+
"";
|
|
3555
|
+
const workspacePath =
|
|
3556
|
+
(typeof project.workspace_path === "string" && project.workspace_path.trim()) ||
|
|
3557
|
+
(typeof project.workspacePath === "string" && project.workspacePath.trim()) ||
|
|
3558
|
+
"";
|
|
3559
|
+
if (workspacePath) {
|
|
3560
|
+
if (daemonHost && daemonHost !== AGENT_NAME) {
|
|
3561
|
+
return null;
|
|
3562
|
+
}
|
|
3563
|
+
return workspacePath;
|
|
3564
|
+
}
|
|
2855
3565
|
if (!project.metadata) {
|
|
2856
3566
|
return null;
|
|
2857
3567
|
}
|
|
@@ -2974,8 +3684,10 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
2974
3684
|
}
|
|
2975
3685
|
|
|
2976
3686
|
async function resolveRestartCwd({
|
|
3687
|
+
taskId,
|
|
2977
3688
|
projectId,
|
|
2978
3689
|
preferredCwd = "",
|
|
3690
|
+
launchConfig = null,
|
|
2979
3691
|
backendType,
|
|
2980
3692
|
sessionId,
|
|
2981
3693
|
sourceSessionFilePath = "",
|
|
@@ -2985,6 +3697,15 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
2985
3697
|
return normalizedPreferredCwd;
|
|
2986
3698
|
}
|
|
2987
3699
|
|
|
3700
|
+
const worktreeCwd = await ensureTaskWorktree({
|
|
3701
|
+
taskId,
|
|
3702
|
+
projectId,
|
|
3703
|
+
launchConfig,
|
|
3704
|
+
});
|
|
3705
|
+
if (worktreeCwd) {
|
|
3706
|
+
return worktreeCwd;
|
|
3707
|
+
}
|
|
3708
|
+
|
|
2988
3709
|
const boundPath = await getProjectLocalPath(projectId);
|
|
2989
3710
|
if (boundPath) {
|
|
2990
3711
|
return boundPath;
|
|
@@ -2994,8 +3715,13 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
2994
3715
|
const normalizedSessionId = typeof sessionId === "string" ? sessionId.trim() : "";
|
|
2995
3716
|
if (normalizedSessionId && normalizedBackend && normalizedBackend !== "opencode") {
|
|
2996
3717
|
try {
|
|
3718
|
+
const configuredBackend = await resolveConfiguredRuntimeBackend(normalizedBackend, ALLOW_CLI_LIST, {
|
|
3719
|
+
configFilePath: config.CONFIG_FILE,
|
|
3720
|
+
});
|
|
3721
|
+
const resumeBackend = configuredBackend?.runtimeBackend ||
|
|
3722
|
+
await normalizeRuntimeBackendAlias(normalizedBackend, { configFilePath: config.CONFIG_FILE });
|
|
2997
3723
|
const resumeContext = await (deps.resolveResumeContext || resolveResumeContext)(
|
|
2998
|
-
|
|
3724
|
+
resumeBackend,
|
|
2999
3725
|
normalizedSessionId,
|
|
3000
3726
|
{
|
|
3001
3727
|
cwd: process.cwd(),
|
|
@@ -3036,6 +3762,7 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
3036
3762
|
request_id: requestIdRaw,
|
|
3037
3763
|
} =
|
|
3038
3764
|
payload || {};
|
|
3765
|
+
const launchConfig = normalizeLaunchConfig(payload?.launch_config);
|
|
3039
3766
|
const requestId = requestIdRaw ? String(requestIdRaw) : "";
|
|
3040
3767
|
|
|
3041
3768
|
if (!taskId || !projectId) {
|
|
@@ -3082,11 +3809,22 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
3082
3809
|
pendingTaskStarts.add(taskId);
|
|
3083
3810
|
let acceptedAckSent = false;
|
|
3084
3811
|
try {
|
|
3085
|
-
const
|
|
3812
|
+
const requestedBackend = normalizeRuntimeBackendName(backendType || SUPPORTED_BACKENDS[0]);
|
|
3813
|
+
const configuredBackend = await resolveConfiguredRuntimeBackend(requestedBackend, ALLOW_CLI_LIST, {
|
|
3086
3814
|
configFilePath: config.CONFIG_FILE,
|
|
3087
3815
|
});
|
|
3088
|
-
|
|
3089
|
-
|
|
3816
|
+
const effectiveBackend = configuredBackend?.runtimeBackend ||
|
|
3817
|
+
await normalizeRuntimeBackendAlias(requestedBackend, { configFilePath: config.CONFIG_FILE });
|
|
3818
|
+
const hasConfiguredEntry = Boolean(configuredBackend?.commandLine);
|
|
3819
|
+
const selectedBackend = configuredBackend?.commandLine
|
|
3820
|
+
? configuredBackend.requestedBackend
|
|
3821
|
+
: effectiveBackend;
|
|
3822
|
+
const isAdvertisedBackend = SUPPORTED_BACKENDS.includes(selectedBackend);
|
|
3823
|
+
const isAllowedExternalBackend =
|
|
3824
|
+
!isBuiltInRuntimeBackend(effectiveBackend) &&
|
|
3825
|
+
await isRuntimeSupportedBackend(effectiveBackend, { configFilePath: config.CONFIG_FILE });
|
|
3826
|
+
if (!isAdvertisedBackend || (!hasConfiguredEntry && !isAllowedExternalBackend)) {
|
|
3827
|
+
logError(`Unsupported backend: ${selectedBackend}. Supported: ${SUPPORTED_BACKENDS.join(", ")}`);
|
|
3090
3828
|
sendAgentCommandAck({
|
|
3091
3829
|
requestId,
|
|
3092
3830
|
taskId,
|
|
@@ -3100,7 +3838,7 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
3100
3838
|
task_id: taskId,
|
|
3101
3839
|
project_id: projectId,
|
|
3102
3840
|
status: "KILLED",
|
|
3103
|
-
summary: `Unsupported backend: ${
|
|
3841
|
+
summary: `Unsupported backend: ${selectedBackend}`,
|
|
3104
3842
|
},
|
|
3105
3843
|
})
|
|
3106
3844
|
.catch(() => {});
|
|
@@ -3117,10 +3855,10 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
3117
3855
|
});
|
|
3118
3856
|
acceptedAckSent = true;
|
|
3119
3857
|
|
|
3120
|
-
const cliCommand = ALLOW_CLI_LIST[effectiveBackend] || "";
|
|
3858
|
+
const cliCommand = ALLOW_CLI_LIST[selectedBackend] || ALLOW_CLI_LIST[effectiveBackend] || "";
|
|
3121
3859
|
|
|
3122
3860
|
log("");
|
|
3123
|
-
log(`Creating task ${taskId} for project ${projectId} (${
|
|
3861
|
+
log(`Creating task ${taskId} for project ${projectId} (${selectedBackend})`);
|
|
3124
3862
|
log(`CLI command: ${formatBackendLaunchCommand(cliCommand)}`);
|
|
3125
3863
|
client
|
|
3126
3864
|
.sendJson({
|
|
@@ -3145,9 +3883,18 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
3145
3883
|
let logPath;
|
|
3146
3884
|
let runTimestampPart = null;
|
|
3147
3885
|
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3886
|
+
const resolvedTaskWorkspace =
|
|
3887
|
+
(await ensureTaskWorktree({
|
|
3888
|
+
taskId,
|
|
3889
|
+
projectId,
|
|
3890
|
+
launchConfig,
|
|
3891
|
+
})) ||
|
|
3892
|
+
normalizeOptionalString(launchConfig.cwd) ||
|
|
3893
|
+
boundPath;
|
|
3894
|
+
|
|
3895
|
+
if (resolvedTaskWorkspace) {
|
|
3896
|
+
taskDir = resolvedTaskWorkspace;
|
|
3897
|
+
log(`Using task workspace: ${taskDir}`);
|
|
3151
3898
|
logPath = path.join(taskDir, "conductor.log");
|
|
3152
3899
|
} else {
|
|
3153
3900
|
const now = new Date();
|
|
@@ -3160,8 +3907,8 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
3160
3907
|
}
|
|
3161
3908
|
|
|
3162
3909
|
const args = [];
|
|
3163
|
-
if (
|
|
3164
|
-
args.push("--backend",
|
|
3910
|
+
if (selectedBackend) {
|
|
3911
|
+
args.push("--backend", selectedBackend);
|
|
3165
3912
|
}
|
|
3166
3913
|
if (initialContent) {
|
|
3167
3914
|
args.push("--prefill", initialContent);
|
|
@@ -3351,6 +4098,7 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
3351
4098
|
const normalizedTargetTaskId = targetTaskId ? String(targetTaskId) : "";
|
|
3352
4099
|
const normalizedProjectId = projectId ? String(projectId) : "";
|
|
3353
4100
|
const normalizedSourceSessionId = sourceSessionId ? String(sourceSessionId).trim() : "";
|
|
4101
|
+
const targetLaunchConfig = normalizeLaunchConfig(payload?.target_launch_config);
|
|
3354
4102
|
|
|
3355
4103
|
if (
|
|
3356
4104
|
!normalizedMode ||
|
|
@@ -3405,16 +4153,27 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
3405
4153
|
return;
|
|
3406
4154
|
}
|
|
3407
4155
|
|
|
3408
|
-
const
|
|
4156
|
+
const requestedBackend = normalizeRuntimeBackendName(targetBackendType || sourceBackendType || SUPPORTED_BACKENDS[0]);
|
|
4157
|
+
const configuredBackend = await resolveConfiguredRuntimeBackend(requestedBackend, ALLOW_CLI_LIST, {
|
|
3409
4158
|
configFilePath: config.CONFIG_FILE,
|
|
3410
4159
|
});
|
|
3411
|
-
|
|
4160
|
+
const effectiveBackend = configuredBackend?.runtimeBackend ||
|
|
4161
|
+
await normalizeRuntimeBackendAlias(requestedBackend, { configFilePath: config.CONFIG_FILE });
|
|
4162
|
+
const hasConfiguredEntry = Boolean(configuredBackend?.commandLine);
|
|
4163
|
+
const selectedBackend = configuredBackend?.commandLine
|
|
4164
|
+
? configuredBackend.requestedBackend
|
|
4165
|
+
: effectiveBackend;
|
|
4166
|
+
const isAdvertisedBackend = SUPPORTED_BACKENDS.includes(selectedBackend);
|
|
4167
|
+
const isAllowedExternalBackend =
|
|
4168
|
+
!isBuiltInRuntimeBackend(effectiveBackend) &&
|
|
4169
|
+
await isRuntimeSupportedBackend(effectiveBackend, { configFilePath: config.CONFIG_FILE });
|
|
4170
|
+
if (!isAdvertisedBackend || (!hasConfiguredEntry && !isAllowedExternalBackend)) {
|
|
3412
4171
|
reportRestartFailure({
|
|
3413
4172
|
taskId: normalizedTargetTaskId,
|
|
3414
4173
|
projectId: normalizedProjectId,
|
|
3415
4174
|
requestId,
|
|
3416
4175
|
mode: normalizedMode,
|
|
3417
|
-
error: new Error(`Unsupported backend: ${
|
|
4176
|
+
error: new Error(`Unsupported backend: ${selectedBackend}`),
|
|
3418
4177
|
});
|
|
3419
4178
|
return;
|
|
3420
4179
|
}
|
|
@@ -3430,7 +4189,16 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
3430
4189
|
});
|
|
3431
4190
|
return;
|
|
3432
4191
|
}
|
|
3433
|
-
|
|
4192
|
+
const normalizedSourceBackend = normalizeRuntimeBackendName(sourceBackendType);
|
|
4193
|
+
const configuredSourceBackend = await resolveConfiguredRuntimeBackend(normalizedSourceBackend, ALLOW_CLI_LIST, {
|
|
4194
|
+
configFilePath: config.CONFIG_FILE,
|
|
4195
|
+
});
|
|
4196
|
+
const sourceRuntimeBackend = configuredSourceBackend?.runtimeBackend ||
|
|
4197
|
+
await normalizeRuntimeBackendAlias(normalizedSourceBackend, { configFilePath: config.CONFIG_FILE });
|
|
4198
|
+
const sourceSelectedBackend = configuredSourceBackend?.commandLine
|
|
4199
|
+
? configuredSourceBackend.requestedBackend
|
|
4200
|
+
: sourceRuntimeBackend;
|
|
4201
|
+
if (selectedBackend !== sourceSelectedBackend) {
|
|
3434
4202
|
reportRestartFailure({
|
|
3435
4203
|
taskId: normalizedTargetTaskId,
|
|
3436
4204
|
projectId: normalizedProjectId,
|
|
@@ -3451,23 +4219,32 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
3451
4219
|
logError(`Failed to report agent_command_ack(restart_task) for ${normalizedTargetTaskId}: ${err?.message || err}`);
|
|
3452
4220
|
});
|
|
3453
4221
|
|
|
4222
|
+
const normalizedSourceBackend = normalizeRuntimeBackendName(sourceBackendType);
|
|
4223
|
+
const configuredSourceBackend = await resolveConfiguredRuntimeBackend(normalizedSourceBackend, ALLOW_CLI_LIST, {
|
|
4224
|
+
configFilePath: config.CONFIG_FILE,
|
|
4225
|
+
});
|
|
4226
|
+
const sourceRuntimeBackend = configuredSourceBackend?.runtimeBackend ||
|
|
4227
|
+
await normalizeRuntimeBackendAlias(normalizedSourceBackend, { configFilePath: config.CONFIG_FILE });
|
|
4228
|
+
|
|
3454
4229
|
let resolvedResumeSessionId = normalizedSourceSessionId;
|
|
3455
4230
|
let resolvedResumeCwd = "";
|
|
3456
4231
|
try {
|
|
3457
4232
|
if (normalizedMode === "bridge_to_new_task" || normalizedMode === "fork_to_new_task") {
|
|
3458
4233
|
const sourceResumeCwd = await resolveRestartCwd({
|
|
4234
|
+
taskId: normalizedTargetTaskId,
|
|
3459
4235
|
projectId: normalizedProjectId,
|
|
3460
4236
|
backendType: sourceBackendType,
|
|
4237
|
+
launchConfig: targetLaunchConfig,
|
|
3461
4238
|
sessionId: normalizedSourceSessionId,
|
|
3462
4239
|
sourceSessionFilePath: sourceSessionFilePath ? String(sourceSessionFilePath) : "",
|
|
3463
4240
|
});
|
|
3464
4241
|
const bridgeSession = await getBridgeSessionHelper();
|
|
3465
4242
|
const bridgeResult = await bridgeSession({
|
|
3466
|
-
sourceTool:
|
|
4243
|
+
sourceTool: sourceRuntimeBackend,
|
|
3467
4244
|
sourceSessionId: normalizedSourceSessionId,
|
|
3468
4245
|
sourceSessionPath: sourceSessionFilePath ? String(sourceSessionFilePath) : undefined,
|
|
3469
4246
|
sourceSessionInfo: {
|
|
3470
|
-
tool:
|
|
4247
|
+
tool: sourceRuntimeBackend,
|
|
3471
4248
|
sessionId: normalizedSourceSessionId,
|
|
3472
4249
|
path: sourceSessionFilePath ? String(sourceSessionFilePath) : undefined,
|
|
3473
4250
|
cwd: sourceResumeCwd || undefined,
|
|
@@ -3477,15 +4254,19 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
3477
4254
|
});
|
|
3478
4255
|
resolvedResumeSessionId = bridgeResult.sessionId;
|
|
3479
4256
|
resolvedResumeCwd = await resolveRestartCwd({
|
|
4257
|
+
taskId: normalizedTargetTaskId,
|
|
3480
4258
|
projectId: normalizedProjectId,
|
|
3481
4259
|
preferredCwd: bridgeResult.cwd,
|
|
4260
|
+
launchConfig: targetLaunchConfig,
|
|
3482
4261
|
backendType: effectiveBackend,
|
|
3483
4262
|
sessionId: bridgeResult.sessionId,
|
|
3484
4263
|
sourceSessionFilePath: sourceSessionFilePath ? String(sourceSessionFilePath) : "",
|
|
3485
4264
|
});
|
|
3486
4265
|
} else if (normalizedMode === "resume_inplace") {
|
|
3487
4266
|
resolvedResumeCwd = await resolveRestartCwd({
|
|
4267
|
+
taskId: normalizedTargetTaskId,
|
|
3488
4268
|
projectId: normalizedProjectId,
|
|
4269
|
+
launchConfig: targetLaunchConfig,
|
|
3489
4270
|
backendType: effectiveBackend,
|
|
3490
4271
|
sessionId: normalizedSourceSessionId,
|
|
3491
4272
|
sourceSessionFilePath: sourceSessionFilePath ? String(sourceSessionFilePath) : "",
|
|
@@ -3517,11 +4298,11 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
3517
4298
|
return;
|
|
3518
4299
|
}
|
|
3519
4300
|
|
|
3520
|
-
const cliCommand = ALLOW_CLI_LIST[effectiveBackend] || "";
|
|
4301
|
+
const cliCommand = ALLOW_CLI_LIST[selectedBackend] || ALLOW_CLI_LIST[effectiveBackend] || "";
|
|
3521
4302
|
|
|
3522
4303
|
log("");
|
|
3523
4304
|
log(
|
|
3524
|
-
`Restarting task ${normalizedTargetTaskId} from ${normalizedSourceTaskId} (${normalizedMode} -> ${
|
|
4305
|
+
`Restarting task ${normalizedTargetTaskId} from ${normalizedSourceTaskId} (${normalizedMode} -> ${selectedBackend})`,
|
|
3525
4306
|
);
|
|
3526
4307
|
log(`CLI command: ${formatBackendLaunchCommand(cliCommand)}`);
|
|
3527
4308
|
|
|
@@ -3570,8 +4351,8 @@ export function startDaemon(config = {}, deps = {}) {
|
|
|
3570
4351
|
}
|
|
3571
4352
|
|
|
3572
4353
|
const args = [];
|
|
3573
|
-
if (
|
|
3574
|
-
args.push("--backend",
|
|
4354
|
+
if (selectedBackend) {
|
|
4355
|
+
args.push("--backend", selectedBackend);
|
|
3575
4356
|
}
|
|
3576
4357
|
args.push("--resume", resolvedResumeSessionId);
|
|
3577
4358
|
args.push("--");
|