@ricsam/r5d-worker 0.0.45 → 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 +308 -131
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-sync.cjs +187 -49
- package/dist/mjs/main.mjs +308 -131
- 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/cjs/main.cjs
CHANGED
|
@@ -282,7 +282,25 @@ async function readResponseText(response) {
|
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
const R5D_ARTIFACTS_DIR_REF = "$R5D_ARTIFACTS_DIR";
|
|
285
|
+
const R5D_PLANS_DIR_REF = "$R5D_PLANS_DIR";
|
|
286
|
+
const R5D_ACTIVE_PLAN_FILE_REF = "$R5D_ACTIVE_PLAN_FILE";
|
|
285
287
|
const R5D_PLANS_DIR_ENV = "R5D_PLANS_DIR";
|
|
288
|
+
const BUILT_IN_TOOL_PATH_REFS = [R5D_ARTIFACTS_DIR_REF, R5D_PLANS_DIR_REF, R5D_ACTIVE_PLAN_FILE_REF];
|
|
289
|
+
function parseBuiltInToolPath(inputPath) {
|
|
290
|
+
const normalized = inputPath.replace(/\\/g, "/");
|
|
291
|
+
const match = /^\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))(?:\/(.*))?$/.exec(normalized);
|
|
292
|
+
if (!match) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
const ref = `$${match[1] ?? match[2]}`;
|
|
296
|
+
if (!BUILT_IN_TOOL_PATH_REFS.includes(ref)) {
|
|
297
|
+
throw new Error(`Unsupported tool path variable "${ref}". Supported variables: ${BUILT_IN_TOOL_PATH_REFS.join(", ")}.`);
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
ref,
|
|
301
|
+
relativePath: match[3] ?? ""
|
|
302
|
+
};
|
|
303
|
+
}
|
|
286
304
|
function validateArtifactSessionId(sessionId) {
|
|
287
305
|
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)) {
|
|
288
306
|
throw new Error(`Invalid artifact session id: ${sessionId}`);
|
|
@@ -291,7 +309,7 @@ function validateArtifactSessionId(sessionId) {
|
|
|
291
309
|
function assertInsideRoot(rootPath, candidatePath, label) {
|
|
292
310
|
const relative = import_node_path.default.relative(rootPath, candidatePath);
|
|
293
311
|
if (relative === ".." || relative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(relative)) {
|
|
294
|
-
throw new Error(`${label} escapes
|
|
312
|
+
throw new Error(`${label} escapes its allowed root`);
|
|
295
313
|
}
|
|
296
314
|
}
|
|
297
315
|
function sessionArtifactDir(artifactRoot, sessionId) {
|
|
@@ -324,7 +342,11 @@ function planEnv(planRoot, projectId, branchName, activePlanId) {
|
|
|
324
342
|
return env;
|
|
325
343
|
}
|
|
326
344
|
function isArtifactEnvPath(filePath) {
|
|
327
|
-
|
|
345
|
+
try {
|
|
346
|
+
return parseBuiltInToolPath(filePath)?.ref === R5D_ARTIFACTS_DIR_REF;
|
|
347
|
+
} catch {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
328
350
|
}
|
|
329
351
|
function artifactApiBasePath(projectId, branchName, sessionId) {
|
|
330
352
|
return `/preview-api/${encodeURIComponent(projectId)}/${encodeURIComponent(branchName)}/artifacts/${encodeURIComponent(sessionId)}`;
|
|
@@ -458,40 +480,45 @@ async function syncSessionArtifacts(input) {
|
|
|
458
480
|
void queued.then(release, release);
|
|
459
481
|
return queued;
|
|
460
482
|
}
|
|
461
|
-
async function
|
|
462
|
-
|
|
463
|
-
|
|
483
|
+
async function prepareBuiltInToolPaths(input) {
|
|
484
|
+
const parsed = parseBuiltInToolPath(input.filePath);
|
|
485
|
+
if (!parsed) {
|
|
486
|
+
return void 0;
|
|
464
487
|
}
|
|
465
|
-
if (
|
|
466
|
-
|
|
488
|
+
if (parsed.ref === R5D_ARTIFACTS_DIR_REF) {
|
|
489
|
+
if (input.access === "write") {
|
|
490
|
+
throw new Error(
|
|
491
|
+
`${R5D_ARTIFACTS_DIR_REF} attachments are read-only. Copy them into a project path before editing or creating files.`
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
if (!input.sessionId) {
|
|
495
|
+
throw new Error(`${R5D_ARTIFACTS_DIR_REF} paths require an active chat session`);
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
artifactsDir: await syncSessionArtifacts({
|
|
499
|
+
baseUrl: input.baseUrl,
|
|
500
|
+
token: input.token,
|
|
501
|
+
projectId: input.projectId,
|
|
502
|
+
branchName: input.branchName,
|
|
503
|
+
sessionId: input.sessionId,
|
|
504
|
+
artifactRoot: input.artifactRoot
|
|
505
|
+
})
|
|
506
|
+
};
|
|
467
507
|
}
|
|
468
|
-
|
|
469
|
-
|
|
508
|
+
const plansDir = projectPlanDir(input.planRoot, input.projectId, input.branchName);
|
|
509
|
+
import_node_fs.default.mkdirSync(plansDir, { recursive: true });
|
|
510
|
+
if (parsed.ref === R5D_PLANS_DIR_REF) {
|
|
511
|
+
return { plansDir };
|
|
470
512
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
token: input.token,
|
|
474
|
-
projectId: input.projectId,
|
|
475
|
-
branchName: input.branchName,
|
|
476
|
-
sessionId: input.sessionId,
|
|
477
|
-
artifactRoot: input.artifactRoot
|
|
478
|
-
});
|
|
479
|
-
const relativePath = import_node_path.default.posix.normalize(input.filePath.slice(`${R5D_ARTIFACTS_DIR_REF}/`.length));
|
|
480
|
-
if (!relativePath || relativePath === "." || relativePath.startsWith("../") || relativePath.includes("\0")) {
|
|
481
|
-
throw new Error(`Invalid artifact path: ${input.filePath}`);
|
|
513
|
+
if (!input.activePlanId) {
|
|
514
|
+
throw new Error(`${R5D_ACTIVE_PLAN_FILE_REF} paths require an active plan`);
|
|
482
515
|
}
|
|
483
|
-
|
|
484
|
-
assertInsideRoot(targetDir, absolutePath, "Artifact path");
|
|
516
|
+
validatePlanId(input.activePlanId);
|
|
485
517
|
return {
|
|
486
|
-
|
|
487
|
-
|
|
518
|
+
plansDir,
|
|
519
|
+
activePlanFile: import_node_path.default.join(plansDir, `${input.activePlanId}.plan.md`)
|
|
488
520
|
};
|
|
489
521
|
}
|
|
490
|
-
function rejectArtifactWritePath(filePath) {
|
|
491
|
-
if (isArtifactEnvPath(filePath)) {
|
|
492
|
-
throw new Error(`${R5D_ARTIFACTS_DIR_REF} attachments are read-only. Copy them into a project path before editing or creating files.`);
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
522
|
async function prepareArtifactEnvForShell(input) {
|
|
496
523
|
if (!input.sessionId) {
|
|
497
524
|
return {};
|
|
@@ -642,10 +669,63 @@ function assertAllowedProjectPath(repoRelativePath, inputPath) {
|
|
|
642
669
|
throw new Error(`Invalid project file path: ${inputPath}`);
|
|
643
670
|
}
|
|
644
671
|
}
|
|
645
|
-
function
|
|
672
|
+
function assertPathInsideRealRoot(rootPath, candidatePath, label) {
|
|
673
|
+
const resolvedRoot = import_node_path.default.resolve(rootPath);
|
|
674
|
+
const resolvedCandidate = import_node_path.default.resolve(candidatePath);
|
|
675
|
+
assertInsideRoot(resolvedRoot, resolvedCandidate, label);
|
|
676
|
+
let existingPath = resolvedCandidate;
|
|
677
|
+
while (!import_node_fs.default.existsSync(existingPath)) {
|
|
678
|
+
const parentPath = import_node_path.default.dirname(existingPath);
|
|
679
|
+
if (parentPath === existingPath) {
|
|
680
|
+
throw new Error(`${label} root does not exist: ${rootPath}`);
|
|
681
|
+
}
|
|
682
|
+
existingPath = parentPath;
|
|
683
|
+
}
|
|
684
|
+
const realRoot = import_node_fs.default.realpathSync(resolvedRoot);
|
|
685
|
+
const realExistingPath = import_node_fs.default.realpathSync(existingPath);
|
|
686
|
+
assertInsideRoot(realRoot, realExistingPath, label);
|
|
687
|
+
}
|
|
688
|
+
function resolveVirtualWorkerFilePath(inputPath, parsed, builtInPaths) {
|
|
689
|
+
if (parsed.relativePath.split("/").includes("..")) {
|
|
690
|
+
throw new Error(`Invalid ${parsed.ref} path: ${inputPath}`);
|
|
691
|
+
}
|
|
692
|
+
let rootPath;
|
|
693
|
+
let absolutePath;
|
|
694
|
+
if (parsed.ref === R5D_ARTIFACTS_DIR_REF) {
|
|
695
|
+
rootPath = builtInPaths?.artifactsDir;
|
|
696
|
+
} else if (parsed.ref === R5D_PLANS_DIR_REF) {
|
|
697
|
+
rootPath = builtInPaths?.plansDir;
|
|
698
|
+
} else {
|
|
699
|
+
if (parsed.relativePath) {
|
|
700
|
+
throw new Error(`${R5D_ACTIVE_PLAN_FILE_REF} must reference the active plan file directly`);
|
|
701
|
+
}
|
|
702
|
+
rootPath = builtInPaths?.plansDir;
|
|
703
|
+
absolutePath = builtInPaths?.activePlanFile;
|
|
704
|
+
}
|
|
705
|
+
if (!rootPath || parsed.ref === R5D_ACTIVE_PLAN_FILE_REF && !absolutePath) {
|
|
706
|
+
const requirement = parsed.ref === R5D_ARTIFACTS_DIR_REF ? "an active chat session" : "an active plan";
|
|
707
|
+
throw new Error(`${parsed.ref} paths require ${requirement}`);
|
|
708
|
+
}
|
|
709
|
+
const normalizedPath = parsed.relativePath ? import_node_path.default.posix.normalize(parsed.relativePath).replace(/^\/+|\/+$/g, "") : "";
|
|
710
|
+
const normalizedRelativePath = normalizedPath === "." ? "" : normalizedPath;
|
|
711
|
+
absolutePath ??= normalizedRelativePath ? import_node_path.default.resolve(rootPath, ...normalizedRelativePath.split("/")) : import_node_path.default.resolve(rootPath);
|
|
712
|
+
assertPathInsideRealRoot(rootPath, absolutePath, `${parsed.ref} path`);
|
|
713
|
+
return {
|
|
714
|
+
absolutePath,
|
|
715
|
+
displayPath: normalizedRelativePath ? `${parsed.ref}/${normalizedRelativePath}` : parsed.ref,
|
|
716
|
+
repoRelativePath: null,
|
|
717
|
+
scope: "virtual",
|
|
718
|
+
virtualRootPath: import_node_path.default.resolve(rootPath)
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
function resolveWorkerFilePath(branchPath, inputPath, builtInPaths) {
|
|
646
722
|
if (typeof inputPath !== "string" || inputPath.length === 0 || inputPath.includes("\0")) {
|
|
647
723
|
throw new Error(`File path must be a non-empty string. Got: ${JSON.stringify(inputPath)}`);
|
|
648
724
|
}
|
|
725
|
+
const parsedBuiltInPath = parseBuiltInToolPath(inputPath);
|
|
726
|
+
if (parsedBuiltInPath) {
|
|
727
|
+
return resolveVirtualWorkerFilePath(inputPath, parsedBuiltInPath, builtInPaths);
|
|
728
|
+
}
|
|
649
729
|
const resolvedBranchPath = import_node_path.default.resolve(branchPath);
|
|
650
730
|
if (import_node_path.default.isAbsolute(inputPath)) {
|
|
651
731
|
const absolutePath2 = import_node_path.default.resolve(inputPath);
|
|
@@ -1102,13 +1182,13 @@ async function withFileMutationQueue(key, operation) {
|
|
|
1102
1182
|
}
|
|
1103
1183
|
}
|
|
1104
1184
|
function mutationQueueKey(projectId, branchName, resolved) {
|
|
1105
|
-
if (resolved.scope
|
|
1106
|
-
return
|
|
1185
|
+
if (resolved.scope !== "project") {
|
|
1186
|
+
return `${resolved.scope}:${import_node_path.default.normalize(resolved.absolutePath)}`;
|
|
1107
1187
|
}
|
|
1108
1188
|
return `project:${projectId}:${branchName}:${import_node_path.default.posix.normalize(resolved.repoRelativePath)}`;
|
|
1109
1189
|
}
|
|
1110
|
-
function readWorkerTextFile(branchPath, filePath, offset, limit) {
|
|
1111
|
-
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1190
|
+
function readWorkerTextFile(branchPath, filePath, offset, limit, builtInPaths) {
|
|
1191
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
|
|
1112
1192
|
if (!import_node_fs.default.existsSync(resolved.absolutePath) || !import_node_fs.default.statSync(resolved.absolutePath).isFile()) {
|
|
1113
1193
|
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
1114
1194
|
}
|
|
@@ -1122,8 +1202,8 @@ function readWorkerTextFile(branchPath, filePath, offset, limit) {
|
|
|
1122
1202
|
...formatted
|
|
1123
1203
|
};
|
|
1124
1204
|
}
|
|
1125
|
-
function writeWorkerTextFile(branchPath, filePath, content) {
|
|
1126
|
-
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1205
|
+
function writeWorkerTextFile(branchPath, filePath, content, builtInPaths) {
|
|
1206
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
|
|
1127
1207
|
import_node_fs.default.mkdirSync(import_node_path.default.dirname(resolved.absolutePath), { recursive: true });
|
|
1128
1208
|
import_node_fs.default.writeFileSync(resolved.absolutePath, content, "utf8");
|
|
1129
1209
|
return {
|
|
@@ -1132,8 +1212,8 @@ function writeWorkerTextFile(branchPath, filePath, content) {
|
|
|
1132
1212
|
gitBlobHash: getGitBlobHashForContent(content)
|
|
1133
1213
|
};
|
|
1134
1214
|
}
|
|
1135
|
-
function editWorkerTextFile(branchPath, filePath, edits) {
|
|
1136
|
-
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1215
|
+
function editWorkerTextFile(branchPath, filePath, edits, builtInPaths) {
|
|
1216
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
|
|
1137
1217
|
if (!Array.isArray(edits) || edits.length === 0) {
|
|
1138
1218
|
throw new Error("edit requires at least one replacement");
|
|
1139
1219
|
}
|
|
@@ -1180,46 +1260,57 @@ function editWorkerTextFile(branchPath, filePath, edits) {
|
|
|
1180
1260
|
};
|
|
1181
1261
|
}
|
|
1182
1262
|
async function executeReadFileOperation(input) {
|
|
1183
|
-
const
|
|
1263
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1184
1264
|
filePath: input.message.filePath,
|
|
1185
1265
|
baseUrl: input.baseUrl,
|
|
1186
1266
|
token: input.token,
|
|
1187
1267
|
projectId: input.projectId,
|
|
1188
1268
|
branchName: input.message.branchName,
|
|
1189
1269
|
sessionId: input.message.sessionId,
|
|
1190
|
-
|
|
1270
|
+
activePlanId: input.message.activePlanId,
|
|
1271
|
+
artifactRoot: input.artifactRoot,
|
|
1272
|
+
planRoot: input.planRoot,
|
|
1273
|
+
access: "read"
|
|
1191
1274
|
});
|
|
1192
|
-
if (artifact) {
|
|
1193
|
-
if (!import_node_fs.default.existsSync(artifact.absolutePath) || !import_node_fs.default.statSync(artifact.absolutePath).isFile()) {
|
|
1194
|
-
throw new Error(`File not found: ${artifact.virtualPath}`);
|
|
1195
|
-
}
|
|
1196
|
-
const buffer = import_node_fs.default.readFileSync(artifact.absolutePath);
|
|
1197
|
-
const formatted = formatLineNumberedContent(buffer.toString("utf8"), input.message.offset, input.message.limit);
|
|
1198
|
-
return {
|
|
1199
|
-
type: "read",
|
|
1200
|
-
kind: "text",
|
|
1201
|
-
file: artifact.virtualPath,
|
|
1202
|
-
gitBlobHash: getGitBlobHashForContent(buffer),
|
|
1203
|
-
...formatted
|
|
1204
|
-
};
|
|
1205
|
-
}
|
|
1206
1275
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1207
|
-
return readWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.offset, input.message.limit);
|
|
1276
|
+
return readWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.offset, input.message.limit, builtInPaths);
|
|
1208
1277
|
}
|
|
1209
1278
|
async function executeWriteFileOperation(input) {
|
|
1210
|
-
|
|
1279
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1280
|
+
filePath: input.message.filePath,
|
|
1281
|
+
baseUrl: input.baseUrl,
|
|
1282
|
+
token: input.token,
|
|
1283
|
+
projectId: input.projectId,
|
|
1284
|
+
branchName: input.message.branchName,
|
|
1285
|
+
sessionId: input.message.sessionId,
|
|
1286
|
+
activePlanId: input.message.activePlanId,
|
|
1287
|
+
artifactRoot: input.artifactRoot,
|
|
1288
|
+
planRoot: input.planRoot,
|
|
1289
|
+
access: "write"
|
|
1290
|
+
});
|
|
1211
1291
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1212
|
-
const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath);
|
|
1292
|
+
const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath, builtInPaths);
|
|
1213
1293
|
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved), async () => {
|
|
1214
|
-
return writeWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.content);
|
|
1294
|
+
return writeWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.content, builtInPaths);
|
|
1215
1295
|
});
|
|
1216
1296
|
}
|
|
1217
1297
|
async function executeEditFileOperation(input) {
|
|
1218
|
-
|
|
1298
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1299
|
+
filePath: input.message.filePath,
|
|
1300
|
+
baseUrl: input.baseUrl,
|
|
1301
|
+
token: input.token,
|
|
1302
|
+
projectId: input.projectId,
|
|
1303
|
+
branchName: input.message.branchName,
|
|
1304
|
+
sessionId: input.message.sessionId,
|
|
1305
|
+
activePlanId: input.message.activePlanId,
|
|
1306
|
+
artifactRoot: input.artifactRoot,
|
|
1307
|
+
planRoot: input.planRoot,
|
|
1308
|
+
access: "write"
|
|
1309
|
+
});
|
|
1219
1310
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1220
|
-
const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath);
|
|
1311
|
+
const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath, builtInPaths);
|
|
1221
1312
|
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved), async () => {
|
|
1222
|
-
return editWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.edits);
|
|
1313
|
+
return editWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.edits, builtInPaths);
|
|
1223
1314
|
});
|
|
1224
1315
|
}
|
|
1225
1316
|
const IGNORED_ENTRY_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", ".cache", "coverage", "target"]);
|
|
@@ -1240,16 +1331,20 @@ function normalizeFindPattern(pattern) {
|
|
|
1240
1331
|
function walkWorkerEntries(branchPath, start) {
|
|
1241
1332
|
const entries = [];
|
|
1242
1333
|
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
1334
|
+
const realVirtualRoot = start.scope === "virtual" ? import_node_fs.default.realpathSync(start.virtualRootPath) : null;
|
|
1243
1335
|
const visit = (absolutePath, isRoot) => {
|
|
1244
1336
|
let stat;
|
|
1245
1337
|
try {
|
|
1246
1338
|
stat = import_node_fs.default.statSync(absolutePath);
|
|
1339
|
+
if (realVirtualRoot) {
|
|
1340
|
+
assertInsideRoot(realVirtualRoot, import_node_fs.default.realpathSync(absolutePath), `${start.displayPath} path`);
|
|
1341
|
+
}
|
|
1247
1342
|
} catch (error) {
|
|
1248
1343
|
if (isRoot) throw error;
|
|
1249
1344
|
return;
|
|
1250
1345
|
}
|
|
1251
|
-
const displayPath = start.scope === "project" ? toProjectDisplayPath(branchPath, absolutePath) : import_node_path.default.resolve(absolutePath);
|
|
1252
1346
|
const hostRelativePath = import_node_path.default.relative(start.absolutePath, absolutePath).split(import_node_path.default.sep).join(import_node_path.default.posix.sep);
|
|
1347
|
+
const displayPath = start.scope === "project" ? toProjectDisplayPath(branchPath, absolutePath) : start.scope === "virtual" ? hostRelativePath ? `${start.displayPath}/${hostRelativePath}` : start.displayPath : import_node_path.default.resolve(absolutePath);
|
|
1253
1348
|
const matchPath = start.scope === "project" ? displayPath : hostRelativePath || import_node_path.default.basename(start.absolutePath);
|
|
1254
1349
|
entries.push({ absolutePath, displayPath, matchPath, stat });
|
|
1255
1350
|
if (!stat.isDirectory()) return;
|
|
@@ -1280,8 +1375,8 @@ function walkWorkerEntries(branchPath, start) {
|
|
|
1280
1375
|
function isProbablyText(bytes) {
|
|
1281
1376
|
return !bytes.subarray(0, Math.min(bytes.length, 4096)).includes(0);
|
|
1282
1377
|
}
|
|
1283
|
-
function grepWorkerFiles(branchPath, input) {
|
|
1284
|
-
const start = resolveWorkerFilePath(branchPath, input.path ?? ".");
|
|
1378
|
+
function grepWorkerFiles(branchPath, input, builtInPaths) {
|
|
1379
|
+
const start = resolveWorkerFilePath(branchPath, input.path ?? ".", builtInPaths);
|
|
1285
1380
|
if (!import_node_fs.default.existsSync(start.absolutePath)) {
|
|
1286
1381
|
throw new Error(`Path not found: ${start.displayPath}`);
|
|
1287
1382
|
}
|
|
@@ -1327,17 +1422,34 @@ function grepWorkerFiles(branchPath, input) {
|
|
|
1327
1422
|
};
|
|
1328
1423
|
}
|
|
1329
1424
|
async function executeGrepOperation(input) {
|
|
1330
|
-
const
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1425
|
+
const toolPath = input.message.path ?? ".";
|
|
1426
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1427
|
+
filePath: toolPath,
|
|
1428
|
+
baseUrl: input.baseUrl,
|
|
1429
|
+
token: input.token,
|
|
1430
|
+
projectId: input.projectId,
|
|
1431
|
+
branchName: input.message.branchName,
|
|
1432
|
+
sessionId: input.message.sessionId,
|
|
1433
|
+
activePlanId: input.message.activePlanId,
|
|
1434
|
+
artifactRoot: input.artifactRoot,
|
|
1435
|
+
planRoot: input.planRoot,
|
|
1436
|
+
access: "read"
|
|
1337
1437
|
});
|
|
1438
|
+
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1439
|
+
return grepWorkerFiles(
|
|
1440
|
+
workspace.branchPath,
|
|
1441
|
+
{
|
|
1442
|
+
pattern: input.message.pattern,
|
|
1443
|
+
path: input.message.path,
|
|
1444
|
+
glob: input.message.glob,
|
|
1445
|
+
caseSensitive: input.message.caseSensitive,
|
|
1446
|
+
limit: input.message.limit
|
|
1447
|
+
},
|
|
1448
|
+
builtInPaths
|
|
1449
|
+
);
|
|
1338
1450
|
}
|
|
1339
|
-
function findWorkerFiles(branchPath, input) {
|
|
1340
|
-
const start = resolveWorkerFilePath(branchPath, input.path ?? ".");
|
|
1451
|
+
function findWorkerFiles(branchPath, input, builtInPaths) {
|
|
1452
|
+
const start = resolveWorkerFilePath(branchPath, input.path ?? ".", builtInPaths);
|
|
1341
1453
|
if (!import_node_fs.default.existsSync(start.absolutePath) || !import_node_fs.default.statSync(start.absolutePath).isDirectory()) {
|
|
1342
1454
|
throw new Error(`Directory not found: ${start.displayPath}`);
|
|
1343
1455
|
}
|
|
@@ -1371,16 +1483,33 @@ function findWorkerFiles(branchPath, input) {
|
|
|
1371
1483
|
};
|
|
1372
1484
|
}
|
|
1373
1485
|
async function executeFindOperation(input) {
|
|
1374
|
-
const
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1486
|
+
const toolPath = input.message.path ?? ".";
|
|
1487
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1488
|
+
filePath: toolPath,
|
|
1489
|
+
baseUrl: input.baseUrl,
|
|
1490
|
+
token: input.token,
|
|
1491
|
+
projectId: input.projectId,
|
|
1492
|
+
branchName: input.message.branchName,
|
|
1493
|
+
sessionId: input.message.sessionId,
|
|
1494
|
+
activePlanId: input.message.activePlanId,
|
|
1495
|
+
artifactRoot: input.artifactRoot,
|
|
1496
|
+
planRoot: input.planRoot,
|
|
1497
|
+
access: "read"
|
|
1380
1498
|
});
|
|
1499
|
+
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1500
|
+
return findWorkerFiles(
|
|
1501
|
+
workspace.branchPath,
|
|
1502
|
+
{
|
|
1503
|
+
pattern: input.message.pattern,
|
|
1504
|
+
path: input.message.path,
|
|
1505
|
+
entryType: input.message.entryType,
|
|
1506
|
+
limit: input.message.limit
|
|
1507
|
+
},
|
|
1508
|
+
builtInPaths
|
|
1509
|
+
);
|
|
1381
1510
|
}
|
|
1382
|
-
function listWorkerDirectory(branchPath, inputPath = ".", inputLimit) {
|
|
1383
|
-
const resolved = resolveWorkerFilePath(branchPath, inputPath);
|
|
1511
|
+
function listWorkerDirectory(branchPath, inputPath = ".", inputLimit, builtInPaths) {
|
|
1512
|
+
const resolved = resolveWorkerFilePath(branchPath, inputPath, builtInPaths);
|
|
1384
1513
|
if (!import_node_fs.default.existsSync(resolved.absolutePath) || !import_node_fs.default.statSync(resolved.absolutePath).isDirectory()) {
|
|
1385
1514
|
throw new Error(`Directory not found: ${resolved.displayPath}`);
|
|
1386
1515
|
}
|
|
@@ -1398,11 +1527,24 @@ function listWorkerDirectory(branchPath, inputPath = ".", inputLimit) {
|
|
|
1398
1527
|
};
|
|
1399
1528
|
}
|
|
1400
1529
|
async function executeLsOperation(input) {
|
|
1530
|
+
const toolPath = input.message.path ?? ".";
|
|
1531
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1532
|
+
filePath: toolPath,
|
|
1533
|
+
baseUrl: input.baseUrl,
|
|
1534
|
+
token: input.token,
|
|
1535
|
+
projectId: input.projectId,
|
|
1536
|
+
branchName: input.message.branchName,
|
|
1537
|
+
sessionId: input.message.sessionId,
|
|
1538
|
+
activePlanId: input.message.activePlanId,
|
|
1539
|
+
artifactRoot: input.artifactRoot,
|
|
1540
|
+
planRoot: input.planRoot,
|
|
1541
|
+
access: "read"
|
|
1542
|
+
});
|
|
1401
1543
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1402
|
-
return listWorkerDirectory(workspace.branchPath, input.message.path, input.message.limit);
|
|
1544
|
+
return listWorkerDirectory(workspace.branchPath, input.message.path, input.message.limit, builtInPaths);
|
|
1403
1545
|
}
|
|
1404
|
-
function readWorkerImageFile(branchPath, filePath) {
|
|
1405
|
-
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1546
|
+
function readWorkerImageFile(branchPath, filePath, builtInPaths) {
|
|
1547
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
|
|
1406
1548
|
if (!import_node_fs.default.existsSync(resolved.absolutePath) || !import_node_fs.default.statSync(resolved.absolutePath).isFile()) {
|
|
1407
1549
|
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
1408
1550
|
}
|
|
@@ -1424,38 +1566,20 @@ function readWorkerImageFile(branchPath, filePath) {
|
|
|
1424
1566
|
};
|
|
1425
1567
|
}
|
|
1426
1568
|
async function executeViewFileBytesOperation(input) {
|
|
1427
|
-
const
|
|
1569
|
+
const builtInPaths = await prepareBuiltInToolPaths({
|
|
1428
1570
|
filePath: input.message.filePath,
|
|
1429
1571
|
baseUrl: input.baseUrl,
|
|
1430
1572
|
token: input.token,
|
|
1431
1573
|
projectId: input.projectId,
|
|
1432
1574
|
branchName: input.message.branchName,
|
|
1433
1575
|
sessionId: input.message.sessionId,
|
|
1434
|
-
|
|
1576
|
+
activePlanId: input.message.activePlanId,
|
|
1577
|
+
artifactRoot: input.artifactRoot,
|
|
1578
|
+
planRoot: input.planRoot,
|
|
1579
|
+
access: "read"
|
|
1435
1580
|
});
|
|
1436
|
-
if (artifact) {
|
|
1437
|
-
if (!import_node_fs.default.existsSync(artifact.absolutePath) || !import_node_fs.default.statSync(artifact.absolutePath).isFile()) {
|
|
1438
|
-
throw new Error(`File not found: ${artifact.virtualPath}`);
|
|
1439
|
-
}
|
|
1440
|
-
const extension = import_node_path.default.extname(artifact.absolutePath).toLowerCase();
|
|
1441
|
-
const mediaType = extension === ".jpg" || extension === ".jpeg" ? "image/jpeg" : extension === ".png" ? "image/png" : null;
|
|
1442
|
-
if (!mediaType) {
|
|
1443
|
-
throw new Error("read supports PNG and JPEG image inputs only");
|
|
1444
|
-
}
|
|
1445
|
-
const bytes = import_node_fs.default.readFileSync(artifact.absolutePath);
|
|
1446
|
-
const dimensions = readImageDimensions(bytes, mediaType);
|
|
1447
|
-
return {
|
|
1448
|
-
type: "view_file_bytes",
|
|
1449
|
-
filePath: artifact.virtualPath,
|
|
1450
|
-
mediaType,
|
|
1451
|
-
base64: bytes.toString("base64"),
|
|
1452
|
-
fileSizeBytes: bytes.length,
|
|
1453
|
-
width: dimensions.width,
|
|
1454
|
-
height: dimensions.height
|
|
1455
|
-
};
|
|
1456
|
-
}
|
|
1457
1581
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1458
|
-
return readWorkerImageFile(workspace.branchPath, input.message.filePath);
|
|
1582
|
+
return readWorkerImageFile(workspace.branchPath, input.message.filePath, builtInPaths);
|
|
1459
1583
|
}
|
|
1460
1584
|
async function executeOperation(input) {
|
|
1461
1585
|
switch (input.message.type) {
|
|
@@ -2094,6 +2218,7 @@ async function startWorker(options) {
|
|
|
2094
2218
|
import_node_fs.default.mkdirSync(artifactRoot, { recursive: true });
|
|
2095
2219
|
import_node_fs.default.mkdirSync(planRoot, { recursive: true });
|
|
2096
2220
|
const manifestByProjectId = /* @__PURE__ */ new Map();
|
|
2221
|
+
const pendingManifestCheckouts = /* @__PURE__ */ new Map();
|
|
2097
2222
|
const workspaceSyncSingleFlight = new import_workspace_sync.WorkspaceSyncSingleFlight();
|
|
2098
2223
|
let workspaceRemoteUrl = null;
|
|
2099
2224
|
let activeWorkspaceIncidentId = null;
|
|
@@ -2122,37 +2247,64 @@ async function startWorker(options) {
|
|
|
2122
2247
|
const sendWorkspaceSyncResult = (requestId, result) => {
|
|
2123
2248
|
sendWorkerMessage(ws, { type: "workspace_sync_result", requestId, result });
|
|
2124
2249
|
};
|
|
2125
|
-
const
|
|
2250
|
+
const manifestCheckoutKey = (projectId, branchName) => `${projectId}\0${branchName}`;
|
|
2251
|
+
const allManifestCheckouts = () => [...manifestByProjectId.values()].flatMap(
|
|
2252
|
+
(manifest) => manifest.branches.map((branchName) => ({ projectId: manifest.projectId, branchName }))
|
|
2253
|
+
);
|
|
2254
|
+
const ensureVisibleWorkspaceCheckouts = (targets) => {
|
|
2126
2255
|
const created = [];
|
|
2127
|
-
for (const
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
projectRoot: projectRootFor(projectsRoot, manifest.projectId, manifestByProjectId),
|
|
2134
|
-
branchName,
|
|
2135
|
-
githubRemoteUrl,
|
|
2136
|
-
githubAuth: gitAuthForRemote(githubRemoteUrl, manifest.repoAuthHeader),
|
|
2137
|
-
githubAuthHeader: manifest.repoAuthHeader,
|
|
2138
|
-
defaultBranch: manifest.defaultBranch || "main",
|
|
2139
|
-
reconcileExistingOrigin: Boolean(manifest.repoHttpUrl)
|
|
2140
|
-
});
|
|
2141
|
-
if (!existed) created.push({ projectId: manifest.projectId, branchName });
|
|
2256
|
+
for (const target of targets) {
|
|
2257
|
+
const manifest = manifestByProjectId.get(target.projectId);
|
|
2258
|
+
const key = manifestCheckoutKey(target.projectId, target.branchName);
|
|
2259
|
+
if (!manifest || !manifest.branches.includes(target.branchName)) {
|
|
2260
|
+
pendingManifestCheckouts.delete(key);
|
|
2261
|
+
continue;
|
|
2142
2262
|
}
|
|
2263
|
+
const projectRoot = projectRootFor(projectsRoot, manifest.projectId, manifestByProjectId);
|
|
2264
|
+
const branchPath = import_node_path.default.join(projectRoot, target.branchName);
|
|
2265
|
+
const existed = hasNormalVisibleGitDir(branchPath);
|
|
2266
|
+
const githubRemoteUrl = githubRemoteUrlFor(baseUrl, manifest.projectId, manifest);
|
|
2267
|
+
ensureVisibleGitCheckout({
|
|
2268
|
+
projectRoot,
|
|
2269
|
+
branchName: target.branchName,
|
|
2270
|
+
githubRemoteUrl,
|
|
2271
|
+
githubAuth: gitAuthForRemote(githubRemoteUrl, manifest.repoAuthHeader),
|
|
2272
|
+
githubAuthHeader: manifest.repoAuthHeader,
|
|
2273
|
+
defaultBranch: manifest.defaultBranch || "main",
|
|
2274
|
+
reconcileExistingOrigin: Boolean(manifest.repoHttpUrl)
|
|
2275
|
+
});
|
|
2276
|
+
if (!existed) created.push(target);
|
|
2143
2277
|
}
|
|
2144
2278
|
return created;
|
|
2145
2279
|
};
|
|
2146
2280
|
const runRequestedWorkspaceSync = async (requestId, trigger, overrides = {}) => {
|
|
2147
2281
|
workspaceSyncRequestsInFlight += 1;
|
|
2148
2282
|
try {
|
|
2149
|
-
|
|
2150
|
-
const result = await workspaceSyncSingleFlight.
|
|
2151
|
-
|
|
2283
|
+
let pendingTargets = [];
|
|
2284
|
+
const result = await workspaceSyncSingleFlight.runPrepared(() => {
|
|
2285
|
+
const shouldEnsureCheckouts = !overrides.skipVisibleMirror && !overrides.resetToCanonical;
|
|
2286
|
+
let checkoutTargets = [];
|
|
2287
|
+
if (shouldEnsureCheckouts) {
|
|
2288
|
+
checkoutTargets = trigger.type === "connect" ? allManifestCheckouts() : [...pendingManifestCheckouts.values()];
|
|
2289
|
+
}
|
|
2290
|
+
pendingTargets = shouldEnsureCheckouts ? [...pendingManifestCheckouts.values()] : [];
|
|
2291
|
+
const createdCheckouts = ensureVisibleWorkspaceCheckouts(checkoutTargets);
|
|
2292
|
+
const newVisibleCheckoutByKey = /* @__PURE__ */ new Map();
|
|
2293
|
+
for (const target of [...overrides.newVisibleCheckouts ?? [], ...createdCheckouts, ...pendingTargets]) {
|
|
2294
|
+
newVisibleCheckoutByKey.set(manifestCheckoutKey(target.projectId, target.branchName), target);
|
|
2295
|
+
}
|
|
2296
|
+
const newVisibleCheckouts = [...newVisibleCheckoutByKey.values()];
|
|
2297
|
+
return workspaceSyncInput(trigger, {
|
|
2152
2298
|
...overrides,
|
|
2153
2299
|
...newVisibleCheckouts.length > 0 ? { newVisibleCheckouts } : {}
|
|
2154
|
-
})
|
|
2155
|
-
);
|
|
2300
|
+
});
|
|
2301
|
+
});
|
|
2302
|
+
if (result.outcome === "no_change" || result.outcome === "published" || result.outcome === "updated" || result.outcome === "conflict_reset") {
|
|
2303
|
+
for (const target of pendingTargets) {
|
|
2304
|
+
const key = manifestCheckoutKey(target.projectId, target.branchName);
|
|
2305
|
+
if (pendingManifestCheckouts.get(key) === target) pendingManifestCheckouts.delete(key);
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2156
2308
|
previousPeriodicFingerprint = null;
|
|
2157
2309
|
sendWorkspaceSyncResult(requestId, result);
|
|
2158
2310
|
return result;
|
|
@@ -2245,9 +2397,26 @@ async function startWorker(options) {
|
|
|
2245
2397
|
configureGitHubAuth(message.githubCredential);
|
|
2246
2398
|
visibleGitIdentity = message.gitIdentity;
|
|
2247
2399
|
workspaceRemoteUrl = message.workspaceRemoteUrl;
|
|
2400
|
+
const previousCheckoutKeys = new Set(
|
|
2401
|
+
allManifestCheckouts().map(({ projectId, branchName }) => manifestCheckoutKey(projectId, branchName))
|
|
2402
|
+
);
|
|
2248
2403
|
const migratedProjectIds = (0, import_managed_paths.migrateLegacyProjectRoots)(projectsRoot, message.projects);
|
|
2249
2404
|
manifestByProjectId.clear();
|
|
2250
2405
|
for (const project of message.projects) manifestByProjectId.set(project.projectId, project);
|
|
2406
|
+
const currentCheckouts = allManifestCheckouts();
|
|
2407
|
+
const currentCheckoutKeys = new Set(
|
|
2408
|
+
currentCheckouts.map(({ projectId, branchName }) => manifestCheckoutKey(projectId, branchName))
|
|
2409
|
+
);
|
|
2410
|
+
for (const key of pendingManifestCheckouts.keys()) {
|
|
2411
|
+
if (!currentCheckoutKeys.has(key)) pendingManifestCheckouts.delete(key);
|
|
2412
|
+
}
|
|
2413
|
+
for (const checkout of currentCheckouts) {
|
|
2414
|
+
const key = manifestCheckoutKey(checkout.projectId, checkout.branchName);
|
|
2415
|
+
if (previousCheckoutKeys.has(key)) continue;
|
|
2416
|
+
const branchPath = import_node_path.default.join(projectRootFor(projectsRoot, checkout.projectId, manifestByProjectId), checkout.branchName);
|
|
2417
|
+
if (!hasNormalVisibleGitDir(branchPath)) pendingManifestCheckouts.set(key, checkout);
|
|
2418
|
+
}
|
|
2419
|
+
previousPeriodicFingerprint = null;
|
|
2251
2420
|
process.stdout.write(
|
|
2252
2421
|
`[r5d-worker] workspace manifest: ${message.projects.length} projects${migratedProjectIds.length > 0 ? `; migrated ${migratedProjectIds.length} project checkout root(s)` : ""}
|
|
2253
2422
|
`
|
|
@@ -2258,8 +2427,15 @@ async function startWorker(options) {
|
|
|
2258
2427
|
if (!workspaceRemoteUrl || activeWorkspaceIncidentId || cliUpdateInProgress || periodicWorkspaceScanInFlight) return;
|
|
2259
2428
|
periodicWorkspaceScanInFlight = true;
|
|
2260
2429
|
void (async () => {
|
|
2261
|
-
|
|
2262
|
-
|
|
2430
|
+
if (pendingManifestCheckouts.size > 0) {
|
|
2431
|
+
previousPeriodicFingerprint = null;
|
|
2432
|
+
await runRequestedWorkspaceSync(crypto.randomUUID(), {
|
|
2433
|
+
type: "periodic",
|
|
2434
|
+
detail: "workspace manifest additions"
|
|
2435
|
+
});
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2438
|
+
const fingerprint = await workspaceSyncSingleFlight.fingerprintPrepared(() => workspaceSyncInput({ type: "periodic" }));
|
|
2263
2439
|
if (fingerprint === emptyFingerprint) {
|
|
2264
2440
|
previousPeriodicFingerprint = null;
|
|
2265
2441
|
return;
|
|
@@ -2526,6 +2702,7 @@ async function startWorker(options) {
|
|
|
2526
2702
|
projectRoot,
|
|
2527
2703
|
syncRoot,
|
|
2528
2704
|
artifactRoot,
|
|
2705
|
+
planRoot,
|
|
2529
2706
|
manifest
|
|
2530
2707
|
});
|
|
2531
2708
|
ws.send(
|
package/dist/cjs/package.json
CHANGED