@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
@@ -15,6 +15,70 @@ var __export = (target, all) => {
15
15
  };
16
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
 
18
+ // packages/cli/src/app-opentui/adapters/common.ts
19
+ function projectRootOf(ctx) {
20
+ const root = ctx.rig?.projectRoot ?? ctx.projectRoot ?? ctx.getState().projectRoot;
21
+ if (typeof root === "string" && root.trim())
22
+ return root;
23
+ throw new Error("App adapter requires a projectRoot.");
24
+ }
25
+ function normalizeAppError(error) {
26
+ const message = error instanceof Error ? error.message : String(error);
27
+ const rawHint = error instanceof Error ? error.hint : undefined;
28
+ return {
29
+ message,
30
+ ...typeof rawHint === "string" && rawHint.trim() ? { hint: rawHint.trim() } : {}
31
+ };
32
+ }
33
+ function emitStarted(ctx, label, optimistic) {
34
+ ctx.emit({ type: "action.started", label, ...optimistic ? { optimistic } : {} });
35
+ }
36
+ function emitProgress(ctx, label, detail, data) {
37
+ ctx.emit({ type: "action.progress", label, ...detail ? { detail } : {}, ...data ? { data } : {} });
38
+ }
39
+ function emitCompleted(ctx, label, data) {
40
+ ctx.emit({ type: "action.completed", label, ...data ? { data } : {} });
41
+ }
42
+ function emitFailed(ctx, label, error, data) {
43
+ if (data)
44
+ patchData(ctx, data);
45
+ const normalized = normalizeAppError(error);
46
+ ctx.emit({
47
+ type: "action.failed",
48
+ label,
49
+ message: normalized.message,
50
+ ...normalized.hint ? { hint: normalized.hint } : {},
51
+ cause: error
52
+ });
53
+ }
54
+ function patchData(ctx, data) {
55
+ ctx.emit({ type: "data.patch", data });
56
+ }
57
+ function patchFooter(ctx, footer) {
58
+ ctx.emit({ type: "footer.patch", footer });
59
+ }
60
+ function arrayFromPayload(value) {
61
+ if (Array.isArray(value)) {
62
+ return value.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
63
+ }
64
+ if (value && typeof value === "object" && !Array.isArray(value)) {
65
+ const entries = value.entries;
66
+ if (Array.isArray(entries))
67
+ return arrayFromPayload(entries);
68
+ }
69
+ return [];
70
+ }
71
+ function stringField(record, keys, fallback = "") {
72
+ for (const key of keys) {
73
+ const value = record[key];
74
+ if (typeof value === "string" && value.trim())
75
+ return value.trim();
76
+ if (typeof value === "number" && Number.isFinite(value))
77
+ return String(value);
78
+ }
79
+ return fallback;
80
+ }
81
+
18
82
  // packages/cli/src/runner.ts
19
83
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
20
84
  import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
@@ -192,17 +256,21 @@ function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
192
256
  const scopedKey = resolve2(projectRoot ?? process.cwd());
193
257
  scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
194
258
  }
195
- function readPrivateRemoteSessionToken(projectRoot) {
259
+ function readRemoteAuthState(projectRoot) {
196
260
  const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
197
261
  if (!existsSync2(path))
198
262
  return null;
199
263
  try {
200
264
  const parsed = JSON.parse(readFileSync2(path, "utf8"));
201
- return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
265
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
202
266
  } catch {
203
267
  return null;
204
268
  }
205
269
  }
270
+ function readPrivateRemoteSessionToken(projectRoot) {
271
+ const parsed = readRemoteAuthState(projectRoot);
272
+ return parsed ? cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined) : null;
273
+ }
206
274
  function readGitHubBearerTokenForRemote(projectRoot) {
207
275
  const scopedKey = resolve2(projectRoot);
208
276
  if (scopedGitHubBearerTokens.has(scopedKey))
@@ -213,15 +281,25 @@ function readGitHubBearerTokenForRemote(projectRoot) {
213
281
  return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
214
282
  }
215
283
  function readStoredGitHubAuthToken(projectRoot) {
216
- const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
217
- if (!existsSync2(path))
284
+ const parsed = readRemoteAuthState(projectRoot);
285
+ return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
286
+ }
287
+ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
288
+ const repo = readRepoConnection(projectRoot);
289
+ const slug = repo?.project?.trim();
290
+ if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
218
291
  return null;
219
- try {
220
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
221
- return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
222
- } catch {
292
+ const auth = readRemoteAuthState(projectRoot);
293
+ const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
294
+ const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
295
+ if (Number.isFinite(authUpdatedAt) && Number.isFinite(repoLinkedAt) && authUpdatedAt + 1000 < repoLinkedAt)
223
296
  return null;
224
- }
297
+ const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
298
+ if (!checkoutBaseDir)
299
+ return null;
300
+ const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
301
+ writeRepoServerProjectRoot(projectRoot, inferred);
302
+ return inferred;
225
303
  }
226
304
  function readLocalConnectionFallbackToken(projectRoot) {
227
305
  return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
@@ -232,7 +310,7 @@ async function ensureServerForCli(projectRoot) {
232
310
  if (selected?.connection.kind === "remote") {
233
311
  reportServerPhase(`Connecting to ${selected.alias}\u2026`);
234
312
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
235
- const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
313
+ const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
236
314
  return {
237
315
  baseUrl: selected.connection.baseUrl,
238
316
  authToken,
@@ -261,7 +339,10 @@ async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken)
261
339
  if (!slug)
262
340
  return null;
263
341
  try {
264
- const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
342
+ const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
343
+ if (authToken && queryAuthFallbackEnabled())
344
+ url.searchParams.set("rt", authToken);
345
+ const response = await fetch(url, {
265
346
  headers: mergeHeaders(undefined, authToken)
266
347
  });
267
348
  if (!response.ok)
@@ -288,10 +369,23 @@ function appendTaskFilterParams(url, filters) {
288
369
  if (filters.limit !== undefined)
289
370
  url.searchParams.set("limit", String(filters.limit));
290
371
  }
372
+ function mergeCookie(existing, name, value) {
373
+ const encoded = `${name}=${encodeURIComponent(value)}`;
374
+ if (!existing?.trim())
375
+ return encoded;
376
+ const parts = existing.split(";").map((part) => part.trim()).filter((part) => part && !part.startsWith(`${name}=`));
377
+ return [...parts, encoded].join("; ");
378
+ }
379
+ function queryAuthFallbackEnabled(env = process.env) {
380
+ return env.RIG_ENABLE_QUERY_AUTH_FALLBACK === "1" || env.RIG_QUERY_AUTH_FALLBACK === "1";
381
+ }
291
382
  function mergeHeaders(headers, authToken) {
292
383
  const merged = new Headers(headers);
293
384
  if (authToken) {
294
- merged.set("authorization", `Bearer ${authToken}`);
385
+ const bearer = `Bearer ${authToken}`;
386
+ merged.set("authorization", bearer);
387
+ merged.set("x-auth", bearer);
388
+ merged.set("cookie", mergeCookie(merged.get("cookie"), "rig_auth", authToken));
295
389
  }
296
390
  return merged;
297
391
  }
@@ -351,15 +445,34 @@ async function buildServerFailureContext(projectRoot, server) {
351
445
  hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
352
446
  };
353
447
  }
448
+ function isLoopbackRemoteBaseUrl(baseUrl) {
449
+ try {
450
+ const host = new URL(baseUrl).hostname.toLowerCase();
451
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
452
+ } catch {
453
+ return false;
454
+ }
455
+ }
456
+ function canUseRemoteWithoutProjectRoot(pathname) {
457
+ 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/");
458
+ }
354
459
  async function requestServerJson(context, pathname, init = {}) {
355
460
  const server = await ensureServerForCli(context.projectRoot);
461
+ const requestUrl = new URL(`${server.baseUrl}${pathname}`);
462
+ if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
463
+ const repo = readRepoConnection(context.projectRoot);
464
+ 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." });
465
+ }
356
466
  const headers = mergeHeaders(init.headers, server.authToken);
357
467
  if (server.serverProjectRoot)
358
468
  headers.set("x-rig-project-root", server.serverProjectRoot);
469
+ if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
470
+ requestUrl.searchParams.set("rt", server.authToken);
471
+ }
359
472
  reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
360
473
  let response;
361
474
  try {
362
- response = await fetch(`${server.baseUrl}${pathname}`, {
475
+ response = await fetch(requestUrl, {
363
476
  ...init,
364
477
  headers
365
478
  });
@@ -421,11 +534,26 @@ async function getGitHubAuthStatusViaServer(context) {
421
534
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
422
535
  }
423
536
  async function postGitHubTokenViaServer(context, token, options = {}) {
424
- const payload = await requestServerJson(context, "/api/github/auth/token", {
425
- method: "POST",
426
- headers: { "content-type": "application/json" },
427
- body: JSON.stringify({ token, selectedRepo: options.selectedRepo, projectRoot: options.projectRoot })
428
- });
537
+ const server = await ensureServerForCli(context.projectRoot);
538
+ const requestUrl = new URL(`${server.baseUrl}/api/github/auth/token`);
539
+ reportServerPhase("POST /api/github/auth/token\u2026");
540
+ let response;
541
+ try {
542
+ response = await fetch(requestUrl, {
543
+ method: "POST",
544
+ headers: { "content-type": "application/json" },
545
+ body: JSON.stringify({ token, selectedRepo: options.selectedRepo, projectRoot: options.projectRoot ?? server.serverProjectRoot ?? undefined })
546
+ });
547
+ } catch (error) {
548
+ const failure = await buildServerFailureContext(context.projectRoot, server);
549
+ throw new CliError(`Rig server auth bootstrap failed: ${error instanceof Error ? error.message : String(error)}
550
+ ${failure.contextLine}`, 1, { hint: failure.hint });
551
+ }
552
+ const payload = await response.json().catch(() => null);
553
+ if (!response.ok) {
554
+ const detail = payload && typeof payload === "object" && !Array.isArray(payload) ? String(payload.error ?? JSON.stringify(payload)) : `HTTP ${response.status}`;
555
+ 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`." });
556
+ }
429
557
  return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
430
558
  }
431
559
  async function prepareRemoteCheckoutViaServer(context, input) {
@@ -702,72 +830,203 @@ var init__server_client = __esm(() => {
702
830
  ]);
703
831
  });
704
832
 
705
- // packages/cli/src/app-opentui/adapters/common.ts
706
- function adapterContextFromRuntime(runtime, renderer) {
707
- return {
708
- projectRoot: runtime.getState().projectRoot,
709
- renderer,
710
- ensureRuntime: () => runtime.getRunnerContext(),
711
- emit: (event) => runtime.emit(event),
712
- getState: () => runtime.getState()
713
- };
714
- }
715
- function projectRootOf(ctx) {
716
- const root = ctx.rig?.projectRoot ?? ctx.projectRoot ?? ctx.getState().projectRoot;
717
- if (typeof root === "string" && root.trim())
718
- return root;
719
- throw new Error("Rig app adapter requires a projectRoot.");
720
- }
721
- function normalizeAppError(error) {
722
- const message = error instanceof Error ? error.message : String(error);
723
- const rawHint = error instanceof Error ? error.hint : undefined;
724
- return {
725
- message,
726
- ...typeof rawHint === "string" && rawHint.trim() ? { hint: rawHint.trim() } : {}
727
- };
728
- }
729
- function emitStarted(ctx, label, optimistic) {
730
- ctx.emit({ type: "action.started", label, ...optimistic ? { optimistic } : {} });
731
- }
732
- function emitProgress(ctx, label, detail, data) {
733
- ctx.emit({ type: "action.progress", label, ...detail ? { detail } : {}, ...data ? { data } : {} });
833
+ // packages/cli/src/app-opentui/adapters/inspect.ts
834
+ var exports_inspect = {};
835
+ __export(exports_inspect, {
836
+ loadInspectRuns: () => loadInspectRuns,
837
+ loadInspect: () => loadInspect,
838
+ INSPECT_VIEWS: () => INSPECT_VIEWS
839
+ });
840
+ import { execFile } from "child_process";
841
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
842
+ import { resolve as resolve3 } from "path";
843
+ import { promisify } from "util";
844
+ import { resolveTaskArtifactDirs } from "@rig/runtime/control-plane/authority-files";
845
+ import { readTaskArtifactPreview } from "@rig/runtime/control-plane/native/workspace-ops";
846
+ import { resolveHarnessPaths, resolveMonorepoRoot, runCapture } from "@rig/runtime/control-plane/native/utils";
847
+ function isFailureEntry(entry) {
848
+ const status = `${entry.status ?? entry.level ?? entry.type ?? ""}`.toLowerCase();
849
+ return status.includes("fail") || status.includes("error");
850
+ }
851
+ async function gitDiff(projectRoot) {
852
+ try {
853
+ const { stdout } = await execFileAsync("git", ["diff", "--no-color"], { cwd: projectRoot, maxBuffer: 8 * 1024 * 1024 });
854
+ return { diff: stdout };
855
+ } catch (error) {
856
+ return { diff: "", error: normalizeAppError(error).message };
857
+ }
734
858
  }
735
- function emitCompleted(ctx, label, data) {
736
- ctx.emit({ type: "action.completed", label, ...data ? { data } : {} });
859
+ function listArtifacts(projectRoot, taskId) {
860
+ if (!taskId)
861
+ return { artifacts: [], error: "run has no task id \u2014 artifacts are resolved per task" };
862
+ try {
863
+ const dirs = resolveTaskArtifactDirs(projectRoot, taskId).filter((dir) => existsSync3(dir));
864
+ const out = [];
865
+ const seen = new Set;
866
+ for (const dir of dirs) {
867
+ for (const name of readdirSync(dir)) {
868
+ if (seen.has(name))
869
+ continue;
870
+ seen.add(name);
871
+ const path = resolve3(dir, name);
872
+ try {
873
+ const stat = statSync(path);
874
+ out.push({ name, path, sizeBytes: stat.size, isDir: stat.isDirectory() });
875
+ } catch {
876
+ out.push({ name, path, sizeBytes: 0, isDir: false });
877
+ }
878
+ }
879
+ }
880
+ out.sort((left, right) => left.name.localeCompare(right.name));
881
+ return { artifacts: out };
882
+ } catch (error) {
883
+ return { artifacts: [], error: normalizeAppError(error).message };
884
+ }
737
885
  }
738
- function emitFailed(ctx, label, error, data) {
739
- if (data)
740
- patchData(ctx, data);
741
- const normalized = normalizeAppError(error);
742
- ctx.emit({
743
- type: "action.failed",
744
- label,
745
- message: normalized.message,
746
- ...normalized.hint ? { hint: normalized.hint } : {},
747
- cause: error
748
- });
886
+ function readArtifact(projectRoot, taskId, fileName) {
887
+ if (!taskId)
888
+ return;
889
+ try {
890
+ const preview = readTaskArtifactPreview(projectRoot, taskId, fileName);
891
+ return { name: fileName, contents: preview.contents, truncated: preview.truncated, sizeBytes: preview.sizeBytes };
892
+ } catch (error) {
893
+ return { name: fileName, contents: `Failed to read artifact: ${normalizeAppError(error).message}`, truncated: false, sizeBytes: 0 };
894
+ }
749
895
  }
750
- function patchData(ctx, data) {
751
- ctx.emit({ type: "data.patch", data });
896
+ function loadGraph(projectRoot) {
897
+ try {
898
+ const result = runCapture(["br", "--no-db", "list", "--pretty"], resolveMonorepoRoot(projectRoot));
899
+ if (result.exitCode !== 0)
900
+ return { graph: "", error: result.stderr || result.stdout || "failed to inspect graph" };
901
+ return { graph: result.stdout };
902
+ } catch (error) {
903
+ return { graph: "", error: normalizeAppError(error).message };
904
+ }
752
905
  }
753
- function patchFooter(ctx, footer) {
754
- ctx.emit({ type: "footer.patch", footer });
906
+ function loadAudit(projectRoot) {
907
+ try {
908
+ const auditPath = resolve3(resolveHarnessPaths(projectRoot).logsDir, "audit.jsonl");
909
+ if (!existsSync3(auditPath))
910
+ return [];
911
+ return readFileSync3(auditPath, "utf-8").split(/\r?\n/).filter(Boolean).slice(-50).flatMap((raw) => {
912
+ try {
913
+ const parsed = JSON.parse(raw);
914
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? [parsed] : [{ detail: raw }];
915
+ } catch {
916
+ return [{ detail: raw }];
917
+ }
918
+ });
919
+ } catch {
920
+ return [];
921
+ }
755
922
  }
756
- function stringField(record, keys, fallback = "") {
757
- for (const key of keys) {
758
- const value = record[key];
759
- if (typeof value === "string" && value.trim())
760
- return value.trim();
761
- if (typeof value === "number" && Number.isFinite(value))
762
- return String(value);
923
+ async function loadInspectRuns(ctx, options = {}) {
924
+ const label = "Loading runs to inspect";
925
+ emitStarted(ctx, label);
926
+ try {
927
+ const projectRoot = projectRootOf(ctx);
928
+ const { listRunsViaServer: listRunsViaServer2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
929
+ emitProgress(ctx, label, "listing recent runs");
930
+ const runs = await listRunsViaServer2({ projectRoot }, { limit: options.limit ?? 50 });
931
+ const options_ = runs.flatMap((record) => {
932
+ const runId = stringField(record, ["runId", "id"]);
933
+ if (!runId)
934
+ return [];
935
+ const taskId = stringField(record, ["taskId"]);
936
+ const updatedAt = stringField(record, ["updatedAt", "createdAt"]);
937
+ return [{
938
+ runId,
939
+ status: stringField(record, ["status", "state"], "unknown"),
940
+ title: stringField(record, ["title", "taskTitle", "taskId"], "untitled run"),
941
+ ...taskId ? { taskId } : {},
942
+ ...updatedAt ? { updatedAt } : {}
943
+ }];
944
+ });
945
+ patchData(ctx, { inspectRuns: options_, lastRefreshError: undefined });
946
+ emitCompleted(ctx, label, { count: options_.length });
947
+ return options_;
948
+ } catch (error) {
949
+ emitFailed(ctx, label, error);
950
+ throw error;
763
951
  }
764
- return fallback;
765
952
  }
766
- function payloadString(intent, key) {
767
- const value = intent.action.payload?.[key];
768
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
953
+ async function loadInspect(ctx, runId) {
954
+ const cleanRunId = runId.trim();
955
+ const label = `Inspecting run ${cleanRunId.slice(0, 8)}`;
956
+ if (!cleanRunId) {
957
+ await loadInspectRuns(ctx).catch(() => []);
958
+ const error = new Error("Inspect requires a run id.");
959
+ throw error;
960
+ }
961
+ emitStarted(ctx, label);
962
+ try {
963
+ const projectRoot = projectRootOf(ctx);
964
+ const { getRunDetailsViaServer: getRunDetailsViaServer2, getRunLogsViaServer: getRunLogsViaServer2, getRunTimelineViaServer: getRunTimelineViaServer2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
965
+ const context = { projectRoot };
966
+ emitProgress(ctx, label, "loading logs, timeline, diff, artifacts, graph, and audit");
967
+ const [runPayload, logsPayload, timelinePayload, diffResult] = await Promise.all([
968
+ getRunDetailsViaServer2(context, cleanRunId).catch((error) => ({ error: normalizeAppError(error).message })),
969
+ getRunLogsViaServer2(context, cleanRunId, { limit: 500 }).catch(() => []),
970
+ getRunTimelineViaServer2(context, cleanRunId, { limit: 500 }).catch(() => []),
971
+ gitDiff(projectRoot)
972
+ ]);
973
+ const run = runPayload && typeof runPayload === "object" && "run" in runPayload && runPayload.run && typeof runPayload.run === "object" ? runPayload.run : runPayload;
974
+ const logs = arrayFromPayload(logsPayload);
975
+ const timeline = arrayFromPayload(timelinePayload);
976
+ const taskId = stringField(run, ["taskId"]) || undefined;
977
+ const { artifacts, error: artifactsError } = listArtifacts(projectRoot, taskId);
978
+ const { graph, error: graphError } = loadGraph(projectRoot);
979
+ const audit = loadAudit(projectRoot);
980
+ const state = ctx.getState();
981
+ const selectedArtifact = typeof state.data?.inspectArtifact === "string" ? state.data.inspectArtifact : undefined;
982
+ const artifactView = selectedArtifact ? readArtifact(projectRoot, taskId, selectedArtifact) : undefined;
983
+ const snapshot = {
984
+ runId: cleanRunId,
985
+ status: stringField(run, ["status", "state"], "unknown"),
986
+ title: stringField(run, ["title", "taskId", "id"], "untitled run"),
987
+ logs,
988
+ timeline,
989
+ diff: diffResult.diff,
990
+ failures: [...logs, ...timeline].filter(isFailureEntry),
991
+ artifacts,
992
+ graph,
993
+ audit,
994
+ refreshedAt: Date.now(),
995
+ ...taskId ? { taskId } : {},
996
+ ...artifactView ? { artifactView } : {},
997
+ ...diffResult.error ? { diffError: diffResult.error } : {},
998
+ ...graphError ? { graphError } : {},
999
+ ...artifactsError ? { artifactsError } : {}
1000
+ };
1001
+ patchData(ctx, { inspect: snapshot, lastRefreshError: undefined });
1002
+ emitCompleted(ctx, label, {
1003
+ runId: cleanRunId,
1004
+ logs: logs.length,
1005
+ timeline: timeline.length,
1006
+ diffBytes: snapshot.diff.length,
1007
+ artifacts: artifacts.length,
1008
+ audit: audit.length
1009
+ });
1010
+ return snapshot;
1011
+ } catch (error) {
1012
+ emitFailed(ctx, label, error, { runId: cleanRunId });
1013
+ throw error;
1014
+ }
769
1015
  }
770
-
1016
+ var execFileAsync, INSPECT_VIEWS;
1017
+ var init_inspect = __esm(() => {
1018
+ execFileAsync = promisify(execFile);
1019
+ INSPECT_VIEWS = [
1020
+ "logs",
1021
+ "timeline",
1022
+ "artifacts",
1023
+ "artifact",
1024
+ "diff",
1025
+ "failures",
1026
+ "graph",
1027
+ "audit"
1028
+ ];
1029
+ });
771
1030
  // packages/cli/src/app-opentui/pi-pty-host.ts
772
1031
  import { fileURLToPath } from "url";
773
1032
  import { basename } from "path";
@@ -777,8 +1036,8 @@ var MIN_COLS = 40;
777
1036
  var MIN_ROWS = 12;
778
1037
  var MAX_ROWS = 300;
779
1038
  var MAX_SNAPSHOT_LINES = 360;
780
- var STYLED_SNAPSHOT_MARGIN = 24;
781
- var SNAPSHOT_DELAY_MS = 33;
1039
+ var STYLED_SNAPSHOT_MARGIN = 6;
1040
+ var SNAPSHOT_DELAY_MS = 120;
782
1041
  var fallbackChildScriptPath = fileURLToPath(new URL("./pi-host-child.ts", import.meta.url));
783
1042
  var activeHost = null;
784
1043
  function clampCols(cols) {
@@ -876,17 +1135,15 @@ function lineToStyledText(line, cols) {
876
1135
  function childCommandPrefix() {
877
1136
  const execName = basename(process.execPath).toLowerCase();
878
1137
  const currentEntry = process.argv[1];
879
- if ((execName === "bun" || execName === "bun.exe") && currentEntry) {
880
- return [process.execPath, currentEntry, "__opentui-pi-host"];
1138
+ if (execName === "bun" || execName === "bun.exe") {
1139
+ return currentEntry ? [process.execPath, currentEntry, "__opentui-pi-host"] : [process.execPath, fallbackChildScriptPath];
881
1140
  }
882
- if (currentEntry)
883
- return [process.execPath, "__opentui-pi-host"];
884
- return [process.execPath, fallbackChildScriptPath];
1141
+ return [process.execPath, "__opentui-pi-host"];
885
1142
  }
886
1143
  function withEnv(base) {
887
1144
  const env = {};
888
1145
  for (const [key, value] of Object.entries(base ?? process.env)) {
889
- if (key === "RIG_PLAIN" || key === "RIG_CLI_PLAIN_HELP")
1146
+ if (key === "RIG_PLAIN" || key === "RIG_CLI_PLAIN_HELP" || key === "NO_COLOR")
890
1147
  continue;
891
1148
  if (typeof value === "string")
892
1149
  env[key] = value;
@@ -921,9 +1178,11 @@ class PiPtyHost {
921
1178
  cols;
922
1179
  rows;
923
1180
  message = "starting bundled Pi";
1181
+ lastResizeError = null;
924
1182
  exitCode;
925
1183
  signal;
926
1184
  notifyTimer = null;
1185
+ lastStreamKey = "";
927
1186
  _disposed = false;
928
1187
  constructor(options) {
929
1188
  this.runId = options.runId;
@@ -937,9 +1196,20 @@ class PiPtyHost {
937
1196
  allowProposedApi: true,
938
1197
  cols: this.cols,
939
1198
  rows: this.rows,
940
- scrollback: 5000
1199
+ scrollback: 1000
941
1200
  });
942
1201
  this.registerTerminalResponders();
1202
+ this.disposables.push(this.terminal.onWriteParsed(() => {
1203
+ if (this._disposed)
1204
+ return;
1205
+ const snapshot = this.createSnapshot();
1206
+ const key = snapshot.lines.join(`
1207
+ `);
1208
+ if (key === this.lastStreamKey)
1209
+ return;
1210
+ this.lastStreamKey = key;
1211
+ this.onSnapshot?.(snapshot);
1212
+ }));
943
1213
  }
944
1214
  get disposed() {
945
1215
  return this._disposed;
@@ -951,7 +1221,7 @@ class PiPtyHost {
951
1221
  if (this._disposed)
952
1222
  throw new Error("Pi PTY host is disposed.");
953
1223
  if (typeof Bun.Terminal !== "function") {
954
- throw new Error("Bun native PTY is unavailable in this runtime. Rig OpenTUI Pi host requires Bun.Terminal.");
1224
+ throw new Error("Bun native PTY is unavailable in this runtime. Pi host requires Bun.Terminal.");
955
1225
  }
956
1226
  const spawnOptions = {
957
1227
  cwd: this.projectRoot,
@@ -975,7 +1245,7 @@ class PiPtyHost {
975
1245
  this.proc = proc;
976
1246
  this.pty = proc.terminal;
977
1247
  this.status = "running";
978
- this.message = "bundled Pi running inside Rig";
1248
+ this.message = "bundled Pi running inside this app";
979
1249
  this.emitSnapshotSoon(0);
980
1250
  proc.exited.then((exitCode) => {
981
1251
  if (this._disposed)
@@ -1026,7 +1296,10 @@ class PiPtyHost {
1026
1296
  this.terminal.resize(nextCols, nextRows);
1027
1297
  try {
1028
1298
  this.pty?.resize(nextCols, nextRows);
1029
- } catch {}
1299
+ this.lastResizeError = null;
1300
+ } catch (error) {
1301
+ this.lastResizeError = error instanceof Error ? error.message : String(error);
1302
+ }
1030
1303
  this.emitSnapshotSoon(0);
1031
1304
  }
1032
1305
  detach() {
@@ -1068,7 +1341,7 @@ class PiPtyHost {
1068
1341
  return;
1069
1342
  const text = this.decoder.decode(data, { stream: true });
1070
1343
  this.respondToRawTerminalQueries(text);
1071
- this.terminal.write(data, () => this.emitSnapshotSoon());
1344
+ this.terminal.write(data);
1072
1345
  }
1073
1346
  emitSnapshotSoon(delayMs = SNAPSHOT_DELAY_MS) {
1074
1347
  if (this._disposed || this.notifyTimer)
@@ -1097,6 +1370,7 @@ class PiPtyHost {
1097
1370
  lines.push("");
1098
1371
  styledLines[lines.length - 1] = new StyledText([{ __isChunk: true, text: "" }]);
1099
1372
  }
1373
+ const resolvedMessage = this.lastResizeError ? `resize degraded: ${this.lastResizeError}` : message;
1100
1374
  return {
1101
1375
  runId: this.runId,
1102
1376
  status: this.status,
@@ -1104,7 +1378,8 @@ class PiPtyHost {
1104
1378
  rows: this.rows,
1105
1379
  lines,
1106
1380
  styledLines,
1107
- message,
1381
+ message: resolvedMessage,
1382
+ ...this.lastResizeError ? { resizeError: this.lastResizeError } : {},
1108
1383
  ...this.exitCode !== undefined ? { exitCode: this.exitCode } : {},
1109
1384
  ...this.signal !== undefined ? { signal: this.signal } : {}
1110
1385
  };
@@ -1165,7 +1440,7 @@ async function refreshFleet(ctx, options = {}) {
1165
1440
  try {
1166
1441
  const { listRunsViaServer: listRunsViaServer2, resolveServerConnectionLabel: resolveServerConnectionLabel2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
1167
1442
  const projectRoot = projectRootOf(ctx);
1168
- emitProgress(ctx, label, "contacting selected Rig server");
1443
+ emitProgress(ctx, label, "contacting selected server");
1169
1444
  const [runs, serverLabel] = await Promise.all([
1170
1445
  listRunsViaServer2({ projectRoot }, { limit: options.limit ?? 50 }),
1171
1446
  resolveServerConnectionLabel2(projectRoot)
@@ -1174,7 +1449,7 @@ async function refreshFleet(ctx, options = {}) {
1174
1449
  const entry = normalizeRunRecord(run);
1175
1450
  return entry ? [entry] : [];
1176
1451
  });
1177
- patchData(ctx, { fleet: { runs: normalized, refreshedAt: new Date().toISOString() } });
1452
+ patchData(ctx, { fleet: { runs: normalized, refreshedAt: new Date().toISOString() }, lastRefreshError: undefined });
1178
1453
  patchFooter(ctx, { server: serverLabel, runs: String(normalized.length) });
1179
1454
  emitCompleted(ctx, label, { count: normalized.length });
1180
1455
  return normalized;
@@ -1185,25 +1460,31 @@ async function refreshFleet(ctx, options = {}) {
1185
1460
  }
1186
1461
 
1187
1462
  // packages/cli/src/app-opentui/adapters/pi-attach.ts
1188
- function createPiAttachAdapter(renderer) {
1189
- return {
1190
- id: "pi-attach",
1191
- async handleIntent(runtime, intent) {
1192
- if (intent.scene !== "handoff" && intent.action.kind !== "run-attach")
1193
- return false;
1194
- const ctx = adapterContextFromRuntime(runtime, renderer);
1195
- const runId = payloadString(intent, "runId") ?? runtime.getState().data.selectedRunId;
1196
- if (intent.action.kind === "run-attach" && runId) {
1197
- await attachRunWithBundledPi(ctx, runId);
1198
- return true;
1199
- }
1200
- if (runId) {
1201
- await preparePiAttachHandoff(ctx, runId);
1202
- return true;
1203
- }
1204
- return false;
1205
- }
1206
- };
1463
+ function currentAttachState(ctx, runId) {
1464
+ const value = ctx.getState().data?.piAttach;
1465
+ if (!value || typeof value !== "object" || Array.isArray(value))
1466
+ return null;
1467
+ const state = value;
1468
+ return state.runId === runId ? state : null;
1469
+ }
1470
+ function recordStep(ctx, runId, label, emitLabel) {
1471
+ const now = Date.now();
1472
+ const previous = currentAttachState(ctx, runId);
1473
+ const priorSteps = previous?.steps ?? [];
1474
+ const settled = priorSteps.map((step) => step.status === "running" ? { ...step, status: "done", endedAtMs: step.endedAtMs ?? now } : step);
1475
+ const steps = [...settled, { label, startedAtMs: now, status: "running" }];
1476
+ patchData(ctx, {
1477
+ piAttach: { ...previous ?? { runId, status: "preparing", message: label }, runId, steps }
1478
+ });
1479
+ emitProgress(ctx, emitLabel, label);
1480
+ }
1481
+ function settleSteps(ctx, runId) {
1482
+ const now = Date.now();
1483
+ const previous = currentAttachState(ctx, runId);
1484
+ if (!previous?.steps)
1485
+ return;
1486
+ const steps = previous.steps.map((step) => step.status === "running" ? { ...step, status: "done", endedAtMs: step.endedAtMs ?? now } : step);
1487
+ patchData(ctx, { piAttach: { ...previous, steps } });
1207
1488
  }
1208
1489
  async function preparePiAttachHandoff(ctx, runId) {
1209
1490
  const cleanRunId = runId.trim();
@@ -1212,7 +1493,7 @@ async function preparePiAttachHandoff(ctx, runId) {
1212
1493
  try {
1213
1494
  const { getRunDetailsViaServer: getRunDetailsViaServer2, getRunPiStatusViaServer: getRunPiStatusViaServer2, resolveServerConnectionLabel: resolveServerConnectionLabel2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
1214
1495
  const projectRoot = projectRootOf(ctx);
1215
- emitProgress(ctx, label, "checking run and Pi session readiness");
1496
+ recordStep(ctx, cleanRunId, "connecting to server \xB7 checking run and Pi session readiness", label);
1216
1497
  const [run, piStatus, server] = await Promise.all([
1217
1498
  getRunDetailsViaServer2({ projectRoot }, cleanRunId).catch((error) => ({ error: error instanceof Error ? error.message : String(error) })),
1218
1499
  getRunPiStatusViaServer2({ projectRoot }, cleanRunId).catch((error) => ({ error: error instanceof Error ? error.message : String(error) })),
@@ -1223,13 +1504,21 @@ async function preparePiAttachHandoff(ctx, runId) {
1223
1504
  status: "preparing",
1224
1505
  message: "ready to enter worker Pi",
1225
1506
  result: { run, piStatus, server },
1226
- ready: true
1507
+ ready: true,
1508
+ ...currentAttachState(ctx, cleanRunId)?.steps ? { steps: currentAttachState(ctx, cleanRunId).steps } : {}
1227
1509
  };
1228
- patchData(ctx, { piAttach: state });
1510
+ patchData(ctx, { piAttach: state, lastRefreshError: undefined });
1229
1511
  emitCompleted(ctx, label, { runId: cleanRunId, server });
1230
1512
  return state;
1231
1513
  } catch (error) {
1232
- const state = { runId: cleanRunId, status: "failed", message: error instanceof Error ? error.message : String(error) };
1514
+ const reason = error instanceof Error ? error.message : String(error);
1515
+ const state = {
1516
+ runId: cleanRunId,
1517
+ status: "failed",
1518
+ message: reason,
1519
+ failureReason: reason,
1520
+ ...currentAttachState(ctx, cleanRunId)?.steps ? { steps: currentAttachState(ctx, cleanRunId).steps } : {}
1521
+ };
1233
1522
  patchData(ctx, { piAttach: state });
1234
1523
  emitFailed(ctx, label, error, { runId: cleanRunId });
1235
1524
  throw error;
@@ -1244,32 +1533,52 @@ async function attachRunWithBundledPi(ctx, runId) {
1244
1533
  throw error;
1245
1534
  }
1246
1535
  emitStarted(ctx, label, { piAttach: { runId: cleanRunId, status: "entering-pi" } });
1247
- patchData(ctx, { piAttach: { runId: cleanRunId, status: "entering-pi", message: "starting bundled Pi inside Rig" } });
1536
+ patchData(ctx, { piAttach: { runId: cleanRunId, status: "entering-pi", message: "starting bundled Pi", steps: [] } });
1248
1537
  try {
1249
- await preparePiAttachHandoff(ctx, cleanRunId).catch(() => null);
1250
- emitProgress(ctx, label, "starting Bun PTY host for bundled Pi");
1538
+ recordStep(ctx, cleanRunId, "connecting to server and fetching run transcript", label);
1539
+ await preparePiAttachHandoff(ctx, cleanRunId).catch((error) => {
1540
+ patchData(ctx, { lastRefreshError: normalizeAppError(error).message });
1541
+ return null;
1542
+ });
1543
+ recordStep(ctx, cleanRunId, "spawning Bun PTY host for bundled Pi", label);
1251
1544
  const projectRoot = projectRootOf(ctx);
1252
1545
  const cols = typeof process.stdout.columns === "number" ? process.stdout.columns : 100;
1253
1546
  const rows = Math.max(1, (typeof process.stdout.rows === "number" ? process.stdout.rows : 35) - 1);
1254
1547
  const patchTerminal = (snapshot) => {
1548
+ const previousSteps = currentAttachState(ctx, cleanRunId)?.steps;
1549
+ const failed = snapshot.status === "failed";
1550
+ const reason = failed ? snapshot.message ?? `bundled Pi exited with code ${snapshot.exitCode ?? "unknown"}` : undefined;
1255
1551
  patchData(ctx, {
1256
1552
  piTerminal: snapshot,
1257
1553
  piAttach: {
1258
1554
  runId: cleanRunId,
1259
- status: snapshot.status === "failed" ? "failed" : snapshot.status === "exited" ? "returned" : "entering-pi",
1260
- message: snapshot.message ?? "bundled Pi running inside Rig",
1261
- result: { runId: cleanRunId, status: snapshot.status, exitCode: snapshot.exitCode }
1555
+ status: failed ? "failed" : snapshot.status === "exited" ? "returned" : "entering-pi",
1556
+ message: snapshot.message ?? "bundled Pi running",
1557
+ result: { runId: cleanRunId, status: snapshot.status, exitCode: snapshot.exitCode },
1558
+ ...previousSteps ? { steps: previousSteps } : {},
1559
+ ...reason ? { failureReason: reason } : {}
1262
1560
  }
1263
1561
  });
1264
1562
  };
1563
+ recordStep(ctx, cleanRunId, "starting bundled Pi", label);
1265
1564
  await startPiPtyHost({
1266
1565
  runId: cleanRunId,
1267
1566
  projectRoot,
1268
1567
  cols,
1269
1568
  rows,
1270
- onSnapshot: patchTerminal,
1569
+ onSnapshot: (snapshot) => {
1570
+ if (snapshot.status === "running" && snapshot.lines.some((entry) => entry.trim().length > 0)) {
1571
+ settleSteps(ctx, cleanRunId);
1572
+ }
1573
+ patchTerminal(snapshot);
1574
+ },
1271
1575
  onExit: (snapshot) => {
1272
1576
  patchTerminal(snapshot);
1577
+ if (snapshot.status === "failed") {
1578
+ Promise.resolve().then(() => (init_inspect(), exports_inspect)).then(({ loadInspect: loadInspect2 }) => loadInspect2(ctx, cleanRunId)).catch(() => null);
1579
+ emitFailed(ctx, label, new Error(snapshot.message ?? `bundled Pi exited with code ${snapshot.exitCode ?? "unknown"}`), { runId: cleanRunId, result: { status: snapshot.status, exitCode: snapshot.exitCode } });
1580
+ return;
1581
+ }
1273
1582
  emitCompleted(ctx, label, { runId: cleanRunId, result: { status: snapshot.status, exitCode: snapshot.exitCode } });
1274
1583
  ctx.emit({ type: "scene.change", scene: "fleet", intent: { scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } } });
1275
1584
  refreshFleet(ctx).catch((error) => emitFailed(ctx, "Refresh runs", error));
@@ -1284,16 +1593,24 @@ async function attachRunWithBundledPi(ctx, runId) {
1284
1593
  status: "running",
1285
1594
  rendered: "bundled Pi embedded via Bun.Terminal + @xterm/headless"
1286
1595
  };
1287
- emitProgress(ctx, label, "bundled Pi is running inside Rig", { piAttach: { runId: cleanRunId, status: "entering-pi", message: "bundled Pi running inside Rig", result: record } });
1596
+ emitProgress(ctx, label, "bundled Pi is running", { piAttach: { runId: cleanRunId, status: "entering-pi", message: "bundled Pi running", result: record } });
1288
1597
  return record;
1289
1598
  } catch (error) {
1290
- patchData(ctx, { piAttach: { runId: cleanRunId, status: "failed", message: error instanceof Error ? error.message : String(error) } });
1599
+ const reason = error instanceof Error ? error.message : String(error);
1600
+ patchData(ctx, {
1601
+ piAttach: {
1602
+ runId: cleanRunId,
1603
+ status: "failed",
1604
+ message: reason,
1605
+ failureReason: reason,
1606
+ ...currentAttachState(ctx, cleanRunId)?.steps ? { steps: currentAttachState(ctx, cleanRunId).steps } : {}
1607
+ }
1608
+ });
1291
1609
  emitFailed(ctx, label, error, { runId: cleanRunId });
1292
1610
  throw error;
1293
1611
  }
1294
1612
  }
1295
1613
  export {
1296
1614
  preparePiAttachHandoff,
1297
- createPiAttachAdapter,
1298
1615
  attachRunWithBundledPi
1299
1616
  };