@h-rig/cli 0.0.6-alpha.90 → 0.0.6-alpha.92

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 (118) hide show
  1. package/README.md +18 -19
  2. package/dist/bin/build-rig-binaries.js +22 -10
  3. package/dist/bin/rig.d.ts +71 -1
  4. package/dist/bin/rig.js +15078 -11169
  5. package/dist/config/rig-default-config.yml +5 -0
  6. package/dist/src/app/drone-ui.d.ts +11 -14
  7. package/dist/src/app/drone-ui.js +70 -86
  8. package/dist/src/commands/_async-ui.d.ts +1 -4
  9. package/dist/src/commands/_async-ui.js +9 -111
  10. package/dist/src/commands/_cli-format.d.ts +16 -9
  11. package/dist/src/commands/_cli-format.js +167 -295
  12. package/dist/src/commands/_connection-state.d.ts +11 -1
  13. package/dist/src/commands/_connection-state.js +50 -5
  14. package/dist/src/commands/_doctor-checks.d.ts +0 -6
  15. package/dist/src/commands/_doctor-checks.js +79 -382
  16. package/dist/src/commands/_help-catalog.d.ts +1 -1
  17. package/dist/src/commands/_help-catalog.js +217 -157
  18. package/dist/src/commands/_inprocess-services.d.ts +33 -0
  19. package/dist/src/commands/_inprocess-services.js +102 -0
  20. package/dist/src/commands/_json-output.js +4 -0
  21. package/dist/src/commands/_lazy-reconcile.d.ts +34 -0
  22. package/dist/src/commands/_lazy-reconcile.js +102 -0
  23. package/dist/src/commands/_paths.js +1 -1
  24. package/dist/src/commands/_pi-frontend.d.ts +18 -10
  25. package/dist/src/commands/_pi-frontend.js +37 -715
  26. package/dist/src/commands/_pi-install.js +18 -36
  27. package/dist/src/commands/_policy.d.ts +1 -1
  28. package/dist/src/commands/_policy.js +56 -15
  29. package/dist/src/commands/_run-bridge.d.ts +114 -0
  30. package/dist/src/commands/_run-bridge.js +387 -0
  31. package/dist/src/commands/_run-diagnostics.d.ts +9 -0
  32. package/dist/src/commands/_run-diagnostics.js +51 -0
  33. package/dist/src/commands/_run-driver-helpers.d.ts +8 -81
  34. package/dist/src/commands/_run-driver-helpers.js +79 -283
  35. package/dist/src/commands/_run-projection.d.ts +50 -0
  36. package/dist/src/commands/_run-projection.js +349 -0
  37. package/dist/src/commands/_run-subcommands.d.ts +3 -0
  38. package/dist/src/commands/_run-subcommands.js +31 -0
  39. package/dist/src/commands/_spinner.js +1 -1
  40. package/dist/src/commands/agent.d.ts +1 -1
  41. package/dist/src/commands/agent.js +8559 -239
  42. package/dist/src/commands/dist.d.ts +1 -1
  43. package/dist/src/commands/dist.js +27 -19
  44. package/dist/src/commands/doctor.d.ts +1 -1
  45. package/dist/src/commands/doctor.js +93 -475
  46. package/dist/src/commands/github.d.ts +1 -1
  47. package/dist/src/commands/github.js +113 -387
  48. package/dist/src/commands/inbox.d.ts +22 -24
  49. package/dist/src/commands/inbox.js +420 -691
  50. package/dist/src/commands/init.d.ts +6 -16
  51. package/dist/src/commands/init.js +334 -971
  52. package/dist/src/commands/inspect.d.ts +19 -2
  53. package/dist/src/commands/inspect.js +644 -610
  54. package/dist/src/commands/pi.d.ts +1 -1
  55. package/dist/src/commands/plugin.d.ts +1 -1
  56. package/dist/src/commands/plugin.js +486 -7
  57. package/dist/src/commands/profile-and-review.d.ts +1 -1
  58. package/dist/src/commands/profile-and-review.js +94 -56
  59. package/dist/src/commands/queue.js +1 -21
  60. package/dist/src/commands/remote.d.ts +1 -1
  61. package/dist/src/commands/remote.js +837 -14
  62. package/dist/src/commands/repo-git-harness.d.ts +1 -1
  63. package/dist/src/commands/repo-git-harness.js +57 -14
  64. package/dist/src/commands/run.d.ts +20 -2
  65. package/dist/src/commands/run.js +17579 -1759
  66. package/dist/src/commands/server.d.ts +2 -6
  67. package/dist/src/commands/server.js +141 -723
  68. package/dist/src/commands/setup.d.ts +1 -1
  69. package/dist/src/commands/setup.js +102 -484
  70. package/dist/src/commands/stats.d.ts +13 -10
  71. package/dist/src/commands/stats.js +689 -761
  72. package/dist/src/commands/task-run-driver.d.ts +50 -88
  73. package/dist/src/commands/task-run-driver.js +116 -2717
  74. package/dist/src/commands/task.d.ts +34 -13
  75. package/dist/src/commands/task.js +668 -2523
  76. package/dist/src/commands/test.d.ts +1 -1
  77. package/dist/src/commands/triage.d.ts +11 -0
  78. package/dist/src/commands/triage.js +227 -0
  79. package/dist/src/commands/workspace.d.ts +1 -1
  80. package/dist/src/commands.d.ts +0 -16
  81. package/dist/src/commands.js +16657 -12250
  82. package/dist/src/index.js +16528 -12497
  83. package/dist/src/launcher.js +4 -0
  84. package/dist/src/operator-cli.d.ts +2 -0
  85. package/dist/src/operator-cli.js +17837 -0
  86. package/dist/src/operator-entry.d.ts +1 -0
  87. package/dist/src/operator-entry.js +3 -0
  88. package/package.json +18 -12
  89. package/dist/src/app/board.d.ts +0 -23
  90. package/dist/src/app/board.js +0 -1786
  91. package/dist/src/app/theme.d.ts +0 -47
  92. package/dist/src/app/theme.js +0 -150
  93. package/dist/src/commands/_authority-runs.d.ts +0 -22
  94. package/dist/src/commands/_authority-runs.js +0 -110
  95. package/dist/src/commands/_operator-surface.d.ts +0 -34
  96. package/dist/src/commands/_operator-surface.js +0 -220
  97. package/dist/src/commands/_operator-view.d.ts +0 -30
  98. package/dist/src/commands/_operator-view.js +0 -1070
  99. package/dist/src/commands/_preflight.d.ts +0 -22
  100. package/dist/src/commands/_preflight.js +0 -540
  101. package/dist/src/commands/_run-replay.d.ts +0 -24
  102. package/dist/src/commands/_run-replay.js +0 -142
  103. package/dist/src/commands/_server-client.d.ts +0 -186
  104. package/dist/src/commands/_server-client.js +0 -681
  105. package/dist/src/commands/_snapshot-upload.d.ts +0 -39
  106. package/dist/src/commands/_snapshot-upload.js +0 -455
  107. package/dist/src/commands/_task-picker.d.ts +0 -9
  108. package/dist/src/commands/_task-picker.js +0 -201
  109. package/dist/src/commands/browser.d.ts +0 -65
  110. package/dist/src/commands/browser.js +0 -1173
  111. package/dist/src/commands/connect.d.ts +0 -7
  112. package/dist/src/commands/connect.js +0 -419
  113. package/dist/src/commands/inspector.d.ts +0 -3
  114. package/dist/src/commands/inspector.js +0 -263
  115. package/dist/src/commands/task-report-bug.d.ts +0 -19
  116. package/dist/src/commands/task-report-bug.js +0 -1281
  117. package/dist/src/report-bug.d.ts +0 -44
  118. package/dist/src/report-bug.js +0 -260
@@ -1,4 +1,7 @@
1
1
  // @bun
2
+ // packages/cli/src/commands/inbox.ts
3
+ import { Duration, Effect, Stream } from "effect";
4
+
2
5
  // packages/cli/src/runner.ts
3
6
  import { EventBus } from "@rig/runtime/control-plane/runtime/events";
4
7
  import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
@@ -41,176 +44,30 @@ Usage: ${usage}`);
41
44
  }
42
45
  }
43
46
 
44
- // packages/cli/src/app/drone-ui.ts
45
- import { ProcessTerminal, TUI, Text, Input, SelectList, matchesKey } from "@earendil-works/pi-tui";
46
-
47
- // packages/cli/src/app/theme.ts
48
- var RIG_PALETTE = {
49
- ink: "#f2f3f6",
50
- ink2: "#aeb0ba",
51
- ink3: "#6c6e79",
52
- ink4: "#44464f",
53
- accent: "#ccff4d",
54
- accentDim: "#a9d63f",
55
- cyan: "#56d8ff",
56
- red: "#ff5d5d",
57
- yellow: "#ffd24d"
58
- };
59
- function hexToRgb(hex) {
60
- const value = hex.replace("#", "");
61
- return [
62
- Number.parseInt(value.slice(0, 2), 16),
63
- Number.parseInt(value.slice(2, 4), 16),
64
- Number.parseInt(value.slice(4, 6), 16)
65
- ];
66
- }
67
- function fg(hex) {
68
- const [r, g, b] = hexToRgb(hex);
69
- return (text) => `\x1B[38;2;${r};${g};${b}m${text}\x1B[39m`;
70
- }
71
- var ink = fg(RIG_PALETTE.ink);
72
- var ink2 = fg(RIG_PALETTE.ink2);
73
- var ink3 = fg(RIG_PALETTE.ink3);
74
- var ink4 = fg(RIG_PALETTE.ink4);
75
- var accent = fg(RIG_PALETTE.accent);
76
- var accentDim = fg(RIG_PALETTE.accentDim);
77
- var cyan = fg(RIG_PALETTE.cyan);
78
- var red = fg(RIG_PALETTE.red);
79
- var yellow = fg(RIG_PALETTE.yellow);
80
- function bold(text) {
81
- return `\x1B[1m${text}\x1B[22m`;
82
- }
83
- var DRONE_ART = [
84
- " .-=-. .-=-. ",
85
- " ( !!! ) ( !!! ) ",
86
- " '-=-'._ _.'-=-' ",
87
- " '._ _.' ",
88
- " '=$$$$$$$=.' ",
89
- " =$$$$$$$$$$$= ",
90
- " $$$@@@@@@@@@@$$$ ",
91
- " $$$@@ @@$$$ ",
92
- " $$@ ? @$$$ ",
93
- " $$$@ '-' @$$$ ",
94
- " $$$@@ @@$$$ ",
95
- " $$$@@@@@@@@@@$$$ ",
96
- " =$$$$$$$$$$$= ",
97
- " '=$$$$$$$=.' ",
98
- " _.' '._ ",
99
- " .-=-.' '.-=-. ",
100
- " ( !!! ) ( !!! ) ",
101
- " '-=-' '-=-' "
102
- ];
103
- var EYE_FRAMES = ["@", "o", "."];
104
- var DRONE_WIDTH = DRONE_ART[0].length;
105
- var DRONE_HEIGHT = DRONE_ART.length;
106
- var MICRO_BLADES = ["---", "\\\\\\", "|||", "///"];
107
- function microDroneFrame(tick) {
108
- const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
109
- const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
110
- return `(${blade})${eye}(${blade})`;
111
- }
112
- var MICRO_DRONE_FRAMES = Array.from({ length: 12 }, (_, index) => microDroneFrame(index));
113
- function renderMicroDroneFrame(tick) {
114
- const blade = MICRO_BLADES[tick % MICRO_BLADES.length];
115
- const eye = EYE_FRAMES[Math.floor(tick / 2) % EYE_FRAMES.length];
116
- return `${ink4("(")}${cyan(blade)}${ink4(")")}${bold(accent(eye))}${ink4("(")}${cyan(blade)}${ink4(")")}`;
117
- }
118
-
119
- // packages/cli/src/commands/_spinner.ts
120
- var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
121
- function createTtySpinner(input) {
122
- const output = input.output ?? process.stdout;
123
- const isTty = output.isTTY === true;
124
- const frames = input.frames && input.frames.length > 0 ? input.frames : SPINNER_FRAMES;
125
- let label = input.label;
126
- let frame = 0;
127
- let paused = false;
128
- let stopped = false;
129
- let lastPrintedLabel = "";
130
- const render = () => {
131
- if (stopped || paused)
132
- return;
133
- if (!isTty) {
134
- if (label !== lastPrintedLabel) {
135
- output.write(`${label}
136
- `);
137
- lastPrintedLabel = label;
138
- }
139
- return;
140
- }
141
- frame = (frame + 1) % frames.length;
142
- const glyph = frames[frame] ?? frames[0] ?? "";
143
- output.write(`\r\x1B[2K${input.styleFrame ? input.styleFrame(glyph) : glyph} ${label}`);
144
- };
145
- const clearLine = () => {
146
- if (isTty)
147
- output.write("\r\x1B[2K");
148
- };
149
- render();
150
- const timer = isTty ? setInterval(render, input.intervalMs ?? 120) : null;
151
- return {
152
- setLabel(next) {
153
- label = next;
154
- render();
155
- },
156
- pause() {
157
- paused = true;
158
- clearLine();
159
- },
160
- resume() {
161
- if (stopped)
162
- return;
163
- paused = false;
164
- render();
165
- },
166
- stop(finalLine) {
167
- if (stopped)
168
- return;
169
- stopped = true;
170
- if (timer)
171
- clearInterval(timer);
172
- clearLine();
173
- if (finalLine)
174
- output.write(`${finalLine}
175
- `);
176
- }
177
- };
178
- }
179
-
180
- // packages/cli/src/app/drone-ui.ts
181
- function droneNote(message, title) {
182
- if (title)
183
- console.log(` ${accentDim("\u25C7")} ${bold(ink2(title))}`);
184
- for (const line of message.split(`
185
- `)) {
186
- console.log(` ${ink4("\u2502")} ${line}`);
187
- }
188
- }
47
+ // packages/cli/src/commands/inbox.ts
48
+ import { localRunChanges } from "@rig/runtime/control-plane/run-discovery-stream";
189
49
 
190
50
  // packages/cli/src/commands/_cli-format.ts
191
51
  import pc from "picocolors";
192
- var themeDim = (value) => ink3(value);
193
- var themeFaint = (value) => ink4(value);
194
- function stringField(record, key, fallback = "") {
195
- const value = record[key];
196
- return typeof value === "string" && value.trim() ? value.trim() : fallback;
197
- }
52
+ var dim = pc.dim;
53
+ var faintBar = pc.dim("\u2502");
54
+ var accent = pc.cyan;
198
55
  function statusColor(status) {
199
- const normalized = status.toLowerCase();
200
- if (["completed", "merged", "closed", "done", "accepted", "pass", "selected", "approved", "running"].includes(normalized))
201
- return accent;
202
- if (["failed", "needs_attention", "needs-attention", "blocked", "error", "rejected"].includes(normalized))
203
- return red;
204
- if (["reviewing", "validating", "in_progress", "in-progress", "remote", "preparing", "closing-out"].includes(normalized))
205
- return cyan;
206
- if (["ready", "open", "queued", "created", "local", "pending"].includes(normalized))
207
- return yellow;
208
- return themeDim;
56
+ const normalized = status.trim().toLowerCase();
57
+ if (["done", "completed", "ready", "healthy", "open", "approved"].includes(normalized))
58
+ return pc.green;
59
+ if (["failed", "error", "blocked", "rejected", "stopped"].includes(normalized))
60
+ return pc.red;
61
+ if (["running", "in_progress", "in-progress", "active", "booting"].includes(normalized))
62
+ return pc.cyan;
63
+ if (["pending", "queued", "created", "local"].includes(normalized))
64
+ return pc.yellow;
65
+ return pc.dim;
209
66
  }
210
67
  function firstString(record, keys, fallback = "") {
211
68
  for (const key of keys) {
212
- const value = stringField(record, key);
213
- if (value)
69
+ const value = record[key];
70
+ if (typeof value === "string" && value.trim())
214
71
  return value;
215
72
  }
216
73
  return fallback;
@@ -218,633 +75,505 @@ function firstString(record, keys, fallback = "") {
218
75
  function requestIdOf(entry) {
219
76
  return firstString(entry, ["requestId", "id", "approvalId", "inputId"], "(unknown-request)");
220
77
  }
221
- function shouldUseClackOutput() {
222
- return Boolean(process.stdout.isTTY) && process.env.RIG_CLI_PLAIN_HELP !== "1";
223
- }
224
- function printFormattedOutput(message, options = {}) {
225
- if (!shouldUseClackOutput()) {
226
- console.log(message);
227
- return;
228
- }
229
- droneNote(message, options.title);
78
+ function printFormattedOutput(message) {
79
+ console.log(message);
230
80
  }
231
81
  function formatStatusPill(status) {
232
82
  const label = status || "unknown";
233
83
  return statusColor(label)(`\u25CF ${label}`);
234
84
  }
235
85
  function formatSection(title, subtitle) {
236
- return `${pc.bold(accent("\u25C6"))} ${pc.bold(title)}${subtitle ? themeDim(` \u2014 ${subtitle}`) : ""}`;
86
+ return `${pc.bold(accent("\u25C6"))} ${pc.bold(title)}${subtitle ? dim(` \u2014 ${subtitle}`) : ""}`;
237
87
  }
238
88
  function formatNextSteps(steps) {
239
89
  if (steps.length === 0)
240
90
  return [];
241
91
  return [pc.bold("Next"), ...steps.map((step) => `${accent("\u203A")} ${step}`)];
242
92
  }
93
+ function formatLegacyAutomationSurface() {
94
+ return [];
95
+ }
243
96
  function formatInboxList(kind, entries) {
244
- const title = kind === "approvals" ? "Approval inbox" : "Input inbox";
97
+ const title = kind === "approvals" ? "Pending approvals" : "Pending user input";
245
98
  if (entries.length === 0) {
246
99
  return [
247
100
  formatSection(title, "empty"),
248
- themeDim(kind === "approvals" ? "No pending approvals." : "No pending user-input requests."),
101
+ dim(kind === "approvals" ? "No pending approvals." : "No pending user-input requests."),
249
102
  "",
250
- ...formatNextSteps(["Check runs: `rig run status`", "Start work: `rig task run --next`"])
103
+ ...formatLegacyAutomationSurface()
251
104
  ].join(`
252
105
  `);
253
106
  }
254
- const lines = [formatSection(title, `${entries.length} pending`)];
255
- for (const entry of entries) {
256
- const record = entry.record && typeof entry.record === "object" && !Array.isArray(entry.record) ? entry.record : entry;
257
- const runId = firstString(entry, ["runId"], firstString(record, ["runId"]));
258
- const taskId = firstString(entry, ["taskId"], firstString(record, ["taskId", "task"]));
259
- const requestId = requestIdOf(record);
260
- const status = firstString(record, ["status", "state"], "pending");
107
+ const lines = [formatSection(title, `${entries.length}`)];
108
+ for (const record of entries) {
109
+ const runId = firstString(record, ["runId"], "(unknown-run)");
110
+ const taskId = firstString(record, ["taskId"], "");
111
+ const status = firstString(record, ["status"], "pending");
261
112
  const prompt = firstString(record, ["prompt", "message", "reason", "title", "summary"], kind === "approvals" ? "Approval requested" : "Input requested");
262
- lines.push(`${themeFaint("\u2502")} ${pc.bold(requestId)} ${formatStatusPill(status)} ${prompt}`);
263
- lines.push(`${themeFaint("\u2502")} ${themeDim("run ")} ${runId || "(unknown-run)"}${taskId ? themeDim(` task ${taskId}`) : ""}`);
113
+ lines.push(`${faintBar} ${pc.bold(requestIdOf(record))} ${formatStatusPill(status)} ${prompt}`);
114
+ lines.push(`${faintBar} ${dim("run".padEnd(12))} ${runId}${taskId ? dim(` task ${taskId}`) : ""}`);
264
115
  }
265
- lines.push("", ...formatNextSteps(kind === "approvals" ? ["Resolve: `rig inbox approve --run <run-id> --request <request-id> --decision approve|reject`", "Rejoin: `rig run attach <run-id> --follow`"] : ["Respond: `rig inbox respond --run <run-id> --request <request-id> --answer key=value`", "Rejoin: `rig run attach <run-id> --follow`"]));
116
+ lines.push("", ...formatLegacyAutomationSurface(), ...formatNextSteps(kind === "approvals" ? ["Resolve: `rig inbox approve --run <run-id> --request <request-id> --decision approve|reject`"] : ["Reply: `rig inbox answer --run <run-id> --request <request-id> --text ...`"]));
266
117
  return lines.join(`
267
118
  `);
268
119
  }
269
120
 
270
- // packages/cli/src/commands/_server-client.ts
271
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
121
+ // packages/cli/src/commands/_run-projection.ts
122
+ import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
272
123
  import { resolve as resolve2 } from "path";
273
- import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
124
+ import {
125
+ foldRunSessionEntries,
126
+ sessionIdFromSessionFile as sessionIdFromSessionFile2
127
+ } from "@rig/contracts";
128
+ import { listAgentRuntimes as listAgentRuntimes2 } from "@rig/runtime/control-plane/runtime/isolation";
274
129
 
275
- // packages/cli/src/commands/_connection-state.ts
276
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
277
- import { homedir } from "os";
130
+ // packages/cli/src/commands/_run-bridge.ts
131
+ import { existsSync, readdirSync, readFileSync } from "fs";
278
132
  import { dirname, resolve } from "path";
279
- function resolveGlobalConnectionsPath(env = process.env) {
280
- const explicit = env.RIG_CONNECTIONS_FILE?.trim();
281
- if (explicit)
282
- return resolve(explicit);
283
- const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
284
- if (stateDir)
285
- return resolve(stateDir, "connections.json");
286
- return resolve(homedir(), ".rig", "connections.json");
287
- }
288
- function resolveRepoConnectionPath(projectRoot) {
289
- return resolve(projectRoot, ".rig", "state", "connection.json");
290
- }
291
- function readJsonFile(path) {
292
- if (!existsSync(path))
293
- return null;
294
- try {
295
- return JSON.parse(readFileSync(path, "utf8"));
296
- } catch (error) {
297
- 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>`." });
298
- }
133
+ import { CUSTOM_TYPE_FOR, sessionIdFromSessionFile } from "@rig/contracts";
134
+ import { createPluginHost } from "@rig/core";
135
+ import { loadConfig } from "@rig/core/load-config";
136
+ import { resolveOwnerNamespaceKey, resolveRegistryBaseUrl, resolveRegistrySecret, resolveSelectedRemote } from "@rig/runtime/control-plane/remote";
137
+ import { listAgentRuntimes } from "@rig/runtime/control-plane/runtime/isolation";
138
+ import { createRegistryClient } from "@rig/relay-registry";
139
+ import {
140
+ listActiveCollabSessions as ompListActiveCollabSessions
141
+ } from "@oh-my-pi/pi-coding-agent/collab/api";
142
+ function registrySecret(env = process.env) {
143
+ return resolveRegistrySecret(process.cwd(), env) ?? null;
144
+ }
145
+ function registryBaseUrl(env = process.env) {
146
+ return resolveRegistryBaseUrl(process.cwd(), env);
147
+ }
148
+ function collabFromRegistryEntry(entry) {
149
+ return {
150
+ sessionId: entry.roomId,
151
+ sessionPath: "",
152
+ cwd: "",
153
+ title: entry.title,
154
+ joinLink: entry.joinLink,
155
+ webLink: entry.webLink,
156
+ relayUrl: entry.relayUrl,
157
+ owner: entry.owner,
158
+ selectedRepo: entry.repo,
159
+ startedAt: entry.startedAt,
160
+ updatedAt: entry.heartbeatAt,
161
+ ...entry.pid === undefined ? {} : { pid: entry.pid },
162
+ stale: entry.stale
163
+ };
299
164
  }
300
- function writeJsonFile(path, value) {
301
- mkdirSync(dirname(path), { recursive: true });
302
- writeFileSync(path, `${JSON.stringify(value, null, 2)}
303
- `, "utf8");
165
+ function isRecord(value) {
166
+ return typeof value === "object" && value !== null;
304
167
  }
305
- function normalizeConnection(value) {
306
- if (!value || typeof value !== "object" || Array.isArray(value))
168
+ function readLocalRunCollab(runtime) {
169
+ if (!runtime.sessionDir || !existsSync(runtime.sessionDir))
170
+ return null;
171
+ let files;
172
+ try {
173
+ files = readdirSync(runtime.sessionDir).filter((f) => f.endsWith(".jsonl")).sort().reverse();
174
+ } catch {
307
175
  return null;
308
- const record = value;
309
- if (record.kind === "local")
310
- return { kind: "local", mode: "auto" };
311
- if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
312
- const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
313
- return { kind: "remote", baseUrl };
176
+ }
177
+ for (const file of files) {
178
+ let lines;
179
+ try {
180
+ lines = readFileSync(resolve(runtime.sessionDir, file), "utf8").split(`
181
+ `);
182
+ } catch {
183
+ continue;
184
+ }
185
+ let host = null;
186
+ for (const line of lines) {
187
+ if (!line.includes("collab-host-started"))
188
+ continue;
189
+ let entry;
190
+ try {
191
+ entry = JSON.parse(line);
192
+ } catch {
193
+ continue;
194
+ }
195
+ if (!isRecord(entry) || entry.customType !== CUSTOM_TYPE_FOR["timeline-entry"] || !isRecord(entry.data))
196
+ continue;
197
+ const payload = isRecord(entry.data.payload) ? entry.data.payload : entry.data;
198
+ if (payload.type !== "collab-host-started")
199
+ continue;
200
+ host = {
201
+ roomId: typeof payload.roomId === "string" ? payload.roomId : undefined,
202
+ joinLink: typeof payload.joinLink === "string" ? payload.joinLink : undefined,
203
+ webLink: typeof payload.webLink === "string" ? payload.webLink : undefined,
204
+ relayUrl: typeof payload.relayUrl === "string" ? payload.relayUrl : undefined,
205
+ at: typeof entry.data.at === "string" ? entry.data.at : undefined
206
+ };
207
+ }
208
+ if (!host?.joinLink)
209
+ continue;
210
+ const at = host.at ?? "";
211
+ const sessionId = host.roomId ?? sessionIdFromSessionFile(resolve(runtime.sessionDir, file)) ?? runtime.id;
212
+ return {
213
+ sessionId,
214
+ sessionPath: resolve(runtime.sessionDir, file),
215
+ cwd: runtime.workspaceDir,
216
+ joinLink: host.joinLink,
217
+ webLink: host.webLink ?? "",
218
+ relayUrl: host.relayUrl ?? "",
219
+ title: runtime.taskId ? `[${runtime.taskId}] Rig run ${sessionId}` : `Rig run ${sessionId}`,
220
+ startedAt: at,
221
+ updatedAt: at,
222
+ stale: false
223
+ };
314
224
  }
315
225
  return null;
316
226
  }
317
- function readGlobalConnections(options = {}) {
318
- const path = resolveGlobalConnectionsPath(options.env ?? process.env);
319
- const payload = readJsonFile(path);
320
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
321
- return { connections: {} };
227
+ async function listLocalRunCollabSessions(projectRoot) {
228
+ let runtimes;
229
+ try {
230
+ runtimes = await listAgentRuntimes(projectRoot);
231
+ } catch {
232
+ return [];
322
233
  }
323
- const rawConnections = payload.connections;
324
- const connections = {};
325
- if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
326
- for (const [alias, raw] of Object.entries(rawConnections)) {
327
- const connection = normalizeConnection(raw);
328
- if (connection)
329
- connections[alias] = connection;
330
- }
234
+ const out = [];
235
+ for (const runtime of runtimes) {
236
+ const collab = readLocalRunCollab(runtime);
237
+ if (collab)
238
+ out.push(collab);
331
239
  }
332
- return { connections };
240
+ return out;
333
241
  }
334
- function readRepoConnection(projectRoot) {
335
- const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
336
- if (!payload || typeof payload !== "object" || Array.isArray(payload))
337
- return null;
338
- const record = payload;
339
- const selected = typeof record.selected === "string" ? record.selected.trim() : "";
340
- if (!selected)
341
- return null;
342
- return {
343
- selected,
344
- project: typeof record.project === "string" ? record.project : undefined,
345
- linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
346
- serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
347
- };
242
+ async function listActiveRunCollab(filter) {
243
+ return defaultListActiveCollabSessions(filter);
348
244
  }
349
- function writeRepoConnection(projectRoot, state) {
350
- writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
245
+ async function defaultListActiveCollabSessions(filter) {
246
+ const projectRoot = process.cwd();
247
+ const local = await listLocalRunCollabSessions(projectRoot);
248
+ const namespaceKey = filter.namespaceKey ?? resolveOwnerNamespaceKey(projectRoot);
249
+ const secret = registrySecret();
250
+ const remote = namespaceKey && secret ? (await createRegistryClient({ baseUrl: registryBaseUrl(), namespaceKey, secret }).listRoomsByOwner(filter)).map(collabFromRegistryEntry) : await ompListActiveCollabSessions(filter);
251
+ const seen = new Set(local.map((c) => c.sessionId));
252
+ return [...local, ...remote.filter((c) => !seen.has(c.sessionId))];
351
253
  }
352
- function resolveSelectedConnection(projectRoot, options = {}) {
353
- const repo = readRepoConnection(projectRoot);
354
- if (!repo)
254
+
255
+ // packages/cli/src/commands/_run-diagnostics.ts
256
+ function normalizeString(value) {
257
+ if (typeof value !== "string")
355
258
  return null;
356
- if (repo.selected === "local")
357
- return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
358
- const global = readGlobalConnections(options);
359
- const connection = global.connections[repo.selected];
360
- if (!connection) {
361
- throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
362
- }
363
- return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
259
+ const normalized = value.replace(/\s+/g, " ").trim();
260
+ return normalized.length > 0 ? normalized : null;
261
+ }
262
+ function isGenericRunFailure(value) {
263
+ const text = normalizeString(value);
264
+ return Boolean(text && /^Task run failed \([^)]*\)$/i.test(text));
265
+ }
266
+ function appendCandidate(candidates, value) {
267
+ const text = normalizeString(value);
268
+ if (text && !candidates.includes(text))
269
+ candidates.push(text);
270
+ }
271
+ function categorizeUsefulRunError(lines) {
272
+ const taskSourceFailure = lines.find((line) => /failed to update task source/i.test(line));
273
+ if (taskSourceFailure)
274
+ return `Task source update failed: ${taskSourceFailure}`;
275
+ const moduleFailure = lines.find((line) => /cannot find module/i.test(line));
276
+ if (moduleFailure)
277
+ return `Runtime module resolution failed: ${moduleFailure}`;
278
+ const providerFailure = lines.find((line) => /no api key found|unauthorized|authentication failed|invalid api key/i.test(line));
279
+ if (providerFailure)
280
+ return `Provider authentication failed: ${providerFailure}`;
281
+ return null;
364
282
  }
365
- function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
366
- const repo = readRepoConnection(projectRoot);
367
- if (!repo)
368
- return;
369
- writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
283
+ function summarizeUsefulRunError(projection) {
284
+ if (projection.status !== "failed")
285
+ return null;
286
+ const candidates = [];
287
+ for (const anomaly of projection.anomalies) {
288
+ appendCandidate(candidates, `Journal anomaly (${anomaly.kind}): ${anomaly.detail}`);
289
+ }
290
+ for (const phase of projection.closeoutPhases) {
291
+ if (phase.outcome === "failed")
292
+ appendCandidate(candidates, phase.detail);
293
+ }
294
+ for (const entry of projection.statusHistory) {
295
+ appendCandidate(candidates, entry.reason);
296
+ }
297
+ appendCandidate(candidates, projection.record.statusDetail);
298
+ appendCandidate(candidates, projection.record.errorText);
299
+ const nonGeneric = candidates.filter((candidate) => !isGenericRunFailure(candidate));
300
+ return categorizeUsefulRunError(nonGeneric) ?? nonGeneric.at(-1) ?? normalizeString(projection.record.errorText);
370
301
  }
371
302
 
372
- // packages/cli/src/commands/_server-client.ts
373
- var scopedGitHubBearerTokens = new Map;
374
- var serverPhaseListener = null;
375
- function setServerPhaseListener(listener) {
376
- const previous = serverPhaseListener;
377
- serverPhaseListener = listener;
378
- return previous;
379
- }
380
- function reportServerPhase(label) {
381
- serverPhaseListener?.(label);
382
- }
383
- function cleanToken(value) {
384
- const trimmed = value?.trim();
385
- return trimmed ? trimmed : null;
386
- }
387
- function readPrivateRemoteSessionToken(projectRoot) {
388
- const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
389
- if (!existsSync2(path))
303
+ // packages/cli/src/commands/_run-projection.ts
304
+ var EMPTY_PROJECTION = foldRunSessionEntries([], "");
305
+ function stringOrNull(value) {
306
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
307
+ }
308
+ function newestSessionFile(sessionDir) {
309
+ if (!sessionDir || !existsSync2(sessionDir))
390
310
  return null;
391
311
  try {
392
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
393
- return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
312
+ const files = readdirSync2(sessionDir).filter((file) => file.endsWith(".jsonl")).sort().reverse();
313
+ return files[0] ? resolve2(sessionDir, files[0]) : null;
394
314
  } catch {
395
315
  return null;
396
316
  }
397
317
  }
398
- function readGitHubBearerTokenForRemote(projectRoot) {
399
- const scopedKey = resolve2(projectRoot);
400
- if (scopedGitHubBearerTokens.has(scopedKey))
401
- return scopedGitHubBearerTokens.get(scopedKey) ?? null;
402
- const privateSession = readPrivateRemoteSessionToken(projectRoot);
403
- if (privateSession)
404
- return privateSession;
405
- return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
406
- }
407
- function readStoredGitHubAuthToken(projectRoot) {
408
- const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
409
- if (!existsSync2(path))
410
- return null;
318
+ function readSessionRunEntries(sessionPath) {
319
+ if (!sessionPath || !existsSync2(sessionPath))
320
+ return [];
321
+ let raw;
411
322
  try {
412
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
413
- return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
323
+ raw = readFileSync2(sessionPath, "utf8");
414
324
  } catch {
415
- return null;
325
+ return [];
416
326
  }
417
- }
418
- function readLocalConnectionFallbackToken(projectRoot) {
419
- return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
420
- }
421
- async function ensureServerForCli(projectRoot) {
422
- try {
423
- const selected = resolveSelectedConnection(projectRoot);
424
- if (selected?.connection.kind === "remote") {
425
- reportServerPhase(`Connecting to ${selected.alias}\u2026`);
426
- const authToken = readGitHubBearerTokenForRemote(projectRoot);
427
- const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
428
- return {
429
- baseUrl: selected.connection.baseUrl,
430
- authToken,
431
- connectionKind: "remote",
432
- serverProjectRoot
433
- };
434
- }
435
- reportServerPhase("Starting local Rig server\u2026");
436
- const connection = await ensureLocalRigServerConnection(projectRoot);
437
- return {
438
- baseUrl: connection.baseUrl,
439
- authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
440
- connectionKind: "local",
441
- serverProjectRoot: resolve2(projectRoot)
442
- };
443
- } catch (error) {
444
- if (error instanceof Error) {
445
- throw new CliError(error.message, 1);
327
+ const entries = [];
328
+ for (const line of raw.split(`
329
+ `)) {
330
+ if (!line.trim())
331
+ continue;
332
+ let parsed;
333
+ try {
334
+ parsed = JSON.parse(line);
335
+ } catch {
336
+ continue;
446
337
  }
447
- throw error;
448
- }
449
- }
450
- async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
451
- const repo = readRepoConnection(projectRoot);
452
- const slug = repo?.project?.trim();
453
- if (!slug)
454
- return null;
455
- try {
456
- const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
457
- headers: mergeHeaders(undefined, authToken)
338
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
339
+ continue;
340
+ entries.push({
341
+ type: "type" in parsed && typeof parsed.type === "string" ? parsed.type : "",
342
+ ..."customType" in parsed && typeof parsed.customType === "string" ? { customType: parsed.customType } : {},
343
+ ..."data" in parsed ? { data: parsed.data } : {}
458
344
  });
459
- if (!response.ok)
460
- return null;
461
- const payload = await response.json();
462
- const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
463
- const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
464
- const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
465
- const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
466
- if (path)
467
- writeRepoServerProjectRoot(projectRoot, path);
468
- return path;
469
- } catch {
470
- return null;
471
345
  }
346
+ return entries;
472
347
  }
473
- function mergeHeaders(headers, authToken) {
474
- const merged = new Headers(headers);
475
- if (authToken) {
476
- merged.set("authorization", `Bearer ${authToken}`);
477
- }
478
- return merged;
348
+ function recordFromProjection(runId, projection, presence) {
349
+ const record = projection.record;
350
+ const status = projection.status ?? (presence.stale ? "stale" : presence.live ? "running" : "unknown");
351
+ const taskId = stringOrNull(record.taskId) ?? presence.fallbackTaskId ?? null;
352
+ const title = stringOrNull(record.title) ?? stringOrNull(presence.title) ?? (taskId ? `Run ${taskId}` : `Rig run ${runId}`);
353
+ return {
354
+ runId,
355
+ taskId,
356
+ title,
357
+ status,
358
+ source: presence.source,
359
+ live: presence.live,
360
+ stale: presence.stale,
361
+ startedAt: stringOrNull(record.startedAt) ?? presence.startedAt ?? null,
362
+ updatedAt: stringOrNull(record.updatedAt) ?? presence.updatedAt ?? projection.lastEventAt ?? null,
363
+ completedAt: stringOrNull(record.completedAt),
364
+ joinLink: presence.joinLink ?? null,
365
+ webLink: presence.webLink ?? null,
366
+ relayUrl: presence.relayUrl ?? null,
367
+ sessionPath: stringOrNull(record.sessionPath) ?? presence.sessionPath ?? null,
368
+ prUrl: stringOrNull(record.prUrl),
369
+ worktreePath: stringOrNull(record.worktreePath),
370
+ pendingApprovals: projection.pendingApprovals.length,
371
+ pendingInputs: projection.pendingUserInputs.length,
372
+ steeringCount: projection.steeringCount,
373
+ stallCount: projection.stallCount,
374
+ errorSummary: summarizeUsefulRunError(projection),
375
+ projection
376
+ };
479
377
  }
480
- function diagnosticMessage(payload) {
481
- if (!payload || typeof payload !== "object" || Array.isArray(payload))
378
+ function recordFromLiveCollab(collab) {
379
+ const runId = collab.sessionId;
380
+ const sessionPath = stringOrNull(collab.sessionPath);
381
+ const projection = sessionPath ? foldRunSessionEntries(readSessionRunEntries(sessionPath), runId) : EMPTY_PROJECTION;
382
+ return recordFromProjection(runId, projection, {
383
+ source: sessionPath ? "local" : "remote",
384
+ live: !collab.stale,
385
+ stale: Boolean(collab.stale),
386
+ title: collab.title,
387
+ joinLink: stringOrNull(collab.joinLink),
388
+ webLink: stringOrNull(collab.webLink),
389
+ relayUrl: stringOrNull(collab.relayUrl),
390
+ sessionPath,
391
+ startedAt: stringOrNull(collab.startedAt),
392
+ updatedAt: stringOrNull(collab.updatedAt)
393
+ });
394
+ }
395
+ function recordFromRuntime(runtime) {
396
+ const sessionPath = newestSessionFile(runtime.sessionDir ?? "");
397
+ if (!sessionPath)
482
398
  return null;
483
- const record = payload;
484
- const diagnostics = Array.isArray(record.diagnostics) ? record.diagnostics : [];
485
- const messages = diagnostics.flatMap((entry) => {
486
- if (!entry || typeof entry !== "object" || Array.isArray(entry))
487
- return [];
488
- const diagnostic = entry;
489
- const kind = typeof diagnostic.kind === "string" ? diagnostic.kind : "task-source";
490
- const message = typeof diagnostic.message === "string" ? diagnostic.message : null;
491
- return message ? [`${kind}: ${message}`] : [];
399
+ const projection = foldRunSessionEntries(readSessionRunEntries(sessionPath), runtime.id);
400
+ if (projection.lastSeq === 0)
401
+ return null;
402
+ return recordFromProjection(sessionIdFromSessionFile2(sessionPath) ?? runtime.id, projection, {
403
+ source: "local",
404
+ live: false,
405
+ stale: false,
406
+ sessionPath,
407
+ fallbackTaskId: stringOrNull(runtime.taskId)
408
+ });
409
+ }
410
+ function sortByRecency(records) {
411
+ return [...records].sort((a, b) => {
412
+ const at = Date.parse(b.updatedAt ?? b.startedAt ?? "") || 0;
413
+ const bt = Date.parse(a.updatedAt ?? a.startedAt ?? "") || 0;
414
+ return at - bt;
492
415
  });
493
- return messages.length > 0 ? messages.join("; ") : null;
494
416
  }
495
- var serverReachabilityCache = new Map;
496
- async function probeServerReachability(baseUrl, authToken) {
417
+ async function listRunProjections(projectRoot, filter = {}) {
418
+ const byRunId = new Map;
419
+ let live = [];
497
420
  try {
498
- const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
499
- headers: mergeHeaders(undefined, authToken),
500
- signal: AbortSignal.timeout(1500)
501
- });
502
- return response.ok;
421
+ live = await listActiveRunCollab(filter);
503
422
  } catch {
504
- return false;
423
+ live = [];
505
424
  }
506
- }
507
- function cachedServerReachability(projectRoot, baseUrl, authToken) {
508
- const key = resolve2(projectRoot);
509
- const cached = serverReachabilityCache.get(key);
510
- if (cached)
511
- return cached;
512
- const probe = probeServerReachability(baseUrl, authToken);
513
- serverReachabilityCache.set(key, probe);
514
- return probe;
515
- }
516
- function describeSelectedServer(projectRoot, server) {
517
- try {
518
- const selected = resolveSelectedConnection(projectRoot);
519
- if (selected) {
520
- return {
521
- alias: selected.alias,
522
- target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
523
- };
524
- }
525
- } catch {}
526
- return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
527
- }
528
- async function buildServerFailureContext(projectRoot, server) {
529
- const { alias, target } = describeSelectedServer(projectRoot, server);
530
- const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
531
- const reachability = reachable ? "server is reachable" : "server is unreachable";
532
- return {
533
- contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
534
- hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
535
- };
536
- }
537
- async function requestServerJson(context, pathname, init = {}) {
538
- const server = await ensureServerForCli(context.projectRoot);
539
- const headers = mergeHeaders(init.headers, server.authToken);
540
- if (server.serverProjectRoot)
541
- headers.set("x-rig-project-root", server.serverProjectRoot);
542
- reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
543
- let response;
425
+ for (const collab of live) {
426
+ const record = recordFromLiveCollab(collab);
427
+ byRunId.set(record.runId, record);
428
+ }
429
+ let runtimes = [];
544
430
  try {
545
- response = await fetch(`${server.baseUrl}${pathname}`, {
546
- ...init,
547
- headers
548
- });
549
- } catch (error) {
550
- const failure = await buildServerFailureContext(context.projectRoot, server);
551
- throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
552
- ${failure.contextLine}`, 1, { hint: failure.hint });
431
+ runtimes = await listAgentRuntimes2(projectRoot);
432
+ } catch {
433
+ runtimes = [];
553
434
  }
554
- const text = await response.text();
555
- const payload = text.trim().length > 0 ? (() => {
556
- try {
557
- return JSON.parse(text);
558
- } catch {
559
- return null;
435
+ for (const runtime of runtimes) {
436
+ const record = recordFromRuntime(runtime);
437
+ if (!record)
438
+ continue;
439
+ const sessionId = sessionIdFromSessionFile2(record.sessionPath);
440
+ const liveMatch = sessionId ? byRunId.get(sessionId) : undefined;
441
+ if (liveMatch) {
442
+ if (liveMatch.source === "remote" || !liveMatch.sessionPath) {
443
+ byRunId.set(liveMatch.runId, { ...liveMatch, sessionPath: record.sessionPath, source: "local" });
444
+ }
445
+ continue;
560
446
  }
561
- })() : null;
562
- if (!response.ok) {
563
- const diagnostics = diagnosticMessage(payload);
564
- const rawDetail = diagnostics ?? (text || response.statusText);
565
- const detail = diagnostics ? rawDetail : rawDetail.split(`
566
- `).map((line) => line.trim()).find((line) => line.length > 0)?.slice(0, 200) ?? response.statusText;
567
- const failure = await buildServerFailureContext(context.projectRoot, server);
568
- throw new CliError(`Rig server request failed (${response.status}): ${detail}
569
- ${failure.contextLine}`, 1, { hint: failure.hint });
447
+ if (byRunId.has(runtime.id))
448
+ continue;
449
+ byRunId.set(record.runId, record);
570
450
  }
571
- return payload;
572
- }
573
- var RESUMABLE_RUN_STATUSES = new Set([
574
- "created",
575
- "preparing",
576
- "running",
577
- "validating",
578
- "reviewing",
579
- "stopped",
580
- "failed",
581
- "needs-attention",
582
- "needs_attention"
583
- ]);
451
+ return sortByRecency([...byRunId.values()]);
452
+ }
584
453
 
585
- // packages/cli/src/commands/_async-ui.ts
586
- import pc2 from "picocolors";
587
- var DONE_SYMBOL = pc2.green("\u25C7");
588
- var FAIL_SYMBOL = pc2.red("\u25A0");
589
- var activeUpdate = null;
590
- async function withSpinner(label, work, options = {}) {
591
- if (options.outputMode === "json") {
592
- return work(() => {});
593
- }
594
- if (activeUpdate) {
595
- const outer = activeUpdate;
596
- outer(label);
597
- return work(outer);
598
- }
599
- const output = options.output ?? process.stderr;
600
- const isTty = output.isTTY === true;
601
- let lastLabel = label;
602
- if (!isTty) {
603
- output.write(`${label}
604
- `);
605
- const update2 = (next) => {
606
- lastLabel = next;
607
- };
608
- activeUpdate = update2;
609
- const previousListener2 = setServerPhaseListener(update2);
610
- try {
611
- return await work(update2);
612
- } finally {
613
- activeUpdate = null;
614
- setServerPhaseListener(previousListener2);
454
+ // packages/cli/src/commands/inbox.ts
455
+ function promptFromPayload(payload, fallback) {
456
+ if (payload && typeof payload === "object") {
457
+ const record = payload;
458
+ for (const key of ["title", "message", "reason", "prompt", "summary"]) {
459
+ const value = record[key];
460
+ if (typeof value === "string" && value.trim().length > 0)
461
+ return value.trim();
615
462
  }
616
463
  }
617
- const spinner = createTtySpinner({
618
- label,
619
- output,
620
- frames: MICRO_DRONE_FRAMES,
621
- styleFrame: (frame) => renderMicroDroneFrame(Math.max(0, MICRO_DRONE_FRAMES.indexOf(frame)))
622
- });
623
- const update = (next) => {
624
- lastLabel = next;
625
- spinner.setLabel(next);
626
- };
627
- activeUpdate = update;
628
- const previousListener = setServerPhaseListener(update);
629
- try {
630
- const result = await work(update);
631
- spinner.stop(options.doneLabel ? `${DONE_SYMBOL} ${options.doneLabel}` : undefined);
632
- return result;
633
- } catch (error) {
634
- spinner.stop(`${FAIL_SYMBOL} ${lastLabel}`);
635
- throw error;
636
- } finally {
637
- activeUpdate = null;
638
- setServerPhaseListener(previousListener);
639
- }
464
+ return fallback;
640
465
  }
641
-
642
- // packages/cli/src/commands/inbox.ts
643
- async function listInboxRecords(context, kind, filters) {
644
- const params = new URLSearchParams;
645
- if (filters.run)
646
- params.set("runId", filters.run);
647
- if (filters.task)
648
- params.set("taskId", filters.task);
649
- const query = params.size > 0 ? `?${params.toString()}` : "";
650
- const payload = await requestServerJson(context, `/api/inbox/${kind}${query}`);
651
- const records = Array.isArray(payload) ? payload : [];
652
- return filters.pendingOnly ? records.filter((entry) => (entry.status ?? "pending") !== "resolved") : records;
466
+ function recordsFromRun(run, kind) {
467
+ const pending = kind === "approvals" ? run.projection.pendingApprovals : run.projection.pendingUserInputs;
468
+ return pending.map((request) => ({
469
+ runId: run.runId,
470
+ taskId: run.taskId,
471
+ requestId: request.requestId,
472
+ status: "pending",
473
+ prompt: promptFromPayload(request.payload, kind === "approvals" ? "Approval requested" : "Input requested"),
474
+ requestedAt: request.requestedAt,
475
+ payload: request.payload
476
+ }));
477
+ }
478
+ async function listInboxRecords(context, kind, filters = {}, deps = {}) {
479
+ const runs = await (deps.listRuns ?? ((root) => listRunProjections(root)))(context.projectRoot);
480
+ const out = [];
481
+ for (const run of runs) {
482
+ if (filters.run && run.runId !== filters.run)
483
+ continue;
484
+ if (filters.task && run.taskId !== filters.task)
485
+ continue;
486
+ out.push(...recordsFromRun(run, kind));
487
+ }
488
+ return out;
653
489
  }
654
490
  async function readPendingInboxCounts(context) {
655
491
  try {
656
492
  const [approvals, inputs] = await Promise.all([
657
- listInboxRecords(context, "approvals", { pendingOnly: true }),
658
- listInboxRecords(context, "inputs", { pendingOnly: true })
493
+ listInboxRecords(context, "approvals"),
494
+ listInboxRecords(context, "inputs")
659
495
  ]);
660
496
  return { approvals: approvals.length, inputs: inputs.length };
661
497
  } catch {
662
498
  return null;
663
499
  }
664
500
  }
665
- async function printPendingInboxFooter(context) {
666
- if (context.outputMode !== "text")
667
- return;
668
- const counts = await readPendingInboxCounts(context);
669
- if (!counts || counts.approvals === 0 && counts.inputs === 0)
670
- return;
671
- const parts = [];
672
- if (counts.approvals > 0)
673
- parts.push(`${counts.approvals} approval${counts.approvals === 1 ? "" : "s"}`);
674
- if (counts.inputs > 0)
675
- parts.push(`${counts.inputs} input request${counts.inputs === 1 ? "" : "s"}`);
676
- console.log(`
677
- \u26A0 ${parts.join(" and ")} pending \u2014 run \`rig inbox\` to review.`);
678
- }
679
- function renderList(context, kind, records) {
680
- if (context.outputMode === "text") {
681
- printFormattedOutput(formatInboxList(kind, records));
682
- }
683
- }
684
- async function watchInbox(context, filters) {
685
- const render = async () => {
686
- const [approvals, inputs] = await Promise.all([
687
- listInboxRecords(context, "approvals", { ...filters, pendingOnly: true }),
688
- listInboxRecords(context, "inputs", { ...filters, pendingOnly: true })
689
- ]);
690
- console.clear();
691
- console.log(`rig inbox \u2014 watching (Ctrl-C to stop) \u2014 ${new Date().toLocaleTimeString()}`);
692
- renderList(context, "approvals", approvals);
693
- renderList(context, "inputs", inputs);
694
- if (approvals.length === 0 && inputs.length === 0) {
695
- console.log("Nothing pending.");
696
- } else {
697
- console.log(`
698
- Resolve with: rig inbox approve --run <id> --request <id> --decision approve|reject`);
699
- }
700
- };
701
- await render();
702
- const server = await ensureServerForCli(context.projectRoot);
703
- const relevant = /"type":"rig\.(approval|user-input)\./;
704
- const fallbackTimer = setInterval(() => {
705
- render();
706
- }, 30000);
707
- fallbackTimer.unref?.();
708
- for (;; ) {
709
- try {
710
- const response = await fetch(`${server.baseUrl}/api/server/events`, {
711
- headers: {
712
- ...server.authToken ? { authorization: `Bearer ${server.authToken}` } : {},
713
- ...server.serverProjectRoot ? { "x-rig-project-root": server.serverProjectRoot } : {},
714
- accept: "text/event-stream"
715
- }
716
- });
717
- if (!response.ok || !response.body)
718
- throw new Error(`SSE ${response.status}`);
719
- const reader = response.body.getReader();
720
- const decoder = new TextDecoder;
721
- for (;; ) {
722
- const { done, value } = await reader.read();
723
- if (done)
724
- break;
725
- const chunk = decoder.decode(value, { stream: true });
726
- if (relevant.test(chunk)) {
727
- await render();
728
- }
729
- }
730
- } catch {
731
- await render();
732
- await new Promise((resolveSleep) => setTimeout(resolveSleep, 2000));
733
- }
501
+ function resolveRequiresAttach(runId, verb) {
502
+ const target = runId ?? "<run-id>";
503
+ throw new CliError(`rig inbox ${verb} cannot deliver a resolution into a live worker from a one-shot command.`, 2, { hint: `Attach to resolve interactively: rig run attach ${target}` });
504
+ }
505
+ async function readInboxSnapshot(context, filters, deps) {
506
+ const [approvals, inputs] = await Promise.all([
507
+ listInboxRecords(context, "approvals", filters, deps),
508
+ listInboxRecords(context, "inputs", filters, deps)
509
+ ]);
510
+ return { approvals, inputs };
511
+ }
512
+ function renderInboxSnapshot(snapshot) {
513
+ console.clear();
514
+ console.log(`rig inbox \u2014 watching (Ctrl-C to stop) \u2014 ${new Date().toLocaleTimeString()}`);
515
+ printFormattedOutput(formatInboxList("approvals", snapshot.approvals.map((entry) => ({ ...entry }))));
516
+ printFormattedOutput(formatInboxList("inputs", snapshot.inputs.map((entry) => ({ ...entry }))));
517
+ if (snapshot.approvals.length === 0 && snapshot.inputs.length === 0)
518
+ console.log("Nothing pending.");
519
+ }
520
+ async function watchInbox(context, filters, deps) {
521
+ const snapshot = await readInboxSnapshot(context, filters, deps);
522
+ if (context.outputMode !== "text") {
523
+ return { ok: true, group: "inbox", command: "watch", details: snapshot };
734
524
  }
525
+ renderInboxSnapshot(snapshot);
526
+ await Effect.runPromise(localRunChanges(context.projectRoot).pipe(Stream.debounce(Duration.millis(500)), Stream.runForEach(() => Effect.promise(async () => {
527
+ renderInboxSnapshot(await readInboxSnapshot(context, filters, deps));
528
+ }))));
529
+ return { ok: true, group: "inbox", command: "watch", details: snapshot };
735
530
  }
736
- async function executeInbox(context, args) {
531
+ async function executeInbox(context, args, deps = {}) {
737
532
  const [command = "approvals", ...rest] = args;
533
+ const text = context.outputMode === "text";
738
534
  switch (command) {
739
- case "approvals": {
740
- let pending = rest;
741
- const run = takeOption(pending, "--run");
742
- pending = run.rest;
743
- const task = takeOption(pending, "--task");
744
- pending = task.rest;
745
- requireNoExtraArgs(pending, "rig inbox approvals [--run <id>] [--task <id>]");
746
- const approvals = await withSpinner("Reading approvals from server\u2026", () => listInboxRecords(context, "approvals", { run: run.value, task: task.value }), { outputMode: context.outputMode });
747
- renderList(context, "approvals", approvals);
748
- return { ok: true, group: "inbox", command, details: { approvals } };
749
- }
535
+ case "approvals":
750
536
  case "inputs": {
751
- let pending = rest;
752
- const run = takeOption(pending, "--run");
753
- pending = run.rest;
754
- const task = takeOption(pending, "--task");
755
- pending = task.rest;
756
- requireNoExtraArgs(pending, "rig inbox inputs [--run <id>] [--task <id>]");
757
- const requests = await withSpinner("Reading input requests from server\u2026", () => listInboxRecords(context, "inputs", { run: run.value, task: task.value }), { outputMode: context.outputMode });
758
- renderList(context, "inputs", requests);
759
- return { ok: true, group: "inbox", command, details: { requests } };
537
+ const run = takeOption(rest, "--run");
538
+ const task = takeOption(run.rest, "--task");
539
+ requireNoExtraArgs(task.rest, `rig inbox ${command} [--run <id>] [--task <id>]`);
540
+ const kind = command === "approvals" ? "approvals" : "inputs";
541
+ const filters = {
542
+ ...run.value ? { run: run.value } : {},
543
+ ...task.value ? { task: task.value } : {}
544
+ };
545
+ const entries = await listInboxRecords(context, kind, filters, deps);
546
+ if (text)
547
+ printFormattedOutput(formatInboxList(kind, entries.map((entry) => ({ ...entry }))));
548
+ return { ok: true, group: "inbox", command, details: { entries } };
760
549
  }
761
550
  case "approve": {
762
- let pending = rest;
763
- const run = takeOption(pending, "--run");
764
- pending = run.rest;
765
- const request = takeOption(pending, "--request");
766
- pending = request.rest;
767
- const decision = takeOption(pending, "--decision");
768
- pending = decision.rest;
769
- const note = takeOption(pending, "--note");
770
- pending = note.rest;
771
- requireNoExtraArgs(pending, "rig inbox approve --run <id> --request <id> --decision approve|reject [--note <text>]");
772
- if (!run.value || !request.value || !decision.value) {
773
- throw new CliError("approve requires --run, --request, and --decision. List pending requests with `rig inbox approvals`.");
774
- }
775
- if (decision.value !== "approve" && decision.value !== "reject") {
776
- throw new CliError("decision must be approve or reject.", 1, { hint: "Re-run as `rig inbox approve --run <id> --request <id> --decision approve|reject`." });
777
- }
778
- const result = await withSpinner(`Resolving approval ${request.value}\u2026`, () => requestServerJson(context, "/api/inbox/approvals/resolve", {
779
- method: "POST",
780
- headers: { "content-type": "application/json" },
781
- body: JSON.stringify({
782
- runId: run.value,
783
- requestId: request.value,
784
- decision: decision.value,
785
- note: note.value ?? null
786
- })
787
- }), { outputMode: context.outputMode });
788
- return { ok: true, group: "inbox", command, details: { result } };
551
+ const run = takeOption(rest, "--run");
552
+ resolveRequiresAttach(run.value, "approve");
553
+ break;
789
554
  }
790
555
  case "respond": {
791
- let pending = rest;
792
- const run = takeOption(pending, "--run");
793
- pending = run.rest;
794
- const request = takeOption(pending, "--request");
795
- pending = request.rest;
796
- const answers = [];
797
- const remaining = [];
798
- for (let index = 0;index < pending.length; index += 1) {
799
- const current = pending[index];
800
- if (current === "--answer") {
801
- const next = pending[index + 1];
802
- if (!next || next.startsWith("-")) {
803
- throw new CliError("Missing value for --answer", 1, { hint: "Pass key=value pairs, e.g. `rig inbox respond --run <id> --request <id> --answer key=value`." });
804
- }
805
- answers.push(next);
806
- index += 1;
807
- continue;
808
- }
809
- if (current !== undefined) {
810
- remaining.push(current);
811
- }
812
- }
813
- requireNoExtraArgs(remaining, "rig inbox respond --run <id> --request <id> --answer key=value [--answer key=value]");
814
- if (!run.value || !request.value || answers.length === 0) {
815
- throw new CliError("respond requires --run, --request, and at least one --answer. List pending requests with `rig inbox inputs`.");
816
- }
817
- const parsedAnswers = Object.fromEntries(answers.map((entry) => {
818
- const [key, ...restValue] = entry.split("=");
819
- return [key, restValue.join("=")];
820
- }));
821
- const result = await withSpinner(`Sending response for ${request.value}\u2026`, () => requestServerJson(context, "/api/inbox/inputs/respond", {
822
- method: "POST",
823
- headers: { "content-type": "application/json" },
824
- body: JSON.stringify({
825
- runId: run.value,
826
- requestId: request.value,
827
- answers: parsedAnswers
828
- })
829
- }), { outputMode: context.outputMode });
830
- return { ok: true, group: "inbox", command, details: { result } };
556
+ const run = takeOption(rest, "--run");
557
+ resolveRequiresAttach(run.value, "respond");
558
+ break;
831
559
  }
832
560
  case "watch": {
833
- let pending = rest;
834
- const run = takeOption(pending, "--run");
835
- pending = run.rest;
836
- const task = takeOption(pending, "--task");
837
- pending = task.rest;
838
- requireNoExtraArgs(pending, "rig inbox watch [--run <id>] [--task <id>]");
839
- return await watchInbox(context, { run: run.value, task: task.value });
561
+ const run = takeOption(rest, "--run");
562
+ const task = takeOption(run.rest, "--task");
563
+ requireNoExtraArgs(task.rest, "rig inbox watch [--run <id>] [--task <id>]");
564
+ const filters = {
565
+ ...run.value ? { run: run.value } : {},
566
+ ...task.value ? { task: task.value } : {}
567
+ };
568
+ return watchInbox(context, filters, deps);
840
569
  }
841
570
  default:
842
- throw new CliError(`Unknown inbox command: ${command}. Use approvals|inputs|approve|respond|watch.`);
571
+ throw new CliError(`Unknown inbox command: ${command}`, 1, { hint: "Run `rig inbox --help` to list inbox commands." });
843
572
  }
573
+ return { ok: true, group: "inbox", command };
844
574
  }
845
575
  export {
846
576
  readPendingInboxCounts,
847
- printPendingInboxFooter,
848
577
  listInboxRecords,
849
578
  executeInbox
850
579
  };