@h-rig/cli 0.0.6-alpha.64 → 0.0.6-alpha.65
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/dist/bin/rig.js +1349 -1599
- package/dist/src/commands/_connection-state.js +14 -5
- package/dist/src/commands/_doctor-checks.js +71 -11
- package/dist/src/commands/_help-catalog.js +99 -60
- package/dist/src/commands/_json-output.js +56 -0
- package/dist/src/commands/_operator-view.js +97 -784
- package/dist/src/commands/_parsers.js +18 -9
- package/dist/src/commands/_pi-frontend.js +96 -788
- package/dist/src/commands/_policy.js +12 -3
- package/dist/src/commands/_preflight.js +73 -13
- package/dist/src/commands/_run-driver-helpers.js +31 -5
- package/dist/src/commands/_run-replay.js +2 -2
- package/dist/src/commands/_server-client.js +76 -16
- package/dist/src/commands/_snapshot-upload.js +70 -10
- package/dist/src/commands/agent.js +21 -11
- package/dist/src/commands/browser.js +24 -15
- package/dist/src/commands/connect.js +22 -17
- package/dist/src/commands/dist.js +15 -6
- package/dist/src/commands/doctor.js +70 -10
- package/dist/src/commands/github.js +79 -19
- package/dist/src/commands/inbox.js +215 -131
- package/dist/src/commands/init.js +202 -35
- package/dist/src/commands/inspect.js +77 -17
- package/dist/src/commands/inspector.js +18 -9
- package/dist/src/commands/pi.js +18 -9
- package/dist/src/commands/plugin.js +19 -10
- package/dist/src/commands/profile-and-review.js +22 -13
- package/dist/src/commands/queue.js +19 -9
- package/dist/src/commands/remote.js +25 -16
- package/dist/src/commands/repo-git-harness.js +18 -9
- package/dist/src/commands/run.js +158 -805
- package/dist/src/commands/server.js +85 -25
- package/dist/src/commands/setup.js +73 -13
- package/dist/src/commands/stats.js +681 -0
- package/dist/src/commands/task-report-bug.js +24 -15
- package/dist/src/commands/task-run-driver.js +121 -49
- package/dist/src/commands/task.js +254 -893
- package/dist/src/commands/test.js +12 -3
- package/dist/src/commands/workspace.js +14 -5
- package/dist/src/commands.js +1339 -1650
- package/dist/src/index.js +1349 -1599
- package/dist/src/launcher.js +72 -10
- package/dist/src/runner.js +14 -5
- package/package.json +10 -7
- package/dist/src/commands/_pi-remote-session.js +0 -771
- package/dist/src/commands/_pi-worker-bridge-extension.js +0 -834
package/dist/src/commands/run.js
CHANGED
|
@@ -4,10 +4,19 @@ import { createInterface as createInterface2 } from "readline/promises";
|
|
|
4
4
|
|
|
5
5
|
// packages/cli/src/runner.ts
|
|
6
6
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
7
|
-
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
7
|
+
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
8
8
|
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
9
9
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
class CliError extends RuntimeCliError {
|
|
12
|
+
hint;
|
|
13
|
+
constructor(message, exitCode = 1, options = {}) {
|
|
14
|
+
super(message, exitCode);
|
|
15
|
+
if (options.hint?.trim()) {
|
|
16
|
+
this.hint = options.hint.trim();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
11
20
|
function takeFlag(args, flag) {
|
|
12
21
|
const rest = [];
|
|
13
22
|
let value = false;
|
|
@@ -28,7 +37,7 @@ function takeOption(args, option) {
|
|
|
28
37
|
if (current === option) {
|
|
29
38
|
const next = args[index + 1];
|
|
30
39
|
if (!next || next.startsWith("-")) {
|
|
31
|
-
throw new CliError(`Missing value for ${option}`);
|
|
40
|
+
throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
|
|
32
41
|
}
|
|
33
42
|
value = next;
|
|
34
43
|
index += 1;
|
|
@@ -73,7 +82,7 @@ function parsePositiveInt(value, option, fallback) {
|
|
|
73
82
|
}
|
|
74
83
|
const parsed = Number.parseInt(value, 10);
|
|
75
84
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
76
|
-
throw new
|
|
85
|
+
throw new CliError(`Invalid ${option} value: ${value}`, 1, { hint: `Pass a positive integer, e.g. \`${option} 10\`.` });
|
|
77
86
|
}
|
|
78
87
|
return parsed;
|
|
79
88
|
}
|
|
@@ -105,7 +114,7 @@ function readJsonFile(path) {
|
|
|
105
114
|
try {
|
|
106
115
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
107
116
|
} catch (error) {
|
|
108
|
-
throw new
|
|
117
|
+
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>`." });
|
|
109
118
|
}
|
|
110
119
|
}
|
|
111
120
|
function writeJsonFile(path, value) {
|
|
@@ -169,7 +178,7 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
169
178
|
const global = readGlobalConnections(options);
|
|
170
179
|
const connection = global.connections[repo.selected];
|
|
171
180
|
if (!connection) {
|
|
172
|
-
throw new
|
|
181
|
+
throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
173
182
|
}
|
|
174
183
|
return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
|
|
175
184
|
}
|
|
@@ -242,7 +251,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
242
251
|
};
|
|
243
252
|
} catch (error) {
|
|
244
253
|
if (error instanceof Error) {
|
|
245
|
-
throw new
|
|
254
|
+
throw new CliError(error.message, 1);
|
|
246
255
|
}
|
|
247
256
|
throw error;
|
|
248
257
|
}
|
|
@@ -292,15 +301,64 @@ function diagnosticMessage(payload) {
|
|
|
292
301
|
});
|
|
293
302
|
return messages.length > 0 ? messages.join("; ") : null;
|
|
294
303
|
}
|
|
304
|
+
var serverReachabilityCache = new Map;
|
|
305
|
+
async function probeServerReachability(baseUrl, authToken) {
|
|
306
|
+
try {
|
|
307
|
+
const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
|
|
308
|
+
headers: mergeHeaders(undefined, authToken),
|
|
309
|
+
signal: AbortSignal.timeout(1500)
|
|
310
|
+
});
|
|
311
|
+
return response.ok;
|
|
312
|
+
} catch {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function cachedServerReachability(projectRoot, baseUrl, authToken) {
|
|
317
|
+
const key = resolve2(projectRoot);
|
|
318
|
+
const cached = serverReachabilityCache.get(key);
|
|
319
|
+
if (cached)
|
|
320
|
+
return cached;
|
|
321
|
+
const probe = probeServerReachability(baseUrl, authToken);
|
|
322
|
+
serverReachabilityCache.set(key, probe);
|
|
323
|
+
return probe;
|
|
324
|
+
}
|
|
325
|
+
function describeSelectedServer(projectRoot, server) {
|
|
326
|
+
try {
|
|
327
|
+
const selected = resolveSelectedConnection(projectRoot);
|
|
328
|
+
if (selected) {
|
|
329
|
+
return {
|
|
330
|
+
alias: selected.alias,
|
|
331
|
+
target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
} catch {}
|
|
335
|
+
return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
|
|
336
|
+
}
|
|
337
|
+
async function buildServerFailureContext(projectRoot, server) {
|
|
338
|
+
const { alias, target } = describeSelectedServer(projectRoot, server);
|
|
339
|
+
const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
|
|
340
|
+
const reachability = reachable ? "server is reachable" : "server is unreachable";
|
|
341
|
+
return {
|
|
342
|
+
contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
|
|
343
|
+
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
344
|
+
};
|
|
345
|
+
}
|
|
295
346
|
async function requestServerJson(context, pathname, init = {}) {
|
|
296
347
|
const server = await ensureServerForCli(context.projectRoot);
|
|
297
348
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
298
349
|
if (server.serverProjectRoot)
|
|
299
350
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
351
|
+
let response;
|
|
352
|
+
try {
|
|
353
|
+
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
354
|
+
...init,
|
|
355
|
+
headers
|
|
356
|
+
});
|
|
357
|
+
} catch (error) {
|
|
358
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
359
|
+
throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
|
|
360
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
361
|
+
}
|
|
304
362
|
const text = await response.text();
|
|
305
363
|
const payload = text.trim().length > 0 ? (() => {
|
|
306
364
|
try {
|
|
@@ -312,7 +370,9 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
312
370
|
if (!response.ok) {
|
|
313
371
|
const diagnostics = diagnosticMessage(payload);
|
|
314
372
|
const detail = diagnostics ?? (text || response.statusText);
|
|
315
|
-
|
|
373
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
374
|
+
throw new CliError(`Rig server request failed (${response.status}): ${detail}
|
|
375
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
316
376
|
}
|
|
317
377
|
return payload;
|
|
318
378
|
}
|
|
@@ -362,26 +422,6 @@ async function steerRunViaServer(context, runId, message) {
|
|
|
362
422
|
});
|
|
363
423
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
364
424
|
}
|
|
365
|
-
async function getRunPiSessionViaServer(context, runId) {
|
|
366
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi`);
|
|
367
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
368
|
-
}
|
|
369
|
-
async function getRunPiMessagesViaServer(context, runId) {
|
|
370
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/messages`);
|
|
371
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { messages: [] };
|
|
372
|
-
}
|
|
373
|
-
async function getRunPiStatusViaServer(context, runId) {
|
|
374
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/status`);
|
|
375
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
376
|
-
}
|
|
377
|
-
async function getRunPiCommandsViaServer(context, runId) {
|
|
378
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
|
|
379
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
|
|
380
|
-
}
|
|
381
|
-
async function getRunPiCapabilitiesViaServer(context, runId) {
|
|
382
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
|
|
383
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
|
|
384
|
-
}
|
|
385
425
|
async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior) {
|
|
386
426
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
|
|
387
427
|
method: "POST",
|
|
@@ -390,44 +430,6 @@ async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior)
|
|
|
390
430
|
});
|
|
391
431
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
|
|
392
432
|
}
|
|
393
|
-
async function sendRunPiShellViaServer(context, runId, text) {
|
|
394
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/shell`, {
|
|
395
|
-
method: "POST",
|
|
396
|
-
headers: { "content-type": "application/json" },
|
|
397
|
-
body: JSON.stringify({ text })
|
|
398
|
-
});
|
|
399
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
|
|
400
|
-
}
|
|
401
|
-
async function runRunPiCommandViaServer(context, runId, text) {
|
|
402
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands/run`, {
|
|
403
|
-
method: "POST",
|
|
404
|
-
headers: { "content-type": "application/json" },
|
|
405
|
-
body: JSON.stringify({ text })
|
|
406
|
-
});
|
|
407
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { type: "done" };
|
|
408
|
-
}
|
|
409
|
-
async function respondRunPiExtensionUiViaServer(context, runId, requestId, valueOrCancel) {
|
|
410
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/extension-ui/respond`, {
|
|
411
|
-
method: "POST",
|
|
412
|
-
headers: { "content-type": "application/json" },
|
|
413
|
-
body: JSON.stringify({ requestId, ...valueOrCancel })
|
|
414
|
-
});
|
|
415
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
|
|
416
|
-
}
|
|
417
|
-
async function abortRunPiViaServer(context, runId) {
|
|
418
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/abort`, { method: "POST" });
|
|
419
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { aborted: true };
|
|
420
|
-
}
|
|
421
|
-
async function buildRunPiEventsWebSocketUrl(context, runId) {
|
|
422
|
-
const server = await ensureServerForCli(context.projectRoot);
|
|
423
|
-
const url = new URL(`${server.baseUrl.replace(/\/+$/, "")}/api/runs/${encodeURIComponent(runId)}/pi/events`);
|
|
424
|
-
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
425
|
-
if (server.authToken)
|
|
426
|
-
url.searchParams.set("token", server.authToken);
|
|
427
|
-
if (server.serverProjectRoot)
|
|
428
|
-
url.searchParams.set("rigProjectRoot", server.serverProjectRoot);
|
|
429
|
-
return url.toString();
|
|
430
|
-
}
|
|
431
433
|
|
|
432
434
|
// packages/cli/src/commands/_run-replay.ts
|
|
433
435
|
import { existsSync as existsSync3, readdirSync } from "fs";
|
|
@@ -435,7 +437,7 @@ import { join, resolve as resolve3 } from "path";
|
|
|
435
437
|
import {
|
|
436
438
|
readAuthorityRun,
|
|
437
439
|
readJsonlFile,
|
|
438
|
-
|
|
440
|
+
runJournalPath
|
|
439
441
|
} from "@rig/runtime/control-plane/authority-files";
|
|
440
442
|
function asRecord(value) {
|
|
441
443
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -533,7 +535,7 @@ function resolveRunSessionFile(record) {
|
|
|
533
535
|
}
|
|
534
536
|
}
|
|
535
537
|
function buildRunReplay(projectRoot, runId, options = {}) {
|
|
536
|
-
const logPath =
|
|
538
|
+
const logPath = runJournalPath(projectRoot, runId);
|
|
537
539
|
const record = readAuthorityRun(projectRoot, runId);
|
|
538
540
|
const lifecycleEntries = readJsonlFile(logPath).map((entry) => asRecord(entry)).filter((entry) => entry !== null);
|
|
539
541
|
const merged = lifecycleEntries.map((entry) => ({
|
|
@@ -763,684 +765,8 @@ function createOperatorSurface(options = {}) {
|
|
|
763
765
|
import { mkdtempSync, rmSync } from "fs";
|
|
764
766
|
import { tmpdir } from "os";
|
|
765
767
|
import { join as join2 } from "path";
|
|
766
|
-
import {
|
|
767
|
-
|
|
768
|
-
createAgentSessionServices,
|
|
769
|
-
main as runPiMain
|
|
770
|
-
} from "@earendil-works/pi-coding-agent";
|
|
771
|
-
|
|
772
|
-
// packages/cli/src/commands/_pi-remote-session.ts
|
|
773
|
-
import { AgentSession as PiAgentSession, expandPromptTemplate } from "@earendil-works/pi-coding-agent";
|
|
774
|
-
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
775
|
-
function defaultTransport() {
|
|
776
|
-
return {
|
|
777
|
-
getSession: getRunPiSessionViaServer,
|
|
778
|
-
getMessages: getRunPiMessagesViaServer,
|
|
779
|
-
getStatus: getRunPiStatusViaServer,
|
|
780
|
-
getCommands: getRunPiCommandsViaServer,
|
|
781
|
-
getCapabilities: getRunPiCapabilitiesViaServer,
|
|
782
|
-
sendPrompt: sendRunPiPromptViaServer,
|
|
783
|
-
sendShell: sendRunPiShellViaServer,
|
|
784
|
-
runCommand: runRunPiCommandViaServer,
|
|
785
|
-
abort: abortRunPiViaServer,
|
|
786
|
-
buildEventsWebSocketUrl: buildRunPiEventsWebSocketUrl
|
|
787
|
-
};
|
|
788
|
-
}
|
|
789
|
-
function recordOf(value) {
|
|
790
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
791
|
-
}
|
|
792
|
-
function emptyRemoteStatus() {
|
|
793
|
-
return {
|
|
794
|
-
isStreaming: false,
|
|
795
|
-
isCompacting: false,
|
|
796
|
-
isBashRunning: false,
|
|
797
|
-
pendingMessageCount: 0,
|
|
798
|
-
steeringMessages: [],
|
|
799
|
-
followUpMessages: [],
|
|
800
|
-
model: null,
|
|
801
|
-
thinkingLevel: null,
|
|
802
|
-
sessionName: null,
|
|
803
|
-
cwd: null,
|
|
804
|
-
stats: null,
|
|
805
|
-
contextUsage: null
|
|
806
|
-
};
|
|
807
|
-
}
|
|
808
|
-
function resolveAttachReadyTimeoutMs() {
|
|
809
|
-
const raw = Number.parseInt(process.env.RIG_PI_ATTACH_TIMEOUT_MS ?? "", 10);
|
|
810
|
-
return Number.isFinite(raw) && raw > 0 ? raw : 10 * 60000;
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
class RigRemoteSessionController {
|
|
814
|
-
context;
|
|
815
|
-
runId;
|
|
816
|
-
status = emptyRemoteStatus();
|
|
817
|
-
transport;
|
|
818
|
-
hooks = {};
|
|
819
|
-
session = null;
|
|
820
|
-
socket = null;
|
|
821
|
-
closed = false;
|
|
822
|
-
pendingShells = [];
|
|
823
|
-
pendingCompactions = [];
|
|
824
|
-
constructor(input) {
|
|
825
|
-
this.context = input.context;
|
|
826
|
-
this.runId = input.runId;
|
|
827
|
-
this.transport = input.transport ?? defaultTransport();
|
|
828
|
-
}
|
|
829
|
-
ingestEnvelope(envelopeValue) {
|
|
830
|
-
this.applyEnvelope(envelopeValue);
|
|
831
|
-
}
|
|
832
|
-
bindSession(session) {
|
|
833
|
-
this.session = session;
|
|
834
|
-
}
|
|
835
|
-
setUiHooks(hooks) {
|
|
836
|
-
this.hooks = hooks;
|
|
837
|
-
}
|
|
838
|
-
async connect() {
|
|
839
|
-
const ready = await this.waitForReady();
|
|
840
|
-
if (!ready || this.closed)
|
|
841
|
-
return;
|
|
842
|
-
let catchupDone = false;
|
|
843
|
-
const buffered = [];
|
|
844
|
-
const wsUrl = await this.transport.buildEventsWebSocketUrl(this.context, this.runId);
|
|
845
|
-
const socket = new WebSocket(wsUrl);
|
|
846
|
-
this.socket = socket;
|
|
847
|
-
socket.onopen = () => {
|
|
848
|
-
this.hooks.onConnectionChange?.(true);
|
|
849
|
-
this.hooks.onStatusText?.("worker session live");
|
|
850
|
-
};
|
|
851
|
-
socket.onmessage = (message) => {
|
|
852
|
-
try {
|
|
853
|
-
const payload = typeof message.data === "string" ? JSON.parse(message.data) : JSON.parse(Buffer.from(message.data).toString("utf8"));
|
|
854
|
-
if (!catchupDone)
|
|
855
|
-
buffered.push(payload);
|
|
856
|
-
else
|
|
857
|
-
this.applyEnvelope(payload);
|
|
858
|
-
} catch (error) {
|
|
859
|
-
this.hooks.onError?.(`Unparseable worker Pi event: ${error instanceof Error ? error.message : String(error)}`);
|
|
860
|
-
}
|
|
861
|
-
};
|
|
862
|
-
socket.onerror = () => socket.close();
|
|
863
|
-
socket.onclose = () => {
|
|
864
|
-
this.hooks.onConnectionChange?.(false);
|
|
865
|
-
if (!this.closed)
|
|
866
|
-
this.hooks.onStatusText?.("worker Pi WebSocket disconnected");
|
|
867
|
-
};
|
|
868
|
-
try {
|
|
869
|
-
const [messagesPayload, statusPayload, commandsPayload, capabilitiesPayload] = await Promise.all([
|
|
870
|
-
this.transport.getMessages(this.context, this.runId),
|
|
871
|
-
this.transport.getStatus(this.context, this.runId),
|
|
872
|
-
this.transport.getCommands(this.context, this.runId),
|
|
873
|
-
this.transport.getCapabilities(this.context, this.runId).catch(() => ({ capabilities: null }))
|
|
874
|
-
]);
|
|
875
|
-
const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
|
|
876
|
-
this.applyStatusPayload(statusPayload);
|
|
877
|
-
this.session?.replaceRemoteMessages(messages);
|
|
878
|
-
const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
|
|
879
|
-
const capabilities = capabilitiesPayload.capabilities && typeof capabilitiesPayload.capabilities === "object" && !Array.isArray(capabilitiesPayload.capabilities) ? capabilitiesPayload.capabilities : null;
|
|
880
|
-
this.hooks.onCatchUp?.(messages, commands, capabilities);
|
|
881
|
-
catchupDone = true;
|
|
882
|
-
for (const payload of buffered.splice(0))
|
|
883
|
-
this.applyEnvelope(payload);
|
|
884
|
-
} catch (error) {
|
|
885
|
-
catchupDone = true;
|
|
886
|
-
this.hooks.onError?.(`Worker Pi catch-up failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
close() {
|
|
890
|
-
this.closed = true;
|
|
891
|
-
this.socket?.close();
|
|
892
|
-
for (const shell of this.pendingShells.splice(0))
|
|
893
|
-
shell.reject(new Error("Remote session closed."));
|
|
894
|
-
for (const compaction of this.pendingCompactions.splice(0))
|
|
895
|
-
compaction.reject(new Error("Remote session closed."));
|
|
896
|
-
}
|
|
897
|
-
async sendPrompt(text2, streamingBehavior) {
|
|
898
|
-
await this.transport.sendPrompt(this.context, this.runId, text2, streamingBehavior);
|
|
899
|
-
}
|
|
900
|
-
async sendCommand(text2) {
|
|
901
|
-
const result = await this.transport.runCommand(this.context, this.runId, text2);
|
|
902
|
-
return typeof result.message === "string" ? result.message : "worker command accepted";
|
|
903
|
-
}
|
|
904
|
-
async sendShell(text2) {
|
|
905
|
-
await this.transport.sendShell(this.context, this.runId, text2);
|
|
906
|
-
}
|
|
907
|
-
async abort() {
|
|
908
|
-
await this.transport.abort(this.context, this.runId);
|
|
909
|
-
}
|
|
910
|
-
registerPendingShell(shell) {
|
|
911
|
-
this.pendingShells.push(shell);
|
|
912
|
-
}
|
|
913
|
-
failPendingShell(shell, error) {
|
|
914
|
-
const index = this.pendingShells.indexOf(shell);
|
|
915
|
-
if (index !== -1)
|
|
916
|
-
this.pendingShells.splice(index, 1);
|
|
917
|
-
shell.reject(error);
|
|
918
|
-
}
|
|
919
|
-
registerPendingCompaction(pending) {
|
|
920
|
-
this.pendingCompactions.push(pending);
|
|
921
|
-
}
|
|
922
|
-
async waitForReady() {
|
|
923
|
-
const startedAt = Date.now();
|
|
924
|
-
const deadline = startedAt + resolveAttachReadyTimeoutMs();
|
|
925
|
-
let consecutiveFailures = 0;
|
|
926
|
-
while (!this.closed) {
|
|
927
|
-
let requestFailed = false;
|
|
928
|
-
const session = await this.transport.getSession(this.context, this.runId).catch((error) => {
|
|
929
|
-
requestFailed = true;
|
|
930
|
-
return { ready: false, status: error instanceof Error ? error.message : String(error), retryAfterMs: 1000 };
|
|
931
|
-
});
|
|
932
|
-
if (session.ready !== false)
|
|
933
|
-
return true;
|
|
934
|
-
consecutiveFailures = requestFailed ? consecutiveFailures + 1 : 0;
|
|
935
|
-
const status = String(session.status ?? "starting");
|
|
936
|
-
if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
|
|
937
|
-
this.hooks.onError?.(`Run ended before the worker Pi daemon became ready: ${status}. Inspect with \`rig run show ${this.runId}\`.`);
|
|
938
|
-
return false;
|
|
939
|
-
}
|
|
940
|
-
this.hooks.onStatusText?.(consecutiveFailures >= 5 ? "Rig server unreachable \xB7 retrying \xB7 /detach to exit" : `waiting for worker Pi daemon \xB7 ${status} \xB7 /detach to exit`);
|
|
941
|
-
if (Date.now() >= deadline) {
|
|
942
|
-
this.hooks.onError?.(`Worker Pi daemon did not become ready (last status: ${status}). Set RIG_PI_ATTACH_TIMEOUT_MS to wait longer.`);
|
|
943
|
-
return false;
|
|
944
|
-
}
|
|
945
|
-
await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
|
|
946
|
-
}
|
|
947
|
-
return false;
|
|
948
|
-
}
|
|
949
|
-
applyEnvelope(envelopeValue) {
|
|
950
|
-
const envelope = recordOf(envelopeValue);
|
|
951
|
-
if (!envelope)
|
|
952
|
-
return;
|
|
953
|
-
const type = String(envelope.type ?? "");
|
|
954
|
-
if (type === "status.update") {
|
|
955
|
-
this.applyStatusPayload(envelope);
|
|
956
|
-
return;
|
|
957
|
-
}
|
|
958
|
-
if (type === "activity.update") {
|
|
959
|
-
const activity = recordOf(envelope.activity);
|
|
960
|
-
this.hooks.onActivity?.(String(activity?.label ?? ""), activity?.detail ? String(activity.detail) : undefined);
|
|
961
|
-
return;
|
|
962
|
-
}
|
|
963
|
-
if (type === "extension_ui_request") {
|
|
964
|
-
const request = recordOf(envelope.request);
|
|
965
|
-
if (request)
|
|
966
|
-
this.hooks.onExtensionUiRequest?.(request);
|
|
967
|
-
return;
|
|
968
|
-
}
|
|
969
|
-
if (type === "pi.ui_event") {
|
|
970
|
-
this.applyShellUiEvent(envelope.event);
|
|
971
|
-
return;
|
|
972
|
-
}
|
|
973
|
-
if (type === "pi.event") {
|
|
974
|
-
this.session?.handleRemoteSessionEvent(envelope.event);
|
|
975
|
-
this.settlePendingCompaction(envelope.event);
|
|
976
|
-
return;
|
|
977
|
-
}
|
|
978
|
-
if (type === "error") {
|
|
979
|
-
this.hooks.onError?.(String(envelope.message ?? envelope.detail ?? "unknown worker error"));
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
applyStatusPayload(payload) {
|
|
983
|
-
const status = recordOf(payload.status) ?? payload;
|
|
984
|
-
const next = this.status;
|
|
985
|
-
next.isStreaming = status.isStreaming === true;
|
|
986
|
-
next.isCompacting = status.isCompacting === true;
|
|
987
|
-
next.isBashRunning = status.isBashRunning === true;
|
|
988
|
-
next.pendingMessageCount = typeof status.pendingMessageCount === "number" ? status.pendingMessageCount : 0;
|
|
989
|
-
next.steeringMessages = Array.isArray(status.steeringMessages) ? status.steeringMessages.map(String) : [];
|
|
990
|
-
next.followUpMessages = Array.isArray(status.followUpMessages) ? status.followUpMessages.map(String) : [];
|
|
991
|
-
next.model = recordOf(status.model) ?? next.model;
|
|
992
|
-
next.thinkingLevel = typeof status.thinkingLevel === "string" ? status.thinkingLevel : next.thinkingLevel;
|
|
993
|
-
next.sessionName = typeof status.sessionName === "string" ? status.sessionName : next.sessionName;
|
|
994
|
-
next.cwd = typeof status.cwd === "string" ? status.cwd : next.cwd;
|
|
995
|
-
next.stats = recordOf(status.stats) ?? next.stats;
|
|
996
|
-
next.contextUsage = recordOf(status.contextUsage) ?? next.contextUsage;
|
|
997
|
-
}
|
|
998
|
-
applyShellUiEvent(value) {
|
|
999
|
-
const event = recordOf(value);
|
|
1000
|
-
if (!event)
|
|
1001
|
-
return;
|
|
1002
|
-
const type = String(event.type ?? "");
|
|
1003
|
-
const pending = this.pendingShells[0];
|
|
1004
|
-
if (type === "shell.chunk" && pending) {
|
|
1005
|
-
pending.sawChunk = true;
|
|
1006
|
-
pending.onData(Buffer.from(String(event.chunk ?? "")));
|
|
1007
|
-
return;
|
|
1008
|
-
}
|
|
1009
|
-
if (type === "shell.end" && pending) {
|
|
1010
|
-
const output = String(event.output ?? "");
|
|
1011
|
-
if (output && !pending.sawChunk)
|
|
1012
|
-
pending.onData(Buffer.from(output));
|
|
1013
|
-
const index = this.pendingShells.indexOf(pending);
|
|
1014
|
-
if (index !== -1)
|
|
1015
|
-
this.pendingShells.splice(index, 1);
|
|
1016
|
-
pending.resolve({ exitCode: typeof event.exitCode === "number" ? event.exitCode : event.isError === true ? 1 : 0 });
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
settlePendingCompaction(eventValue) {
|
|
1020
|
-
const event = recordOf(eventValue);
|
|
1021
|
-
if (!event)
|
|
1022
|
-
return;
|
|
1023
|
-
const type = String(event.type ?? "");
|
|
1024
|
-
if (type !== "compaction_end" || this.pendingCompactions.length === 0)
|
|
1025
|
-
return;
|
|
1026
|
-
const pending = this.pendingCompactions.shift();
|
|
1027
|
-
const result = recordOf(event.result);
|
|
1028
|
-
if (result)
|
|
1029
|
-
pending.resolve(result);
|
|
1030
|
-
else if (event.aborted === true)
|
|
1031
|
-
pending.reject(new Error("Compaction aborted on the worker."));
|
|
1032
|
-
else
|
|
1033
|
-
pending.resolve({});
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
class RigRemoteAgentSession extends PiAgentSession {
|
|
1038
|
-
remote;
|
|
1039
|
-
constructor(config, remote) {
|
|
1040
|
-
super(config);
|
|
1041
|
-
this.remote = remote;
|
|
1042
|
-
remote.bindSession(this);
|
|
1043
|
-
}
|
|
1044
|
-
handleRemoteSessionEvent(eventValue) {
|
|
1045
|
-
const event = recordOf(eventValue);
|
|
1046
|
-
if (!event)
|
|
1047
|
-
return;
|
|
1048
|
-
const type = String(event.type ?? "");
|
|
1049
|
-
if ((type === "message_end" || type === "turn_end") && recordOf(event.message)) {
|
|
1050
|
-
this.agent.state.messages = [...this.agent.state.messages, event.message];
|
|
1051
|
-
}
|
|
1052
|
-
this._emit(eventValue);
|
|
1053
|
-
}
|
|
1054
|
-
replaceRemoteMessages(messages) {
|
|
1055
|
-
this.agent.state.messages = messages;
|
|
1056
|
-
}
|
|
1057
|
-
async expandLocalInput(text2) {
|
|
1058
|
-
let current = text2;
|
|
1059
|
-
const runner = this.extensionRunner;
|
|
1060
|
-
if (runner.hasHandlers("input")) {
|
|
1061
|
-
const result = await runner.emitInput(current, undefined, "interactive");
|
|
1062
|
-
if (result.action === "handled")
|
|
1063
|
-
return null;
|
|
1064
|
-
if (result.action === "transform")
|
|
1065
|
-
current = result.text;
|
|
1066
|
-
}
|
|
1067
|
-
current = this._expandSkillCommand(current);
|
|
1068
|
-
current = expandPromptTemplate(current, [...this.promptTemplates]);
|
|
1069
|
-
return current;
|
|
1070
|
-
}
|
|
1071
|
-
async prompt(text2, options) {
|
|
1072
|
-
const trimmed = text2.trim();
|
|
1073
|
-
if (!trimmed)
|
|
1074
|
-
return;
|
|
1075
|
-
if (trimmed.startsWith("/")) {
|
|
1076
|
-
if (await this._tryExecuteExtensionCommand(trimmed))
|
|
1077
|
-
return;
|
|
1078
|
-
const expanded2 = await this.expandLocalInput(trimmed);
|
|
1079
|
-
if (expanded2 === null)
|
|
1080
|
-
return;
|
|
1081
|
-
if (expanded2 !== trimmed) {
|
|
1082
|
-
const behavior2 = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
|
|
1083
|
-
options?.preflightResult?.(true);
|
|
1084
|
-
await this.remote.sendPrompt(expanded2, behavior2);
|
|
1085
|
-
return;
|
|
1086
|
-
}
|
|
1087
|
-
await this.remote.sendCommand(trimmed);
|
|
1088
|
-
return;
|
|
1089
|
-
}
|
|
1090
|
-
if (trimmed.startsWith("!")) {
|
|
1091
|
-
await this.remote.sendShell(trimmed);
|
|
1092
|
-
return;
|
|
1093
|
-
}
|
|
1094
|
-
const expanded = await this.expandLocalInput(trimmed);
|
|
1095
|
-
if (expanded === null)
|
|
1096
|
-
return;
|
|
1097
|
-
const behavior = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
|
|
1098
|
-
options?.preflightResult?.(true);
|
|
1099
|
-
await this.remote.sendPrompt(expanded, behavior);
|
|
1100
|
-
}
|
|
1101
|
-
async steer(text2) {
|
|
1102
|
-
const trimmed = text2.trim();
|
|
1103
|
-
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
1104
|
-
return;
|
|
1105
|
-
const expanded = await this.expandLocalInput(trimmed);
|
|
1106
|
-
if (expanded === null)
|
|
1107
|
-
return;
|
|
1108
|
-
await this.remote.sendPrompt(expanded, "steer");
|
|
1109
|
-
}
|
|
1110
|
-
async followUp(text2) {
|
|
1111
|
-
const trimmed = text2.trim();
|
|
1112
|
-
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
1113
|
-
return;
|
|
1114
|
-
const expanded = await this.expandLocalInput(trimmed);
|
|
1115
|
-
if (expanded === null)
|
|
1116
|
-
return;
|
|
1117
|
-
await this.remote.sendPrompt(expanded, "followUp");
|
|
1118
|
-
}
|
|
1119
|
-
async abort() {
|
|
1120
|
-
await this.remote.abort();
|
|
1121
|
-
}
|
|
1122
|
-
async compact(customInstructions) {
|
|
1123
|
-
const pending = new Promise((resolve4, reject) => {
|
|
1124
|
-
this.remote.registerPendingCompaction({ resolve: resolve4, reject });
|
|
1125
|
-
});
|
|
1126
|
-
await this.remote.sendCommand(`/compact${customInstructions ? ` ${customInstructions}` : ""}`);
|
|
1127
|
-
return pending;
|
|
1128
|
-
}
|
|
1129
|
-
clearQueue() {
|
|
1130
|
-
const cleared = {
|
|
1131
|
-
steering: [...this.remote.status.steeringMessages],
|
|
1132
|
-
followUp: [...this.remote.status.followUpMessages]
|
|
1133
|
-
};
|
|
1134
|
-
this.remote.status.steeringMessages = [];
|
|
1135
|
-
this.remote.status.followUpMessages = [];
|
|
1136
|
-
this.remote.status.pendingMessageCount = 0;
|
|
1137
|
-
this.remote.sendCommand("/queue-clear").catch(() => {});
|
|
1138
|
-
return cleared;
|
|
1139
|
-
}
|
|
1140
|
-
get isStreaming() {
|
|
1141
|
-
return this.remote.status.isStreaming;
|
|
1142
|
-
}
|
|
1143
|
-
get isCompacting() {
|
|
1144
|
-
return this.remote.status.isCompacting;
|
|
1145
|
-
}
|
|
1146
|
-
get isBashRunning() {
|
|
1147
|
-
return this.remote.status.isBashRunning;
|
|
1148
|
-
}
|
|
1149
|
-
get pendingMessageCount() {
|
|
1150
|
-
return this.remote.status.pendingMessageCount;
|
|
1151
|
-
}
|
|
1152
|
-
getSteeringMessages() {
|
|
1153
|
-
return this.remote.status.steeringMessages;
|
|
1154
|
-
}
|
|
1155
|
-
getFollowUpMessages() {
|
|
1156
|
-
return this.remote.status.followUpMessages;
|
|
1157
|
-
}
|
|
1158
|
-
get model() {
|
|
1159
|
-
return this.remote.status.model ?? super.model;
|
|
1160
|
-
}
|
|
1161
|
-
async setModel(model) {
|
|
1162
|
-
await this.remote.sendCommand(`/model ${model.provider}/${model.id}`);
|
|
1163
|
-
this.remote.status.model = model;
|
|
1164
|
-
}
|
|
1165
|
-
get thinkingLevel() {
|
|
1166
|
-
return this.remote.status.thinkingLevel ?? super.thinkingLevel;
|
|
1167
|
-
}
|
|
1168
|
-
setThinkingLevel(level) {
|
|
1169
|
-
this.remote.status.thinkingLevel = level;
|
|
1170
|
-
this.remote.sendCommand(`/thinking ${level}`).catch(() => {});
|
|
1171
|
-
}
|
|
1172
|
-
get sessionName() {
|
|
1173
|
-
return this.remote.status.sessionName ?? super.sessionName;
|
|
1174
|
-
}
|
|
1175
|
-
setSessionName(name) {
|
|
1176
|
-
this.remote.status.sessionName = name;
|
|
1177
|
-
this.remote.sendCommand(`/name ${name}`).catch(() => {});
|
|
1178
|
-
}
|
|
1179
|
-
getSessionStats() {
|
|
1180
|
-
return this.remote.status.stats ?? super.getSessionStats();
|
|
1181
|
-
}
|
|
1182
|
-
getContextUsage() {
|
|
1183
|
-
return this.remote.status.contextUsage ?? super.getContextUsage();
|
|
1184
|
-
}
|
|
1185
|
-
dispose() {
|
|
1186
|
-
this.remote.close();
|
|
1187
|
-
super.dispose();
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
function createRemoteBashOperations(controller, excludeFromContext) {
|
|
1191
|
-
return {
|
|
1192
|
-
exec(command, _cwd, execOptions) {
|
|
1193
|
-
return new Promise((resolve4, reject) => {
|
|
1194
|
-
const pending = { onData: execOptions.onData, resolve: resolve4, reject, sawChunk: false };
|
|
1195
|
-
const cleanup = () => {
|
|
1196
|
-
execOptions.signal?.removeEventListener("abort", onAbort);
|
|
1197
|
-
if (timer)
|
|
1198
|
-
clearTimeout(timer);
|
|
1199
|
-
};
|
|
1200
|
-
const onAbort = () => {
|
|
1201
|
-
cleanup();
|
|
1202
|
-
controller.failPendingShell(pending, new Error("Remote worker shell command aborted locally."));
|
|
1203
|
-
};
|
|
1204
|
-
const timeoutMs = typeof execOptions.timeout === "number" && execOptions.timeout > 0 ? execOptions.timeout : 0;
|
|
1205
|
-
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
1206
|
-
cleanup();
|
|
1207
|
-
controller.failPendingShell(pending, new Error(`Remote worker shell command timed out after ${timeoutMs}ms.`));
|
|
1208
|
-
}, timeoutMs) : null;
|
|
1209
|
-
const wrappedResolve = pending.resolve;
|
|
1210
|
-
const wrappedReject = pending.reject;
|
|
1211
|
-
pending.resolve = (result) => {
|
|
1212
|
-
cleanup();
|
|
1213
|
-
wrappedResolve(result);
|
|
1214
|
-
};
|
|
1215
|
-
pending.reject = (error) => {
|
|
1216
|
-
cleanup();
|
|
1217
|
-
wrappedReject(error);
|
|
1218
|
-
};
|
|
1219
|
-
execOptions.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1220
|
-
controller.registerPendingShell(pending);
|
|
1221
|
-
controller.sendShell(`${excludeFromContext ? "!!" : "!"}${command}`).catch((error) => {
|
|
1222
|
-
cleanup();
|
|
1223
|
-
controller.failPendingShell(pending, error instanceof Error ? error : new Error(String(error)));
|
|
1224
|
-
});
|
|
1225
|
-
});
|
|
1226
|
-
}
|
|
1227
|
-
};
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
// packages/cli/src/commands/_spinner.ts
|
|
1231
|
-
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1232
|
-
|
|
1233
|
-
// packages/cli/src/commands/_pi-worker-bridge-extension.ts
|
|
1234
|
-
function recordOf2(value) {
|
|
1235
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1236
|
-
}
|
|
1237
|
-
function asText(value) {
|
|
1238
|
-
if (typeof value === "string")
|
|
1239
|
-
return value;
|
|
1240
|
-
if (value === null || value === undefined)
|
|
1241
|
-
return "";
|
|
1242
|
-
if (typeof value === "number" || typeof value === "boolean")
|
|
1243
|
-
return String(value);
|
|
1244
|
-
try {
|
|
1245
|
-
return JSON.stringify(value);
|
|
1246
|
-
} catch {
|
|
1247
|
-
return String(value);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
function names(value, key = "name") {
|
|
1251
|
-
if (!Array.isArray(value))
|
|
1252
|
-
return [];
|
|
1253
|
-
return value.flatMap((entry) => {
|
|
1254
|
-
if (typeof entry === "string")
|
|
1255
|
-
return [entry];
|
|
1256
|
-
const record = recordOf2(entry);
|
|
1257
|
-
const name = record?.[key];
|
|
1258
|
-
return typeof name === "string" ? [name] : [];
|
|
1259
|
-
});
|
|
1260
|
-
}
|
|
1261
|
-
function renderWorkerCapabilities(capabilities) {
|
|
1262
|
-
const lines = ["Worker session capabilities (in effect for this run)"];
|
|
1263
|
-
const tools = Array.isArray(capabilities.tools) ? capabilities.tools : [];
|
|
1264
|
-
const activeTools = tools.flatMap((tool) => {
|
|
1265
|
-
const record = recordOf2(tool);
|
|
1266
|
-
return record && record.active === true && typeof record.name === "string" ? [record.name] : [];
|
|
1267
|
-
});
|
|
1268
|
-
const inactiveCount = tools.length - activeTools.length;
|
|
1269
|
-
lines.push(` tools ${activeTools.join(", ") || "(none)"}${inactiveCount > 0 ? ` (+${inactiveCount} inactive)` : ""}`);
|
|
1270
|
-
const extensions = Array.isArray(capabilities.extensions) ? capabilities.extensions : [];
|
|
1271
|
-
const extensionLabels = extensions.flatMap((entry) => {
|
|
1272
|
-
const record = recordOf2(entry);
|
|
1273
|
-
if (!record)
|
|
1274
|
-
return [];
|
|
1275
|
-
const path = typeof record.path === "string" ? record.path : "";
|
|
1276
|
-
const short = path.split("/").filter(Boolean).slice(-2).join("/") || path || "extension";
|
|
1277
|
-
return [short];
|
|
1278
|
-
});
|
|
1279
|
-
lines.push(` extensions ${extensionLabels.join(", ") || "(none)"}`);
|
|
1280
|
-
const hookEvents = names(capabilities.hookEvents);
|
|
1281
|
-
const hookList = Array.isArray(capabilities.hookEvents) ? capabilities.hookEvents.map(asText).filter(Boolean) : hookEvents;
|
|
1282
|
-
lines.push(` hooks ${hookList.join(", ") || "(none)"}`);
|
|
1283
|
-
lines.push(` skills ${names(capabilities.skills).join(", ") || "(none)"}`);
|
|
1284
|
-
lines.push(` prompts ${names(capabilities.prompts).join(", ") || "(none)"}`);
|
|
1285
|
-
const model = typeof capabilities.model === "string" ? capabilities.model : "(unknown)";
|
|
1286
|
-
const thinking = typeof capabilities.thinkingLevel === "string" ? capabilities.thinkingLevel : "";
|
|
1287
|
-
const cwd = typeof capabilities.cwd === "string" ? capabilities.cwd : "";
|
|
1288
|
-
lines.push(` model ${model}${thinking ? ` \xB7 ${thinking}` : ""}`);
|
|
1289
|
-
if (cwd)
|
|
1290
|
-
lines.push(` cwd ${cwd}`);
|
|
1291
|
-
lines.push(" (worker commands are in your palette as [worker \u2026] \xB7 /worker shows this again)");
|
|
1292
|
-
return lines;
|
|
1293
|
-
}
|
|
1294
|
-
async function answerExtensionUiRequest(options, ctx, request) {
|
|
1295
|
-
const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
|
|
1296
|
-
const method = String(request.method ?? request.type ?? "input");
|
|
1297
|
-
const prompt = asText(request.prompt ?? request.message ?? request.title ?? method);
|
|
1298
|
-
const rawOptions = Array.isArray(request.options) ? request.options : Array.isArray(request.items) ? request.items : [];
|
|
1299
|
-
const choices = rawOptions.map((option) => {
|
|
1300
|
-
const record = recordOf2(option);
|
|
1301
|
-
return record ? asText(record.label ?? record.value ?? record.name ?? option) : asText(option);
|
|
1302
|
-
}).filter(Boolean);
|
|
1303
|
-
try {
|
|
1304
|
-
if (method === "confirm") {
|
|
1305
|
-
const confirmed = await ctx.ui.confirm("Worker request", prompt);
|
|
1306
|
-
await respondRunPiExtensionUiViaServer(options.context, options.runId, requestId, { value: confirmed, confirmed });
|
|
1307
|
-
return;
|
|
1308
|
-
}
|
|
1309
|
-
if (choices.length > 0) {
|
|
1310
|
-
const selected = await ctx.ui.select(prompt, choices);
|
|
1311
|
-
await respondRunPiExtensionUiViaServer(options.context, options.runId, requestId, selected === undefined ? { cancelled: true } : { value: selected });
|
|
1312
|
-
return;
|
|
1313
|
-
}
|
|
1314
|
-
const value = await ctx.ui.input("Worker request", prompt);
|
|
1315
|
-
await respondRunPiExtensionUiViaServer(options.context, options.runId, requestId, value === undefined ? { cancelled: true } : { value });
|
|
1316
|
-
} catch (error) {
|
|
1317
|
-
ctx.ui.notify(`Failed to answer worker UI request: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "worker", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
|
|
1321
|
-
function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
1322
|
-
for (const command of commands) {
|
|
1323
|
-
const record = recordOf2(command);
|
|
1324
|
-
const name = typeof record?.name === "string" ? record.name : "";
|
|
1325
|
-
const source = typeof record?.source === "string" ? record.source : "worker";
|
|
1326
|
-
if (!name || source === "builtin" || registered.has(name) || LOCALLY_OWNED_COMMANDS.has(name))
|
|
1327
|
-
continue;
|
|
1328
|
-
registered.add(name);
|
|
1329
|
-
const description = typeof record?.description === "string" ? record.description : undefined;
|
|
1330
|
-
try {
|
|
1331
|
-
pi.registerCommand(name, {
|
|
1332
|
-
description: `[worker ${source}] ${description ?? ""}`.trim(),
|
|
1333
|
-
handler: async (args) => {
|
|
1334
|
-
try {
|
|
1335
|
-
const message = await options.controller.sendCommand(`/${name}${args ? ` ${args}` : ""}`);
|
|
1336
|
-
ctx.ui.notify(message, "info");
|
|
1337
|
-
} catch (error) {
|
|
1338
|
-
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
});
|
|
1342
|
-
} catch {}
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
function createRigWorkerPiBridgeExtension(options) {
|
|
1346
|
-
return (pi) => {
|
|
1347
|
-
const registeredDaemonCommands = new Set;
|
|
1348
|
-
let capabilityLines = null;
|
|
1349
|
-
let statusText = "connecting to worker session";
|
|
1350
|
-
let busy = true;
|
|
1351
|
-
let connected = false;
|
|
1352
|
-
let frame = 0;
|
|
1353
|
-
let spinnerTimer = null;
|
|
1354
|
-
const renderStatus = (ctx) => {
|
|
1355
|
-
const prefix = busy ? `${SPINNER_FRAMES[frame]} ` : connected ? "\u25CF " : "\u25CB ";
|
|
1356
|
-
ctx.ui.setStatus("rig-worker-pi", `${prefix}${statusText}`);
|
|
1357
|
-
};
|
|
1358
|
-
const setStatus = (ctx, text2, isBusy) => {
|
|
1359
|
-
statusText = text2;
|
|
1360
|
-
busy = isBusy;
|
|
1361
|
-
renderStatus(ctx);
|
|
1362
|
-
};
|
|
1363
|
-
pi.registerCommand("detach", {
|
|
1364
|
-
description: "Detach from this run; the worker keeps going",
|
|
1365
|
-
handler: async (_args, ctx) => {
|
|
1366
|
-
ctx.ui.notify("Detached locally; the worker Pi daemon continues.", "info");
|
|
1367
|
-
ctx.shutdown();
|
|
1368
|
-
}
|
|
1369
|
-
});
|
|
1370
|
-
pi.registerCommand("stop", {
|
|
1371
|
-
description: "Stop the worker Pi run and detach",
|
|
1372
|
-
handler: async (_args, ctx) => {
|
|
1373
|
-
await options.controller.abort();
|
|
1374
|
-
ctx.ui.notify("Stop requested for the worker Pi daemon.", "info");
|
|
1375
|
-
ctx.shutdown();
|
|
1376
|
-
}
|
|
1377
|
-
});
|
|
1378
|
-
pi.registerCommand("worker", {
|
|
1379
|
-
description: "Show the worker session's real capabilities (tools, extensions, hooks, skills)",
|
|
1380
|
-
handler: async (_args, ctx) => {
|
|
1381
|
-
if (capabilityLines)
|
|
1382
|
-
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
1383
|
-
else
|
|
1384
|
-
ctx.ui.notify("Worker capabilities are not available yet (still connecting).", "info");
|
|
1385
|
-
}
|
|
1386
|
-
});
|
|
1387
|
-
pi.on("user_bash", (event) => ({
|
|
1388
|
-
operations: createRemoteBashOperations(options.controller, event.excludeFromContext === true)
|
|
1389
|
-
}));
|
|
1390
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
1391
|
-
ctx.ui.setTitle("Rig \xB7 enriched bundled Pi");
|
|
1392
|
-
setStatus(ctx, "waiting for worker Pi daemon", true);
|
|
1393
|
-
spinnerTimer = setInterval(() => {
|
|
1394
|
-
frame = (frame + 1) % SPINNER_FRAMES.length;
|
|
1395
|
-
if (busy)
|
|
1396
|
-
renderStatus(ctx);
|
|
1397
|
-
}, 150);
|
|
1398
|
-
ctx.ui.notify(`Enriched bundled Pi \u2014 native UI + Rig layers, worker brain. Attached to run ${options.runId}. /worker shows live capabilities \xB7 /detach exits \xB7 /stop cancels.`, "info");
|
|
1399
|
-
if (options.initialMessageSent)
|
|
1400
|
-
ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
|
|
1401
|
-
const nativeUi = ctx.ui;
|
|
1402
|
-
options.controller.setUiHooks({
|
|
1403
|
-
onStatusText: (text2) => setStatus(ctx, text2, !connected),
|
|
1404
|
-
onActivity: (label, detail) => {
|
|
1405
|
-
const active = label !== "idle" && !/complete|ready/i.test(label);
|
|
1406
|
-
setStatus(ctx, [label, detail].filter(Boolean).join(" \u2014 "), active);
|
|
1407
|
-
if (/agent running|tool:/.test(label))
|
|
1408
|
-
ctx.ui.setWidget("rig-worker-capabilities", undefined);
|
|
1409
|
-
},
|
|
1410
|
-
onConnectionChange: (nextConnected) => {
|
|
1411
|
-
connected = nextConnected;
|
|
1412
|
-
setStatus(ctx, nextConnected ? "worker session live" : "worker session disconnected", false);
|
|
1413
|
-
},
|
|
1414
|
-
onError: (message) => ctx.ui.notify(message, "error"),
|
|
1415
|
-
onExtensionUiRequest: (request) => {
|
|
1416
|
-
answerExtensionUiRequest(options, ctx, request);
|
|
1417
|
-
},
|
|
1418
|
-
onCatchUp: (messages, commands, capabilities) => {
|
|
1419
|
-
if (nativeUi.appendSessionMessages)
|
|
1420
|
-
nativeUi.appendSessionMessages(messages);
|
|
1421
|
-
const cwd = options.controller.status.cwd;
|
|
1422
|
-
if (nativeUi.setDisplayCwd && cwd)
|
|
1423
|
-
nativeUi.setDisplayCwd(cwd);
|
|
1424
|
-
registerDaemonCommands(pi, options, ctx, commands, registeredDaemonCommands);
|
|
1425
|
-
if (capabilities) {
|
|
1426
|
-
capabilityLines = renderWorkerCapabilities(capabilities);
|
|
1427
|
-
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
});
|
|
1431
|
-
options.controller.connect().catch((error) => {
|
|
1432
|
-
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
1433
|
-
});
|
|
1434
|
-
});
|
|
1435
|
-
pi.on("session_shutdown", () => {
|
|
1436
|
-
if (spinnerTimer)
|
|
1437
|
-
clearInterval(spinnerTimer);
|
|
1438
|
-
options.controller.close();
|
|
1439
|
-
});
|
|
1440
|
-
};
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
// packages/cli/src/commands/_pi-frontend.ts
|
|
768
|
+
import { main as runPiMain } from "@earendil-works/pi-coding-agent";
|
|
769
|
+
import createPiRigExtension from "@rig/pi-rig";
|
|
1444
770
|
function setTemporaryEnv(updates) {
|
|
1445
771
|
const previous = new Map;
|
|
1446
772
|
for (const [key, value] of Object.entries(updates)) {
|
|
@@ -1456,51 +782,38 @@ function setTemporaryEnv(updates) {
|
|
|
1456
782
|
}
|
|
1457
783
|
};
|
|
1458
784
|
}
|
|
785
|
+
function buildOperatorPiEnv(input) {
|
|
786
|
+
return {
|
|
787
|
+
PI_CODING_AGENT_SESSION_DIR: input.sessionDir,
|
|
788
|
+
PI_SKIP_VERSION_CHECK: "1",
|
|
789
|
+
RIG_PI_OPERATOR_SESSION: "1",
|
|
790
|
+
RIG_RUN_ID: input.runId,
|
|
791
|
+
RIG_SERVER_URL: input.serverUrl,
|
|
792
|
+
...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
|
|
793
|
+
...input.serverProjectRoot ? { RIG_PROJECT_ROOT: input.serverProjectRoot } : {}
|
|
794
|
+
};
|
|
795
|
+
}
|
|
1459
796
|
async function attachRunBundledPiFrontend(context, input) {
|
|
1460
797
|
const tempSessionDir = mkdtempSync(join2(tmpdir(), "rig-pi-frontend-sessions-"));
|
|
1461
|
-
const
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
resourceLoaderOptions: {
|
|
1472
|
-
extensionFactories: [
|
|
1473
|
-
createRigWorkerPiBridgeExtension({
|
|
1474
|
-
context,
|
|
1475
|
-
controller,
|
|
1476
|
-
runId: input.runId,
|
|
1477
|
-
initialMessageSent: input.steered === true
|
|
1478
|
-
})
|
|
1479
|
-
],
|
|
1480
|
-
noExtensions: true,
|
|
1481
|
-
noSkills: true,
|
|
1482
|
-
noPromptTemplates: true,
|
|
1483
|
-
noContextFiles: true
|
|
1484
|
-
}
|
|
1485
|
-
});
|
|
1486
|
-
const created = await createAgentSessionFromServices({
|
|
1487
|
-
services,
|
|
1488
|
-
sessionManager,
|
|
1489
|
-
sessionStartEvent,
|
|
1490
|
-
noTools: "all",
|
|
1491
|
-
sessionFactory: (config) => new RigRemoteAgentSession(config, controller)
|
|
1492
|
-
});
|
|
1493
|
-
return { ...created, services, diagnostics: services.diagnostics };
|
|
798
|
+
const server = await ensureServerForCli(context.projectRoot);
|
|
799
|
+
const restoreEnv = setTemporaryEnv(buildOperatorPiEnv({
|
|
800
|
+
runId: input.runId,
|
|
801
|
+
serverUrl: server.baseUrl,
|
|
802
|
+
authToken: server.authToken,
|
|
803
|
+
serverProjectRoot: server.serverProjectRoot,
|
|
804
|
+
sessionDir: tempSessionDir
|
|
805
|
+
}));
|
|
806
|
+
const piRigExtensionFactory = (pi) => {
|
|
807
|
+
createPiRigExtension(pi);
|
|
1494
808
|
};
|
|
1495
809
|
let detached = false;
|
|
1496
810
|
try {
|
|
1497
811
|
await runPiMain([], {
|
|
1498
|
-
|
|
812
|
+
extensionFactories: [piRigExtensionFactory]
|
|
1499
813
|
});
|
|
1500
814
|
detached = true;
|
|
1501
815
|
} finally {
|
|
1502
816
|
restoreEnv();
|
|
1503
|
-
controller.close();
|
|
1504
817
|
rmSync(tempSessionDir, { recursive: true, force: true });
|
|
1505
818
|
}
|
|
1506
819
|
let run = { runId: input.runId, status: "unknown" };
|
|
@@ -1514,12 +827,12 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
1514
827
|
timelineCursor: null,
|
|
1515
828
|
steered: input.steered === true,
|
|
1516
829
|
detached,
|
|
1517
|
-
rendered: "
|
|
830
|
+
rendered: "stock Pi operator console with the pi-rig extension"
|
|
1518
831
|
};
|
|
1519
832
|
}
|
|
1520
833
|
|
|
1521
834
|
// packages/cli/src/commands/_operator-view.ts
|
|
1522
|
-
var
|
|
835
|
+
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
1523
836
|
function runStatusFromPayload(payload) {
|
|
1524
837
|
const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
|
|
1525
838
|
return String(run.status ?? "unknown").toLowerCase();
|
|
@@ -1596,7 +909,7 @@ async function attachRunOperatorView(context, input) {
|
|
|
1596
909
|
}
|
|
1597
910
|
const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
|
|
1598
911
|
let timelineCursor = snapshot.timelineCursor;
|
|
1599
|
-
while (!detached && !
|
|
912
|
+
while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
|
|
1600
913
|
await Bun.sleep(pollMs);
|
|
1601
914
|
snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
|
|
1602
915
|
timelineCursor = snapshot.timelineCursor;
|
|
@@ -1803,6 +1116,44 @@ function formatRunSummaryLine(run) {
|
|
|
1803
1116
|
return `${pc.dim("\u2502")} ${pc.bold(runId)} ${formatStatusPill(status)} ${descriptor}${runtime ? pc.dim(` ${runtime}`) : ""}`;
|
|
1804
1117
|
}
|
|
1805
1118
|
|
|
1119
|
+
// packages/cli/src/commands/inbox.ts
|
|
1120
|
+
async function listInboxRecords(context, kind, filters) {
|
|
1121
|
+
const params = new URLSearchParams;
|
|
1122
|
+
if (filters.run)
|
|
1123
|
+
params.set("runId", filters.run);
|
|
1124
|
+
if (filters.task)
|
|
1125
|
+
params.set("taskId", filters.task);
|
|
1126
|
+
const query = params.size > 0 ? `?${params.toString()}` : "";
|
|
1127
|
+
const payload = await requestServerJson(context, `/api/inbox/${kind}${query}`);
|
|
1128
|
+
const records = Array.isArray(payload) ? payload : [];
|
|
1129
|
+
return filters.pendingOnly ? records.filter((entry) => (entry.status ?? "pending") !== "resolved") : records;
|
|
1130
|
+
}
|
|
1131
|
+
async function readPendingInboxCounts(context) {
|
|
1132
|
+
try {
|
|
1133
|
+
const [approvals, inputs] = await Promise.all([
|
|
1134
|
+
listInboxRecords(context, "approvals", { pendingOnly: true }),
|
|
1135
|
+
listInboxRecords(context, "inputs", { pendingOnly: true })
|
|
1136
|
+
]);
|
|
1137
|
+
return { approvals: approvals.length, inputs: inputs.length };
|
|
1138
|
+
} catch {
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
async function printPendingInboxFooter(context) {
|
|
1143
|
+
if (context.outputMode !== "text")
|
|
1144
|
+
return;
|
|
1145
|
+
const counts = await readPendingInboxCounts(context);
|
|
1146
|
+
if (!counts || counts.approvals === 0 && counts.inputs === 0)
|
|
1147
|
+
return;
|
|
1148
|
+
const parts = [];
|
|
1149
|
+
if (counts.approvals > 0)
|
|
1150
|
+
parts.push(`${counts.approvals} approval${counts.approvals === 1 ? "" : "s"}`);
|
|
1151
|
+
if (counts.inputs > 0)
|
|
1152
|
+
parts.push(`${counts.inputs} input request${counts.inputs === 1 ? "" : "s"}`);
|
|
1153
|
+
console.log(`
|
|
1154
|
+
\u26A0 ${parts.join(" and ")} pending \u2014 run \`rig inbox\` to review.`);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1806
1157
|
// packages/cli/src/commands/run.ts
|
|
1807
1158
|
function normalizeRemoteRunDetails(payload) {
|
|
1808
1159
|
const run = payload.run;
|
|
@@ -1854,7 +1205,7 @@ async function promptForEpicSelection(projectRoot, command) {
|
|
|
1854
1205
|
options.unshift(defaultEpic);
|
|
1855
1206
|
}
|
|
1856
1207
|
if (options.length === 0) {
|
|
1857
|
-
throw new
|
|
1208
|
+
throw new CliError("No open epic found. Pass --epic <id>.", 1, { hint: "Re-run with `rig run start --epic <id>`." });
|
|
1858
1209
|
}
|
|
1859
1210
|
console.log(`Select epic for run ${command}:`);
|
|
1860
1211
|
options.forEach((id, index) => {
|
|
@@ -1876,7 +1227,7 @@ async function promptForEpicSelection(projectRoot, command) {
|
|
|
1876
1227
|
return fallback ?? options[0];
|
|
1877
1228
|
}
|
|
1878
1229
|
if (answer === "q" || answer === "quit") {
|
|
1879
|
-
throw new
|
|
1230
|
+
throw new CliError("Run cancelled by user.");
|
|
1880
1231
|
}
|
|
1881
1232
|
if (/^\d+$/.test(answer)) {
|
|
1882
1233
|
const index = Number.parseInt(answer, 10) - 1;
|
|
@@ -1902,6 +1253,7 @@ async function executeRun(context, args) {
|
|
|
1902
1253
|
const { runs, source } = await listRunsForSelectedConnection(context, { limit: 100 });
|
|
1903
1254
|
if (context.outputMode === "text") {
|
|
1904
1255
|
printFormattedOutput(formatRunList(runs, { source }));
|
|
1256
|
+
await printPendingInboxFooter(context);
|
|
1905
1257
|
}
|
|
1906
1258
|
return { ok: true, group: "run", command, details: { runs, source } };
|
|
1907
1259
|
}
|
|
@@ -1913,7 +1265,7 @@ async function executeRun(context, args) {
|
|
|
1913
1265
|
pending = purgeArtifacts.rest;
|
|
1914
1266
|
requireNoExtraArgs(pending, "rig run delete --run <id> [--purge-artifacts]");
|
|
1915
1267
|
if (!run.value) {
|
|
1916
|
-
throw new
|
|
1268
|
+
throw new CliError("run delete requires --run <id>.", 1, { hint: "Run `rig run list` to find run ids, then `rig run delete --run <run-id>`." });
|
|
1917
1269
|
}
|
|
1918
1270
|
const result = await deleteRunState(context.projectRoot, {
|
|
1919
1271
|
runId: run.value,
|
|
@@ -1943,7 +1295,7 @@ async function executeRun(context, args) {
|
|
|
1943
1295
|
pending = keepQueue.rest;
|
|
1944
1296
|
requireNoExtraArgs(pending, "rig run cleanup --all [--keep-artifacts] [--keep-runtimes] [--keep-queue]");
|
|
1945
1297
|
if (!all.value) {
|
|
1946
|
-
throw new
|
|
1298
|
+
throw new CliError("run cleanup currently requires --all.", 1, { hint: "Run `rig run cleanup --all` (add --keep-artifacts/--keep-runtimes/--keep-queue to retain state)." });
|
|
1947
1299
|
}
|
|
1948
1300
|
const result = await cleanupRunState(context.projectRoot, {
|
|
1949
1301
|
includeArtifacts: !keepArtifacts.value,
|
|
@@ -1969,11 +1321,11 @@ async function executeRun(context, args) {
|
|
|
1969
1321
|
requireNoExtraArgs(extra, "rig run show <id>|--run <id> [--raw]");
|
|
1970
1322
|
const runId = run.value ?? positionalRunId;
|
|
1971
1323
|
if (!runId) {
|
|
1972
|
-
throw new
|
|
1324
|
+
throw new CliError("run show requires a run id.", 1, { hint: "Run `rig run list` to find run ids, then `rig run show <run-id>`." });
|
|
1973
1325
|
}
|
|
1974
1326
|
const record = readAuthorityRun2(context.projectRoot, runId) ?? normalizeRemoteRunDetails(await getRunDetailsViaServer(context, runId).catch(() => ({})));
|
|
1975
1327
|
if (!record) {
|
|
1976
|
-
throw new
|
|
1328
|
+
throw new CliError(`Run not found: ${runId}`, 2, { hint: "Run `rig run list` to see recorded runs." });
|
|
1977
1329
|
}
|
|
1978
1330
|
if (context.outputMode === "text") {
|
|
1979
1331
|
printFormattedOutput(rawResult.value ? JSON.stringify(record, null, 2) : formatRunCard(record));
|
|
@@ -1988,7 +1340,7 @@ async function executeRun(context, args) {
|
|
|
1988
1340
|
pending = follow.rest;
|
|
1989
1341
|
requireNoExtraArgs(pending, "rig run timeline --run <id> [--follow]");
|
|
1990
1342
|
if (!run.value) {
|
|
1991
|
-
throw new
|
|
1343
|
+
throw new CliError("run timeline requires --run <id>.", 1, { hint: "Run `rig run list` to find run ids, then `rig run timeline --run <run-id>`." });
|
|
1992
1344
|
}
|
|
1993
1345
|
const renderer = createPiRunStreamRenderer();
|
|
1994
1346
|
let cursor = null;
|
|
@@ -2020,11 +1372,11 @@ async function executeRun(context, args) {
|
|
|
2020
1372
|
requireNoExtraArgs(extra, "rig run replay <id>|--run <id> [--with-session]");
|
|
2021
1373
|
const runId = run.value ?? positionalRunId;
|
|
2022
1374
|
if (!runId) {
|
|
2023
|
-
throw new
|
|
1375
|
+
throw new CliError("run replay requires a run id.", 2, { hint: "Run `rig run list` to find run ids, then `rig run replay <run-id>`." });
|
|
2024
1376
|
}
|
|
2025
1377
|
const replay = buildRunReplay(context.projectRoot, runId, { withSession: withSession.value });
|
|
2026
1378
|
if (replay.entryCount === 0 && replay.sessionEntryCount === 0) {
|
|
2027
|
-
throw new
|
|
1379
|
+
throw new CliError(`No run.jsonl lifecycle log found for ${runId} (looked at ${replay.logPath}).`, 2, { hint: "Run `rig run list` to confirm the run id; older runs may predate consolidated run.jsonl logging." });
|
|
2028
1380
|
}
|
|
2029
1381
|
if (context.outputMode === "text") {
|
|
2030
1382
|
console.log(`Replay of ${runId} (${replay.entryCount} lifecycle entries${withSession.value ? `, ${replay.sessionEntryCount} session entries` : ""}):`);
|
|
@@ -2066,7 +1418,7 @@ async function executeRun(context, args) {
|
|
|
2066
1418
|
requireNoExtraArgs(extra, "rig run attach <run-id>|--run <run-id> [--message <text>] [--once|--follow] [--poll-ms <ms>]");
|
|
2067
1419
|
const runId = runOption.value ?? positionalRunId;
|
|
2068
1420
|
if (!runId) {
|
|
2069
|
-
throw new
|
|
1421
|
+
throw new CliError("run attach requires a run id.", 2, { hint: "Run `rig run list` to find run ids, then `rig run attach <run-id> --follow`." });
|
|
2070
1422
|
}
|
|
2071
1423
|
let steered = false;
|
|
2072
1424
|
if (messageOption.value?.trim()) {
|
|
@@ -2095,6 +1447,7 @@ async function executeRun(context, args) {
|
|
|
2095
1447
|
const recentRuns = Array.isArray(summary.recentRuns) ? summary.recentRuns.filter((run) => Boolean(run && typeof run === "object" && !Array.isArray(run))) : [];
|
|
2096
1448
|
if (context.outputMode === "text") {
|
|
2097
1449
|
printFormattedOutput(formatRunStatus({ activeRuns, recentRuns, runs: Array.isArray(summary.runs) ? summary.runs : [...activeRuns, ...recentRuns] }, { source: isRemoteConnectionSelected(context.projectRoot) ? "server" : "local" }));
|
|
1450
|
+
await printPendingInboxFooter(context);
|
|
2098
1451
|
}
|
|
2099
1452
|
return { ok: true, group: "run", command, details: summary };
|
|
2100
1453
|
}
|
|
@@ -2120,10 +1473,10 @@ async function executeRun(context, args) {
|
|
|
2120
1473
|
pending = noServerResult.rest;
|
|
2121
1474
|
requireNoExtraArgs(pending, "rig run start [--epic <id>] [--prompt-epic|--no-epic-prompt] [--ws-port <n>] [--server-host <host>] [--server-port <n>] [--poll-ms <n>] [--no-server]");
|
|
2122
1475
|
if (promptEpicResult.value && noEpicPromptResult.value) {
|
|
2123
|
-
throw new
|
|
1476
|
+
throw new CliError("Cannot use --prompt-epic and --no-epic-prompt together.", 1, { hint: "Pass only one of --prompt-epic or --no-epic-prompt." });
|
|
2124
1477
|
}
|
|
2125
1478
|
if (promptEpicResult.value && (context.outputMode !== "text" || !process.stdin.isTTY || !process.stdout.isTTY)) {
|
|
2126
|
-
throw new
|
|
1479
|
+
throw new CliError("--prompt-epic requires an interactive terminal (TTY) in text mode.", 1, { hint: "Pass the epic explicitly instead: `rig run start --epic <id>`." });
|
|
2127
1480
|
}
|
|
2128
1481
|
let resolvedEpicId = epicResult.value || undefined;
|
|
2129
1482
|
if (!resolvedEpicId && shouldPromptForEpicSelection(context, command, promptEpicResult.value, noEpicPromptResult.value)) {
|
|
@@ -2151,7 +1504,7 @@ async function executeRun(context, args) {
|
|
|
2151
1504
|
console.log(`Runs: ${result.runIds.join(", ")}`);
|
|
2152
1505
|
}
|
|
2153
1506
|
if (result.exitCode !== 0) {
|
|
2154
|
-
throw new
|
|
1507
|
+
throw new CliError(`run ${command} failed with exit code ${result.exitCode}.`, result.exitCode, { hint: "Inspect with `rig run status` and `rig inspect logs --task <id>`." });
|
|
2155
1508
|
}
|
|
2156
1509
|
return {
|
|
2157
1510
|
ok: true,
|
|
@@ -2203,10 +1556,10 @@ async function executeRun(context, args) {
|
|
|
2203
1556
|
const runId = runOption.value ?? positionalRunId;
|
|
2204
1557
|
const message = messageOption.value ?? shortMessageOption.value;
|
|
2205
1558
|
if (!runId) {
|
|
2206
|
-
throw new
|
|
1559
|
+
throw new CliError("run steer requires a run id (positional or --run <id>).", 2, { hint: "Run `rig run list` to find run ids, then `rig run steer <run-id> --message <text>`." });
|
|
2207
1560
|
}
|
|
2208
1561
|
if (!message?.trim()) {
|
|
2209
|
-
throw new
|
|
1562
|
+
throw new CliError("run steer requires --message <text>.", 2, { hint: 'Re-run as `rig run steer <run-id> --message "your steering note"`.' });
|
|
2210
1563
|
}
|
|
2211
1564
|
if (context.dryRun) {
|
|
2212
1565
|
if (context.outputMode === "text") {
|
|
@@ -2242,7 +1595,7 @@ async function executeRun(context, args) {
|
|
|
2242
1595
|
}
|
|
2243
1596
|
const result = await runStop(context.projectRoot);
|
|
2244
1597
|
if (result.remaining.length > 0) {
|
|
2245
|
-
throw new
|
|
1598
|
+
throw new CliError(`Failed to stop run(s): ${result.remaining.join(", ")}`, 1, { hint: "Check `rig run status`, then retry `rig run stop <run-id>` for the remaining runs." });
|
|
2246
1599
|
}
|
|
2247
1600
|
if (context.outputMode === "text") {
|
|
2248
1601
|
console.log(`Stopped process count: ${result.stopped}`);
|
|
@@ -2258,7 +1611,7 @@ async function executeRun(context, args) {
|
|
|
2258
1611
|
};
|
|
2259
1612
|
}
|
|
2260
1613
|
default:
|
|
2261
|
-
throw new
|
|
1614
|
+
throw new CliError(`Unknown run command: ${command}`, 1, { hint: "Run `rig run --help` to list available run commands." });
|
|
2262
1615
|
}
|
|
2263
1616
|
}
|
|
2264
1617
|
export {
|