@ricsam/r5d-worker 0.0.29 → 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 +252 -123
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/process-tree.cjs +117 -0
- package/dist/mjs/main.mjs +246 -123
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/process-tree.mjs +93 -0
- package/dist/types/main.d.ts +68 -2
- package/dist/types/process-tree.d.ts +16 -0
- 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
|
|
@@ -49,6 +55,7 @@ var import_node_child_process = require("node:child_process");
|
|
|
49
55
|
var import_cli_update = require("./cli-update.cjs");
|
|
50
56
|
var import_git_identity = require("./git-identity.cjs");
|
|
51
57
|
var import_heartbeat = require("./heartbeat.cjs");
|
|
58
|
+
var import_process_tree = require("./process-tree.cjs");
|
|
52
59
|
var import_supervisor = require("./supervisor.cjs");
|
|
53
60
|
const import_meta = {};
|
|
54
61
|
const DEFAULT_BASE_URL = "https://r5d.dev";
|
|
@@ -705,36 +712,69 @@ function isInsideBranchPath(branchPath, filePath) {
|
|
|
705
712
|
const relative = import_node_path.default.relative(import_node_path.default.resolve(branchPath), import_node_path.default.resolve(filePath));
|
|
706
713
|
return relative === "" || relative !== ".." && !relative.startsWith(`..${import_node_path.default.sep}`) && !import_node_path.default.isAbsolute(relative);
|
|
707
714
|
}
|
|
708
|
-
function
|
|
709
|
-
|
|
710
|
-
|
|
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}`);
|
|
711
725
|
}
|
|
712
726
|
}
|
|
713
|
-
function
|
|
714
|
-
if (typeof
|
|
715
|
-
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)}`);
|
|
716
730
|
}
|
|
717
|
-
const
|
|
718
|
-
if (
|
|
719
|
-
const
|
|
720
|
-
if (
|
|
721
|
-
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
|
+
};
|
|
722
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
|
+
};
|
|
723
751
|
}
|
|
724
|
-
|
|
725
|
-
|
|
752
|
+
const normalizedInputPath = inputPath.replace(/\\/g, "/");
|
|
753
|
+
if (normalizedInputPath.split("/").includes("..")) {
|
|
754
|
+
throw new Error(`Invalid project file path: ${inputPath}`);
|
|
726
755
|
}
|
|
727
|
-
const normalized = import_node_path.default.posix.normalize(
|
|
728
|
-
|
|
729
|
-
|
|
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}`);
|
|
730
762
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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
|
+
);
|
|
734
776
|
}
|
|
735
|
-
|
|
736
|
-
assertInsideBranch(branchPath, absolutePath);
|
|
737
|
-
return { absolutePath, virtualPath: repoRelativePath ? `/${repoRelativePath}` : "/", repoRelativePath };
|
|
777
|
+
return resolved;
|
|
738
778
|
}
|
|
739
779
|
function resolveRemoteUrl(baseUrl, remoteUrl) {
|
|
740
780
|
if (/^https?:\/\//i.test(remoteUrl)) {
|
|
@@ -1348,6 +1388,21 @@ async function withFileMutationQueue(key, operation) {
|
|
|
1348
1388
|
function mutationQueueKey(projectId, branchName, filePath) {
|
|
1349
1389
|
return `${projectId}:${branchName}:${import_node_path.default.posix.normalize(filePath)}`;
|
|
1350
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
|
+
}
|
|
1351
1406
|
async function executeReadFileOperation(input) {
|
|
1352
1407
|
const artifact = await resolveArtifactEnvFilePath({
|
|
1353
1408
|
filePath: input.message.filePath,
|
|
@@ -1362,55 +1417,43 @@ async function executeReadFileOperation(input) {
|
|
|
1362
1417
|
if (!import_node_fs.default.existsSync(artifact.absolutePath) || !import_node_fs.default.statSync(artifact.absolutePath).isFile()) {
|
|
1363
1418
|
throw new Error(`File not found: ${artifact.virtualPath}`);
|
|
1364
1419
|
}
|
|
1365
|
-
const
|
|
1366
|
-
const
|
|
1420
|
+
const buffer = import_node_fs.default.readFileSync(artifact.absolutePath);
|
|
1421
|
+
const formatted = formatLineNumberedContent(buffer.toString("utf8"), input.message.offset, input.message.limit);
|
|
1367
1422
|
return {
|
|
1368
1423
|
type: "read",
|
|
1369
1424
|
kind: "text",
|
|
1370
1425
|
file: artifact.virtualPath,
|
|
1371
|
-
gitBlobHash: getGitBlobHashForContent(
|
|
1372
|
-
...
|
|
1426
|
+
gitBlobHash: getGitBlobHashForContent(buffer),
|
|
1427
|
+
...formatted
|
|
1373
1428
|
};
|
|
1374
1429
|
}
|
|
1375
1430
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1376
|
-
|
|
1377
|
-
if (!import_node_fs.default.existsSync(resolved.absolutePath) || !import_node_fs.default.statSync(resolved.absolutePath).isFile()) {
|
|
1378
|
-
throw new Error(`File not found: ${resolved.virtualPath}`);
|
|
1379
|
-
}
|
|
1380
|
-
const buffer = import_node_fs.default.readFileSync(resolved.absolutePath);
|
|
1381
|
-
const formatted = formatLineNumberedContent(buffer.toString("utf8"), input.message.offset, input.message.limit);
|
|
1382
|
-
return {
|
|
1383
|
-
type: "read",
|
|
1384
|
-
kind: "text",
|
|
1385
|
-
file: resolved.virtualPath,
|
|
1386
|
-
gitBlobHash: getGitBlobHashForContent(buffer),
|
|
1387
|
-
...formatted
|
|
1388
|
-
};
|
|
1431
|
+
return readWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.offset, input.message.limit);
|
|
1389
1432
|
}
|
|
1390
1433
|
async function executeWriteFileOperation(input) {
|
|
1391
1434
|
rejectArtifactWritePath(input.message.filePath);
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
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 () => {
|
|
1395
1438
|
import_node_fs.default.mkdirSync(import_node_path.default.dirname(resolved.absolutePath), { recursive: true });
|
|
1396
1439
|
import_node_fs.default.writeFileSync(resolved.absolutePath, input.message.content, "utf8");
|
|
1397
1440
|
return {
|
|
1398
1441
|
type: "write",
|
|
1399
|
-
file: resolved.
|
|
1442
|
+
file: resolved.displayPath,
|
|
1400
1443
|
gitBlobHash: getGitBlobHashForContent(input.message.content)
|
|
1401
1444
|
};
|
|
1402
1445
|
});
|
|
1403
1446
|
}
|
|
1404
1447
|
async function executeEditFileOperation(input) {
|
|
1405
1448
|
rejectArtifactWritePath(input.message.filePath);
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
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 () => {
|
|
1409
1452
|
if (!Array.isArray(input.message.edits) || input.message.edits.length === 0) {
|
|
1410
1453
|
throw new Error("edit requires at least one replacement");
|
|
1411
1454
|
}
|
|
1412
1455
|
if (!import_node_fs.default.existsSync(resolved.absolutePath) || !import_node_fs.default.statSync(resolved.absolutePath).isFile()) {
|
|
1413
|
-
throw new Error(`File not found: ${resolved.
|
|
1456
|
+
throw new Error(`File not found: ${resolved.displayPath}`);
|
|
1414
1457
|
}
|
|
1415
1458
|
const originalContent = import_node_fs.default.readFileSync(resolved.absolutePath, "utf8");
|
|
1416
1459
|
const ranges = input.message.edits.map((edit, index) => {
|
|
@@ -1445,7 +1488,7 @@ async function executeEditFileOperation(input) {
|
|
|
1445
1488
|
import_node_fs.default.writeFileSync(resolved.absolutePath, nextContent, "utf8");
|
|
1446
1489
|
return {
|
|
1447
1490
|
type: "edit",
|
|
1448
|
-
file: resolved.
|
|
1491
|
+
file: resolved.displayPath,
|
|
1449
1492
|
edits: input.message.edits.length
|
|
1450
1493
|
};
|
|
1451
1494
|
});
|
|
@@ -1465,47 +1508,70 @@ function wildcardToRegex(pattern) {
|
|
|
1465
1508
|
function normalizeFindPattern(pattern) {
|
|
1466
1509
|
return /[*?]/.test(pattern) ? pattern : `*${pattern}*`;
|
|
1467
1510
|
}
|
|
1468
|
-
function
|
|
1469
|
-
const relative = import_node_path.default.relative(branchPath, absolutePath).split(import_node_path.default.sep).join(import_node_path.default.posix.sep);
|
|
1470
|
-
return relative ? `/${relative}` : "/";
|
|
1471
|
-
}
|
|
1472
|
-
function walkProjectEntries(branchPath, startPath) {
|
|
1511
|
+
function walkWorkerEntries(branchPath, start) {
|
|
1473
1512
|
const entries = [];
|
|
1474
|
-
const
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
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 });
|
|
1478
1526
|
if (!stat.isDirectory()) return;
|
|
1479
|
-
|
|
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
|
+
}
|
|
1480
1543
|
for (const child of children) {
|
|
1481
1544
|
if (IGNORED_ENTRY_NAMES.has(child.name)) continue;
|
|
1482
|
-
visit(import_node_path.default.join(absolutePath, child.name));
|
|
1545
|
+
visit(import_node_path.default.join(absolutePath, child.name), false);
|
|
1483
1546
|
}
|
|
1484
1547
|
};
|
|
1485
|
-
visit(
|
|
1548
|
+
visit(start.absolutePath, true);
|
|
1486
1549
|
return entries;
|
|
1487
1550
|
}
|
|
1488
1551
|
function isProbablyText(bytes) {
|
|
1489
1552
|
return !bytes.subarray(0, Math.min(bytes.length, 4096)).includes(0);
|
|
1490
1553
|
}
|
|
1491
|
-
|
|
1492
|
-
const
|
|
1493
|
-
const start = resolveProjectFilePath(workspace.branchPath, input.message.path ?? "/");
|
|
1554
|
+
function grepWorkerFiles(branchPath, input) {
|
|
1555
|
+
const start = resolveWorkerFilePath(branchPath, input.path ?? ".");
|
|
1494
1556
|
if (!import_node_fs.default.existsSync(start.absolutePath)) {
|
|
1495
|
-
throw new Error(`Path not found: ${start.
|
|
1557
|
+
throw new Error(`Path not found: ${start.displayPath}`);
|
|
1496
1558
|
}
|
|
1497
|
-
const limit = normalizeToolLimit(input.
|
|
1498
|
-
const flags = input.
|
|
1499
|
-
const regex = new RegExp(input.
|
|
1500
|
-
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;
|
|
1501
1563
|
const lines = [];
|
|
1502
1564
|
let matchCount = 0;
|
|
1503
1565
|
let truncated = false;
|
|
1504
|
-
for (const entry of
|
|
1566
|
+
for (const entry of walkWorkerEntries(branchPath, start)) {
|
|
1505
1567
|
if (!entry.stat.isFile()) continue;
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
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
|
+
}
|
|
1509
1575
|
if (!isProbablyText(bytes)) continue;
|
|
1510
1576
|
const fileLines = bytes.toString("utf8").split(/\r?\n/);
|
|
1511
1577
|
for (let index = 0; index < fileLines.length; index += 1) {
|
|
@@ -1516,7 +1582,7 @@ async function executeGrepOperation(input) {
|
|
|
1516
1582
|
matchCount += 1;
|
|
1517
1583
|
if (lines.length < limit) {
|
|
1518
1584
|
const column = Math.max(1, (match.index ?? 0) + 1);
|
|
1519
|
-
lines.push(`${entry.
|
|
1585
|
+
lines.push(`${entry.displayPath}:${index + 1}:${column}: ${line.slice(0, MAX_LINE_LENGTH)}`);
|
|
1520
1586
|
} else {
|
|
1521
1587
|
truncated = true;
|
|
1522
1588
|
}
|
|
@@ -1531,26 +1597,35 @@ async function executeGrepOperation(input) {
|
|
|
1531
1597
|
truncated
|
|
1532
1598
|
};
|
|
1533
1599
|
}
|
|
1534
|
-
async function
|
|
1600
|
+
async function executeGrepOperation(input) {
|
|
1535
1601
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1536
|
-
|
|
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 ?? ".");
|
|
1537
1612
|
if (!import_node_fs.default.existsSync(start.absolutePath) || !import_node_fs.default.statSync(start.absolutePath).isDirectory()) {
|
|
1538
|
-
throw new Error(`Directory not found: ${start.
|
|
1613
|
+
throw new Error(`Directory not found: ${start.displayPath}`);
|
|
1539
1614
|
}
|
|
1540
|
-
const pattern = normalizeFindPattern(input.
|
|
1615
|
+
const pattern = normalizeFindPattern(input.pattern ?? "*");
|
|
1541
1616
|
const matcher = wildcardToRegex(pattern);
|
|
1542
|
-
const entryType = input.
|
|
1543
|
-
const limit = normalizeToolLimit(input.
|
|
1617
|
+
const entryType = input.entryType ?? "file";
|
|
1618
|
+
const limit = normalizeToolLimit(input.limit, 200, 1e3);
|
|
1544
1619
|
const matches = [];
|
|
1545
1620
|
let count = 0;
|
|
1546
1621
|
let truncated = false;
|
|
1547
|
-
for (const entry of
|
|
1548
|
-
if (entry.
|
|
1622
|
+
for (const entry of walkWorkerEntries(branchPath, start)) {
|
|
1623
|
+
if (entry.absolutePath === start.absolutePath) continue;
|
|
1549
1624
|
const isDirectory = entry.stat.isDirectory();
|
|
1550
1625
|
if (entryType === "file" && !entry.stat.isFile()) continue;
|
|
1551
1626
|
if (entryType === "directory" && !isDirectory) continue;
|
|
1552
|
-
const label = isDirectory ? `${entry.
|
|
1553
|
-
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;
|
|
1554
1629
|
count += 1;
|
|
1555
1630
|
if (matches.length < limit) {
|
|
1556
1631
|
matches.push(label);
|
|
@@ -1566,25 +1641,59 @@ async function executeFindOperation(input) {
|
|
|
1566
1641
|
truncated
|
|
1567
1642
|
};
|
|
1568
1643
|
}
|
|
1569
|
-
async function
|
|
1644
|
+
async function executeFindOperation(input) {
|
|
1570
1645
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1571
|
-
|
|
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);
|
|
1572
1655
|
if (!import_node_fs.default.existsSync(resolved.absolutePath) || !import_node_fs.default.statSync(resolved.absolutePath).isDirectory()) {
|
|
1573
|
-
throw new Error(`Directory not found: ${resolved.
|
|
1656
|
+
throw new Error(`Directory not found: ${resolved.displayPath}`);
|
|
1574
1657
|
}
|
|
1575
|
-
const limit = normalizeToolLimit(
|
|
1658
|
+
const limit = normalizeToolLimit(inputLimit, 200, 1e3);
|
|
1576
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));
|
|
1577
1660
|
const displayed = entries.slice(0, limit).map((entry) => `${entry.name}${entry.isDirectory() ? "/" : ""}`);
|
|
1578
1661
|
const truncated = entries.length > displayed.length;
|
|
1579
1662
|
const hint = truncated ? "\n... truncated. Increase limit to continue." : "";
|
|
1580
1663
|
return {
|
|
1581
1664
|
type: "ls",
|
|
1582
|
-
path: resolved.
|
|
1665
|
+
path: resolved.displayPath,
|
|
1583
1666
|
content: displayed.length > 0 ? `${displayed.join("\n")}${hint}` : "(empty directory)",
|
|
1584
1667
|
count: entries.length,
|
|
1585
1668
|
truncated
|
|
1586
1669
|
};
|
|
1587
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
|
+
}
|
|
1588
1697
|
async function executeViewFileBytesOperation(input) {
|
|
1589
1698
|
const artifact = await resolveArtifactEnvFilePath({
|
|
1590
1699
|
filePath: input.message.filePath,
|
|
@@ -1599,44 +1708,25 @@ async function executeViewFileBytesOperation(input) {
|
|
|
1599
1708
|
if (!import_node_fs.default.existsSync(artifact.absolutePath) || !import_node_fs.default.statSync(artifact.absolutePath).isFile()) {
|
|
1600
1709
|
throw new Error(`File not found: ${artifact.virtualPath}`);
|
|
1601
1710
|
}
|
|
1602
|
-
const
|
|
1603
|
-
const
|
|
1604
|
-
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) {
|
|
1605
1714
|
throw new Error("read supports PNG and JPEG image inputs only");
|
|
1606
1715
|
}
|
|
1607
|
-
const
|
|
1608
|
-
const
|
|
1716
|
+
const bytes = import_node_fs.default.readFileSync(artifact.absolutePath);
|
|
1717
|
+
const dimensions = readImageDimensions(bytes, mediaType);
|
|
1609
1718
|
return {
|
|
1610
1719
|
type: "view_file_bytes",
|
|
1611
1720
|
filePath: artifact.virtualPath,
|
|
1612
|
-
mediaType
|
|
1613
|
-
base64:
|
|
1614
|
-
fileSizeBytes:
|
|
1615
|
-
width:
|
|
1616
|
-
height:
|
|
1721
|
+
mediaType,
|
|
1722
|
+
base64: bytes.toString("base64"),
|
|
1723
|
+
fileSizeBytes: bytes.length,
|
|
1724
|
+
width: dimensions.width,
|
|
1725
|
+
height: dimensions.height
|
|
1617
1726
|
};
|
|
1618
1727
|
}
|
|
1619
1728
|
const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
|
|
1620
|
-
|
|
1621
|
-
if (!import_node_fs.default.existsSync(resolved.absolutePath) || !import_node_fs.default.statSync(resolved.absolutePath).isFile()) {
|
|
1622
|
-
throw new Error(`File not found: ${resolved.virtualPath}`);
|
|
1623
|
-
}
|
|
1624
|
-
const extension = import_node_path.default.extname(resolved.absolutePath).toLowerCase();
|
|
1625
|
-
const mediaType = extension === ".jpg" || extension === ".jpeg" ? "image/jpeg" : extension === ".png" ? "image/png" : null;
|
|
1626
|
-
if (!mediaType) {
|
|
1627
|
-
throw new Error("read supports PNG and JPEG image inputs only");
|
|
1628
|
-
}
|
|
1629
|
-
const bytes = import_node_fs.default.readFileSync(resolved.absolutePath);
|
|
1630
|
-
const dimensions = readImageDimensions(bytes, mediaType);
|
|
1631
|
-
return {
|
|
1632
|
-
type: "view_file_bytes",
|
|
1633
|
-
filePath: resolved.virtualPath,
|
|
1634
|
-
mediaType,
|
|
1635
|
-
base64: bytes.toString("base64"),
|
|
1636
|
-
fileSizeBytes: bytes.length,
|
|
1637
|
-
width: dimensions.width,
|
|
1638
|
-
height: dimensions.height
|
|
1639
|
-
};
|
|
1729
|
+
return readWorkerImageFile(workspace.branchPath, input.message.filePath);
|
|
1640
1730
|
}
|
|
1641
1731
|
function stagedBlobSizeBytes(context) {
|
|
1642
1732
|
const names = runInternalGit(context, ["diff", "--cached", "--name-only", "-z", "--", ".", ":(exclude).git"]).split("\0").filter(Boolean);
|
|
@@ -1817,6 +1907,7 @@ async function executeCommand(input) {
|
|
|
1817
1907
|
cwd,
|
|
1818
1908
|
stdout: "pipe",
|
|
1819
1909
|
stderr: "pipe",
|
|
1910
|
+
detached: true,
|
|
1820
1911
|
env: {
|
|
1821
1912
|
...process.env,
|
|
1822
1913
|
...containerRegistryEnv(),
|
|
@@ -1838,7 +1929,12 @@ async function executeCommand(input) {
|
|
|
1838
1929
|
});
|
|
1839
1930
|
if (input.message.timeoutMs) {
|
|
1840
1931
|
timeout = setTimeout(() => {
|
|
1841
|
-
subprocess.
|
|
1932
|
+
void (0, import_process_tree.terminateProcessTree)(subprocess).catch((error) => {
|
|
1933
|
+
process.stderr.write(
|
|
1934
|
+
`[r5d-worker] failed to terminate timed-out command ${input.message.runId}: ${error instanceof Error ? error.message : String(error)}
|
|
1935
|
+
`
|
|
1936
|
+
);
|
|
1937
|
+
});
|
|
1842
1938
|
}, input.message.timeoutMs);
|
|
1843
1939
|
}
|
|
1844
1940
|
const [stdout, stderr, exitCode] = await Promise.all([
|
|
@@ -1874,6 +1970,8 @@ async function executeCommand(input) {
|
|
|
1874
1970
|
async function executeStreamingCommand(input) {
|
|
1875
1971
|
const startedAt = Date.now();
|
|
1876
1972
|
let started = false;
|
|
1973
|
+
let timedOut = false;
|
|
1974
|
+
let timeout;
|
|
1877
1975
|
try {
|
|
1878
1976
|
if (cancelledProcessRuns.delete(input.message.runId)) {
|
|
1879
1977
|
sendWorkerMessage(input.ws, {
|
|
@@ -1914,6 +2012,7 @@ async function executeStreamingCommand(input) {
|
|
|
1914
2012
|
cwd,
|
|
1915
2013
|
stdout: "pipe",
|
|
1916
2014
|
stderr: "pipe",
|
|
2015
|
+
detached: true,
|
|
1917
2016
|
env: {
|
|
1918
2017
|
...process.env,
|
|
1919
2018
|
...containerRegistryEnv(),
|
|
@@ -1940,6 +2039,17 @@ async function executeStreamingCommand(input) {
|
|
|
1940
2039
|
requestId: input.message.requestId,
|
|
1941
2040
|
runId: input.message.runId
|
|
1942
2041
|
});
|
|
2042
|
+
if (input.message.timeoutMs) {
|
|
2043
|
+
timeout = setTimeout(() => {
|
|
2044
|
+
timedOut = true;
|
|
2045
|
+
void (0, import_process_tree.terminateProcessTree)(subprocess).catch((error) => {
|
|
2046
|
+
process.stderr.write(
|
|
2047
|
+
`[r5d-worker] failed to terminate timed-out process ${input.message.runId}: ${error instanceof Error ? error.message : String(error)}
|
|
2048
|
+
`
|
|
2049
|
+
);
|
|
2050
|
+
});
|
|
2051
|
+
}, input.message.timeoutMs);
|
|
2052
|
+
}
|
|
1943
2053
|
const [exitCode] = await Promise.all([
|
|
1944
2054
|
subprocess.exited,
|
|
1945
2055
|
streamCommandOutput(subprocess.stdout, (data) => {
|
|
@@ -1963,7 +2073,8 @@ async function executeStreamingCommand(input) {
|
|
|
1963
2073
|
type: "exec_exit",
|
|
1964
2074
|
runId: input.message.runId,
|
|
1965
2075
|
exitCode,
|
|
1966
|
-
durationMs: Date.now() - startedAt
|
|
2076
|
+
durationMs: Date.now() - startedAt,
|
|
2077
|
+
...timedOut ? { timedOut: true } : {}
|
|
1967
2078
|
});
|
|
1968
2079
|
} catch (error) {
|
|
1969
2080
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1983,6 +2094,9 @@ async function executeStreamingCommand(input) {
|
|
|
1983
2094
|
});
|
|
1984
2095
|
}
|
|
1985
2096
|
} finally {
|
|
2097
|
+
if (timeout) {
|
|
2098
|
+
clearTimeout(timeout);
|
|
2099
|
+
}
|
|
1986
2100
|
activeProcesses.delete(input.message.runId);
|
|
1987
2101
|
cancelledProcessRuns.delete(input.message.runId);
|
|
1988
2102
|
}
|
|
@@ -2545,18 +2659,27 @@ async function startWorker(options) {
|
|
|
2545
2659
|
}
|
|
2546
2660
|
if (message.type === "cancel") {
|
|
2547
2661
|
const active = activeProcesses.get(message.runId);
|
|
2662
|
+
let cancelled = true;
|
|
2663
|
+
let cancelMessage;
|
|
2548
2664
|
if (active) {
|
|
2549
|
-
|
|
2665
|
+
try {
|
|
2666
|
+
await (0, import_process_tree.terminateProcessTree)(active.process);
|
|
2667
|
+
cancelMessage = `Stopped ${active.command}`;
|
|
2668
|
+
} catch (error) {
|
|
2669
|
+
cancelled = false;
|
|
2670
|
+
cancelMessage = `Failed to stop ${active.command}: ${error instanceof Error ? error.message : String(error)}`;
|
|
2671
|
+
}
|
|
2550
2672
|
} else {
|
|
2551
2673
|
cancelledProcessRuns.add(message.runId);
|
|
2674
|
+
cancelMessage = "Cancellation queued before command start";
|
|
2552
2675
|
}
|
|
2553
2676
|
ws.send(
|
|
2554
2677
|
JSON.stringify({
|
|
2555
2678
|
type: "cancel_result",
|
|
2556
2679
|
requestId: message.requestId,
|
|
2557
2680
|
runId: message.runId,
|
|
2558
|
-
cancelled
|
|
2559
|
-
message:
|
|
2681
|
+
cancelled,
|
|
2682
|
+
message: cancelMessage
|
|
2560
2683
|
})
|
|
2561
2684
|
);
|
|
2562
2685
|
return;
|
|
@@ -2796,11 +2919,17 @@ if (isCliEntrypoint()) {
|
|
|
2796
2919
|
0 && (module.exports = {
|
|
2797
2920
|
ensureVisibleGitCheckout,
|
|
2798
2921
|
findRepositorySyncBlockers,
|
|
2922
|
+
findWorkerFiles,
|
|
2923
|
+
grepWorkerFiles,
|
|
2799
2924
|
isArtifactEnvPath,
|
|
2925
|
+
listWorkerDirectory,
|
|
2800
2926
|
prepareArtifactEnvForShell,
|
|
2801
2927
|
preparePlanEnvForShell,
|
|
2928
|
+
readWorkerImageFile,
|
|
2929
|
+
readWorkerTextFile,
|
|
2802
2930
|
resolveHostShell,
|
|
2803
2931
|
resolveProjectFilePath,
|
|
2932
|
+
resolveWorkerFilePath,
|
|
2804
2933
|
syncManifestProjectsFromInternal,
|
|
2805
2934
|
syncProjectPlans,
|
|
2806
2935
|
syncSessionArtifacts
|
package/dist/cjs/package.json
CHANGED