@h-rig/cli 0.0.6-alpha.82 → 0.0.6-alpha.84

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.
Files changed (206) hide show
  1. package/dist/bin/build-rig-binaries.js +40 -8
  2. package/dist/bin/rig.js +23402 -14577
  3. package/dist/src/app/board.js +217 -41
  4. package/dist/src/app-opentui/adapters/command.d.ts +2 -0
  5. package/dist/src/app-opentui/adapters/command.js +329 -0
  6. package/dist/src/app-opentui/adapters/common.js +2 -2
  7. package/dist/src/app-opentui/adapters/doctor.d.ts +0 -2
  8. package/dist/src/app-opentui/adapters/doctor.js +64 -39
  9. package/dist/src/app-opentui/adapters/family.d.ts +62 -0
  10. package/dist/src/app-opentui/adapters/family.js +14305 -0
  11. package/dist/src/app-opentui/adapters/fleet.d.ts +0 -2
  12. package/dist/src/app-opentui/adapters/fleet.js +90 -60
  13. package/dist/src/app-opentui/adapters/inbox.d.ts +12 -2
  14. package/dist/src/app-opentui/adapters/inbox.js +137 -78
  15. package/dist/src/app-opentui/adapters/init.d.ts +0 -2
  16. package/dist/src/app-opentui/adapters/init.js +85 -47
  17. package/dist/src/app-opentui/adapters/inspect.d.ts +52 -0
  18. package/dist/src/app-opentui/adapters/inspect.js +1024 -0
  19. package/dist/src/app-opentui/adapters/pi-attach.d.ts +15 -6
  20. package/dist/src/app-opentui/adapters/pi-attach.js +442 -125
  21. package/dist/src/app-opentui/adapters/pi.d.ts +23 -0
  22. package/dist/src/app-opentui/adapters/pi.js +363 -0
  23. package/dist/src/app-opentui/adapters/plugin.d.ts +84 -0
  24. package/dist/src/app-opentui/adapters/plugin.js +544 -0
  25. package/dist/src/app-opentui/adapters/repo.d.ts +37 -0
  26. package/dist/src/app-opentui/adapters/repo.js +186 -0
  27. package/dist/src/app-opentui/adapters/run-detail.d.ts +9 -2
  28. package/dist/src/app-opentui/adapters/run-detail.js +180 -63
  29. package/dist/src/app-opentui/adapters/server.d.ts +10 -2
  30. package/dist/src/app-opentui/adapters/server.js +191 -45
  31. package/dist/src/app-opentui/adapters/tasks.d.ts +11 -2
  32. package/dist/src/app-opentui/adapters/tasks.js +1123 -143
  33. package/dist/src/app-opentui/adapters/workspace.d.ts +49 -0
  34. package/dist/src/app-opentui/adapters/workspace.js +333 -0
  35. package/dist/src/app-opentui/autocomplete.d.ts +20 -0
  36. package/dist/src/app-opentui/autocomplete.js +576 -0
  37. package/dist/src/app-opentui/bootstrap.d.ts +1 -6
  38. package/dist/src/app-opentui/bootstrap.js +25252 -16474
  39. package/dist/src/app-opentui/command-palette.d.ts +3 -0
  40. package/dist/src/app-opentui/command-palette.js +1010 -0
  41. package/dist/src/app-opentui/command-pty-host.d.ts +62 -0
  42. package/dist/src/app-opentui/command-pty-host.js +248 -0
  43. package/dist/src/app-opentui/drone.js +8 -6
  44. package/dist/src/app-opentui/events.js +1 -1
  45. package/dist/src/app-opentui/fleet-stats.d.ts +32 -0
  46. package/dist/src/app-opentui/fleet-stats.js +114 -0
  47. package/dist/src/app-opentui/focus-manager.d.ts +14 -0
  48. package/dist/src/app-opentui/focus-manager.js +24 -0
  49. package/dist/src/app-opentui/index.js +5431 -2797
  50. package/dist/src/app-opentui/intent.js +179 -50
  51. package/dist/src/app-opentui/keymap.d.ts +21 -0
  52. package/dist/src/app-opentui/keymap.js +1748 -0
  53. package/dist/src/app-opentui/launch-routing.d.ts +16 -0
  54. package/dist/src/app-opentui/launch-routing.js +55 -0
  55. package/dist/src/app-opentui/layout.d.ts +7 -0
  56. package/dist/src/app-opentui/layout.js +13 -6
  57. package/dist/src/app-opentui/list-search.d.ts +24 -0
  58. package/dist/src/app-opentui/list-search.js +88 -1
  59. package/dist/src/app-opentui/pi-host-child.js +99 -17
  60. package/dist/src/app-opentui/pi-pty-host.d.ts +14 -0
  61. package/dist/src/app-opentui/pi-pty-host.js +30 -14
  62. package/dist/src/app-opentui/react/App.d.ts +9 -0
  63. package/dist/src/app-opentui/react/App.js +5144 -0
  64. package/dist/src/app-opentui/react/Backdrop.d.ts +5 -0
  65. package/dist/src/app-opentui/react/Backdrop.js +1834 -0
  66. package/dist/src/app-opentui/react/ChromeHost.d.ts +5 -0
  67. package/dist/src/app-opentui/react/ChromeHost.js +2942 -0
  68. package/dist/src/app-opentui/react/SceneFrameView.d.ts +7 -0
  69. package/dist/src/app-opentui/react/SceneFrameView.js +529 -0
  70. package/dist/src/app-opentui/react/context.d.ts +17 -0
  71. package/dist/src/app-opentui/react/context.js +37 -0
  72. package/dist/src/app-opentui/react/launch.d.ts +2 -0
  73. package/dist/src/app-opentui/react/launch.js +5743 -0
  74. package/dist/src/app-opentui/react/nav.d.ts +18 -0
  75. package/dist/src/app-opentui/react/nav.js +54 -0
  76. package/dist/src/app-opentui/react/scroll.d.ts +12 -0
  77. package/dist/src/app-opentui/react/scroll.js +21 -0
  78. package/dist/src/app-opentui/react/syntax.d.ts +2 -0
  79. package/dist/src/app-opentui/react/syntax.js +64 -0
  80. package/dist/src/app-opentui/registry.js +20428 -4828
  81. package/dist/src/app-opentui/render/ascii-fleet.d.ts +15 -0
  82. package/dist/src/app-opentui/render/ascii-fleet.js +82 -0
  83. package/dist/src/app-opentui/render/constants.d.ts +31 -0
  84. package/dist/src/app-opentui/render/constants.js +66 -0
  85. package/dist/src/app-opentui/render/graphics.d.ts +2 -1
  86. package/dist/src/app-opentui/render/graphics.js +228 -46
  87. package/dist/src/app-opentui/render/image-visual-layer-worker.js +469 -930
  88. package/dist/src/app-opentui/render/image-visual-layer.d.ts +25 -10
  89. package/dist/src/app-opentui/render/image-visual-layer.js +448 -329
  90. package/dist/src/app-opentui/render/native-host.d.ts +37 -0
  91. package/dist/src/app-opentui/render/native-host.js +179 -0
  92. package/dist/src/app-opentui/render/panel-layout.d.ts +38 -0
  93. package/dist/src/app-opentui/render/panel-layout.js +48 -0
  94. package/dist/src/app-opentui/render/panels.d.ts +2 -0
  95. package/dist/src/app-opentui/render/panels.js +78 -38
  96. package/dist/src/app-opentui/render/preloader.d.ts +10 -0
  97. package/dist/src/app-opentui/render/preloader.js +165 -0
  98. package/dist/src/app-opentui/render/scene.d.ts +53 -1
  99. package/dist/src/app-opentui/render/scene.js +195 -6
  100. package/dist/src/app-opentui/render/text.d.ts +7 -1
  101. package/dist/src/app-opentui/render/text.js +15 -8
  102. package/dist/src/app-opentui/render/type-bar.d.ts +2 -1
  103. package/dist/src/app-opentui/render/type-bar.js +113 -39
  104. package/dist/src/app-opentui/runtime-resources.d.ts +16 -0
  105. package/dist/src/app-opentui/runtime-resources.js +62 -0
  106. package/dist/src/app-opentui/runtime.d.ts +44 -1
  107. package/dist/src/app-opentui/runtime.js +5415 -2738
  108. package/dist/src/app-opentui/scenes/command.d.ts +3 -0
  109. package/dist/src/app-opentui/scenes/command.js +117 -0
  110. package/dist/src/app-opentui/scenes/doctor.d.ts +2 -1
  111. package/dist/src/app-opentui/scenes/doctor.js +221 -17
  112. package/dist/src/app-opentui/scenes/error.js +60 -20
  113. package/dist/src/app-opentui/scenes/family-domains/agent.d.ts +2 -0
  114. package/dist/src/app-opentui/scenes/family-domains/agent.js +348 -0
  115. package/dist/src/app-opentui/scenes/family-domains/browser.d.ts +2 -0
  116. package/dist/src/app-opentui/scenes/family-domains/browser.js +195 -0
  117. package/dist/src/app-opentui/scenes/family-domains/dist.d.ts +2 -0
  118. package/dist/src/app-opentui/scenes/family-domains/dist.js +243 -0
  119. package/dist/src/app-opentui/scenes/family-domains/git.d.ts +2 -0
  120. package/dist/src/app-opentui/scenes/family-domains/git.js +195 -0
  121. package/dist/src/app-opentui/scenes/family-domains/github.d.ts +2 -0
  122. package/dist/src/app-opentui/scenes/family-domains/github.js +274 -0
  123. package/dist/src/app-opentui/scenes/family-domains/harness.d.ts +2 -0
  124. package/dist/src/app-opentui/scenes/family-domains/harness.js +152 -0
  125. package/dist/src/app-opentui/scenes/family-domains/index.d.ts +4 -0
  126. package/dist/src/app-opentui/scenes/family-domains/index.js +1679 -0
  127. package/dist/src/app-opentui/scenes/family-domains/kit.d.ts +76 -0
  128. package/dist/src/app-opentui/scenes/family-domains/kit.js +305 -0
  129. package/dist/src/app-opentui/scenes/family-domains/profile.d.ts +2 -0
  130. package/dist/src/app-opentui/scenes/family-domains/profile.js +212 -0
  131. package/dist/src/app-opentui/scenes/family-domains/queue.d.ts +2 -0
  132. package/dist/src/app-opentui/scenes/family-domains/queue.js +146 -0
  133. package/dist/src/app-opentui/scenes/family-domains/remote.d.ts +2 -0
  134. package/dist/src/app-opentui/scenes/family-domains/remote.js +518 -0
  135. package/dist/src/app-opentui/scenes/family-domains/review.d.ts +2 -0
  136. package/dist/src/app-opentui/scenes/family-domains/review.js +280 -0
  137. package/dist/src/app-opentui/scenes/family-domains/setup.d.ts +2 -0
  138. package/dist/src/app-opentui/scenes/family-domains/setup.js +267 -0
  139. package/dist/src/app-opentui/scenes/family-domains/stats.d.ts +2 -0
  140. package/dist/src/app-opentui/scenes/family-domains/stats.js +370 -0
  141. package/dist/src/app-opentui/scenes/family.d.ts +3 -0
  142. package/dist/src/app-opentui/scenes/family.js +2144 -0
  143. package/dist/src/app-opentui/scenes/fleet.js +552 -43
  144. package/dist/src/app-opentui/scenes/handoff.js +342 -35
  145. package/dist/src/app-opentui/scenes/help.js +640 -56
  146. package/dist/src/app-opentui/scenes/inbox.d.ts +2 -1
  147. package/dist/src/app-opentui/scenes/inbox.js +329 -21
  148. package/dist/src/app-opentui/scenes/init.d.ts +2 -1
  149. package/dist/src/app-opentui/scenes/init.js +120 -34
  150. package/dist/src/app-opentui/scenes/inspect.d.ts +3 -0
  151. package/dist/src/app-opentui/scenes/inspect.js +793 -0
  152. package/dist/src/app-opentui/scenes/main.d.ts +2 -1
  153. package/dist/src/app-opentui/scenes/main.js +264 -29
  154. package/dist/src/app-opentui/scenes/pi.d.ts +3 -0
  155. package/dist/src/app-opentui/scenes/pi.js +508 -0
  156. package/dist/src/app-opentui/scenes/plugin.d.ts +3 -0
  157. package/dist/src/app-opentui/scenes/plugin.js +486 -0
  158. package/dist/src/app-opentui/scenes/repo.d.ts +3 -0
  159. package/dist/src/app-opentui/scenes/repo.js +424 -0
  160. package/dist/src/app-opentui/scenes/run-detail.d.ts +2 -1
  161. package/dist/src/app-opentui/scenes/run-detail.js +362 -35
  162. package/dist/src/app-opentui/scenes/server.d.ts +2 -1
  163. package/dist/src/app-opentui/scenes/server.js +243 -26
  164. package/dist/src/app-opentui/scenes/tasks.js +518 -41
  165. package/dist/src/app-opentui/scenes/workspace.d.ts +3 -0
  166. package/dist/src/app-opentui/scenes/workspace.js +426 -0
  167. package/dist/src/app-opentui/selectable.d.ts +19 -0
  168. package/dist/src/app-opentui/selectable.js +79 -0
  169. package/dist/src/app-opentui/state.js +129 -36
  170. package/dist/src/app-opentui/surface-catalog.d.ts +20 -0
  171. package/dist/src/app-opentui/surface-catalog.js +540 -0
  172. package/dist/src/app-opentui/terminal-capabilities.d.ts +7 -0
  173. package/dist/src/app-opentui/terminal-capabilities.js +15 -0
  174. package/dist/src/app-opentui/theme.d.ts +28 -6
  175. package/dist/src/app-opentui/theme.js +61 -8
  176. package/dist/src/app-opentui/types.d.ts +130 -5
  177. package/dist/src/commands/_authority-runs.d.ts +1 -1
  178. package/dist/src/commands/_authority-runs.js +2 -12
  179. package/dist/src/commands/_doctor-checks.js +62 -13
  180. package/dist/src/commands/_help-catalog.js +95 -15
  181. package/dist/src/commands/_operator-view.js +97 -15
  182. package/dist/src/commands/_pi-frontend.d.ts +2 -0
  183. package/dist/src/commands/_pi-frontend.js +97 -13
  184. package/dist/src/commands/_preflight.js +64 -14
  185. package/dist/src/commands/_server-client.js +82 -18
  186. package/dist/src/commands/_server-events.d.ts +26 -0
  187. package/dist/src/commands/_server-events.js +310 -0
  188. package/dist/src/commands/_snapshot-upload.js +62 -13
  189. package/dist/src/commands/agent.js +2 -12
  190. package/dist/src/commands/connect.js +7 -1
  191. package/dist/src/commands/doctor.js +62 -13
  192. package/dist/src/commands/github.js +144 -23
  193. package/dist/src/commands/inbox.js +62 -13
  194. package/dist/src/commands/init.js +82 -18
  195. package/dist/src/commands/inspect.js +62 -13
  196. package/dist/src/commands/run.js +100 -15
  197. package/dist/src/commands/server.js +71 -26
  198. package/dist/src/commands/setup.js +62 -13
  199. package/dist/src/commands/stats.js +157 -28
  200. package/dist/src/commands/task-run-driver.js +64 -25
  201. package/dist/src/commands/task.js +196 -43
  202. package/dist/src/commands.js +419 -123
  203. package/dist/src/index.js +426 -130
  204. package/package.json +11 -10
  205. package/dist/src/app-opentui/render/image-visual-layer-native-canvas.d.ts +0 -2
  206. package/dist/src/app-opentui/render/image-visual-layer-native-canvas.js +0 -1484
@@ -502,17 +502,21 @@ function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
502
502
  const scopedKey = resolve2(projectRoot ?? process.cwd());
503
503
  scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
504
504
  }
505
- function readPrivateRemoteSessionToken(projectRoot) {
505
+ function readRemoteAuthState(projectRoot) {
506
506
  const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
507
507
  if (!existsSync2(path))
508
508
  return null;
509
509
  try {
510
510
  const parsed = JSON.parse(readFileSync2(path, "utf8"));
511
- return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
511
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
512
512
  } catch {
513
513
  return null;
514
514
  }
515
515
  }
516
+ function readPrivateRemoteSessionToken(projectRoot) {
517
+ const parsed = readRemoteAuthState(projectRoot);
518
+ return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
519
+ }
516
520
  function readGitHubBearerTokenForRemote(projectRoot) {
517
521
  const scopedKey = resolve2(projectRoot);
518
522
  if (scopedGitHubBearerTokens.has(scopedKey))
@@ -523,15 +527,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
523
527
  return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
524
528
  }
525
529
  function readStoredGitHubAuthToken(projectRoot) {
526
- const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
527
- if (!existsSync2(path))
530
+ const parsed = readRemoteAuthState(projectRoot);
531
+ return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
532
+ }
533
+ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
534
+ const repo = readRepoConnection(projectRoot);
535
+ const slug = repo?.project?.trim();
536
+ if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
528
537
  return null;
529
- try {
530
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
531
- return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
532
- } catch {
538
+ const auth = readRemoteAuthState(projectRoot);
539
+ const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
540
+ const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
541
+ if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
533
542
  return null;
534
- }
543
+ const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
544
+ if (!checkoutBaseDir)
545
+ return null;
546
+ const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
547
+ writeRepoServerProjectRoot(projectRoot, inferred);
548
+ return inferred;
535
549
  }
536
550
  function readLocalConnectionFallbackToken(projectRoot) {
537
551
  return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
@@ -542,7 +556,7 @@ async function ensureServerForCli(projectRoot) {
542
556
  if (selected?.connection.kind === "remote") {
543
557
  reportServerPhase(`Connecting to ${selected.alias}\u2026`);
544
558
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
545
- const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
559
+ const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
546
560
  return {
547
561
  baseUrl: selected.connection.baseUrl,
548
562
  authToken,
@@ -571,7 +585,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
571
585
  if (!slug)
572
586
  return null;
573
587
  try {
574
- const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
588
+ const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
589
+ if (authToken && queryAuthFallbackEnabled())
590
+ url.searchParams.set("rt", authToken);
591
+ const response = await fetch(url, {
575
592
  headers: mergeHeaders(undefined, authToken)
576
593
  });
577
594
  if (!response.ok)
@@ -588,10 +605,23 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
588
605
  return null;
589
606
  }
590
607
  }
608
+ function mergeCookie(existing, name, value) {
609
+ const encoded = `${name}=${encodeURIComponent(value)}`;
610
+ if (!existing?.trim())
611
+ return encoded;
612
+ const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
613
+ return [...parts, encoded].join("; ");
614
+ }
615
+ function queryAuthFallbackEnabled(env = process.env) {
616
+ return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
617
+ }
591
618
  function mergeHeaders(headers, authToken) {
592
619
  const merged = new Headers(headers);
593
620
  if (authToken) {
594
- merged.set("authorization", `Bearer ${authToken}`);
621
+ const bearer = `Bearer ${authToken}`;
622
+ merged.set("authorization", bearer);
623
+ merged.set("x-auth", bearer);
624
+ merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
595
625
  }
596
626
  return merged;
597
627
  }
@@ -652,15 +682,34 @@ async function buildServerFailureContext(projectRoot, server) {
652
682
  hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
653
683
  };
654
684
  }
685
+ function isLoopbackRemoteBaseUrl(baseUrl) {
686
+ try {
687
+ const host = new URL(baseUrl).hostname.toLowerCase();
688
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
689
+ } catch {
690
+ return false;
691
+ }
692
+ }
693
+ function canUseRemoteWithoutProjectRoot(pathname) {
694
+ return pathname === "/health" || pathname === "/api/health" || pathname === "/api/server/status" || pathname === "/api/server/project-root" || pathname.startsWith("/api/github/auth/") || pathname === "/api/projects" || pathname.startsWith("/api/projects/");
695
+ }
655
696
  async function requestServerJson(context, pathname, init = {}) {
656
697
  const server = await ensureServerForCli(context.projectRoot);
698
+ const requestUrl = new URL(`${server.baseUrl}${pathname}`);
699
+ if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
700
+ const repo = readRepoConnection(context.projectRoot);
701
+ throw new CliError(`Remote server is selected for ${repo?.project ?? "this repo"}, but this checkout has no server-host project root link.`, 1, { hint: "Run `rig init --repair` or `rig init --yes --repo owner/repo --server remote` to repair the remote project link before task/run commands." });
702
+ }
657
703
  const headers = mergeHeaders(init.headers, server.authToken);
658
704
  if (server.serverProjectRoot)
659
705
  headers.set("x-rig-project-root", server.serverProjectRoot);
706
+ if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
707
+ requestUrl.searchParams.set("rt", server.authToken);
708
+ }
660
709
  reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
661
710
  let response;
662
711
  try {
663
- response = await fetch(`${server.baseUrl}${pathname}`, {
712
+ response = await fetch(requestUrl, {
664
713
  ...init,
665
714
  headers
666
715
  });
@@ -689,11 +738,26 @@ ${failure.contextLine}`, 1, { hint: failure.hint });
689
738
  return payload;
690
739
  }
691
740
  async function postGitHubTokenViaServer(context, token, options = {}) {
692
- const payload = await requestServerJson(context, "/api/github/auth/token", {
693
- method: "POST",
694
- headers: { "content-type": "application/json" },
695
- body: JSON.stringify({ token, selectedRepo: options.selectedRepo, projectRoot: options.projectRoot })
696
- });
741
+ const server = await ensureServerForCli(context.projectRoot);
742
+ const requestUrl = new URL(`${server.baseUrl}/api/github/auth/token`);
743
+ reportServerPhase("POST /api/github/auth/token\u2026");
744
+ let response;
745
+ try {
746
+ response = await fetch(requestUrl, {
747
+ method: "POST",
748
+ headers: { "content-type": "application/json" },
749
+ body: JSON.stringify({ token, selectedRepo: options.selectedRepo, projectRoot: options.projectRoot ?? server.serverProjectRoot ?? undefined })
750
+ });
751
+ } catch (error) {
752
+ const failure = await buildServerFailureContext(context.projectRoot, server);
753
+ throw new CliError(`Rig server auth bootstrap failed: ${error instanceof Error ? error.message : String(error)}
754
+ ${failure.contextLine}`, 1, { hint: failure.hint });
755
+ }
756
+ const payload = await response.json().catch(() => null);
757
+ if (!response.ok) {
758
+ const detail = payload && typeof payload === "object" && !Array.isArray(payload) ? String(payload.error ?? JSON.stringify(payload)) : `HTTP ${response.status}`;
759
+ throw new CliError(`Rig server auth bootstrap failed (${response.status}): ${detail}`, 1, { hint: "Re-run `rig github auth import-gh`, or check the selected server with `rig server status`." });
760
+ }
697
761
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
698
762
  }
699
763
  async function prepareRemoteCheckoutViaServer(context, input) {
@@ -180,17 +180,21 @@ function cleanToken(value) {
180
180
  const trimmed = value?.trim();
181
181
  return trimmed ? trimmed : null;
182
182
  }
183
- function readPrivateRemoteSessionToken(projectRoot) {
183
+ function readRemoteAuthState(projectRoot) {
184
184
  const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
185
185
  if (!existsSync2(path))
186
186
  return null;
187
187
  try {
188
188
  const parsed = JSON.parse(readFileSync2(path, "utf8"));
189
- return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
189
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
190
190
  } catch {
191
191
  return null;
192
192
  }
193
193
  }
194
+ function readPrivateRemoteSessionToken(projectRoot) {
195
+ const parsed = readRemoteAuthState(projectRoot);
196
+ return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
197
+ }
194
198
  function readGitHubBearerTokenForRemote(projectRoot) {
195
199
  const scopedKey = resolve2(projectRoot);
196
200
  if (scopedGitHubBearerTokens.has(scopedKey))
@@ -201,15 +205,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
201
205
  return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
202
206
  }
203
207
  function readStoredGitHubAuthToken(projectRoot) {
204
- const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
205
- if (!existsSync2(path))
208
+ const parsed = readRemoteAuthState(projectRoot);
209
+ return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
210
+ }
211
+ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
212
+ const repo = readRepoConnection(projectRoot);
213
+ const slug = repo?.project?.trim();
214
+ if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
206
215
  return null;
207
- try {
208
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
209
- return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
210
- } catch {
216
+ const auth = readRemoteAuthState(projectRoot);
217
+ const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
218
+ const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
219
+ if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
211
220
  return null;
212
- }
221
+ const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
222
+ if (!checkoutBaseDir)
223
+ return null;
224
+ const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
225
+ writeRepoServerProjectRoot(projectRoot, inferred);
226
+ return inferred;
213
227
  }
214
228
  function readLocalConnectionFallbackToken(projectRoot) {
215
229
  return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
@@ -220,7 +234,7 @@ async function ensureServerForCli(projectRoot) {
220
234
  if (selected?.connection.kind === "remote") {
221
235
  reportServerPhase(`Connecting to ${selected.alias}\u2026`);
222
236
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
223
- const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
237
+ const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
224
238
  return {
225
239
  baseUrl: selected.connection.baseUrl,
226
240
  authToken,
@@ -249,7 +263,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
249
263
  if (!slug)
250
264
  return null;
251
265
  try {
252
- const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
266
+ const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
267
+ if (authToken && queryAuthFallbackEnabled())
268
+ url.searchParams.set("rt", authToken);
269
+ const response = await fetch(url, {
253
270
  headers: mergeHeaders(undefined, authToken)
254
271
  });
255
272
  if (!response.ok)
@@ -266,10 +283,23 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
266
283
  return null;
267
284
  }
268
285
  }
286
+ function mergeCookie(existing, name, value) {
287
+ const encoded = `${name}=${encodeURIComponent(value)}`;
288
+ if (!existing?.trim())
289
+ return encoded;
290
+ const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
291
+ return [...parts, encoded].join("; ");
292
+ }
293
+ function queryAuthFallbackEnabled(env = process.env) {
294
+ return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
295
+ }
269
296
  function mergeHeaders(headers, authToken) {
270
297
  const merged = new Headers(headers);
271
298
  if (authToken) {
272
- merged.set("authorization", `Bearer ${authToken}`);
299
+ const bearer = `Bearer ${authToken}`;
300
+ merged.set("authorization", bearer);
301
+ merged.set("x-auth", bearer);
302
+ merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
273
303
  }
274
304
  return merged;
275
305
  }
@@ -330,15 +360,34 @@ async function buildServerFailureContext(projectRoot, server) {
330
360
  hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
331
361
  };
332
362
  }
363
+ function isLoopbackRemoteBaseUrl(baseUrl) {
364
+ try {
365
+ const host = new URL(baseUrl).hostname.toLowerCase();
366
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
367
+ } catch {
368
+ return false;
369
+ }
370
+ }
371
+ function canUseRemoteWithoutProjectRoot(pathname) {
372
+ return pathname === "/health" || pathname === "/api/health" || pathname === "/api/server/status" || pathname === "/api/server/project-root" || pathname.startsWith("/api/github/auth/") || pathname === "/api/projects" || pathname.startsWith("/api/projects/");
373
+ }
333
374
  async function requestServerJson(context, pathname, init = {}) {
334
375
  const server = await ensureServerForCli(context.projectRoot);
376
+ const requestUrl = new URL(`${server.baseUrl}${pathname}`);
377
+ if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
378
+ const repo = readRepoConnection(context.projectRoot);
379
+ throw new CliError(`Remote server is selected for ${repo?.project ?? "this repo"}, but this checkout has no server-host project root link.`, 1, { hint: "Run `rig init --repair` or `rig init --yes --repo owner/repo --server remote` to repair the remote project link before task/run commands." });
380
+ }
335
381
  const headers = mergeHeaders(init.headers, server.authToken);
336
382
  if (server.serverProjectRoot)
337
383
  headers.set("x-rig-project-root", server.serverProjectRoot);
384
+ if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
385
+ requestUrl.searchParams.set("rt", server.authToken);
386
+ }
338
387
  reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
339
388
  let response;
340
389
  try {
341
- response = await fetch(`${server.baseUrl}${pathname}`, {
390
+ response = await fetch(requestUrl, {
342
391
  ...init,
343
392
  headers
344
393
  });
@@ -205,17 +205,21 @@ function cleanToken(value) {
205
205
  const trimmed = value?.trim();
206
206
  return trimmed ? trimmed : null;
207
207
  }
208
- function readPrivateRemoteSessionToken(projectRoot) {
208
+ function readRemoteAuthState(projectRoot) {
209
209
  const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
210
210
  if (!existsSync2(path))
211
211
  return null;
212
212
  try {
213
213
  const parsed = JSON.parse(readFileSync2(path, "utf8"));
214
- return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
214
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
215
215
  } catch {
216
216
  return null;
217
217
  }
218
218
  }
219
+ function readPrivateRemoteSessionToken(projectRoot) {
220
+ const parsed = readRemoteAuthState(projectRoot);
221
+ return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
222
+ }
219
223
  function readGitHubBearerTokenForRemote(projectRoot) {
220
224
  const scopedKey = resolve2(projectRoot);
221
225
  if (scopedGitHubBearerTokens.has(scopedKey))
@@ -226,15 +230,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
226
230
  return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
227
231
  }
228
232
  function readStoredGitHubAuthToken(projectRoot) {
229
- const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
230
- if (!existsSync2(path))
233
+ const parsed = readRemoteAuthState(projectRoot);
234
+ return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
235
+ }
236
+ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
237
+ const repo = readRepoConnection(projectRoot);
238
+ const slug = repo?.project?.trim();
239
+ if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
231
240
  return null;
232
- try {
233
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
234
- return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
235
- } catch {
241
+ const auth = readRemoteAuthState(projectRoot);
242
+ const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
243
+ const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
244
+ if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
236
245
  return null;
237
- }
246
+ const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
247
+ if (!checkoutBaseDir)
248
+ return null;
249
+ const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
250
+ writeRepoServerProjectRoot(projectRoot, inferred);
251
+ return inferred;
238
252
  }
239
253
  function readLocalConnectionFallbackToken(projectRoot) {
240
254
  return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
@@ -245,7 +259,7 @@ async function ensureServerForCli(projectRoot) {
245
259
  if (selected?.connection.kind === "remote") {
246
260
  reportServerPhase(`Connecting to ${selected.alias}\u2026`);
247
261
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
248
- const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
262
+ const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
249
263
  return {
250
264
  baseUrl: selected.connection.baseUrl,
251
265
  authToken,
@@ -274,7 +288,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
274
288
  if (!slug)
275
289
  return null;
276
290
  try {
277
- const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
291
+ const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
292
+ if (authToken && queryAuthFallbackEnabled())
293
+ url.searchParams.set("rt", authToken);
294
+ const response = await fetch(url, {
278
295
  headers: mergeHeaders(undefined, authToken)
279
296
  });
280
297
  if (!response.ok)
@@ -291,10 +308,23 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
291
308
  return null;
292
309
  }
293
310
  }
311
+ function mergeCookie(existing, name, value) {
312
+ const encoded = `${name}=${encodeURIComponent(value)}`;
313
+ if (!existing?.trim())
314
+ return encoded;
315
+ const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
316
+ return [...parts, encoded].join("; ");
317
+ }
318
+ function queryAuthFallbackEnabled(env = process.env) {
319
+ return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
320
+ }
294
321
  function mergeHeaders(headers, authToken) {
295
322
  const merged = new Headers(headers);
296
323
  if (authToken) {
297
- merged.set("authorization", `Bearer ${authToken}`);
324
+ const bearer = `Bearer ${authToken}`;
325
+ merged.set("authorization", bearer);
326
+ merged.set("x-auth", bearer);
327
+ merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
298
328
  }
299
329
  return merged;
300
330
  }
@@ -355,15 +385,34 @@ async function buildServerFailureContext(projectRoot, server) {
355
385
  hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
356
386
  };
357
387
  }
388
+ function isLoopbackRemoteBaseUrl(baseUrl) {
389
+ try {
390
+ const host = new URL(baseUrl).hostname.toLowerCase();
391
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
392
+ } catch {
393
+ return false;
394
+ }
395
+ }
396
+ function canUseRemoteWithoutProjectRoot(pathname) {
397
+ return pathname === "/health" || pathname === "/api/health" || pathname === "/api/server/status" || pathname === "/api/server/project-root" || pathname.startsWith("/api/github/auth/") || pathname === "/api/projects" || pathname.startsWith("/api/projects/");
398
+ }
358
399
  async function requestServerJson(context, pathname, init = {}) {
359
400
  const server = await ensureServerForCli(context.projectRoot);
401
+ const requestUrl = new URL(`${server.baseUrl}${pathname}`);
402
+ if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
403
+ const repo = readRepoConnection(context.projectRoot);
404
+ throw new CliError(`Remote server is selected for ${repo?.project ?? "this repo"}, but this checkout has no server-host project root link.`, 1, { hint: "Run `rig init --repair` or `rig init --yes --repo owner/repo --server remote` to repair the remote project link before task/run commands." });
405
+ }
360
406
  const headers = mergeHeaders(init.headers, server.authToken);
361
407
  if (server.serverProjectRoot)
362
408
  headers.set("x-rig-project-root", server.serverProjectRoot);
409
+ if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
410
+ requestUrl.searchParams.set("rt", server.authToken);
411
+ }
363
412
  reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
364
413
  let response;
365
414
  try {
366
- response = await fetch(`${server.baseUrl}${pathname}`, {
415
+ response = await fetch(requestUrl, {
367
416
  ...init,
368
417
  headers
369
418
  });
@@ -1008,6 +1057,14 @@ async function withSpinner(label, work, options = {}) {
1008
1057
  }
1009
1058
 
1010
1059
  // packages/cli/src/commands/_pi-frontend.ts
1060
+ var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
1061
+
1062
+ class OperatorTranscriptUnavailableError extends Error {
1063
+ constructor(message) {
1064
+ super(message);
1065
+ this.name = "OperatorTranscriptUnavailableError";
1066
+ }
1067
+ }
1011
1068
  function setTemporaryEnv(updates) {
1012
1069
  const previous = new Map;
1013
1070
  for (const [key, value] of Object.entries(updates)) {
@@ -1035,6 +1092,23 @@ function buildOperatorPiEnv(input) {
1035
1092
  ...input.serverProjectRoot ? { RIG_PROJECT_ROOT: input.serverProjectRoot } : {}
1036
1093
  };
1037
1094
  }
1095
+ function shouldRequireOperatorTranscript(status) {
1096
+ return typeof status === "string" && TERMINAL_RUN_STATUSES.has(status.trim().toLowerCase());
1097
+ }
1098
+ function missingOperatorTranscriptMessage(runId, status) {
1099
+ const shortRun = runId.trim().slice(0, 8) || "unknown";
1100
+ const statusText = typeof status === "string" && status.trim() ? status.trim() : "terminal";
1101
+ return `Run ${shortRun} is ${statusText}, but no worker Pi session transcript is recorded for this run. It likely failed before Pi started or predates full-session capture. Use \`rig run show ${runId}\` and \`rig run logs ${runId}\` for the lifecycle details.`;
1102
+ }
1103
+ function statusFromRunDetails(run) {
1104
+ if (!run || typeof run !== "object" || Array.isArray(run))
1105
+ return;
1106
+ const record = run;
1107
+ if (typeof record.status === "string")
1108
+ return record.status;
1109
+ const nested = record.run;
1110
+ return nested && typeof nested === "object" && !Array.isArray(nested) ? nested.status : undefined;
1111
+ }
1038
1112
  async function prepareOperatorConsole(context, runId, tempSessionDir) {
1039
1113
  const server = await ensureServerForCli(context.projectRoot);
1040
1114
  const localCwd = join2(homedir2(), ".rig", "drones", runId.slice(0, 8));
@@ -1064,6 +1138,13 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
1064
1138
  sessionFileArg = ["--session", localSessionPath];
1065
1139
  }
1066
1140
  } catch {}
1141
+ if (sessionFileArg.length === 0) {
1142
+ const run = await getRunDetailsViaServer(context, runId).catch(() => null);
1143
+ const status = statusFromRunDetails(run);
1144
+ if (shouldRequireOperatorTranscript(status)) {
1145
+ throw new OperatorTranscriptUnavailableError(missingOperatorTranscriptMessage(runId, status));
1146
+ }
1147
+ }
1067
1148
  return { server, sessionFileArg };
1068
1149
  }
1069
1150
  var RIG_PI_THEME = {
@@ -1181,6 +1262,7 @@ async function attachRunBundledPiFrontend(context, input) {
1181
1262
  let detached = false;
1182
1263
  try {
1183
1264
  await runPiMain([
1265
+ "--offline",
1184
1266
  "--no-extensions",
1185
1267
  "--no-skills",
1186
1268
  "--no-prompt-templates",
@@ -1210,7 +1292,7 @@ async function attachRunBundledPiFrontend(context, input) {
1210
1292
  }
1211
1293
 
1212
1294
  // packages/cli/src/commands/_operator-view.ts
1213
- var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
1295
+ var TERMINAL_RUN_STATUSES2 = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
1214
1296
  function runStatusFromPayload(payload) {
1215
1297
  const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
1216
1298
  return String(run.status ?? "unknown").toLowerCase();
@@ -1288,7 +1370,7 @@ async function attachRunOperatorView(context, input) {
1288
1370
  }
1289
1371
  const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
1290
1372
  let timelineCursor = snapshot.timelineCursor;
1291
- while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
1373
+ while (!detached && !TERMINAL_RUN_STATUSES2.has(runStatusFromPayload(snapshot.run))) {
1292
1374
  await Bun.sleep(pollMs);
1293
1375
  snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
1294
1376
  timelineCursor = snapshot.timelineCursor;
@@ -1991,6 +2073,9 @@ async function executeRun(context, args) {
1991
2073
  console.log(`Stop requested: ${runId}`);
1992
2074
  return { ok: true, group: "run", command, details: stopped };
1993
2075
  }
2076
+ if (isRemoteConnectionSelected(context.projectRoot)) {
2077
+ throw new CliError("Remote run stop requires an explicit run id.", 2, { hint: "Run `rig run list`, then `rig run stop <run-id>`." });
2078
+ }
1994
2079
  const result = await runStop(context.projectRoot);
1995
2080
  if (result.remaining.length > 0) {
1996
2081
  throw new CliError(`Failed to stop run(s): ${result.remaining.join(", ")}`, 1, { hint: "Check `rig run status`, then retry `rig run stop <run-id>` for the remaining runs." });