@ricsam/r5d-worker 0.0.46 → 0.0.48

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/README.md CHANGED
@@ -37,3 +37,5 @@ The agent runs commands through the virtual shell command:
37
37
  worker exec macos docker compose up -d
38
38
  worker exec macos bun test
39
39
  ```
40
+
41
+ When the connected `r5d-browser` requests a port forward, the worker opens each relayed connection only to `127.0.0.1` on the requested worker port. Browser-side and worker-side ports may differ. The worker never opens a public listener, and a disconnected worker leaves the browser's long-lived mapping unavailable until the same worker label reconnects.
package/dist/cjs/main.cjs CHANGED
@@ -43,6 +43,9 @@ __export(main_exports, {
43
43
  resolveHostShell: () => resolveHostShell,
44
44
  resolveWorkerFilePath: () => resolveWorkerFilePath,
45
45
  syncSessionArtifacts: () => syncSessionArtifacts,
46
+ visibleCheckoutGitTestHarness: () => visibleCheckoutGitTestHarness,
47
+ visibleCheckoutRemoteFor: () => visibleCheckoutRemoteFor,
48
+ workspaceSyncCheckoutTargets: () => workspaceSyncCheckoutTargets,
46
49
  writeWorkerTextFile: () => writeWorkerTextFile
47
50
  });
48
51
  module.exports = __toCommonJS(main_exports);
@@ -55,6 +58,7 @@ var import_cli_update = require("./cli-update.cjs");
55
58
  var import_git_identity = require("./git-identity.cjs");
56
59
  var import_heartbeat = require("./heartbeat.cjs");
57
60
  var import_process_tree = require("./process-tree.cjs");
61
+ var import_port_forward_client = require("./port-forward-client.cjs");
58
62
  var import_supervisor = require("./supervisor.cjs");
59
63
  var import_managed_paths = require("./managed-paths.cjs");
60
64
  var import_workspace_sync = require("./workspace-sync.cjs");
@@ -614,6 +618,14 @@ function validatePlanId(planId) {
614
618
  throw new Error(`Invalid plan id from server: ${planId}`);
615
619
  }
616
620
  }
621
+ const NON_RECURSIVE_GIT_CONFIG_ARGS = [
622
+ "-c",
623
+ "submodule.recurse=false",
624
+ "-c",
625
+ "fetch.recurseSubmodules=false",
626
+ "-c",
627
+ "push.recurseSubmodules=false"
628
+ ];
617
629
  function gitExtraHeaderConfigKey(extraHeaderUrl) {
618
630
  return `http.${normalizeBaseUrl(extraHeaderUrl)}/.extraHeader`;
619
631
  }
@@ -621,10 +633,23 @@ function gitAuthArgs(auth) {
621
633
  if (!auth) {
622
634
  return [];
623
635
  }
624
- return ["-c", `${gitExtraHeaderConfigKey(auth.extraHeaderUrl)}=${auth.header}`];
636
+ const scopedKey = gitExtraHeaderConfigKey(auth.extraHeaderUrl);
637
+ return ["-c", "http.extraHeader=", "-c", `${scopedKey}=`, "-c", `${scopedKey}=${auth.header}`];
638
+ }
639
+ const visibleCheckoutGitTestHarness = {
640
+ gitAuthArgs,
641
+ commandArgs: (args, auth) => ["git", ...NON_RECURSIVE_GIT_CONFIG_ARGS, ...gitAuthArgs(auth), ...args],
642
+ cloneArgs: (remoteUrl, branchPath) => ["clone", "--no-recurse-submodules", "--origin", "origin", remoteUrl, branchPath],
643
+ fetchArgs: (...args) => ["fetch", "--no-recurse-submodules", ...args]
644
+ };
645
+ function visibleCheckoutCloneArgs(remoteUrl, branchPath) {
646
+ return visibleCheckoutGitTestHarness.cloneArgs(remoteUrl, branchPath);
647
+ }
648
+ function visibleCheckoutFetchArgs(...args) {
649
+ return visibleCheckoutGitTestHarness.fetchArgs(...args);
625
650
  }
626
651
  function runGit(args, options = {}) {
627
- const command = ["git", ...gitAuthArgs(options.auth), ...args];
652
+ const command = visibleCheckoutGitTestHarness.commandArgs(args, options.auth);
628
653
  const result = Bun.spawnSync(command, {
629
654
  cwd: options.cwd,
630
655
  stdout: "pipe",
@@ -768,8 +793,28 @@ function resolveWorkerFilePath(branchPath, inputPath, builtInPaths) {
768
793
  function fallbackInternalRemoteUrl(baseUrl, projectId) {
769
794
  return new URL(`/git/${projectId}.git`, baseUrl).toString();
770
795
  }
771
- function githubRemoteUrlFor(baseUrl, projectId, manifest) {
772
- return manifest?.repoHttpUrl ?? fallbackInternalRemoteUrl(baseUrl, projectId);
796
+ function visibleCheckoutRemoteFor(input) {
797
+ const canonicalCheckout = input.manifest?.canonicalCheckouts?.find((checkout) => checkout.branchName === input.branchName);
798
+ const isCanonicalManifestBranch = Boolean(canonicalCheckout);
799
+ const usesCanonicalRemote = isCanonicalManifestBranch || !input.manifest?.repoHttpUrl;
800
+ if (usesCanonicalRemote) {
801
+ return {
802
+ remoteUrl: fallbackInternalRemoteUrl(input.baseUrl, input.projectId),
803
+ authHeader: `Authorization: Bearer ${input.token}`,
804
+ persistAuth: false,
805
+ reconcileExistingOrigin: isCanonicalManifestBranch,
806
+ requireRemoteBranch: isCanonicalManifestBranch,
807
+ requiredBaseCommit: canonicalCheckout?.scaffoldCommitHash ?? null
808
+ };
809
+ }
810
+ return {
811
+ remoteUrl: input.manifest?.repoHttpUrl ?? fallbackInternalRemoteUrl(input.baseUrl, input.projectId),
812
+ authHeader: input.manifest?.repoAuthHeader ?? null,
813
+ persistAuth: true,
814
+ reconcileExistingOrigin: Boolean(input.manifest?.repoHttpUrl),
815
+ requireRemoteBranch: false,
816
+ requiredBaseCommit: null
817
+ };
773
818
  }
774
819
  function gitExtraHeaderUrlForRemote(remoteUrl) {
775
820
  try {
@@ -798,6 +843,10 @@ function ensureOriginRemote(branchPath, remoteUrl) {
798
843
  runGit(["remote", "add", "origin", remoteUrl], { cwd: branchPath });
799
844
  }
800
845
  }
846
+ function originRemoteUrl(branchPath) {
847
+ if (!tryGit(["remote", "get-url", "origin"], { cwd: branchPath })) return null;
848
+ return runGit(["remote", "get-url", "origin"], { cwd: branchPath });
849
+ }
801
850
  function shellQuote(value) {
802
851
  return `'${value.replace(/'/g, "'\\''")}'`;
803
852
  }
@@ -866,6 +915,18 @@ function configureVisibleGitHubAuth(branchPath, remoteUrl, authHeader) {
866
915
  }
867
916
  runGit(["config", `${gitExtraHeaderConfigKey(gitExtraHeaderUrlForRemote(remoteUrl))}`, authHeader], { cwd: branchPath });
868
917
  }
918
+ function clearVisibleGitAuthentication(branchPath) {
919
+ let extraHeaderKeys = [];
920
+ try {
921
+ extraHeaderKeys = runGit(["config", "--local", "--name-only", "--get-regexp", "^http\\..*extraheader$"], { cwd: branchPath }).split(/\r?\n/).filter(Boolean);
922
+ } catch {
923
+ }
924
+ for (const key of extraHeaderKeys) {
925
+ tryGit(["config", "--local", "--unset-all", key], { cwd: branchPath });
926
+ }
927
+ runGit(["config", "--local", "--replace-all", "credential.helper", ""], { cwd: branchPath });
928
+ tryGit(["config", "--local", "--unset-all", "credential.useHttpPath"], { cwd: branchPath });
929
+ }
869
930
  function dockerConfigPath() {
870
931
  return import_node_path.default.join(import_node_os.default.homedir(), ".docker", "config.json");
871
932
  }
@@ -954,18 +1015,58 @@ function checkoutVisibleBranch(input) {
954
1015
  });
955
1016
  const clean = !hasWorktreeChanges(input.branchPath);
956
1017
  if (remoteBranchExists && clean) {
957
- runGit(["checkout", "-B", input.branchName, `origin/${input.branchName}`], { cwd: input.branchPath });
1018
+ runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName, `origin/${input.branchName}`], {
1019
+ cwd: input.branchPath
1020
+ });
958
1021
  return;
959
1022
  }
960
1023
  if (branchExists) {
961
- runGit(["checkout", input.branchName], { cwd: input.branchPath });
1024
+ runGit(["checkout", "--no-recurse-submodules", input.branchName], { cwd: input.branchPath });
962
1025
  return;
963
1026
  }
964
1027
  if (defaultRemoteExists) {
965
- runGit(["checkout", "-B", input.branchName, `origin/${input.defaultBranch}`], { cwd: input.branchPath });
1028
+ runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName, `origin/${input.defaultBranch}`], {
1029
+ cwd: input.branchPath
1030
+ });
966
1031
  return;
967
1032
  }
968
- runGit(["checkout", "-B", input.branchName], { cwd: input.branchPath });
1033
+ runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName], { cwd: input.branchPath });
1034
+ }
1035
+ function migrateExistingCheckoutToRequiredRemote(input) {
1036
+ if (originRemoteUrl(input.branchPath) === input.remoteUrl) return;
1037
+ if (hasWorktreeChanges(input.branchPath)) {
1038
+ throw new Error(`Cannot migrate dirty checkout for ${input.branchName} to its canonical remote`);
1039
+ }
1040
+ runGit(
1041
+ visibleCheckoutFetchArgs("--no-tags", input.remoteUrl, `+refs/heads/${input.branchName}:refs/remotes/origin/${input.branchName}`),
1042
+ {
1043
+ cwd: input.branchPath,
1044
+ auth: input.remoteAuth
1045
+ }
1046
+ );
1047
+ const canonicalRemoteRef = `refs/remotes/origin/${input.branchName}`;
1048
+ if (!tryGit(["show-ref", "--verify", "--quiet", canonicalRemoteRef], { cwd: input.branchPath })) {
1049
+ throw new Error(`Canonical branch ${input.branchName} is unavailable during checkout migration`);
1050
+ }
1051
+ if (input.requiredBaseCommit && (!tryGit(["cat-file", "-e", `${input.requiredBaseCommit}^{commit}`], { cwd: input.branchPath }) || !tryGit(["merge-base", "--is-ancestor", input.requiredBaseCommit, canonicalRemoteRef], { cwd: input.branchPath }))) {
1052
+ throw new Error(`Canonical branch ${input.branchName} is not derived from required scaffold ${input.requiredBaseCommit}`);
1053
+ }
1054
+ if (tryGit(["rev-parse", "--verify", "HEAD"], { cwd: input.branchPath })) {
1055
+ const previousHead = runGit(["rev-parse", "HEAD"], { cwd: input.branchPath });
1056
+ runGit(["update-ref", `refs/r5d/pre-canonical-checkout/${previousHead}`, previousHead], { cwd: input.branchPath });
1057
+ }
1058
+ ensureOriginRemote(input.branchPath, input.remoteUrl);
1059
+ runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName, `origin/${input.branchName}`], {
1060
+ cwd: input.branchPath
1061
+ });
1062
+ }
1063
+ function assertCheckoutDerivedFromRequiredBase(input) {
1064
+ const remoteRevision = `refs/remotes/origin/${input.branchName}`;
1065
+ for (const revision of [remoteRevision, "HEAD"]) {
1066
+ if (!tryGit(["cat-file", "-e", `${input.requiredBaseCommit}^{commit}`], { cwd: input.branchPath }) || !tryGit(["merge-base", "--is-ancestor", input.requiredBaseCommit, revision], { cwd: input.branchPath })) {
1067
+ throw new Error(`Canonical checkout ${input.branchName} is not derived from required scaffold ${input.requiredBaseCommit}`);
1068
+ }
1069
+ }
969
1070
  }
970
1071
  function ensureVisibleGitCheckout(input) {
971
1072
  validateBranchName(input.branchName);
@@ -975,40 +1076,114 @@ function ensureVisibleGitCheckout(input) {
975
1076
  if (createdCheckout) {
976
1077
  import_node_fs.default.rmSync(branchPath, { recursive: true, force: true });
977
1078
  import_node_fs.default.mkdirSync(import_node_path.default.dirname(branchPath), { recursive: true });
978
- if (!tryGit(["clone", "--origin", "origin", input.githubRemoteUrl, branchPath], { auth: input.githubAuth })) {
1079
+ const cloned = tryGit(visibleCheckoutCloneArgs(input.remoteUrl, branchPath), {
1080
+ auth: input.remoteAuth
1081
+ });
1082
+ if (!cloned && input.requireRemoteBranch) {
1083
+ import_node_fs.default.rmSync(branchPath, { recursive: true, force: true });
1084
+ throw new Error(`Failed to clone canonical checkout for branch ${input.branchName}`);
1085
+ }
1086
+ if (!cloned) {
979
1087
  import_node_fs.default.mkdirSync(branchPath, { recursive: true });
980
1088
  runGit(["init"], { cwd: branchPath });
981
1089
  }
982
- ensureOriginRemote(branchPath, input.githubRemoteUrl);
983
- configureVisibleGitHubAuth(branchPath, input.githubRemoteUrl, input.githubAuthHeader);
1090
+ ensureOriginRemote(branchPath, input.remoteUrl);
1091
+ if (input.clearPersistentAuth) clearVisibleGitAuthentication(branchPath);
1092
+ else configureVisibleGitHubAuth(branchPath, input.remoteUrl, input.persistentAuthHeader);
984
1093
  (0, import_git_identity.configureVisibleGitIdentity)(branchPath, visibleGitIdentity, runGit);
985
- tryGit(["fetch", "origin", "--prune"], { cwd: branchPath, auth: input.githubAuth });
1094
+ tryGit(visibleCheckoutFetchArgs("origin", "--prune"), { cwd: branchPath, auth: input.remoteAuth });
1095
+ if (input.requireRemoteBranch && !tryGit(["show-ref", "--verify", "--quiet", `refs/remotes/origin/${input.branchName}`], { cwd: branchPath })) {
1096
+ import_node_fs.default.rmSync(branchPath, { recursive: true, force: true });
1097
+ throw new Error(`Canonical branch ${input.branchName} is unavailable after clone`);
1098
+ }
986
1099
  checkoutVisibleBranch({ branchPath, branchName: input.branchName, defaultBranch: input.defaultBranch });
1100
+ if (input.requiredBaseCommit) {
1101
+ try {
1102
+ assertCheckoutDerivedFromRequiredBase({
1103
+ branchPath,
1104
+ branchName: input.branchName,
1105
+ requiredBaseCommit: input.requiredBaseCommit
1106
+ });
1107
+ } catch (error) {
1108
+ import_node_fs.default.rmSync(branchPath, { recursive: true, force: true });
1109
+ throw error;
1110
+ }
1111
+ }
987
1112
  return branchPath;
988
1113
  }
1114
+ if (input.requireRemoteBranch) {
1115
+ if (input.allowRequiredRemoteMigration === false && originRemoteUrl(branchPath) !== input.remoteUrl) {
1116
+ throw new Error(`Canonical checkout ${input.branchName} no longer uses its required remote`);
1117
+ }
1118
+ migrateExistingCheckoutToRequiredRemote({
1119
+ branchPath,
1120
+ branchName: input.branchName,
1121
+ remoteUrl: input.remoteUrl,
1122
+ remoteAuth: input.remoteAuth,
1123
+ requiredBaseCommit: input.requiredBaseCommit
1124
+ });
1125
+ runGit(
1126
+ visibleCheckoutFetchArgs("--no-tags", input.remoteUrl, `+refs/heads/${input.branchName}:refs/remotes/origin/${input.branchName}`),
1127
+ {
1128
+ cwd: branchPath,
1129
+ auth: input.remoteAuth
1130
+ }
1131
+ );
1132
+ }
989
1133
  if (input.reconcileExistingOrigin) {
990
- ensureOriginRemote(branchPath, input.githubRemoteUrl);
1134
+ ensureOriginRemote(branchPath, input.remoteUrl);
991
1135
  }
992
- configureVisibleGitHubAuth(branchPath, input.githubRemoteUrl, input.githubAuthHeader);
1136
+ if (input.requireRemoteBranch && !tryGit(["show-ref", "--verify", "--quiet", `refs/remotes/origin/${input.branchName}`], { cwd: branchPath })) {
1137
+ throw new Error(`Existing checkout for ${input.branchName} does not contain its canonical remote branch`);
1138
+ }
1139
+ if (input.requiredBaseCommit) {
1140
+ assertCheckoutDerivedFromRequiredBase({
1141
+ branchPath,
1142
+ branchName: input.branchName,
1143
+ requiredBaseCommit: input.requiredBaseCommit
1144
+ });
1145
+ }
1146
+ if (input.clearPersistentAuth) clearVisibleGitAuthentication(branchPath);
1147
+ else configureVisibleGitHubAuth(branchPath, input.remoteUrl, input.persistentAuthHeader);
993
1148
  (0, import_git_identity.configureVisibleGitIdentity)(branchPath, visibleGitIdentity, runGit);
994
1149
  return branchPath;
995
1150
  }
996
1151
  function ensureBranchWorkspace(input) {
997
1152
  const defaultBranch = input.manifest?.defaultBranch || "main";
998
- const githubRemoteUrl = githubRemoteUrlFor(input.baseUrl, input.projectId, input.manifest);
999
- const githubAuth = gitAuthForRemote(githubRemoteUrl, input.manifest?.repoAuthHeader);
1153
+ const remote = visibleCheckoutRemoteFor(input);
1000
1154
  return {
1001
1155
  branchPath: ensureVisibleGitCheckout({
1002
1156
  projectRoot: input.projectRoot,
1003
1157
  branchName: input.branchName,
1004
- githubRemoteUrl,
1005
- githubAuth,
1006
- githubAuthHeader: input.manifest?.repoAuthHeader ?? null,
1158
+ remoteUrl: remote.remoteUrl,
1159
+ remoteAuth: gitAuthForRemote(remote.remoteUrl, remote.authHeader),
1160
+ persistentAuthHeader: remote.persistAuth ? remote.authHeader : null,
1007
1161
  defaultBranch,
1008
- reconcileExistingOrigin: Boolean(input.manifest?.repoHttpUrl)
1162
+ reconcileExistingOrigin: remote.reconcileExistingOrigin,
1163
+ requireRemoteBranch: remote.requireRemoteBranch,
1164
+ requiredBaseCommit: remote.requiredBaseCommit,
1165
+ clearPersistentAuth: !remote.persistAuth
1009
1166
  })
1010
1167
  };
1011
1168
  }
1169
+ function workspaceSyncCheckoutTargets(input) {
1170
+ const selected = /* @__PURE__ */ new Map();
1171
+ const add = (target) => {
1172
+ selected.set(`${target.projectId}\0${target.branchName}`, target);
1173
+ };
1174
+ if (input.canonicalCheckoutOnly) {
1175
+ add(input.canonicalCheckoutOnly);
1176
+ } else if (input.includeAllManifestCheckouts) {
1177
+ for (const project of input.projects) {
1178
+ for (const branchName of project.branches) add({ projectId: project.projectId, branchName });
1179
+ }
1180
+ } else {
1181
+ for (const target of input.pendingTargets) add(target);
1182
+ }
1183
+ return [...selected.values()].sort(
1184
+ (left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
1185
+ );
1186
+ }
1012
1187
  function resolveCommandCwd(branchPath, cwd) {
1013
1188
  if (!cwd) {
1014
1189
  return branchPath;
@@ -2232,6 +2407,7 @@ async function startWorker(options) {
2232
2407
  let reloadAfterClose = false;
2233
2408
  const workspaceSyncInput = (trigger, overrides = {}) => {
2234
2409
  if (!workspaceRemoteUrl) throw new Error("Worker workspace manifest has not been received");
2410
+ const projects = (0, import_workspace_sync.workspaceProjectsForSync)([...manifestByProjectId.values()], trigger);
2235
2411
  return {
2236
2412
  workerLabel: label,
2237
2413
  remoteUrl: workspaceRemoteUrl,
@@ -2239,7 +2415,7 @@ async function startWorker(options) {
2239
2415
  projectsRoot,
2240
2416
  plansRoot: planRoot,
2241
2417
  shadowRoot: workspaceShadowRoot,
2242
- projects: [...manifestByProjectId.values()],
2418
+ projects,
2243
2419
  trigger,
2244
2420
  ...overrides
2245
2421
  };
@@ -2251,7 +2427,7 @@ async function startWorker(options) {
2251
2427
  const allManifestCheckouts = () => [...manifestByProjectId.values()].flatMap(
2252
2428
  (manifest) => manifest.branches.map((branchName) => ({ projectId: manifest.projectId, branchName }))
2253
2429
  );
2254
- const ensureVisibleWorkspaceCheckouts = (targets) => {
2430
+ const ensureVisibleWorkspaceCheckouts = (targets, options2 = {}) => {
2255
2431
  const created = [];
2256
2432
  for (const target of targets) {
2257
2433
  const manifest = manifestByProjectId.get(target.projectId);
@@ -2263,15 +2439,25 @@ async function startWorker(options) {
2263
2439
  const projectRoot = projectRootFor(projectsRoot, manifest.projectId, manifestByProjectId);
2264
2440
  const branchPath = import_node_path.default.join(projectRoot, target.branchName);
2265
2441
  const existed = hasNormalVisibleGitDir(branchPath);
2266
- const githubRemoteUrl = githubRemoteUrlFor(baseUrl, manifest.projectId, manifest);
2442
+ const remote = visibleCheckoutRemoteFor({
2443
+ baseUrl,
2444
+ token,
2445
+ projectId: manifest.projectId,
2446
+ branchName: target.branchName,
2447
+ manifest
2448
+ });
2267
2449
  ensureVisibleGitCheckout({
2268
2450
  projectRoot,
2269
2451
  branchName: target.branchName,
2270
- githubRemoteUrl,
2271
- githubAuth: gitAuthForRemote(githubRemoteUrl, manifest.repoAuthHeader),
2272
- githubAuthHeader: manifest.repoAuthHeader,
2452
+ remoteUrl: remote.remoteUrl,
2453
+ remoteAuth: gitAuthForRemote(remote.remoteUrl, remote.authHeader),
2454
+ persistentAuthHeader: remote.persistAuth ? remote.authHeader : null,
2273
2455
  defaultBranch: manifest.defaultBranch || "main",
2274
- reconcileExistingOrigin: Boolean(manifest.repoHttpUrl)
2456
+ reconcileExistingOrigin: remote.reconcileExistingOrigin,
2457
+ requireRemoteBranch: remote.requireRemoteBranch,
2458
+ requiredBaseCommit: remote.requiredBaseCommit,
2459
+ allowRequiredRemoteMigration: !(options2.strictCanonicalRevalidation && remote.requireRemoteBranch),
2460
+ clearPersistentAuth: !remote.persistAuth
2275
2461
  });
2276
2462
  if (!existed) created.push(target);
2277
2463
  }
@@ -2283,15 +2469,30 @@ async function startWorker(options) {
2283
2469
  let pendingTargets = [];
2284
2470
  const result = await workspaceSyncSingleFlight.runPrepared(() => {
2285
2471
  const shouldEnsureCheckouts = !overrides.skipVisibleMirror && !overrides.resetToCanonical;
2472
+ const syncProjects = (0, import_workspace_sync.workspaceProjectsForSync)([...manifestByProjectId.values()], trigger);
2473
+ const allowedCheckoutKeys = new Set(
2474
+ syncProjects.flatMap((project) => project.branches.map((branchName) => manifestCheckoutKey(project.projectId, branchName)))
2475
+ );
2476
+ const scopedPendingTargets = [...pendingManifestCheckouts.values()].filter(
2477
+ (target) => allowedCheckoutKeys.has(manifestCheckoutKey(target.projectId, target.branchName))
2478
+ );
2286
2479
  let checkoutTargets = [];
2287
2480
  if (shouldEnsureCheckouts) {
2288
- checkoutTargets = trigger.type === "connect" ? allManifestCheckouts() : [...pendingManifestCheckouts.values()];
2481
+ checkoutTargets = workspaceSyncCheckoutTargets({
2482
+ projects: syncProjects,
2483
+ pendingTargets: scopedPendingTargets,
2484
+ includeAllManifestCheckouts: trigger.type === "connect",
2485
+ ...trigger.canonicalCheckoutOnly && trigger.projectId && trigger.branchName ? { canonicalCheckoutOnly: { projectId: trigger.projectId, branchName: trigger.branchName } } : {}
2486
+ });
2289
2487
  }
2290
- pendingTargets = shouldEnsureCheckouts ? [...pendingManifestCheckouts.values()] : [];
2291
- const createdCheckouts = ensureVisibleWorkspaceCheckouts(checkoutTargets);
2488
+ pendingTargets = shouldEnsureCheckouts ? scopedPendingTargets : [];
2489
+ const createdCheckouts = ensureVisibleWorkspaceCheckouts(checkoutTargets, {
2490
+ strictCanonicalRevalidation: true
2491
+ });
2292
2492
  const newVisibleCheckoutByKey = /* @__PURE__ */ new Map();
2293
2493
  for (const target of [...overrides.newVisibleCheckouts ?? [], ...createdCheckouts, ...pendingTargets]) {
2294
- newVisibleCheckoutByKey.set(manifestCheckoutKey(target.projectId, target.branchName), target);
2494
+ const key = manifestCheckoutKey(target.projectId, target.branchName);
2495
+ if (allowedCheckoutKeys.has(key)) newVisibleCheckoutByKey.set(key, target);
2295
2496
  }
2296
2497
  const newVisibleCheckouts = [...newVisibleCheckoutByKey.values()];
2297
2498
  return workspaceSyncInput(trigger, {
@@ -2367,7 +2568,9 @@ async function startWorker(options) {
2367
2568
  version: getWorkerVersion(),
2368
2569
  r5dctlVersion: (0, import_cli_update.readInstalledCliVersion)("r5dctl"),
2369
2570
  capabilities: {
2370
- updateClis: true
2571
+ updateClis: true,
2572
+ canonicalResolverCheckout: true,
2573
+ browserPortForwarding: true
2371
2574
  },
2372
2575
  projectRoot: projectsRoot,
2373
2576
  artifactRoot,
@@ -2427,7 +2630,15 @@ async function startWorker(options) {
2427
2630
  if (!workspaceRemoteUrl || activeWorkspaceIncidentId || cliUpdateInProgress || periodicWorkspaceScanInFlight) return;
2428
2631
  periodicWorkspaceScanInFlight = true;
2429
2632
  void (async () => {
2430
- if (pendingManifestCheckouts.size > 0) {
2633
+ const genericCheckoutKeys = new Set(
2634
+ (0, import_workspace_sync.workspaceProjectsForSync)([...manifestByProjectId.values()], { type: "periodic" }).flatMap(
2635
+ (project) => project.branches.map((branchName) => manifestCheckoutKey(project.projectId, branchName))
2636
+ )
2637
+ );
2638
+ const hasPendingGenericCheckout = [...pendingManifestCheckouts.values()].some(
2639
+ (target) => genericCheckoutKeys.has(manifestCheckoutKey(target.projectId, target.branchName))
2640
+ );
2641
+ if (hasPendingGenericCheckout) {
2431
2642
  previousPeriodicFingerprint = null;
2432
2643
  await runRequestedWorkspaceSync(crypto.randomUUID(), {
2433
2644
  type: "periodic",
@@ -2512,6 +2723,17 @@ async function startWorker(options) {
2512
2723
  pendingProcessTerminals.delete(message.runId);
2513
2724
  return;
2514
2725
  }
2726
+ if (message.type === "port_forward_connect") {
2727
+ (0, import_port_forward_client.openWorkerPortForwardRelay)({
2728
+ baseUrl,
2729
+ token,
2730
+ label,
2731
+ forwardId: message.forwardId,
2732
+ relayConnectionId: message.relayConnectionId,
2733
+ workerPort: message.workerPort
2734
+ });
2735
+ return;
2736
+ }
2515
2737
  if (message.type === "update_clis") {
2516
2738
  if (cliUpdateInProgress) {
2517
2739
  sendWorkerMessage(ws, {
@@ -2845,5 +3067,8 @@ if (isCliEntrypoint()) {
2845
3067
  resolveHostShell,
2846
3068
  resolveWorkerFilePath,
2847
3069
  syncSessionArtifacts,
3070
+ visibleCheckoutGitTestHarness,
3071
+ visibleCheckoutRemoteFor,
3072
+ workspaceSyncCheckoutTargets,
2848
3073
  writeWorkerTextFile
2849
3074
  });
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.46",
3
+ "version": "0.0.48",
4
4
  "type": "commonjs"
5
5
  }
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var port_forward_client_exports = {};
30
+ __export(port_forward_client_exports, {
31
+ openWorkerPortForwardRelay: () => openWorkerPortForwardRelay
32
+ });
33
+ module.exports = __toCommonJS(port_forward_client_exports);
34
+ var import_node_net = __toESM(require("node:net"), 1);
35
+ var import_ws = __toESM(require("ws"), 1);
36
+ const RELAY_FRAME_BYTES = 64 * 1024;
37
+ const RELAY_PAUSE_BYTES = 1024 * 1024;
38
+ const RELAY_RESUME_BYTES = 256 * 1024;
39
+ function relayUrl(baseUrl, label, forwardId, relayConnectionId) {
40
+ const url = new URL("/worker/port-forward/ws", baseUrl);
41
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
42
+ url.searchParams.set("label", label);
43
+ url.searchParams.set("forward", forwardId);
44
+ url.searchParams.set("connection", relayConnectionId);
45
+ return url.toString();
46
+ }
47
+ function rawDataBuffer(data) {
48
+ if (Buffer.isBuffer(data)) return data;
49
+ if (Array.isArray(data)) return Buffer.concat(data);
50
+ return Buffer.from(data);
51
+ }
52
+ function openWorkerPortForwardRelay(options) {
53
+ const socket = import_node_net.default.createConnection({ host: "127.0.0.1", port: options.workerPort, allowHalfOpen: true });
54
+ socket.pause();
55
+ socket.setNoDelay(true);
56
+ const relay = new import_ws.default(relayUrl(options.baseUrl, options.label, options.forwardId, options.relayConnectionId), {
57
+ headers: { Authorization: `Bearer ${options.token}` },
58
+ perMessageDeflate: false
59
+ });
60
+ let relayReady = false;
61
+ let targetReady = false;
62
+ const pendingChunks = [];
63
+ let pendingBytes = 0;
64
+ let closing = false;
65
+ let resumeTimer;
66
+ const maybeResume = () => {
67
+ if (relayReady && targetReady && !resumeTimer) socket.resume();
68
+ };
69
+ const cleanup = () => {
70
+ if (closing) return;
71
+ closing = true;
72
+ if (resumeTimer) clearInterval(resumeTimer);
73
+ socket.destroy();
74
+ if (relay.readyState === import_ws.default.OPEN || relay.readyState === import_ws.default.CONNECTING) relay.close();
75
+ };
76
+ const sendError = (message) => {
77
+ if (relay.readyState === import_ws.default.OPEN) relay.send(JSON.stringify({ type: "error", error: message }));
78
+ cleanup();
79
+ };
80
+ const waitForRelayDrain = () => {
81
+ if (resumeTimer || relay.bufferedAmount <= RELAY_PAUSE_BYTES) return;
82
+ socket.pause();
83
+ resumeTimer = setInterval(() => {
84
+ if (relay.readyState !== import_ws.default.OPEN) return cleanup();
85
+ if (relay.bufferedAmount > RELAY_RESUME_BYTES) return;
86
+ clearInterval(resumeTimer);
87
+ resumeTimer = void 0;
88
+ maybeResume();
89
+ }, 10);
90
+ };
91
+ const sendChunk = (chunk) => {
92
+ for (let offset = 0; offset < chunk.byteLength; offset += RELAY_FRAME_BYTES) {
93
+ relay.send(chunk.subarray(offset, Math.min(chunk.byteLength, offset + RELAY_FRAME_BYTES)), { binary: true });
94
+ }
95
+ waitForRelayDrain();
96
+ };
97
+ socket.once("connect", () => {
98
+ targetReady = true;
99
+ maybeResume();
100
+ });
101
+ socket.on("data", (chunk) => {
102
+ if (!relayReady || relay.readyState !== import_ws.default.OPEN) {
103
+ pendingBytes += chunk.byteLength;
104
+ if (pendingBytes > RELAY_PAUSE_BYTES) return cleanup();
105
+ pendingChunks.push(Buffer.from(chunk));
106
+ socket.pause();
107
+ return;
108
+ }
109
+ sendChunk(chunk);
110
+ });
111
+ socket.on("end", () => {
112
+ if (relay.readyState === import_ws.default.OPEN) relay.send(JSON.stringify({ type: "end" }));
113
+ });
114
+ socket.on("error", (error) => sendError(`Could not connect to 127.0.0.1:${options.workerPort}: ${error.message}`));
115
+ socket.on("close", cleanup);
116
+ relay.on("message", (data, isBinary) => {
117
+ if (!isBinary) {
118
+ let control;
119
+ try {
120
+ control = JSON.parse(rawDataBuffer(data).toString("utf8"));
121
+ } catch {
122
+ return cleanup();
123
+ }
124
+ if (control.type === "ready") {
125
+ relayReady = true;
126
+ for (const chunk of pendingChunks.splice(0)) sendChunk(chunk);
127
+ pendingBytes = 0;
128
+ maybeResume();
129
+ } else if (control.type === "end") {
130
+ socket.end();
131
+ } else if (control.type === "error") {
132
+ socket.destroy(new Error(control.error || "Port-forward relay failed."));
133
+ }
134
+ return;
135
+ }
136
+ if (!socket.write(rawDataBuffer(data))) {
137
+ relay.pause();
138
+ socket.once("drain", () => relay.resume());
139
+ }
140
+ });
141
+ relay.once("error", cleanup);
142
+ relay.once("close", cleanup);
143
+ socket.pause();
144
+ }
145
+ // Annotate the CommonJS export names for ESM import in node:
146
+ 0 && (module.exports = {
147
+ openWorkerPortForwardRelay
148
+ });