@ricsam/r5d-worker 0.0.40 → 0.0.42
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 +78 -137
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/main.mjs +75 -134
- package/dist/mjs/package.json +1 -1
- package/dist/types/main.d.ts +16 -13
- package/dist/types/workspace-sync.d.ts +1 -1
- package/package.json +1 -1
package/dist/cjs/main.cjs
CHANGED
|
@@ -29,6 +29,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
29
29
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
30
|
var main_exports = {};
|
|
31
31
|
__export(main_exports, {
|
|
32
|
+
editWorkerTextFile: () => editWorkerTextFile,
|
|
32
33
|
ensureVisibleGitCheckout: () => ensureVisibleGitCheckout,
|
|
33
34
|
findWorkerFiles: () => findWorkerFiles,
|
|
34
35
|
githubCliEnv: () => githubCliEnv,
|
|
@@ -40,10 +41,9 @@ __export(main_exports, {
|
|
|
40
41
|
readWorkerImageFile: () => readWorkerImageFile,
|
|
41
42
|
readWorkerTextFile: () => readWorkerTextFile,
|
|
42
43
|
resolveHostShell: () => resolveHostShell,
|
|
43
|
-
resolveProjectFilePath: () => resolveProjectFilePath,
|
|
44
44
|
resolveWorkerFilePath: () => resolveWorkerFilePath,
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
syncSessionArtifacts: () => syncSessionArtifacts,
|
|
46
|
+
writeWorkerTextFile: () => writeWorkerTextFile
|
|
47
47
|
});
|
|
48
48
|
module.exports = __toCommonJS(main_exports);
|
|
49
49
|
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
@@ -335,15 +335,6 @@ function artifactManifestUrl(baseUrl, projectId, branchName, sessionId) {
|
|
|
335
335
|
function artifactDownloadUrl(baseUrl, projectId, branchName, sessionId, filename) {
|
|
336
336
|
return new URL(`${artifactApiBasePath(projectId, branchName, sessionId)}/${encodeURIComponent(filename)}`, baseUrl);
|
|
337
337
|
}
|
|
338
|
-
function planApiBasePath(projectId, branchName) {
|
|
339
|
-
return `/preview-api/${encodeURIComponent(projectId)}/${encodeURIComponent(branchName)}/plans`;
|
|
340
|
-
}
|
|
341
|
-
function planManifestUrl(baseUrl, projectId, branchName) {
|
|
342
|
-
return new URL(planApiBasePath(projectId, branchName), baseUrl);
|
|
343
|
-
}
|
|
344
|
-
function planDownloadUrl(baseUrl, projectId, branchName, filename) {
|
|
345
|
-
return new URL(`${planApiBasePath(projectId, branchName)}/${encodeURIComponent(filename)}`, baseUrl);
|
|
346
|
-
}
|
|
347
338
|
function sha256Path(filePath) {
|
|
348
339
|
return (0, import_node_crypto.createHash)("sha256").update(import_node_fs.default.readFileSync(filePath)).digest("hex");
|
|
349
340
|
}
|
|
@@ -456,30 +447,6 @@ async function syncSessionArtifacts(input) {
|
|
|
456
447
|
})
|
|
457
448
|
});
|
|
458
449
|
}
|
|
459
|
-
async function fetchProjectPlanManifest(input) {
|
|
460
|
-
return fetchRemoteFileManifest({
|
|
461
|
-
url: planManifestUrl(input.baseUrl, input.projectId, input.branchName),
|
|
462
|
-
token: input.token,
|
|
463
|
-
collectionKey: "plans",
|
|
464
|
-
label: "plan"
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
async function syncProjectPlans(input) {
|
|
468
|
-
const targetDir = projectPlanDir(input.planRoot, input.projectId, input.branchName);
|
|
469
|
-
const plans = await fetchProjectPlanManifest(input);
|
|
470
|
-
return syncRemoteFileSet({
|
|
471
|
-
targetDir,
|
|
472
|
-
files: plans,
|
|
473
|
-
label: "Plan path",
|
|
474
|
-
download: (plan) => downloadRemoteSyncedFile({
|
|
475
|
-
url: planDownloadUrl(input.baseUrl, input.projectId, input.branchName, plan.filename),
|
|
476
|
-
token: input.token,
|
|
477
|
-
targetDir,
|
|
478
|
-
file: plan,
|
|
479
|
-
label: "plan"
|
|
480
|
-
})
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
450
|
async function resolveArtifactEnvFilePath(input) {
|
|
484
451
|
if (!isArtifactEnvPath(input.filePath)) {
|
|
485
452
|
return null;
|
|
@@ -539,15 +506,7 @@ async function prepareArtifactEnvForShell(input) {
|
|
|
539
506
|
R5D_ARTIFACTS_DIR: artifactsDir
|
|
540
507
|
};
|
|
541
508
|
}
|
|
542
|
-
|
|
543
|
-
try {
|
|
544
|
-
await syncProjectPlans(input);
|
|
545
|
-
} catch (error) {
|
|
546
|
-
process.stderr.write(
|
|
547
|
-
`[r5d-worker] plan sync skipped for ${input.projectId}/${input.branchName}: ${error instanceof Error ? error.message : String(error)}
|
|
548
|
-
`
|
|
549
|
-
);
|
|
550
|
-
}
|
|
509
|
+
function preparePlanEnvForShell(input) {
|
|
551
510
|
const plansDir = projectPlanDir(input.planRoot, input.projectId, input.branchName);
|
|
552
511
|
import_node_fs.default.mkdirSync(plansDir, { recursive: true });
|
|
553
512
|
return planEnv(input.planRoot, input.projectId, input.branchName, input.activePlanId);
|
|
@@ -668,9 +627,6 @@ function toProjectDisplayPath(branchPath, absolutePath) {
|
|
|
668
627
|
return relative || ".";
|
|
669
628
|
}
|
|
670
629
|
function assertAllowedProjectPath(repoRelativePath, inputPath) {
|
|
671
|
-
if (repoRelativePath === ".git" || repoRelativePath.startsWith(".git/")) {
|
|
672
|
-
throw new Error("Refusing to access .git through worker file tools");
|
|
673
|
-
}
|
|
674
630
|
if (repoRelativePath.split("/").includes("..")) {
|
|
675
631
|
throw new Error(`Invalid project file path: ${inputPath}`);
|
|
676
632
|
}
|
|
@@ -718,13 +674,6 @@ function resolveWorkerFilePath(branchPath, inputPath) {
|
|
|
718
674
|
scope: "project"
|
|
719
675
|
};
|
|
720
676
|
}
|
|
721
|
-
function resolveProjectFilePath(branchPath, inputPath) {
|
|
722
|
-
const resolved = resolveWorkerFilePath(branchPath, inputPath);
|
|
723
|
-
if (resolved.scope === "host") {
|
|
724
|
-
throw new Error(`Project file writes must use a checkout-relative path. Absolute host paths are read-only in file tools: ${inputPath}`);
|
|
725
|
-
}
|
|
726
|
-
return resolved;
|
|
727
|
-
}
|
|
728
677
|
function fallbackInternalRemoteUrl(baseUrl, projectId) {
|
|
729
678
|
return new URL(`/git/${projectId}.git`, baseUrl).toString();
|
|
730
679
|
}
|
|
@@ -1141,8 +1090,11 @@ async function withFileMutationQueue(key, operation) {
|
|
|
1141
1090
|
}
|
|
1142
1091
|
}
|
|
1143
1092
|
}
|
|
1144
|
-
function mutationQueueKey(projectId, branchName,
|
|
1145
|
-
|
|
1093
|
+
function mutationQueueKey(projectId, branchName, resolved) {
|
|
1094
|
+
if (resolved.scope === "host") {
|
|
1095
|
+
return `host:${import_node_path.default.normalize(resolved.absolutePath)}`;
|
|
1096
|
+
}
|
|
1097
|
+
return `project:${projectId}:${branchName}:${import_node_path.default.posix.normalize(resolved.repoRelativePath)}`;
|
|
1146
1098
|
}
|
|
1147
1099
|
function readWorkerTextFile(branchPath, filePath, offset, limit) {
|
|
1148
1100
|
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
@@ -1159,6 +1111,63 @@ function readWorkerTextFile(branchPath, filePath, offset, limit) {
|
|
|
1159
1111
|
...formatted
|
|
1160
1112
|
};
|
|
1161
1113
|
}
|
|
1114
|
+
function writeWorkerTextFile(branchPath, filePath, content) {
|
|
1115
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1116
|
+
import_node_fs.default.mkdirSync(import_node_path.default.dirname(resolved.absolutePath), { recursive: true });
|
|
1117
|
+
import_node_fs.default.writeFileSync(resolved.absolutePath, content, "utf8");
|
|
1118
|
+
return {
|
|
1119
|
+
type: "write",
|
|
1120
|
+
file: resolved.displayPath,
|
|
1121
|
+
gitBlobHash: getGitBlobHashForContent(content)
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
function editWorkerTextFile(branchPath, filePath, edits) {
|
|
1125
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1126
|
+
if (!Array.isArray(edits) || edits.length === 0) {
|
|
1127
|
+
throw new Error("edit requires at least one replacement");
|
|
1128
|
+
}
|
|
1129
|
+
if (!import_node_fs.default.existsSync(resolved.absolutePath) || !import_node_fs.default.statSync(resolved.absolutePath).isFile()) {
|
|
1130
|
+
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
1131
|
+
}
|
|
1132
|
+
const originalContent = import_node_fs.default.readFileSync(resolved.absolutePath, "utf8");
|
|
1133
|
+
const ranges = edits.map((edit, index) => {
|
|
1134
|
+
if (edit.oldText === edit.newText) {
|
|
1135
|
+
throw new Error(`edits[${index}].oldText and edits[${index}].newText must be different`);
|
|
1136
|
+
}
|
|
1137
|
+
if (edit.oldText.length === 0) {
|
|
1138
|
+
throw new Error(`edits[${index}].oldText cannot be empty`);
|
|
1139
|
+
}
|
|
1140
|
+
const occurrences = originalContent.split(edit.oldText).length - 1;
|
|
1141
|
+
if (occurrences === 0) {
|
|
1142
|
+
throw new Error(`Could not find edits[${index}].oldText: "${edit.oldText.slice(0, 100)}${edit.oldText.length > 100 ? "..." : ""}"`);
|
|
1143
|
+
}
|
|
1144
|
+
if (occurrences > 1) {
|
|
1145
|
+
throw new Error(
|
|
1146
|
+
`edits[${index}].oldText is not unique in the file (found ${occurrences} occurrences). Include more surrounding context.`
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
const start = originalContent.indexOf(edit.oldText);
|
|
1150
|
+
return { index, start, end: start + edit.oldText.length, edit };
|
|
1151
|
+
});
|
|
1152
|
+
const sortedRanges = [...ranges].sort((a, b) => a.start - b.start);
|
|
1153
|
+
for (let index = 1; index < sortedRanges.length; index += 1) {
|
|
1154
|
+
const previous = sortedRanges[index - 1];
|
|
1155
|
+
const current = sortedRanges[index];
|
|
1156
|
+
if (current.start < previous.end) {
|
|
1157
|
+
throw new Error(`edits[${current.index}] overlaps edits[${previous.index}]. Merge nearby changes into one replacement.`);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
let nextContent = originalContent;
|
|
1161
|
+
for (const range of [...sortedRanges].reverse()) {
|
|
1162
|
+
nextContent = nextContent.slice(0, range.start) + range.edit.newText + nextContent.slice(range.end);
|
|
1163
|
+
}
|
|
1164
|
+
import_node_fs.default.writeFileSync(resolved.absolutePath, nextContent, "utf8");
|
|
1165
|
+
return {
|
|
1166
|
+
type: "edit",
|
|
1167
|
+
file: resolved.displayPath,
|
|
1168
|
+
edits: edits.length
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1162
1171
|
async function executeReadFileOperation(input) {
|
|
1163
1172
|
const artifact = await resolveArtifactEnvFilePath({
|
|
1164
1173
|
filePath: input.message.filePath,
|
|
@@ -1189,66 +1198,17 @@ async function executeReadFileOperation(input) {
|
|
|
1189
1198
|
async function executeWriteFileOperation(input) {
|
|
1190
1199
|
rejectArtifactWritePath(input.message.filePath);
|
|
1191
1200
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1192
|
-
const resolved =
|
|
1193
|
-
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved
|
|
1194
|
-
|
|
1195
|
-
import_node_fs.default.writeFileSync(resolved.absolutePath, input.message.content, "utf8");
|
|
1196
|
-
return {
|
|
1197
|
-
type: "write",
|
|
1198
|
-
file: resolved.displayPath,
|
|
1199
|
-
gitBlobHash: getGitBlobHashForContent(input.message.content)
|
|
1200
|
-
};
|
|
1201
|
+
const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath);
|
|
1202
|
+
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved), async () => {
|
|
1203
|
+
return writeWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.content);
|
|
1201
1204
|
});
|
|
1202
1205
|
}
|
|
1203
1206
|
async function executeEditFileOperation(input) {
|
|
1204
1207
|
rejectArtifactWritePath(input.message.filePath);
|
|
1205
1208
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1206
|
-
const resolved =
|
|
1207
|
-
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved
|
|
1208
|
-
|
|
1209
|
-
throw new Error("edit requires at least one replacement");
|
|
1210
|
-
}
|
|
1211
|
-
if (!import_node_fs.default.existsSync(resolved.absolutePath) || !import_node_fs.default.statSync(resolved.absolutePath).isFile()) {
|
|
1212
|
-
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
1213
|
-
}
|
|
1214
|
-
const originalContent = import_node_fs.default.readFileSync(resolved.absolutePath, "utf8");
|
|
1215
|
-
const ranges = input.message.edits.map((edit, index) => {
|
|
1216
|
-
if (edit.oldText === edit.newText) {
|
|
1217
|
-
throw new Error(`edits[${index}].oldText and edits[${index}].newText must be different`);
|
|
1218
|
-
}
|
|
1219
|
-
if (edit.oldText.length === 0) {
|
|
1220
|
-
throw new Error(`edits[${index}].oldText cannot be empty`);
|
|
1221
|
-
}
|
|
1222
|
-
const occurrences = originalContent.split(edit.oldText).length - 1;
|
|
1223
|
-
if (occurrences === 0) {
|
|
1224
|
-
throw new Error(`Could not find edits[${index}].oldText: "${edit.oldText.slice(0, 100)}${edit.oldText.length > 100 ? "..." : ""}"`);
|
|
1225
|
-
}
|
|
1226
|
-
if (occurrences > 1) {
|
|
1227
|
-
throw new Error(
|
|
1228
|
-
`edits[${index}].oldText is not unique in the file (found ${occurrences} occurrences). Include more surrounding context.`
|
|
1229
|
-
);
|
|
1230
|
-
}
|
|
1231
|
-
const start = originalContent.indexOf(edit.oldText);
|
|
1232
|
-
return { index, start, end: start + edit.oldText.length, edit };
|
|
1233
|
-
});
|
|
1234
|
-
const sortedRanges = [...ranges].sort((a, b) => a.start - b.start);
|
|
1235
|
-
for (let index = 1; index < sortedRanges.length; index += 1) {
|
|
1236
|
-
const previous = sortedRanges[index - 1];
|
|
1237
|
-
const current = sortedRanges[index];
|
|
1238
|
-
if (current.start < previous.end) {
|
|
1239
|
-
throw new Error(`edits[${current.index}] overlaps edits[${previous.index}]. Merge nearby changes into one replacement.`);
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
let nextContent = originalContent;
|
|
1243
|
-
for (const range of [...sortedRanges].reverse()) {
|
|
1244
|
-
nextContent = nextContent.slice(0, range.start) + range.edit.newText + nextContent.slice(range.end);
|
|
1245
|
-
}
|
|
1246
|
-
import_node_fs.default.writeFileSync(resolved.absolutePath, nextContent, "utf8");
|
|
1247
|
-
return {
|
|
1248
|
-
type: "edit",
|
|
1249
|
-
file: resolved.displayPath,
|
|
1250
|
-
edits: input.message.edits.length
|
|
1251
|
-
};
|
|
1209
|
+
const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath);
|
|
1210
|
+
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved), async () => {
|
|
1211
|
+
return editWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.edits);
|
|
1252
1212
|
});
|
|
1253
1213
|
}
|
|
1254
1214
|
const IGNORED_ENTRY_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", ".cache", "coverage", "target"]);
|
|
@@ -1525,9 +1485,7 @@ async function executeCommand(input) {
|
|
|
1525
1485
|
sessionId: input.message.sessionId,
|
|
1526
1486
|
artifactRoot: input.artifactRoot
|
|
1527
1487
|
});
|
|
1528
|
-
const planProcessEnv =
|
|
1529
|
-
baseUrl: input.baseUrl,
|
|
1530
|
-
token: input.token,
|
|
1488
|
+
const planProcessEnv = preparePlanEnvForShell({
|
|
1531
1489
|
projectId: input.projectId,
|
|
1532
1490
|
branchName: input.message.branchName,
|
|
1533
1491
|
planRoot: input.planRoot,
|
|
@@ -1636,9 +1594,7 @@ async function executeStreamingCommand(input) {
|
|
|
1636
1594
|
sessionId: input.message.sessionId,
|
|
1637
1595
|
artifactRoot: input.artifactRoot
|
|
1638
1596
|
});
|
|
1639
|
-
const planProcessEnv =
|
|
1640
|
-
baseUrl: input.baseUrl,
|
|
1641
|
-
token: input.token,
|
|
1597
|
+
const planProcessEnv = preparePlanEnvForShell({
|
|
1642
1598
|
projectId: input.projectId,
|
|
1643
1599
|
branchName: input.message.branchName,
|
|
1644
1600
|
planRoot: input.planRoot,
|
|
@@ -1982,9 +1938,7 @@ async function openPty(input) {
|
|
|
1982
1938
|
branchName: input.message.branchName,
|
|
1983
1939
|
manifest: input.manifest
|
|
1984
1940
|
});
|
|
1985
|
-
const planProcessEnv =
|
|
1986
|
-
baseUrl: input.baseUrl,
|
|
1987
|
-
token: input.token,
|
|
1941
|
+
const planProcessEnv = preparePlanEnvForShell({
|
|
1988
1942
|
projectId: input.projectId,
|
|
1989
1943
|
branchName: input.message.branchName,
|
|
1990
1944
|
planRoot: input.planRoot,
|
|
@@ -2180,19 +2134,6 @@ async function startWorker(options) {
|
|
|
2180
2134
|
const runRequestedWorkspaceSync = async (requestId, trigger, overrides = {}) => {
|
|
2181
2135
|
workspaceSyncRequestsInFlight += 1;
|
|
2182
2136
|
try {
|
|
2183
|
-
if (trigger.type === "plan_write" || trigger.type === "task_status" || trigger.type === "connect" || trigger.type === "inbound_head" || trigger.type === "manual") {
|
|
2184
|
-
for (const manifest of manifestByProjectId.values()) {
|
|
2185
|
-
for (const branchName of manifest.branches) {
|
|
2186
|
-
await syncProjectPlans({
|
|
2187
|
-
baseUrl,
|
|
2188
|
-
token,
|
|
2189
|
-
projectId: manifest.projectId,
|
|
2190
|
-
branchName,
|
|
2191
|
-
planRoot
|
|
2192
|
-
});
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
2137
|
const newVisibleCheckouts = trigger.type === "connect" ? ensureAllVisibleWorkspaceCheckouts() : [];
|
|
2197
2138
|
const result = await workspaceSyncSingleFlight.run(
|
|
2198
2139
|
workspaceSyncInput(trigger, {
|
|
@@ -2673,6 +2614,7 @@ if (isCliEntrypoint()) {
|
|
|
2673
2614
|
}
|
|
2674
2615
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2675
2616
|
0 && (module.exports = {
|
|
2617
|
+
editWorkerTextFile,
|
|
2676
2618
|
ensureVisibleGitCheckout,
|
|
2677
2619
|
findWorkerFiles,
|
|
2678
2620
|
githubCliEnv,
|
|
@@ -2684,8 +2626,7 @@ if (isCliEntrypoint()) {
|
|
|
2684
2626
|
readWorkerImageFile,
|
|
2685
2627
|
readWorkerTextFile,
|
|
2686
2628
|
resolveHostShell,
|
|
2687
|
-
resolveProjectFilePath,
|
|
2688
2629
|
resolveWorkerFilePath,
|
|
2689
|
-
|
|
2690
|
-
|
|
2630
|
+
syncSessionArtifacts,
|
|
2631
|
+
writeWorkerTextFile
|
|
2691
2632
|
});
|
package/dist/cjs/package.json
CHANGED
package/dist/mjs/main.mjs
CHANGED
|
@@ -299,15 +299,6 @@ function artifactManifestUrl(baseUrl, projectId, branchName, sessionId) {
|
|
|
299
299
|
function artifactDownloadUrl(baseUrl, projectId, branchName, sessionId, filename) {
|
|
300
300
|
return new URL(`${artifactApiBasePath(projectId, branchName, sessionId)}/${encodeURIComponent(filename)}`, baseUrl);
|
|
301
301
|
}
|
|
302
|
-
function planApiBasePath(projectId, branchName) {
|
|
303
|
-
return `/preview-api/${encodeURIComponent(projectId)}/${encodeURIComponent(branchName)}/plans`;
|
|
304
|
-
}
|
|
305
|
-
function planManifestUrl(baseUrl, projectId, branchName) {
|
|
306
|
-
return new URL(planApiBasePath(projectId, branchName), baseUrl);
|
|
307
|
-
}
|
|
308
|
-
function planDownloadUrl(baseUrl, projectId, branchName, filename) {
|
|
309
|
-
return new URL(`${planApiBasePath(projectId, branchName)}/${encodeURIComponent(filename)}`, baseUrl);
|
|
310
|
-
}
|
|
311
302
|
function sha256Path(filePath) {
|
|
312
303
|
return createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
|
|
313
304
|
}
|
|
@@ -420,30 +411,6 @@ async function syncSessionArtifacts(input) {
|
|
|
420
411
|
})
|
|
421
412
|
});
|
|
422
413
|
}
|
|
423
|
-
async function fetchProjectPlanManifest(input) {
|
|
424
|
-
return fetchRemoteFileManifest({
|
|
425
|
-
url: planManifestUrl(input.baseUrl, input.projectId, input.branchName),
|
|
426
|
-
token: input.token,
|
|
427
|
-
collectionKey: "plans",
|
|
428
|
-
label: "plan"
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
async function syncProjectPlans(input) {
|
|
432
|
-
const targetDir = projectPlanDir(input.planRoot, input.projectId, input.branchName);
|
|
433
|
-
const plans = await fetchProjectPlanManifest(input);
|
|
434
|
-
return syncRemoteFileSet({
|
|
435
|
-
targetDir,
|
|
436
|
-
files: plans,
|
|
437
|
-
label: "Plan path",
|
|
438
|
-
download: (plan) => downloadRemoteSyncedFile({
|
|
439
|
-
url: planDownloadUrl(input.baseUrl, input.projectId, input.branchName, plan.filename),
|
|
440
|
-
token: input.token,
|
|
441
|
-
targetDir,
|
|
442
|
-
file: plan,
|
|
443
|
-
label: "plan"
|
|
444
|
-
})
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
414
|
async function resolveArtifactEnvFilePath(input) {
|
|
448
415
|
if (!isArtifactEnvPath(input.filePath)) {
|
|
449
416
|
return null;
|
|
@@ -503,15 +470,7 @@ async function prepareArtifactEnvForShell(input) {
|
|
|
503
470
|
R5D_ARTIFACTS_DIR: artifactsDir
|
|
504
471
|
};
|
|
505
472
|
}
|
|
506
|
-
|
|
507
|
-
try {
|
|
508
|
-
await syncProjectPlans(input);
|
|
509
|
-
} catch (error) {
|
|
510
|
-
process.stderr.write(
|
|
511
|
-
`[r5d-worker] plan sync skipped for ${input.projectId}/${input.branchName}: ${error instanceof Error ? error.message : String(error)}
|
|
512
|
-
`
|
|
513
|
-
);
|
|
514
|
-
}
|
|
473
|
+
function preparePlanEnvForShell(input) {
|
|
515
474
|
const plansDir = projectPlanDir(input.planRoot, input.projectId, input.branchName);
|
|
516
475
|
fs.mkdirSync(plansDir, { recursive: true });
|
|
517
476
|
return planEnv(input.planRoot, input.projectId, input.branchName, input.activePlanId);
|
|
@@ -632,9 +591,6 @@ function toProjectDisplayPath(branchPath, absolutePath) {
|
|
|
632
591
|
return relative || ".";
|
|
633
592
|
}
|
|
634
593
|
function assertAllowedProjectPath(repoRelativePath, inputPath) {
|
|
635
|
-
if (repoRelativePath === ".git" || repoRelativePath.startsWith(".git/")) {
|
|
636
|
-
throw new Error("Refusing to access .git through worker file tools");
|
|
637
|
-
}
|
|
638
594
|
if (repoRelativePath.split("/").includes("..")) {
|
|
639
595
|
throw new Error(`Invalid project file path: ${inputPath}`);
|
|
640
596
|
}
|
|
@@ -682,13 +638,6 @@ function resolveWorkerFilePath(branchPath, inputPath) {
|
|
|
682
638
|
scope: "project"
|
|
683
639
|
};
|
|
684
640
|
}
|
|
685
|
-
function resolveProjectFilePath(branchPath, inputPath) {
|
|
686
|
-
const resolved = resolveWorkerFilePath(branchPath, inputPath);
|
|
687
|
-
if (resolved.scope === "host") {
|
|
688
|
-
throw new Error(`Project file writes must use a checkout-relative path. Absolute host paths are read-only in file tools: ${inputPath}`);
|
|
689
|
-
}
|
|
690
|
-
return resolved;
|
|
691
|
-
}
|
|
692
641
|
function fallbackInternalRemoteUrl(baseUrl, projectId) {
|
|
693
642
|
return new URL(`/git/${projectId}.git`, baseUrl).toString();
|
|
694
643
|
}
|
|
@@ -1105,8 +1054,11 @@ async function withFileMutationQueue(key, operation) {
|
|
|
1105
1054
|
}
|
|
1106
1055
|
}
|
|
1107
1056
|
}
|
|
1108
|
-
function mutationQueueKey(projectId, branchName,
|
|
1109
|
-
|
|
1057
|
+
function mutationQueueKey(projectId, branchName, resolved) {
|
|
1058
|
+
if (resolved.scope === "host") {
|
|
1059
|
+
return `host:${path.normalize(resolved.absolutePath)}`;
|
|
1060
|
+
}
|
|
1061
|
+
return `project:${projectId}:${branchName}:${path.posix.normalize(resolved.repoRelativePath)}`;
|
|
1110
1062
|
}
|
|
1111
1063
|
function readWorkerTextFile(branchPath, filePath, offset, limit) {
|
|
1112
1064
|
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
@@ -1123,6 +1075,63 @@ function readWorkerTextFile(branchPath, filePath, offset, limit) {
|
|
|
1123
1075
|
...formatted
|
|
1124
1076
|
};
|
|
1125
1077
|
}
|
|
1078
|
+
function writeWorkerTextFile(branchPath, filePath, content) {
|
|
1079
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1080
|
+
fs.mkdirSync(path.dirname(resolved.absolutePath), { recursive: true });
|
|
1081
|
+
fs.writeFileSync(resolved.absolutePath, content, "utf8");
|
|
1082
|
+
return {
|
|
1083
|
+
type: "write",
|
|
1084
|
+
file: resolved.displayPath,
|
|
1085
|
+
gitBlobHash: getGitBlobHashForContent(content)
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
function editWorkerTextFile(branchPath, filePath, edits) {
|
|
1089
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1090
|
+
if (!Array.isArray(edits) || edits.length === 0) {
|
|
1091
|
+
throw new Error("edit requires at least one replacement");
|
|
1092
|
+
}
|
|
1093
|
+
if (!fs.existsSync(resolved.absolutePath) || !fs.statSync(resolved.absolutePath).isFile()) {
|
|
1094
|
+
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
1095
|
+
}
|
|
1096
|
+
const originalContent = fs.readFileSync(resolved.absolutePath, "utf8");
|
|
1097
|
+
const ranges = edits.map((edit, index) => {
|
|
1098
|
+
if (edit.oldText === edit.newText) {
|
|
1099
|
+
throw new Error(`edits[${index}].oldText and edits[${index}].newText must be different`);
|
|
1100
|
+
}
|
|
1101
|
+
if (edit.oldText.length === 0) {
|
|
1102
|
+
throw new Error(`edits[${index}].oldText cannot be empty`);
|
|
1103
|
+
}
|
|
1104
|
+
const occurrences = originalContent.split(edit.oldText).length - 1;
|
|
1105
|
+
if (occurrences === 0) {
|
|
1106
|
+
throw new Error(`Could not find edits[${index}].oldText: "${edit.oldText.slice(0, 100)}${edit.oldText.length > 100 ? "..." : ""}"`);
|
|
1107
|
+
}
|
|
1108
|
+
if (occurrences > 1) {
|
|
1109
|
+
throw new Error(
|
|
1110
|
+
`edits[${index}].oldText is not unique in the file (found ${occurrences} occurrences). Include more surrounding context.`
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
const start = originalContent.indexOf(edit.oldText);
|
|
1114
|
+
return { index, start, end: start + edit.oldText.length, edit };
|
|
1115
|
+
});
|
|
1116
|
+
const sortedRanges = [...ranges].sort((a, b) => a.start - b.start);
|
|
1117
|
+
for (let index = 1; index < sortedRanges.length; index += 1) {
|
|
1118
|
+
const previous = sortedRanges[index - 1];
|
|
1119
|
+
const current = sortedRanges[index];
|
|
1120
|
+
if (current.start < previous.end) {
|
|
1121
|
+
throw new Error(`edits[${current.index}] overlaps edits[${previous.index}]. Merge nearby changes into one replacement.`);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
let nextContent = originalContent;
|
|
1125
|
+
for (const range of [...sortedRanges].reverse()) {
|
|
1126
|
+
nextContent = nextContent.slice(0, range.start) + range.edit.newText + nextContent.slice(range.end);
|
|
1127
|
+
}
|
|
1128
|
+
fs.writeFileSync(resolved.absolutePath, nextContent, "utf8");
|
|
1129
|
+
return {
|
|
1130
|
+
type: "edit",
|
|
1131
|
+
file: resolved.displayPath,
|
|
1132
|
+
edits: edits.length
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1126
1135
|
async function executeReadFileOperation(input) {
|
|
1127
1136
|
const artifact = await resolveArtifactEnvFilePath({
|
|
1128
1137
|
filePath: input.message.filePath,
|
|
@@ -1153,66 +1162,17 @@ async function executeReadFileOperation(input) {
|
|
|
1153
1162
|
async function executeWriteFileOperation(input) {
|
|
1154
1163
|
rejectArtifactWritePath(input.message.filePath);
|
|
1155
1164
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1156
|
-
const resolved =
|
|
1157
|
-
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved
|
|
1158
|
-
|
|
1159
|
-
fs.writeFileSync(resolved.absolutePath, input.message.content, "utf8");
|
|
1160
|
-
return {
|
|
1161
|
-
type: "write",
|
|
1162
|
-
file: resolved.displayPath,
|
|
1163
|
-
gitBlobHash: getGitBlobHashForContent(input.message.content)
|
|
1164
|
-
};
|
|
1165
|
+
const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath);
|
|
1166
|
+
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved), async () => {
|
|
1167
|
+
return writeWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.content);
|
|
1165
1168
|
});
|
|
1166
1169
|
}
|
|
1167
1170
|
async function executeEditFileOperation(input) {
|
|
1168
1171
|
rejectArtifactWritePath(input.message.filePath);
|
|
1169
1172
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1170
|
-
const resolved =
|
|
1171
|
-
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved
|
|
1172
|
-
|
|
1173
|
-
throw new Error("edit requires at least one replacement");
|
|
1174
|
-
}
|
|
1175
|
-
if (!fs.existsSync(resolved.absolutePath) || !fs.statSync(resolved.absolutePath).isFile()) {
|
|
1176
|
-
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
1177
|
-
}
|
|
1178
|
-
const originalContent = fs.readFileSync(resolved.absolutePath, "utf8");
|
|
1179
|
-
const ranges = input.message.edits.map((edit, index) => {
|
|
1180
|
-
if (edit.oldText === edit.newText) {
|
|
1181
|
-
throw new Error(`edits[${index}].oldText and edits[${index}].newText must be different`);
|
|
1182
|
-
}
|
|
1183
|
-
if (edit.oldText.length === 0) {
|
|
1184
|
-
throw new Error(`edits[${index}].oldText cannot be empty`);
|
|
1185
|
-
}
|
|
1186
|
-
const occurrences = originalContent.split(edit.oldText).length - 1;
|
|
1187
|
-
if (occurrences === 0) {
|
|
1188
|
-
throw new Error(`Could not find edits[${index}].oldText: "${edit.oldText.slice(0, 100)}${edit.oldText.length > 100 ? "..." : ""}"`);
|
|
1189
|
-
}
|
|
1190
|
-
if (occurrences > 1) {
|
|
1191
|
-
throw new Error(
|
|
1192
|
-
`edits[${index}].oldText is not unique in the file (found ${occurrences} occurrences). Include more surrounding context.`
|
|
1193
|
-
);
|
|
1194
|
-
}
|
|
1195
|
-
const start = originalContent.indexOf(edit.oldText);
|
|
1196
|
-
return { index, start, end: start + edit.oldText.length, edit };
|
|
1197
|
-
});
|
|
1198
|
-
const sortedRanges = [...ranges].sort((a, b) => a.start - b.start);
|
|
1199
|
-
for (let index = 1; index < sortedRanges.length; index += 1) {
|
|
1200
|
-
const previous = sortedRanges[index - 1];
|
|
1201
|
-
const current = sortedRanges[index];
|
|
1202
|
-
if (current.start < previous.end) {
|
|
1203
|
-
throw new Error(`edits[${current.index}] overlaps edits[${previous.index}]. Merge nearby changes into one replacement.`);
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
let nextContent = originalContent;
|
|
1207
|
-
for (const range of [...sortedRanges].reverse()) {
|
|
1208
|
-
nextContent = nextContent.slice(0, range.start) + range.edit.newText + nextContent.slice(range.end);
|
|
1209
|
-
}
|
|
1210
|
-
fs.writeFileSync(resolved.absolutePath, nextContent, "utf8");
|
|
1211
|
-
return {
|
|
1212
|
-
type: "edit",
|
|
1213
|
-
file: resolved.displayPath,
|
|
1214
|
-
edits: input.message.edits.length
|
|
1215
|
-
};
|
|
1173
|
+
const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath);
|
|
1174
|
+
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved), async () => {
|
|
1175
|
+
return editWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.edits);
|
|
1216
1176
|
});
|
|
1217
1177
|
}
|
|
1218
1178
|
const IGNORED_ENTRY_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", ".cache", "coverage", "target"]);
|
|
@@ -1489,9 +1449,7 @@ async function executeCommand(input) {
|
|
|
1489
1449
|
sessionId: input.message.sessionId,
|
|
1490
1450
|
artifactRoot: input.artifactRoot
|
|
1491
1451
|
});
|
|
1492
|
-
const planProcessEnv =
|
|
1493
|
-
baseUrl: input.baseUrl,
|
|
1494
|
-
token: input.token,
|
|
1452
|
+
const planProcessEnv = preparePlanEnvForShell({
|
|
1495
1453
|
projectId: input.projectId,
|
|
1496
1454
|
branchName: input.message.branchName,
|
|
1497
1455
|
planRoot: input.planRoot,
|
|
@@ -1600,9 +1558,7 @@ async function executeStreamingCommand(input) {
|
|
|
1600
1558
|
sessionId: input.message.sessionId,
|
|
1601
1559
|
artifactRoot: input.artifactRoot
|
|
1602
1560
|
});
|
|
1603
|
-
const planProcessEnv =
|
|
1604
|
-
baseUrl: input.baseUrl,
|
|
1605
|
-
token: input.token,
|
|
1561
|
+
const planProcessEnv = preparePlanEnvForShell({
|
|
1606
1562
|
projectId: input.projectId,
|
|
1607
1563
|
branchName: input.message.branchName,
|
|
1608
1564
|
planRoot: input.planRoot,
|
|
@@ -1946,9 +1902,7 @@ async function openPty(input) {
|
|
|
1946
1902
|
branchName: input.message.branchName,
|
|
1947
1903
|
manifest: input.manifest
|
|
1948
1904
|
});
|
|
1949
|
-
const planProcessEnv =
|
|
1950
|
-
baseUrl: input.baseUrl,
|
|
1951
|
-
token: input.token,
|
|
1905
|
+
const planProcessEnv = preparePlanEnvForShell({
|
|
1952
1906
|
projectId: input.projectId,
|
|
1953
1907
|
branchName: input.message.branchName,
|
|
1954
1908
|
planRoot: input.planRoot,
|
|
@@ -2144,19 +2098,6 @@ async function startWorker(options) {
|
|
|
2144
2098
|
const runRequestedWorkspaceSync = async (requestId, trigger, overrides = {}) => {
|
|
2145
2099
|
workspaceSyncRequestsInFlight += 1;
|
|
2146
2100
|
try {
|
|
2147
|
-
if (trigger.type === "plan_write" || trigger.type === "task_status" || trigger.type === "connect" || trigger.type === "inbound_head" || trigger.type === "manual") {
|
|
2148
|
-
for (const manifest of manifestByProjectId.values()) {
|
|
2149
|
-
for (const branchName of manifest.branches) {
|
|
2150
|
-
await syncProjectPlans({
|
|
2151
|
-
baseUrl,
|
|
2152
|
-
token,
|
|
2153
|
-
projectId: manifest.projectId,
|
|
2154
|
-
branchName,
|
|
2155
|
-
planRoot
|
|
2156
|
-
});
|
|
2157
|
-
}
|
|
2158
|
-
}
|
|
2159
|
-
}
|
|
2160
2101
|
const newVisibleCheckouts = trigger.type === "connect" ? ensureAllVisibleWorkspaceCheckouts() : [];
|
|
2161
2102
|
const result = await workspaceSyncSingleFlight.run(
|
|
2162
2103
|
workspaceSyncInput(trigger, {
|
|
@@ -2636,6 +2577,7 @@ if (isCliEntrypoint()) {
|
|
|
2636
2577
|
});
|
|
2637
2578
|
}
|
|
2638
2579
|
export {
|
|
2580
|
+
editWorkerTextFile,
|
|
2639
2581
|
ensureVisibleGitCheckout,
|
|
2640
2582
|
findWorkerFiles,
|
|
2641
2583
|
githubCliEnv,
|
|
@@ -2647,8 +2589,7 @@ export {
|
|
|
2647
2589
|
readWorkerImageFile,
|
|
2648
2590
|
readWorkerTextFile,
|
|
2649
2591
|
resolveHostShell,
|
|
2650
|
-
resolveProjectFilePath,
|
|
2651
2592
|
resolveWorkerFilePath,
|
|
2652
|
-
|
|
2653
|
-
|
|
2593
|
+
syncSessionArtifacts,
|
|
2594
|
+
writeWorkerTextFile
|
|
2654
2595
|
};
|
package/dist/mjs/package.json
CHANGED
package/dist/types/main.d.ts
CHANGED
|
@@ -11,6 +11,16 @@ type WorkerReadFileResult = {
|
|
|
11
11
|
truncated: boolean;
|
|
12
12
|
continuation?: string;
|
|
13
13
|
};
|
|
14
|
+
type WorkerWriteFileResult = {
|
|
15
|
+
type: "write";
|
|
16
|
+
file: string;
|
|
17
|
+
gitBlobHash?: string;
|
|
18
|
+
};
|
|
19
|
+
type WorkerEditFileResult = {
|
|
20
|
+
type: "edit";
|
|
21
|
+
file: string;
|
|
22
|
+
edits: number;
|
|
23
|
+
};
|
|
14
24
|
type WorkerGrepResult = {
|
|
15
25
|
type: "grep";
|
|
16
26
|
content: string;
|
|
@@ -48,13 +58,6 @@ export declare function syncSessionArtifacts(input: {
|
|
|
48
58
|
sessionId: string;
|
|
49
59
|
artifactRoot: string;
|
|
50
60
|
}): Promise<string>;
|
|
51
|
-
export declare function syncProjectPlans(input: {
|
|
52
|
-
baseUrl: string;
|
|
53
|
-
token: string;
|
|
54
|
-
projectId: string;
|
|
55
|
-
branchName: string;
|
|
56
|
-
planRoot: string;
|
|
57
|
-
}): Promise<string>;
|
|
58
61
|
export declare function prepareArtifactEnvForShell(input: {
|
|
59
62
|
baseUrl: string;
|
|
60
63
|
token: string;
|
|
@@ -64,13 +67,11 @@ export declare function prepareArtifactEnvForShell(input: {
|
|
|
64
67
|
artifactRoot: string;
|
|
65
68
|
}): Promise<Record<string, string>>;
|
|
66
69
|
export declare function preparePlanEnvForShell(input: {
|
|
67
|
-
baseUrl: string;
|
|
68
|
-
token: string;
|
|
69
70
|
projectId: string;
|
|
70
71
|
branchName: string;
|
|
71
72
|
planRoot: string;
|
|
72
73
|
activePlanId?: string;
|
|
73
|
-
}):
|
|
74
|
+
}): Record<string, string>;
|
|
74
75
|
type GitAuth = {
|
|
75
76
|
extraHeaderUrl: string;
|
|
76
77
|
header: string;
|
|
@@ -87,9 +88,6 @@ type ResolvedWorkerFilePath = {
|
|
|
87
88
|
scope: "host";
|
|
88
89
|
};
|
|
89
90
|
export declare function resolveWorkerFilePath(branchPath: string, inputPath: string): ResolvedWorkerFilePath;
|
|
90
|
-
export declare function resolveProjectFilePath(branchPath: string, inputPath: string): Extract<ResolvedWorkerFilePath, {
|
|
91
|
-
scope: "project";
|
|
92
|
-
}>;
|
|
93
91
|
export declare function githubCliEnv(token: string | null | undefined): Record<string, string>;
|
|
94
92
|
export declare function ensureVisibleGitCheckout(input: {
|
|
95
93
|
projectRoot: string;
|
|
@@ -101,6 +99,11 @@ export declare function ensureVisibleGitCheckout(input: {
|
|
|
101
99
|
reconcileExistingOrigin?: boolean;
|
|
102
100
|
}): string;
|
|
103
101
|
export declare function readWorkerTextFile(branchPath: string, filePath: string, offset?: number, limit?: number): WorkerReadFileResult;
|
|
102
|
+
export declare function writeWorkerTextFile(branchPath: string, filePath: string, content: string): WorkerWriteFileResult;
|
|
103
|
+
export declare function editWorkerTextFile(branchPath: string, filePath: string, edits: Array<{
|
|
104
|
+
oldText: string;
|
|
105
|
+
newText: string;
|
|
106
|
+
}>): WorkerEditFileResult;
|
|
104
107
|
export declare function grepWorkerFiles(branchPath: string, input: {
|
|
105
108
|
pattern: string;
|
|
106
109
|
path?: string;
|
|
@@ -12,7 +12,7 @@ export type WorkspaceProjectManifestEntry = {
|
|
|
12
12
|
branches: string[];
|
|
13
13
|
};
|
|
14
14
|
export type WorkspaceSyncTrigger = {
|
|
15
|
-
type: "connect" | "inbound_head" | "write" | "edit" | "
|
|
15
|
+
type: "connect" | "inbound_head" | "write" | "edit" | "shell_inline" | "process_terminal" | "process_cancel" | "periodic" | "manual" | "remediation" | "remediation_confirm" | "remediation_reset";
|
|
16
16
|
sessionId?: string;
|
|
17
17
|
processRunId?: string;
|
|
18
18
|
toolCallId?: string;
|