@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
@@ -138,17 +138,21 @@ function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
138
138
  const scopedKey = resolve2(projectRoot ?? process.cwd());
139
139
  scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
140
140
  }
141
- function readPrivateRemoteSessionToken(projectRoot) {
141
+ function readRemoteAuthState(projectRoot) {
142
142
  const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
143
143
  if (!existsSync2(path))
144
144
  return null;
145
145
  try {
146
146
  const parsed = JSON.parse(readFileSync2(path, "utf8"));
147
- return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
147
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
148
148
  } catch {
149
149
  return null;
150
150
  }
151
151
  }
152
+ function readPrivateRemoteSessionToken(projectRoot) {
153
+ const parsed = readRemoteAuthState(projectRoot);
154
+ return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
155
+ }
152
156
  function readGitHubBearerTokenForRemote(projectRoot) {
153
157
  const scopedKey = resolve2(projectRoot);
154
158
  if (scopedGitHubBearerTokens.has(scopedKey))
@@ -159,15 +163,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
159
163
  return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
160
164
  }
161
165
  function readStoredGitHubAuthToken(projectRoot) {
162
- const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
163
- if (!existsSync2(path))
166
+ const parsed = readRemoteAuthState(projectRoot);
167
+ return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
168
+ }
169
+ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
170
+ const repo = readRepoConnection(projectRoot);
171
+ const slug = repo?.project?.trim();
172
+ if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
164
173
  return null;
165
- try {
166
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
167
- return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
168
- } catch {
174
+ const auth = readRemoteAuthState(projectRoot);
175
+ const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
176
+ const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
177
+ if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
169
178
  return null;
170
- }
179
+ const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
180
+ if (!checkoutBaseDir)
181
+ return null;
182
+ const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
183
+ writeRepoServerProjectRoot(projectRoot, inferred);
184
+ return inferred;
171
185
  }
172
186
  function readLocalConnectionFallbackToken(projectRoot) {
173
187
  return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
@@ -178,7 +192,7 @@ async function ensureServerForCli(projectRoot) {
178
192
  if (selected?.connection.kind === "remote") {
179
193
  reportServerPhase(`Connecting to ${selected.alias}\u2026`);
180
194
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
181
- const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
195
+ const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
182
196
  return {
183
197
  baseUrl: selected.connection.baseUrl,
184
198
  authToken,
@@ -207,7 +221,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
207
221
  if (!slug)
208
222
  return null;
209
223
  try {
210
- const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
224
+ const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
225
+ if (authToken && queryAuthFallbackEnabled())
226
+ url.searchParams.set("rt", authToken);
227
+ const response = await fetch(url, {
211
228
  headers: mergeHeaders(undefined, authToken)
212
229
  });
213
230
  if (!response.ok)
@@ -234,10 +251,23 @@ function appendTaskFilterParams(url, filters) {
234
251
  if (filters.limit !== undefined)
235
252
  url.searchParams.set("limit", String(filters.limit));
236
253
  }
254
+ function mergeCookie(existing, name, value) {
255
+ const encoded = `${name}=${encodeURIComponent(value)}`;
256
+ if (!existing?.trim())
257
+ return encoded;
258
+ const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
259
+ return [...parts, encoded].join("; ");
260
+ }
261
+ function queryAuthFallbackEnabled(env = process.env) {
262
+ return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
263
+ }
237
264
  function mergeHeaders(headers, authToken) {
238
265
  const merged = new Headers(headers);
239
266
  if (authToken) {
240
- merged.set("authorization", `Bearer ${authToken}`);
267
+ const bearer = `Bearer ${authToken}`;
268
+ merged.set("authorization", bearer);
269
+ merged.set("x-auth", bearer);
270
+ merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
241
271
  }
242
272
  return merged;
243
273
  }
@@ -298,15 +328,34 @@ async function buildServerFailureContext(projectRoot, server) {
298
328
  hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
299
329
  };
300
330
  }
331
+ function isLoopbackRemoteBaseUrl(baseUrl) {
332
+ try {
333
+ const host = new URL(baseUrl).hostname.toLowerCase();
334
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
335
+ } catch {
336
+ return false;
337
+ }
338
+ }
339
+ function canUseRemoteWithoutProjectRoot(pathname) {
340
+ 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/");
341
+ }
301
342
  async function requestServerJson(context, pathname, init = {}) {
302
343
  const server = await ensureServerForCli(context.projectRoot);
344
+ const requestUrl = new URL(`${server.baseUrl}${pathname}`);
345
+ if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
346
+ const repo = readRepoConnection(context.projectRoot);
347
+ 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." });
348
+ }
303
349
  const headers = mergeHeaders(init.headers, server.authToken);
304
350
  if (server.serverProjectRoot)
305
351
  headers.set("x-rig-project-root", server.serverProjectRoot);
352
+ if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
353
+ requestUrl.searchParams.set("rt", server.authToken);
354
+ }
306
355
  reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
307
356
  let response;
308
357
  try {
309
- response = await fetch(`${server.baseUrl}${pathname}`, {
358
+ response = await fetch(requestUrl, {
310
359
  ...init,
311
360
  headers
312
361
  });
@@ -368,11 +417,26 @@ async function getGitHubAuthStatusViaServer(context) {
368
417
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
369
418
  }
370
419
  async function postGitHubTokenViaServer(context, token, options = {}) {
371
- const payload = await requestServerJson(context, "/api/github/auth/token", {
372
- method: "POST",
373
- headers: { "content-type": "application/json" },
374
- body: JSON.stringify({ token, selectedRepo: options.selectedRepo, projectRoot: options.projectRoot })
375
- });
420
+ const server = await ensureServerForCli(context.projectRoot);
421
+ const requestUrl = new URL(`${server.baseUrl}/api/github/auth/token`);
422
+ reportServerPhase("POST /api/github/auth/token\u2026");
423
+ let response;
424
+ try {
425
+ response = await fetch(requestUrl, {
426
+ method: "POST",
427
+ headers: { "content-type": "application/json" },
428
+ body: JSON.stringify({ token, selectedRepo: options.selectedRepo, projectRoot: options.projectRoot ?? server.serverProjectRoot ?? undefined })
429
+ });
430
+ } catch (error) {
431
+ const failure = await buildServerFailureContext(context.projectRoot, server);
432
+ throw new CliError(`Rig server auth bootstrap failed: ${error instanceof Error ? error.message : String(error)}
433
+ ${failure.contextLine}`, 1, { hint: failure.hint });
434
+ }
435
+ const payload = await response.json().catch(() => null);
436
+ if (!response.ok) {
437
+ const detail = payload && typeof payload === "object" && !Array.isArray(payload) ? String(payload.error ?? JSON.stringify(payload)) : `HTTP ${response.status}`;
438
+ 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`." });
439
+ }
376
440
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
377
441
  }
378
442
  async function prepareRemoteCheckoutViaServer(context, input) {
@@ -0,0 +1,26 @@
1
+ export type RigLiveStatus = "connecting" | "connected" | "disconnected";
2
+ export type RigLiveHandlers = {
3
+ /** Any snapshot-affecting change (run/task/inbox state). The canonical
4
+ * "something changed, re-pull what you show" signal. */
5
+ readonly onSnapshotInvalidated?: (payload: unknown) => void;
6
+ /** A run appended a log/timeline line — for live run-detail tails. */
7
+ readonly onRunLogAppended?: (payload: unknown) => void;
8
+ /** Sequence-numbered engine domain events. */
9
+ readonly onEngineEvent?: (payload: unknown) => void;
10
+ /** Socket health transitions; callers gate their polling fallback on this. */
11
+ readonly onStatus?: (status: RigLiveStatus) => void;
12
+ };
13
+ export type RigLiveSubscription = {
14
+ /** True while the push socket is connected — gate HTTP fallback polling on it. */
15
+ readonly connected: () => boolean;
16
+ readonly close: () => void;
17
+ };
18
+ /** http(s):// → ws(s)://, with the auth token as the `token` query param the
19
+ * server's upgrade handler authenticates against (packages/server/src/
20
+ * websocket.ts). Mirrors pi-rig's buildRigWebSocketUrl so every surface speaks
21
+ * the same dialect. */
22
+ export declare function buildRigWebSocketUrl(baseUrl: string, authToken: string | null): string;
23
+ /** Open a reconnecting push subscription to the Rig server for a project root.
24
+ * Resolves the same connection (local/remote + auth) the HTTP `…ViaServer`
25
+ * calls use, so the socket targets the exact server the surface reads from. */
26
+ export declare function connectRigServerEvents(projectRoot: string, handlers: RigLiveHandlers): Promise<RigLiveSubscription>;
@@ -0,0 +1,310 @@
1
+ // @bun
2
+ // packages/cli/src/commands/_server-events.ts
3
+ import { WsTransport } from "@rig/client";
4
+ import { RIG_WS_CHANNELS } from "@rig/contracts";
5
+
6
+ // packages/cli/src/commands/_server-client.ts
7
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
8
+ import { resolve as resolve2 } from "path";
9
+
10
+ // packages/cli/src/runner.ts
11
+ import { EventBus } from "@rig/runtime/control-plane/runtime/events";
12
+ import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
13
+ import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
14
+ import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
15
+
16
+ class CliError extends RuntimeCliError {
17
+ hint;
18
+ constructor(message, exitCode = 1, options = {}) {
19
+ super(message, exitCode);
20
+ if (options.hint?.trim()) {
21
+ this.hint = options.hint.trim();
22
+ }
23
+ }
24
+ }
25
+
26
+ // packages/cli/src/commands/_server-client.ts
27
+ import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
28
+
29
+ // packages/cli/src/commands/_connection-state.ts
30
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
31
+ import { homedir } from "os";
32
+ import { dirname, resolve } from "path";
33
+ function resolveGlobalConnectionsPath(env = process.env) {
34
+ const explicit = env.RIG_CONNECTIONS_FILE?.trim();
35
+ if (explicit)
36
+ return resolve(explicit);
37
+ const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
38
+ if (stateDir)
39
+ return resolve(stateDir, "connections.json");
40
+ return resolve(homedir(), ".rig", "connections.json");
41
+ }
42
+ function resolveRepoConnectionPath(projectRoot) {
43
+ return resolve(projectRoot, ".rig", "state", "connection.json");
44
+ }
45
+ function readJsonFile(path) {
46
+ if (!existsSync(path))
47
+ return null;
48
+ try {
49
+ return JSON.parse(readFileSync(path, "utf8"));
50
+ } catch (error) {
51
+ throw new CliError(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1, { hint: "Fix or delete that file, then re-select a server with `rig server use <alias|local>`." });
52
+ }
53
+ }
54
+ function writeJsonFile(path, value) {
55
+ mkdirSync(dirname(path), { recursive: true });
56
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
57
+ `, "utf8");
58
+ }
59
+ function normalizeConnection(value) {
60
+ if (!value || typeof value !== "object" || Array.isArray(value))
61
+ return null;
62
+ const record = value;
63
+ if (record.kind === "local")
64
+ return { kind: "local", mode: "auto" };
65
+ if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
66
+ const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
67
+ return { kind: "remote", baseUrl };
68
+ }
69
+ return null;
70
+ }
71
+ function readGlobalConnections(options = {}) {
72
+ const path = resolveGlobalConnectionsPath(options.env ?? process.env);
73
+ const payload = readJsonFile(path);
74
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
75
+ return { connections: {} };
76
+ }
77
+ const rawConnections = payload.connections;
78
+ const connections = {};
79
+ if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
80
+ for (const [alias, raw] of Object.entries(rawConnections)) {
81
+ const connection = normalizeConnection(raw);
82
+ if (connection)
83
+ connections[alias] = connection;
84
+ }
85
+ }
86
+ return { connections };
87
+ }
88
+ function readRepoConnection(projectRoot) {
89
+ const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
90
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
91
+ return null;
92
+ const record = payload;
93
+ const selected = typeof record.selected === "string" ? record.selected.trim() : "";
94
+ if (!selected)
95
+ return null;
96
+ return {
97
+ selected,
98
+ project: typeof record.project === "string" ? record.project : undefined,
99
+ linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
100
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
101
+ };
102
+ }
103
+ function writeRepoConnection(projectRoot, state) {
104
+ writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
105
+ }
106
+ function resolveSelectedConnection(projectRoot, options = {}) {
107
+ const repo = readRepoConnection(projectRoot);
108
+ if (!repo)
109
+ return null;
110
+ if (repo.selected === "local")
111
+ return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
112
+ const global = readGlobalConnections(options);
113
+ const connection = global.connections[repo.selected];
114
+ if (!connection) {
115
+ throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
116
+ }
117
+ return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
118
+ }
119
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
120
+ const repo = readRepoConnection(projectRoot);
121
+ if (!repo)
122
+ return;
123
+ writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
124
+ }
125
+
126
+ // packages/cli/src/commands/_server-client.ts
127
+ var scopedGitHubBearerTokens = new Map;
128
+ var serverPhaseListener = null;
129
+ function reportServerPhase(label) {
130
+ serverPhaseListener?.(label);
131
+ }
132
+ function cleanToken(value) {
133
+ const trimmed = value?.trim();
134
+ return trimmed ? trimmed : null;
135
+ }
136
+ function readRemoteAuthState(projectRoot) {
137
+ const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
138
+ if (!existsSync2(path))
139
+ return null;
140
+ try {
141
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
142
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
143
+ } catch {
144
+ return null;
145
+ }
146
+ }
147
+ function readPrivateRemoteSessionToken(projectRoot) {
148
+ const parsed = readRemoteAuthState(projectRoot);
149
+ return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
150
+ }
151
+ function readGitHubBearerTokenForRemote(projectRoot) {
152
+ const scopedKey = resolve2(projectRoot);
153
+ if (scopedGitHubBearerTokens.has(scopedKey))
154
+ return scopedGitHubBearerTokens.get(scopedKey) ?? null;
155
+ const privateSession = readPrivateRemoteSessionToken(projectRoot);
156
+ if (privateSession)
157
+ return privateSession;
158
+ return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
159
+ }
160
+ function readStoredGitHubAuthToken(projectRoot) {
161
+ const parsed = readRemoteAuthState(projectRoot);
162
+ return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
163
+ }
164
+ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
165
+ const repo = readRepoConnection(projectRoot);
166
+ const slug = repo?.project?.trim();
167
+ if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
168
+ return null;
169
+ const auth = readRemoteAuthState(projectRoot);
170
+ const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
171
+ const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
172
+ if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
173
+ return null;
174
+ const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
175
+ if (!checkoutBaseDir)
176
+ return null;
177
+ const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
178
+ writeRepoServerProjectRoot(projectRoot, inferred);
179
+ return inferred;
180
+ }
181
+ function readLocalConnectionFallbackToken(projectRoot) {
182
+ return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
183
+ }
184
+ async function ensureServerForCli(projectRoot) {
185
+ try {
186
+ const selected = resolveSelectedConnection(projectRoot);
187
+ if (selected?.connection.kind === "remote") {
188
+ reportServerPhase(`Connecting to ${selected.alias}\u2026`);
189
+ const authToken = readGitHubBearerTokenForRemote(projectRoot);
190
+ const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
191
+ return {
192
+ baseUrl: selected.connection.baseUrl,
193
+ authToken,
194
+ connectionKind: "remote",
195
+ serverProjectRoot
196
+ };
197
+ }
198
+ reportServerPhase("Starting local Rig server\u2026");
199
+ const connection = await ensureLocalRigServerConnection(projectRoot);
200
+ return {
201
+ baseUrl: connection.baseUrl,
202
+ authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
203
+ connectionKind: "local",
204
+ serverProjectRoot: resolve2(projectRoot)
205
+ };
206
+ } catch (error) {
207
+ if (error instanceof Error) {
208
+ throw new CliError(error.message, 1);
209
+ }
210
+ throw error;
211
+ }
212
+ }
213
+ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
214
+ const repo = readRepoConnection(projectRoot);
215
+ const slug = repo?.project?.trim();
216
+ if (!slug)
217
+ return null;
218
+ try {
219
+ const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
220
+ if (authToken && queryAuthFallbackEnabled())
221
+ url.searchParams.set("rt", authToken);
222
+ const response = await fetch(url, {
223
+ headers: mergeHeaders(undefined, authToken)
224
+ });
225
+ if (!response.ok)
226
+ return null;
227
+ const payload = await response.json();
228
+ const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
229
+ const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
230
+ const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
231
+ const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
232
+ if (path)
233
+ writeRepoServerProjectRoot(projectRoot, path);
234
+ return path;
235
+ } catch {
236
+ return null;
237
+ }
238
+ }
239
+ function mergeCookie(existing, name, value) {
240
+ const encoded = `${name}=${encodeURIComponent(value)}`;
241
+ if (!existing?.trim())
242
+ return encoded;
243
+ const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
244
+ return [...parts, encoded].join("; ");
245
+ }
246
+ function queryAuthFallbackEnabled(env = process.env) {
247
+ return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
248
+ }
249
+ function mergeHeaders(headers, authToken) {
250
+ const merged = new Headers(headers);
251
+ if (authToken) {
252
+ const bearer = `Bearer ${authToken}`;
253
+ merged.set("authorization", bearer);
254
+ merged.set("x-auth", bearer);
255
+ merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
256
+ }
257
+ return merged;
258
+ }
259
+ var serverReachabilityCache = new Map;
260
+ var RESUMABLE_RUN_STATUSES = new Set([
261
+ "created",
262
+ "preparing",
263
+ "running",
264
+ "validating",
265
+ "reviewing",
266
+ "stopped",
267
+ "failed",
268
+ "needs-attention",
269
+ "needs_attention"
270
+ ]);
271
+
272
+ // packages/cli/src/commands/_server-events.ts
273
+ function buildRigWebSocketUrl(baseUrl, authToken) {
274
+ const url = new URL(baseUrl);
275
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
276
+ if (authToken)
277
+ url.searchParams.set("token", authToken);
278
+ return url.toString();
279
+ }
280
+ async function connectRigServerEvents(projectRoot, handlers) {
281
+ const { baseUrl, authToken } = await ensureServerForCli(projectRoot);
282
+ const transport = new WsTransport(buildRigWebSocketUrl(baseUrl, authToken));
283
+ const unsubscribers = [];
284
+ if (handlers.onSnapshotInvalidated)
285
+ unsubscribers.push(transport.subscribe(RIG_WS_CHANNELS.snapshotInvalidated, handlers.onSnapshotInvalidated));
286
+ if (handlers.onRunLogAppended)
287
+ unsubscribers.push(transport.subscribe(RIG_WS_CHANNELS.runLogAppended, handlers.onRunLogAppended));
288
+ if (handlers.onEngineEvent)
289
+ unsubscribers.push(transport.subscribe(RIG_WS_CHANNELS.event, handlers.onEngineEvent));
290
+ let status = "connecting";
291
+ unsubscribers.push(transport.subscribeState((state) => {
292
+ status = state.status;
293
+ handlers.onStatus?.(state.status);
294
+ }));
295
+ return {
296
+ connected: () => status === "connected",
297
+ close: () => {
298
+ for (const unsubscribe of unsubscribers) {
299
+ try {
300
+ unsubscribe();
301
+ } catch {}
302
+ }
303
+ transport.dispose();
304
+ }
305
+ };
306
+ }
307
+ export {
308
+ connectRigServerEvents,
309
+ buildRigWebSocketUrl
310
+ };
@@ -133,17 +133,21 @@ function cleanToken(value) {
133
133
  const trimmed = value?.trim();
134
134
  return trimmed ? trimmed : null;
135
135
  }
136
- function readPrivateRemoteSessionToken(projectRoot) {
136
+ function readRemoteAuthState(projectRoot) {
137
137
  const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
138
138
  if (!existsSync2(path))
139
139
  return null;
140
140
  try {
141
141
  const parsed = JSON.parse(readFileSync2(path, "utf8"));
142
- return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
142
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
143
143
  } catch {
144
144
  return null;
145
145
  }
146
146
  }
147
+ function readPrivateRemoteSessionToken(projectRoot) {
148
+ const parsed = readRemoteAuthState(projectRoot);
149
+ return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
150
+ }
147
151
  function readGitHubBearerTokenForRemote(projectRoot) {
148
152
  const scopedKey = resolve2(projectRoot);
149
153
  if (scopedGitHubBearerTokens.has(scopedKey))
@@ -154,15 +158,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
154
158
  return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
155
159
  }
156
160
  function readStoredGitHubAuthToken(projectRoot) {
157
- const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
158
- if (!existsSync2(path))
161
+ const parsed = readRemoteAuthState(projectRoot);
162
+ return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
163
+ }
164
+ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
165
+ const repo = readRepoConnection(projectRoot);
166
+ const slug = repo?.project?.trim();
167
+ if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
159
168
  return null;
160
- try {
161
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
162
- return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
163
- } catch {
169
+ const auth = readRemoteAuthState(projectRoot);
170
+ const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
171
+ const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
172
+ if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
164
173
  return null;
165
- }
174
+ const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
175
+ if (!checkoutBaseDir)
176
+ return null;
177
+ const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
178
+ writeRepoServerProjectRoot(projectRoot, inferred);
179
+ return inferred;
166
180
  }
167
181
  function readLocalConnectionFallbackToken(projectRoot) {
168
182
  return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
@@ -173,7 +187,7 @@ async function ensureServerForCli(projectRoot) {
173
187
  if (selected?.connection.kind === "remote") {
174
188
  reportServerPhase(`Connecting to ${selected.alias}\u2026`);
175
189
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
176
- const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
190
+ const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
177
191
  return {
178
192
  baseUrl: selected.connection.baseUrl,
179
193
  authToken,
@@ -202,7 +216,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
202
216
  if (!slug)
203
217
  return null;
204
218
  try {
205
- const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
219
+ const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
220
+ if (authToken && queryAuthFallbackEnabled())
221
+ url.searchParams.set("rt", authToken);
222
+ const response = await fetch(url, {
206
223
  headers: mergeHeaders(undefined, authToken)
207
224
  });
208
225
  if (!response.ok)
@@ -219,10 +236,23 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
219
236
  return null;
220
237
  }
221
238
  }
239
+ function mergeCookie(existing, name, value) {
240
+ const encoded = `${name}=${encodeURIComponent(value)}`;
241
+ if (!existing?.trim())
242
+ return encoded;
243
+ const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
244
+ return [...parts, encoded].join("; ");
245
+ }
246
+ function queryAuthFallbackEnabled(env = process.env) {
247
+ return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
248
+ }
222
249
  function mergeHeaders(headers, authToken) {
223
250
  const merged = new Headers(headers);
224
251
  if (authToken) {
225
- merged.set("authorization", `Bearer ${authToken}`);
252
+ const bearer = `Bearer ${authToken}`;
253
+ merged.set("authorization", bearer);
254
+ merged.set("x-auth", bearer);
255
+ merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
226
256
  }
227
257
  return merged;
228
258
  }
@@ -283,15 +313,34 @@ async function buildServerFailureContext(projectRoot, server) {
283
313
  hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
284
314
  };
285
315
  }
316
+ function isLoopbackRemoteBaseUrl(baseUrl) {
317
+ try {
318
+ const host = new URL(baseUrl).hostname.toLowerCase();
319
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
320
+ } catch {
321
+ return false;
322
+ }
323
+ }
324
+ function canUseRemoteWithoutProjectRoot(pathname) {
325
+ 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/");
326
+ }
286
327
  async function requestServerJson(context, pathname, init = {}) {
287
328
  const server = await ensureServerForCli(context.projectRoot);
329
+ const requestUrl = new URL(`${server.baseUrl}${pathname}`);
330
+ if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
331
+ const repo = readRepoConnection(context.projectRoot);
332
+ 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." });
333
+ }
288
334
  const headers = mergeHeaders(init.headers, server.authToken);
289
335
  if (server.serverProjectRoot)
290
336
  headers.set("x-rig-project-root", server.serverProjectRoot);
337
+ if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
338
+ requestUrl.searchParams.set("rt", server.authToken);
339
+ }
291
340
  reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
292
341
  let response;
293
342
  try {
294
- response = await fetch(`${server.baseUrl}${pathname}`, {
343
+ response = await fetch(requestUrl, {
295
344
  ...init,
296
345
  headers
297
346
  });
@@ -82,18 +82,8 @@ function resolveControlPlaneMonorepoRoot(projectRoot) {
82
82
 
83
83
  // packages/cli/src/commands/_authority-runs.ts
84
84
  var RIG_WORKSPACE_ID = "rig-local-workspace";
85
- function normalizeRuntimeAdapter(value) {
86
- const normalized = value?.trim().toLowerCase();
87
- if (!normalized) {
88
- return "pi";
89
- }
90
- if (normalized === "codex" || normalized === "codex-cli" || normalized === "codex-app-server" || normalized === "gpt-codex") {
91
- return "codex";
92
- }
93
- if (normalized === "pi" || normalized === "rig-pi" || normalized === "@rig/pi") {
94
- return "pi";
95
- }
96
- return "claude-code";
85
+ function normalizeRuntimeAdapter(_value) {
86
+ return "pi";
97
87
  }
98
88
  function readLatestBeadRecord(projectRoot, taskId) {
99
89
  const issuesPath = resolve(resolveControlPlaneMonorepoRoot(projectRoot), ".beads", "issues.jsonl");