@ricsam/r5d-worker 0.0.30 → 0.0.31
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 +213 -118
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/main.mjs +207 -118
- package/dist/mjs/package.json +1 -1
- package/dist/types/main.d.ts +68 -2
- package/package.json +1 -1
package/dist/cjs/main.cjs
CHANGED
|
@@ -31,11 +31,17 @@ var main_exports = {};
|
|
|
31
31
|
__export(main_exports, {
|
|
32
32
|
ensureVisibleGitCheckout: () => ensureVisibleGitCheckout,
|
|
33
33
|
findRepositorySyncBlockers: () => findRepositorySyncBlockers,
|
|
34
|
+
findWorkerFiles: () => findWorkerFiles,
|
|
35
|
+
grepWorkerFiles: () => grepWorkerFiles,
|
|
34
36
|
isArtifactEnvPath: () => isArtifactEnvPath,
|
|
37
|
+
listWorkerDirectory: () => listWorkerDirectory,
|
|
35
38
|
prepareArtifactEnvForShell: () => prepareArtifactEnvForShell,
|
|
36
39
|
preparePlanEnvForShell: () => preparePlanEnvForShell,
|
|
40
|
+
readWorkerImageFile: () => readWorkerImageFile,
|
|
41
|
+
readWorkerTextFile: () => readWorkerTextFile,
|
|
37
42
|
resolveHostShell: () => resolveHostShell,
|
|
38
43
|
resolveProjectFilePath: () => resolveProjectFilePath,
|
|
44
|
+
resolveWorkerFilePath: () => resolveWorkerFilePath,
|
|
39
45
|
syncManifestProjectsFromInternal: () => syncManifestProjectsFromInternal,
|
|
40
46
|
syncProjectPlans: () => syncProjectPlans,
|
|
41
47
|
syncSessionArtifacts: () => syncSessionArtifacts
|
|
@@ -706,36 +712,69 @@ function isInsideBranchPath(branchPath, filePath) {
|
|
|
706
712
|
const relative = import_node_path.default.relative(import_node_path.default.resolve(branchPath), import_node_path.default.resolve(filePath));
|
|
707
713
|
return relative === "" || relative !== ".." && !relative.startsWith(`..${import_node_path.default.sep}`) && !import_node_path.default.isAbsolute(relative);
|
|
708
714
|
}
|
|
709
|
-
function
|
|
710
|
-
|
|
711
|
-
|
|
715
|
+
function toProjectDisplayPath(branchPath, absolutePath) {
|
|
716
|
+
const relative = import_node_path.default.relative(import_node_path.default.resolve(branchPath), import_node_path.default.resolve(absolutePath)).split(import_node_path.default.sep).join(import_node_path.default.posix.sep);
|
|
717
|
+
return relative || ".";
|
|
718
|
+
}
|
|
719
|
+
function assertAllowedProjectPath(repoRelativePath, inputPath) {
|
|
720
|
+
if (repoRelativePath === ".git" || repoRelativePath.startsWith(".git/")) {
|
|
721
|
+
throw new Error("Refusing to access .git through worker file tools");
|
|
722
|
+
}
|
|
723
|
+
if (repoRelativePath.split("/").includes("..")) {
|
|
724
|
+
throw new Error(`Invalid project file path: ${inputPath}`);
|
|
712
725
|
}
|
|
713
726
|
}
|
|
714
|
-
function
|
|
715
|
-
if (typeof
|
|
716
|
-
throw new Error(`File path must be
|
|
727
|
+
function resolveWorkerFilePath(branchPath, inputPath) {
|
|
728
|
+
if (typeof inputPath !== "string" || inputPath.length === 0 || inputPath.includes("\0")) {
|
|
729
|
+
throw new Error(`File path must be a non-empty string. Got: ${JSON.stringify(inputPath)}`);
|
|
717
730
|
}
|
|
718
|
-
const
|
|
719
|
-
if (
|
|
720
|
-
const
|
|
721
|
-
if (
|
|
722
|
-
return
|
|
731
|
+
const resolvedBranchPath = import_node_path.default.resolve(branchPath);
|
|
732
|
+
if (import_node_path.default.isAbsolute(inputPath)) {
|
|
733
|
+
const absolutePath2 = import_node_path.default.resolve(inputPath);
|
|
734
|
+
if (!isInsideBranchPath(resolvedBranchPath, absolutePath2)) {
|
|
735
|
+
return {
|
|
736
|
+
absolutePath: absolutePath2,
|
|
737
|
+
displayPath: absolutePath2,
|
|
738
|
+
repoRelativePath: null,
|
|
739
|
+
scope: "host"
|
|
740
|
+
};
|
|
723
741
|
}
|
|
742
|
+
const displayPath = toProjectDisplayPath(resolvedBranchPath, absolutePath2);
|
|
743
|
+
const repoRelativePath2 = displayPath === "." ? "" : displayPath;
|
|
744
|
+
assertAllowedProjectPath(repoRelativePath2, inputPath);
|
|
745
|
+
return {
|
|
746
|
+
absolutePath: absolutePath2,
|
|
747
|
+
displayPath,
|
|
748
|
+
repoRelativePath: repoRelativePath2,
|
|
749
|
+
scope: "project"
|
|
750
|
+
};
|
|
724
751
|
}
|
|
725
|
-
|
|
726
|
-
|
|
752
|
+
const normalizedInputPath = inputPath.replace(/\\/g, "/");
|
|
753
|
+
if (normalizedInputPath.split("/").includes("..")) {
|
|
754
|
+
throw new Error(`Invalid project file path: ${inputPath}`);
|
|
727
755
|
}
|
|
728
|
-
const normalized = import_node_path.default.posix.normalize(
|
|
729
|
-
|
|
730
|
-
|
|
756
|
+
const normalized = import_node_path.default.posix.normalize(normalizedInputPath);
|
|
757
|
+
const repoRelativePath = normalized === "." ? "" : normalized.replace(/^\.\/+/, "");
|
|
758
|
+
assertAllowedProjectPath(repoRelativePath, inputPath);
|
|
759
|
+
const absolutePath = repoRelativePath ? import_node_path.default.resolve(resolvedBranchPath, ...repoRelativePath.split("/")) : resolvedBranchPath;
|
|
760
|
+
if (!isInsideBranchPath(resolvedBranchPath, absolutePath)) {
|
|
761
|
+
throw new Error(`Invalid project file path: ${inputPath}`);
|
|
731
762
|
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
763
|
+
return {
|
|
764
|
+
absolutePath,
|
|
765
|
+
displayPath: repoRelativePath || ".",
|
|
766
|
+
repoRelativePath,
|
|
767
|
+
scope: "project"
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
function resolveProjectFilePath(branchPath, inputPath) {
|
|
771
|
+
const resolved = resolveWorkerFilePath(branchPath, inputPath);
|
|
772
|
+
if (resolved.scope === "host") {
|
|
773
|
+
throw new Error(
|
|
774
|
+
`Project file writes must use a checkout-relative path. Absolute host paths are read-only in file tools: ${inputPath}`
|
|
775
|
+
);
|
|
735
776
|
}
|
|
736
|
-
|
|
737
|
-
assertInsideBranch(branchPath, absolutePath);
|
|
738
|
-
return { absolutePath, virtualPath: repoRelativePath ? `/${repoRelativePath}` : "/", repoRelativePath };
|
|
777
|
+
return resolved;
|
|
739
778
|
}
|
|
740
779
|
function resolveRemoteUrl(baseUrl, remoteUrl) {
|
|
741
780
|
if (/^https?:\/\//i.test(remoteUrl)) {
|
|
@@ -1349,6 +1388,21 @@ async function withFileMutationQueue(key, operation) {
|
|
|
1349
1388
|
function mutationQueueKey(projectId, branchName, filePath) {
|
|
1350
1389
|
return `${projectId}:${branchName}:${import_node_path.default.posix.normalize(filePath)}`;
|
|
1351
1390
|
}
|
|
1391
|
+
function readWorkerTextFile(branchPath, filePath, offset, limit) {
|
|
1392
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1393
|
+
if (!import_node_fs.default.existsSync(resolved.absolutePath) || !import_node_fs.default.statSync(resolved.absolutePath).isFile()) {
|
|
1394
|
+
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
1395
|
+
}
|
|
1396
|
+
const buffer = import_node_fs.default.readFileSync(resolved.absolutePath);
|
|
1397
|
+
const formatted = formatLineNumberedContent(buffer.toString("utf8"), offset, limit);
|
|
1398
|
+
return {
|
|
1399
|
+
type: "read",
|
|
1400
|
+
kind: "text",
|
|
1401
|
+
file: resolved.displayPath,
|
|
1402
|
+
gitBlobHash: getGitBlobHashForContent(buffer),
|
|
1403
|
+
...formatted
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1352
1406
|
async function executeReadFileOperation(input) {
|
|
1353
1407
|
const artifact = await resolveArtifactEnvFilePath({
|
|
1354
1408
|
filePath: input.message.filePath,
|
|
@@ -1363,55 +1417,43 @@ async function executeReadFileOperation(input) {
|
|
|
1363
1417
|
if (!import_node_fs.default.existsSync(artifact.absolutePath) || !import_node_fs.default.statSync(artifact.absolutePath).isFile()) {
|
|
1364
1418
|
throw new Error(`File not found: ${artifact.virtualPath}`);
|
|
1365
1419
|
}
|
|
1366
|
-
const
|
|
1367
|
-
const
|
|
1420
|
+
const buffer = import_node_fs.default.readFileSync(artifact.absolutePath);
|
|
1421
|
+
const formatted = formatLineNumberedContent(buffer.toString("utf8"), input.message.offset, input.message.limit);
|
|
1368
1422
|
return {
|
|
1369
1423
|
type: "read",
|
|
1370
1424
|
kind: "text",
|
|
1371
1425
|
file: artifact.virtualPath,
|
|
1372
|
-
gitBlobHash: getGitBlobHashForContent(
|
|
1373
|
-
...
|
|
1426
|
+
gitBlobHash: getGitBlobHashForContent(buffer),
|
|
1427
|
+
...formatted
|
|
1374
1428
|
};
|
|
1375
1429
|
}
|
|
1376
1430
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1377
|
-
|
|
1378
|
-
if (!import_node_fs.default.existsSync(resolved.absolutePath) || !import_node_fs.default.statSync(resolved.absolutePath).isFile()) {
|
|
1379
|
-
throw new Error(`File not found: ${resolved.virtualPath}`);
|
|
1380
|
-
}
|
|
1381
|
-
const buffer = import_node_fs.default.readFileSync(resolved.absolutePath);
|
|
1382
|
-
const formatted = formatLineNumberedContent(buffer.toString("utf8"), input.message.offset, input.message.limit);
|
|
1383
|
-
return {
|
|
1384
|
-
type: "read",
|
|
1385
|
-
kind: "text",
|
|
1386
|
-
file: resolved.virtualPath,
|
|
1387
|
-
gitBlobHash: getGitBlobHashForContent(buffer),
|
|
1388
|
-
...formatted
|
|
1389
|
-
};
|
|
1431
|
+
return readWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.offset, input.message.limit);
|
|
1390
1432
|
}
|
|
1391
1433
|
async function executeWriteFileOperation(input) {
|
|
1392
1434
|
rejectArtifactWritePath(input.message.filePath);
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1435
|
+
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1436
|
+
const resolved = resolveProjectFilePath(workspace.branchPath, input.message.filePath);
|
|
1437
|
+
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved.repoRelativePath), async () => {
|
|
1396
1438
|
import_node_fs.default.mkdirSync(import_node_path.default.dirname(resolved.absolutePath), { recursive: true });
|
|
1397
1439
|
import_node_fs.default.writeFileSync(resolved.absolutePath, input.message.content, "utf8");
|
|
1398
1440
|
return {
|
|
1399
1441
|
type: "write",
|
|
1400
|
-
file: resolved.
|
|
1442
|
+
file: resolved.displayPath,
|
|
1401
1443
|
gitBlobHash: getGitBlobHashForContent(input.message.content)
|
|
1402
1444
|
};
|
|
1403
1445
|
});
|
|
1404
1446
|
}
|
|
1405
1447
|
async function executeEditFileOperation(input) {
|
|
1406
1448
|
rejectArtifactWritePath(input.message.filePath);
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1449
|
+
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1450
|
+
const resolved = resolveProjectFilePath(workspace.branchPath, input.message.filePath);
|
|
1451
|
+
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved.repoRelativePath), async () => {
|
|
1410
1452
|
if (!Array.isArray(input.message.edits) || input.message.edits.length === 0) {
|
|
1411
1453
|
throw new Error("edit requires at least one replacement");
|
|
1412
1454
|
}
|
|
1413
1455
|
if (!import_node_fs.default.existsSync(resolved.absolutePath) || !import_node_fs.default.statSync(resolved.absolutePath).isFile()) {
|
|
1414
|
-
throw new Error(`File not found: ${resolved.
|
|
1456
|
+
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
1415
1457
|
}
|
|
1416
1458
|
const originalContent = import_node_fs.default.readFileSync(resolved.absolutePath, "utf8");
|
|
1417
1459
|
const ranges = input.message.edits.map((edit, index) => {
|
|
@@ -1446,7 +1488,7 @@ async function executeEditFileOperation(input) {
|
|
|
1446
1488
|
import_node_fs.default.writeFileSync(resolved.absolutePath, nextContent, "utf8");
|
|
1447
1489
|
return {
|
|
1448
1490
|
type: "edit",
|
|
1449
|
-
file: resolved.
|
|
1491
|
+
file: resolved.displayPath,
|
|
1450
1492
|
edits: input.message.edits.length
|
|
1451
1493
|
};
|
|
1452
1494
|
});
|
|
@@ -1466,47 +1508,70 @@ function wildcardToRegex(pattern) {
|
|
|
1466
1508
|
function normalizeFindPattern(pattern) {
|
|
1467
1509
|
return /[*?]/.test(pattern) ? pattern : `*${pattern}*`;
|
|
1468
1510
|
}
|
|
1469
|
-
function
|
|
1470
|
-
const relative = import_node_path.default.relative(branchPath, absolutePath).split(import_node_path.default.sep).join(import_node_path.default.posix.sep);
|
|
1471
|
-
return relative ? `/${relative}` : "/";
|
|
1472
|
-
}
|
|
1473
|
-
function walkProjectEntries(branchPath, startPath) {
|
|
1511
|
+
function walkWorkerEntries(branchPath, start) {
|
|
1474
1512
|
const entries = [];
|
|
1475
|
-
const
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1513
|
+
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
1514
|
+
const visit = (absolutePath, isRoot) => {
|
|
1515
|
+
let stat;
|
|
1516
|
+
try {
|
|
1517
|
+
stat = import_node_fs.default.statSync(absolutePath);
|
|
1518
|
+
} catch (error) {
|
|
1519
|
+
if (isRoot) throw error;
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
const displayPath = start.scope === "project" ? toProjectDisplayPath(branchPath, absolutePath) : import_node_path.default.resolve(absolutePath);
|
|
1523
|
+
const hostRelativePath = import_node_path.default.relative(start.absolutePath, absolutePath).split(import_node_path.default.sep).join(import_node_path.default.posix.sep);
|
|
1524
|
+
const matchPath = start.scope === "project" ? displayPath : hostRelativePath || import_node_path.default.basename(start.absolutePath);
|
|
1525
|
+
entries.push({ absolutePath, displayPath, matchPath, stat });
|
|
1479
1526
|
if (!stat.isDirectory()) return;
|
|
1480
|
-
|
|
1527
|
+
let realDirectoryPath;
|
|
1528
|
+
try {
|
|
1529
|
+
realDirectoryPath = import_node_fs.default.realpathSync(absolutePath);
|
|
1530
|
+
} catch (error) {
|
|
1531
|
+
if (isRoot) throw error;
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
if (visitedDirectories.has(realDirectoryPath)) return;
|
|
1535
|
+
visitedDirectories.add(realDirectoryPath);
|
|
1536
|
+
let children;
|
|
1537
|
+
try {
|
|
1538
|
+
children = import_node_fs.default.readdirSync(absolutePath, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
1539
|
+
} catch (error) {
|
|
1540
|
+
if (isRoot) throw error;
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1481
1543
|
for (const child of children) {
|
|
1482
1544
|
if (IGNORED_ENTRY_NAMES.has(child.name)) continue;
|
|
1483
|
-
visit(import_node_path.default.join(absolutePath, child.name));
|
|
1545
|
+
visit(import_node_path.default.join(absolutePath, child.name), false);
|
|
1484
1546
|
}
|
|
1485
1547
|
};
|
|
1486
|
-
visit(
|
|
1548
|
+
visit(start.absolutePath, true);
|
|
1487
1549
|
return entries;
|
|
1488
1550
|
}
|
|
1489
1551
|
function isProbablyText(bytes) {
|
|
1490
1552
|
return !bytes.subarray(0, Math.min(bytes.length, 4096)).includes(0);
|
|
1491
1553
|
}
|
|
1492
|
-
|
|
1493
|
-
const
|
|
1494
|
-
const start = resolveProjectFilePath(workspace.branchPath, input.message.path ?? "/");
|
|
1554
|
+
function grepWorkerFiles(branchPath, input) {
|
|
1555
|
+
const start = resolveWorkerFilePath(branchPath, input.path ?? ".");
|
|
1495
1556
|
if (!import_node_fs.default.existsSync(start.absolutePath)) {
|
|
1496
|
-
throw new Error(`Path not found: ${start.
|
|
1557
|
+
throw new Error(`Path not found: ${start.displayPath}`);
|
|
1497
1558
|
}
|
|
1498
|
-
const limit = normalizeToolLimit(input.
|
|
1499
|
-
const flags = input.
|
|
1500
|
-
const regex = new RegExp(input.
|
|
1501
|
-
const globRegex = input.
|
|
1559
|
+
const limit = normalizeToolLimit(input.limit, 100, 1e3);
|
|
1560
|
+
const flags = input.caseSensitive === false ? "i" : "";
|
|
1561
|
+
const regex = new RegExp(input.pattern, flags);
|
|
1562
|
+
const globRegex = input.glob ? wildcardToRegex(input.glob) : null;
|
|
1502
1563
|
const lines = [];
|
|
1503
1564
|
let matchCount = 0;
|
|
1504
1565
|
let truncated = false;
|
|
1505
|
-
for (const entry of
|
|
1566
|
+
for (const entry of walkWorkerEntries(branchPath, start)) {
|
|
1506
1567
|
if (!entry.stat.isFile()) continue;
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1568
|
+
if (globRegex && !globRegex.test(entry.matchPath)) continue;
|
|
1569
|
+
let bytes;
|
|
1570
|
+
try {
|
|
1571
|
+
bytes = import_node_fs.default.readFileSync(entry.absolutePath);
|
|
1572
|
+
} catch {
|
|
1573
|
+
continue;
|
|
1574
|
+
}
|
|
1510
1575
|
if (!isProbablyText(bytes)) continue;
|
|
1511
1576
|
const fileLines = bytes.toString("utf8").split(/\r?\n/);
|
|
1512
1577
|
for (let index = 0; index < fileLines.length; index += 1) {
|
|
@@ -1517,7 +1582,7 @@ async function executeGrepOperation(input) {
|
|
|
1517
1582
|
matchCount += 1;
|
|
1518
1583
|
if (lines.length < limit) {
|
|
1519
1584
|
const column = Math.max(1, (match.index ?? 0) + 1);
|
|
1520
|
-
lines.push(`${entry.
|
|
1585
|
+
lines.push(`${entry.displayPath}:${index + 1}:${column}: ${line.slice(0, MAX_LINE_LENGTH)}`);
|
|
1521
1586
|
} else {
|
|
1522
1587
|
truncated = true;
|
|
1523
1588
|
}
|
|
@@ -1532,26 +1597,35 @@ async function executeGrepOperation(input) {
|
|
|
1532
1597
|
truncated
|
|
1533
1598
|
};
|
|
1534
1599
|
}
|
|
1535
|
-
async function
|
|
1600
|
+
async function executeGrepOperation(input) {
|
|
1536
1601
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1537
|
-
|
|
1602
|
+
return grepWorkerFiles(workspace.branchPath, {
|
|
1603
|
+
pattern: input.message.pattern,
|
|
1604
|
+
path: input.message.path,
|
|
1605
|
+
glob: input.message.glob,
|
|
1606
|
+
caseSensitive: input.message.caseSensitive,
|
|
1607
|
+
limit: input.message.limit
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
function findWorkerFiles(branchPath, input) {
|
|
1611
|
+
const start = resolveWorkerFilePath(branchPath, input.path ?? ".");
|
|
1538
1612
|
if (!import_node_fs.default.existsSync(start.absolutePath) || !import_node_fs.default.statSync(start.absolutePath).isDirectory()) {
|
|
1539
|
-
throw new Error(`Directory not found: ${start.
|
|
1613
|
+
throw new Error(`Directory not found: ${start.displayPath}`);
|
|
1540
1614
|
}
|
|
1541
|
-
const pattern = normalizeFindPattern(input.
|
|
1615
|
+
const pattern = normalizeFindPattern(input.pattern ?? "*");
|
|
1542
1616
|
const matcher = wildcardToRegex(pattern);
|
|
1543
|
-
const entryType = input.
|
|
1544
|
-
const limit = normalizeToolLimit(input.
|
|
1617
|
+
const entryType = input.entryType ?? "file";
|
|
1618
|
+
const limit = normalizeToolLimit(input.limit, 200, 1e3);
|
|
1545
1619
|
const matches = [];
|
|
1546
1620
|
let count = 0;
|
|
1547
1621
|
let truncated = false;
|
|
1548
|
-
for (const entry of
|
|
1549
|
-
if (entry.
|
|
1622
|
+
for (const entry of walkWorkerEntries(branchPath, start)) {
|
|
1623
|
+
if (entry.absolutePath === start.absolutePath) continue;
|
|
1550
1624
|
const isDirectory = entry.stat.isDirectory();
|
|
1551
1625
|
if (entryType === "file" && !entry.stat.isFile()) continue;
|
|
1552
1626
|
if (entryType === "directory" && !isDirectory) continue;
|
|
1553
|
-
const label = isDirectory ? `${entry.
|
|
1554
|
-
if (!matcher.test(import_node_path.default.
|
|
1627
|
+
const label = isDirectory ? `${entry.displayPath}${start.scope === "host" ? import_node_path.default.sep : "/"}` : entry.displayPath;
|
|
1628
|
+
if (!matcher.test(import_node_path.default.basename(entry.absolutePath)) && !matcher.test(entry.matchPath)) continue;
|
|
1555
1629
|
count += 1;
|
|
1556
1630
|
if (matches.length < limit) {
|
|
1557
1631
|
matches.push(label);
|
|
@@ -1567,25 +1641,59 @@ async function executeFindOperation(input) {
|
|
|
1567
1641
|
truncated
|
|
1568
1642
|
};
|
|
1569
1643
|
}
|
|
1570
|
-
async function
|
|
1644
|
+
async function executeFindOperation(input) {
|
|
1571
1645
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1572
|
-
|
|
1646
|
+
return findWorkerFiles(workspace.branchPath, {
|
|
1647
|
+
pattern: input.message.pattern,
|
|
1648
|
+
path: input.message.path,
|
|
1649
|
+
entryType: input.message.entryType,
|
|
1650
|
+
limit: input.message.limit
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
function listWorkerDirectory(branchPath, inputPath = ".", inputLimit) {
|
|
1654
|
+
const resolved = resolveWorkerFilePath(branchPath, inputPath);
|
|
1573
1655
|
if (!import_node_fs.default.existsSync(resolved.absolutePath) || !import_node_fs.default.statSync(resolved.absolutePath).isDirectory()) {
|
|
1574
|
-
throw new Error(`Directory not found: ${resolved.
|
|
1656
|
+
throw new Error(`Directory not found: ${resolved.displayPath}`);
|
|
1575
1657
|
}
|
|
1576
|
-
const limit = normalizeToolLimit(
|
|
1658
|
+
const limit = normalizeToolLimit(inputLimit, 200, 1e3);
|
|
1577
1659
|
const entries = import_node_fs.default.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));
|
|
1578
1660
|
const displayed = entries.slice(0, limit).map((entry) => `${entry.name}${entry.isDirectory() ? "/" : ""}`);
|
|
1579
1661
|
const truncated = entries.length > displayed.length;
|
|
1580
1662
|
const hint = truncated ? "\n... truncated. Increase limit to continue." : "";
|
|
1581
1663
|
return {
|
|
1582
1664
|
type: "ls",
|
|
1583
|
-
path: resolved.
|
|
1665
|
+
path: resolved.displayPath,
|
|
1584
1666
|
content: displayed.length > 0 ? `${displayed.join("\n")}${hint}` : "(empty directory)",
|
|
1585
1667
|
count: entries.length,
|
|
1586
1668
|
truncated
|
|
1587
1669
|
};
|
|
1588
1670
|
}
|
|
1671
|
+
async function executeLsOperation(input) {
|
|
1672
|
+
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1673
|
+
return listWorkerDirectory(workspace.branchPath, input.message.path, input.message.limit);
|
|
1674
|
+
}
|
|
1675
|
+
function readWorkerImageFile(branchPath, filePath) {
|
|
1676
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1677
|
+
if (!import_node_fs.default.existsSync(resolved.absolutePath) || !import_node_fs.default.statSync(resolved.absolutePath).isFile()) {
|
|
1678
|
+
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
1679
|
+
}
|
|
1680
|
+
const extension = import_node_path.default.extname(resolved.absolutePath).toLowerCase();
|
|
1681
|
+
const mediaType = extension === ".jpg" || extension === ".jpeg" ? "image/jpeg" : extension === ".png" ? "image/png" : null;
|
|
1682
|
+
if (!mediaType) {
|
|
1683
|
+
throw new Error("read supports PNG and JPEG image inputs only");
|
|
1684
|
+
}
|
|
1685
|
+
const bytes = import_node_fs.default.readFileSync(resolved.absolutePath);
|
|
1686
|
+
const dimensions = readImageDimensions(bytes, mediaType);
|
|
1687
|
+
return {
|
|
1688
|
+
type: "view_file_bytes",
|
|
1689
|
+
filePath: resolved.displayPath,
|
|
1690
|
+
mediaType,
|
|
1691
|
+
base64: bytes.toString("base64"),
|
|
1692
|
+
fileSizeBytes: bytes.length,
|
|
1693
|
+
width: dimensions.width,
|
|
1694
|
+
height: dimensions.height
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1589
1697
|
async function executeViewFileBytesOperation(input) {
|
|
1590
1698
|
const artifact = await resolveArtifactEnvFilePath({
|
|
1591
1699
|
filePath: input.message.filePath,
|
|
@@ -1600,44 +1708,25 @@ async function executeViewFileBytesOperation(input) {
|
|
|
1600
1708
|
if (!import_node_fs.default.existsSync(artifact.absolutePath) || !import_node_fs.default.statSync(artifact.absolutePath).isFile()) {
|
|
1601
1709
|
throw new Error(`File not found: ${artifact.virtualPath}`);
|
|
1602
1710
|
}
|
|
1603
|
-
const
|
|
1604
|
-
const
|
|
1605
|
-
if (!
|
|
1711
|
+
const extension = import_node_path.default.extname(artifact.absolutePath).toLowerCase();
|
|
1712
|
+
const mediaType = extension === ".jpg" || extension === ".jpeg" ? "image/jpeg" : extension === ".png" ? "image/png" : null;
|
|
1713
|
+
if (!mediaType) {
|
|
1606
1714
|
throw new Error("read supports PNG and JPEG image inputs only");
|
|
1607
1715
|
}
|
|
1608
|
-
const
|
|
1609
|
-
const
|
|
1716
|
+
const bytes = import_node_fs.default.readFileSync(artifact.absolutePath);
|
|
1717
|
+
const dimensions = readImageDimensions(bytes, mediaType);
|
|
1610
1718
|
return {
|
|
1611
1719
|
type: "view_file_bytes",
|
|
1612
1720
|
filePath: artifact.virtualPath,
|
|
1613
|
-
mediaType
|
|
1614
|
-
base64:
|
|
1615
|
-
fileSizeBytes:
|
|
1616
|
-
width:
|
|
1617
|
-
height:
|
|
1721
|
+
mediaType,
|
|
1722
|
+
base64: bytes.toString("base64"),
|
|
1723
|
+
fileSizeBytes: bytes.length,
|
|
1724
|
+
width: dimensions.width,
|
|
1725
|
+
height: dimensions.height
|
|
1618
1726
|
};
|
|
1619
1727
|
}
|
|
1620
1728
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1621
|
-
|
|
1622
|
-
if (!import_node_fs.default.existsSync(resolved.absolutePath) || !import_node_fs.default.statSync(resolved.absolutePath).isFile()) {
|
|
1623
|
-
throw new Error(`File not found: ${resolved.virtualPath}`);
|
|
1624
|
-
}
|
|
1625
|
-
const extension = import_node_path.default.extname(resolved.absolutePath).toLowerCase();
|
|
1626
|
-
const mediaType = extension === ".jpg" || extension === ".jpeg" ? "image/jpeg" : extension === ".png" ? "image/png" : null;
|
|
1627
|
-
if (!mediaType) {
|
|
1628
|
-
throw new Error("read supports PNG and JPEG image inputs only");
|
|
1629
|
-
}
|
|
1630
|
-
const bytes = import_node_fs.default.readFileSync(resolved.absolutePath);
|
|
1631
|
-
const dimensions = readImageDimensions(bytes, mediaType);
|
|
1632
|
-
return {
|
|
1633
|
-
type: "view_file_bytes",
|
|
1634
|
-
filePath: resolved.virtualPath,
|
|
1635
|
-
mediaType,
|
|
1636
|
-
base64: bytes.toString("base64"),
|
|
1637
|
-
fileSizeBytes: bytes.length,
|
|
1638
|
-
width: dimensions.width,
|
|
1639
|
-
height: dimensions.height
|
|
1640
|
-
};
|
|
1729
|
+
return readWorkerImageFile(workspace.branchPath, input.message.filePath);
|
|
1641
1730
|
}
|
|
1642
1731
|
function stagedBlobSizeBytes(context) {
|
|
1643
1732
|
const names = runInternalGit(context, ["diff", "--cached", "--name-only", "-z", "--", ".", ":(exclude).git"]).split("\0").filter(Boolean);
|
|
@@ -2830,11 +2919,17 @@ if (isCliEntrypoint()) {
|
|
|
2830
2919
|
0 && (module.exports = {
|
|
2831
2920
|
ensureVisibleGitCheckout,
|
|
2832
2921
|
findRepositorySyncBlockers,
|
|
2922
|
+
findWorkerFiles,
|
|
2923
|
+
grepWorkerFiles,
|
|
2833
2924
|
isArtifactEnvPath,
|
|
2925
|
+
listWorkerDirectory,
|
|
2834
2926
|
prepareArtifactEnvForShell,
|
|
2835
2927
|
preparePlanEnvForShell,
|
|
2928
|
+
readWorkerImageFile,
|
|
2929
|
+
readWorkerTextFile,
|
|
2836
2930
|
resolveHostShell,
|
|
2837
2931
|
resolveProjectFilePath,
|
|
2932
|
+
resolveWorkerFilePath,
|
|
2838
2933
|
syncManifestProjectsFromInternal,
|
|
2839
2934
|
syncProjectPlans,
|
|
2840
2935
|
syncSessionArtifacts
|
package/dist/cjs/package.json
CHANGED
package/dist/mjs/main.mjs
CHANGED
|
@@ -663,36 +663,69 @@ function isInsideBranchPath(branchPath, filePath) {
|
|
|
663
663
|
const relative = path.relative(path.resolve(branchPath), path.resolve(filePath));
|
|
664
664
|
return relative === "" || relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
665
665
|
}
|
|
666
|
-
function
|
|
667
|
-
|
|
668
|
-
|
|
666
|
+
function toProjectDisplayPath(branchPath, absolutePath) {
|
|
667
|
+
const relative = path.relative(path.resolve(branchPath), path.resolve(absolutePath)).split(path.sep).join(path.posix.sep);
|
|
668
|
+
return relative || ".";
|
|
669
|
+
}
|
|
670
|
+
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
|
+
if (repoRelativePath.split("/").includes("..")) {
|
|
675
|
+
throw new Error(`Invalid project file path: ${inputPath}`);
|
|
669
676
|
}
|
|
670
677
|
}
|
|
671
|
-
function
|
|
672
|
-
if (typeof
|
|
673
|
-
throw new Error(`File path must be
|
|
678
|
+
function resolveWorkerFilePath(branchPath, inputPath) {
|
|
679
|
+
if (typeof inputPath !== "string" || inputPath.length === 0 || inputPath.includes("\0")) {
|
|
680
|
+
throw new Error(`File path must be a non-empty string. Got: ${JSON.stringify(inputPath)}`);
|
|
674
681
|
}
|
|
675
|
-
const
|
|
676
|
-
if (
|
|
677
|
-
const
|
|
678
|
-
if (
|
|
679
|
-
return
|
|
682
|
+
const resolvedBranchPath = path.resolve(branchPath);
|
|
683
|
+
if (path.isAbsolute(inputPath)) {
|
|
684
|
+
const absolutePath2 = path.resolve(inputPath);
|
|
685
|
+
if (!isInsideBranchPath(resolvedBranchPath, absolutePath2)) {
|
|
686
|
+
return {
|
|
687
|
+
absolutePath: absolutePath2,
|
|
688
|
+
displayPath: absolutePath2,
|
|
689
|
+
repoRelativePath: null,
|
|
690
|
+
scope: "host"
|
|
691
|
+
};
|
|
680
692
|
}
|
|
693
|
+
const displayPath = toProjectDisplayPath(resolvedBranchPath, absolutePath2);
|
|
694
|
+
const repoRelativePath2 = displayPath === "." ? "" : displayPath;
|
|
695
|
+
assertAllowedProjectPath(repoRelativePath2, inputPath);
|
|
696
|
+
return {
|
|
697
|
+
absolutePath: absolutePath2,
|
|
698
|
+
displayPath,
|
|
699
|
+
repoRelativePath: repoRelativePath2,
|
|
700
|
+
scope: "project"
|
|
701
|
+
};
|
|
681
702
|
}
|
|
682
|
-
|
|
683
|
-
|
|
703
|
+
const normalizedInputPath = inputPath.replace(/\\/g, "/");
|
|
704
|
+
if (normalizedInputPath.split("/").includes("..")) {
|
|
705
|
+
throw new Error(`Invalid project file path: ${inputPath}`);
|
|
684
706
|
}
|
|
685
|
-
const normalized = path.posix.normalize(
|
|
686
|
-
|
|
687
|
-
|
|
707
|
+
const normalized = path.posix.normalize(normalizedInputPath);
|
|
708
|
+
const repoRelativePath = normalized === "." ? "" : normalized.replace(/^\.\/+/, "");
|
|
709
|
+
assertAllowedProjectPath(repoRelativePath, inputPath);
|
|
710
|
+
const absolutePath = repoRelativePath ? path.resolve(resolvedBranchPath, ...repoRelativePath.split("/")) : resolvedBranchPath;
|
|
711
|
+
if (!isInsideBranchPath(resolvedBranchPath, absolutePath)) {
|
|
712
|
+
throw new Error(`Invalid project file path: ${inputPath}`);
|
|
688
713
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
714
|
+
return {
|
|
715
|
+
absolutePath,
|
|
716
|
+
displayPath: repoRelativePath || ".",
|
|
717
|
+
repoRelativePath,
|
|
718
|
+
scope: "project"
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
function resolveProjectFilePath(branchPath, inputPath) {
|
|
722
|
+
const resolved = resolveWorkerFilePath(branchPath, inputPath);
|
|
723
|
+
if (resolved.scope === "host") {
|
|
724
|
+
throw new Error(
|
|
725
|
+
`Project file writes must use a checkout-relative path. Absolute host paths are read-only in file tools: ${inputPath}`
|
|
726
|
+
);
|
|
692
727
|
}
|
|
693
|
-
|
|
694
|
-
assertInsideBranch(branchPath, absolutePath);
|
|
695
|
-
return { absolutePath, virtualPath: repoRelativePath ? `/${repoRelativePath}` : "/", repoRelativePath };
|
|
728
|
+
return resolved;
|
|
696
729
|
}
|
|
697
730
|
function resolveRemoteUrl(baseUrl, remoteUrl) {
|
|
698
731
|
if (/^https?:\/\//i.test(remoteUrl)) {
|
|
@@ -1306,6 +1339,21 @@ async function withFileMutationQueue(key, operation) {
|
|
|
1306
1339
|
function mutationQueueKey(projectId, branchName, filePath) {
|
|
1307
1340
|
return `${projectId}:${branchName}:${path.posix.normalize(filePath)}`;
|
|
1308
1341
|
}
|
|
1342
|
+
function readWorkerTextFile(branchPath, filePath, offset, limit) {
|
|
1343
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1344
|
+
if (!fs.existsSync(resolved.absolutePath) || !fs.statSync(resolved.absolutePath).isFile()) {
|
|
1345
|
+
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
1346
|
+
}
|
|
1347
|
+
const buffer = fs.readFileSync(resolved.absolutePath);
|
|
1348
|
+
const formatted = formatLineNumberedContent(buffer.toString("utf8"), offset, limit);
|
|
1349
|
+
return {
|
|
1350
|
+
type: "read",
|
|
1351
|
+
kind: "text",
|
|
1352
|
+
file: resolved.displayPath,
|
|
1353
|
+
gitBlobHash: getGitBlobHashForContent(buffer),
|
|
1354
|
+
...formatted
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1309
1357
|
async function executeReadFileOperation(input) {
|
|
1310
1358
|
const artifact = await resolveArtifactEnvFilePath({
|
|
1311
1359
|
filePath: input.message.filePath,
|
|
@@ -1320,55 +1368,43 @@ async function executeReadFileOperation(input) {
|
|
|
1320
1368
|
if (!fs.existsSync(artifact.absolutePath) || !fs.statSync(artifact.absolutePath).isFile()) {
|
|
1321
1369
|
throw new Error(`File not found: ${artifact.virtualPath}`);
|
|
1322
1370
|
}
|
|
1323
|
-
const
|
|
1324
|
-
const
|
|
1371
|
+
const buffer = fs.readFileSync(artifact.absolutePath);
|
|
1372
|
+
const formatted = formatLineNumberedContent(buffer.toString("utf8"), input.message.offset, input.message.limit);
|
|
1325
1373
|
return {
|
|
1326
1374
|
type: "read",
|
|
1327
1375
|
kind: "text",
|
|
1328
1376
|
file: artifact.virtualPath,
|
|
1329
|
-
gitBlobHash: getGitBlobHashForContent(
|
|
1330
|
-
...
|
|
1377
|
+
gitBlobHash: getGitBlobHashForContent(buffer),
|
|
1378
|
+
...formatted
|
|
1331
1379
|
};
|
|
1332
1380
|
}
|
|
1333
1381
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1334
|
-
|
|
1335
|
-
if (!fs.existsSync(resolved.absolutePath) || !fs.statSync(resolved.absolutePath).isFile()) {
|
|
1336
|
-
throw new Error(`File not found: ${resolved.virtualPath}`);
|
|
1337
|
-
}
|
|
1338
|
-
const buffer = fs.readFileSync(resolved.absolutePath);
|
|
1339
|
-
const formatted = formatLineNumberedContent(buffer.toString("utf8"), input.message.offset, input.message.limit);
|
|
1340
|
-
return {
|
|
1341
|
-
type: "read",
|
|
1342
|
-
kind: "text",
|
|
1343
|
-
file: resolved.virtualPath,
|
|
1344
|
-
gitBlobHash: getGitBlobHashForContent(buffer),
|
|
1345
|
-
...formatted
|
|
1346
|
-
};
|
|
1382
|
+
return readWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.offset, input.message.limit);
|
|
1347
1383
|
}
|
|
1348
1384
|
async function executeWriteFileOperation(input) {
|
|
1349
1385
|
rejectArtifactWritePath(input.message.filePath);
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1386
|
+
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1387
|
+
const resolved = resolveProjectFilePath(workspace.branchPath, input.message.filePath);
|
|
1388
|
+
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved.repoRelativePath), async () => {
|
|
1353
1389
|
fs.mkdirSync(path.dirname(resolved.absolutePath), { recursive: true });
|
|
1354
1390
|
fs.writeFileSync(resolved.absolutePath, input.message.content, "utf8");
|
|
1355
1391
|
return {
|
|
1356
1392
|
type: "write",
|
|
1357
|
-
file: resolved.
|
|
1393
|
+
file: resolved.displayPath,
|
|
1358
1394
|
gitBlobHash: getGitBlobHashForContent(input.message.content)
|
|
1359
1395
|
};
|
|
1360
1396
|
});
|
|
1361
1397
|
}
|
|
1362
1398
|
async function executeEditFileOperation(input) {
|
|
1363
1399
|
rejectArtifactWritePath(input.message.filePath);
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1400
|
+
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1401
|
+
const resolved = resolveProjectFilePath(workspace.branchPath, input.message.filePath);
|
|
1402
|
+
return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved.repoRelativePath), async () => {
|
|
1367
1403
|
if (!Array.isArray(input.message.edits) || input.message.edits.length === 0) {
|
|
1368
1404
|
throw new Error("edit requires at least one replacement");
|
|
1369
1405
|
}
|
|
1370
1406
|
if (!fs.existsSync(resolved.absolutePath) || !fs.statSync(resolved.absolutePath).isFile()) {
|
|
1371
|
-
throw new Error(`File not found: ${resolved.
|
|
1407
|
+
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
1372
1408
|
}
|
|
1373
1409
|
const originalContent = fs.readFileSync(resolved.absolutePath, "utf8");
|
|
1374
1410
|
const ranges = input.message.edits.map((edit, index) => {
|
|
@@ -1403,7 +1439,7 @@ async function executeEditFileOperation(input) {
|
|
|
1403
1439
|
fs.writeFileSync(resolved.absolutePath, nextContent, "utf8");
|
|
1404
1440
|
return {
|
|
1405
1441
|
type: "edit",
|
|
1406
|
-
file: resolved.
|
|
1442
|
+
file: resolved.displayPath,
|
|
1407
1443
|
edits: input.message.edits.length
|
|
1408
1444
|
};
|
|
1409
1445
|
});
|
|
@@ -1423,47 +1459,70 @@ function wildcardToRegex(pattern) {
|
|
|
1423
1459
|
function normalizeFindPattern(pattern) {
|
|
1424
1460
|
return /[*?]/.test(pattern) ? pattern : `*${pattern}*`;
|
|
1425
1461
|
}
|
|
1426
|
-
function
|
|
1427
|
-
const relative = path.relative(branchPath, absolutePath).split(path.sep).join(path.posix.sep);
|
|
1428
|
-
return relative ? `/${relative}` : "/";
|
|
1429
|
-
}
|
|
1430
|
-
function walkProjectEntries(branchPath, startPath) {
|
|
1462
|
+
function walkWorkerEntries(branchPath, start) {
|
|
1431
1463
|
const entries = [];
|
|
1432
|
-
const
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1464
|
+
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
1465
|
+
const visit = (absolutePath, isRoot) => {
|
|
1466
|
+
let stat;
|
|
1467
|
+
try {
|
|
1468
|
+
stat = fs.statSync(absolutePath);
|
|
1469
|
+
} catch (error) {
|
|
1470
|
+
if (isRoot) throw error;
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
const displayPath = start.scope === "project" ? toProjectDisplayPath(branchPath, absolutePath) : path.resolve(absolutePath);
|
|
1474
|
+
const hostRelativePath = path.relative(start.absolutePath, absolutePath).split(path.sep).join(path.posix.sep);
|
|
1475
|
+
const matchPath = start.scope === "project" ? displayPath : hostRelativePath || path.basename(start.absolutePath);
|
|
1476
|
+
entries.push({ absolutePath, displayPath, matchPath, stat });
|
|
1436
1477
|
if (!stat.isDirectory()) return;
|
|
1437
|
-
|
|
1478
|
+
let realDirectoryPath;
|
|
1479
|
+
try {
|
|
1480
|
+
realDirectoryPath = fs.realpathSync(absolutePath);
|
|
1481
|
+
} catch (error) {
|
|
1482
|
+
if (isRoot) throw error;
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1485
|
+
if (visitedDirectories.has(realDirectoryPath)) return;
|
|
1486
|
+
visitedDirectories.add(realDirectoryPath);
|
|
1487
|
+
let children;
|
|
1488
|
+
try {
|
|
1489
|
+
children = fs.readdirSync(absolutePath, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
if (isRoot) throw error;
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1438
1494
|
for (const child of children) {
|
|
1439
1495
|
if (IGNORED_ENTRY_NAMES.has(child.name)) continue;
|
|
1440
|
-
visit(path.join(absolutePath, child.name));
|
|
1496
|
+
visit(path.join(absolutePath, child.name), false);
|
|
1441
1497
|
}
|
|
1442
1498
|
};
|
|
1443
|
-
visit(
|
|
1499
|
+
visit(start.absolutePath, true);
|
|
1444
1500
|
return entries;
|
|
1445
1501
|
}
|
|
1446
1502
|
function isProbablyText(bytes) {
|
|
1447
1503
|
return !bytes.subarray(0, Math.min(bytes.length, 4096)).includes(0);
|
|
1448
1504
|
}
|
|
1449
|
-
|
|
1450
|
-
const
|
|
1451
|
-
const start = resolveProjectFilePath(workspace.branchPath, input.message.path ?? "/");
|
|
1505
|
+
function grepWorkerFiles(branchPath, input) {
|
|
1506
|
+
const start = resolveWorkerFilePath(branchPath, input.path ?? ".");
|
|
1452
1507
|
if (!fs.existsSync(start.absolutePath)) {
|
|
1453
|
-
throw new Error(`Path not found: ${start.
|
|
1508
|
+
throw new Error(`Path not found: ${start.displayPath}`);
|
|
1454
1509
|
}
|
|
1455
|
-
const limit = normalizeToolLimit(input.
|
|
1456
|
-
const flags = input.
|
|
1457
|
-
const regex = new RegExp(input.
|
|
1458
|
-
const globRegex = input.
|
|
1510
|
+
const limit = normalizeToolLimit(input.limit, 100, 1e3);
|
|
1511
|
+
const flags = input.caseSensitive === false ? "i" : "";
|
|
1512
|
+
const regex = new RegExp(input.pattern, flags);
|
|
1513
|
+
const globRegex = input.glob ? wildcardToRegex(input.glob) : null;
|
|
1459
1514
|
const lines = [];
|
|
1460
1515
|
let matchCount = 0;
|
|
1461
1516
|
let truncated = false;
|
|
1462
|
-
for (const entry of
|
|
1517
|
+
for (const entry of walkWorkerEntries(branchPath, start)) {
|
|
1463
1518
|
if (!entry.stat.isFile()) continue;
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1519
|
+
if (globRegex && !globRegex.test(entry.matchPath)) continue;
|
|
1520
|
+
let bytes;
|
|
1521
|
+
try {
|
|
1522
|
+
bytes = fs.readFileSync(entry.absolutePath);
|
|
1523
|
+
} catch {
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1467
1526
|
if (!isProbablyText(bytes)) continue;
|
|
1468
1527
|
const fileLines = bytes.toString("utf8").split(/\r?\n/);
|
|
1469
1528
|
for (let index = 0; index < fileLines.length; index += 1) {
|
|
@@ -1474,7 +1533,7 @@ async function executeGrepOperation(input) {
|
|
|
1474
1533
|
matchCount += 1;
|
|
1475
1534
|
if (lines.length < limit) {
|
|
1476
1535
|
const column = Math.max(1, (match.index ?? 0) + 1);
|
|
1477
|
-
lines.push(`${entry.
|
|
1536
|
+
lines.push(`${entry.displayPath}:${index + 1}:${column}: ${line.slice(0, MAX_LINE_LENGTH)}`);
|
|
1478
1537
|
} else {
|
|
1479
1538
|
truncated = true;
|
|
1480
1539
|
}
|
|
@@ -1489,26 +1548,35 @@ async function executeGrepOperation(input) {
|
|
|
1489
1548
|
truncated
|
|
1490
1549
|
};
|
|
1491
1550
|
}
|
|
1492
|
-
async function
|
|
1551
|
+
async function executeGrepOperation(input) {
|
|
1493
1552
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1494
|
-
|
|
1553
|
+
return grepWorkerFiles(workspace.branchPath, {
|
|
1554
|
+
pattern: input.message.pattern,
|
|
1555
|
+
path: input.message.path,
|
|
1556
|
+
glob: input.message.glob,
|
|
1557
|
+
caseSensitive: input.message.caseSensitive,
|
|
1558
|
+
limit: input.message.limit
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
function findWorkerFiles(branchPath, input) {
|
|
1562
|
+
const start = resolveWorkerFilePath(branchPath, input.path ?? ".");
|
|
1495
1563
|
if (!fs.existsSync(start.absolutePath) || !fs.statSync(start.absolutePath).isDirectory()) {
|
|
1496
|
-
throw new Error(`Directory not found: ${start.
|
|
1564
|
+
throw new Error(`Directory not found: ${start.displayPath}`);
|
|
1497
1565
|
}
|
|
1498
|
-
const pattern = normalizeFindPattern(input.
|
|
1566
|
+
const pattern = normalizeFindPattern(input.pattern ?? "*");
|
|
1499
1567
|
const matcher = wildcardToRegex(pattern);
|
|
1500
|
-
const entryType = input.
|
|
1501
|
-
const limit = normalizeToolLimit(input.
|
|
1568
|
+
const entryType = input.entryType ?? "file";
|
|
1569
|
+
const limit = normalizeToolLimit(input.limit, 200, 1e3);
|
|
1502
1570
|
const matches = [];
|
|
1503
1571
|
let count = 0;
|
|
1504
1572
|
let truncated = false;
|
|
1505
|
-
for (const entry of
|
|
1506
|
-
if (entry.
|
|
1573
|
+
for (const entry of walkWorkerEntries(branchPath, start)) {
|
|
1574
|
+
if (entry.absolutePath === start.absolutePath) continue;
|
|
1507
1575
|
const isDirectory = entry.stat.isDirectory();
|
|
1508
1576
|
if (entryType === "file" && !entry.stat.isFile()) continue;
|
|
1509
1577
|
if (entryType === "directory" && !isDirectory) continue;
|
|
1510
|
-
const label = isDirectory ? `${entry.
|
|
1511
|
-
if (!matcher.test(path.
|
|
1578
|
+
const label = isDirectory ? `${entry.displayPath}${start.scope === "host" ? path.sep : "/"}` : entry.displayPath;
|
|
1579
|
+
if (!matcher.test(path.basename(entry.absolutePath)) && !matcher.test(entry.matchPath)) continue;
|
|
1512
1580
|
count += 1;
|
|
1513
1581
|
if (matches.length < limit) {
|
|
1514
1582
|
matches.push(label);
|
|
@@ -1524,25 +1592,59 @@ async function executeFindOperation(input) {
|
|
|
1524
1592
|
truncated
|
|
1525
1593
|
};
|
|
1526
1594
|
}
|
|
1527
|
-
async function
|
|
1595
|
+
async function executeFindOperation(input) {
|
|
1528
1596
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1529
|
-
|
|
1597
|
+
return findWorkerFiles(workspace.branchPath, {
|
|
1598
|
+
pattern: input.message.pattern,
|
|
1599
|
+
path: input.message.path,
|
|
1600
|
+
entryType: input.message.entryType,
|
|
1601
|
+
limit: input.message.limit
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
function listWorkerDirectory(branchPath, inputPath = ".", inputLimit) {
|
|
1605
|
+
const resolved = resolveWorkerFilePath(branchPath, inputPath);
|
|
1530
1606
|
if (!fs.existsSync(resolved.absolutePath) || !fs.statSync(resolved.absolutePath).isDirectory()) {
|
|
1531
|
-
throw new Error(`Directory not found: ${resolved.
|
|
1607
|
+
throw new Error(`Directory not found: ${resolved.displayPath}`);
|
|
1532
1608
|
}
|
|
1533
|
-
const limit = normalizeToolLimit(
|
|
1609
|
+
const limit = normalizeToolLimit(inputLimit, 200, 1e3);
|
|
1534
1610
|
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));
|
|
1535
1611
|
const displayed = entries.slice(0, limit).map((entry) => `${entry.name}${entry.isDirectory() ? "/" : ""}`);
|
|
1536
1612
|
const truncated = entries.length > displayed.length;
|
|
1537
1613
|
const hint = truncated ? "\n... truncated. Increase limit to continue." : "";
|
|
1538
1614
|
return {
|
|
1539
1615
|
type: "ls",
|
|
1540
|
-
path: resolved.
|
|
1616
|
+
path: resolved.displayPath,
|
|
1541
1617
|
content: displayed.length > 0 ? `${displayed.join("\n")}${hint}` : "(empty directory)",
|
|
1542
1618
|
count: entries.length,
|
|
1543
1619
|
truncated
|
|
1544
1620
|
};
|
|
1545
1621
|
}
|
|
1622
|
+
async function executeLsOperation(input) {
|
|
1623
|
+
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1624
|
+
return listWorkerDirectory(workspace.branchPath, input.message.path, input.message.limit);
|
|
1625
|
+
}
|
|
1626
|
+
function readWorkerImageFile(branchPath, filePath) {
|
|
1627
|
+
const resolved = resolveWorkerFilePath(branchPath, filePath);
|
|
1628
|
+
if (!fs.existsSync(resolved.absolutePath) || !fs.statSync(resolved.absolutePath).isFile()) {
|
|
1629
|
+
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
1630
|
+
}
|
|
1631
|
+
const extension = path.extname(resolved.absolutePath).toLowerCase();
|
|
1632
|
+
const mediaType = extension === ".jpg" || extension === ".jpeg" ? "image/jpeg" : extension === ".png" ? "image/png" : null;
|
|
1633
|
+
if (!mediaType) {
|
|
1634
|
+
throw new Error("read supports PNG and JPEG image inputs only");
|
|
1635
|
+
}
|
|
1636
|
+
const bytes = fs.readFileSync(resolved.absolutePath);
|
|
1637
|
+
const dimensions = readImageDimensions(bytes, mediaType);
|
|
1638
|
+
return {
|
|
1639
|
+
type: "view_file_bytes",
|
|
1640
|
+
filePath: resolved.displayPath,
|
|
1641
|
+
mediaType,
|
|
1642
|
+
base64: bytes.toString("base64"),
|
|
1643
|
+
fileSizeBytes: bytes.length,
|
|
1644
|
+
width: dimensions.width,
|
|
1645
|
+
height: dimensions.height
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1546
1648
|
async function executeViewFileBytesOperation(input) {
|
|
1547
1649
|
const artifact = await resolveArtifactEnvFilePath({
|
|
1548
1650
|
filePath: input.message.filePath,
|
|
@@ -1557,44 +1659,25 @@ async function executeViewFileBytesOperation(input) {
|
|
|
1557
1659
|
if (!fs.existsSync(artifact.absolutePath) || !fs.statSync(artifact.absolutePath).isFile()) {
|
|
1558
1660
|
throw new Error(`File not found: ${artifact.virtualPath}`);
|
|
1559
1661
|
}
|
|
1560
|
-
const
|
|
1561
|
-
const
|
|
1562
|
-
if (!
|
|
1662
|
+
const extension = path.extname(artifact.absolutePath).toLowerCase();
|
|
1663
|
+
const mediaType = extension === ".jpg" || extension === ".jpeg" ? "image/jpeg" : extension === ".png" ? "image/png" : null;
|
|
1664
|
+
if (!mediaType) {
|
|
1563
1665
|
throw new Error("read supports PNG and JPEG image inputs only");
|
|
1564
1666
|
}
|
|
1565
|
-
const
|
|
1566
|
-
const
|
|
1667
|
+
const bytes = fs.readFileSync(artifact.absolutePath);
|
|
1668
|
+
const dimensions = readImageDimensions(bytes, mediaType);
|
|
1567
1669
|
return {
|
|
1568
1670
|
type: "view_file_bytes",
|
|
1569
1671
|
filePath: artifact.virtualPath,
|
|
1570
|
-
mediaType
|
|
1571
|
-
base64:
|
|
1572
|
-
fileSizeBytes:
|
|
1573
|
-
width:
|
|
1574
|
-
height:
|
|
1672
|
+
mediaType,
|
|
1673
|
+
base64: bytes.toString("base64"),
|
|
1674
|
+
fileSizeBytes: bytes.length,
|
|
1675
|
+
width: dimensions.width,
|
|
1676
|
+
height: dimensions.height
|
|
1575
1677
|
};
|
|
1576
1678
|
}
|
|
1577
1679
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1578
|
-
|
|
1579
|
-
if (!fs.existsSync(resolved.absolutePath) || !fs.statSync(resolved.absolutePath).isFile()) {
|
|
1580
|
-
throw new Error(`File not found: ${resolved.virtualPath}`);
|
|
1581
|
-
}
|
|
1582
|
-
const extension = path.extname(resolved.absolutePath).toLowerCase();
|
|
1583
|
-
const mediaType = extension === ".jpg" || extension === ".jpeg" ? "image/jpeg" : extension === ".png" ? "image/png" : null;
|
|
1584
|
-
if (!mediaType) {
|
|
1585
|
-
throw new Error("read supports PNG and JPEG image inputs only");
|
|
1586
|
-
}
|
|
1587
|
-
const bytes = fs.readFileSync(resolved.absolutePath);
|
|
1588
|
-
const dimensions = readImageDimensions(bytes, mediaType);
|
|
1589
|
-
return {
|
|
1590
|
-
type: "view_file_bytes",
|
|
1591
|
-
filePath: resolved.virtualPath,
|
|
1592
|
-
mediaType,
|
|
1593
|
-
base64: bytes.toString("base64"),
|
|
1594
|
-
fileSizeBytes: bytes.length,
|
|
1595
|
-
width: dimensions.width,
|
|
1596
|
-
height: dimensions.height
|
|
1597
|
-
};
|
|
1680
|
+
return readWorkerImageFile(workspace.branchPath, input.message.filePath);
|
|
1598
1681
|
}
|
|
1599
1682
|
function stagedBlobSizeBytes(context) {
|
|
1600
1683
|
const names = runInternalGit(context, ["diff", "--cached", "--name-only", "-z", "--", ".", ":(exclude).git"]).split("\0").filter(Boolean);
|
|
@@ -2786,11 +2869,17 @@ if (isCliEntrypoint()) {
|
|
|
2786
2869
|
export {
|
|
2787
2870
|
ensureVisibleGitCheckout,
|
|
2788
2871
|
findRepositorySyncBlockers,
|
|
2872
|
+
findWorkerFiles,
|
|
2873
|
+
grepWorkerFiles,
|
|
2789
2874
|
isArtifactEnvPath,
|
|
2875
|
+
listWorkerDirectory,
|
|
2790
2876
|
prepareArtifactEnvForShell,
|
|
2791
2877
|
preparePlanEnvForShell,
|
|
2878
|
+
readWorkerImageFile,
|
|
2879
|
+
readWorkerTextFile,
|
|
2792
2880
|
resolveHostShell,
|
|
2793
2881
|
resolveProjectFilePath,
|
|
2882
|
+
resolveWorkerFilePath,
|
|
2794
2883
|
syncManifestProjectsFromInternal,
|
|
2795
2884
|
syncProjectPlans,
|
|
2796
2885
|
syncSessionArtifacts
|
package/dist/mjs/package.json
CHANGED
package/dist/types/main.d.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
type WorkerReadFileResult = {
|
|
3
|
+
type: "read";
|
|
4
|
+
kind: "text";
|
|
5
|
+
file: string;
|
|
6
|
+
gitBlobHash?: string;
|
|
7
|
+
content: string;
|
|
8
|
+
totalLines: number;
|
|
9
|
+
startLine: number;
|
|
10
|
+
endLine: number;
|
|
11
|
+
truncated: boolean;
|
|
12
|
+
continuation?: string;
|
|
13
|
+
};
|
|
2
14
|
type WorkerProjectManifestEntry = {
|
|
3
15
|
projectId: string;
|
|
4
16
|
repoSlug: string;
|
|
@@ -28,6 +40,34 @@ type WorkerRepositorySyncResult = {
|
|
|
28
40
|
id: string;
|
|
29
41
|
}>;
|
|
30
42
|
};
|
|
43
|
+
type WorkerGrepResult = {
|
|
44
|
+
type: "grep";
|
|
45
|
+
content: string;
|
|
46
|
+
matchCount: number;
|
|
47
|
+
truncated: boolean;
|
|
48
|
+
};
|
|
49
|
+
type WorkerFindResult = {
|
|
50
|
+
type: "find";
|
|
51
|
+
content: string;
|
|
52
|
+
count: number;
|
|
53
|
+
truncated: boolean;
|
|
54
|
+
};
|
|
55
|
+
type WorkerLsResult = {
|
|
56
|
+
type: "ls";
|
|
57
|
+
path: string;
|
|
58
|
+
content: string;
|
|
59
|
+
count: number;
|
|
60
|
+
truncated: boolean;
|
|
61
|
+
};
|
|
62
|
+
type WorkerViewFileBytesResult = {
|
|
63
|
+
type: "view_file_bytes";
|
|
64
|
+
filePath: string;
|
|
65
|
+
mediaType: "image/png" | "image/jpeg";
|
|
66
|
+
base64: string;
|
|
67
|
+
fileSizeBytes: number;
|
|
68
|
+
width: number;
|
|
69
|
+
height: number;
|
|
70
|
+
};
|
|
31
71
|
export declare function isArtifactEnvPath(filePath: string): boolean;
|
|
32
72
|
export declare function syncSessionArtifacts(input: {
|
|
33
73
|
baseUrl: string;
|
|
@@ -64,11 +104,21 @@ type GitAuth = {
|
|
|
64
104
|
extraHeaderUrl: string;
|
|
65
105
|
header: string;
|
|
66
106
|
};
|
|
67
|
-
|
|
107
|
+
type ResolvedWorkerFilePath = {
|
|
68
108
|
absolutePath: string;
|
|
69
|
-
|
|
109
|
+
displayPath: string;
|
|
70
110
|
repoRelativePath: string;
|
|
111
|
+
scope: "project";
|
|
112
|
+
} | {
|
|
113
|
+
absolutePath: string;
|
|
114
|
+
displayPath: string;
|
|
115
|
+
repoRelativePath: null;
|
|
116
|
+
scope: "host";
|
|
71
117
|
};
|
|
118
|
+
export declare function resolveWorkerFilePath(branchPath: string, inputPath: string): ResolvedWorkerFilePath;
|
|
119
|
+
export declare function resolveProjectFilePath(branchPath: string, inputPath: string): Extract<ResolvedWorkerFilePath, {
|
|
120
|
+
scope: "project";
|
|
121
|
+
}>;
|
|
72
122
|
export declare function ensureVisibleGitCheckout(input: {
|
|
73
123
|
projectRoot: string;
|
|
74
124
|
branchName: string;
|
|
@@ -99,6 +149,22 @@ export declare function syncManifestProjectsFromInternal(input: {
|
|
|
99
149
|
manifests: WorkerProjectManifestEntry[];
|
|
100
150
|
projectIds?: string[];
|
|
101
151
|
}): Promise<WorkerRepositorySyncResult>;
|
|
152
|
+
export declare function readWorkerTextFile(branchPath: string, filePath: string, offset?: number, limit?: number): WorkerReadFileResult;
|
|
153
|
+
export declare function grepWorkerFiles(branchPath: string, input: {
|
|
154
|
+
pattern: string;
|
|
155
|
+
path?: string;
|
|
156
|
+
glob?: string;
|
|
157
|
+
caseSensitive?: boolean;
|
|
158
|
+
limit?: number;
|
|
159
|
+
}): WorkerGrepResult;
|
|
160
|
+
export declare function findWorkerFiles(branchPath: string, input: {
|
|
161
|
+
pattern?: string;
|
|
162
|
+
path?: string;
|
|
163
|
+
entryType?: string;
|
|
164
|
+
limit?: number;
|
|
165
|
+
}): WorkerFindResult;
|
|
166
|
+
export declare function listWorkerDirectory(branchPath: string, inputPath?: string, inputLimit?: number): WorkerLsResult;
|
|
167
|
+
export declare function readWorkerImageFile(branchPath: string, filePath: string): WorkerViewFileBytesResult;
|
|
102
168
|
export declare function resolveHostShell(command?: string, platform?: NodeJS.Platform): {
|
|
103
169
|
file: string;
|
|
104
170
|
args: string[];
|