@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/dist/mjs/main.mjs CHANGED
@@ -8,6 +8,7 @@ import { installCliUpdate, readInstalledCliVersion } from "./cli-update.mjs";
8
8
  import { configureVisibleGitIdentity } from "./git-identity.mjs";
9
9
  import { hasWorkerHeartbeatTimedOut, WORKER_HEARTBEAT_INTERVAL_MS } from "./heartbeat.mjs";
10
10
  import { terminateProcessTree } from "./process-tree.mjs";
11
+ import { openWorkerPortForwardRelay } from "./port-forward-client.mjs";
11
12
  import {
12
13
  isRetryableWorkerServerStatus,
13
14
  superviseWorkerRuntime,
@@ -21,7 +22,8 @@ import {
21
22
  WORKSPACE_PERIODIC_SCAN_INTERVAL_MS,
22
23
  WorkspaceSyncSingleFlight,
23
24
  calculateWorkspaceDiffFingerprint,
24
- synchronizeWorkspace
25
+ synchronizeWorkspace,
26
+ workspaceProjectsForSync
25
27
  } from "./workspace-sync.mjs";
26
28
  class WorkerServerUnavailableError extends Error {
27
29
  name = "WorkerServerUnavailableError";
@@ -578,6 +580,14 @@ function validatePlanId(planId) {
578
580
  throw new Error(`Invalid plan id from server: ${planId}`);
579
581
  }
580
582
  }
583
+ const NON_RECURSIVE_GIT_CONFIG_ARGS = [
584
+ "-c",
585
+ "submodule.recurse=false",
586
+ "-c",
587
+ "fetch.recurseSubmodules=false",
588
+ "-c",
589
+ "push.recurseSubmodules=false"
590
+ ];
581
591
  function gitExtraHeaderConfigKey(extraHeaderUrl) {
582
592
  return `http.${normalizeBaseUrl(extraHeaderUrl)}/.extraHeader`;
583
593
  }
@@ -585,10 +595,23 @@ function gitAuthArgs(auth) {
585
595
  if (!auth) {
586
596
  return [];
587
597
  }
588
- return ["-c", `${gitExtraHeaderConfigKey(auth.extraHeaderUrl)}=${auth.header}`];
598
+ const scopedKey = gitExtraHeaderConfigKey(auth.extraHeaderUrl);
599
+ return ["-c", "http.extraHeader=", "-c", `${scopedKey}=`, "-c", `${scopedKey}=${auth.header}`];
600
+ }
601
+ const visibleCheckoutGitTestHarness = {
602
+ gitAuthArgs,
603
+ commandArgs: (args, auth) => ["git", ...NON_RECURSIVE_GIT_CONFIG_ARGS, ...gitAuthArgs(auth), ...args],
604
+ cloneArgs: (remoteUrl, branchPath) => ["clone", "--no-recurse-submodules", "--origin", "origin", remoteUrl, branchPath],
605
+ fetchArgs: (...args) => ["fetch", "--no-recurse-submodules", ...args]
606
+ };
607
+ function visibleCheckoutCloneArgs(remoteUrl, branchPath) {
608
+ return visibleCheckoutGitTestHarness.cloneArgs(remoteUrl, branchPath);
609
+ }
610
+ function visibleCheckoutFetchArgs(...args) {
611
+ return visibleCheckoutGitTestHarness.fetchArgs(...args);
589
612
  }
590
613
  function runGit(args, options = {}) {
591
- const command = ["git", ...gitAuthArgs(options.auth), ...args];
614
+ const command = visibleCheckoutGitTestHarness.commandArgs(args, options.auth);
592
615
  const result = Bun.spawnSync(command, {
593
616
  cwd: options.cwd,
594
617
  stdout: "pipe",
@@ -732,8 +755,28 @@ function resolveWorkerFilePath(branchPath, inputPath, builtInPaths) {
732
755
  function fallbackInternalRemoteUrl(baseUrl, projectId) {
733
756
  return new URL(`/git/${projectId}.git`, baseUrl).toString();
734
757
  }
735
- function githubRemoteUrlFor(baseUrl, projectId, manifest) {
736
- return manifest?.repoHttpUrl ?? fallbackInternalRemoteUrl(baseUrl, projectId);
758
+ function visibleCheckoutRemoteFor(input) {
759
+ const canonicalCheckout = input.manifest?.canonicalCheckouts?.find((checkout) => checkout.branchName === input.branchName);
760
+ const isCanonicalManifestBranch = Boolean(canonicalCheckout);
761
+ const usesCanonicalRemote = isCanonicalManifestBranch || !input.manifest?.repoHttpUrl;
762
+ if (usesCanonicalRemote) {
763
+ return {
764
+ remoteUrl: fallbackInternalRemoteUrl(input.baseUrl, input.projectId),
765
+ authHeader: `Authorization: Bearer ${input.token}`,
766
+ persistAuth: false,
767
+ reconcileExistingOrigin: isCanonicalManifestBranch,
768
+ requireRemoteBranch: isCanonicalManifestBranch,
769
+ requiredBaseCommit: canonicalCheckout?.scaffoldCommitHash ?? null
770
+ };
771
+ }
772
+ return {
773
+ remoteUrl: input.manifest?.repoHttpUrl ?? fallbackInternalRemoteUrl(input.baseUrl, input.projectId),
774
+ authHeader: input.manifest?.repoAuthHeader ?? null,
775
+ persistAuth: true,
776
+ reconcileExistingOrigin: Boolean(input.manifest?.repoHttpUrl),
777
+ requireRemoteBranch: false,
778
+ requiredBaseCommit: null
779
+ };
737
780
  }
738
781
  function gitExtraHeaderUrlForRemote(remoteUrl) {
739
782
  try {
@@ -762,6 +805,10 @@ function ensureOriginRemote(branchPath, remoteUrl) {
762
805
  runGit(["remote", "add", "origin", remoteUrl], { cwd: branchPath });
763
806
  }
764
807
  }
808
+ function originRemoteUrl(branchPath) {
809
+ if (!tryGit(["remote", "get-url", "origin"], { cwd: branchPath })) return null;
810
+ return runGit(["remote", "get-url", "origin"], { cwd: branchPath });
811
+ }
765
812
  function shellQuote(value) {
766
813
  return `'${value.replace(/'/g, "'\\''")}'`;
767
814
  }
@@ -830,6 +877,18 @@ function configureVisibleGitHubAuth(branchPath, remoteUrl, authHeader) {
830
877
  }
831
878
  runGit(["config", `${gitExtraHeaderConfigKey(gitExtraHeaderUrlForRemote(remoteUrl))}`, authHeader], { cwd: branchPath });
832
879
  }
880
+ function clearVisibleGitAuthentication(branchPath) {
881
+ let extraHeaderKeys = [];
882
+ try {
883
+ extraHeaderKeys = runGit(["config", "--local", "--name-only", "--get-regexp", "^http\\..*extraheader$"], { cwd: branchPath }).split(/\r?\n/).filter(Boolean);
884
+ } catch {
885
+ }
886
+ for (const key of extraHeaderKeys) {
887
+ tryGit(["config", "--local", "--unset-all", key], { cwd: branchPath });
888
+ }
889
+ runGit(["config", "--local", "--replace-all", "credential.helper", ""], { cwd: branchPath });
890
+ tryGit(["config", "--local", "--unset-all", "credential.useHttpPath"], { cwd: branchPath });
891
+ }
833
892
  function dockerConfigPath() {
834
893
  return path.join(os.homedir(), ".docker", "config.json");
835
894
  }
@@ -918,18 +977,58 @@ function checkoutVisibleBranch(input) {
918
977
  });
919
978
  const clean = !hasWorktreeChanges(input.branchPath);
920
979
  if (remoteBranchExists && clean) {
921
- runGit(["checkout", "-B", input.branchName, `origin/${input.branchName}`], { cwd: input.branchPath });
980
+ runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName, `origin/${input.branchName}`], {
981
+ cwd: input.branchPath
982
+ });
922
983
  return;
923
984
  }
924
985
  if (branchExists) {
925
- runGit(["checkout", input.branchName], { cwd: input.branchPath });
986
+ runGit(["checkout", "--no-recurse-submodules", input.branchName], { cwd: input.branchPath });
926
987
  return;
927
988
  }
928
989
  if (defaultRemoteExists) {
929
- runGit(["checkout", "-B", input.branchName, `origin/${input.defaultBranch}`], { cwd: input.branchPath });
990
+ runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName, `origin/${input.defaultBranch}`], {
991
+ cwd: input.branchPath
992
+ });
930
993
  return;
931
994
  }
932
- runGit(["checkout", "-B", input.branchName], { cwd: input.branchPath });
995
+ runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName], { cwd: input.branchPath });
996
+ }
997
+ function migrateExistingCheckoutToRequiredRemote(input) {
998
+ if (originRemoteUrl(input.branchPath) === input.remoteUrl) return;
999
+ if (hasWorktreeChanges(input.branchPath)) {
1000
+ throw new Error(`Cannot migrate dirty checkout for ${input.branchName} to its canonical remote`);
1001
+ }
1002
+ runGit(
1003
+ visibleCheckoutFetchArgs("--no-tags", input.remoteUrl, `+refs/heads/${input.branchName}:refs/remotes/origin/${input.branchName}`),
1004
+ {
1005
+ cwd: input.branchPath,
1006
+ auth: input.remoteAuth
1007
+ }
1008
+ );
1009
+ const canonicalRemoteRef = `refs/remotes/origin/${input.branchName}`;
1010
+ if (!tryGit(["show-ref", "--verify", "--quiet", canonicalRemoteRef], { cwd: input.branchPath })) {
1011
+ throw new Error(`Canonical branch ${input.branchName} is unavailable during checkout migration`);
1012
+ }
1013
+ if (input.requiredBaseCommit && (!tryGit(["cat-file", "-e", `${input.requiredBaseCommit}^{commit}`], { cwd: input.branchPath }) || !tryGit(["merge-base", "--is-ancestor", input.requiredBaseCommit, canonicalRemoteRef], { cwd: input.branchPath }))) {
1014
+ throw new Error(`Canonical branch ${input.branchName} is not derived from required scaffold ${input.requiredBaseCommit}`);
1015
+ }
1016
+ if (tryGit(["rev-parse", "--verify", "HEAD"], { cwd: input.branchPath })) {
1017
+ const previousHead = runGit(["rev-parse", "HEAD"], { cwd: input.branchPath });
1018
+ runGit(["update-ref", `refs/r5d/pre-canonical-checkout/${previousHead}`, previousHead], { cwd: input.branchPath });
1019
+ }
1020
+ ensureOriginRemote(input.branchPath, input.remoteUrl);
1021
+ runGit(["checkout", "--no-recurse-submodules", "-B", input.branchName, `origin/${input.branchName}`], {
1022
+ cwd: input.branchPath
1023
+ });
1024
+ }
1025
+ function assertCheckoutDerivedFromRequiredBase(input) {
1026
+ const remoteRevision = `refs/remotes/origin/${input.branchName}`;
1027
+ for (const revision of [remoteRevision, "HEAD"]) {
1028
+ if (!tryGit(["cat-file", "-e", `${input.requiredBaseCommit}^{commit}`], { cwd: input.branchPath }) || !tryGit(["merge-base", "--is-ancestor", input.requiredBaseCommit, revision], { cwd: input.branchPath })) {
1029
+ throw new Error(`Canonical checkout ${input.branchName} is not derived from required scaffold ${input.requiredBaseCommit}`);
1030
+ }
1031
+ }
933
1032
  }
934
1033
  function ensureVisibleGitCheckout(input) {
935
1034
  validateBranchName(input.branchName);
@@ -939,40 +1038,114 @@ function ensureVisibleGitCheckout(input) {
939
1038
  if (createdCheckout) {
940
1039
  fs.rmSync(branchPath, { recursive: true, force: true });
941
1040
  fs.mkdirSync(path.dirname(branchPath), { recursive: true });
942
- if (!tryGit(["clone", "--origin", "origin", input.githubRemoteUrl, branchPath], { auth: input.githubAuth })) {
1041
+ const cloned = tryGit(visibleCheckoutCloneArgs(input.remoteUrl, branchPath), {
1042
+ auth: input.remoteAuth
1043
+ });
1044
+ if (!cloned && input.requireRemoteBranch) {
1045
+ fs.rmSync(branchPath, { recursive: true, force: true });
1046
+ throw new Error(`Failed to clone canonical checkout for branch ${input.branchName}`);
1047
+ }
1048
+ if (!cloned) {
943
1049
  fs.mkdirSync(branchPath, { recursive: true });
944
1050
  runGit(["init"], { cwd: branchPath });
945
1051
  }
946
- ensureOriginRemote(branchPath, input.githubRemoteUrl);
947
- configureVisibleGitHubAuth(branchPath, input.githubRemoteUrl, input.githubAuthHeader);
1052
+ ensureOriginRemote(branchPath, input.remoteUrl);
1053
+ if (input.clearPersistentAuth) clearVisibleGitAuthentication(branchPath);
1054
+ else configureVisibleGitHubAuth(branchPath, input.remoteUrl, input.persistentAuthHeader);
948
1055
  configureVisibleGitIdentity(branchPath, visibleGitIdentity, runGit);
949
- tryGit(["fetch", "origin", "--prune"], { cwd: branchPath, auth: input.githubAuth });
1056
+ tryGit(visibleCheckoutFetchArgs("origin", "--prune"), { cwd: branchPath, auth: input.remoteAuth });
1057
+ if (input.requireRemoteBranch && !tryGit(["show-ref", "--verify", "--quiet", `refs/remotes/origin/${input.branchName}`], { cwd: branchPath })) {
1058
+ fs.rmSync(branchPath, { recursive: true, force: true });
1059
+ throw new Error(`Canonical branch ${input.branchName} is unavailable after clone`);
1060
+ }
950
1061
  checkoutVisibleBranch({ branchPath, branchName: input.branchName, defaultBranch: input.defaultBranch });
1062
+ if (input.requiredBaseCommit) {
1063
+ try {
1064
+ assertCheckoutDerivedFromRequiredBase({
1065
+ branchPath,
1066
+ branchName: input.branchName,
1067
+ requiredBaseCommit: input.requiredBaseCommit
1068
+ });
1069
+ } catch (error) {
1070
+ fs.rmSync(branchPath, { recursive: true, force: true });
1071
+ throw error;
1072
+ }
1073
+ }
951
1074
  return branchPath;
952
1075
  }
1076
+ if (input.requireRemoteBranch) {
1077
+ if (input.allowRequiredRemoteMigration === false && originRemoteUrl(branchPath) !== input.remoteUrl) {
1078
+ throw new Error(`Canonical checkout ${input.branchName} no longer uses its required remote`);
1079
+ }
1080
+ migrateExistingCheckoutToRequiredRemote({
1081
+ branchPath,
1082
+ branchName: input.branchName,
1083
+ remoteUrl: input.remoteUrl,
1084
+ remoteAuth: input.remoteAuth,
1085
+ requiredBaseCommit: input.requiredBaseCommit
1086
+ });
1087
+ runGit(
1088
+ visibleCheckoutFetchArgs("--no-tags", input.remoteUrl, `+refs/heads/${input.branchName}:refs/remotes/origin/${input.branchName}`),
1089
+ {
1090
+ cwd: branchPath,
1091
+ auth: input.remoteAuth
1092
+ }
1093
+ );
1094
+ }
953
1095
  if (input.reconcileExistingOrigin) {
954
- ensureOriginRemote(branchPath, input.githubRemoteUrl);
1096
+ ensureOriginRemote(branchPath, input.remoteUrl);
955
1097
  }
956
- configureVisibleGitHubAuth(branchPath, input.githubRemoteUrl, input.githubAuthHeader);
1098
+ if (input.requireRemoteBranch && !tryGit(["show-ref", "--verify", "--quiet", `refs/remotes/origin/${input.branchName}`], { cwd: branchPath })) {
1099
+ throw new Error(`Existing checkout for ${input.branchName} does not contain its canonical remote branch`);
1100
+ }
1101
+ if (input.requiredBaseCommit) {
1102
+ assertCheckoutDerivedFromRequiredBase({
1103
+ branchPath,
1104
+ branchName: input.branchName,
1105
+ requiredBaseCommit: input.requiredBaseCommit
1106
+ });
1107
+ }
1108
+ if (input.clearPersistentAuth) clearVisibleGitAuthentication(branchPath);
1109
+ else configureVisibleGitHubAuth(branchPath, input.remoteUrl, input.persistentAuthHeader);
957
1110
  configureVisibleGitIdentity(branchPath, visibleGitIdentity, runGit);
958
1111
  return branchPath;
959
1112
  }
960
1113
  function ensureBranchWorkspace(input) {
961
1114
  const defaultBranch = input.manifest?.defaultBranch || "main";
962
- const githubRemoteUrl = githubRemoteUrlFor(input.baseUrl, input.projectId, input.manifest);
963
- const githubAuth = gitAuthForRemote(githubRemoteUrl, input.manifest?.repoAuthHeader);
1115
+ const remote = visibleCheckoutRemoteFor(input);
964
1116
  return {
965
1117
  branchPath: ensureVisibleGitCheckout({
966
1118
  projectRoot: input.projectRoot,
967
1119
  branchName: input.branchName,
968
- githubRemoteUrl,
969
- githubAuth,
970
- githubAuthHeader: input.manifest?.repoAuthHeader ?? null,
1120
+ remoteUrl: remote.remoteUrl,
1121
+ remoteAuth: gitAuthForRemote(remote.remoteUrl, remote.authHeader),
1122
+ persistentAuthHeader: remote.persistAuth ? remote.authHeader : null,
971
1123
  defaultBranch,
972
- reconcileExistingOrigin: Boolean(input.manifest?.repoHttpUrl)
1124
+ reconcileExistingOrigin: remote.reconcileExistingOrigin,
1125
+ requireRemoteBranch: remote.requireRemoteBranch,
1126
+ requiredBaseCommit: remote.requiredBaseCommit,
1127
+ clearPersistentAuth: !remote.persistAuth
973
1128
  })
974
1129
  };
975
1130
  }
1131
+ function workspaceSyncCheckoutTargets(input) {
1132
+ const selected = /* @__PURE__ */ new Map();
1133
+ const add = (target) => {
1134
+ selected.set(`${target.projectId}\0${target.branchName}`, target);
1135
+ };
1136
+ if (input.canonicalCheckoutOnly) {
1137
+ add(input.canonicalCheckoutOnly);
1138
+ } else if (input.includeAllManifestCheckouts) {
1139
+ for (const project of input.projects) {
1140
+ for (const branchName of project.branches) add({ projectId: project.projectId, branchName });
1141
+ }
1142
+ } else {
1143
+ for (const target of input.pendingTargets) add(target);
1144
+ }
1145
+ return [...selected.values()].sort(
1146
+ (left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
1147
+ );
1148
+ }
976
1149
  function resolveCommandCwd(branchPath, cwd) {
977
1150
  if (!cwd) {
978
1151
  return branchPath;
@@ -2196,6 +2369,7 @@ async function startWorker(options) {
2196
2369
  let reloadAfterClose = false;
2197
2370
  const workspaceSyncInput = (trigger, overrides = {}) => {
2198
2371
  if (!workspaceRemoteUrl) throw new Error("Worker workspace manifest has not been received");
2372
+ const projects = workspaceProjectsForSync([...manifestByProjectId.values()], trigger);
2199
2373
  return {
2200
2374
  workerLabel: label,
2201
2375
  remoteUrl: workspaceRemoteUrl,
@@ -2203,7 +2377,7 @@ async function startWorker(options) {
2203
2377
  projectsRoot,
2204
2378
  plansRoot: planRoot,
2205
2379
  shadowRoot: workspaceShadowRoot,
2206
- projects: [...manifestByProjectId.values()],
2380
+ projects,
2207
2381
  trigger,
2208
2382
  ...overrides
2209
2383
  };
@@ -2215,7 +2389,7 @@ async function startWorker(options) {
2215
2389
  const allManifestCheckouts = () => [...manifestByProjectId.values()].flatMap(
2216
2390
  (manifest) => manifest.branches.map((branchName) => ({ projectId: manifest.projectId, branchName }))
2217
2391
  );
2218
- const ensureVisibleWorkspaceCheckouts = (targets) => {
2392
+ const ensureVisibleWorkspaceCheckouts = (targets, options2 = {}) => {
2219
2393
  const created = [];
2220
2394
  for (const target of targets) {
2221
2395
  const manifest = manifestByProjectId.get(target.projectId);
@@ -2227,15 +2401,25 @@ async function startWorker(options) {
2227
2401
  const projectRoot = projectRootFor(projectsRoot, manifest.projectId, manifestByProjectId);
2228
2402
  const branchPath = path.join(projectRoot, target.branchName);
2229
2403
  const existed = hasNormalVisibleGitDir(branchPath);
2230
- const githubRemoteUrl = githubRemoteUrlFor(baseUrl, manifest.projectId, manifest);
2404
+ const remote = visibleCheckoutRemoteFor({
2405
+ baseUrl,
2406
+ token,
2407
+ projectId: manifest.projectId,
2408
+ branchName: target.branchName,
2409
+ manifest
2410
+ });
2231
2411
  ensureVisibleGitCheckout({
2232
2412
  projectRoot,
2233
2413
  branchName: target.branchName,
2234
- githubRemoteUrl,
2235
- githubAuth: gitAuthForRemote(githubRemoteUrl, manifest.repoAuthHeader),
2236
- githubAuthHeader: manifest.repoAuthHeader,
2414
+ remoteUrl: remote.remoteUrl,
2415
+ remoteAuth: gitAuthForRemote(remote.remoteUrl, remote.authHeader),
2416
+ persistentAuthHeader: remote.persistAuth ? remote.authHeader : null,
2237
2417
  defaultBranch: manifest.defaultBranch || "main",
2238
- reconcileExistingOrigin: Boolean(manifest.repoHttpUrl)
2418
+ reconcileExistingOrigin: remote.reconcileExistingOrigin,
2419
+ requireRemoteBranch: remote.requireRemoteBranch,
2420
+ requiredBaseCommit: remote.requiredBaseCommit,
2421
+ allowRequiredRemoteMigration: !(options2.strictCanonicalRevalidation && remote.requireRemoteBranch),
2422
+ clearPersistentAuth: !remote.persistAuth
2239
2423
  });
2240
2424
  if (!existed) created.push(target);
2241
2425
  }
@@ -2247,15 +2431,30 @@ async function startWorker(options) {
2247
2431
  let pendingTargets = [];
2248
2432
  const result = await workspaceSyncSingleFlight.runPrepared(() => {
2249
2433
  const shouldEnsureCheckouts = !overrides.skipVisibleMirror && !overrides.resetToCanonical;
2434
+ const syncProjects = workspaceProjectsForSync([...manifestByProjectId.values()], trigger);
2435
+ const allowedCheckoutKeys = new Set(
2436
+ syncProjects.flatMap((project) => project.branches.map((branchName) => manifestCheckoutKey(project.projectId, branchName)))
2437
+ );
2438
+ const scopedPendingTargets = [...pendingManifestCheckouts.values()].filter(
2439
+ (target) => allowedCheckoutKeys.has(manifestCheckoutKey(target.projectId, target.branchName))
2440
+ );
2250
2441
  let checkoutTargets = [];
2251
2442
  if (shouldEnsureCheckouts) {
2252
- checkoutTargets = trigger.type === "connect" ? allManifestCheckouts() : [...pendingManifestCheckouts.values()];
2443
+ checkoutTargets = workspaceSyncCheckoutTargets({
2444
+ projects: syncProjects,
2445
+ pendingTargets: scopedPendingTargets,
2446
+ includeAllManifestCheckouts: trigger.type === "connect",
2447
+ ...trigger.canonicalCheckoutOnly && trigger.projectId && trigger.branchName ? { canonicalCheckoutOnly: { projectId: trigger.projectId, branchName: trigger.branchName } } : {}
2448
+ });
2253
2449
  }
2254
- pendingTargets = shouldEnsureCheckouts ? [...pendingManifestCheckouts.values()] : [];
2255
- const createdCheckouts = ensureVisibleWorkspaceCheckouts(checkoutTargets);
2450
+ pendingTargets = shouldEnsureCheckouts ? scopedPendingTargets : [];
2451
+ const createdCheckouts = ensureVisibleWorkspaceCheckouts(checkoutTargets, {
2452
+ strictCanonicalRevalidation: true
2453
+ });
2256
2454
  const newVisibleCheckoutByKey = /* @__PURE__ */ new Map();
2257
2455
  for (const target of [...overrides.newVisibleCheckouts ?? [], ...createdCheckouts, ...pendingTargets]) {
2258
- newVisibleCheckoutByKey.set(manifestCheckoutKey(target.projectId, target.branchName), target);
2456
+ const key = manifestCheckoutKey(target.projectId, target.branchName);
2457
+ if (allowedCheckoutKeys.has(key)) newVisibleCheckoutByKey.set(key, target);
2259
2458
  }
2260
2459
  const newVisibleCheckouts = [...newVisibleCheckoutByKey.values()];
2261
2460
  return workspaceSyncInput(trigger, {
@@ -2331,7 +2530,9 @@ async function startWorker(options) {
2331
2530
  version: getWorkerVersion(),
2332
2531
  r5dctlVersion: readInstalledCliVersion("r5dctl"),
2333
2532
  capabilities: {
2334
- updateClis: true
2533
+ updateClis: true,
2534
+ canonicalResolverCheckout: true,
2535
+ browserPortForwarding: true
2335
2536
  },
2336
2537
  projectRoot: projectsRoot,
2337
2538
  artifactRoot,
@@ -2391,7 +2592,15 @@ async function startWorker(options) {
2391
2592
  if (!workspaceRemoteUrl || activeWorkspaceIncidentId || cliUpdateInProgress || periodicWorkspaceScanInFlight) return;
2392
2593
  periodicWorkspaceScanInFlight = true;
2393
2594
  void (async () => {
2394
- if (pendingManifestCheckouts.size > 0) {
2595
+ const genericCheckoutKeys = new Set(
2596
+ workspaceProjectsForSync([...manifestByProjectId.values()], { type: "periodic" }).flatMap(
2597
+ (project) => project.branches.map((branchName) => manifestCheckoutKey(project.projectId, branchName))
2598
+ )
2599
+ );
2600
+ const hasPendingGenericCheckout = [...pendingManifestCheckouts.values()].some(
2601
+ (target) => genericCheckoutKeys.has(manifestCheckoutKey(target.projectId, target.branchName))
2602
+ );
2603
+ if (hasPendingGenericCheckout) {
2395
2604
  previousPeriodicFingerprint = null;
2396
2605
  await runRequestedWorkspaceSync(crypto.randomUUID(), {
2397
2606
  type: "periodic",
@@ -2476,6 +2685,17 @@ async function startWorker(options) {
2476
2685
  pendingProcessTerminals.delete(message.runId);
2477
2686
  return;
2478
2687
  }
2688
+ if (message.type === "port_forward_connect") {
2689
+ openWorkerPortForwardRelay({
2690
+ baseUrl,
2691
+ token,
2692
+ label,
2693
+ forwardId: message.forwardId,
2694
+ relayConnectionId: message.relayConnectionId,
2695
+ workerPort: message.workerPort
2696
+ });
2697
+ return;
2698
+ }
2479
2699
  if (message.type === "update_clis") {
2480
2700
  if (cliUpdateInProgress) {
2481
2701
  sendWorkerMessage(ws, {
@@ -2808,5 +3028,8 @@ export {
2808
3028
  resolveHostShell,
2809
3029
  resolveWorkerFilePath,
2810
3030
  syncSessionArtifacts,
3031
+ visibleCheckoutGitTestHarness,
3032
+ visibleCheckoutRemoteFor,
3033
+ workspaceSyncCheckoutTargets,
2811
3034
  writeWorkerTextFile
2812
3035
  };
@@ -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": "module"
5
5
  }
@@ -0,0 +1,114 @@
1
+ import net from "node:net";
2
+ import WebSocket, {} from "ws";
3
+ const RELAY_FRAME_BYTES = 64 * 1024;
4
+ const RELAY_PAUSE_BYTES = 1024 * 1024;
5
+ const RELAY_RESUME_BYTES = 256 * 1024;
6
+ function relayUrl(baseUrl, label, forwardId, relayConnectionId) {
7
+ const url = new URL("/worker/port-forward/ws", baseUrl);
8
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
9
+ url.searchParams.set("label", label);
10
+ url.searchParams.set("forward", forwardId);
11
+ url.searchParams.set("connection", relayConnectionId);
12
+ return url.toString();
13
+ }
14
+ function rawDataBuffer(data) {
15
+ if (Buffer.isBuffer(data)) return data;
16
+ if (Array.isArray(data)) return Buffer.concat(data);
17
+ return Buffer.from(data);
18
+ }
19
+ function openWorkerPortForwardRelay(options) {
20
+ const socket = net.createConnection({ host: "127.0.0.1", port: options.workerPort, allowHalfOpen: true });
21
+ socket.pause();
22
+ socket.setNoDelay(true);
23
+ const relay = new WebSocket(relayUrl(options.baseUrl, options.label, options.forwardId, options.relayConnectionId), {
24
+ headers: { Authorization: `Bearer ${options.token}` },
25
+ perMessageDeflate: false
26
+ });
27
+ let relayReady = false;
28
+ let targetReady = false;
29
+ const pendingChunks = [];
30
+ let pendingBytes = 0;
31
+ let closing = false;
32
+ let resumeTimer;
33
+ const maybeResume = () => {
34
+ if (relayReady && targetReady && !resumeTimer) socket.resume();
35
+ };
36
+ const cleanup = () => {
37
+ if (closing) return;
38
+ closing = true;
39
+ if (resumeTimer) clearInterval(resumeTimer);
40
+ socket.destroy();
41
+ if (relay.readyState === WebSocket.OPEN || relay.readyState === WebSocket.CONNECTING) relay.close();
42
+ };
43
+ const sendError = (message) => {
44
+ if (relay.readyState === WebSocket.OPEN) relay.send(JSON.stringify({ type: "error", error: message }));
45
+ cleanup();
46
+ };
47
+ const waitForRelayDrain = () => {
48
+ if (resumeTimer || relay.bufferedAmount <= RELAY_PAUSE_BYTES) return;
49
+ socket.pause();
50
+ resumeTimer = setInterval(() => {
51
+ if (relay.readyState !== WebSocket.OPEN) return cleanup();
52
+ if (relay.bufferedAmount > RELAY_RESUME_BYTES) return;
53
+ clearInterval(resumeTimer);
54
+ resumeTimer = void 0;
55
+ maybeResume();
56
+ }, 10);
57
+ };
58
+ const sendChunk = (chunk) => {
59
+ for (let offset = 0; offset < chunk.byteLength; offset += RELAY_FRAME_BYTES) {
60
+ relay.send(chunk.subarray(offset, Math.min(chunk.byteLength, offset + RELAY_FRAME_BYTES)), { binary: true });
61
+ }
62
+ waitForRelayDrain();
63
+ };
64
+ socket.once("connect", () => {
65
+ targetReady = true;
66
+ maybeResume();
67
+ });
68
+ socket.on("data", (chunk) => {
69
+ if (!relayReady || relay.readyState !== WebSocket.OPEN) {
70
+ pendingBytes += chunk.byteLength;
71
+ if (pendingBytes > RELAY_PAUSE_BYTES) return cleanup();
72
+ pendingChunks.push(Buffer.from(chunk));
73
+ socket.pause();
74
+ return;
75
+ }
76
+ sendChunk(chunk);
77
+ });
78
+ socket.on("end", () => {
79
+ if (relay.readyState === WebSocket.OPEN) relay.send(JSON.stringify({ type: "end" }));
80
+ });
81
+ socket.on("error", (error) => sendError(`Could not connect to 127.0.0.1:${options.workerPort}: ${error.message}`));
82
+ socket.on("close", cleanup);
83
+ relay.on("message", (data, isBinary) => {
84
+ if (!isBinary) {
85
+ let control;
86
+ try {
87
+ control = JSON.parse(rawDataBuffer(data).toString("utf8"));
88
+ } catch {
89
+ return cleanup();
90
+ }
91
+ if (control.type === "ready") {
92
+ relayReady = true;
93
+ for (const chunk of pendingChunks.splice(0)) sendChunk(chunk);
94
+ pendingBytes = 0;
95
+ maybeResume();
96
+ } else if (control.type === "end") {
97
+ socket.end();
98
+ } else if (control.type === "error") {
99
+ socket.destroy(new Error(control.error || "Port-forward relay failed."));
100
+ }
101
+ return;
102
+ }
103
+ if (!socket.write(rawDataBuffer(data))) {
104
+ relay.pause();
105
+ socket.once("drain", () => relay.resume());
106
+ }
107
+ });
108
+ relay.once("error", cleanup);
109
+ relay.once("close", cleanup);
110
+ socket.pause();
111
+ }
112
+ export {
113
+ openWorkerPortForwardRelay
114
+ };