@ricsam/r5d-worker 0.0.80 → 0.0.82
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/README.md +7 -1
- package/dist/cjs/main.cjs +331 -98
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-command-sync-policy.cjs +3 -3
- package/dist/cjs/workspace-git-sync.cjs +377 -122
- package/dist/cjs/workspace-merge-projection.cjs +392 -0
- package/dist/mjs/main.mjs +329 -98
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/workspace-command-sync-policy.mjs +3 -3
- package/dist/mjs/workspace-git-sync.mjs +381 -122
- package/dist/mjs/workspace-merge-projection.mjs +355 -0
- package/dist/types/main.d.ts +63 -0
- package/dist/types/working-tree-mirror.d.ts +1 -2
- package/dist/types/workspace-command-sync-policy.d.ts +4 -3
- package/dist/types/workspace-git-sync.d.ts +6 -0
- package/dist/types/workspace-merge-projection.d.ts +42 -0
- package/package.json +1 -1
package/dist/mjs/main.mjs
CHANGED
|
@@ -99,6 +99,10 @@ const DEFAULT_READ_MAX_BYTES = 5e4;
|
|
|
99
99
|
const MAX_LINE_LENGTH = 2e3;
|
|
100
100
|
const WORKSPACE_GIT_QUIET_MS = 5e3;
|
|
101
101
|
const WORKSPACE_GIT_PERIODIC_MS = 6e4;
|
|
102
|
+
const PTY_INPUT_BUSY_GRACE_MS = 3e3;
|
|
103
|
+
const PTY_FOREGROUND_POLL_MS = 1e3;
|
|
104
|
+
const PTY_FOREGROUND_IDLE_ENABLED = process.env.R5D_PTY_FOREGROUND_IDLE !== "0";
|
|
105
|
+
const PTY_TMP_PATH_PREFIX = "r5d-worker-tmp://";
|
|
102
106
|
const activeProcesses = /* @__PURE__ */ new Map();
|
|
103
107
|
const credentialBearingProcessGroups = /* @__PURE__ */ new Map();
|
|
104
108
|
const credentialBearingProcessGroupTargets = /* @__PURE__ */ new Map();
|
|
@@ -156,6 +160,106 @@ function assertWorkerChildAdmission(input) {
|
|
|
156
160
|
throw new StaleWorkerAdmissionError();
|
|
157
161
|
}
|
|
158
162
|
}
|
|
163
|
+
function pathIsInsideRoot(rootPath, candidatePath) {
|
|
164
|
+
const relative = path.relative(rootPath, candidatePath);
|
|
165
|
+
return relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
|
|
166
|
+
}
|
|
167
|
+
function resolvePtyEnvFilePath(requestedPath, temporaryRoot = os.tmpdir()) {
|
|
168
|
+
const canonicalTemporaryRoot = fs.realpathSync.native(temporaryRoot);
|
|
169
|
+
if (requestedPath.startsWith(PTY_TMP_PATH_PREFIX)) {
|
|
170
|
+
const filename = requestedPath.slice(PTY_TMP_PATH_PREFIX.length);
|
|
171
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/.test(filename)) {
|
|
172
|
+
throw new Error("PTY environment file temporary token must contain one safe filename");
|
|
173
|
+
}
|
|
174
|
+
return path.join(canonicalTemporaryRoot, filename);
|
|
175
|
+
}
|
|
176
|
+
if (!path.isAbsolute(requestedPath)) {
|
|
177
|
+
throw new Error(`PTY environment file path must be absolute or use ${PTY_TMP_PATH_PREFIX}`);
|
|
178
|
+
}
|
|
179
|
+
const resolvedPath = path.resolve(requestedPath);
|
|
180
|
+
const canonicalParent = fs.realpathSync.native(path.dirname(resolvedPath));
|
|
181
|
+
const canonicalPath = path.join(canonicalParent, path.basename(resolvedPath));
|
|
182
|
+
if (!pathIsInsideRoot(canonicalTemporaryRoot, canonicalPath) || canonicalPath === canonicalTemporaryRoot) {
|
|
183
|
+
throw new Error(`PTY environment file path must be inside ${canonicalTemporaryRoot}`);
|
|
184
|
+
}
|
|
185
|
+
return canonicalPath;
|
|
186
|
+
}
|
|
187
|
+
function removePtyEnvFiles(paths) {
|
|
188
|
+
for (const filePath of paths) {
|
|
189
|
+
try {
|
|
190
|
+
fs.rmSync(filePath, { force: true });
|
|
191
|
+
} catch (error) {
|
|
192
|
+
process.stderr.write(
|
|
193
|
+
`[r5d-worker] failed to remove PTY environment file ${filePath}: ${error instanceof Error ? error.message : String(error)}
|
|
194
|
+
`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function stagePtyEnvFiles(envFiles, temporaryRoot = os.tmpdir()) {
|
|
200
|
+
const staged = { paths: [], resolvedByRequestedPath: /* @__PURE__ */ new Map() };
|
|
201
|
+
try {
|
|
202
|
+
for (const envFile of envFiles ?? []) {
|
|
203
|
+
if (!envFile || typeof envFile.path !== "string" || typeof envFile.content !== "string") {
|
|
204
|
+
throw new Error("PTY environment files require string path and content values");
|
|
205
|
+
}
|
|
206
|
+
if (envFile.mode !== void 0 && envFile.mode !== 384) {
|
|
207
|
+
throw new Error("PTY environment files must use mode 0600");
|
|
208
|
+
}
|
|
209
|
+
const resolvedPath = resolvePtyEnvFilePath(envFile.path, temporaryRoot);
|
|
210
|
+
if (staged.resolvedByRequestedPath.has(envFile.path) || staged.paths.includes(resolvedPath)) {
|
|
211
|
+
throw new Error(`Duplicate PTY environment file path: ${envFile.path}`);
|
|
212
|
+
}
|
|
213
|
+
const descriptor = fs.openSync(
|
|
214
|
+
resolvedPath,
|
|
215
|
+
fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | (typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0),
|
|
216
|
+
384
|
|
217
|
+
);
|
|
218
|
+
staged.paths.push(resolvedPath);
|
|
219
|
+
staged.resolvedByRequestedPath.set(envFile.path, resolvedPath);
|
|
220
|
+
try {
|
|
221
|
+
fs.writeFileSync(descriptor, envFile.content, "utf8");
|
|
222
|
+
fs.fchmodSync(descriptor, 384);
|
|
223
|
+
fs.fsyncSync(descriptor);
|
|
224
|
+
} finally {
|
|
225
|
+
fs.closeSync(descriptor);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return staged;
|
|
229
|
+
} catch (error) {
|
|
230
|
+
removePtyEnvFiles(staged.paths);
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function resolvePtyEnvFileReferences(env, staged) {
|
|
235
|
+
return Object.fromEntries(Object.entries(env).map(([name, value]) => [name, staged.resolvedByRequestedPath.get(value) ?? value]));
|
|
236
|
+
}
|
|
237
|
+
function workerCommandHasWorkspaceEffect(message) {
|
|
238
|
+
return message.workspaceEffect !== "none";
|
|
239
|
+
}
|
|
240
|
+
function workerPtyIsWorkspaceBusy(pty, now = Date.now(), foregroundIdleEnabled = PTY_FOREGROUND_IDLE_ENABLED) {
|
|
241
|
+
return !foregroundIdleEnabled || pty.foregroundBusy || now - pty.lastInputAt < PTY_INPUT_BUSY_GRACE_MS;
|
|
242
|
+
}
|
|
243
|
+
function parseLinuxPtyForegroundBusy(stat) {
|
|
244
|
+
const commandEnd = stat.lastIndexOf(")");
|
|
245
|
+
if (commandEnd < 0) return null;
|
|
246
|
+
const fields = stat.slice(commandEnd + 1).trim().split(/\s+/);
|
|
247
|
+
const processGroup = Number(fields[2]);
|
|
248
|
+
const foregroundProcessGroup = Number(fields[5]);
|
|
249
|
+
if (!Number.isSafeInteger(processGroup) || processGroup <= 0 || !Number.isSafeInteger(foregroundProcessGroup)) return null;
|
|
250
|
+
return foregroundProcessGroup !== processGroup;
|
|
251
|
+
}
|
|
252
|
+
const workerPtyTestHarness = {
|
|
253
|
+
temporaryPathPrefix: PTY_TMP_PATH_PREFIX,
|
|
254
|
+
inputBusyGraceMs: PTY_INPUT_BUSY_GRACE_MS,
|
|
255
|
+
resolveEnvFilePath: resolvePtyEnvFilePath,
|
|
256
|
+
stageEnvFiles: stagePtyEnvFiles,
|
|
257
|
+
removeEnvFiles: removePtyEnvFiles,
|
|
258
|
+
resolveEnvFileReferences: resolvePtyEnvFileReferences,
|
|
259
|
+
commandHasWorkspaceEffect: workerCommandHasWorkspaceEffect,
|
|
260
|
+
ptyIsWorkspaceBusy: workerPtyIsWorkspaceBusy,
|
|
261
|
+
parseLinuxForegroundBusy: parseLinuxPtyForegroundBusy
|
|
262
|
+
};
|
|
159
263
|
function defaultConfigPath() {
|
|
160
264
|
return path.join(os.homedir(), ".config", "r5d", "r5dctl", "config.json");
|
|
161
265
|
}
|
|
@@ -633,7 +737,7 @@ function preparePlanEnvForShell(input) {
|
|
|
633
737
|
fs.mkdirSync(plansDir, { recursive: true });
|
|
634
738
|
return planEnv(input.planRoot, input.target, input.activePlanId);
|
|
635
739
|
}
|
|
636
|
-
async function verifyR5dctlAuth(baseUrl, token) {
|
|
740
|
+
async function verifyR5dctlAuth(baseUrl, token, workerLabel) {
|
|
637
741
|
const statusUrl = new URL("/api/r5dctl/auth/status", baseUrl);
|
|
638
742
|
let response;
|
|
639
743
|
try {
|
|
@@ -662,13 +766,16 @@ async function verifyR5dctlAuth(baseUrl, token) {
|
|
|
662
766
|
throw new WorkerServerUnavailableError(`Server unavailable at ${baseUrl} (${response.status})${detail}`);
|
|
663
767
|
}
|
|
664
768
|
throw new Error(
|
|
665
|
-
`Authentication failed against ${baseUrl} (${response.status})${detail}. Run \`r5dctl auth login --base-url ${baseUrl}\` or pass
|
|
769
|
+
`Authentication failed against ${baseUrl} (${response.status})${detail}. Run \`r5dctl auth login --base-url ${baseUrl} --worker-label ${workerLabel}\` or pass that exact label-bound worker token.`
|
|
666
770
|
);
|
|
667
771
|
}
|
|
668
772
|
function resolveCredentials(options, config) {
|
|
669
773
|
const token = options.token ?? process.env.R5D_WORKER_TOKEN ?? process.env.R5D_TOKEN ?? process.env.R5DCTL_TOKEN ?? config.token ?? options.apiKey ?? process.env.R5D_API_KEY ?? process.env.R5DCTL_API_KEY ?? config.apiKey;
|
|
670
774
|
if (!token) {
|
|
671
|
-
|
|
775
|
+
const workerLabel = options.label ?? "<label>";
|
|
776
|
+
throw new Error(
|
|
777
|
+
`Authentication required. Run \`r5dctl auth login --worker-label ${workerLabel}\` or set R5D_WORKER_TOKEN to that label-bound token.`
|
|
778
|
+
);
|
|
672
779
|
}
|
|
673
780
|
return {
|
|
674
781
|
baseUrl: normalizeBaseUrl(
|
|
@@ -2334,7 +2441,9 @@ async function executeCommand(input) {
|
|
|
2334
2441
|
});
|
|
2335
2442
|
spawnedProcess = subprocess;
|
|
2336
2443
|
credentialBearingProcessGroups.set(subprocess.pid, subprocess);
|
|
2337
|
-
|
|
2444
|
+
if (workerCommandHasWorkspaceEffect(input.message)) {
|
|
2445
|
+
credentialBearingProcessGroupTargets.set(subprocess.pid, input.resolvedTarget.target);
|
|
2446
|
+
}
|
|
2338
2447
|
activeProcesses.set(input.message.runId, {
|
|
2339
2448
|
process: subprocess,
|
|
2340
2449
|
target: input.resolvedTarget.target,
|
|
@@ -2345,7 +2454,8 @@ async function executeCommand(input) {
|
|
|
2345
2454
|
argv: input.message.argv,
|
|
2346
2455
|
command: input.message.argv.join(" "),
|
|
2347
2456
|
cwd,
|
|
2348
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2457
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2458
|
+
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {}
|
|
2349
2459
|
});
|
|
2350
2460
|
if (input.message.timeoutMs) {
|
|
2351
2461
|
timeout = setTimeout(() => {
|
|
@@ -2431,7 +2541,9 @@ async function executeStreamingCommand(input) {
|
|
|
2431
2541
|
});
|
|
2432
2542
|
spawnedProcess = subprocess;
|
|
2433
2543
|
credentialBearingProcessGroups.set(subprocess.pid, subprocess);
|
|
2434
|
-
|
|
2544
|
+
if (workerCommandHasWorkspaceEffect(input.message)) {
|
|
2545
|
+
credentialBearingProcessGroupTargets.set(subprocess.pid, input.resolvedTarget.target);
|
|
2546
|
+
}
|
|
2435
2547
|
activeProcesses.set(input.message.runId, {
|
|
2436
2548
|
process: subprocess,
|
|
2437
2549
|
target: input.resolvedTarget.target,
|
|
@@ -2444,7 +2556,8 @@ async function executeStreamingCommand(input) {
|
|
|
2444
2556
|
command: input.message.command,
|
|
2445
2557
|
cwd,
|
|
2446
2558
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2447
|
-
...interactive ? { interactive: true, stdin: subprocess.stdin } : {}
|
|
2559
|
+
...interactive ? { interactive: true, stdin: subprocess.stdin } : {},
|
|
2560
|
+
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {}
|
|
2448
2561
|
});
|
|
2449
2562
|
started = true;
|
|
2450
2563
|
sendWorkerMessage(input.ws, {
|
|
@@ -2491,7 +2604,8 @@ async function executeStreamingCommand(input) {
|
|
|
2491
2604
|
runId: input.message.runId,
|
|
2492
2605
|
exitCode,
|
|
2493
2606
|
durationMs: Date.now() - startedAt,
|
|
2494
|
-
...timedOut ? { timedOut: true } : {}
|
|
2607
|
+
...timedOut ? { timedOut: true } : {},
|
|
2608
|
+
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {}
|
|
2495
2609
|
};
|
|
2496
2610
|
pendingProcessTerminals.set(input.message.runId, terminal);
|
|
2497
2611
|
sendWorkerMessage(input.ws, terminal);
|
|
@@ -2503,7 +2617,8 @@ async function executeStreamingCommand(input) {
|
|
|
2503
2617
|
type: "exec_error",
|
|
2504
2618
|
runId: input.message.runId,
|
|
2505
2619
|
error: message,
|
|
2506
|
-
durationMs: Date.now() - startedAt
|
|
2620
|
+
durationMs: Date.now() - startedAt,
|
|
2621
|
+
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {}
|
|
2507
2622
|
};
|
|
2508
2623
|
pendingProcessTerminals.set(input.message.runId, terminal);
|
|
2509
2624
|
sendWorkerMessage(input.ws, terminal);
|
|
@@ -2584,7 +2699,8 @@ function buildActiveProcessReports() {
|
|
|
2584
2699
|
command: active.command,
|
|
2585
2700
|
...active.cwd ? { cwd: active.cwd } : {},
|
|
2586
2701
|
startedAt: active.startedAt,
|
|
2587
|
-
...active.interactive ? { interactive: true } : {}
|
|
2702
|
+
...active.interactive ? { interactive: true } : {},
|
|
2703
|
+
...active.workspaceEffect === "none" ? { workspaceEffect: "none" } : {}
|
|
2588
2704
|
}));
|
|
2589
2705
|
}
|
|
2590
2706
|
function sendActiveProcessReport(ws) {
|
|
@@ -2596,8 +2712,13 @@ function sendActiveProcessReport(ws) {
|
|
|
2596
2712
|
const PTY_BRIDGE_SCRIPT = String.raw`
|
|
2597
2713
|
const readline = require("node:readline");
|
|
2598
2714
|
const nodePty = require("node-pty");
|
|
2715
|
+
const fs = require("node:fs");
|
|
2716
|
+
const { execFile } = require("node:child_process");
|
|
2599
2717
|
|
|
2600
2718
|
let ptyProcess = null;
|
|
2719
|
+
let foregroundTimer = null;
|
|
2720
|
+
let foregroundPollInFlight = false;
|
|
2721
|
+
let lastForegroundBusy = null;
|
|
2601
2722
|
|
|
2602
2723
|
function send(message, callback) {
|
|
2603
2724
|
process.stdout.write(JSON.stringify(message) + "\n", callback);
|
|
@@ -2607,6 +2728,46 @@ function decode(data) {
|
|
|
2607
2728
|
return Buffer.from(data, "base64").toString("utf8");
|
|
2608
2729
|
}
|
|
2609
2730
|
|
|
2731
|
+
function emitForeground(busy) {
|
|
2732
|
+
if (busy === lastForegroundBusy) return;
|
|
2733
|
+
lastForegroundBusy = busy;
|
|
2734
|
+
send({ type: "foreground", busy });
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
function linuxForegroundBusy(pid) {
|
|
2738
|
+
const stat = fs.readFileSync("/proc/" + pid + "/stat", "utf8");
|
|
2739
|
+
const commandEnd = stat.lastIndexOf(")");
|
|
2740
|
+
if (commandEnd < 0) throw new Error("invalid /proc stat");
|
|
2741
|
+
const fields = stat.slice(commandEnd + 1).trim().split(/\s+/);
|
|
2742
|
+
const processGroup = Number(fields[2]);
|
|
2743
|
+
const foregroundProcessGroup = Number(fields[5]);
|
|
2744
|
+
if (!Number.isSafeInteger(processGroup) || processGroup <= 0 || !Number.isSafeInteger(foregroundProcessGroup)) {
|
|
2745
|
+
throw new Error("invalid process group fields");
|
|
2746
|
+
}
|
|
2747
|
+
return foregroundProcessGroup !== processGroup;
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2750
|
+
function pollForeground() {
|
|
2751
|
+
if (!ptyProcess || !Number.isSafeInteger(ptyProcess.pid) || ptyProcess.pid <= 0) return;
|
|
2752
|
+
if (process.platform === "linux") {
|
|
2753
|
+
try {
|
|
2754
|
+
emitForeground(linuxForegroundBusy(ptyProcess.pid));
|
|
2755
|
+
} catch {
|
|
2756
|
+
emitForeground(true);
|
|
2757
|
+
}
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
if (process.platform !== "darwin" || foregroundPollInFlight) return;
|
|
2761
|
+
foregroundPollInFlight = true;
|
|
2762
|
+
const pid = ptyProcess.pid;
|
|
2763
|
+
execFile("/bin/ps", ["-o", "tpgid=", "-p", String(pid)], (error, stdout) => {
|
|
2764
|
+
foregroundPollInFlight = false;
|
|
2765
|
+
if (!ptyProcess || ptyProcess.pid !== pid) return;
|
|
2766
|
+
const foregroundProcessGroup = Number(String(stdout).trim());
|
|
2767
|
+
emitForeground(Boolean(error) || !Number.isSafeInteger(foregroundProcessGroup) || foregroundProcessGroup !== pid);
|
|
2768
|
+
});
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2610
2771
|
const rl = readline.createInterface({ input: process.stdin });
|
|
2611
2772
|
|
|
2612
2773
|
rl.on("line", (line) => {
|
|
@@ -2625,9 +2786,14 @@ rl.on("line", (line) => {
|
|
|
2625
2786
|
send({ type: "output", data: Buffer.from(data, "utf8").toString("base64") });
|
|
2626
2787
|
});
|
|
2627
2788
|
ptyProcess.onExit((event) => {
|
|
2789
|
+
if (foregroundTimer) clearInterval(foregroundTimer);
|
|
2790
|
+
foregroundTimer = null;
|
|
2628
2791
|
send({ type: "exit", exitCode: event.exitCode, signal: event.signal }, () => process.exit(0));
|
|
2629
2792
|
});
|
|
2630
|
-
send({ type: "opened" });
|
|
2793
|
+
send({ type: "opened", pid: ptyProcess.pid });
|
|
2794
|
+
pollForeground();
|
|
2795
|
+
foregroundTimer = setInterval(pollForeground, ${PTY_FOREGROUND_POLL_MS});
|
|
2796
|
+
foregroundTimer.unref();
|
|
2631
2797
|
return;
|
|
2632
2798
|
}
|
|
2633
2799
|
|
|
@@ -2720,7 +2886,11 @@ function createNodePtyBridge(options) {
|
|
|
2720
2886
|
};
|
|
2721
2887
|
const handleEvent = (event) => {
|
|
2722
2888
|
if (event.type === "opened") {
|
|
2723
|
-
options.onOpened();
|
|
2889
|
+
options.onOpened(event.pid);
|
|
2890
|
+
return;
|
|
2891
|
+
}
|
|
2892
|
+
if (event.type === "foreground") {
|
|
2893
|
+
options.onForeground(event.busy);
|
|
2724
2894
|
return;
|
|
2725
2895
|
}
|
|
2726
2896
|
if (event.type === "output") {
|
|
@@ -2801,6 +2971,9 @@ function createNodePtyBridge(options) {
|
|
|
2801
2971
|
}
|
|
2802
2972
|
};
|
|
2803
2973
|
}
|
|
2974
|
+
const workerPtyBridgeTestHarness = {
|
|
2975
|
+
create: createNodePtyBridge
|
|
2976
|
+
};
|
|
2804
2977
|
function resolveHostShell(command, platform = process.platform) {
|
|
2805
2978
|
if (platform === "win32") {
|
|
2806
2979
|
const file2 = process.env.COMSPEC || "powershell.exe";
|
|
@@ -2836,64 +3009,90 @@ async function openPty(input) {
|
|
|
2836
3009
|
}
|
|
2837
3010
|
});
|
|
2838
3011
|
input.assertAdmission();
|
|
2839
|
-
const
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
}
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
}
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
3012
|
+
const stagedEnvFiles = stagePtyEnvFiles(input.message.envFiles);
|
|
3013
|
+
let envFilesRemoved = false;
|
|
3014
|
+
const cleanupEnvFiles = () => {
|
|
3015
|
+
if (envFilesRemoved) return;
|
|
3016
|
+
envFilesRemoved = true;
|
|
3017
|
+
removePtyEnvFiles(stagedEnvFiles.paths);
|
|
3018
|
+
};
|
|
3019
|
+
try {
|
|
3020
|
+
const ptyProcess = createNodePtyBridge({
|
|
3021
|
+
file: shell.file,
|
|
3022
|
+
args: shell.args,
|
|
3023
|
+
ptyOptions: {
|
|
3024
|
+
name: "xterm-256color",
|
|
3025
|
+
cols: Math.max(1, Math.min(Math.floor(input.message.cols || 80), 500)),
|
|
3026
|
+
rows: Math.max(1, Math.min(Math.floor(input.message.rows || 24), 500)),
|
|
3027
|
+
cwd: input.resolvedTarget.rootPath,
|
|
3028
|
+
env: workerChildProcessEnvironment([
|
|
3029
|
+
githubProcessEnv(),
|
|
3030
|
+
resolvePtyEnvFileReferences(input.message.env ?? {}, stagedEnvFiles),
|
|
3031
|
+
planProcessEnv,
|
|
3032
|
+
targetProcessEnv,
|
|
3033
|
+
shell.env ?? {}
|
|
3034
|
+
])
|
|
3035
|
+
},
|
|
3036
|
+
onOpened: () => {
|
|
3037
|
+
sendWorkerMessage(input.ws, {
|
|
3038
|
+
type: "pty_opened",
|
|
3039
|
+
requestId: input.message.requestId,
|
|
3040
|
+
ptyId: input.message.ptyId
|
|
3041
|
+
});
|
|
3042
|
+
},
|
|
3043
|
+
onForeground: (busy) => {
|
|
3044
|
+
const activePty = activePtys.get(input.message.ptyId);
|
|
3045
|
+
if (activePty && (busy || input.message.command === void 0)) activePty.foregroundBusy = busy;
|
|
3046
|
+
},
|
|
3047
|
+
onOutput: (data) => {
|
|
3048
|
+
outputCoalescer.push(data);
|
|
3049
|
+
},
|
|
3050
|
+
onExit: (event) => {
|
|
3051
|
+
outputCoalescer.flush();
|
|
3052
|
+
input.releaseWorkspaceMutation?.();
|
|
3053
|
+
activePtys.delete(input.message.ptyId);
|
|
3054
|
+
cleanupEnvFiles();
|
|
3055
|
+
input.onTerminal?.();
|
|
3056
|
+
sendWorkerMessage(input.ws, {
|
|
3057
|
+
type: "pty_exit",
|
|
3058
|
+
ptyId: input.message.ptyId,
|
|
3059
|
+
exitCode: event.exitCode,
|
|
3060
|
+
signal: event.signal
|
|
3061
|
+
});
|
|
3062
|
+
},
|
|
3063
|
+
onError: (error) => {
|
|
3064
|
+
outputCoalescer.flush();
|
|
3065
|
+
input.releaseWorkspaceMutation?.();
|
|
3066
|
+
activePtys.delete(input.message.ptyId);
|
|
3067
|
+
cleanupEnvFiles();
|
|
3068
|
+
input.onTerminal?.();
|
|
3069
|
+
sendWorkerMessage(input.ws, {
|
|
3070
|
+
type: "pty_error",
|
|
3071
|
+
requestId: input.message.requestId,
|
|
3072
|
+
ptyId: input.message.ptyId,
|
|
3073
|
+
error: error.message
|
|
3074
|
+
});
|
|
3075
|
+
},
|
|
3076
|
+
onTerminationError: (error) => {
|
|
3077
|
+
sendWorkerMessage(input.ws, {
|
|
3078
|
+
type: "pty_error",
|
|
3079
|
+
requestId: input.message.requestId,
|
|
3080
|
+
ptyId: input.message.ptyId,
|
|
3081
|
+
error: error.message
|
|
3082
|
+
});
|
|
3083
|
+
}
|
|
3084
|
+
});
|
|
3085
|
+
activePtys.set(input.message.ptyId, {
|
|
3086
|
+
...ptyProcess,
|
|
3087
|
+
target: input.resolvedTarget.target,
|
|
3088
|
+
foregroundBusy: true,
|
|
3089
|
+
lastInputAt: 0,
|
|
3090
|
+
...input.releaseWorkspaceMutation ? { releaseWorkspaceMutation: input.releaseWorkspaceMutation } : {}
|
|
3091
|
+
});
|
|
3092
|
+
} catch (error) {
|
|
3093
|
+
cleanupEnvFiles();
|
|
3094
|
+
throw error;
|
|
3095
|
+
}
|
|
2897
3096
|
}
|
|
2898
3097
|
function writePty(ws, message) {
|
|
2899
3098
|
const ptyProcess = activePtys.get(message.ptyId);
|
|
@@ -2905,6 +3104,7 @@ function writePty(ws, message) {
|
|
|
2905
3104
|
});
|
|
2906
3105
|
return;
|
|
2907
3106
|
}
|
|
3107
|
+
ptyProcess.lastInputAt = Date.now();
|
|
2908
3108
|
ptyProcess.write(message.data);
|
|
2909
3109
|
}
|
|
2910
3110
|
function resizePty(message) {
|
|
@@ -3025,7 +3225,7 @@ async function startWorker(options) {
|
|
|
3025
3225
|
process.stdout.write(`[r5d-worker] server: ${baseUrl}
|
|
3026
3226
|
`);
|
|
3027
3227
|
runGit(["--version"]);
|
|
3028
|
-
await verifyR5dctlAuth(baseUrl, token);
|
|
3228
|
+
await verifyR5dctlAuth(baseUrl, token, label);
|
|
3029
3229
|
const initializedWorkspaceState = await workspaceSyncSingleFlight.runExclusive(() => {
|
|
3030
3230
|
if (!startupProjectSnapshotRecoveryCompleted) {
|
|
3031
3231
|
const snapshotRecovery = recoverStaleProjectWorktreeSnapshots({
|
|
@@ -3342,13 +3542,24 @@ async function startWorker(options) {
|
|
|
3342
3542
|
});
|
|
3343
3543
|
};
|
|
3344
3544
|
const activeWorkspaceMutationTargets = () => [
|
|
3345
|
-
...Array.from(activeProcesses.values()
|
|
3545
|
+
...Array.from(activeProcesses.values()).filter(workerCommandHasWorkspaceEffect).map(({ target }) => target),
|
|
3346
3546
|
...credentialBearingProcessGroupTargets.values(),
|
|
3347
3547
|
...workspaceSyncPriorityProcessTargets.values(),
|
|
3348
3548
|
...workspaceSyncPriorityOperationTargets.values(),
|
|
3349
|
-
...Array.from(activePtys.values()
|
|
3549
|
+
...Array.from(activePtys.values()).filter((pty) => workerPtyIsWorkspaceBusy(pty)).map(({ target }) => target),
|
|
3350
3550
|
...workspaceSyncPriorityPtyTargets.values()
|
|
3351
3551
|
];
|
|
3552
|
+
let visibleWorkspaceMutationEpoch = 0;
|
|
3553
|
+
const projectWorkspaceMutationEpochs = /* @__PURE__ */ new Map();
|
|
3554
|
+
const recordVisibleWorkspaceMutation = (target) => {
|
|
3555
|
+
if (target.type === "workspace") {
|
|
3556
|
+
if (target.rootProfile === "visible_projects") visibleWorkspaceMutationEpoch += 1;
|
|
3557
|
+
return;
|
|
3558
|
+
}
|
|
3559
|
+
const key = projectBranchKey(target.projectId, target.branchName);
|
|
3560
|
+
projectWorkspaceMutationEpochs.set(key, (projectWorkspaceMutationEpochs.get(key) ?? 0) + 1);
|
|
3561
|
+
};
|
|
3562
|
+
const projectWorkspaceMutationToken = (projectId, branchName) => `${visibleWorkspaceMutationEpoch}:${projectWorkspaceMutationEpochs.get(projectBranchKey(projectId, branchName)) ?? 0}`;
|
|
3352
3563
|
const canonicalWorkspaceMutationIsActive = (targets) => targets.some((target) => target.type === "workspace" && target.rootProfile === "canonical_sync");
|
|
3353
3564
|
const projectBranchMountActivityBusy = (projectId, branchName, branchPath) => {
|
|
3354
3565
|
const activeTargets = activeWorkspaceMutationTargets();
|
|
@@ -3385,6 +3596,7 @@ async function startWorker(options) {
|
|
|
3385
3596
|
preserveLocalOnInitialOuterAbsence: creatorLocalIncarnation,
|
|
3386
3597
|
preserveLocalOnHydrationBasisChange: preservesCheckoutPathMove,
|
|
3387
3598
|
busy: () => projectBranchMountBusy(project.projectId, branch.branchName, branchPath),
|
|
3599
|
+
mutationToken: () => projectWorkspaceMutationToken(project.projectId, branch.branchName),
|
|
3388
3600
|
busyForRecovery: () => projectBranchMountActivityBusy(project.projectId, branch.branchName, branchPath)
|
|
3389
3601
|
});
|
|
3390
3602
|
mounts.push({
|
|
@@ -3398,6 +3610,7 @@ async function startWorker(options) {
|
|
|
3398
3610
|
preserveLocalOnInitialOuterAbsence: creatorLocalIncarnation,
|
|
3399
3611
|
preserveLocalOnHydrationBasisChange: preservesCheckoutPathMove,
|
|
3400
3612
|
busy: () => projectBranchMountBusy(project.projectId, branch.branchName, branchPath),
|
|
3613
|
+
mutationToken: () => projectWorkspaceMutationToken(project.projectId, branch.branchName),
|
|
3401
3614
|
busyForRecovery: () => projectBranchMountActivityBusy(project.projectId, branch.branchName, branchPath)
|
|
3402
3615
|
});
|
|
3403
3616
|
}
|
|
@@ -3418,6 +3631,7 @@ async function startWorker(options) {
|
|
|
3418
3631
|
const activeTargets = activeWorkspaceMutationTargets();
|
|
3419
3632
|
return canonicalWorkspaceMutationIsActive(activeTargets) || hasActiveVisibleProjectsWorkspaceTarget(activeTargets);
|
|
3420
3633
|
},
|
|
3634
|
+
mutationToken: () => String(visibleWorkspaceMutationEpoch),
|
|
3421
3635
|
busyForRecovery: () => {
|
|
3422
3636
|
const activeTargets = activeWorkspaceMutationTargets();
|
|
3423
3637
|
return canonicalWorkspaceMutationIsActive(activeTargets) || hasActiveVisibleProjectsWorkspaceTarget(activeTargets);
|
|
@@ -3775,6 +3989,7 @@ async function startWorker(options) {
|
|
|
3775
3989
|
localChangesDiscarded: false,
|
|
3776
3990
|
...result.conflictPaths ? { conflictPaths: result.conflictPaths } : {},
|
|
3777
3991
|
...result.conflictSnapshotRefs ? { conflictSnapshotRefs: result.conflictSnapshotRefs } : {},
|
|
3992
|
+
...result.conflictKind ? { conflictKind: result.conflictKind } : {},
|
|
3778
3993
|
...result.error ? { error: result.error } : {}
|
|
3779
3994
|
};
|
|
3780
3995
|
};
|
|
@@ -3841,10 +4056,7 @@ async function startWorker(options) {
|
|
|
3841
4056
|
};
|
|
3842
4057
|
}
|
|
3843
4058
|
const outerRemediation = input.trigger.type === "remediation" || input.trigger.type === "remediation_confirm";
|
|
3844
|
-
const
|
|
3845
|
-
(mount) => !(mount.deleteWhenSourceMissing && !fs.existsSync(mount.sourcePath)) && mount.busy?.()
|
|
3846
|
-
);
|
|
3847
|
-
const observedProjectHeads = outerRemediation || ordinaryCycleHasBusyLiveMount ? [] : observeProjectHeadsBeforeOuterWorkspace({ allowNonFastForward: false, failClosed: false });
|
|
4059
|
+
const observedProjectHeads = outerRemediation ? [] : observeProjectHeadsBeforeOuterWorkspace({ allowNonFastForward: false, failClosed: false });
|
|
3848
4060
|
const inboundMoveMountIds = new Set(
|
|
3849
4061
|
observedProjectHeads.flatMap(
|
|
3850
4062
|
({ projectId, observations }) => observations.filter(({ shouldMove }) => shouldMove).map(({ branchName }) => projectMountId(projectId, branchName))
|
|
@@ -4000,7 +4212,8 @@ async function startWorker(options) {
|
|
|
4000
4212
|
);
|
|
4001
4213
|
workspaceAutomaticTimer.unref();
|
|
4002
4214
|
};
|
|
4003
|
-
const markWorkspaceDirty = (trigger, force = false) => {
|
|
4215
|
+
const markWorkspaceDirty = (trigger, force = false, target) => {
|
|
4216
|
+
if (target) recordVisibleWorkspaceMutation(target);
|
|
4004
4217
|
scheduleAutomaticWorkspaceSync(trigger, force ? 0 : WORKSPACE_GIT_QUIET_MS);
|
|
4005
4218
|
};
|
|
4006
4219
|
const targetMayMutateVisibleWorkspace = (target) => target.type === "project" || target.rootProfile === "visible_projects";
|
|
@@ -4261,7 +4474,7 @@ async function startWorker(options) {
|
|
|
4261
4474
|
sourcePath: recoverySourceOverrides.get(mount.id) ?? mount.sourcePath,
|
|
4262
4475
|
busy: mount.busyForRecovery ?? mount.busy
|
|
4263
4476
|
}));
|
|
4264
|
-
recoverWorkspaceGitHydration(workspaceShadowRoot, recoveryMounts);
|
|
4477
|
+
recoverWorkspaceGitHydration(workspaceShadowRoot, recoveryMounts, { preserveStaleBases: true });
|
|
4265
4478
|
for (const project of message.projects) {
|
|
4266
4479
|
for (const { branchName } of project.preserveOnlyBranches) {
|
|
4267
4480
|
if (stillPendingCreatedBranches.has(pendingCreatedBranchKey(project.projectId, branchName))) {
|
|
@@ -4437,7 +4650,8 @@ async function startWorker(options) {
|
|
|
4437
4650
|
capabilities: {
|
|
4438
4651
|
updateClis: true,
|
|
4439
4652
|
browserPortForwarding: true,
|
|
4440
|
-
execStdinV1: true
|
|
4653
|
+
execStdinV1: true,
|
|
4654
|
+
ptyEnvFilesV1: true
|
|
4441
4655
|
},
|
|
4442
4656
|
projectRoot: projectsRoot,
|
|
4443
4657
|
artifactRoot,
|
|
@@ -4812,6 +5026,9 @@ async function startWorker(options) {
|
|
|
4812
5026
|
});
|
|
4813
5027
|
return;
|
|
4814
5028
|
}
|
|
5029
|
+
if (active.workspaceEffect !== "none" && targetMayMutateVisibleWorkspace(active.target)) {
|
|
5030
|
+
recordVisibleWorkspaceMutation(active.target);
|
|
5031
|
+
}
|
|
4815
5032
|
try {
|
|
4816
5033
|
let bytesWritten = 0;
|
|
4817
5034
|
if (message.data !== void 0 && message.data.length > 0) {
|
|
@@ -4822,8 +5039,8 @@ async function startWorker(options) {
|
|
|
4822
5039
|
await active.stdin.end();
|
|
4823
5040
|
active.stdin = void 0;
|
|
4824
5041
|
}
|
|
4825
|
-
if (targetMayMutateVisibleWorkspace(active.target)) {
|
|
4826
|
-
markWorkspaceDirty({ type: "shell_inline", detail: `exec stdin ${message.runId}` });
|
|
5042
|
+
if (active.workspaceEffect !== "none" && targetMayMutateVisibleWorkspace(active.target)) {
|
|
5043
|
+
markWorkspaceDirty({ type: "shell_inline", detail: `exec stdin ${message.runId}` }, false, active.target);
|
|
4827
5044
|
}
|
|
4828
5045
|
sendAck({
|
|
4829
5046
|
result: {
|
|
@@ -4850,6 +5067,7 @@ async function startWorker(options) {
|
|
|
4850
5067
|
}
|
|
4851
5068
|
await reserveWorkspaceCommandAfterCurrentSync(message.target, workspaceSyncSingleFlight, () => {
|
|
4852
5069
|
workspaceSyncPriorityPtyTargets.set(message.ptyId, message.target);
|
|
5070
|
+
if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
|
|
4853
5071
|
});
|
|
4854
5072
|
let releaseWorkspaceMutation;
|
|
4855
5073
|
let mutationLeaseTransferred = false;
|
|
@@ -4867,7 +5085,7 @@ async function startWorker(options) {
|
|
|
4867
5085
|
...releaseWorkspaceMutation ? { releaseWorkspaceMutation } : {},
|
|
4868
5086
|
...targetMayMutateVisibleWorkspace(message.target) ? {
|
|
4869
5087
|
onTerminal: () => {
|
|
4870
|
-
markWorkspaceDirty({ type: "process_terminal", detail: `pty ${message.ptyId} completed` }, true);
|
|
5088
|
+
markWorkspaceDirty({ type: "process_terminal", detail: `pty ${message.ptyId} completed` }, true, message.target);
|
|
4871
5089
|
}
|
|
4872
5090
|
} : {}
|
|
4873
5091
|
});
|
|
@@ -4888,7 +5106,7 @@ async function startWorker(options) {
|
|
|
4888
5106
|
if (message.type === "pty_input") {
|
|
4889
5107
|
const activePty = activePtys.get(message.ptyId);
|
|
4890
5108
|
if (activePty && targetMayMutateVisibleWorkspace(activePty.target)) {
|
|
4891
|
-
markWorkspaceDirty({ type: "shell_inline", detail: `pty ${message.ptyId}` });
|
|
5109
|
+
markWorkspaceDirty({ type: "shell_inline", detail: `pty ${message.ptyId}` }, false, activePty.target);
|
|
4892
5110
|
}
|
|
4893
5111
|
writePty(ws, message);
|
|
4894
5112
|
return;
|
|
@@ -4901,7 +5119,7 @@ async function startWorker(options) {
|
|
|
4901
5119
|
const activePty = activePtys.get(message.ptyId);
|
|
4902
5120
|
closePty(message);
|
|
4903
5121
|
if (activePty && targetMayMutateVisibleWorkspace(activePty.target)) {
|
|
4904
|
-
markWorkspaceDirty({ type: "process_terminal", detail: `pty ${message.ptyId} closed` }, true);
|
|
5122
|
+
markWorkspaceDirty({ type: "process_terminal", detail: `pty ${message.ptyId} closed` }, true, activePty.target);
|
|
4905
5123
|
}
|
|
4906
5124
|
return;
|
|
4907
5125
|
}
|
|
@@ -4918,11 +5136,15 @@ async function startWorker(options) {
|
|
|
4918
5136
|
}
|
|
4919
5137
|
let result;
|
|
4920
5138
|
let targetReserved = false;
|
|
5139
|
+
const hasWorkspaceEffect = workerCommandHasWorkspaceEffect(message);
|
|
4921
5140
|
try {
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
5141
|
+
if (hasWorkspaceEffect) {
|
|
5142
|
+
await reserveWorkspaceCommandAfterCurrentSync(message.target, workspaceSyncSingleFlight, () => {
|
|
5143
|
+
workspaceSyncPriorityProcessTargets.set(message.runId, message.target);
|
|
5144
|
+
targetReserved = true;
|
|
5145
|
+
if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
|
|
5146
|
+
});
|
|
5147
|
+
}
|
|
4926
5148
|
const runCommand = async () => {
|
|
4927
5149
|
const resolvedTarget = resolveMessageTarget(message.target);
|
|
4928
5150
|
process.stdout.write(`[r5d-worker] exec ${message.runId}: ${message.argv.join(" ")}
|
|
@@ -4952,10 +5174,11 @@ async function startWorker(options) {
|
|
|
4952
5174
|
if (targetReserved) workspaceSyncPriorityProcessTargets.delete(message.runId);
|
|
4953
5175
|
}
|
|
4954
5176
|
ws.send(JSON.stringify(result));
|
|
4955
|
-
if (targetMayMutateVisibleWorkspace(message.target)) {
|
|
5177
|
+
if (hasWorkspaceEffect && targetMayMutateVisibleWorkspace(message.target)) {
|
|
4956
5178
|
markWorkspaceDirty(
|
|
4957
5179
|
{ type: "shell_inline", sessionId: message.sessionId, processRunId: message.runId, detail: "foreground command completed" },
|
|
4958
|
-
true
|
|
5180
|
+
true,
|
|
5181
|
+
message.target
|
|
4959
5182
|
);
|
|
4960
5183
|
}
|
|
4961
5184
|
return;
|
|
@@ -4975,6 +5198,7 @@ async function startWorker(options) {
|
|
|
4975
5198
|
requestId: message.requestId,
|
|
4976
5199
|
runId: message.runId
|
|
4977
5200
|
});
|
|
5201
|
+
const hasWorkspaceEffect = workerCommandHasWorkspaceEffect(message);
|
|
4978
5202
|
const runCommand = async () => {
|
|
4979
5203
|
try {
|
|
4980
5204
|
const resolvedTarget = resolveMessageTarget(message.target);
|
|
@@ -4991,7 +5215,7 @@ async function startWorker(options) {
|
|
|
4991
5215
|
assertAdmission: assertMessageAdmission
|
|
4992
5216
|
});
|
|
4993
5217
|
} finally {
|
|
4994
|
-
if (targetMayMutateVisibleWorkspace(message.target)) {
|
|
5218
|
+
if (hasWorkspaceEffect && targetMayMutateVisibleWorkspace(message.target)) {
|
|
4995
5219
|
markWorkspaceDirty(
|
|
4996
5220
|
{
|
|
4997
5221
|
type: "process_terminal",
|
|
@@ -4999,7 +5223,8 @@ async function startWorker(options) {
|
|
|
4999
5223
|
processRunId: message.runId,
|
|
5000
5224
|
detail: "process completed"
|
|
5001
5225
|
},
|
|
5002
|
-
true
|
|
5226
|
+
true,
|
|
5227
|
+
message.target
|
|
5003
5228
|
);
|
|
5004
5229
|
}
|
|
5005
5230
|
}
|
|
@@ -5007,10 +5232,13 @@ async function startWorker(options) {
|
|
|
5007
5232
|
const execution = (async () => {
|
|
5008
5233
|
let targetReserved = false;
|
|
5009
5234
|
try {
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5235
|
+
if (hasWorkspaceEffect) {
|
|
5236
|
+
await reserveWorkspaceCommandAfterCurrentSync(message.target, workspaceSyncSingleFlight, () => {
|
|
5237
|
+
workspaceSyncPriorityProcessTargets.set(message.runId, message.target);
|
|
5238
|
+
targetReserved = true;
|
|
5239
|
+
if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
|
|
5240
|
+
});
|
|
5241
|
+
}
|
|
5014
5242
|
await runWorkspaceCommand(message.target, workspaceSyncSingleFlight, runCommand);
|
|
5015
5243
|
} finally {
|
|
5016
5244
|
if (targetReserved) workspaceSyncPriorityProcessTargets.delete(message.runId);
|
|
@@ -5033,6 +5261,7 @@ async function startWorker(options) {
|
|
|
5033
5261
|
if (reservesVisibleWorkspace) {
|
|
5034
5262
|
await reserveWorkspaceCommandAfterCurrentSync(message.target, workspaceSyncSingleFlight, () => {
|
|
5035
5263
|
workspaceSyncPriorityOperationTargets.set(message.requestId, message.target);
|
|
5264
|
+
if (mutatesVisibleWorkspace) recordVisibleWorkspaceMutation(message.target);
|
|
5036
5265
|
});
|
|
5037
5266
|
}
|
|
5038
5267
|
let dirtyTrigger;
|
|
@@ -5062,7 +5291,7 @@ async function startWorker(options) {
|
|
|
5062
5291
|
toolCallId: message.requestId,
|
|
5063
5292
|
...message.target.type === "project" ? { projectId: message.target.projectId, branchName: message.target.branchName } : {}
|
|
5064
5293
|
};
|
|
5065
|
-
markWorkspaceDirty(dirtyTrigger);
|
|
5294
|
+
markWorkspaceDirty(dirtyTrigger, false, message.target);
|
|
5066
5295
|
}
|
|
5067
5296
|
} catch (error) {
|
|
5068
5297
|
ws.send(
|
|
@@ -5222,5 +5451,7 @@ export {
|
|
|
5222
5451
|
syncSessionArtifacts,
|
|
5223
5452
|
workerChildProcessEnvironment,
|
|
5224
5453
|
workerGitSecurityTestHarness,
|
|
5454
|
+
workerPtyBridgeTestHarness,
|
|
5455
|
+
workerPtyTestHarness,
|
|
5225
5456
|
writeWorkerTextFile
|
|
5226
5457
|
};
|