@h-rig/cli 0.0.6-alpha.90 → 0.0.6-alpha.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -19
- package/dist/bin/build-rig-binaries.js +22 -10
- package/dist/bin/rig.d.ts +71 -1
- package/dist/bin/rig.js +15078 -11169
- package/dist/config/rig-default-config.yml +5 -0
- package/dist/src/app/drone-ui.d.ts +11 -14
- package/dist/src/app/drone-ui.js +70 -86
- package/dist/src/commands/_async-ui.d.ts +1 -4
- package/dist/src/commands/_async-ui.js +9 -111
- package/dist/src/commands/_cli-format.d.ts +16 -9
- package/dist/src/commands/_cli-format.js +167 -295
- package/dist/src/commands/_connection-state.d.ts +11 -1
- package/dist/src/commands/_connection-state.js +50 -5
- package/dist/src/commands/_doctor-checks.d.ts +0 -6
- package/dist/src/commands/_doctor-checks.js +79 -382
- package/dist/src/commands/_help-catalog.d.ts +1 -1
- package/dist/src/commands/_help-catalog.js +217 -157
- package/dist/src/commands/_inprocess-services.d.ts +33 -0
- package/dist/src/commands/_inprocess-services.js +102 -0
- package/dist/src/commands/_json-output.js +4 -0
- package/dist/src/commands/_lazy-reconcile.d.ts +34 -0
- package/dist/src/commands/_lazy-reconcile.js +102 -0
- package/dist/src/commands/_paths.js +1 -1
- package/dist/src/commands/_pi-frontend.d.ts +18 -10
- package/dist/src/commands/_pi-frontend.js +37 -715
- package/dist/src/commands/_pi-install.js +18 -36
- package/dist/src/commands/_policy.d.ts +1 -1
- package/dist/src/commands/_policy.js +56 -15
- package/dist/src/commands/_run-bridge.d.ts +114 -0
- package/dist/src/commands/_run-bridge.js +387 -0
- package/dist/src/commands/_run-diagnostics.d.ts +9 -0
- package/dist/src/commands/_run-diagnostics.js +51 -0
- package/dist/src/commands/_run-driver-helpers.d.ts +8 -81
- package/dist/src/commands/_run-driver-helpers.js +79 -283
- package/dist/src/commands/_run-projection.d.ts +50 -0
- package/dist/src/commands/_run-projection.js +349 -0
- package/dist/src/commands/_run-subcommands.d.ts +3 -0
- package/dist/src/commands/_run-subcommands.js +31 -0
- package/dist/src/commands/_spinner.js +1 -1
- package/dist/src/commands/agent.d.ts +1 -1
- package/dist/src/commands/agent.js +8559 -239
- package/dist/src/commands/dist.d.ts +1 -1
- package/dist/src/commands/dist.js +27 -19
- package/dist/src/commands/doctor.d.ts +1 -1
- package/dist/src/commands/doctor.js +93 -475
- package/dist/src/commands/github.d.ts +1 -1
- package/dist/src/commands/github.js +113 -387
- package/dist/src/commands/inbox.d.ts +22 -24
- package/dist/src/commands/inbox.js +420 -691
- package/dist/src/commands/init.d.ts +6 -16
- package/dist/src/commands/init.js +334 -971
- package/dist/src/commands/inspect.d.ts +19 -2
- package/dist/src/commands/inspect.js +644 -610
- package/dist/src/commands/pi.d.ts +1 -1
- package/dist/src/commands/plugin.d.ts +1 -1
- package/dist/src/commands/plugin.js +486 -7
- package/dist/src/commands/profile-and-review.d.ts +1 -1
- package/dist/src/commands/profile-and-review.js +94 -56
- package/dist/src/commands/queue.js +1 -21
- package/dist/src/commands/remote.d.ts +1 -1
- package/dist/src/commands/remote.js +837 -14
- package/dist/src/commands/repo-git-harness.d.ts +1 -1
- package/dist/src/commands/repo-git-harness.js +57 -14
- package/dist/src/commands/run.d.ts +20 -2
- package/dist/src/commands/run.js +17579 -1759
- package/dist/src/commands/server.d.ts +2 -6
- package/dist/src/commands/server.js +141 -723
- package/dist/src/commands/setup.d.ts +1 -1
- package/dist/src/commands/setup.js +102 -484
- package/dist/src/commands/stats.d.ts +13 -10
- package/dist/src/commands/stats.js +689 -761
- package/dist/src/commands/task-run-driver.d.ts +50 -88
- package/dist/src/commands/task-run-driver.js +116 -2717
- package/dist/src/commands/task.d.ts +34 -13
- package/dist/src/commands/task.js +668 -2523
- package/dist/src/commands/test.d.ts +1 -1
- package/dist/src/commands/triage.d.ts +11 -0
- package/dist/src/commands/triage.js +227 -0
- package/dist/src/commands/workspace.d.ts +1 -1
- package/dist/src/commands.d.ts +0 -16
- package/dist/src/commands.js +16657 -12250
- package/dist/src/index.js +16528 -12497
- package/dist/src/launcher.js +4 -0
- package/dist/src/operator-cli.d.ts +2 -0
- package/dist/src/operator-cli.js +17837 -0
- package/dist/src/operator-entry.d.ts +1 -0
- package/dist/src/operator-entry.js +3 -0
- package/package.json +18 -12
- package/dist/src/app/board.d.ts +0 -23
- package/dist/src/app/board.js +0 -1786
- package/dist/src/app/theme.d.ts +0 -47
- package/dist/src/app/theme.js +0 -150
- package/dist/src/commands/_authority-runs.d.ts +0 -22
- package/dist/src/commands/_authority-runs.js +0 -110
- package/dist/src/commands/_operator-surface.d.ts +0 -34
- package/dist/src/commands/_operator-surface.js +0 -220
- package/dist/src/commands/_operator-view.d.ts +0 -30
- package/dist/src/commands/_operator-view.js +0 -1070
- package/dist/src/commands/_preflight.d.ts +0 -22
- package/dist/src/commands/_preflight.js +0 -540
- package/dist/src/commands/_run-replay.d.ts +0 -24
- package/dist/src/commands/_run-replay.js +0 -142
- package/dist/src/commands/_server-client.d.ts +0 -186
- package/dist/src/commands/_server-client.js +0 -681
- package/dist/src/commands/_snapshot-upload.d.ts +0 -39
- package/dist/src/commands/_snapshot-upload.js +0 -455
- package/dist/src/commands/_task-picker.d.ts +0 -9
- package/dist/src/commands/_task-picker.js +0 -201
- package/dist/src/commands/browser.d.ts +0 -65
- package/dist/src/commands/browser.js +0 -1173
- package/dist/src/commands/connect.d.ts +0 -7
- package/dist/src/commands/connect.js +0 -419
- package/dist/src/commands/inspector.d.ts +0 -3
- package/dist/src/commands/inspector.js +0 -263
- package/dist/src/commands/task-report-bug.d.ts +0 -19
- package/dist/src/commands/task-report-bug.js +0 -1281
- package/dist/src/report-bug.d.ts +0 -44
- 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/
|
|
45
|
-
import {
|
|
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
|
|
193
|
-
var
|
|
194
|
-
|
|
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 (["
|
|
201
|
-
return
|
|
202
|
-
if (["failed", "
|
|
203
|
-
return red;
|
|
204
|
-
if (["
|
|
205
|
-
return cyan;
|
|
206
|
-
if (["
|
|
207
|
-
return yellow;
|
|
208
|
-
return
|
|
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 =
|
|
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
|
|
222
|
-
|
|
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 ?
|
|
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" ? "
|
|
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
|
-
|
|
101
|
+
dim(kind === "approvals" ? "No pending approvals." : "No pending user-input requests."),
|
|
249
102
|
"",
|
|
250
|
-
...
|
|
103
|
+
...formatLegacyAutomationSurface()
|
|
251
104
|
].join(`
|
|
252
105
|
`);
|
|
253
106
|
}
|
|
254
|
-
const lines = [formatSection(title, `${entries.length}
|
|
255
|
-
for (const
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
const
|
|
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(`${
|
|
263
|
-
lines.push(`${
|
|
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`"
|
|
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/
|
|
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 {
|
|
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/
|
|
276
|
-
import { existsSync,
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
function
|
|
289
|
-
return
|
|
290
|
-
}
|
|
291
|
-
function
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
|
301
|
-
|
|
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
|
|
306
|
-
if (!
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
227
|
+
async function listLocalRunCollabSessions(projectRoot) {
|
|
228
|
+
let runtimes;
|
|
229
|
+
try {
|
|
230
|
+
runtimes = await listAgentRuntimes(projectRoot);
|
|
231
|
+
} catch {
|
|
232
|
+
return [];
|
|
322
233
|
}
|
|
323
|
-
const
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
|
240
|
+
return out;
|
|
333
241
|
}
|
|
334
|
-
function
|
|
335
|
-
|
|
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
|
|
350
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
254
|
+
|
|
255
|
+
// packages/cli/src/commands/_run-diagnostics.ts
|
|
256
|
+
function normalizeString(value) {
|
|
257
|
+
if (typeof value !== "string")
|
|
355
258
|
return null;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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/
|
|
373
|
-
var
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
|
393
|
-
return
|
|
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
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
413
|
-
return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
|
|
323
|
+
raw = readFileSync2(sessionPath, "utf8");
|
|
414
324
|
} catch {
|
|
415
|
-
return
|
|
325
|
+
return [];
|
|
416
326
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
return
|
|
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
|
|
481
|
-
|
|
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
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
-
|
|
496
|
-
|
|
417
|
+
async function listRunProjections(projectRoot, filter = {}) {
|
|
418
|
+
const byRunId = new Map;
|
|
419
|
+
let live = [];
|
|
497
420
|
try {
|
|
498
|
-
|
|
499
|
-
headers: mergeHeaders(undefined, authToken),
|
|
500
|
-
signal: AbortSignal.timeout(1500)
|
|
501
|
-
});
|
|
502
|
-
return response.ok;
|
|
421
|
+
live = await listActiveRunCollab(filter);
|
|
503
422
|
} catch {
|
|
504
|
-
|
|
423
|
+
live = [];
|
|
505
424
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
|
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/
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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
|
-
|
|
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
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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"
|
|
658
|
-
listInboxRecords(context, "inputs"
|
|
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
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
}
|
|
684
|
-
async function watchInbox(context, filters) {
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
|
|
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
|
-
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
-
|
|
763
|
-
|
|
764
|
-
|
|
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
|
-
|
|
792
|
-
|
|
793
|
-
|
|
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
|
-
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
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}.
|
|
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
|
};
|