@ricsam/r5d-worker 0.0.44 → 0.0.46
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/dist/cjs/main.cjs +361 -144
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-sync.cjs +187 -49
- package/dist/mjs/main.mjs +361 -144
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/workspace-sync.mjs +187 -49
- package/dist/types/main.d.ts +19 -8
- package/dist/types/workspace-sync.d.ts +3 -0
- package/package.json +1 -1
package/dist/mjs/main.mjs
CHANGED
|
@@ -246,7 +246,25 @@ async function readResponseText(response) {
|
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
248
|
const R5D_ARTIFACTS_DIR_REF = "$R5D_ARTIFACTS_DIR";
|
|
249
|
+
const R5D_PLANS_DIR_REF = "$R5D_PLANS_DIR";
|
|
250
|
+
const R5D_ACTIVE_PLAN_FILE_REF = "$R5D_ACTIVE_PLAN_FILE";
|
|
249
251
|
const R5D_PLANS_DIR_ENV = "R5D_PLANS_DIR";
|
|
252
|
+
const BUILT_IN_TOOL_PATH_REFS = [R5D_ARTIFACTS_DIR_REF, R5D_PLANS_DIR_REF, R5D_ACTIVE_PLAN_FILE_REF];
|
|
253
|
+
function parseBuiltInToolPath(inputPath) {
|
|
254
|
+
const normalized = inputPath.replace(/\\/g, "/");
|
|
255
|
+
const match = /^\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))(?:\/(.*))?$/.exec(normalized);
|
|
256
|
+
if (!match) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
const ref = `$${match[1] ?? match[2]}`;
|
|
260
|
+
if (!BUILT_IN_TOOL_PATH_REFS.includes(ref)) {
|
|
261
|
+
throw new Error(`Unsupported tool path variable "${ref}". Supported variables: ${BUILT_IN_TOOL_PATH_REFS.join(", ")}.`);
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
ref,
|
|
265
|
+
relativePath: match[3] ?? ""
|
|
266
|
+
};
|
|
267
|
+
}
|
|
250
268
|
function validateArtifactSessionId(sessionId) {
|
|
251
269
|
if (!/^(migration-)?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(sessionId)) {
|
|
252
270
|
throw new Error(`Invalid artifact session id: ${sessionId}`);
|
|
@@ -255,7 +273,7 @@ function validateArtifactSessionId(sessionId) {
|
|
|
255
273
|
function assertInsideRoot(rootPath, candidatePath, label) {
|
|
256
274
|
const relative = path.relative(rootPath, candidatePath);
|
|
257
275
|
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
258
|
-
throw new Error(`${label} escapes
|
|
276
|
+
throw new Error(`${label} escapes its allowed root`);
|
|
259
277
|
}
|
|
260
278
|
}
|
|
261
279
|
function sessionArtifactDir(artifactRoot, sessionId) {
|
|
@@ -288,7 +306,11 @@ function planEnv(planRoot, projectId, branchName, activePlanId) {
|
|
|
288
306
|
return env;
|
|
289
307
|
}
|
|
290
308
|
function isArtifactEnvPath(filePath) {
|
|
291
|
-
|
|
309
|
+
try {
|
|
310
|
+
return parseBuiltInToolPath(filePath)?.ref === R5D_ARTIFACTS_DIR_REF;
|
|
311
|
+
} catch {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
292
314
|
}
|
|
293
315
|
function artifactApiBasePath(projectId, branchName, sessionId) {
|
|
294
316
|
return `/preview-api/${encodeURIComponent(projectId)}/${encodeURIComponent(branchName)}/artifacts/${encodeURIComponent(sessionId)}`;
|
|
@@ -395,56 +417,72 @@ async function fetchSessionArtifactManifest(input) {
|
|
|
395
417
|
label: "artifact"
|
|
396
418
|
});
|
|
397
419
|
}
|
|
420
|
+
const sessionArtifactSyncQueues = /* @__PURE__ */ new Map();
|
|
398
421
|
async function syncSessionArtifacts(input) {
|
|
399
|
-
const
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
targetDir,
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
download: (artifact) => downloadRemoteSyncedFile({
|
|
406
|
-
url: artifactDownloadUrl(input.baseUrl, input.projectId, input.branchName, input.sessionId, artifact.filename),
|
|
407
|
-
token: input.token,
|
|
422
|
+
const queueKey = `${path.resolve(input.artifactRoot)}\0${input.sessionId}`;
|
|
423
|
+
const previous = sessionArtifactSyncQueues.get(queueKey);
|
|
424
|
+
const queued = (previous ? previous.catch(() => "") : Promise.resolve("")).then(async () => {
|
|
425
|
+
const targetDir = sessionArtifactDir(input.artifactRoot, input.sessionId);
|
|
426
|
+
const artifacts = await fetchSessionArtifactManifest(input);
|
|
427
|
+
return syncRemoteFileSet({
|
|
408
428
|
targetDir,
|
|
409
|
-
|
|
410
|
-
label: "
|
|
411
|
-
|
|
429
|
+
files: artifacts,
|
|
430
|
+
label: "Artifact path",
|
|
431
|
+
download: (artifact) => downloadRemoteSyncedFile({
|
|
432
|
+
url: artifactDownloadUrl(input.baseUrl, input.projectId, input.branchName, input.sessionId, artifact.filename),
|
|
433
|
+
token: input.token,
|
|
434
|
+
targetDir,
|
|
435
|
+
file: artifact,
|
|
436
|
+
label: "artifact"
|
|
437
|
+
})
|
|
438
|
+
});
|
|
412
439
|
});
|
|
440
|
+
sessionArtifactSyncQueues.set(queueKey, queued);
|
|
441
|
+
const release = () => {
|
|
442
|
+
if (sessionArtifactSyncQueues.get(queueKey) === queued) sessionArtifactSyncQueues.delete(queueKey);
|
|
443
|
+
};
|
|
444
|
+
void queued.then(release, release);
|
|
445
|
+
return queued;
|
|
413
446
|
}
|
|
414
|
-
async function
|
|
415
|
-
|
|
416
|
-
|
|
447
|
+
async function prepareBuiltInToolPaths(input) {
|
|
448
|
+
const parsed = parseBuiltInToolPath(input.filePath);
|
|
449
|
+
if (!parsed) {
|
|
450
|
+
return void 0;
|
|
417
451
|
}
|
|
418
|
-
if (
|
|
419
|
-
|
|
452
|
+
if (parsed.ref === R5D_ARTIFACTS_DIR_REF) {
|
|
453
|
+
if (input.access === "write") {
|
|
454
|
+
throw new Error(
|
|
455
|
+
`${R5D_ARTIFACTS_DIR_REF} attachments are read-only. Copy them into a project path before editing or creating files.`
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
if (!input.sessionId) {
|
|
459
|
+
throw new Error(`${R5D_ARTIFACTS_DIR_REF} paths require an active chat session`);
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
artifactsDir: await syncSessionArtifacts({
|
|
463
|
+
baseUrl: input.baseUrl,
|
|
464
|
+
token: input.token,
|
|
465
|
+
projectId: input.projectId,
|
|
466
|
+
branchName: input.branchName,
|
|
467
|
+
sessionId: input.sessionId,
|
|
468
|
+
artifactRoot: input.artifactRoot
|
|
469
|
+
})
|
|
470
|
+
};
|
|
420
471
|
}
|
|
421
|
-
|
|
422
|
-
|
|
472
|
+
const plansDir = projectPlanDir(input.planRoot, input.projectId, input.branchName);
|
|
473
|
+
fs.mkdirSync(plansDir, { recursive: true });
|
|
474
|
+
if (parsed.ref === R5D_PLANS_DIR_REF) {
|
|
475
|
+
return { plansDir };
|
|
423
476
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
token: input.token,
|
|
427
|
-
projectId: input.projectId,
|
|
428
|
-
branchName: input.branchName,
|
|
429
|
-
sessionId: input.sessionId,
|
|
430
|
-
artifactRoot: input.artifactRoot
|
|
431
|
-
});
|
|
432
|
-
const relativePath = path.posix.normalize(input.filePath.slice(`${R5D_ARTIFACTS_DIR_REF}/`.length));
|
|
433
|
-
if (!relativePath || relativePath === "." || relativePath.startsWith("../") || relativePath.includes("\0")) {
|
|
434
|
-
throw new Error(`Invalid artifact path: ${input.filePath}`);
|
|
477
|
+
if (!input.activePlanId) {
|
|
478
|
+
throw new Error(`${R5D_ACTIVE_PLAN_FILE_REF} paths require an active plan`);
|
|
435
479
|
}
|
|
436
|
-
|
|
437
|
-
assertInsideRoot(targetDir, absolutePath, "Artifact path");
|
|
480
|
+
validatePlanId(input.activePlanId);
|
|
438
481
|
return {
|
|
439
|
-
|
|
440
|
-
|
|
482
|
+
plansDir,
|
|
483
|
+
activePlanFile: path.join(plansDir, `${input.activePlanId}.plan.md`)
|
|
441
484
|
};
|
|
442
485
|
}
|
|
443
|
-
function rejectArtifactWritePath(filePath) {
|
|
444
|
-
if (isArtifactEnvPath(filePath)) {
|
|
445
|
-
throw new Error(`${R5D_ARTIFACTS_DIR_REF} attachments are read-only. Copy them into a project path before editing or creating files.`);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
486
|
async function prepareArtifactEnvForShell(input) {
|
|
449
487
|
if (!input.sessionId) {
|
|
450
488
|
return {};
|
|
@@ -595,10 +633,63 @@ function assertAllowedProjectPath(repoRelativePath, inputPath) {
|
|
|
595
633
|
throw new Error(`Invalid project file path: ${inputPath}`);
|
|
596
634
|
}
|
|
597
635
|
}
|
|
598
|
-
function
|
|
636
|
+
function assertPathInsideRealRoot(rootPath, candidatePath, label) {
|
|
637
|
+
const resolvedRoot = path.resolve(rootPath);
|
|
638
|
+
const resolvedCandidate = path.resolve(candidatePath);
|
|
639
|
+
assertInsideRoot(resolvedRoot, resolvedCandidate, label);
|
|
640
|
+
let existingPath = resolvedCandidate;
|
|
641
|
+
while (!fs.existsSync(existingPath)) {
|
|
642
|
+
const parentPath = path.dirname(existingPath);
|
|
643
|
+
if (parentPath === existingPath) {
|
|
644
|
+
throw new Error(`${label} root does not exist: ${rootPath}`);
|
|
645
|
+
}
|
|
646
|
+
existingPath = parentPath;
|
|
647
|
+
}
|
|
648
|
+
const realRoot = fs.realpathSync(resolvedRoot);
|
|
649
|
+
const realExistingPath = fs.realpathSync(existingPath);
|
|
650
|
+
assertInsideRoot(realRoot, realExistingPath, label);
|
|
651
|
+
}
|
|
652
|
+
function resolveVirtualWorkerFilePath(inputPath, parsed, builtInPaths) {
|
|
653
|
+
if (parsed.relativePath.split("/").includes("..")) {
|
|
654
|
+
throw new Error(`Invalid ${parsed.ref} path: ${inputPath}`);
|
|
655
|
+
}
|
|
656
|
+
let rootPath;
|
|
657
|
+
let absolutePath;
|
|
658
|
+
if (parsed.ref === R5D_ARTIFACTS_DIR_REF) {
|
|
659
|
+
rootPath = builtInPaths?.artifactsDir;
|
|
660
|
+
} else if (parsed.ref === R5D_PLANS_DIR_REF) {
|
|
661
|
+
rootPath = builtInPaths?.plansDir;
|
|
662
|
+
} else {
|
|
663
|
+
if (parsed.relativePath) {
|
|
664
|
+
throw new Error(`${R5D_ACTIVE_PLAN_FILE_REF} must reference the active plan file directly`);
|
|
665
|
+
}
|
|
666
|
+
rootPath = builtInPaths?.plansDir;
|
|
667
|
+
absolutePath = builtInPaths?.activePlanFile;
|
|
668
|
+
}
|
|
669
|
+
if (!rootPath || parsed.ref === R5D_ACTIVE_PLAN_FILE_REF && !absolutePath) {
|
|
670
|
+
const requirement = parsed.ref === R5D_ARTIFACTS_DIR_REF ? "an active chat session" : "an active plan";
|
|
671
|
+
throw new Error(`${parsed.ref} paths require ${requirement}`);
|
|
672
|
+
}
|
|
673
|
+
const normalizedPath = parsed.relativePath ? path.posix.normalize(parsed.relativePath).replace(/^\/+|\/+$/g, "") : "";
|
|
674
|
+
const normalizedRelativePath = normalizedPath === "." ? "" : normalizedPath;
|
|
675
|
+
absolutePath ??= normalizedRelativePath ? path.resolve(rootPath, ...normalizedRelativePath.split("/")) : path.resolve(rootPath);
|
|
676
|
+
assertPathInsideRealRoot(rootPath, absolutePath, `${parsed.ref} path`);
|
|
677
|
+
return {
|
|
678
|
+
absolutePath,
|
|
679
|
+
displayPath: normalizedRelativePath ? `${parsed.ref}/${normalizedRelativePath}` : parsed.ref,
|
|
680
|
+
repoRelativePath: null,
|
|
681
|
+
scope: "virtual",
|
|
682
|
+
virtualRootPath: path.resolve(rootPath)
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
function resolveWorkerFilePath(branchPath, inputPath, builtInPaths) {
|
|
599
686
|
if (typeof inputPath !== "string" || inputPath.length === 0 || inputPath.includes("\0")) {
|
|
600
687
|
throw new Error(`File path must be a non-empty string. Got: ${JSON.stringify(inputPath)}`);
|
|
601
688
|
}
|
|
689
|
+
const parsedBuiltInPath = parseBuiltInToolPath(inputPath);
|
|
690
|
+
if (parsedBuiltInPath) {
|
|
691
|
+
return resolveVirtualWorkerFilePath(inputPath, parsedBuiltInPath, builtInPaths);
|
|
692
|
+
}
|
|
602
693
|
const resolvedBranchPath = path.resolve(branchPath);
|
|
603
694
|
if (path.isAbsolute(inputPath)) {
|
|
604
695
|
const absolutePath2 = path.resolve(inputPath);
|
|
@@ -1055,13 +1146,13 @@ async function withFileMutationQueue(key, operation) {
|
|
|
1055
1146
|
}
|
|
1056
1147
|
}
|
|
1057
1148
|
function mutationQueueKey(projectId, branchName, resolved) {
|
|
1058
|
-
if (resolved.scope
|
|
1059
|
-
return
|
|
1149
|
+
if (resolved.scope !== "project") {
|
|
1150
|
+
return `${resolved.scope}:${path.normalize(resolved.absolutePath)}`;
|
|
1060
1151
|
}
|
|
1061
1152
|
return `project:${projectId}:${branchName}:${path.posix.normalize(resolved.repoRelativePath)}`;
|
|
1062
1153
|
}
|
|
1063
|
-
function readWorkerTextFile(branchPath, filePath, offset, limit) {
|
|
1064
|
-
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1154
|
+
function readWorkerTextFile(branchPath, filePath, offset, limit, builtInPaths) {
|
|
1155
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
|
|
1065
1156
|
if (!fs.existsSync(resolved.absolutePath) || !fs.statSync(resolved.absolutePath).isFile()) {
|
|
1066
1157
|
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
1067
1158
|
}
|
|
@@ -1075,8 +1166,8 @@ function readWorkerTextFile(branchPath, filePath, offset, limit) {
|
|
|
1075
1166
|
...formatted
|
|
1076
1167
|
};
|
|
1077
1168
|
}
|
|
1078
|
-
function writeWorkerTextFile(branchPath, filePath, content) {
|
|
1079
|
-
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1169
|
+
function writeWorkerTextFile(branchPath, filePath, content, builtInPaths) {
|
|
1170
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
|
|
1080
1171
|
fs.mkdirSync(path.dirname(resolved.absolutePath), { recursive: true });
|
|
1081
1172
|
fs.writeFileSync(resolved.absolutePath, content, "utf8");
|
|
1082
1173
|
return {
|
|
@@ -1085,8 +1176,8 @@ function writeWorkerTextFile(branchPath, filePath, content) {
|
|
|
1085
1176
|
gitBlobHash: getGitBlobHashForContent(content)
|
|
1086
1177
|
};
|
|
1087
1178
|
}
|
|
1088
|
-
function editWorkerTextFile(branchPath, filePath, edits) {
|
|
1089
|
-
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1179
|
+
function editWorkerTextFile(branchPath, filePath, edits, builtInPaths) {
|
|
1180
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
|
|
1090
1181
|
if (!Array.isArray(edits) || edits.length === 0) {
|
|
1091
1182
|
throw new Error("edit requires at least one replacement");
|
|
1092
1183
|
}
|
|
@@ -1133,46 +1224,57 @@ function editWorkerTextFile(branchPath, filePath, edits) {
|
|
|
1133
1224
|
};
|
|
1134
1225
|
}
|
|
1135
1226
|
async function executeReadFileOperation(input) {
|
|
1136
|
-
const
|
|
1227
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1137
1228
|
filePath: input.message.filePath,
|
|
1138
1229
|
baseUrl: input.baseUrl,
|
|
1139
1230
|
token: input.token,
|
|
1140
1231
|
projectId: input.projectId,
|
|
1141
1232
|
branchName: input.message.branchName,
|
|
1142
1233
|
sessionId: input.message.sessionId,
|
|
1143
|
-
|
|
1234
|
+
activePlanId: input.message.activePlanId,
|
|
1235
|
+
artifactRoot: input.artifactRoot,
|
|
1236
|
+
planRoot: input.planRoot,
|
|
1237
|
+
access: "read"
|
|
1144
1238
|
});
|
|
1145
|
-
if (artifact) {
|
|
1146
|
-
if (!fs.existsSync(artifact.absolutePath) || !fs.statSync(artifact.absolutePath).isFile()) {
|
|
1147
|
-
throw new Error(`File not found: ${artifact.virtualPath}`);
|
|
1148
|
-
}
|
|
1149
|
-
const buffer = fs.readFileSync(artifact.absolutePath);
|
|
1150
|
-
const formatted = formatLineNumberedContent(buffer.toString("utf8"), input.message.offset, input.message.limit);
|
|
1151
|
-
return {
|
|
1152
|
-
type: "read",
|
|
1153
|
-
kind: "text",
|
|
1154
|
-
file: artifact.virtualPath,
|
|
1155
|
-
gitBlobHash: getGitBlobHashForContent(buffer),
|
|
1156
|
-
...formatted
|
|
1157
|
-
};
|
|
1158
|
-
}
|
|
1159
1239
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1160
|
-
return readWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.offset, input.message.limit);
|
|
1240
|
+
return readWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.offset, input.message.limit, builtInPaths);
|
|
1161
1241
|
}
|
|
1162
1242
|
async function executeWriteFileOperation(input) {
|
|
1163
|
-
|
|
1243
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1244
|
+
filePath: input.message.filePath,
|
|
1245
|
+
baseUrl: input.baseUrl,
|
|
1246
|
+
token: input.token,
|
|
1247
|
+
projectId: input.projectId,
|
|
1248
|
+
branchName: input.message.branchName,
|
|
1249
|
+
sessionId: input.message.sessionId,
|
|
1250
|
+
activePlanId: input.message.activePlanId,
|
|
1251
|
+
artifactRoot: input.artifactRoot,
|
|
1252
|
+
planRoot: input.planRoot,
|
|
1253
|
+
access: "write"
|
|
1254
|
+
});
|
|
1164
1255
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1165
|
-
const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath);
|
|
1256
|
+
const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath, builtInPaths);
|
|
1166
1257
|
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved), async () => {
|
|
1167
|
-
return writeWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.content);
|
|
1258
|
+
return writeWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.content, builtInPaths);
|
|
1168
1259
|
});
|
|
1169
1260
|
}
|
|
1170
1261
|
async function executeEditFileOperation(input) {
|
|
1171
|
-
|
|
1262
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1263
|
+
filePath: input.message.filePath,
|
|
1264
|
+
baseUrl: input.baseUrl,
|
|
1265
|
+
token: input.token,
|
|
1266
|
+
projectId: input.projectId,
|
|
1267
|
+
branchName: input.message.branchName,
|
|
1268
|
+
sessionId: input.message.sessionId,
|
|
1269
|
+
activePlanId: input.message.activePlanId,
|
|
1270
|
+
artifactRoot: input.artifactRoot,
|
|
1271
|
+
planRoot: input.planRoot,
|
|
1272
|
+
access: "write"
|
|
1273
|
+
});
|
|
1172
1274
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1173
|
-
const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath);
|
|
1275
|
+
const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath, builtInPaths);
|
|
1174
1276
|
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved), async () => {
|
|
1175
|
-
return editWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.edits);
|
|
1277
|
+
return editWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.edits, builtInPaths);
|
|
1176
1278
|
});
|
|
1177
1279
|
}
|
|
1178
1280
|
const IGNORED_ENTRY_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", ".cache", "coverage", "target"]);
|
|
@@ -1193,16 +1295,20 @@ function normalizeFindPattern(pattern) {
|
|
|
1193
1295
|
function walkWorkerEntries(branchPath, start) {
|
|
1194
1296
|
const entries = [];
|
|
1195
1297
|
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
1298
|
+
const realVirtualRoot = start.scope === "virtual" ? fs.realpathSync(start.virtualRootPath) : null;
|
|
1196
1299
|
const visit = (absolutePath, isRoot) => {
|
|
1197
1300
|
let stat;
|
|
1198
1301
|
try {
|
|
1199
1302
|
stat = fs.statSync(absolutePath);
|
|
1303
|
+
if (realVirtualRoot) {
|
|
1304
|
+
assertInsideRoot(realVirtualRoot, fs.realpathSync(absolutePath), `${start.displayPath} path`);
|
|
1305
|
+
}
|
|
1200
1306
|
} catch (error) {
|
|
1201
1307
|
if (isRoot) throw error;
|
|
1202
1308
|
return;
|
|
1203
1309
|
}
|
|
1204
|
-
const displayPath = start.scope === "project" ? toProjectDisplayPath(branchPath, absolutePath) : path.resolve(absolutePath);
|
|
1205
1310
|
const hostRelativePath = path.relative(start.absolutePath, absolutePath).split(path.sep).join(path.posix.sep);
|
|
1311
|
+
const displayPath = start.scope === "project" ? toProjectDisplayPath(branchPath, absolutePath) : start.scope === "virtual" ? hostRelativePath ? `${start.displayPath}/${hostRelativePath}` : start.displayPath : path.resolve(absolutePath);
|
|
1206
1312
|
const matchPath = start.scope === "project" ? displayPath : hostRelativePath || path.basename(start.absolutePath);
|
|
1207
1313
|
entries.push({ absolutePath, displayPath, matchPath, stat });
|
|
1208
1314
|
if (!stat.isDirectory()) return;
|
|
@@ -1233,8 +1339,8 @@ function walkWorkerEntries(branchPath, start) {
|
|
|
1233
1339
|
function isProbablyText(bytes) {
|
|
1234
1340
|
return !bytes.subarray(0, Math.min(bytes.length, 4096)).includes(0);
|
|
1235
1341
|
}
|
|
1236
|
-
function grepWorkerFiles(branchPath, input) {
|
|
1237
|
-
const start = resolveWorkerFilePath(branchPath, input.path ?? ".");
|
|
1342
|
+
function grepWorkerFiles(branchPath, input, builtInPaths) {
|
|
1343
|
+
const start = resolveWorkerFilePath(branchPath, input.path ?? ".", builtInPaths);
|
|
1238
1344
|
if (!fs.existsSync(start.absolutePath)) {
|
|
1239
1345
|
throw new Error(`Path not found: ${start.displayPath}`);
|
|
1240
1346
|
}
|
|
@@ -1280,17 +1386,34 @@ function grepWorkerFiles(branchPath, input) {
|
|
|
1280
1386
|
};
|
|
1281
1387
|
}
|
|
1282
1388
|
async function executeGrepOperation(input) {
|
|
1283
|
-
const
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1389
|
+
const toolPath = input.message.path ?? ".";
|
|
1390
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1391
|
+
filePath: toolPath,
|
|
1392
|
+
baseUrl: input.baseUrl,
|
|
1393
|
+
token: input.token,
|
|
1394
|
+
projectId: input.projectId,
|
|
1395
|
+
branchName: input.message.branchName,
|
|
1396
|
+
sessionId: input.message.sessionId,
|
|
1397
|
+
activePlanId: input.message.activePlanId,
|
|
1398
|
+
artifactRoot: input.artifactRoot,
|
|
1399
|
+
planRoot: input.planRoot,
|
|
1400
|
+
access: "read"
|
|
1290
1401
|
});
|
|
1402
|
+
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1403
|
+
return grepWorkerFiles(
|
|
1404
|
+
workspace.branchPath,
|
|
1405
|
+
{
|
|
1406
|
+
pattern: input.message.pattern,
|
|
1407
|
+
path: input.message.path,
|
|
1408
|
+
glob: input.message.glob,
|
|
1409
|
+
caseSensitive: input.message.caseSensitive,
|
|
1410
|
+
limit: input.message.limit
|
|
1411
|
+
},
|
|
1412
|
+
builtInPaths
|
|
1413
|
+
);
|
|
1291
1414
|
}
|
|
1292
|
-
function findWorkerFiles(branchPath, input) {
|
|
1293
|
-
const start = resolveWorkerFilePath(branchPath, input.path ?? ".");
|
|
1415
|
+
function findWorkerFiles(branchPath, input, builtInPaths) {
|
|
1416
|
+
const start = resolveWorkerFilePath(branchPath, input.path ?? ".", builtInPaths);
|
|
1294
1417
|
if (!fs.existsSync(start.absolutePath) || !fs.statSync(start.absolutePath).isDirectory()) {
|
|
1295
1418
|
throw new Error(`Directory not found: ${start.displayPath}`);
|
|
1296
1419
|
}
|
|
@@ -1324,16 +1447,33 @@ function findWorkerFiles(branchPath, input) {
|
|
|
1324
1447
|
};
|
|
1325
1448
|
}
|
|
1326
1449
|
async function executeFindOperation(input) {
|
|
1327
|
-
const
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1450
|
+
const toolPath = input.message.path ?? ".";
|
|
1451
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1452
|
+
filePath: toolPath,
|
|
1453
|
+
baseUrl: input.baseUrl,
|
|
1454
|
+
token: input.token,
|
|
1455
|
+
projectId: input.projectId,
|
|
1456
|
+
branchName: input.message.branchName,
|
|
1457
|
+
sessionId: input.message.sessionId,
|
|
1458
|
+
activePlanId: input.message.activePlanId,
|
|
1459
|
+
artifactRoot: input.artifactRoot,
|
|
1460
|
+
planRoot: input.planRoot,
|
|
1461
|
+
access: "read"
|
|
1333
1462
|
});
|
|
1463
|
+
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1464
|
+
return findWorkerFiles(
|
|
1465
|
+
workspace.branchPath,
|
|
1466
|
+
{
|
|
1467
|
+
pattern: input.message.pattern,
|
|
1468
|
+
path: input.message.path,
|
|
1469
|
+
entryType: input.message.entryType,
|
|
1470
|
+
limit: input.message.limit
|
|
1471
|
+
},
|
|
1472
|
+
builtInPaths
|
|
1473
|
+
);
|
|
1334
1474
|
}
|
|
1335
|
-
function listWorkerDirectory(branchPath, inputPath = ".", inputLimit) {
|
|
1336
|
-
const resolved = resolveWorkerFilePath(branchPath, inputPath);
|
|
1475
|
+
function listWorkerDirectory(branchPath, inputPath = ".", inputLimit, builtInPaths) {
|
|
1476
|
+
const resolved = resolveWorkerFilePath(branchPath, inputPath, builtInPaths);
|
|
1337
1477
|
if (!fs.existsSync(resolved.absolutePath) || !fs.statSync(resolved.absolutePath).isDirectory()) {
|
|
1338
1478
|
throw new Error(`Directory not found: ${resolved.displayPath}`);
|
|
1339
1479
|
}
|
|
@@ -1351,11 +1491,24 @@ function listWorkerDirectory(branchPath, inputPath = ".", inputLimit) {
|
|
|
1351
1491
|
};
|
|
1352
1492
|
}
|
|
1353
1493
|
async function executeLsOperation(input) {
|
|
1494
|
+
const toolPath = input.message.path ?? ".";
|
|
1495
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1496
|
+
filePath: toolPath,
|
|
1497
|
+
baseUrl: input.baseUrl,
|
|
1498
|
+
token: input.token,
|
|
1499
|
+
projectId: input.projectId,
|
|
1500
|
+
branchName: input.message.branchName,
|
|
1501
|
+
sessionId: input.message.sessionId,
|
|
1502
|
+
activePlanId: input.message.activePlanId,
|
|
1503
|
+
artifactRoot: input.artifactRoot,
|
|
1504
|
+
planRoot: input.planRoot,
|
|
1505
|
+
access: "read"
|
|
1506
|
+
});
|
|
1354
1507
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1355
|
-
return listWorkerDirectory(workspace.branchPath, input.message.path, input.message.limit);
|
|
1508
|
+
return listWorkerDirectory(workspace.branchPath, input.message.path, input.message.limit, builtInPaths);
|
|
1356
1509
|
}
|
|
1357
|
-
function readWorkerImageFile(branchPath, filePath) {
|
|
1358
|
-
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1510
|
+
function readWorkerImageFile(branchPath, filePath, builtInPaths) {
|
|
1511
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
|
|
1359
1512
|
if (!fs.existsSync(resolved.absolutePath) || !fs.statSync(resolved.absolutePath).isFile()) {
|
|
1360
1513
|
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
1361
1514
|
}
|
|
@@ -1377,38 +1530,20 @@ function readWorkerImageFile(branchPath, filePath) {
|
|
|
1377
1530
|
};
|
|
1378
1531
|
}
|
|
1379
1532
|
async function executeViewFileBytesOperation(input) {
|
|
1380
|
-
const
|
|
1533
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1381
1534
|
filePath: input.message.filePath,
|
|
1382
1535
|
baseUrl: input.baseUrl,
|
|
1383
1536
|
token: input.token,
|
|
1384
1537
|
projectId: input.projectId,
|
|
1385
1538
|
branchName: input.message.branchName,
|
|
1386
1539
|
sessionId: input.message.sessionId,
|
|
1387
|
-
|
|
1540
|
+
activePlanId: input.message.activePlanId,
|
|
1541
|
+
artifactRoot: input.artifactRoot,
|
|
1542
|
+
planRoot: input.planRoot,
|
|
1543
|
+
access: "read"
|
|
1388
1544
|
});
|
|
1389
|
-
if (artifact) {
|
|
1390
|
-
if (!fs.existsSync(artifact.absolutePath) || !fs.statSync(artifact.absolutePath).isFile()) {
|
|
1391
|
-
throw new Error(`File not found: ${artifact.virtualPath}`);
|
|
1392
|
-
}
|
|
1393
|
-
const extension = path.extname(artifact.absolutePath).toLowerCase();
|
|
1394
|
-
const mediaType = extension === ".jpg" || extension === ".jpeg" ? "image/jpeg" : extension === ".png" ? "image/png" : null;
|
|
1395
|
-
if (!mediaType) {
|
|
1396
|
-
throw new Error("read supports PNG and JPEG image inputs only");
|
|
1397
|
-
}
|
|
1398
|
-
const bytes = fs.readFileSync(artifact.absolutePath);
|
|
1399
|
-
const dimensions = readImageDimensions(bytes, mediaType);
|
|
1400
|
-
return {
|
|
1401
|
-
type: "view_file_bytes",
|
|
1402
|
-
filePath: artifact.virtualPath,
|
|
1403
|
-
mediaType,
|
|
1404
|
-
base64: bytes.toString("base64"),
|
|
1405
|
-
fileSizeBytes: bytes.length,
|
|
1406
|
-
width: dimensions.width,
|
|
1407
|
-
height: dimensions.height
|
|
1408
|
-
};
|
|
1409
|
-
}
|
|
1410
1545
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1411
|
-
return readWorkerImageFile(workspace.branchPath, input.message.filePath);
|
|
1546
|
+
return readWorkerImageFile(workspace.branchPath, input.message.filePath, builtInPaths);
|
|
1412
1547
|
}
|
|
1413
1548
|
async function executeOperation(input) {
|
|
1414
1549
|
switch (input.message.type) {
|
|
@@ -2047,6 +2182,7 @@ async function startWorker(options) {
|
|
|
2047
2182
|
fs.mkdirSync(artifactRoot, { recursive: true });
|
|
2048
2183
|
fs.mkdirSync(planRoot, { recursive: true });
|
|
2049
2184
|
const manifestByProjectId = /* @__PURE__ */ new Map();
|
|
2185
|
+
const pendingManifestCheckouts = /* @__PURE__ */ new Map();
|
|
2050
2186
|
const workspaceSyncSingleFlight = new WorkspaceSyncSingleFlight();
|
|
2051
2187
|
let workspaceRemoteUrl = null;
|
|
2052
2188
|
let activeWorkspaceIncidentId = null;
|
|
@@ -2055,6 +2191,7 @@ async function startWorker(options) {
|
|
|
2055
2191
|
let periodicWorkspaceScanInFlight = false;
|
|
2056
2192
|
let terminalReplayTimer;
|
|
2057
2193
|
let workspaceSyncRequestsInFlight = 0;
|
|
2194
|
+
let sessionArtifactSyncRequestsInFlight = 0;
|
|
2058
2195
|
let cliUpdateInProgress = false;
|
|
2059
2196
|
let reloadAfterClose = false;
|
|
2060
2197
|
const workspaceSyncInput = (trigger, overrides = {}) => {
|
|
@@ -2074,37 +2211,64 @@ async function startWorker(options) {
|
|
|
2074
2211
|
const sendWorkspaceSyncResult = (requestId, result) => {
|
|
2075
2212
|
sendWorkerMessage(ws, { type: "workspace_sync_result", requestId, result });
|
|
2076
2213
|
};
|
|
2077
|
-
const
|
|
2214
|
+
const manifestCheckoutKey = (projectId, branchName) => `${projectId}\0${branchName}`;
|
|
2215
|
+
const allManifestCheckouts = () => [...manifestByProjectId.values()].flatMap(
|
|
2216
|
+
(manifest) => manifest.branches.map((branchName) => ({ projectId: manifest.projectId, branchName }))
|
|
2217
|
+
);
|
|
2218
|
+
const ensureVisibleWorkspaceCheckouts = (targets) => {
|
|
2078
2219
|
const created = [];
|
|
2079
|
-
for (const
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
projectRoot: projectRootFor(projectsRoot, manifest.projectId, manifestByProjectId),
|
|
2086
|
-
branchName,
|
|
2087
|
-
githubRemoteUrl,
|
|
2088
|
-
githubAuth: gitAuthForRemote(githubRemoteUrl, manifest.repoAuthHeader),
|
|
2089
|
-
githubAuthHeader: manifest.repoAuthHeader,
|
|
2090
|
-
defaultBranch: manifest.defaultBranch || "main",
|
|
2091
|
-
reconcileExistingOrigin: Boolean(manifest.repoHttpUrl)
|
|
2092
|
-
});
|
|
2093
|
-
if (!existed) created.push({ projectId: manifest.projectId, branchName });
|
|
2220
|
+
for (const target of targets) {
|
|
2221
|
+
const manifest = manifestByProjectId.get(target.projectId);
|
|
2222
|
+
const key = manifestCheckoutKey(target.projectId, target.branchName);
|
|
2223
|
+
if (!manifest || !manifest.branches.includes(target.branchName)) {
|
|
2224
|
+
pendingManifestCheckouts.delete(key);
|
|
2225
|
+
continue;
|
|
2094
2226
|
}
|
|
2227
|
+
const projectRoot = projectRootFor(projectsRoot, manifest.projectId, manifestByProjectId);
|
|
2228
|
+
const branchPath = path.join(projectRoot, target.branchName);
|
|
2229
|
+
const existed = hasNormalVisibleGitDir(branchPath);
|
|
2230
|
+
const githubRemoteUrl = githubRemoteUrlFor(baseUrl, manifest.projectId, manifest);
|
|
2231
|
+
ensureVisibleGitCheckout({
|
|
2232
|
+
projectRoot,
|
|
2233
|
+
branchName: target.branchName,
|
|
2234
|
+
githubRemoteUrl,
|
|
2235
|
+
githubAuth: gitAuthForRemote(githubRemoteUrl, manifest.repoAuthHeader),
|
|
2236
|
+
githubAuthHeader: manifest.repoAuthHeader,
|
|
2237
|
+
defaultBranch: manifest.defaultBranch || "main",
|
|
2238
|
+
reconcileExistingOrigin: Boolean(manifest.repoHttpUrl)
|
|
2239
|
+
});
|
|
2240
|
+
if (!existed) created.push(target);
|
|
2095
2241
|
}
|
|
2096
2242
|
return created;
|
|
2097
2243
|
};
|
|
2098
2244
|
const runRequestedWorkspaceSync = async (requestId, trigger, overrides = {}) => {
|
|
2099
2245
|
workspaceSyncRequestsInFlight += 1;
|
|
2100
2246
|
try {
|
|
2101
|
-
|
|
2102
|
-
const result = await workspaceSyncSingleFlight.
|
|
2103
|
-
|
|
2247
|
+
let pendingTargets = [];
|
|
2248
|
+
const result = await workspaceSyncSingleFlight.runPrepared(() => {
|
|
2249
|
+
const shouldEnsureCheckouts = !overrides.skipVisibleMirror && !overrides.resetToCanonical;
|
|
2250
|
+
let checkoutTargets = [];
|
|
2251
|
+
if (shouldEnsureCheckouts) {
|
|
2252
|
+
checkoutTargets = trigger.type === "connect" ? allManifestCheckouts() : [...pendingManifestCheckouts.values()];
|
|
2253
|
+
}
|
|
2254
|
+
pendingTargets = shouldEnsureCheckouts ? [...pendingManifestCheckouts.values()] : [];
|
|
2255
|
+
const createdCheckouts = ensureVisibleWorkspaceCheckouts(checkoutTargets);
|
|
2256
|
+
const newVisibleCheckoutByKey = /* @__PURE__ */ new Map();
|
|
2257
|
+
for (const target of [...overrides.newVisibleCheckouts ?? [], ...createdCheckouts, ...pendingTargets]) {
|
|
2258
|
+
newVisibleCheckoutByKey.set(manifestCheckoutKey(target.projectId, target.branchName), target);
|
|
2259
|
+
}
|
|
2260
|
+
const newVisibleCheckouts = [...newVisibleCheckoutByKey.values()];
|
|
2261
|
+
return workspaceSyncInput(trigger, {
|
|
2104
2262
|
...overrides,
|
|
2105
2263
|
...newVisibleCheckouts.length > 0 ? { newVisibleCheckouts } : {}
|
|
2106
|
-
})
|
|
2107
|
-
);
|
|
2264
|
+
});
|
|
2265
|
+
});
|
|
2266
|
+
if (result.outcome === "no_change" || result.outcome === "published" || result.outcome === "updated" || result.outcome === "conflict_reset") {
|
|
2267
|
+
for (const target of pendingTargets) {
|
|
2268
|
+
const key = manifestCheckoutKey(target.projectId, target.branchName);
|
|
2269
|
+
if (pendingManifestCheckouts.get(key) === target) pendingManifestCheckouts.delete(key);
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2108
2272
|
previousPeriodicFingerprint = null;
|
|
2109
2273
|
sendWorkspaceSyncResult(requestId, result);
|
|
2110
2274
|
return result;
|
|
@@ -2197,9 +2361,26 @@ async function startWorker(options) {
|
|
|
2197
2361
|
configureGitHubAuth(message.githubCredential);
|
|
2198
2362
|
visibleGitIdentity = message.gitIdentity;
|
|
2199
2363
|
workspaceRemoteUrl = message.workspaceRemoteUrl;
|
|
2364
|
+
const previousCheckoutKeys = new Set(
|
|
2365
|
+
allManifestCheckouts().map(({ projectId, branchName }) => manifestCheckoutKey(projectId, branchName))
|
|
2366
|
+
);
|
|
2200
2367
|
const migratedProjectIds = migrateLegacyProjectRoots(projectsRoot, message.projects);
|
|
2201
2368
|
manifestByProjectId.clear();
|
|
2202
2369
|
for (const project of message.projects) manifestByProjectId.set(project.projectId, project);
|
|
2370
|
+
const currentCheckouts = allManifestCheckouts();
|
|
2371
|
+
const currentCheckoutKeys = new Set(
|
|
2372
|
+
currentCheckouts.map(({ projectId, branchName }) => manifestCheckoutKey(projectId, branchName))
|
|
2373
|
+
);
|
|
2374
|
+
for (const key of pendingManifestCheckouts.keys()) {
|
|
2375
|
+
if (!currentCheckoutKeys.has(key)) pendingManifestCheckouts.delete(key);
|
|
2376
|
+
}
|
|
2377
|
+
for (const checkout of currentCheckouts) {
|
|
2378
|
+
const key = manifestCheckoutKey(checkout.projectId, checkout.branchName);
|
|
2379
|
+
if (previousCheckoutKeys.has(key)) continue;
|
|
2380
|
+
const branchPath = path.join(projectRootFor(projectsRoot, checkout.projectId, manifestByProjectId), checkout.branchName);
|
|
2381
|
+
if (!hasNormalVisibleGitDir(branchPath)) pendingManifestCheckouts.set(key, checkout);
|
|
2382
|
+
}
|
|
2383
|
+
previousPeriodicFingerprint = null;
|
|
2203
2384
|
process.stdout.write(
|
|
2204
2385
|
`[r5d-worker] workspace manifest: ${message.projects.length} projects${migratedProjectIds.length > 0 ? `; migrated ${migratedProjectIds.length} project checkout root(s)` : ""}
|
|
2205
2386
|
`
|
|
@@ -2210,8 +2391,15 @@ async function startWorker(options) {
|
|
|
2210
2391
|
if (!workspaceRemoteUrl || activeWorkspaceIncidentId || cliUpdateInProgress || periodicWorkspaceScanInFlight) return;
|
|
2211
2392
|
periodicWorkspaceScanInFlight = true;
|
|
2212
2393
|
void (async () => {
|
|
2213
|
-
|
|
2214
|
-
|
|
2394
|
+
if (pendingManifestCheckouts.size > 0) {
|
|
2395
|
+
previousPeriodicFingerprint = null;
|
|
2396
|
+
await runRequestedWorkspaceSync(crypto.randomUUID(), {
|
|
2397
|
+
type: "periodic",
|
|
2398
|
+
detail: "workspace manifest additions"
|
|
2399
|
+
});
|
|
2400
|
+
return;
|
|
2401
|
+
}
|
|
2402
|
+
const fingerprint = await workspaceSyncSingleFlight.fingerprintPrepared(() => workspaceSyncInput({ type: "periodic" }));
|
|
2215
2403
|
if (fingerprint === emptyFingerprint) {
|
|
2216
2404
|
previousPeriodicFingerprint = null;
|
|
2217
2405
|
return;
|
|
@@ -2251,6 +2439,34 @@ async function startWorker(options) {
|
|
|
2251
2439
|
}
|
|
2252
2440
|
return;
|
|
2253
2441
|
}
|
|
2442
|
+
if (message.type === "sync_session_artifacts") {
|
|
2443
|
+
sessionArtifactSyncRequestsInFlight += 1;
|
|
2444
|
+
try {
|
|
2445
|
+
await syncSessionArtifacts({
|
|
2446
|
+
baseUrl,
|
|
2447
|
+
token,
|
|
2448
|
+
projectId: message.projectId,
|
|
2449
|
+
branchName: message.branchName,
|
|
2450
|
+
sessionId: message.sessionId,
|
|
2451
|
+
artifactRoot
|
|
2452
|
+
});
|
|
2453
|
+
sendWorkerMessage(ws, {
|
|
2454
|
+
type: "session_artifacts_sync_result",
|
|
2455
|
+
requestId: message.requestId,
|
|
2456
|
+
sessionId: message.sessionId
|
|
2457
|
+
});
|
|
2458
|
+
} catch (error) {
|
|
2459
|
+
sendWorkerMessage(ws, {
|
|
2460
|
+
type: "session_artifacts_sync_result",
|
|
2461
|
+
requestId: message.requestId,
|
|
2462
|
+
sessionId: message.sessionId,
|
|
2463
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2464
|
+
});
|
|
2465
|
+
} finally {
|
|
2466
|
+
sessionArtifactSyncRequestsInFlight -= 1;
|
|
2467
|
+
}
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2254
2470
|
if (message.type === "workspace_incident_updated") {
|
|
2255
2471
|
activeWorkspaceIncidentId = message.status === "remediating" || message.status === "waiting_for_worker" ? message.incidentId : null;
|
|
2256
2472
|
previousPeriodicFingerprint = null;
|
|
@@ -2269,7 +2485,7 @@ async function startWorker(options) {
|
|
|
2269
2485
|
});
|
|
2270
2486
|
return;
|
|
2271
2487
|
}
|
|
2272
|
-
if (activeProcesses.size > 0 || activePtys.size > 0 || workspaceSyncRequestsInFlight > 0) {
|
|
2488
|
+
if (activeProcesses.size > 0 || activePtys.size > 0 || workspaceSyncRequestsInFlight > 0 || sessionArtifactSyncRequestsInFlight > 0) {
|
|
2273
2489
|
sendWorkerMessage(ws, {
|
|
2274
2490
|
type: "update_clis_result",
|
|
2275
2491
|
requestId: message.requestId,
|
|
@@ -2450,6 +2666,7 @@ async function startWorker(options) {
|
|
|
2450
2666
|
projectRoot,
|
|
2451
2667
|
syncRoot,
|
|
2452
2668
|
artifactRoot,
|
|
2669
|
+
planRoot,
|
|
2453
2670
|
manifest
|
|
2454
2671
|
});
|
|
2455
2672
|
ws.send(
|