@ricsam/r5d-worker 0.0.47 → 0.0.49

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 CHANGED
@@ -29,6 +29,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
30
  var main_exports = {};
31
31
  __export(main_exports, {
32
+ describeWorkerSessionTarget: () => describeWorkerSessionTarget,
32
33
  editWorkerTextFile: () => editWorkerTextFile,
33
34
  ensureVisibleGitCheckout: () => ensureVisibleGitCheckout,
34
35
  findWorkerFiles: () => findWorkerFiles,
@@ -37,11 +38,14 @@ __export(main_exports, {
37
38
  isArtifactEnvPath: () => isArtifactEnvPath,
38
39
  listWorkerDirectory: () => listWorkerDirectory,
39
40
  prepareArtifactEnvForShell: () => prepareArtifactEnvForShell,
41
+ prepareBuiltInToolPathsForTarget: () => prepareBuiltInToolPathsForTarget,
40
42
  preparePlanEnvForShell: () => preparePlanEnvForShell,
43
+ prepareShellEnvForTarget: () => prepareShellEnvForTarget,
41
44
  readWorkerImageFile: () => readWorkerImageFile,
42
45
  readWorkerTextFile: () => readWorkerTextFile,
43
46
  resolveHostShell: () => resolveHostShell,
44
47
  resolveWorkerFilePath: () => resolveWorkerFilePath,
48
+ resolveWorkerSessionTarget: () => resolveWorkerSessionTarget,
45
49
  syncSessionArtifacts: () => syncSessionArtifacts,
46
50
  visibleCheckoutGitTestHarness: () => visibleCheckoutGitTestHarness,
47
51
  visibleCheckoutRemoteFor: () => visibleCheckoutRemoteFor,
@@ -58,6 +62,7 @@ var import_cli_update = require("./cli-update.cjs");
58
62
  var import_git_identity = require("./git-identity.cjs");
59
63
  var import_heartbeat = require("./heartbeat.cjs");
60
64
  var import_process_tree = require("./process-tree.cjs");
65
+ var import_port_forward_client = require("./port-forward-client.cjs");
61
66
  var import_supervisor = require("./supervisor.cjs");
62
67
  var import_managed_paths = require("./managed-paths.cjs");
63
68
  var import_workspace_sync = require("./workspace-sync.cjs");
@@ -332,8 +337,15 @@ function projectPlanDir(planRoot, projectId, branchName) {
332
337
  validateBranchName(branchName);
333
338
  return import_node_path.default.join(planRoot, projectId, branchName);
334
339
  }
335
- function planEnv(planRoot, projectId, branchName, activePlanId) {
336
- const plansDir = projectPlanDir(planRoot, projectId, branchName);
340
+ function targetPlanDir(planRoot, target) {
341
+ if (target.type === "project") return projectPlanDir(planRoot, target.projectId, target.branchName);
342
+ if (target.rootProfile === "canonical_sync") {
343
+ throw new Error("Ordinary plans are unavailable in canonical synchronization sessions");
344
+ }
345
+ return import_node_path.default.join(planRoot, "workspace");
346
+ }
347
+ function planEnv(planRoot, target, activePlanId) {
348
+ const plansDir = targetPlanDir(planRoot, target);
337
349
  const env = {
338
350
  [R5D_PLANS_DIR_ENV]: plansDir
339
351
  };
@@ -351,14 +363,14 @@ function isArtifactEnvPath(filePath) {
351
363
  return false;
352
364
  }
353
365
  }
354
- function artifactApiBasePath(projectId, branchName, sessionId) {
355
- return `/preview-api/${encodeURIComponent(projectId)}/${encodeURIComponent(branchName)}/artifacts/${encodeURIComponent(sessionId)}`;
366
+ function artifactApiBasePath(sessionId) {
367
+ return `/preview-api/artifacts/${encodeURIComponent(sessionId)}`;
356
368
  }
357
- function artifactManifestUrl(baseUrl, projectId, branchName, sessionId) {
358
- return new URL(artifactApiBasePath(projectId, branchName, sessionId), baseUrl);
369
+ function artifactManifestUrl(baseUrl, sessionId) {
370
+ return new URL(artifactApiBasePath(sessionId), baseUrl);
359
371
  }
360
- function artifactDownloadUrl(baseUrl, projectId, branchName, sessionId, filename) {
361
- return new URL(`${artifactApiBasePath(projectId, branchName, sessionId)}/${encodeURIComponent(filename)}`, baseUrl);
372
+ function artifactDownloadUrl(baseUrl, sessionId, filename) {
373
+ return new URL(`${artifactApiBasePath(sessionId)}/${encodeURIComponent(filename)}`, baseUrl);
362
374
  }
363
375
  function sha256Path(filePath) {
364
376
  return (0, import_node_crypto.createHash)("sha256").update(import_node_fs.default.readFileSync(filePath)).digest("hex");
@@ -450,7 +462,7 @@ async function syncRemoteFileSet(input) {
450
462
  }
451
463
  async function fetchSessionArtifactManifest(input) {
452
464
  return fetchRemoteFileManifest({
453
- url: artifactManifestUrl(input.baseUrl, input.projectId, input.branchName, input.sessionId),
465
+ url: artifactManifestUrl(input.baseUrl, input.sessionId),
454
466
  token: input.token,
455
467
  collectionKey: "artifacts",
456
468
  label: "artifact"
@@ -468,7 +480,7 @@ async function syncSessionArtifacts(input) {
468
480
  files: artifacts,
469
481
  label: "Artifact path",
470
482
  download: (artifact) => downloadRemoteSyncedFile({
471
- url: artifactDownloadUrl(input.baseUrl, input.projectId, input.branchName, input.sessionId, artifact.filename),
483
+ url: artifactDownloadUrl(input.baseUrl, input.sessionId, artifact.filename),
472
484
  token: input.token,
473
485
  targetDir,
474
486
  file: artifact,
@@ -501,14 +513,12 @@ async function prepareBuiltInToolPaths(input) {
501
513
  artifactsDir: await syncSessionArtifacts({
502
514
  baseUrl: input.baseUrl,
503
515
  token: input.token,
504
- projectId: input.projectId,
505
- branchName: input.branchName,
506
516
  sessionId: input.sessionId,
507
517
  artifactRoot: input.artifactRoot
508
518
  })
509
519
  };
510
520
  }
511
- const plansDir = projectPlanDir(input.planRoot, input.projectId, input.branchName);
521
+ const plansDir = targetPlanDir(input.planRoot, input.target);
512
522
  import_node_fs.default.mkdirSync(plansDir, { recursive: true });
513
523
  if (parsed.ref === R5D_PLANS_DIR_REF) {
514
524
  return { plansDir };
@@ -530,8 +540,6 @@ async function prepareArtifactEnvForShell(input) {
530
540
  await syncSessionArtifacts({
531
541
  baseUrl: input.baseUrl,
532
542
  token: input.token,
533
- projectId: input.projectId,
534
- branchName: input.branchName,
535
543
  sessionId: input.sessionId,
536
544
  artifactRoot: input.artifactRoot
537
545
  });
@@ -548,9 +556,9 @@ async function prepareArtifactEnvForShell(input) {
548
556
  };
549
557
  }
550
558
  function preparePlanEnvForShell(input) {
551
- const plansDir = projectPlanDir(input.planRoot, input.projectId, input.branchName);
559
+ const plansDir = targetPlanDir(input.planRoot, input.target);
552
560
  import_node_fs.default.mkdirSync(plansDir, { recursive: true });
553
- return planEnv(input.planRoot, input.projectId, input.branchName, input.activePlanId);
561
+ return planEnv(input.planRoot, input.target, input.activePlanId);
554
562
  }
555
563
  async function verifyR5dctlAuth(baseUrl, token) {
556
564
  const statusUrl = new URL("/api/r5dctl/auth/status", baseUrl);
@@ -1165,6 +1173,32 @@ function ensureBranchWorkspace(input) {
1165
1173
  })
1166
1174
  };
1167
1175
  }
1176
+ function describeWorkerSessionTarget(target) {
1177
+ return target.type === "project" ? `${target.projectId}/${target.branchName}` : `${target.ownerUserId}/${target.rootProfile}`;
1178
+ }
1179
+ function resolveWorkerSessionTarget(input) {
1180
+ if (input.target.type === "workspace") {
1181
+ if (input.target.rootProfile === "visible_projects") {
1182
+ return { target: input.target, rootPath: input.projectsRoot };
1183
+ }
1184
+ if (!import_node_fs.default.existsSync(import_node_path.default.join(input.workspaceShadowRoot, ".git"))) {
1185
+ throw new Error("The canonical workspace checkout is not initialized on this worker");
1186
+ }
1187
+ return { target: input.target, rootPath: input.workspaceShadowRoot };
1188
+ }
1189
+ const manifest = input.manifestByProjectId.get(input.target.projectId);
1190
+ const projectRoot = projectRootFor(input.projectsRoot, input.target.projectId, input.manifestByProjectId);
1191
+ const workspace = ensureBranchWorkspace({
1192
+ projectId: input.target.projectId,
1193
+ baseUrl: input.baseUrl,
1194
+ token: input.token,
1195
+ projectRoot,
1196
+ syncRoot: input.syncRoot,
1197
+ branchName: input.target.branchName,
1198
+ manifest
1199
+ });
1200
+ return { target: input.target, rootPath: workspace.branchPath, manifest };
1201
+ }
1168
1202
  function workspaceSyncCheckoutTargets(input) {
1169
1203
  const selected = /* @__PURE__ */ new Map();
1170
1204
  const add = (target) => {
@@ -1193,7 +1227,7 @@ function resolveCommandCwd(branchPath, cwd) {
1193
1227
  const resolved = import_node_path.default.resolve(branchPath, cwd);
1194
1228
  const relative = import_node_path.default.relative(branchPath, resolved);
1195
1229
  if (relative === ".." || relative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(relative)) {
1196
- throw new Error(`Relative cwd escapes the project branch: ${cwd}`);
1230
+ throw new Error(`Relative cwd escapes the execution root: ${cwd}`);
1197
1231
  }
1198
1232
  return resolved;
1199
1233
  }
@@ -1228,8 +1262,8 @@ async function streamCommandOutput(stream, onData) {
1228
1262
  reader.releaseLock();
1229
1263
  }
1230
1264
  }
1231
- function ensureOperationBranch(input) {
1232
- return ensureBranchWorkspace(input);
1265
+ async function prepareBuiltInToolPathsForTarget(input) {
1266
+ return prepareBuiltInToolPaths(input);
1233
1267
  }
1234
1268
  function formatLineNumberedContent(content, offset, limit) {
1235
1269
  const allLines = content.split("\n");
@@ -1355,11 +1389,11 @@ async function withFileMutationQueue(key, operation) {
1355
1389
  }
1356
1390
  }
1357
1391
  }
1358
- function mutationQueueKey(projectId, branchName, resolved) {
1392
+ function mutationQueueKey(target, resolved) {
1359
1393
  if (resolved.scope !== "project") {
1360
1394
  return `${resolved.scope}:${import_node_path.default.normalize(resolved.absolutePath)}`;
1361
1395
  }
1362
- return `project:${projectId}:${branchName}:${import_node_path.default.posix.normalize(resolved.repoRelativePath)}`;
1396
+ return `${describeWorkerSessionTarget(target)}:${import_node_path.default.posix.normalize(resolved.repoRelativePath)}`;
1363
1397
  }
1364
1398
  function readWorkerTextFile(branchPath, filePath, offset, limit, builtInPaths) {
1365
1399
  const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
@@ -1434,57 +1468,51 @@ function editWorkerTextFile(branchPath, filePath, edits, builtInPaths) {
1434
1468
  };
1435
1469
  }
1436
1470
  async function executeReadFileOperation(input) {
1437
- const builtInPaths = await prepareBuiltInToolPaths({
1471
+ const builtInPaths = await prepareBuiltInToolPathsForTarget({
1472
+ target: input.resolvedTarget.target,
1438
1473
  filePath: input.message.filePath,
1439
1474
  baseUrl: input.baseUrl,
1440
1475
  token: input.token,
1441
- projectId: input.projectId,
1442
- branchName: input.message.branchName,
1443
1476
  sessionId: input.message.sessionId,
1444
1477
  activePlanId: input.message.activePlanId,
1445
1478
  artifactRoot: input.artifactRoot,
1446
1479
  planRoot: input.planRoot,
1447
1480
  access: "read"
1448
1481
  });
1449
- const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
1450
- return readWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.offset, input.message.limit, builtInPaths);
1482
+ return readWorkerTextFile(input.resolvedTarget.rootPath, input.message.filePath, input.message.offset, input.message.limit, builtInPaths);
1451
1483
  }
1452
1484
  async function executeWriteFileOperation(input) {
1453
- const builtInPaths = await prepareBuiltInToolPaths({
1485
+ const builtInPaths = await prepareBuiltInToolPathsForTarget({
1486
+ target: input.resolvedTarget.target,
1454
1487
  filePath: input.message.filePath,
1455
1488
  baseUrl: input.baseUrl,
1456
1489
  token: input.token,
1457
- projectId: input.projectId,
1458
- branchName: input.message.branchName,
1459
1490
  sessionId: input.message.sessionId,
1460
1491
  activePlanId: input.message.activePlanId,
1461
1492
  artifactRoot: input.artifactRoot,
1462
1493
  planRoot: input.planRoot,
1463
1494
  access: "write"
1464
1495
  });
1465
- const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
1466
- const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath, builtInPaths);
1467
- return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved), async () => {
1468
- return writeWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.content, builtInPaths);
1496
+ const resolved = resolveWorkerFilePath(input.resolvedTarget.rootPath, input.message.filePath, builtInPaths);
1497
+ return withFileMutationQueue(mutationQueueKey(input.resolvedTarget.target, resolved), async () => {
1498
+ return writeWorkerTextFile(input.resolvedTarget.rootPath, input.message.filePath, input.message.content, builtInPaths);
1469
1499
  });
1470
1500
  }
1471
1501
  async function executeEditFileOperation(input) {
1472
- const builtInPaths = await prepareBuiltInToolPaths({
1502
+ const builtInPaths = await prepareBuiltInToolPathsForTarget({
1503
+ target: input.resolvedTarget.target,
1473
1504
  filePath: input.message.filePath,
1474
1505
  baseUrl: input.baseUrl,
1475
1506
  token: input.token,
1476
- projectId: input.projectId,
1477
- branchName: input.message.branchName,
1478
1507
  sessionId: input.message.sessionId,
1479
1508
  activePlanId: input.message.activePlanId,
1480
1509
  artifactRoot: input.artifactRoot,
1481
1510
  planRoot: input.planRoot,
1482
1511
  access: "write"
1483
1512
  });
1484
- const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
1485
- const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath, builtInPaths);
1486
- return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved), async () => {
1487
- return editWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.edits, builtInPaths);
1513
+ const resolved = resolveWorkerFilePath(input.resolvedTarget.rootPath, input.message.filePath, builtInPaths);
1514
+ return withFileMutationQueue(mutationQueueKey(input.resolvedTarget.target, resolved), async () => {
1515
+ return editWorkerTextFile(input.resolvedTarget.rootPath, input.message.filePath, input.message.edits, builtInPaths);
1488
1516
  });
1489
1517
  }
1490
1518
  const IGNORED_ENTRY_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", ".cache", "coverage", "target"]);
@@ -1597,21 +1625,19 @@ function grepWorkerFiles(branchPath, input, builtInPaths) {
1597
1625
  }
1598
1626
  async function executeGrepOperation(input) {
1599
1627
  const toolPath = input.message.path ?? ".";
1600
- const builtInPaths = await prepareBuiltInToolPaths({
1628
+ const builtInPaths = await prepareBuiltInToolPathsForTarget({
1629
+ target: input.resolvedTarget.target,
1601
1630
  filePath: toolPath,
1602
1631
  baseUrl: input.baseUrl,
1603
1632
  token: input.token,
1604
- projectId: input.projectId,
1605
- branchName: input.message.branchName,
1606
1633
  sessionId: input.message.sessionId,
1607
1634
  activePlanId: input.message.activePlanId,
1608
1635
  artifactRoot: input.artifactRoot,
1609
1636
  planRoot: input.planRoot,
1610
1637
  access: "read"
1611
1638
  });
1612
- const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
1613
1639
  return grepWorkerFiles(
1614
- workspace.branchPath,
1640
+ input.resolvedTarget.rootPath,
1615
1641
  {
1616
1642
  pattern: input.message.pattern,
1617
1643
  path: input.message.path,
@@ -1658,21 +1684,19 @@ function findWorkerFiles(branchPath, input, builtInPaths) {
1658
1684
  }
1659
1685
  async function executeFindOperation(input) {
1660
1686
  const toolPath = input.message.path ?? ".";
1661
- const builtInPaths = await prepareBuiltInToolPaths({
1687
+ const builtInPaths = await prepareBuiltInToolPathsForTarget({
1688
+ target: input.resolvedTarget.target,
1662
1689
  filePath: toolPath,
1663
1690
  baseUrl: input.baseUrl,
1664
1691
  token: input.token,
1665
- projectId: input.projectId,
1666
- branchName: input.message.branchName,
1667
1692
  sessionId: input.message.sessionId,
1668
1693
  activePlanId: input.message.activePlanId,
1669
1694
  artifactRoot: input.artifactRoot,
1670
1695
  planRoot: input.planRoot,
1671
1696
  access: "read"
1672
1697
  });
1673
- const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
1674
1698
  return findWorkerFiles(
1675
- workspace.branchPath,
1699
+ input.resolvedTarget.rootPath,
1676
1700
  {
1677
1701
  pattern: input.message.pattern,
1678
1702
  path: input.message.path,
@@ -1702,20 +1726,18 @@ function listWorkerDirectory(branchPath, inputPath = ".", inputLimit, builtInPat
1702
1726
  }
1703
1727
  async function executeLsOperation(input) {
1704
1728
  const toolPath = input.message.path ?? ".";
1705
- const builtInPaths = await prepareBuiltInToolPaths({
1729
+ const builtInPaths = await prepareBuiltInToolPathsForTarget({
1730
+ target: input.resolvedTarget.target,
1706
1731
  filePath: toolPath,
1707
1732
  baseUrl: input.baseUrl,
1708
1733
  token: input.token,
1709
- projectId: input.projectId,
1710
- branchName: input.message.branchName,
1711
1734
  sessionId: input.message.sessionId,
1712
1735
  activePlanId: input.message.activePlanId,
1713
1736
  artifactRoot: input.artifactRoot,
1714
1737
  planRoot: input.planRoot,
1715
1738
  access: "read"
1716
1739
  });
1717
- const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
1718
- return listWorkerDirectory(workspace.branchPath, input.message.path, input.message.limit, builtInPaths);
1740
+ return listWorkerDirectory(input.resolvedTarget.rootPath, input.message.path, input.message.limit, builtInPaths);
1719
1741
  }
1720
1742
  function readWorkerImageFile(branchPath, filePath, builtInPaths) {
1721
1743
  const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
@@ -1740,20 +1762,18 @@ function readWorkerImageFile(branchPath, filePath, builtInPaths) {
1740
1762
  };
1741
1763
  }
1742
1764
  async function executeViewFileBytesOperation(input) {
1743
- const builtInPaths = await prepareBuiltInToolPaths({
1765
+ const builtInPaths = await prepareBuiltInToolPathsForTarget({
1766
+ target: input.resolvedTarget.target,
1744
1767
  filePath: input.message.filePath,
1745
1768
  baseUrl: input.baseUrl,
1746
1769
  token: input.token,
1747
- projectId: input.projectId,
1748
- branchName: input.message.branchName,
1749
1770
  sessionId: input.message.sessionId,
1750
1771
  activePlanId: input.message.activePlanId,
1751
1772
  artifactRoot: input.artifactRoot,
1752
1773
  planRoot: input.planRoot,
1753
1774
  access: "read"
1754
1775
  });
1755
- const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
1756
- return readWorkerImageFile(workspace.branchPath, input.message.filePath, builtInPaths);
1776
+ return readWorkerImageFile(input.resolvedTarget.rootPath, input.message.filePath, builtInPaths);
1757
1777
  }
1758
1778
  async function executeOperation(input) {
1759
1779
  switch (input.message.type) {
@@ -1773,38 +1793,56 @@ async function executeOperation(input) {
1773
1793
  return executeViewFileBytesOperation({ ...input, message: input.message });
1774
1794
  }
1775
1795
  }
1796
+ function targetIdentityEnv(target) {
1797
+ return target.type === "project" ? {
1798
+ R5D_PROJECT_ID: target.projectId,
1799
+ R5D_BRANCH_NAME: target.branchName,
1800
+ R5D_WORKSPACE_OWNER_ID: "",
1801
+ R5D_WORKSPACE_ROOT_PROFILE: ""
1802
+ } : {
1803
+ R5D_WORKSPACE_OWNER_ID: target.ownerUserId,
1804
+ R5D_WORKSPACE_ROOT_PROFILE: target.rootProfile,
1805
+ R5D_PROJECT_ID: "",
1806
+ R5D_BRANCH_NAME: ""
1807
+ };
1808
+ }
1809
+ async function prepareShellEnvForTarget(input) {
1810
+ const artifactProcessEnv = await prepareArtifactEnvForShell({
1811
+ baseUrl: input.baseUrl,
1812
+ token: input.token,
1813
+ sessionId: input.sessionId,
1814
+ artifactRoot: input.artifactRoot
1815
+ });
1816
+ if (input.target.type === "workspace" && input.target.rootProfile === "canonical_sync") {
1817
+ return {
1818
+ ...artifactProcessEnv,
1819
+ ...targetIdentityEnv(input.target)
1820
+ };
1821
+ }
1822
+ return {
1823
+ ...artifactProcessEnv,
1824
+ ...preparePlanEnvForShell({
1825
+ target: input.target,
1826
+ planRoot: input.planRoot,
1827
+ activePlanId: input.activePlanId
1828
+ }),
1829
+ ...targetIdentityEnv(input.target)
1830
+ };
1831
+ }
1776
1832
  async function executeCommand(input) {
1777
1833
  const startedAt = Date.now();
1778
1834
  let timeout;
1779
1835
  try {
1780
- const workspace = ensureBranchWorkspace({
1781
- projectId: input.projectId,
1782
- baseUrl: input.baseUrl,
1783
- token: input.token,
1784
- projectRoot: input.projectRoot,
1785
- syncRoot: input.syncRoot,
1786
- branchName: input.message.branchName,
1787
- manifest: input.manifest
1788
- });
1789
- const artifactProcessEnv = await prepareArtifactEnvForShell({
1836
+ const targetProcessEnv = await prepareShellEnvForTarget({
1837
+ target: input.resolvedTarget.target,
1790
1838
  baseUrl: input.baseUrl,
1791
1839
  token: input.token,
1792
- projectId: input.projectId,
1793
- branchName: input.message.branchName,
1794
1840
  sessionId: input.message.sessionId,
1795
- artifactRoot: input.artifactRoot
1796
- });
1797
- const planProcessEnv = preparePlanEnvForShell({
1798
- projectId: input.projectId,
1799
- branchName: input.message.branchName,
1841
+ artifactRoot: input.artifactRoot,
1800
1842
  planRoot: input.planRoot,
1801
1843
  activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
1802
1844
  });
1803
- const commandRoot = input.message.env?.R5D_USE_WORKSPACE_GIT === "1" ? input.workspaceShadowRoot : workspace.branchPath;
1804
- if (input.message.env?.R5D_USE_WORKSPACE_GIT === "1" && !import_node_fs.default.existsSync(import_node_path.default.join(commandRoot, ".git"))) {
1805
- throw new Error("The canonical workspace checkout is not initialized on this worker");
1806
- }
1807
- const cwd = resolveCommandCwd(commandRoot, input.message.cwd);
1845
+ const cwd = resolveCommandCwd(input.resolvedTarget.rootPath, input.message.cwd);
1808
1846
  const subprocess = Bun.spawn(input.message.argv, {
1809
1847
  cwd,
1810
1848
  stdout: "pipe",
@@ -1814,15 +1852,13 @@ async function executeCommand(input) {
1814
1852
  ...process.env,
1815
1853
  ...githubProcessEnv(),
1816
1854
  ...input.message.env ?? {},
1817
- ...artifactProcessEnv,
1818
- ...planProcessEnv
1855
+ ...targetProcessEnv
1819
1856
  }
1820
1857
  });
1821
1858
  activeProcesses.set(input.message.runId, {
1822
1859
  process: subprocess,
1823
- projectId: input.projectId,
1860
+ target: input.resolvedTarget.target,
1824
1861
  sessionId: input.message.sessionId ?? "",
1825
- branchName: input.message.branchName,
1826
1862
  mode: "foreground",
1827
1863
  pid: subprocess.pid,
1828
1864
  processGroupId: process.platform === "win32" ? void 0 : subprocess.pid,
@@ -1886,34 +1922,16 @@ async function executeStreamingCommand(input) {
1886
1922
  });
1887
1923
  return;
1888
1924
  }
1889
- const workspace = ensureBranchWorkspace({
1890
- projectId: input.projectId,
1891
- baseUrl: input.baseUrl,
1892
- token: input.token,
1893
- projectRoot: input.projectRoot,
1894
- syncRoot: input.syncRoot,
1895
- branchName: input.message.branchName,
1896
- manifest: input.manifest
1897
- });
1898
- const artifactProcessEnv = await prepareArtifactEnvForShell({
1925
+ const targetProcessEnv = await prepareShellEnvForTarget({
1926
+ target: input.resolvedTarget.target,
1899
1927
  baseUrl: input.baseUrl,
1900
1928
  token: input.token,
1901
- projectId: input.projectId,
1902
- branchName: input.message.branchName,
1903
1929
  sessionId: input.message.sessionId,
1904
- artifactRoot: input.artifactRoot
1905
- });
1906
- const planProcessEnv = preparePlanEnvForShell({
1907
- projectId: input.projectId,
1908
- branchName: input.message.branchName,
1930
+ artifactRoot: input.artifactRoot,
1909
1931
  planRoot: input.planRoot,
1910
1932
  activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
1911
1933
  });
1912
- const commandRoot = input.message.env?.R5D_USE_WORKSPACE_GIT === "1" ? input.workspaceShadowRoot : workspace.branchPath;
1913
- if (input.message.env?.R5D_USE_WORKSPACE_GIT === "1" && !import_node_fs.default.existsSync(import_node_path.default.join(commandRoot, ".git"))) {
1914
- throw new Error("The canonical workspace checkout is not initialized on this worker");
1915
- }
1916
- const cwd = resolveCommandCwd(commandRoot, input.message.cwd);
1934
+ const cwd = resolveCommandCwd(input.resolvedTarget.rootPath, input.message.cwd);
1917
1935
  const subprocess = Bun.spawn(input.message.argv, {
1918
1936
  cwd,
1919
1937
  stdout: "pipe",
@@ -1923,15 +1941,13 @@ async function executeStreamingCommand(input) {
1923
1941
  ...process.env,
1924
1942
  ...githubProcessEnv(),
1925
1943
  ...input.message.env ?? {},
1926
- ...artifactProcessEnv,
1927
- ...planProcessEnv
1944
+ ...targetProcessEnv
1928
1945
  }
1929
1946
  });
1930
1947
  activeProcesses.set(input.message.runId, {
1931
1948
  process: subprocess,
1932
- projectId: input.projectId,
1949
+ target: input.resolvedTarget.target,
1933
1950
  sessionId: input.message.sessionId,
1934
- branchName: input.message.branchName,
1935
1951
  mode: input.message.mode,
1936
1952
  pid: subprocess.pid,
1937
1953
  processGroupId: process.platform === "win32" ? void 0 : subprocess.pid,
@@ -2028,9 +2044,8 @@ function sendWorkerMessage(ws, message) {
2028
2044
  function buildActiveProcessReports() {
2029
2045
  return Array.from(activeProcesses.entries()).filter(([, active]) => active.sessionId.length > 0).map(([runId, active]) => ({
2030
2046
  runId,
2031
- projectId: active.projectId,
2047
+ target: active.target,
2032
2048
  sessionId: active.sessionId,
2033
- branchName: active.branchName,
2034
2049
  platform: process.platform,
2035
2050
  arch: process.arch,
2036
2051
  mode: active.mode,
@@ -2238,21 +2253,12 @@ function resolveHostShell(command, platform = process.platform) {
2238
2253
  }
2239
2254
  async function openPty(input) {
2240
2255
  try {
2241
- const branchPath = ensureBranchWorkspace({
2242
- projectId: input.projectId,
2243
- baseUrl: input.baseUrl,
2244
- token: input.token,
2245
- projectRoot: input.projectRoot,
2246
- syncRoot: input.syncRoot,
2247
- branchName: input.message.branchName,
2248
- manifest: input.manifest
2249
- });
2250
- const planProcessEnv = preparePlanEnvForShell({
2251
- projectId: input.projectId,
2252
- branchName: input.message.branchName,
2256
+ const planProcessEnv = !(input.resolvedTarget.target.type === "workspace" && input.resolvedTarget.target.rootProfile === "canonical_sync") ? preparePlanEnvForShell({
2257
+ target: input.resolvedTarget.target,
2253
2258
  planRoot: input.planRoot,
2254
2259
  activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
2255
- });
2260
+ }) : {};
2261
+ const targetProcessEnv = targetIdentityEnv(input.resolvedTarget.target);
2256
2262
  const shell = resolveHostShell(input.message.command);
2257
2263
  const ptyProcess = createNodePtyBridge({
2258
2264
  file: shell.file,
@@ -2261,14 +2267,13 @@ async function openPty(input) {
2261
2267
  name: "xterm-256color",
2262
2268
  cols: Math.max(1, Math.min(Math.floor(input.message.cols || 80), 500)),
2263
2269
  rows: Math.max(1, Math.min(Math.floor(input.message.rows || 24), 500)),
2264
- cwd: branchPath.branchPath,
2270
+ cwd: input.resolvedTarget.rootPath,
2265
2271
  env: {
2266
2272
  ...process.env,
2267
2273
  ...githubProcessEnv(),
2268
2274
  ...input.message.env ?? {},
2269
2275
  ...planProcessEnv,
2270
- R5D_PROJECT_ID: input.projectId,
2271
- R5D_BRANCH_NAME: input.message.branchName
2276
+ ...targetProcessEnv
2272
2277
  }
2273
2278
  },
2274
2279
  onOpened: () => {
@@ -2306,8 +2311,7 @@ async function openPty(input) {
2306
2311
  });
2307
2312
  activePtys.set(input.message.ptyId, {
2308
2313
  ...ptyProcess,
2309
- projectId: input.projectId,
2310
- branchName: input.message.branchName
2314
+ target: input.resolvedTarget.target
2311
2315
  });
2312
2316
  } catch (error) {
2313
2317
  sendWorkerMessage(input.ws, {
@@ -2426,6 +2430,15 @@ async function startWorker(options) {
2426
2430
  const allManifestCheckouts = () => [...manifestByProjectId.values()].flatMap(
2427
2431
  (manifest) => manifest.branches.map((branchName) => ({ projectId: manifest.projectId, branchName }))
2428
2432
  );
2433
+ const resolveMessageTarget = (target) => resolveWorkerSessionTarget({
2434
+ target,
2435
+ baseUrl,
2436
+ token,
2437
+ projectsRoot,
2438
+ syncRoot,
2439
+ workspaceShadowRoot,
2440
+ manifestByProjectId
2441
+ });
2429
2442
  const ensureVisibleWorkspaceCheckouts = (targets, options2 = {}) => {
2430
2443
  const created = [];
2431
2444
  for (const target of targets) {
@@ -2568,7 +2581,8 @@ async function startWorker(options) {
2568
2581
  r5dctlVersion: (0, import_cli_update.readInstalledCliVersion)("r5dctl"),
2569
2582
  capabilities: {
2570
2583
  updateClis: true,
2571
- canonicalResolverCheckout: true
2584
+ canonicalResolverCheckout: true,
2585
+ browserPortForwarding: true
2572
2586
  },
2573
2587
  projectRoot: projectsRoot,
2574
2588
  artifactRoot,
@@ -2680,7 +2694,11 @@ async function startWorker(options) {
2680
2694
  }
2681
2695
  if (message.type === "workspace_head_updated") {
2682
2696
  if (!activeWorkspaceIncidentId) {
2683
- await runRequestedWorkspaceSync(crypto.randomUUID(), { type: "inbound_head", detail: message.commitHash });
2697
+ await runRequestedWorkspaceSync(
2698
+ crypto.randomUUID(),
2699
+ { type: "inbound_head", detail: message.commitHash },
2700
+ { skipVisibleMirror: true }
2701
+ );
2684
2702
  }
2685
2703
  return;
2686
2704
  }
@@ -2690,8 +2708,6 @@ async function startWorker(options) {
2690
2708
  await syncSessionArtifacts({
2691
2709
  baseUrl,
2692
2710
  token,
2693
- projectId: message.projectId,
2694
- branchName: message.branchName,
2695
2711
  sessionId: message.sessionId,
2696
2712
  artifactRoot
2697
2713
  });
@@ -2721,6 +2737,17 @@ async function startWorker(options) {
2721
2737
  pendingProcessTerminals.delete(message.runId);
2722
2738
  return;
2723
2739
  }
2740
+ if (message.type === "port_forward_connect") {
2741
+ (0, import_port_forward_client.openWorkerPortForwardRelay)({
2742
+ baseUrl,
2743
+ token,
2744
+ label,
2745
+ forwardId: message.forwardId,
2746
+ relayConnectionId: message.relayConnectionId,
2747
+ workerPort: message.workerPort
2748
+ });
2749
+ return;
2750
+ }
2724
2751
  if (message.type === "update_clis") {
2725
2752
  if (cliUpdateInProgress) {
2726
2753
  sendWorkerMessage(ws, {
@@ -2800,11 +2827,19 @@ async function startWorker(options) {
2800
2827
  });
2801
2828
  return;
2802
2829
  }
2803
- const manifest = manifestByProjectId.get(message.projectId);
2804
- const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
2805
- process.stdout.write(`[r5d-worker] pty ${message.ptyId}: ${message.projectId}/${message.branchName}
2830
+ try {
2831
+ const resolvedTarget = resolveMessageTarget(message.target);
2832
+ process.stdout.write(`[r5d-worker] pty ${message.ptyId}: ${describeWorkerSessionTarget(message.target)}
2806
2833
  `);
2807
- await openPty({ ws, message, projectId: message.projectId, baseUrl, token, projectRoot, syncRoot, planRoot, manifest });
2834
+ await openPty({ ws, message, resolvedTarget, planRoot });
2835
+ } catch (error) {
2836
+ sendWorkerMessage(ws, {
2837
+ type: "pty_error",
2838
+ requestId: message.requestId,
2839
+ ptyId: message.ptyId,
2840
+ error: error instanceof Error ? error.message : String(error)
2841
+ });
2842
+ }
2808
2843
  return;
2809
2844
  }
2810
2845
  if (message.type === "pty_input") {
@@ -2830,22 +2865,22 @@ async function startWorker(options) {
2830
2865
  });
2831
2866
  return;
2832
2867
  }
2833
- const manifest = manifestByProjectId.get(message.projectId);
2834
- const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
2835
- process.stdout.write(`[r5d-worker] exec ${message.runId}: ${message.argv.join(" ")}
2868
+ let result;
2869
+ try {
2870
+ const resolvedTarget = resolveMessageTarget(message.target);
2871
+ process.stdout.write(`[r5d-worker] exec ${message.runId}: ${message.argv.join(" ")}
2836
2872
  `);
2837
- const result = await executeCommand({
2838
- message,
2839
- projectId: message.projectId,
2840
- baseUrl,
2841
- token,
2842
- projectRoot,
2843
- syncRoot,
2844
- artifactRoot,
2845
- planRoot,
2846
- workspaceShadowRoot,
2847
- manifest
2848
- });
2873
+ result = await executeCommand({ message, resolvedTarget, baseUrl, token, artifactRoot, planRoot });
2874
+ } catch (error) {
2875
+ result = {
2876
+ type: "exec_result",
2877
+ requestId: message.requestId,
2878
+ stdout: "",
2879
+ stderr: "",
2880
+ exitCode: 1,
2881
+ error: error instanceof Error ? error.message : String(error)
2882
+ };
2883
+ }
2849
2884
  ws.send(JSON.stringify(result));
2850
2885
  return;
2851
2886
  }
@@ -2865,22 +2900,17 @@ async function startWorker(options) {
2865
2900
  runId: message.runId
2866
2901
  });
2867
2902
  try {
2868
- const manifest = manifestByProjectId.get(message.projectId);
2869
- const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
2903
+ const resolvedTarget = resolveMessageTarget(message.target);
2870
2904
  process.stdout.write(`[r5d-worker] exec_start ${message.runId}: ${message.argv.join(" ")}
2871
2905
  `);
2872
2906
  void executeStreamingCommand({
2873
2907
  ws,
2874
2908
  message,
2875
- projectId: message.projectId,
2909
+ resolvedTarget,
2876
2910
  baseUrl,
2877
2911
  token,
2878
- projectRoot,
2879
- syncRoot,
2880
2912
  artifactRoot,
2881
- planRoot,
2882
- workspaceShadowRoot,
2883
- manifest
2913
+ planRoot
2884
2914
  }).catch((error) => {
2885
2915
  sendWorkerMessage(ws, {
2886
2916
  type: "exec_start_error",
@@ -2901,18 +2931,14 @@ async function startWorker(options) {
2901
2931
  }
2902
2932
  if (message.type === "read" || message.type === "write" || message.type === "edit" || message.type === "grep" || message.type === "find" || message.type === "ls" || message.type === "view_file_bytes") {
2903
2933
  try {
2904
- const manifest = manifestByProjectId.get(message.projectId);
2905
- const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
2934
+ const resolvedTarget = resolveMessageTarget(message.target);
2906
2935
  const result = await executeOperation({
2907
2936
  message,
2908
- projectId: message.projectId,
2937
+ resolvedTarget,
2909
2938
  baseUrl,
2910
2939
  token,
2911
- projectRoot,
2912
- syncRoot,
2913
2940
  artifactRoot,
2914
- planRoot,
2915
- manifest
2941
+ planRoot
2916
2942
  });
2917
2943
  ws.send(
2918
2944
  JSON.stringify({
@@ -3040,6 +3066,7 @@ if (isCliEntrypoint()) {
3040
3066
  }
3041
3067
  // Annotate the CommonJS export names for ESM import in node:
3042
3068
  0 && (module.exports = {
3069
+ describeWorkerSessionTarget,
3043
3070
  editWorkerTextFile,
3044
3071
  ensureVisibleGitCheckout,
3045
3072
  findWorkerFiles,
@@ -3048,11 +3075,14 @@ if (isCliEntrypoint()) {
3048
3075
  isArtifactEnvPath,
3049
3076
  listWorkerDirectory,
3050
3077
  prepareArtifactEnvForShell,
3078
+ prepareBuiltInToolPathsForTarget,
3051
3079
  preparePlanEnvForShell,
3080
+ prepareShellEnvForTarget,
3052
3081
  readWorkerImageFile,
3053
3082
  readWorkerTextFile,
3054
3083
  resolveHostShell,
3055
3084
  resolveWorkerFilePath,
3085
+ resolveWorkerSessionTarget,
3056
3086
  syncSessionArtifacts,
3057
3087
  visibleCheckoutGitTestHarness,
3058
3088
  visibleCheckoutRemoteFor,