@ricsam/r5d-worker 0.0.11 → 0.0.13
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 +277 -224
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/main.mjs +276 -224
- package/dist/mjs/package.json +1 -1
- package/dist/types/main.d.ts +8 -0
- package/package.json +1 -1
package/dist/cjs/package.json
CHANGED
package/dist/mjs/main.mjs
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
3
|
import path from "node:path";
|
|
5
|
-
import { createHash
|
|
6
|
-
import { hostname } from "node:os";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import os, { hostname } from "node:os";
|
|
7
6
|
import { spawn as spawnChildProcess } from "node:child_process";
|
|
8
7
|
import { configureVisibleGitIdentity } from "./git-identity.mjs";
|
|
9
8
|
const DEFAULT_BASE_URL = "https://r5d.dev";
|
|
@@ -11,6 +10,7 @@ const WORKER_PACKAGE_NAME = "@ricsam/r5d-worker";
|
|
|
11
10
|
const LABEL_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,62}$/;
|
|
12
11
|
const BRANCH_RE = /^[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9]$|^[A-Za-z0-9]$/;
|
|
13
12
|
const DEFAULT_READ_LIMIT = 2e3;
|
|
13
|
+
const DEFAULT_READ_MAX_BYTES = 5e4;
|
|
14
14
|
const MAX_LINE_LENGTH = 2e3;
|
|
15
15
|
const MAX_SYNC_DIFF_BYTES = 5 * 1024 * 1024;
|
|
16
16
|
const R5D_REMOTE_NAME = "r5d";
|
|
@@ -374,6 +374,29 @@ function rejectArtifactWritePath(filePath) {
|
|
|
374
374
|
throw new Error(`${R5D_ARTIFACTS_DIR_REF} attachments are read-only. Copy them into a project path before editing or creating files.`);
|
|
375
375
|
}
|
|
376
376
|
}
|
|
377
|
+
async function prepareArtifactEnvForShell(input) {
|
|
378
|
+
if (!input.sessionId) {
|
|
379
|
+
return {};
|
|
380
|
+
}
|
|
381
|
+
try {
|
|
382
|
+
await syncSessionArtifacts({
|
|
383
|
+
baseUrl: input.baseUrl,
|
|
384
|
+
token: input.token,
|
|
385
|
+
projectId: input.projectId,
|
|
386
|
+
branchName: input.branchName,
|
|
387
|
+
sessionId: input.sessionId,
|
|
388
|
+
artifactRoot: input.artifactRoot
|
|
389
|
+
});
|
|
390
|
+
} catch (error) {
|
|
391
|
+
process.stderr.write(`[r5d-worker] artifact sync skipped for ${input.sessionId}: ${error instanceof Error ? error.message : String(error)}
|
|
392
|
+
`);
|
|
393
|
+
}
|
|
394
|
+
const artifactsDir = sessionArtifactDir(input.artifactRoot, input.sessionId);
|
|
395
|
+
fs.mkdirSync(artifactsDir, { recursive: true });
|
|
396
|
+
return {
|
|
397
|
+
R5D_ARTIFACTS_DIR: artifactsDir
|
|
398
|
+
};
|
|
399
|
+
}
|
|
377
400
|
async function verifyR5dctlAuth(baseUrl, token) {
|
|
378
401
|
const statusUrl = new URL("/api/r5dctl/auth/status", baseUrl);
|
|
379
402
|
let response;
|
|
@@ -885,16 +908,36 @@ function formatLineNumberedContent(content, offset, limit) {
|
|
|
885
908
|
const endLine = Math.min(startLine + readLimit - 1, totalLines);
|
|
886
909
|
const selectedLines = allLines.slice(startLine - 1, endLine);
|
|
887
910
|
const padWidth = String(endLine).length;
|
|
888
|
-
const formattedLines =
|
|
911
|
+
const formattedLines = [];
|
|
912
|
+
let actualEndLine = endLine;
|
|
913
|
+
let budgetTruncated = false;
|
|
914
|
+
for (let index = 0; index < selectedLines.length; index += 1) {
|
|
915
|
+
const line = selectedLines[index] ?? "";
|
|
889
916
|
const lineNumber = startLine + index;
|
|
890
917
|
const truncatedLine = line.length > MAX_LINE_LENGTH ? `${line.slice(0, MAX_LINE_LENGTH)}...` : line;
|
|
891
|
-
|
|
892
|
-
|
|
918
|
+
const formattedLine = `${String(lineNumber).padStart(padWidth, " ")}\u2192${truncatedLine}`;
|
|
919
|
+
const candidateLength = formattedLines.length === 0 ? formattedLine.length : formattedLine.length + 1;
|
|
920
|
+
const currentLength = formattedLines.join("\n").length;
|
|
921
|
+
if (currentLength + candidateLength > DEFAULT_READ_MAX_BYTES) {
|
|
922
|
+
budgetTruncated = true;
|
|
923
|
+
break;
|
|
924
|
+
}
|
|
925
|
+
formattedLines.push(formattedLine);
|
|
926
|
+
actualEndLine = lineNumber;
|
|
927
|
+
}
|
|
928
|
+
let formattedContent = formattedLines.join("\n");
|
|
929
|
+
if (budgetTruncated) {
|
|
930
|
+
formattedContent = `${formattedContent}
|
|
931
|
+
... (truncated by byte budget)`;
|
|
932
|
+
}
|
|
933
|
+
const truncated = budgetTruncated || actualEndLine < totalLines;
|
|
893
934
|
return {
|
|
894
|
-
content:
|
|
935
|
+
content: formattedContent,
|
|
895
936
|
totalLines,
|
|
896
937
|
startLine,
|
|
897
|
-
endLine
|
|
938
|
+
endLine: actualEndLine,
|
|
939
|
+
truncated,
|
|
940
|
+
continuation: truncated ? `Call read with offset ${actualEndLine + 1} to continue.` : void 0
|
|
898
941
|
};
|
|
899
942
|
}
|
|
900
943
|
function readPngDimensions(bytes) {
|
|
@@ -961,6 +1004,27 @@ function readImageDimensions(bytes, mediaType) {
|
|
|
961
1004
|
}
|
|
962
1005
|
return dimensions;
|
|
963
1006
|
}
|
|
1007
|
+
const fileMutationQueues = /* @__PURE__ */ new Map();
|
|
1008
|
+
async function withFileMutationQueue(key, operation) {
|
|
1009
|
+
const previous = fileMutationQueues.get(key) ?? Promise.resolve();
|
|
1010
|
+
let release;
|
|
1011
|
+
const current = previous.catch(() => void 0).then(() => new Promise((resolve) => {
|
|
1012
|
+
release = resolve;
|
|
1013
|
+
}));
|
|
1014
|
+
fileMutationQueues.set(key, current);
|
|
1015
|
+
await previous.catch(() => void 0);
|
|
1016
|
+
try {
|
|
1017
|
+
return await operation();
|
|
1018
|
+
} finally {
|
|
1019
|
+
release();
|
|
1020
|
+
if (fileMutationQueues.get(key) === current) {
|
|
1021
|
+
fileMutationQueues.delete(key);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
function mutationQueueKey(projectId, branchName, filePath) {
|
|
1026
|
+
return `${projectId}:${branchName}:${path.posix.normalize(filePath)}`;
|
|
1027
|
+
}
|
|
964
1028
|
async function executeReadFileOperation(input) {
|
|
965
1029
|
const artifact = await resolveArtifactEnvFilePath({
|
|
966
1030
|
filePath: input.message.filePath,
|
|
@@ -978,7 +1042,8 @@ async function executeReadFileOperation(input) {
|
|
|
978
1042
|
const buffer2 = fs.readFileSync(artifact.absolutePath);
|
|
979
1043
|
const formatted2 = formatLineNumberedContent(buffer2.toString("utf8"), input.message.offset, input.message.limit);
|
|
980
1044
|
return {
|
|
981
|
-
type: "
|
|
1045
|
+
type: "read",
|
|
1046
|
+
kind: "text",
|
|
982
1047
|
file: artifact.virtualPath,
|
|
983
1048
|
gitBlobHash: getGitBlobHashForContent(buffer2),
|
|
984
1049
|
...formatted2
|
|
@@ -992,222 +1057,209 @@ async function executeReadFileOperation(input) {
|
|
|
992
1057
|
const buffer = fs.readFileSync(resolved.absolutePath);
|
|
993
1058
|
const formatted = formatLineNumberedContent(buffer.toString("utf8"), input.message.offset, input.message.limit);
|
|
994
1059
|
return {
|
|
995
|
-
type: "
|
|
1060
|
+
type: "read",
|
|
1061
|
+
kind: "text",
|
|
996
1062
|
file: resolved.virtualPath,
|
|
997
1063
|
gitBlobHash: getGitBlobHashForContent(buffer),
|
|
998
1064
|
...formatted
|
|
999
1065
|
};
|
|
1000
1066
|
}
|
|
1001
|
-
async function
|
|
1067
|
+
async function executeWriteFileOperation(input) {
|
|
1002
1068
|
rejectArtifactWritePath(input.message.filePath);
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
};
|
|
1069
|
+
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, input.message.filePath), async () => {
|
|
1070
|
+
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1071
|
+
const resolved = resolveProjectFilePath(workspace.branchPath, input.message.filePath);
|
|
1072
|
+
fs.mkdirSync(path.dirname(resolved.absolutePath), { recursive: true });
|
|
1073
|
+
fs.writeFileSync(resolved.absolutePath, input.message.content, "utf8");
|
|
1074
|
+
return {
|
|
1075
|
+
type: "write",
|
|
1076
|
+
file: resolved.virtualPath,
|
|
1077
|
+
gitBlobHash: getGitBlobHashForContent(input.message.content)
|
|
1078
|
+
};
|
|
1079
|
+
});
|
|
1015
1080
|
}
|
|
1016
1081
|
async function executeEditFileOperation(input) {
|
|
1017
1082
|
rejectArtifactWritePath(input.message.filePath);
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1083
|
+
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, input.message.filePath), async () => {
|
|
1084
|
+
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1085
|
+
const resolved = resolveProjectFilePath(workspace.branchPath, input.message.filePath);
|
|
1086
|
+
if (!Array.isArray(input.message.edits) || input.message.edits.length === 0) {
|
|
1087
|
+
throw new Error("edit requires at least one replacement");
|
|
1088
|
+
}
|
|
1089
|
+
if (!fs.existsSync(resolved.absolutePath) || !fs.statSync(resolved.absolutePath).isFile()) {
|
|
1090
|
+
throw new Error(`File not found: ${resolved.virtualPath}`);
|
|
1091
|
+
}
|
|
1092
|
+
const originalContent = fs.readFileSync(resolved.absolutePath, "utf8");
|
|
1093
|
+
const ranges = input.message.edits.map((edit, index) => {
|
|
1094
|
+
if (edit.oldText === edit.newText) {
|
|
1095
|
+
throw new Error(`edits[${index}].oldText and edits[${index}].newText must be different`);
|
|
1096
|
+
}
|
|
1097
|
+
if (edit.oldText.length === 0) {
|
|
1098
|
+
throw new Error(`edits[${index}].oldText cannot be empty`);
|
|
1099
|
+
}
|
|
1100
|
+
const occurrences = originalContent.split(edit.oldText).length - 1;
|
|
1101
|
+
if (occurrences === 0) {
|
|
1102
|
+
throw new Error(`Could not find edits[${index}].oldText: "${edit.oldText.slice(0, 100)}${edit.oldText.length > 100 ? "..." : ""}"`);
|
|
1103
|
+
}
|
|
1104
|
+
if (occurrences > 1) {
|
|
1105
|
+
throw new Error(`edits[${index}].oldText is not unique in the file (found ${occurrences} occurrences). Include more surrounding context.`);
|
|
1106
|
+
}
|
|
1107
|
+
const start = originalContent.indexOf(edit.oldText);
|
|
1108
|
+
return { index, start, end: start + edit.oldText.length, edit };
|
|
1109
|
+
});
|
|
1110
|
+
const sortedRanges = [...ranges].sort((a, b) => a.start - b.start);
|
|
1111
|
+
for (let index = 1; index < sortedRanges.length; index += 1) {
|
|
1112
|
+
const previous = sortedRanges[index - 1];
|
|
1113
|
+
const current = sortedRanges[index];
|
|
1114
|
+
if (current.start < previous.end) {
|
|
1115
|
+
throw new Error(`edits[${current.index}] overlaps edits[${previous.index}]. Merge nearby changes into one replacement.`);
|
|
1116
|
+
}
|
|
1038
1117
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1118
|
+
let nextContent = originalContent;
|
|
1119
|
+
for (const range of [...sortedRanges].reverse()) {
|
|
1120
|
+
nextContent = nextContent.slice(0, range.start) + range.edit.newText + nextContent.slice(range.end);
|
|
1121
|
+
}
|
|
1122
|
+
fs.writeFileSync(resolved.absolutePath, nextContent, "utf8");
|
|
1123
|
+
return {
|
|
1124
|
+
type: "edit",
|
|
1125
|
+
file: resolved.virtualPath,
|
|
1126
|
+
edits: input.message.edits.length
|
|
1127
|
+
};
|
|
1128
|
+
});
|
|
1046
1129
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
if (
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1130
|
+
const IGNORED_ENTRY_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", ".cache", "coverage", "target"]);
|
|
1131
|
+
function normalizeToolLimit(value, fallback, max) {
|
|
1132
|
+
if (!Number.isFinite(value) || value === void 0) return fallback;
|
|
1133
|
+
return Math.max(1, Math.min(max, Math.floor(value)));
|
|
1134
|
+
}
|
|
1135
|
+
function escapeRegex(value) {
|
|
1136
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1137
|
+
}
|
|
1138
|
+
function wildcardToRegex(pattern) {
|
|
1139
|
+
const escaped = escapeRegex(pattern).replace(/\\\*/g, ".*").replace(/\\\?/g, ".");
|
|
1140
|
+
return new RegExp(`^${escaped}$`);
|
|
1141
|
+
}
|
|
1142
|
+
function normalizeFindPattern(pattern) {
|
|
1143
|
+
return /[*?]/.test(pattern) ? pattern : `*${pattern}*`;
|
|
1144
|
+
}
|
|
1145
|
+
function toVirtualPath(branchPath, absolutePath) {
|
|
1146
|
+
const relative = path.relative(branchPath, absolutePath).split(path.sep).join(path.posix.sep);
|
|
1147
|
+
return relative ? `/${relative}` : "/";
|
|
1148
|
+
}
|
|
1149
|
+
function walkProjectEntries(branchPath, startPath) {
|
|
1150
|
+
const entries = [];
|
|
1151
|
+
const visit = (absolutePath) => {
|
|
1152
|
+
const stat = fs.statSync(absolutePath);
|
|
1153
|
+
const virtualPath = toVirtualPath(branchPath, absolutePath);
|
|
1154
|
+
entries.push({ absolutePath, virtualPath, stat });
|
|
1155
|
+
if (!stat.isDirectory()) return;
|
|
1156
|
+
const children = fs.readdirSync(absolutePath, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
1157
|
+
for (const child of children) {
|
|
1158
|
+
if (IGNORED_ENTRY_NAMES.has(child.name)) continue;
|
|
1159
|
+
visit(path.join(absolutePath, child.name));
|
|
1054
1160
|
}
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1161
|
+
};
|
|
1162
|
+
visit(startPath);
|
|
1163
|
+
return entries;
|
|
1057
1164
|
}
|
|
1058
|
-
function
|
|
1059
|
-
|
|
1060
|
-
let patchPath = decodePatchPath(withoutMetadata);
|
|
1061
|
-
if (patchPath === "/dev/null") {
|
|
1062
|
-
return null;
|
|
1063
|
-
}
|
|
1064
|
-
if (patchPath.startsWith("a/") || patchPath.startsWith("b/")) {
|
|
1065
|
-
patchPath = patchPath.slice(2);
|
|
1066
|
-
}
|
|
1067
|
-
rejectArtifactWritePath(patchPath);
|
|
1068
|
-
if (!patchPath || path.isAbsolute(patchPath)) {
|
|
1069
|
-
throw new Error(`Invalid patch path: ${rawPath}`);
|
|
1070
|
-
}
|
|
1071
|
-
const normalized = path.posix.normalize(patchPath);
|
|
1072
|
-
if (normalized === "." || normalized.startsWith("../") || normalized === "..") {
|
|
1073
|
-
throw new Error(`Invalid patch path: ${rawPath}`);
|
|
1074
|
-
}
|
|
1075
|
-
return normalized;
|
|
1165
|
+
function isProbablyText(bytes) {
|
|
1166
|
+
return !bytes.subarray(0, Math.min(bytes.length, 4096)).includes(0);
|
|
1076
1167
|
}
|
|
1077
|
-
function
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
const
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1168
|
+
async function executeGrepOperation(input) {
|
|
1169
|
+
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1170
|
+
const start = resolveProjectFilePath(workspace.branchPath, input.message.path ?? "/");
|
|
1171
|
+
if (!fs.existsSync(start.absolutePath)) {
|
|
1172
|
+
throw new Error(`Path not found: ${start.virtualPath}`);
|
|
1173
|
+
}
|
|
1174
|
+
const limit = normalizeToolLimit(input.message.limit, 100, 1e3);
|
|
1175
|
+
const flags = input.message.caseSensitive === false ? "i" : "";
|
|
1176
|
+
const regex = new RegExp(input.message.pattern, flags);
|
|
1177
|
+
const globRegex = input.message.glob ? wildcardToRegex(input.message.glob) : null;
|
|
1178
|
+
const lines = [];
|
|
1179
|
+
let matchCount = 0;
|
|
1180
|
+
let truncated = false;
|
|
1181
|
+
for (const entry of walkProjectEntries(workspace.branchPath, start.absolutePath)) {
|
|
1182
|
+
if (!entry.stat.isFile()) continue;
|
|
1183
|
+
const repoRelative = entry.virtualPath.slice(1);
|
|
1184
|
+
if (globRegex && !globRegex.test(repoRelative)) continue;
|
|
1185
|
+
const bytes = fs.readFileSync(entry.absolutePath);
|
|
1186
|
+
if (!isProbablyText(bytes)) continue;
|
|
1187
|
+
const fileLines = bytes.toString("utf8").split(/\r?\n/);
|
|
1188
|
+
for (let index = 0; index < fileLines.length; index += 1) {
|
|
1189
|
+
const line = fileLines[index] ?? "";
|
|
1190
|
+
regex.lastIndex = 0;
|
|
1191
|
+
const match = regex.exec(line);
|
|
1192
|
+
if (!match) continue;
|
|
1193
|
+
matchCount += 1;
|
|
1194
|
+
if (lines.length < limit) {
|
|
1195
|
+
const column = Math.max(1, (match.index ?? 0) + 1);
|
|
1196
|
+
lines.push(`${entry.virtualPath}:${index + 1}:${column}: ${line.slice(0, MAX_LINE_LENGTH)}`);
|
|
1197
|
+
} else {
|
|
1198
|
+
truncated = true;
|
|
1199
|
+
}
|
|
1104
1200
|
}
|
|
1105
|
-
index += 1;
|
|
1106
|
-
}
|
|
1107
|
-
throw new Error("Unterminated quoted path in patch header");
|
|
1108
|
-
}
|
|
1109
|
-
function parseDiffGitPaths(line) {
|
|
1110
|
-
if (!line.startsWith("diff --git ")) {
|
|
1111
|
-
throw new Error(`Invalid patch header: ${line}`);
|
|
1112
|
-
}
|
|
1113
|
-
const rest = line.slice("diff --git ".length);
|
|
1114
|
-
const first = readDiffPathToken(rest, 0);
|
|
1115
|
-
const second = readDiffPathToken(rest, first.nextIndex);
|
|
1116
|
-
if (rest.slice(second.nextIndex).trim()) {
|
|
1117
|
-
throw new Error(`Invalid patch header: ${line}`);
|
|
1118
1201
|
}
|
|
1202
|
+
const hint = truncated ? `
|
|
1203
|
+
... truncated. Narrow path/glob or increase limit to continue.` : "";
|
|
1119
1204
|
return {
|
|
1120
|
-
|
|
1121
|
-
|
|
1205
|
+
type: "grep",
|
|
1206
|
+
content: lines.length > 0 ? `${lines.join("\n")}${hint}` : "No matches.",
|
|
1207
|
+
matchCount,
|
|
1208
|
+
truncated
|
|
1122
1209
|
};
|
|
1123
1210
|
}
|
|
1124
|
-
function
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
}
|
|
1150
|
-
if (line.startsWith("new file mode ")) {
|
|
1151
|
-
current.operation = "add";
|
|
1152
|
-
continue;
|
|
1153
|
-
}
|
|
1154
|
-
if (line.startsWith("deleted file mode ")) {
|
|
1155
|
-
current.operation = "delete";
|
|
1156
|
-
if (current.oldPath) {
|
|
1157
|
-
current.path = current.oldPath;
|
|
1158
|
-
delete current.oldPath;
|
|
1159
|
-
}
|
|
1160
|
-
continue;
|
|
1161
|
-
}
|
|
1162
|
-
if (line.startsWith("rename from ")) {
|
|
1163
|
-
current.operation = "move";
|
|
1164
|
-
current.oldPath = normalizePatchPath(line.slice("rename from ".length)) ?? void 0;
|
|
1165
|
-
continue;
|
|
1166
|
-
}
|
|
1167
|
-
if (line.startsWith("rename to ")) {
|
|
1168
|
-
current.operation = "move";
|
|
1169
|
-
current.path = normalizePatchPath(line.slice("rename to ".length)) ?? current.path;
|
|
1170
|
-
continue;
|
|
1171
|
-
}
|
|
1172
|
-
if (line.startsWith("@@")) {
|
|
1173
|
-
current.hunks += 1;
|
|
1174
|
-
continue;
|
|
1175
|
-
}
|
|
1176
|
-
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
1177
|
-
current.additions += 1;
|
|
1178
|
-
continue;
|
|
1179
|
-
}
|
|
1180
|
-
if (line.startsWith("-") && !line.startsWith("---")) {
|
|
1181
|
-
current.deletions += 1;
|
|
1211
|
+
async function executeFindOperation(input) {
|
|
1212
|
+
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1213
|
+
const start = resolveProjectFilePath(workspace.branchPath, input.message.path ?? "/");
|
|
1214
|
+
if (!fs.existsSync(start.absolutePath) || !fs.statSync(start.absolutePath).isDirectory()) {
|
|
1215
|
+
throw new Error(`Directory not found: ${start.virtualPath}`);
|
|
1216
|
+
}
|
|
1217
|
+
const pattern = normalizeFindPattern(input.message.pattern ?? "*");
|
|
1218
|
+
const matcher = wildcardToRegex(pattern);
|
|
1219
|
+
const entryType = input.message.entryType ?? "file";
|
|
1220
|
+
const limit = normalizeToolLimit(input.message.limit, 200, 1e3);
|
|
1221
|
+
const matches = [];
|
|
1222
|
+
let count = 0;
|
|
1223
|
+
let truncated = false;
|
|
1224
|
+
for (const entry of walkProjectEntries(workspace.branchPath, start.absolutePath)) {
|
|
1225
|
+
if (entry.virtualPath === start.virtualPath) continue;
|
|
1226
|
+
const isDirectory = entry.stat.isDirectory();
|
|
1227
|
+
if (entryType === "file" && !entry.stat.isFile()) continue;
|
|
1228
|
+
if (entryType === "directory" && !isDirectory) continue;
|
|
1229
|
+
const label = isDirectory ? `${entry.virtualPath}/` : entry.virtualPath;
|
|
1230
|
+
if (!matcher.test(path.posix.basename(entry.virtualPath)) && !matcher.test(entry.virtualPath.slice(1))) continue;
|
|
1231
|
+
count += 1;
|
|
1232
|
+
if (matches.length < limit) {
|
|
1233
|
+
matches.push(label);
|
|
1234
|
+
} else {
|
|
1235
|
+
truncated = true;
|
|
1182
1236
|
}
|
|
1183
1237
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1238
|
+
const hint = truncated ? "\n... truncated. Narrow path/pattern or increase limit to continue." : "";
|
|
1239
|
+
return {
|
|
1240
|
+
type: "find",
|
|
1241
|
+
content: matches.length > 0 ? `${matches.join("\n")}${hint}` : "No paths found.",
|
|
1242
|
+
count,
|
|
1243
|
+
truncated
|
|
1244
|
+
};
|
|
1188
1245
|
}
|
|
1189
|
-
async function
|
|
1246
|
+
async function executeLsOperation(input) {
|
|
1190
1247
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1191
|
-
const
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
fs.rmSync(patchPath, { force: true });
|
|
1201
|
-
}
|
|
1248
|
+
const resolved = resolveProjectFilePath(workspace.branchPath, input.message.path ?? "/");
|
|
1249
|
+
if (!fs.existsSync(resolved.absolutePath) || !fs.statSync(resolved.absolutePath).isDirectory()) {
|
|
1250
|
+
throw new Error(`Directory not found: ${resolved.virtualPath}`);
|
|
1251
|
+
}
|
|
1252
|
+
const limit = normalizeToolLimit(input.message.limit, 200, 1e3);
|
|
1253
|
+
const entries = fs.readdirSync(resolved.absolutePath, { withFileTypes: true }).filter((entry) => !IGNORED_ENTRY_NAMES.has(entry.name)).sort((a, b) => Number(b.isDirectory()) - Number(a.isDirectory()) || a.name.localeCompare(b.name));
|
|
1254
|
+
const displayed = entries.slice(0, limit).map((entry) => `${entry.name}${entry.isDirectory() ? "/" : ""}`);
|
|
1255
|
+
const truncated = entries.length > displayed.length;
|
|
1256
|
+
const hint = truncated ? "\n... truncated. Increase limit to continue." : "";
|
|
1202
1257
|
return {
|
|
1203
|
-
type: "
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
deletions: files.reduce((sum, file) => sum + file.deletions, 0),
|
|
1209
|
-
hunks: files.reduce((sum, file) => sum + file.hunks, 0)
|
|
1210
|
-
}
|
|
1258
|
+
type: "ls",
|
|
1259
|
+
path: resolved.virtualPath,
|
|
1260
|
+
content: displayed.length > 0 ? `${displayed.join("\n")}${hint}` : "(empty directory)",
|
|
1261
|
+
count: entries.length,
|
|
1262
|
+
truncated
|
|
1211
1263
|
};
|
|
1212
1264
|
}
|
|
1213
1265
|
async function executeViewFileBytesOperation(input) {
|
|
@@ -1227,7 +1279,7 @@ async function executeViewFileBytesOperation(input) {
|
|
|
1227
1279
|
const extension2 = path.extname(artifact.absolutePath).toLowerCase();
|
|
1228
1280
|
const mediaType2 = extension2 === ".jpg" || extension2 === ".jpeg" ? "image/jpeg" : extension2 === ".png" ? "image/png" : null;
|
|
1229
1281
|
if (!mediaType2) {
|
|
1230
|
-
throw new Error("
|
|
1282
|
+
throw new Error("read supports PNG and JPEG image inputs only");
|
|
1231
1283
|
}
|
|
1232
1284
|
const bytes2 = fs.readFileSync(artifact.absolutePath);
|
|
1233
1285
|
const dimensions2 = readImageDimensions(bytes2, mediaType2);
|
|
@@ -1249,7 +1301,7 @@ async function executeViewFileBytesOperation(input) {
|
|
|
1249
1301
|
const extension = path.extname(resolved.absolutePath).toLowerCase();
|
|
1250
1302
|
const mediaType = extension === ".jpg" || extension === ".jpeg" ? "image/jpeg" : extension === ".png" ? "image/png" : null;
|
|
1251
1303
|
if (!mediaType) {
|
|
1252
|
-
throw new Error("
|
|
1304
|
+
throw new Error("read supports PNG and JPEG image inputs only");
|
|
1253
1305
|
}
|
|
1254
1306
|
const bytes = fs.readFileSync(resolved.absolutePath);
|
|
1255
1307
|
const dimensions = readImageDimensions(bytes, mediaType);
|
|
@@ -1355,14 +1407,18 @@ function pullBranch(input) {
|
|
|
1355
1407
|
}
|
|
1356
1408
|
async function executeOperation(input) {
|
|
1357
1409
|
switch (input.message.type) {
|
|
1358
|
-
case "
|
|
1410
|
+
case "read":
|
|
1359
1411
|
return executeReadFileOperation({ ...input, message: input.message });
|
|
1360
|
-
case "
|
|
1361
|
-
return
|
|
1362
|
-
case "
|
|
1412
|
+
case "write":
|
|
1413
|
+
return executeWriteFileOperation({ ...input, message: input.message });
|
|
1414
|
+
case "edit":
|
|
1363
1415
|
return executeEditFileOperation({ ...input, message: input.message });
|
|
1364
|
-
case "
|
|
1365
|
-
return
|
|
1416
|
+
case "grep":
|
|
1417
|
+
return executeGrepOperation({ ...input, message: input.message });
|
|
1418
|
+
case "find":
|
|
1419
|
+
return executeFindOperation({ ...input, message: input.message });
|
|
1420
|
+
case "ls":
|
|
1421
|
+
return executeLsOperation({ ...input, message: input.message });
|
|
1366
1422
|
case "view_file_bytes":
|
|
1367
1423
|
return executeViewFileBytesOperation({ ...input, message: input.message });
|
|
1368
1424
|
case "sync":
|
|
@@ -1417,18 +1473,14 @@ async function executeCommand(input) {
|
|
|
1417
1473
|
branchName: input.message.branchName,
|
|
1418
1474
|
manifest: input.manifest
|
|
1419
1475
|
});
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
artifactRoot: input.artifactRoot
|
|
1429
|
-
});
|
|
1430
|
-
artifactProcessEnv = artifactEnv(input.artifactRoot, input.message.sessionId);
|
|
1431
|
-
}
|
|
1476
|
+
const artifactProcessEnv = await prepareArtifactEnvForShell({
|
|
1477
|
+
baseUrl: input.baseUrl,
|
|
1478
|
+
token: input.token,
|
|
1479
|
+
projectId: input.projectId,
|
|
1480
|
+
branchName: input.message.branchName,
|
|
1481
|
+
sessionId: input.message.sessionId,
|
|
1482
|
+
artifactRoot: input.artifactRoot
|
|
1483
|
+
});
|
|
1432
1484
|
const cwd = resolveCommandCwd(workspace.branchPath, input.message.cwd);
|
|
1433
1485
|
const subprocess = Bun.spawn(input.message.argv, {
|
|
1434
1486
|
cwd,
|
|
@@ -1502,7 +1554,7 @@ async function executeStreamingCommand(input) {
|
|
|
1502
1554
|
branchName: input.message.branchName,
|
|
1503
1555
|
manifest: input.manifest
|
|
1504
1556
|
});
|
|
1505
|
-
await
|
|
1557
|
+
const artifactProcessEnv = await prepareArtifactEnvForShell({
|
|
1506
1558
|
baseUrl: input.baseUrl,
|
|
1507
1559
|
token: input.token,
|
|
1508
1560
|
projectId: input.projectId,
|
|
@@ -1510,7 +1562,6 @@ async function executeStreamingCommand(input) {
|
|
|
1510
1562
|
sessionId: input.message.sessionId,
|
|
1511
1563
|
artifactRoot: input.artifactRoot
|
|
1512
1564
|
});
|
|
1513
|
-
const artifactProcessEnv = artifactEnv(input.artifactRoot, input.message.sessionId);
|
|
1514
1565
|
const cwd = resolveCommandCwd(workspace.branchPath, input.message.cwd);
|
|
1515
1566
|
const subprocess = Bun.spawn(input.message.argv, {
|
|
1516
1567
|
cwd,
|
|
@@ -2063,7 +2114,7 @@ async function startWorker(options) {
|
|
|
2063
2114
|
}
|
|
2064
2115
|
return;
|
|
2065
2116
|
}
|
|
2066
|
-
if (message.type === "
|
|
2117
|
+
if (message.type === "read" || message.type === "write" || message.type === "edit" || message.type === "grep" || message.type === "find" || message.type === "ls" || message.type === "view_file_bytes" || message.type === "sync" || message.type === "confirm_large_diff" || message.type === "pull_branch") {
|
|
2067
2118
|
try {
|
|
2068
2119
|
const manifest = manifestByProjectId.get(message.projectId);
|
|
2069
2120
|
const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
|
|
@@ -2148,5 +2199,6 @@ if (isCliEntrypoint()) {
|
|
|
2148
2199
|
export {
|
|
2149
2200
|
ensureVisibleGitCheckout,
|
|
2150
2201
|
isArtifactEnvPath,
|
|
2202
|
+
prepareArtifactEnvForShell,
|
|
2151
2203
|
syncSessionArtifacts
|
|
2152
2204
|
};
|
package/dist/mjs/package.json
CHANGED