@ricsam/r5d-worker 0.0.48 → 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/mjs/main.mjs CHANGED
@@ -295,8 +295,15 @@ function projectPlanDir(planRoot, projectId, branchName) {
295
295
  validateBranchName(branchName);
296
296
  return path.join(planRoot, projectId, branchName);
297
297
  }
298
- function planEnv(planRoot, projectId, branchName, activePlanId) {
299
- const plansDir = projectPlanDir(planRoot, projectId, branchName);
298
+ function targetPlanDir(planRoot, target) {
299
+ if (target.type === "project") return projectPlanDir(planRoot, target.projectId, target.branchName);
300
+ if (target.rootProfile === "canonical_sync") {
301
+ throw new Error("Ordinary plans are unavailable in canonical synchronization sessions");
302
+ }
303
+ return path.join(planRoot, "workspace");
304
+ }
305
+ function planEnv(planRoot, target, activePlanId) {
306
+ const plansDir = targetPlanDir(planRoot, target);
300
307
  const env = {
301
308
  [R5D_PLANS_DIR_ENV]: plansDir
302
309
  };
@@ -314,14 +321,14 @@ function isArtifactEnvPath(filePath) {
314
321
  return false;
315
322
  }
316
323
  }
317
- function artifactApiBasePath(projectId, branchName, sessionId) {
318
- return `/preview-api/${encodeURIComponent(projectId)}/${encodeURIComponent(branchName)}/artifacts/${encodeURIComponent(sessionId)}`;
324
+ function artifactApiBasePath(sessionId) {
325
+ return `/preview-api/artifacts/${encodeURIComponent(sessionId)}`;
319
326
  }
320
- function artifactManifestUrl(baseUrl, projectId, branchName, sessionId) {
321
- return new URL(artifactApiBasePath(projectId, branchName, sessionId), baseUrl);
327
+ function artifactManifestUrl(baseUrl, sessionId) {
328
+ return new URL(artifactApiBasePath(sessionId), baseUrl);
322
329
  }
323
- function artifactDownloadUrl(baseUrl, projectId, branchName, sessionId, filename) {
324
- return new URL(`${artifactApiBasePath(projectId, branchName, sessionId)}/${encodeURIComponent(filename)}`, baseUrl);
330
+ function artifactDownloadUrl(baseUrl, sessionId, filename) {
331
+ return new URL(`${artifactApiBasePath(sessionId)}/${encodeURIComponent(filename)}`, baseUrl);
325
332
  }
326
333
  function sha256Path(filePath) {
327
334
  return createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
@@ -413,7 +420,7 @@ async function syncRemoteFileSet(input) {
413
420
  }
414
421
  async function fetchSessionArtifactManifest(input) {
415
422
  return fetchRemoteFileManifest({
416
- url: artifactManifestUrl(input.baseUrl, input.projectId, input.branchName, input.sessionId),
423
+ url: artifactManifestUrl(input.baseUrl, input.sessionId),
417
424
  token: input.token,
418
425
  collectionKey: "artifacts",
419
426
  label: "artifact"
@@ -431,7 +438,7 @@ async function syncSessionArtifacts(input) {
431
438
  files: artifacts,
432
439
  label: "Artifact path",
433
440
  download: (artifact) => downloadRemoteSyncedFile({
434
- url: artifactDownloadUrl(input.baseUrl, input.projectId, input.branchName, input.sessionId, artifact.filename),
441
+ url: artifactDownloadUrl(input.baseUrl, input.sessionId, artifact.filename),
435
442
  token: input.token,
436
443
  targetDir,
437
444
  file: artifact,
@@ -464,14 +471,12 @@ async function prepareBuiltInToolPaths(input) {
464
471
  artifactsDir: await syncSessionArtifacts({
465
472
  baseUrl: input.baseUrl,
466
473
  token: input.token,
467
- projectId: input.projectId,
468
- branchName: input.branchName,
469
474
  sessionId: input.sessionId,
470
475
  artifactRoot: input.artifactRoot
471
476
  })
472
477
  };
473
478
  }
474
- const plansDir = projectPlanDir(input.planRoot, input.projectId, input.branchName);
479
+ const plansDir = targetPlanDir(input.planRoot, input.target);
475
480
  fs.mkdirSync(plansDir, { recursive: true });
476
481
  if (parsed.ref === R5D_PLANS_DIR_REF) {
477
482
  return { plansDir };
@@ -493,8 +498,6 @@ async function prepareArtifactEnvForShell(input) {
493
498
  await syncSessionArtifacts({
494
499
  baseUrl: input.baseUrl,
495
500
  token: input.token,
496
- projectId: input.projectId,
497
- branchName: input.branchName,
498
501
  sessionId: input.sessionId,
499
502
  artifactRoot: input.artifactRoot
500
503
  });
@@ -511,9 +514,9 @@ async function prepareArtifactEnvForShell(input) {
511
514
  };
512
515
  }
513
516
  function preparePlanEnvForShell(input) {
514
- const plansDir = projectPlanDir(input.planRoot, input.projectId, input.branchName);
517
+ const plansDir = targetPlanDir(input.planRoot, input.target);
515
518
  fs.mkdirSync(plansDir, { recursive: true });
516
- return planEnv(input.planRoot, input.projectId, input.branchName, input.activePlanId);
519
+ return planEnv(input.planRoot, input.target, input.activePlanId);
517
520
  }
518
521
  async function verifyR5dctlAuth(baseUrl, token) {
519
522
  const statusUrl = new URL("/api/r5dctl/auth/status", baseUrl);
@@ -1128,6 +1131,32 @@ function ensureBranchWorkspace(input) {
1128
1131
  })
1129
1132
  };
1130
1133
  }
1134
+ function describeWorkerSessionTarget(target) {
1135
+ return target.type === "project" ? `${target.projectId}/${target.branchName}` : `${target.ownerUserId}/${target.rootProfile}`;
1136
+ }
1137
+ function resolveWorkerSessionTarget(input) {
1138
+ if (input.target.type === "workspace") {
1139
+ if (input.target.rootProfile === "visible_projects") {
1140
+ return { target: input.target, rootPath: input.projectsRoot };
1141
+ }
1142
+ if (!fs.existsSync(path.join(input.workspaceShadowRoot, ".git"))) {
1143
+ throw new Error("The canonical workspace checkout is not initialized on this worker");
1144
+ }
1145
+ return { target: input.target, rootPath: input.workspaceShadowRoot };
1146
+ }
1147
+ const manifest = input.manifestByProjectId.get(input.target.projectId);
1148
+ const projectRoot = projectRootFor(input.projectsRoot, input.target.projectId, input.manifestByProjectId);
1149
+ const workspace = ensureBranchWorkspace({
1150
+ projectId: input.target.projectId,
1151
+ baseUrl: input.baseUrl,
1152
+ token: input.token,
1153
+ projectRoot,
1154
+ syncRoot: input.syncRoot,
1155
+ branchName: input.target.branchName,
1156
+ manifest
1157
+ });
1158
+ return { target: input.target, rootPath: workspace.branchPath, manifest };
1159
+ }
1131
1160
  function workspaceSyncCheckoutTargets(input) {
1132
1161
  const selected = /* @__PURE__ */ new Map();
1133
1162
  const add = (target) => {
@@ -1156,7 +1185,7 @@ function resolveCommandCwd(branchPath, cwd) {
1156
1185
  const resolved = path.resolve(branchPath, cwd);
1157
1186
  const relative = path.relative(branchPath, resolved);
1158
1187
  if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
1159
- throw new Error(`Relative cwd escapes the project branch: ${cwd}`);
1188
+ throw new Error(`Relative cwd escapes the execution root: ${cwd}`);
1160
1189
  }
1161
1190
  return resolved;
1162
1191
  }
@@ -1191,8 +1220,8 @@ async function streamCommandOutput(stream, onData) {
1191
1220
  reader.releaseLock();
1192
1221
  }
1193
1222
  }
1194
- function ensureOperationBranch(input) {
1195
- return ensureBranchWorkspace(input);
1223
+ async function prepareBuiltInToolPathsForTarget(input) {
1224
+ return prepareBuiltInToolPaths(input);
1196
1225
  }
1197
1226
  function formatLineNumberedContent(content, offset, limit) {
1198
1227
  const allLines = content.split("\n");
@@ -1318,11 +1347,11 @@ async function withFileMutationQueue(key, operation) {
1318
1347
  }
1319
1348
  }
1320
1349
  }
1321
- function mutationQueueKey(projectId, branchName, resolved) {
1350
+ function mutationQueueKey(target, resolved) {
1322
1351
  if (resolved.scope !== "project") {
1323
1352
  return `${resolved.scope}:${path.normalize(resolved.absolutePath)}`;
1324
1353
  }
1325
- return `project:${projectId}:${branchName}:${path.posix.normalize(resolved.repoRelativePath)}`;
1354
+ return `${describeWorkerSessionTarget(target)}:${path.posix.normalize(resolved.repoRelativePath)}`;
1326
1355
  }
1327
1356
  function readWorkerTextFile(branchPath, filePath, offset, limit, builtInPaths) {
1328
1357
  const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
@@ -1397,57 +1426,51 @@ function editWorkerTextFile(branchPath, filePath, edits, builtInPaths) {
1397
1426
  };
1398
1427
  }
1399
1428
  async function executeReadFileOperation(input) {
1400
- const builtInPaths = await prepareBuiltInToolPaths({
1429
+ const builtInPaths = await prepareBuiltInToolPathsForTarget({
1430
+ target: input.resolvedTarget.target,
1401
1431
  filePath: input.message.filePath,
1402
1432
  baseUrl: input.baseUrl,
1403
1433
  token: input.token,
1404
- projectId: input.projectId,
1405
- branchName: input.message.branchName,
1406
1434
  sessionId: input.message.sessionId,
1407
1435
  activePlanId: input.message.activePlanId,
1408
1436
  artifactRoot: input.artifactRoot,
1409
1437
  planRoot: input.planRoot,
1410
1438
  access: "read"
1411
1439
  });
1412
- const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
1413
- return readWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.offset, input.message.limit, builtInPaths);
1440
+ return readWorkerTextFile(input.resolvedTarget.rootPath, input.message.filePath, input.message.offset, input.message.limit, builtInPaths);
1414
1441
  }
1415
1442
  async function executeWriteFileOperation(input) {
1416
- const builtInPaths = await prepareBuiltInToolPaths({
1443
+ const builtInPaths = await prepareBuiltInToolPathsForTarget({
1444
+ target: input.resolvedTarget.target,
1417
1445
  filePath: input.message.filePath,
1418
1446
  baseUrl: input.baseUrl,
1419
1447
  token: input.token,
1420
- projectId: input.projectId,
1421
- branchName: input.message.branchName,
1422
1448
  sessionId: input.message.sessionId,
1423
1449
  activePlanId: input.message.activePlanId,
1424
1450
  artifactRoot: input.artifactRoot,
1425
1451
  planRoot: input.planRoot,
1426
1452
  access: "write"
1427
1453
  });
1428
- const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
1429
- const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath, builtInPaths);
1430
- return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved), async () => {
1431
- return writeWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.content, builtInPaths);
1454
+ const resolved = resolveWorkerFilePath(input.resolvedTarget.rootPath, input.message.filePath, builtInPaths);
1455
+ return withFileMutationQueue(mutationQueueKey(input.resolvedTarget.target, resolved), async () => {
1456
+ return writeWorkerTextFile(input.resolvedTarget.rootPath, input.message.filePath, input.message.content, builtInPaths);
1432
1457
  });
1433
1458
  }
1434
1459
  async function executeEditFileOperation(input) {
1435
- const builtInPaths = await prepareBuiltInToolPaths({
1460
+ const builtInPaths = await prepareBuiltInToolPathsForTarget({
1461
+ target: input.resolvedTarget.target,
1436
1462
  filePath: input.message.filePath,
1437
1463
  baseUrl: input.baseUrl,
1438
1464
  token: input.token,
1439
- projectId: input.projectId,
1440
- branchName: input.message.branchName,
1441
1465
  sessionId: input.message.sessionId,
1442
1466
  activePlanId: input.message.activePlanId,
1443
1467
  artifactRoot: input.artifactRoot,
1444
1468
  planRoot: input.planRoot,
1445
1469
  access: "write"
1446
1470
  });
1447
- const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
1448
- const resolved = resolveWorkerFilePath(workspace.branchPath, input.message.filePath, builtInPaths);
1449
- return withFileMutationQueue(mutationQueueKey(input.projectId, input.message.branchName, resolved), async () => {
1450
- return editWorkerTextFile(workspace.branchPath, input.message.filePath, input.message.edits, builtInPaths);
1471
+ const resolved = resolveWorkerFilePath(input.resolvedTarget.rootPath, input.message.filePath, builtInPaths);
1472
+ return withFileMutationQueue(mutationQueueKey(input.resolvedTarget.target, resolved), async () => {
1473
+ return editWorkerTextFile(input.resolvedTarget.rootPath, input.message.filePath, input.message.edits, builtInPaths);
1451
1474
  });
1452
1475
  }
1453
1476
  const IGNORED_ENTRY_NAMES = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "build", ".next", ".cache", "coverage", "target"]);
@@ -1560,21 +1583,19 @@ function grepWorkerFiles(branchPath, input, builtInPaths) {
1560
1583
  }
1561
1584
  async function executeGrepOperation(input) {
1562
1585
  const toolPath = input.message.path ?? ".";
1563
- const builtInPaths = await prepareBuiltInToolPaths({
1586
+ const builtInPaths = await prepareBuiltInToolPathsForTarget({
1587
+ target: input.resolvedTarget.target,
1564
1588
  filePath: toolPath,
1565
1589
  baseUrl: input.baseUrl,
1566
1590
  token: input.token,
1567
- projectId: input.projectId,
1568
- branchName: input.message.branchName,
1569
1591
  sessionId: input.message.sessionId,
1570
1592
  activePlanId: input.message.activePlanId,
1571
1593
  artifactRoot: input.artifactRoot,
1572
1594
  planRoot: input.planRoot,
1573
1595
  access: "read"
1574
1596
  });
1575
- const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
1576
1597
  return grepWorkerFiles(
1577
- workspace.branchPath,
1598
+ input.resolvedTarget.rootPath,
1578
1599
  {
1579
1600
  pattern: input.message.pattern,
1580
1601
  path: input.message.path,
@@ -1621,21 +1642,19 @@ function findWorkerFiles(branchPath, input, builtInPaths) {
1621
1642
  }
1622
1643
  async function executeFindOperation(input) {
1623
1644
  const toolPath = input.message.path ?? ".";
1624
- const builtInPaths = await prepareBuiltInToolPaths({
1645
+ const builtInPaths = await prepareBuiltInToolPathsForTarget({
1646
+ target: input.resolvedTarget.target,
1625
1647
  filePath: toolPath,
1626
1648
  baseUrl: input.baseUrl,
1627
1649
  token: input.token,
1628
- projectId: input.projectId,
1629
- branchName: input.message.branchName,
1630
1650
  sessionId: input.message.sessionId,
1631
1651
  activePlanId: input.message.activePlanId,
1632
1652
  artifactRoot: input.artifactRoot,
1633
1653
  planRoot: input.planRoot,
1634
1654
  access: "read"
1635
1655
  });
1636
- const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
1637
1656
  return findWorkerFiles(
1638
- workspace.branchPath,
1657
+ input.resolvedTarget.rootPath,
1639
1658
  {
1640
1659
  pattern: input.message.pattern,
1641
1660
  path: input.message.path,
@@ -1665,20 +1684,18 @@ function listWorkerDirectory(branchPath, inputPath = ".", inputLimit, builtInPat
1665
1684
  }
1666
1685
  async function executeLsOperation(input) {
1667
1686
  const toolPath = input.message.path ?? ".";
1668
- const builtInPaths = await prepareBuiltInToolPaths({
1687
+ const builtInPaths = await prepareBuiltInToolPathsForTarget({
1688
+ target: input.resolvedTarget.target,
1669
1689
  filePath: toolPath,
1670
1690
  baseUrl: input.baseUrl,
1671
1691
  token: input.token,
1672
- projectId: input.projectId,
1673
- branchName: input.message.branchName,
1674
1692
  sessionId: input.message.sessionId,
1675
1693
  activePlanId: input.message.activePlanId,
1676
1694
  artifactRoot: input.artifactRoot,
1677
1695
  planRoot: input.planRoot,
1678
1696
  access: "read"
1679
1697
  });
1680
- const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
1681
- return listWorkerDirectory(workspace.branchPath, input.message.path, input.message.limit, builtInPaths);
1698
+ return listWorkerDirectory(input.resolvedTarget.rootPath, input.message.path, input.message.limit, builtInPaths);
1682
1699
  }
1683
1700
  function readWorkerImageFile(branchPath, filePath, builtInPaths) {
1684
1701
  const resolved = resolveWorkerFilePath(branchPath, filePath, builtInPaths);
@@ -1703,20 +1720,18 @@ function readWorkerImageFile(branchPath, filePath, builtInPaths) {
1703
1720
  };
1704
1721
  }
1705
1722
  async function executeViewFileBytesOperation(input) {
1706
- const builtInPaths = await prepareBuiltInToolPaths({
1723
+ const builtInPaths = await prepareBuiltInToolPathsForTarget({
1724
+ target: input.resolvedTarget.target,
1707
1725
  filePath: input.message.filePath,
1708
1726
  baseUrl: input.baseUrl,
1709
1727
  token: input.token,
1710
- projectId: input.projectId,
1711
- branchName: input.message.branchName,
1712
1728
  sessionId: input.message.sessionId,
1713
1729
  activePlanId: input.message.activePlanId,
1714
1730
  artifactRoot: input.artifactRoot,
1715
1731
  planRoot: input.planRoot,
1716
1732
  access: "read"
1717
1733
  });
1718
- const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
1719
- return readWorkerImageFile(workspace.branchPath, input.message.filePath, builtInPaths);
1734
+ return readWorkerImageFile(input.resolvedTarget.rootPath, input.message.filePath, builtInPaths);
1720
1735
  }
1721
1736
  async function executeOperation(input) {
1722
1737
  switch (input.message.type) {
@@ -1736,38 +1751,56 @@ async function executeOperation(input) {
1736
1751
  return executeViewFileBytesOperation({ ...input, message: input.message });
1737
1752
  }
1738
1753
  }
1754
+ function targetIdentityEnv(target) {
1755
+ return target.type === "project" ? {
1756
+ R5D_PROJECT_ID: target.projectId,
1757
+ R5D_BRANCH_NAME: target.branchName,
1758
+ R5D_WORKSPACE_OWNER_ID: "",
1759
+ R5D_WORKSPACE_ROOT_PROFILE: ""
1760
+ } : {
1761
+ R5D_WORKSPACE_OWNER_ID: target.ownerUserId,
1762
+ R5D_WORKSPACE_ROOT_PROFILE: target.rootProfile,
1763
+ R5D_PROJECT_ID: "",
1764
+ R5D_BRANCH_NAME: ""
1765
+ };
1766
+ }
1767
+ async function prepareShellEnvForTarget(input) {
1768
+ const artifactProcessEnv = await prepareArtifactEnvForShell({
1769
+ baseUrl: input.baseUrl,
1770
+ token: input.token,
1771
+ sessionId: input.sessionId,
1772
+ artifactRoot: input.artifactRoot
1773
+ });
1774
+ if (input.target.type === "workspace" && input.target.rootProfile === "canonical_sync") {
1775
+ return {
1776
+ ...artifactProcessEnv,
1777
+ ...targetIdentityEnv(input.target)
1778
+ };
1779
+ }
1780
+ return {
1781
+ ...artifactProcessEnv,
1782
+ ...preparePlanEnvForShell({
1783
+ target: input.target,
1784
+ planRoot: input.planRoot,
1785
+ activePlanId: input.activePlanId
1786
+ }),
1787
+ ...targetIdentityEnv(input.target)
1788
+ };
1789
+ }
1739
1790
  async function executeCommand(input) {
1740
1791
  const startedAt = Date.now();
1741
1792
  let timeout;
1742
1793
  try {
1743
- const workspace = ensureBranchWorkspace({
1744
- projectId: input.projectId,
1745
- baseUrl: input.baseUrl,
1746
- token: input.token,
1747
- projectRoot: input.projectRoot,
1748
- syncRoot: input.syncRoot,
1749
- branchName: input.message.branchName,
1750
- manifest: input.manifest
1751
- });
1752
- const artifactProcessEnv = await prepareArtifactEnvForShell({
1794
+ const targetProcessEnv = await prepareShellEnvForTarget({
1795
+ target: input.resolvedTarget.target,
1753
1796
  baseUrl: input.baseUrl,
1754
1797
  token: input.token,
1755
- projectId: input.projectId,
1756
- branchName: input.message.branchName,
1757
1798
  sessionId: input.message.sessionId,
1758
- artifactRoot: input.artifactRoot
1759
- });
1760
- const planProcessEnv = preparePlanEnvForShell({
1761
- projectId: input.projectId,
1762
- branchName: input.message.branchName,
1799
+ artifactRoot: input.artifactRoot,
1763
1800
  planRoot: input.planRoot,
1764
1801
  activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
1765
1802
  });
1766
- const commandRoot = input.message.env?.R5D_USE_WORKSPACE_GIT === "1" ? input.workspaceShadowRoot : workspace.branchPath;
1767
- if (input.message.env?.R5D_USE_WORKSPACE_GIT === "1" && !fs.existsSync(path.join(commandRoot, ".git"))) {
1768
- throw new Error("The canonical workspace checkout is not initialized on this worker");
1769
- }
1770
- const cwd = resolveCommandCwd(commandRoot, input.message.cwd);
1803
+ const cwd = resolveCommandCwd(input.resolvedTarget.rootPath, input.message.cwd);
1771
1804
  const subprocess = Bun.spawn(input.message.argv, {
1772
1805
  cwd,
1773
1806
  stdout: "pipe",
@@ -1777,15 +1810,13 @@ async function executeCommand(input) {
1777
1810
  ...process.env,
1778
1811
  ...githubProcessEnv(),
1779
1812
  ...input.message.env ?? {},
1780
- ...artifactProcessEnv,
1781
- ...planProcessEnv
1813
+ ...targetProcessEnv
1782
1814
  }
1783
1815
  });
1784
1816
  activeProcesses.set(input.message.runId, {
1785
1817
  process: subprocess,
1786
- projectId: input.projectId,
1818
+ target: input.resolvedTarget.target,
1787
1819
  sessionId: input.message.sessionId ?? "",
1788
- branchName: input.message.branchName,
1789
1820
  mode: "foreground",
1790
1821
  pid: subprocess.pid,
1791
1822
  processGroupId: process.platform === "win32" ? void 0 : subprocess.pid,
@@ -1849,34 +1880,16 @@ async function executeStreamingCommand(input) {
1849
1880
  });
1850
1881
  return;
1851
1882
  }
1852
- const workspace = ensureBranchWorkspace({
1853
- projectId: input.projectId,
1854
- baseUrl: input.baseUrl,
1855
- token: input.token,
1856
- projectRoot: input.projectRoot,
1857
- syncRoot: input.syncRoot,
1858
- branchName: input.message.branchName,
1859
- manifest: input.manifest
1860
- });
1861
- const artifactProcessEnv = await prepareArtifactEnvForShell({
1883
+ const targetProcessEnv = await prepareShellEnvForTarget({
1884
+ target: input.resolvedTarget.target,
1862
1885
  baseUrl: input.baseUrl,
1863
1886
  token: input.token,
1864
- projectId: input.projectId,
1865
- branchName: input.message.branchName,
1866
1887
  sessionId: input.message.sessionId,
1867
- artifactRoot: input.artifactRoot
1868
- });
1869
- const planProcessEnv = preparePlanEnvForShell({
1870
- projectId: input.projectId,
1871
- branchName: input.message.branchName,
1888
+ artifactRoot: input.artifactRoot,
1872
1889
  planRoot: input.planRoot,
1873
1890
  activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
1874
1891
  });
1875
- const commandRoot = input.message.env?.R5D_USE_WORKSPACE_GIT === "1" ? input.workspaceShadowRoot : workspace.branchPath;
1876
- if (input.message.env?.R5D_USE_WORKSPACE_GIT === "1" && !fs.existsSync(path.join(commandRoot, ".git"))) {
1877
- throw new Error("The canonical workspace checkout is not initialized on this worker");
1878
- }
1879
- const cwd = resolveCommandCwd(commandRoot, input.message.cwd);
1892
+ const cwd = resolveCommandCwd(input.resolvedTarget.rootPath, input.message.cwd);
1880
1893
  const subprocess = Bun.spawn(input.message.argv, {
1881
1894
  cwd,
1882
1895
  stdout: "pipe",
@@ -1886,15 +1899,13 @@ async function executeStreamingCommand(input) {
1886
1899
  ...process.env,
1887
1900
  ...githubProcessEnv(),
1888
1901
  ...input.message.env ?? {},
1889
- ...artifactProcessEnv,
1890
- ...planProcessEnv
1902
+ ...targetProcessEnv
1891
1903
  }
1892
1904
  });
1893
1905
  activeProcesses.set(input.message.runId, {
1894
1906
  process: subprocess,
1895
- projectId: input.projectId,
1907
+ target: input.resolvedTarget.target,
1896
1908
  sessionId: input.message.sessionId,
1897
- branchName: input.message.branchName,
1898
1909
  mode: input.message.mode,
1899
1910
  pid: subprocess.pid,
1900
1911
  processGroupId: process.platform === "win32" ? void 0 : subprocess.pid,
@@ -1991,9 +2002,8 @@ function sendWorkerMessage(ws, message) {
1991
2002
  function buildActiveProcessReports() {
1992
2003
  return Array.from(activeProcesses.entries()).filter(([, active]) => active.sessionId.length > 0).map(([runId, active]) => ({
1993
2004
  runId,
1994
- projectId: active.projectId,
2005
+ target: active.target,
1995
2006
  sessionId: active.sessionId,
1996
- branchName: active.branchName,
1997
2007
  platform: process.platform,
1998
2008
  arch: process.arch,
1999
2009
  mode: active.mode,
@@ -2201,21 +2211,12 @@ function resolveHostShell(command, platform = process.platform) {
2201
2211
  }
2202
2212
  async function openPty(input) {
2203
2213
  try {
2204
- const branchPath = ensureBranchWorkspace({
2205
- projectId: input.projectId,
2206
- baseUrl: input.baseUrl,
2207
- token: input.token,
2208
- projectRoot: input.projectRoot,
2209
- syncRoot: input.syncRoot,
2210
- branchName: input.message.branchName,
2211
- manifest: input.manifest
2212
- });
2213
- const planProcessEnv = preparePlanEnvForShell({
2214
- projectId: input.projectId,
2215
- branchName: input.message.branchName,
2214
+ const planProcessEnv = !(input.resolvedTarget.target.type === "workspace" && input.resolvedTarget.target.rootProfile === "canonical_sync") ? preparePlanEnvForShell({
2215
+ target: input.resolvedTarget.target,
2216
2216
  planRoot: input.planRoot,
2217
2217
  activePlanId: input.message.env?.R5D_ACTIVE_PLAN_ID
2218
- });
2218
+ }) : {};
2219
+ const targetProcessEnv = targetIdentityEnv(input.resolvedTarget.target);
2219
2220
  const shell = resolveHostShell(input.message.command);
2220
2221
  const ptyProcess = createNodePtyBridge({
2221
2222
  file: shell.file,
@@ -2224,14 +2225,13 @@ async function openPty(input) {
2224
2225
  name: "xterm-256color",
2225
2226
  cols: Math.max(1, Math.min(Math.floor(input.message.cols || 80), 500)),
2226
2227
  rows: Math.max(1, Math.min(Math.floor(input.message.rows || 24), 500)),
2227
- cwd: branchPath.branchPath,
2228
+ cwd: input.resolvedTarget.rootPath,
2228
2229
  env: {
2229
2230
  ...process.env,
2230
2231
  ...githubProcessEnv(),
2231
2232
  ...input.message.env ?? {},
2232
2233
  ...planProcessEnv,
2233
- R5D_PROJECT_ID: input.projectId,
2234
- R5D_BRANCH_NAME: input.message.branchName
2234
+ ...targetProcessEnv
2235
2235
  }
2236
2236
  },
2237
2237
  onOpened: () => {
@@ -2269,8 +2269,7 @@ async function openPty(input) {
2269
2269
  });
2270
2270
  activePtys.set(input.message.ptyId, {
2271
2271
  ...ptyProcess,
2272
- projectId: input.projectId,
2273
- branchName: input.message.branchName
2272
+ target: input.resolvedTarget.target
2274
2273
  });
2275
2274
  } catch (error) {
2276
2275
  sendWorkerMessage(input.ws, {
@@ -2389,6 +2388,15 @@ async function startWorker(options) {
2389
2388
  const allManifestCheckouts = () => [...manifestByProjectId.values()].flatMap(
2390
2389
  (manifest) => manifest.branches.map((branchName) => ({ projectId: manifest.projectId, branchName }))
2391
2390
  );
2391
+ const resolveMessageTarget = (target) => resolveWorkerSessionTarget({
2392
+ target,
2393
+ baseUrl,
2394
+ token,
2395
+ projectsRoot,
2396
+ syncRoot,
2397
+ workspaceShadowRoot,
2398
+ manifestByProjectId
2399
+ });
2392
2400
  const ensureVisibleWorkspaceCheckouts = (targets, options2 = {}) => {
2393
2401
  const created = [];
2394
2402
  for (const target of targets) {
@@ -2644,7 +2652,11 @@ async function startWorker(options) {
2644
2652
  }
2645
2653
  if (message.type === "workspace_head_updated") {
2646
2654
  if (!activeWorkspaceIncidentId) {
2647
- await runRequestedWorkspaceSync(crypto.randomUUID(), { type: "inbound_head", detail: message.commitHash });
2655
+ await runRequestedWorkspaceSync(
2656
+ crypto.randomUUID(),
2657
+ { type: "inbound_head", detail: message.commitHash },
2658
+ { skipVisibleMirror: true }
2659
+ );
2648
2660
  }
2649
2661
  return;
2650
2662
  }
@@ -2654,8 +2666,6 @@ async function startWorker(options) {
2654
2666
  await syncSessionArtifacts({
2655
2667
  baseUrl,
2656
2668
  token,
2657
- projectId: message.projectId,
2658
- branchName: message.branchName,
2659
2669
  sessionId: message.sessionId,
2660
2670
  artifactRoot
2661
2671
  });
@@ -2775,11 +2785,19 @@ async function startWorker(options) {
2775
2785
  });
2776
2786
  return;
2777
2787
  }
2778
- const manifest = manifestByProjectId.get(message.projectId);
2779
- const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
2780
- process.stdout.write(`[r5d-worker] pty ${message.ptyId}: ${message.projectId}/${message.branchName}
2788
+ try {
2789
+ const resolvedTarget = resolveMessageTarget(message.target);
2790
+ process.stdout.write(`[r5d-worker] pty ${message.ptyId}: ${describeWorkerSessionTarget(message.target)}
2781
2791
  `);
2782
- await openPty({ ws, message, projectId: message.projectId, baseUrl, token, projectRoot, syncRoot, planRoot, manifest });
2792
+ await openPty({ ws, message, resolvedTarget, planRoot });
2793
+ } catch (error) {
2794
+ sendWorkerMessage(ws, {
2795
+ type: "pty_error",
2796
+ requestId: message.requestId,
2797
+ ptyId: message.ptyId,
2798
+ error: error instanceof Error ? error.message : String(error)
2799
+ });
2800
+ }
2783
2801
  return;
2784
2802
  }
2785
2803
  if (message.type === "pty_input") {
@@ -2805,22 +2823,22 @@ async function startWorker(options) {
2805
2823
  });
2806
2824
  return;
2807
2825
  }
2808
- const manifest = manifestByProjectId.get(message.projectId);
2809
- const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
2810
- process.stdout.write(`[r5d-worker] exec ${message.runId}: ${message.argv.join(" ")}
2826
+ let result;
2827
+ try {
2828
+ const resolvedTarget = resolveMessageTarget(message.target);
2829
+ process.stdout.write(`[r5d-worker] exec ${message.runId}: ${message.argv.join(" ")}
2811
2830
  `);
2812
- const result = await executeCommand({
2813
- message,
2814
- projectId: message.projectId,
2815
- baseUrl,
2816
- token,
2817
- projectRoot,
2818
- syncRoot,
2819
- artifactRoot,
2820
- planRoot,
2821
- workspaceShadowRoot,
2822
- manifest
2823
- });
2831
+ result = await executeCommand({ message, resolvedTarget, baseUrl, token, artifactRoot, planRoot });
2832
+ } catch (error) {
2833
+ result = {
2834
+ type: "exec_result",
2835
+ requestId: message.requestId,
2836
+ stdout: "",
2837
+ stderr: "",
2838
+ exitCode: 1,
2839
+ error: error instanceof Error ? error.message : String(error)
2840
+ };
2841
+ }
2824
2842
  ws.send(JSON.stringify(result));
2825
2843
  return;
2826
2844
  }
@@ -2840,22 +2858,17 @@ async function startWorker(options) {
2840
2858
  runId: message.runId
2841
2859
  });
2842
2860
  try {
2843
- const manifest = manifestByProjectId.get(message.projectId);
2844
- const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
2861
+ const resolvedTarget = resolveMessageTarget(message.target);
2845
2862
  process.stdout.write(`[r5d-worker] exec_start ${message.runId}: ${message.argv.join(" ")}
2846
2863
  `);
2847
2864
  void executeStreamingCommand({
2848
2865
  ws,
2849
2866
  message,
2850
- projectId: message.projectId,
2867
+ resolvedTarget,
2851
2868
  baseUrl,
2852
2869
  token,
2853
- projectRoot,
2854
- syncRoot,
2855
2870
  artifactRoot,
2856
- planRoot,
2857
- workspaceShadowRoot,
2858
- manifest
2871
+ planRoot
2859
2872
  }).catch((error) => {
2860
2873
  sendWorkerMessage(ws, {
2861
2874
  type: "exec_start_error",
@@ -2876,18 +2889,14 @@ async function startWorker(options) {
2876
2889
  }
2877
2890
  if (message.type === "read" || message.type === "write" || message.type === "edit" || message.type === "grep" || message.type === "find" || message.type === "ls" || message.type === "view_file_bytes") {
2878
2891
  try {
2879
- const manifest = manifestByProjectId.get(message.projectId);
2880
- const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
2892
+ const resolvedTarget = resolveMessageTarget(message.target);
2881
2893
  const result = await executeOperation({
2882
2894
  message,
2883
- projectId: message.projectId,
2895
+ resolvedTarget,
2884
2896
  baseUrl,
2885
2897
  token,
2886
- projectRoot,
2887
- syncRoot,
2888
2898
  artifactRoot,
2889
- planRoot,
2890
- manifest
2899
+ planRoot
2891
2900
  });
2892
2901
  ws.send(
2893
2902
  JSON.stringify({
@@ -3014,6 +3023,7 @@ if (isCliEntrypoint()) {
3014
3023
  });
3015
3024
  }
3016
3025
  export {
3026
+ describeWorkerSessionTarget,
3017
3027
  editWorkerTextFile,
3018
3028
  ensureVisibleGitCheckout,
3019
3029
  findWorkerFiles,
@@ -3022,11 +3032,14 @@ export {
3022
3032
  isArtifactEnvPath,
3023
3033
  listWorkerDirectory,
3024
3034
  prepareArtifactEnvForShell,
3035
+ prepareBuiltInToolPathsForTarget,
3025
3036
  preparePlanEnvForShell,
3037
+ prepareShellEnvForTarget,
3026
3038
  readWorkerImageFile,
3027
3039
  readWorkerTextFile,
3028
3040
  resolveHostShell,
3029
3041
  resolveWorkerFilePath,
3042
+ resolveWorkerSessionTarget,
3030
3043
  syncSessionArtifacts,
3031
3044
  visibleCheckoutGitTestHarness,
3032
3045
  visibleCheckoutRemoteFor,