@h-rig/cli 0.0.6-alpha.64 → 0.0.6-alpha.66
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
|
@@ -7,10 +7,19 @@ import { cancel as cancel2, confirm, isCancel as isCancel2 } from "@clack/prompt
|
|
|
7
7
|
|
|
8
8
|
// packages/cli/src/runner.ts
|
|
9
9
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
10
|
-
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
10
|
+
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
11
11
|
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
12
12
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
class CliError extends RuntimeCliError {
|
|
15
|
+
hint;
|
|
16
|
+
constructor(message, exitCode = 1, options = {}) {
|
|
17
|
+
super(message, exitCode);
|
|
18
|
+
if (options.hint?.trim()) {
|
|
19
|
+
this.hint = options.hint.trim();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
14
23
|
function takeFlag(args, flag) {
|
|
15
24
|
const rest = [];
|
|
16
25
|
let value = false;
|
|
@@ -31,7 +40,7 @@ function takeOption(args, option) {
|
|
|
31
40
|
if (current === option) {
|
|
32
41
|
const next = args[index + 1];
|
|
33
42
|
if (!next || next.startsWith("-")) {
|
|
34
|
-
throw new CliError(`Missing value for ${option}`);
|
|
43
|
+
throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
|
|
35
44
|
}
|
|
36
45
|
value = next;
|
|
37
46
|
index += 1;
|
|
@@ -124,7 +133,7 @@ function readJsonFile(path) {
|
|
|
124
133
|
try {
|
|
125
134
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
126
135
|
} catch (error) {
|
|
127
|
-
throw new
|
|
136
|
+
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>`." });
|
|
128
137
|
}
|
|
129
138
|
}
|
|
130
139
|
function writeJsonFile(path, value) {
|
|
@@ -188,7 +197,7 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
188
197
|
const global = readGlobalConnections(options);
|
|
189
198
|
const connection = global.connections[repo.selected];
|
|
190
199
|
if (!connection) {
|
|
191
|
-
throw new
|
|
200
|
+
throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
192
201
|
}
|
|
193
202
|
return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
|
|
194
203
|
}
|
|
@@ -264,7 +273,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
264
273
|
};
|
|
265
274
|
} catch (error) {
|
|
266
275
|
if (error instanceof Error) {
|
|
267
|
-
throw new
|
|
276
|
+
throw new CliError(error.message, 1);
|
|
268
277
|
}
|
|
269
278
|
throw error;
|
|
270
279
|
}
|
|
@@ -324,15 +333,64 @@ function diagnosticMessage(payload) {
|
|
|
324
333
|
});
|
|
325
334
|
return messages.length > 0 ? messages.join("; ") : null;
|
|
326
335
|
}
|
|
336
|
+
var serverReachabilityCache = new Map;
|
|
337
|
+
async function probeServerReachability(baseUrl, authToken) {
|
|
338
|
+
try {
|
|
339
|
+
const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
|
|
340
|
+
headers: mergeHeaders(undefined, authToken),
|
|
341
|
+
signal: AbortSignal.timeout(1500)
|
|
342
|
+
});
|
|
343
|
+
return response.ok;
|
|
344
|
+
} catch {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function cachedServerReachability(projectRoot, baseUrl, authToken) {
|
|
349
|
+
const key = resolve2(projectRoot);
|
|
350
|
+
const cached = serverReachabilityCache.get(key);
|
|
351
|
+
if (cached)
|
|
352
|
+
return cached;
|
|
353
|
+
const probe = probeServerReachability(baseUrl, authToken);
|
|
354
|
+
serverReachabilityCache.set(key, probe);
|
|
355
|
+
return probe;
|
|
356
|
+
}
|
|
357
|
+
function describeSelectedServer(projectRoot, server) {
|
|
358
|
+
try {
|
|
359
|
+
const selected = resolveSelectedConnection(projectRoot);
|
|
360
|
+
if (selected) {
|
|
361
|
+
return {
|
|
362
|
+
alias: selected.alias,
|
|
363
|
+
target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
} catch {}
|
|
367
|
+
return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
|
|
368
|
+
}
|
|
369
|
+
async function buildServerFailureContext(projectRoot, server) {
|
|
370
|
+
const { alias, target } = describeSelectedServer(projectRoot, server);
|
|
371
|
+
const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
|
|
372
|
+
const reachability = reachable ? "server is reachable" : "server is unreachable";
|
|
373
|
+
return {
|
|
374
|
+
contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
|
|
375
|
+
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
376
|
+
};
|
|
377
|
+
}
|
|
327
378
|
async function requestServerJson(context, pathname, init = {}) {
|
|
328
379
|
const server = await ensureServerForCli(context.projectRoot);
|
|
329
380
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
330
381
|
if (server.serverProjectRoot)
|
|
331
382
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
383
|
+
let response;
|
|
384
|
+
try {
|
|
385
|
+
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
386
|
+
...init,
|
|
387
|
+
headers
|
|
388
|
+
});
|
|
389
|
+
} catch (error) {
|
|
390
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
391
|
+
throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
|
|
392
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
393
|
+
}
|
|
336
394
|
const text = await response.text();
|
|
337
395
|
const payload = text.trim().length > 0 ? (() => {
|
|
338
396
|
try {
|
|
@@ -344,7 +402,9 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
344
402
|
if (!response.ok) {
|
|
345
403
|
const diagnostics = diagnosticMessage(payload);
|
|
346
404
|
const detail = diagnostics ?? (text || response.statusText);
|
|
347
|
-
|
|
405
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
406
|
+
throw new CliError(`Rig server request failed (${response.status}): ${detail}
|
|
407
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
348
408
|
}
|
|
349
409
|
return payload;
|
|
350
410
|
}
|
|
@@ -353,7 +413,7 @@ async function listWorkspaceTasksViaServer(context, filters = {}) {
|
|
|
353
413
|
appendTaskFilterParams(url, filters);
|
|
354
414
|
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
355
415
|
if (!Array.isArray(payload)) {
|
|
356
|
-
throw new
|
|
416
|
+
throw new CliError("Rig server returned an invalid task list payload.", 1, { hint: "Check the selected server with `rig server status`; mixed CLI/server versions can mismatch \u2014 try `rig doctor`." });
|
|
357
417
|
}
|
|
358
418
|
return payload.flatMap((entry) => entry && typeof entry === "object" && !Array.isArray(entry) ? [entry] : []);
|
|
359
419
|
}
|
|
@@ -369,7 +429,7 @@ async function selectNextWorkspaceTaskViaServer(context, filters = {}) {
|
|
|
369
429
|
appendTaskFilterParams(url, filters);
|
|
370
430
|
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
371
431
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
372
|
-
throw new
|
|
432
|
+
throw new CliError("Rig server returned an invalid next-task payload.", 1, { hint: "Check the selected server with `rig server status`; try `rig task list` to see the raw task set." });
|
|
373
433
|
}
|
|
374
434
|
const record = payload;
|
|
375
435
|
const rawTask = record.task;
|
|
@@ -415,26 +475,6 @@ async function steerRunViaServer(context, runId, message) {
|
|
|
415
475
|
});
|
|
416
476
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
417
477
|
}
|
|
418
|
-
async function getRunPiSessionViaServer(context, runId) {
|
|
419
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi`);
|
|
420
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
421
|
-
}
|
|
422
|
-
async function getRunPiMessagesViaServer(context, runId) {
|
|
423
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/messages`);
|
|
424
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { messages: [] };
|
|
425
|
-
}
|
|
426
|
-
async function getRunPiStatusViaServer(context, runId) {
|
|
427
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/status`);
|
|
428
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
429
|
-
}
|
|
430
|
-
async function getRunPiCommandsViaServer(context, runId) {
|
|
431
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
|
|
432
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
|
|
433
|
-
}
|
|
434
|
-
async function getRunPiCapabilitiesViaServer(context, runId) {
|
|
435
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
|
|
436
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
|
|
437
|
-
}
|
|
438
478
|
async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior) {
|
|
439
479
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
|
|
440
480
|
method: "POST",
|
|
@@ -443,44 +483,6 @@ async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior)
|
|
|
443
483
|
});
|
|
444
484
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
|
|
445
485
|
}
|
|
446
|
-
async function sendRunPiShellViaServer(context, runId, text) {
|
|
447
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/shell`, {
|
|
448
|
-
method: "POST",
|
|
449
|
-
headers: { "content-type": "application/json" },
|
|
450
|
-
body: JSON.stringify({ text })
|
|
451
|
-
});
|
|
452
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
|
|
453
|
-
}
|
|
454
|
-
async function runRunPiCommandViaServer(context, runId, text) {
|
|
455
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands/run`, {
|
|
456
|
-
method: "POST",
|
|
457
|
-
headers: { "content-type": "application/json" },
|
|
458
|
-
body: JSON.stringify({ text })
|
|
459
|
-
});
|
|
460
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { type: "done" };
|
|
461
|
-
}
|
|
462
|
-
async function respondRunPiExtensionUiViaServer(context, runId, requestId, valueOrCancel) {
|
|
463
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/extension-ui/respond`, {
|
|
464
|
-
method: "POST",
|
|
465
|
-
headers: { "content-type": "application/json" },
|
|
466
|
-
body: JSON.stringify({ requestId, ...valueOrCancel })
|
|
467
|
-
});
|
|
468
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
|
|
469
|
-
}
|
|
470
|
-
async function abortRunPiViaServer(context, runId) {
|
|
471
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/abort`, { method: "POST" });
|
|
472
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { aborted: true };
|
|
473
|
-
}
|
|
474
|
-
async function buildRunPiEventsWebSocketUrl(context, runId) {
|
|
475
|
-
const server = await ensureServerForCli(context.projectRoot);
|
|
476
|
-
const url = new URL(`${server.baseUrl.replace(/\/+$/, "")}/api/runs/${encodeURIComponent(runId)}/pi/events`);
|
|
477
|
-
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
478
|
-
if (server.authToken)
|
|
479
|
-
url.searchParams.set("token", server.authToken);
|
|
480
|
-
if (server.serverProjectRoot)
|
|
481
|
-
url.searchParams.set("rigProjectRoot", server.serverProjectRoot);
|
|
482
|
-
return url.toString();
|
|
483
|
-
}
|
|
484
486
|
async function submitTaskRunViaServer(context, input) {
|
|
485
487
|
const isTaskRun = Boolean(input.taskId);
|
|
486
488
|
const endpoint = isTaskRun ? "/api/runs/task" : "/api/runs/adhoc";
|
|
@@ -504,11 +506,11 @@ async function submitTaskRunViaServer(context, input) {
|
|
|
504
506
|
})
|
|
505
507
|
});
|
|
506
508
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
507
|
-
throw new
|
|
509
|
+
throw new CliError("Rig server returned an invalid run submission payload.", 1, { hint: "Check `rig server status` and retry; `rig run list` shows whether the run was created anyway." });
|
|
508
510
|
}
|
|
509
511
|
const runId = payload.runId;
|
|
510
512
|
if (typeof runId !== "string" || runId.trim().length === 0) {
|
|
511
|
-
throw new
|
|
513
|
+
throw new CliError("Rig server returned no runId for the submitted run.", 1, { hint: "Check `rig run list` \u2014 the run may still have been created; otherwise retry the submission." });
|
|
512
514
|
}
|
|
513
515
|
return { runId };
|
|
514
516
|
}
|
|
@@ -678,9 +680,9 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
678
680
|
if (failures.length > 0) {
|
|
679
681
|
const summary = failures.map((check) => `${check.label}${check.detail ? `: ${check.detail}` : ""}`).join("; ");
|
|
680
682
|
if (failures.some((check) => check.id === "duplicate-active-run") && taskId) {
|
|
681
|
-
throw new
|
|
683
|
+
throw new CliError(`Task ${taskId} already has an active Rig run. ${summary}`, 1, { hint: `Attach to it with \`rig run attach <run-id> --follow\`, or stop it first with \`rig run stop <run-id>\`.` });
|
|
682
684
|
}
|
|
683
|
-
throw new
|
|
685
|
+
throw new CliError(`Task run preflight failed: ${summary}`, 1, { hint: "Run `rig doctor` to diagnose, then retry `rig task run`." });
|
|
684
686
|
}
|
|
685
687
|
return { ok: true, checks };
|
|
686
688
|
}
|
|
@@ -697,7 +699,7 @@ async function runProjectMainSyncPreflight(context, options) {
|
|
|
697
699
|
runBootstrap: async () => {
|
|
698
700
|
const bootstrap = await context.runCommand(["bun", "run", "bootstrap"]);
|
|
699
701
|
if (bootstrap.exitCode !== 0) {
|
|
700
|
-
throw new
|
|
702
|
+
throw new CliError(bootstrap.stderr || bootstrap.stdout || "bun run bootstrap failed during project pre-run sync", bootstrap.exitCode || 1);
|
|
701
703
|
}
|
|
702
704
|
}
|
|
703
705
|
});
|
|
@@ -1027,684 +1029,8 @@ async function selectTaskWithTextPicker(tasks, io = {}) {
|
|
|
1027
1029
|
import { mkdtempSync, rmSync } from "fs";
|
|
1028
1030
|
import { tmpdir } from "os";
|
|
1029
1031
|
import { join } from "path";
|
|
1030
|
-
import {
|
|
1031
|
-
|
|
1032
|
-
createAgentSessionServices,
|
|
1033
|
-
main as runPiMain
|
|
1034
|
-
} from "@earendil-works/pi-coding-agent";
|
|
1035
|
-
|
|
1036
|
-
// packages/cli/src/commands/_pi-remote-session.ts
|
|
1037
|
-
import { AgentSession as PiAgentSession, expandPromptTemplate } from "@earendil-works/pi-coding-agent";
|
|
1038
|
-
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
1039
|
-
function defaultTransport() {
|
|
1040
|
-
return {
|
|
1041
|
-
getSession: getRunPiSessionViaServer,
|
|
1042
|
-
getMessages: getRunPiMessagesViaServer,
|
|
1043
|
-
getStatus: getRunPiStatusViaServer,
|
|
1044
|
-
getCommands: getRunPiCommandsViaServer,
|
|
1045
|
-
getCapabilities: getRunPiCapabilitiesViaServer,
|
|
1046
|
-
sendPrompt: sendRunPiPromptViaServer,
|
|
1047
|
-
sendShell: sendRunPiShellViaServer,
|
|
1048
|
-
runCommand: runRunPiCommandViaServer,
|
|
1049
|
-
abort: abortRunPiViaServer,
|
|
1050
|
-
buildEventsWebSocketUrl: buildRunPiEventsWebSocketUrl
|
|
1051
|
-
};
|
|
1052
|
-
}
|
|
1053
|
-
function recordOf(value) {
|
|
1054
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1055
|
-
}
|
|
1056
|
-
function emptyRemoteStatus() {
|
|
1057
|
-
return {
|
|
1058
|
-
isStreaming: false,
|
|
1059
|
-
isCompacting: false,
|
|
1060
|
-
isBashRunning: false,
|
|
1061
|
-
pendingMessageCount: 0,
|
|
1062
|
-
steeringMessages: [],
|
|
1063
|
-
followUpMessages: [],
|
|
1064
|
-
model: null,
|
|
1065
|
-
thinkingLevel: null,
|
|
1066
|
-
sessionName: null,
|
|
1067
|
-
cwd: null,
|
|
1068
|
-
stats: null,
|
|
1069
|
-
contextUsage: null
|
|
1070
|
-
};
|
|
1071
|
-
}
|
|
1072
|
-
function resolveAttachReadyTimeoutMs() {
|
|
1073
|
-
const raw = Number.parseInt(process.env.RIG_PI_ATTACH_TIMEOUT_MS ?? "", 10);
|
|
1074
|
-
return Number.isFinite(raw) && raw > 0 ? raw : 10 * 60000;
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
class RigRemoteSessionController {
|
|
1078
|
-
context;
|
|
1079
|
-
runId;
|
|
1080
|
-
status = emptyRemoteStatus();
|
|
1081
|
-
transport;
|
|
1082
|
-
hooks = {};
|
|
1083
|
-
session = null;
|
|
1084
|
-
socket = null;
|
|
1085
|
-
closed = false;
|
|
1086
|
-
pendingShells = [];
|
|
1087
|
-
pendingCompactions = [];
|
|
1088
|
-
constructor(input) {
|
|
1089
|
-
this.context = input.context;
|
|
1090
|
-
this.runId = input.runId;
|
|
1091
|
-
this.transport = input.transport ?? defaultTransport();
|
|
1092
|
-
}
|
|
1093
|
-
ingestEnvelope(envelopeValue) {
|
|
1094
|
-
this.applyEnvelope(envelopeValue);
|
|
1095
|
-
}
|
|
1096
|
-
bindSession(session) {
|
|
1097
|
-
this.session = session;
|
|
1098
|
-
}
|
|
1099
|
-
setUiHooks(hooks) {
|
|
1100
|
-
this.hooks = hooks;
|
|
1101
|
-
}
|
|
1102
|
-
async connect() {
|
|
1103
|
-
const ready = await this.waitForReady();
|
|
1104
|
-
if (!ready || this.closed)
|
|
1105
|
-
return;
|
|
1106
|
-
let catchupDone = false;
|
|
1107
|
-
const buffered = [];
|
|
1108
|
-
const wsUrl = await this.transport.buildEventsWebSocketUrl(this.context, this.runId);
|
|
1109
|
-
const socket = new WebSocket(wsUrl);
|
|
1110
|
-
this.socket = socket;
|
|
1111
|
-
socket.onopen = () => {
|
|
1112
|
-
this.hooks.onConnectionChange?.(true);
|
|
1113
|
-
this.hooks.onStatusText?.("worker session live");
|
|
1114
|
-
};
|
|
1115
|
-
socket.onmessage = (message2) => {
|
|
1116
|
-
try {
|
|
1117
|
-
const payload = typeof message2.data === "string" ? JSON.parse(message2.data) : JSON.parse(Buffer.from(message2.data).toString("utf8"));
|
|
1118
|
-
if (!catchupDone)
|
|
1119
|
-
buffered.push(payload);
|
|
1120
|
-
else
|
|
1121
|
-
this.applyEnvelope(payload);
|
|
1122
|
-
} catch (error) {
|
|
1123
|
-
this.hooks.onError?.(`Unparseable worker Pi event: ${error instanceof Error ? error.message : String(error)}`);
|
|
1124
|
-
}
|
|
1125
|
-
};
|
|
1126
|
-
socket.onerror = () => socket.close();
|
|
1127
|
-
socket.onclose = () => {
|
|
1128
|
-
this.hooks.onConnectionChange?.(false);
|
|
1129
|
-
if (!this.closed)
|
|
1130
|
-
this.hooks.onStatusText?.("worker Pi WebSocket disconnected");
|
|
1131
|
-
};
|
|
1132
|
-
try {
|
|
1133
|
-
const [messagesPayload, statusPayload, commandsPayload, capabilitiesPayload] = await Promise.all([
|
|
1134
|
-
this.transport.getMessages(this.context, this.runId),
|
|
1135
|
-
this.transport.getStatus(this.context, this.runId),
|
|
1136
|
-
this.transport.getCommands(this.context, this.runId),
|
|
1137
|
-
this.transport.getCapabilities(this.context, this.runId).catch(() => ({ capabilities: null }))
|
|
1138
|
-
]);
|
|
1139
|
-
const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
|
|
1140
|
-
this.applyStatusPayload(statusPayload);
|
|
1141
|
-
this.session?.replaceRemoteMessages(messages);
|
|
1142
|
-
const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
|
|
1143
|
-
const capabilities = capabilitiesPayload.capabilities && typeof capabilitiesPayload.capabilities === "object" && !Array.isArray(capabilitiesPayload.capabilities) ? capabilitiesPayload.capabilities : null;
|
|
1144
|
-
this.hooks.onCatchUp?.(messages, commands, capabilities);
|
|
1145
|
-
catchupDone = true;
|
|
1146
|
-
for (const payload of buffered.splice(0))
|
|
1147
|
-
this.applyEnvelope(payload);
|
|
1148
|
-
} catch (error) {
|
|
1149
|
-
catchupDone = true;
|
|
1150
|
-
this.hooks.onError?.(`Worker Pi catch-up failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
close() {
|
|
1154
|
-
this.closed = true;
|
|
1155
|
-
this.socket?.close();
|
|
1156
|
-
for (const shell of this.pendingShells.splice(0))
|
|
1157
|
-
shell.reject(new Error("Remote session closed."));
|
|
1158
|
-
for (const compaction of this.pendingCompactions.splice(0))
|
|
1159
|
-
compaction.reject(new Error("Remote session closed."));
|
|
1160
|
-
}
|
|
1161
|
-
async sendPrompt(text, streamingBehavior) {
|
|
1162
|
-
await this.transport.sendPrompt(this.context, this.runId, text, streamingBehavior);
|
|
1163
|
-
}
|
|
1164
|
-
async sendCommand(text) {
|
|
1165
|
-
const result = await this.transport.runCommand(this.context, this.runId, text);
|
|
1166
|
-
return typeof result.message === "string" ? result.message : "worker command accepted";
|
|
1167
|
-
}
|
|
1168
|
-
async sendShell(text) {
|
|
1169
|
-
await this.transport.sendShell(this.context, this.runId, text);
|
|
1170
|
-
}
|
|
1171
|
-
async abort() {
|
|
1172
|
-
await this.transport.abort(this.context, this.runId);
|
|
1173
|
-
}
|
|
1174
|
-
registerPendingShell(shell) {
|
|
1175
|
-
this.pendingShells.push(shell);
|
|
1176
|
-
}
|
|
1177
|
-
failPendingShell(shell, error) {
|
|
1178
|
-
const index = this.pendingShells.indexOf(shell);
|
|
1179
|
-
if (index !== -1)
|
|
1180
|
-
this.pendingShells.splice(index, 1);
|
|
1181
|
-
shell.reject(error);
|
|
1182
|
-
}
|
|
1183
|
-
registerPendingCompaction(pending) {
|
|
1184
|
-
this.pendingCompactions.push(pending);
|
|
1185
|
-
}
|
|
1186
|
-
async waitForReady() {
|
|
1187
|
-
const startedAt = Date.now();
|
|
1188
|
-
const deadline = startedAt + resolveAttachReadyTimeoutMs();
|
|
1189
|
-
let consecutiveFailures = 0;
|
|
1190
|
-
while (!this.closed) {
|
|
1191
|
-
let requestFailed = false;
|
|
1192
|
-
const session = await this.transport.getSession(this.context, this.runId).catch((error) => {
|
|
1193
|
-
requestFailed = true;
|
|
1194
|
-
return { ready: false, status: error instanceof Error ? error.message : String(error), retryAfterMs: 1000 };
|
|
1195
|
-
});
|
|
1196
|
-
if (session.ready !== false)
|
|
1197
|
-
return true;
|
|
1198
|
-
consecutiveFailures = requestFailed ? consecutiveFailures + 1 : 0;
|
|
1199
|
-
const status = String(session.status ?? "starting");
|
|
1200
|
-
if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
|
|
1201
|
-
this.hooks.onError?.(`Run ended before the worker Pi daemon became ready: ${status}. Inspect with \`rig run show ${this.runId}\`.`);
|
|
1202
|
-
return false;
|
|
1203
|
-
}
|
|
1204
|
-
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`);
|
|
1205
|
-
if (Date.now() >= deadline) {
|
|
1206
|
-
this.hooks.onError?.(`Worker Pi daemon did not become ready (last status: ${status}). Set RIG_PI_ATTACH_TIMEOUT_MS to wait longer.`);
|
|
1207
|
-
return false;
|
|
1208
|
-
}
|
|
1209
|
-
await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
|
|
1210
|
-
}
|
|
1211
|
-
return false;
|
|
1212
|
-
}
|
|
1213
|
-
applyEnvelope(envelopeValue) {
|
|
1214
|
-
const envelope = recordOf(envelopeValue);
|
|
1215
|
-
if (!envelope)
|
|
1216
|
-
return;
|
|
1217
|
-
const type = String(envelope.type ?? "");
|
|
1218
|
-
if (type === "status.update") {
|
|
1219
|
-
this.applyStatusPayload(envelope);
|
|
1220
|
-
return;
|
|
1221
|
-
}
|
|
1222
|
-
if (type === "activity.update") {
|
|
1223
|
-
const activity = recordOf(envelope.activity);
|
|
1224
|
-
this.hooks.onActivity?.(String(activity?.label ?? ""), activity?.detail ? String(activity.detail) : undefined);
|
|
1225
|
-
return;
|
|
1226
|
-
}
|
|
1227
|
-
if (type === "extension_ui_request") {
|
|
1228
|
-
const request = recordOf(envelope.request);
|
|
1229
|
-
if (request)
|
|
1230
|
-
this.hooks.onExtensionUiRequest?.(request);
|
|
1231
|
-
return;
|
|
1232
|
-
}
|
|
1233
|
-
if (type === "pi.ui_event") {
|
|
1234
|
-
this.applyShellUiEvent(envelope.event);
|
|
1235
|
-
return;
|
|
1236
|
-
}
|
|
1237
|
-
if (type === "pi.event") {
|
|
1238
|
-
this.session?.handleRemoteSessionEvent(envelope.event);
|
|
1239
|
-
this.settlePendingCompaction(envelope.event);
|
|
1240
|
-
return;
|
|
1241
|
-
}
|
|
1242
|
-
if (type === "error") {
|
|
1243
|
-
this.hooks.onError?.(String(envelope.message ?? envelope.detail ?? "unknown worker error"));
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
applyStatusPayload(payload) {
|
|
1247
|
-
const status = recordOf(payload.status) ?? payload;
|
|
1248
|
-
const next = this.status;
|
|
1249
|
-
next.isStreaming = status.isStreaming === true;
|
|
1250
|
-
next.isCompacting = status.isCompacting === true;
|
|
1251
|
-
next.isBashRunning = status.isBashRunning === true;
|
|
1252
|
-
next.pendingMessageCount = typeof status.pendingMessageCount === "number" ? status.pendingMessageCount : 0;
|
|
1253
|
-
next.steeringMessages = Array.isArray(status.steeringMessages) ? status.steeringMessages.map(String) : [];
|
|
1254
|
-
next.followUpMessages = Array.isArray(status.followUpMessages) ? status.followUpMessages.map(String) : [];
|
|
1255
|
-
next.model = recordOf(status.model) ?? next.model;
|
|
1256
|
-
next.thinkingLevel = typeof status.thinkingLevel === "string" ? status.thinkingLevel : next.thinkingLevel;
|
|
1257
|
-
next.sessionName = typeof status.sessionName === "string" ? status.sessionName : next.sessionName;
|
|
1258
|
-
next.cwd = typeof status.cwd === "string" ? status.cwd : next.cwd;
|
|
1259
|
-
next.stats = recordOf(status.stats) ?? next.stats;
|
|
1260
|
-
next.contextUsage = recordOf(status.contextUsage) ?? next.contextUsage;
|
|
1261
|
-
}
|
|
1262
|
-
applyShellUiEvent(value) {
|
|
1263
|
-
const event = recordOf(value);
|
|
1264
|
-
if (!event)
|
|
1265
|
-
return;
|
|
1266
|
-
const type = String(event.type ?? "");
|
|
1267
|
-
const pending = this.pendingShells[0];
|
|
1268
|
-
if (type === "shell.chunk" && pending) {
|
|
1269
|
-
pending.sawChunk = true;
|
|
1270
|
-
pending.onData(Buffer.from(String(event.chunk ?? "")));
|
|
1271
|
-
return;
|
|
1272
|
-
}
|
|
1273
|
-
if (type === "shell.end" && pending) {
|
|
1274
|
-
const output = String(event.output ?? "");
|
|
1275
|
-
if (output && !pending.sawChunk)
|
|
1276
|
-
pending.onData(Buffer.from(output));
|
|
1277
|
-
const index = this.pendingShells.indexOf(pending);
|
|
1278
|
-
if (index !== -1)
|
|
1279
|
-
this.pendingShells.splice(index, 1);
|
|
1280
|
-
pending.resolve({ exitCode: typeof event.exitCode === "number" ? event.exitCode : event.isError === true ? 1 : 0 });
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
settlePendingCompaction(eventValue) {
|
|
1284
|
-
const event = recordOf(eventValue);
|
|
1285
|
-
if (!event)
|
|
1286
|
-
return;
|
|
1287
|
-
const type = String(event.type ?? "");
|
|
1288
|
-
if (type !== "compaction_end" || this.pendingCompactions.length === 0)
|
|
1289
|
-
return;
|
|
1290
|
-
const pending = this.pendingCompactions.shift();
|
|
1291
|
-
const result = recordOf(event.result);
|
|
1292
|
-
if (result)
|
|
1293
|
-
pending.resolve(result);
|
|
1294
|
-
else if (event.aborted === true)
|
|
1295
|
-
pending.reject(new Error("Compaction aborted on the worker."));
|
|
1296
|
-
else
|
|
1297
|
-
pending.resolve({});
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
class RigRemoteAgentSession extends PiAgentSession {
|
|
1302
|
-
remote;
|
|
1303
|
-
constructor(config, remote) {
|
|
1304
|
-
super(config);
|
|
1305
|
-
this.remote = remote;
|
|
1306
|
-
remote.bindSession(this);
|
|
1307
|
-
}
|
|
1308
|
-
handleRemoteSessionEvent(eventValue) {
|
|
1309
|
-
const event = recordOf(eventValue);
|
|
1310
|
-
if (!event)
|
|
1311
|
-
return;
|
|
1312
|
-
const type = String(event.type ?? "");
|
|
1313
|
-
if ((type === "message_end" || type === "turn_end") && recordOf(event.message)) {
|
|
1314
|
-
this.agent.state.messages = [...this.agent.state.messages, event.message];
|
|
1315
|
-
}
|
|
1316
|
-
this._emit(eventValue);
|
|
1317
|
-
}
|
|
1318
|
-
replaceRemoteMessages(messages) {
|
|
1319
|
-
this.agent.state.messages = messages;
|
|
1320
|
-
}
|
|
1321
|
-
async expandLocalInput(text) {
|
|
1322
|
-
let current = text;
|
|
1323
|
-
const runner = this.extensionRunner;
|
|
1324
|
-
if (runner.hasHandlers("input")) {
|
|
1325
|
-
const result = await runner.emitInput(current, undefined, "interactive");
|
|
1326
|
-
if (result.action === "handled")
|
|
1327
|
-
return null;
|
|
1328
|
-
if (result.action === "transform")
|
|
1329
|
-
current = result.text;
|
|
1330
|
-
}
|
|
1331
|
-
current = this._expandSkillCommand(current);
|
|
1332
|
-
current = expandPromptTemplate(current, [...this.promptTemplates]);
|
|
1333
|
-
return current;
|
|
1334
|
-
}
|
|
1335
|
-
async prompt(text, options) {
|
|
1336
|
-
const trimmed = text.trim();
|
|
1337
|
-
if (!trimmed)
|
|
1338
|
-
return;
|
|
1339
|
-
if (trimmed.startsWith("/")) {
|
|
1340
|
-
if (await this._tryExecuteExtensionCommand(trimmed))
|
|
1341
|
-
return;
|
|
1342
|
-
const expanded2 = await this.expandLocalInput(trimmed);
|
|
1343
|
-
if (expanded2 === null)
|
|
1344
|
-
return;
|
|
1345
|
-
if (expanded2 !== trimmed) {
|
|
1346
|
-
const behavior2 = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
|
|
1347
|
-
options?.preflightResult?.(true);
|
|
1348
|
-
await this.remote.sendPrompt(expanded2, behavior2);
|
|
1349
|
-
return;
|
|
1350
|
-
}
|
|
1351
|
-
await this.remote.sendCommand(trimmed);
|
|
1352
|
-
return;
|
|
1353
|
-
}
|
|
1354
|
-
if (trimmed.startsWith("!")) {
|
|
1355
|
-
await this.remote.sendShell(trimmed);
|
|
1356
|
-
return;
|
|
1357
|
-
}
|
|
1358
|
-
const expanded = await this.expandLocalInput(trimmed);
|
|
1359
|
-
if (expanded === null)
|
|
1360
|
-
return;
|
|
1361
|
-
const behavior = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
|
|
1362
|
-
options?.preflightResult?.(true);
|
|
1363
|
-
await this.remote.sendPrompt(expanded, behavior);
|
|
1364
|
-
}
|
|
1365
|
-
async steer(text) {
|
|
1366
|
-
const trimmed = text.trim();
|
|
1367
|
-
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
1368
|
-
return;
|
|
1369
|
-
const expanded = await this.expandLocalInput(trimmed);
|
|
1370
|
-
if (expanded === null)
|
|
1371
|
-
return;
|
|
1372
|
-
await this.remote.sendPrompt(expanded, "steer");
|
|
1373
|
-
}
|
|
1374
|
-
async followUp(text) {
|
|
1375
|
-
const trimmed = text.trim();
|
|
1376
|
-
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
1377
|
-
return;
|
|
1378
|
-
const expanded = await this.expandLocalInput(trimmed);
|
|
1379
|
-
if (expanded === null)
|
|
1380
|
-
return;
|
|
1381
|
-
await this.remote.sendPrompt(expanded, "followUp");
|
|
1382
|
-
}
|
|
1383
|
-
async abort() {
|
|
1384
|
-
await this.remote.abort();
|
|
1385
|
-
}
|
|
1386
|
-
async compact(customInstructions) {
|
|
1387
|
-
const pending = new Promise((resolve3, reject) => {
|
|
1388
|
-
this.remote.registerPendingCompaction({ resolve: resolve3, reject });
|
|
1389
|
-
});
|
|
1390
|
-
await this.remote.sendCommand(`/compact${customInstructions ? ` ${customInstructions}` : ""}`);
|
|
1391
|
-
return pending;
|
|
1392
|
-
}
|
|
1393
|
-
clearQueue() {
|
|
1394
|
-
const cleared = {
|
|
1395
|
-
steering: [...this.remote.status.steeringMessages],
|
|
1396
|
-
followUp: [...this.remote.status.followUpMessages]
|
|
1397
|
-
};
|
|
1398
|
-
this.remote.status.steeringMessages = [];
|
|
1399
|
-
this.remote.status.followUpMessages = [];
|
|
1400
|
-
this.remote.status.pendingMessageCount = 0;
|
|
1401
|
-
this.remote.sendCommand("/queue-clear").catch(() => {});
|
|
1402
|
-
return cleared;
|
|
1403
|
-
}
|
|
1404
|
-
get isStreaming() {
|
|
1405
|
-
return this.remote.status.isStreaming;
|
|
1406
|
-
}
|
|
1407
|
-
get isCompacting() {
|
|
1408
|
-
return this.remote.status.isCompacting;
|
|
1409
|
-
}
|
|
1410
|
-
get isBashRunning() {
|
|
1411
|
-
return this.remote.status.isBashRunning;
|
|
1412
|
-
}
|
|
1413
|
-
get pendingMessageCount() {
|
|
1414
|
-
return this.remote.status.pendingMessageCount;
|
|
1415
|
-
}
|
|
1416
|
-
getSteeringMessages() {
|
|
1417
|
-
return this.remote.status.steeringMessages;
|
|
1418
|
-
}
|
|
1419
|
-
getFollowUpMessages() {
|
|
1420
|
-
return this.remote.status.followUpMessages;
|
|
1421
|
-
}
|
|
1422
|
-
get model() {
|
|
1423
|
-
return this.remote.status.model ?? super.model;
|
|
1424
|
-
}
|
|
1425
|
-
async setModel(model) {
|
|
1426
|
-
await this.remote.sendCommand(`/model ${model.provider}/${model.id}`);
|
|
1427
|
-
this.remote.status.model = model;
|
|
1428
|
-
}
|
|
1429
|
-
get thinkingLevel() {
|
|
1430
|
-
return this.remote.status.thinkingLevel ?? super.thinkingLevel;
|
|
1431
|
-
}
|
|
1432
|
-
setThinkingLevel(level) {
|
|
1433
|
-
this.remote.status.thinkingLevel = level;
|
|
1434
|
-
this.remote.sendCommand(`/thinking ${level}`).catch(() => {});
|
|
1435
|
-
}
|
|
1436
|
-
get sessionName() {
|
|
1437
|
-
return this.remote.status.sessionName ?? super.sessionName;
|
|
1438
|
-
}
|
|
1439
|
-
setSessionName(name) {
|
|
1440
|
-
this.remote.status.sessionName = name;
|
|
1441
|
-
this.remote.sendCommand(`/name ${name}`).catch(() => {});
|
|
1442
|
-
}
|
|
1443
|
-
getSessionStats() {
|
|
1444
|
-
return this.remote.status.stats ?? super.getSessionStats();
|
|
1445
|
-
}
|
|
1446
|
-
getContextUsage() {
|
|
1447
|
-
return this.remote.status.contextUsage ?? super.getContextUsage();
|
|
1448
|
-
}
|
|
1449
|
-
dispose() {
|
|
1450
|
-
this.remote.close();
|
|
1451
|
-
super.dispose();
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
function createRemoteBashOperations(controller, excludeFromContext) {
|
|
1455
|
-
return {
|
|
1456
|
-
exec(command, _cwd, execOptions) {
|
|
1457
|
-
return new Promise((resolve3, reject) => {
|
|
1458
|
-
const pending = { onData: execOptions.onData, resolve: resolve3, reject, sawChunk: false };
|
|
1459
|
-
const cleanup = () => {
|
|
1460
|
-
execOptions.signal?.removeEventListener("abort", onAbort);
|
|
1461
|
-
if (timer)
|
|
1462
|
-
clearTimeout(timer);
|
|
1463
|
-
};
|
|
1464
|
-
const onAbort = () => {
|
|
1465
|
-
cleanup();
|
|
1466
|
-
controller.failPendingShell(pending, new Error("Remote worker shell command aborted locally."));
|
|
1467
|
-
};
|
|
1468
|
-
const timeoutMs = typeof execOptions.timeout === "number" && execOptions.timeout > 0 ? execOptions.timeout : 0;
|
|
1469
|
-
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
1470
|
-
cleanup();
|
|
1471
|
-
controller.failPendingShell(pending, new Error(`Remote worker shell command timed out after ${timeoutMs}ms.`));
|
|
1472
|
-
}, timeoutMs) : null;
|
|
1473
|
-
const wrappedResolve = pending.resolve;
|
|
1474
|
-
const wrappedReject = pending.reject;
|
|
1475
|
-
pending.resolve = (result) => {
|
|
1476
|
-
cleanup();
|
|
1477
|
-
wrappedResolve(result);
|
|
1478
|
-
};
|
|
1479
|
-
pending.reject = (error) => {
|
|
1480
|
-
cleanup();
|
|
1481
|
-
wrappedReject(error);
|
|
1482
|
-
};
|
|
1483
|
-
execOptions.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1484
|
-
controller.registerPendingShell(pending);
|
|
1485
|
-
controller.sendShell(`${excludeFromContext ? "!!" : "!"}${command}`).catch((error) => {
|
|
1486
|
-
cleanup();
|
|
1487
|
-
controller.failPendingShell(pending, error instanceof Error ? error : new Error(String(error)));
|
|
1488
|
-
});
|
|
1489
|
-
});
|
|
1490
|
-
}
|
|
1491
|
-
};
|
|
1492
|
-
}
|
|
1493
|
-
|
|
1494
|
-
// packages/cli/src/commands/_spinner.ts
|
|
1495
|
-
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1496
|
-
|
|
1497
|
-
// packages/cli/src/commands/_pi-worker-bridge-extension.ts
|
|
1498
|
-
function recordOf2(value) {
|
|
1499
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1500
|
-
}
|
|
1501
|
-
function asText(value) {
|
|
1502
|
-
if (typeof value === "string")
|
|
1503
|
-
return value;
|
|
1504
|
-
if (value === null || value === undefined)
|
|
1505
|
-
return "";
|
|
1506
|
-
if (typeof value === "number" || typeof value === "boolean")
|
|
1507
|
-
return String(value);
|
|
1508
|
-
try {
|
|
1509
|
-
return JSON.stringify(value);
|
|
1510
|
-
} catch {
|
|
1511
|
-
return String(value);
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
function names(value, key = "name") {
|
|
1515
|
-
if (!Array.isArray(value))
|
|
1516
|
-
return [];
|
|
1517
|
-
return value.flatMap((entry) => {
|
|
1518
|
-
if (typeof entry === "string")
|
|
1519
|
-
return [entry];
|
|
1520
|
-
const record = recordOf2(entry);
|
|
1521
|
-
const name = record?.[key];
|
|
1522
|
-
return typeof name === "string" ? [name] : [];
|
|
1523
|
-
});
|
|
1524
|
-
}
|
|
1525
|
-
function renderWorkerCapabilities(capabilities) {
|
|
1526
|
-
const lines = ["Worker session capabilities (in effect for this run)"];
|
|
1527
|
-
const tools = Array.isArray(capabilities.tools) ? capabilities.tools : [];
|
|
1528
|
-
const activeTools = tools.flatMap((tool) => {
|
|
1529
|
-
const record = recordOf2(tool);
|
|
1530
|
-
return record && record.active === true && typeof record.name === "string" ? [record.name] : [];
|
|
1531
|
-
});
|
|
1532
|
-
const inactiveCount = tools.length - activeTools.length;
|
|
1533
|
-
lines.push(` tools ${activeTools.join(", ") || "(none)"}${inactiveCount > 0 ? ` (+${inactiveCount} inactive)` : ""}`);
|
|
1534
|
-
const extensions = Array.isArray(capabilities.extensions) ? capabilities.extensions : [];
|
|
1535
|
-
const extensionLabels = extensions.flatMap((entry) => {
|
|
1536
|
-
const record = recordOf2(entry);
|
|
1537
|
-
if (!record)
|
|
1538
|
-
return [];
|
|
1539
|
-
const path = typeof record.path === "string" ? record.path : "";
|
|
1540
|
-
const short = path.split("/").filter(Boolean).slice(-2).join("/") || path || "extension";
|
|
1541
|
-
return [short];
|
|
1542
|
-
});
|
|
1543
|
-
lines.push(` extensions ${extensionLabels.join(", ") || "(none)"}`);
|
|
1544
|
-
const hookEvents = names(capabilities.hookEvents);
|
|
1545
|
-
const hookList = Array.isArray(capabilities.hookEvents) ? capabilities.hookEvents.map(asText).filter(Boolean) : hookEvents;
|
|
1546
|
-
lines.push(` hooks ${hookList.join(", ") || "(none)"}`);
|
|
1547
|
-
lines.push(` skills ${names(capabilities.skills).join(", ") || "(none)"}`);
|
|
1548
|
-
lines.push(` prompts ${names(capabilities.prompts).join(", ") || "(none)"}`);
|
|
1549
|
-
const model = typeof capabilities.model === "string" ? capabilities.model : "(unknown)";
|
|
1550
|
-
const thinking = typeof capabilities.thinkingLevel === "string" ? capabilities.thinkingLevel : "";
|
|
1551
|
-
const cwd = typeof capabilities.cwd === "string" ? capabilities.cwd : "";
|
|
1552
|
-
lines.push(` model ${model}${thinking ? ` \xB7 ${thinking}` : ""}`);
|
|
1553
|
-
if (cwd)
|
|
1554
|
-
lines.push(` cwd ${cwd}`);
|
|
1555
|
-
lines.push(" (worker commands are in your palette as [worker \u2026] \xB7 /worker shows this again)");
|
|
1556
|
-
return lines;
|
|
1557
|
-
}
|
|
1558
|
-
async function answerExtensionUiRequest(options, ctx, request) {
|
|
1559
|
-
const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
|
|
1560
|
-
const method = String(request.method ?? request.type ?? "input");
|
|
1561
|
-
const prompt = asText(request.prompt ?? request.message ?? request.title ?? method);
|
|
1562
|
-
const rawOptions = Array.isArray(request.options) ? request.options : Array.isArray(request.items) ? request.items : [];
|
|
1563
|
-
const choices = rawOptions.map((option) => {
|
|
1564
|
-
const record = recordOf2(option);
|
|
1565
|
-
return record ? asText(record.label ?? record.value ?? record.name ?? option) : asText(option);
|
|
1566
|
-
}).filter(Boolean);
|
|
1567
|
-
try {
|
|
1568
|
-
if (method === "confirm") {
|
|
1569
|
-
const confirmed = await ctx.ui.confirm("Worker request", prompt);
|
|
1570
|
-
await respondRunPiExtensionUiViaServer(options.context, options.runId, requestId, { value: confirmed, confirmed });
|
|
1571
|
-
return;
|
|
1572
|
-
}
|
|
1573
|
-
if (choices.length > 0) {
|
|
1574
|
-
const selected = await ctx.ui.select(prompt, choices);
|
|
1575
|
-
await respondRunPiExtensionUiViaServer(options.context, options.runId, requestId, selected === undefined ? { cancelled: true } : { value: selected });
|
|
1576
|
-
return;
|
|
1577
|
-
}
|
|
1578
|
-
const value = await ctx.ui.input("Worker request", prompt);
|
|
1579
|
-
await respondRunPiExtensionUiViaServer(options.context, options.runId, requestId, value === undefined ? { cancelled: true } : { value });
|
|
1580
|
-
} catch (error) {
|
|
1581
|
-
ctx.ui.notify(`Failed to answer worker UI request: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "worker", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
|
|
1585
|
-
function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
1586
|
-
for (const command of commands) {
|
|
1587
|
-
const record = recordOf2(command);
|
|
1588
|
-
const name = typeof record?.name === "string" ? record.name : "";
|
|
1589
|
-
const source = typeof record?.source === "string" ? record.source : "worker";
|
|
1590
|
-
if (!name || source === "builtin" || registered.has(name) || LOCALLY_OWNED_COMMANDS.has(name))
|
|
1591
|
-
continue;
|
|
1592
|
-
registered.add(name);
|
|
1593
|
-
const description = typeof record?.description === "string" ? record.description : undefined;
|
|
1594
|
-
try {
|
|
1595
|
-
pi.registerCommand(name, {
|
|
1596
|
-
description: `[worker ${source}] ${description ?? ""}`.trim(),
|
|
1597
|
-
handler: async (args) => {
|
|
1598
|
-
try {
|
|
1599
|
-
const message2 = await options.controller.sendCommand(`/${name}${args ? ` ${args}` : ""}`);
|
|
1600
|
-
ctx.ui.notify(message2, "info");
|
|
1601
|
-
} catch (error) {
|
|
1602
|
-
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
});
|
|
1606
|
-
} catch {}
|
|
1607
|
-
}
|
|
1608
|
-
}
|
|
1609
|
-
function createRigWorkerPiBridgeExtension(options) {
|
|
1610
|
-
return (pi) => {
|
|
1611
|
-
const registeredDaemonCommands = new Set;
|
|
1612
|
-
let capabilityLines = null;
|
|
1613
|
-
let statusText = "connecting to worker session";
|
|
1614
|
-
let busy = true;
|
|
1615
|
-
let connected = false;
|
|
1616
|
-
let frame = 0;
|
|
1617
|
-
let spinnerTimer = null;
|
|
1618
|
-
const renderStatus = (ctx) => {
|
|
1619
|
-
const prefix = busy ? `${SPINNER_FRAMES[frame]} ` : connected ? "\u25CF " : "\u25CB ";
|
|
1620
|
-
ctx.ui.setStatus("rig-worker-pi", `${prefix}${statusText}`);
|
|
1621
|
-
};
|
|
1622
|
-
const setStatus = (ctx, text, isBusy) => {
|
|
1623
|
-
statusText = text;
|
|
1624
|
-
busy = isBusy;
|
|
1625
|
-
renderStatus(ctx);
|
|
1626
|
-
};
|
|
1627
|
-
pi.registerCommand("detach", {
|
|
1628
|
-
description: "Detach from this run; the worker keeps going",
|
|
1629
|
-
handler: async (_args, ctx) => {
|
|
1630
|
-
ctx.ui.notify("Detached locally; the worker Pi daemon continues.", "info");
|
|
1631
|
-
ctx.shutdown();
|
|
1632
|
-
}
|
|
1633
|
-
});
|
|
1634
|
-
pi.registerCommand("stop", {
|
|
1635
|
-
description: "Stop the worker Pi run and detach",
|
|
1636
|
-
handler: async (_args, ctx) => {
|
|
1637
|
-
await options.controller.abort();
|
|
1638
|
-
ctx.ui.notify("Stop requested for the worker Pi daemon.", "info");
|
|
1639
|
-
ctx.shutdown();
|
|
1640
|
-
}
|
|
1641
|
-
});
|
|
1642
|
-
pi.registerCommand("worker", {
|
|
1643
|
-
description: "Show the worker session's real capabilities (tools, extensions, hooks, skills)",
|
|
1644
|
-
handler: async (_args, ctx) => {
|
|
1645
|
-
if (capabilityLines)
|
|
1646
|
-
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
1647
|
-
else
|
|
1648
|
-
ctx.ui.notify("Worker capabilities are not available yet (still connecting).", "info");
|
|
1649
|
-
}
|
|
1650
|
-
});
|
|
1651
|
-
pi.on("user_bash", (event) => ({
|
|
1652
|
-
operations: createRemoteBashOperations(options.controller, event.excludeFromContext === true)
|
|
1653
|
-
}));
|
|
1654
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
1655
|
-
ctx.ui.setTitle("Rig \xB7 enriched bundled Pi");
|
|
1656
|
-
setStatus(ctx, "waiting for worker Pi daemon", true);
|
|
1657
|
-
spinnerTimer = setInterval(() => {
|
|
1658
|
-
frame = (frame + 1) % SPINNER_FRAMES.length;
|
|
1659
|
-
if (busy)
|
|
1660
|
-
renderStatus(ctx);
|
|
1661
|
-
}, 150);
|
|
1662
|
-
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");
|
|
1663
|
-
if (options.initialMessageSent)
|
|
1664
|
-
ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
|
|
1665
|
-
const nativeUi = ctx.ui;
|
|
1666
|
-
options.controller.setUiHooks({
|
|
1667
|
-
onStatusText: (text) => setStatus(ctx, text, !connected),
|
|
1668
|
-
onActivity: (label, detail) => {
|
|
1669
|
-
const active = label !== "idle" && !/complete|ready/i.test(label);
|
|
1670
|
-
setStatus(ctx, [label, detail].filter(Boolean).join(" \u2014 "), active);
|
|
1671
|
-
if (/agent running|tool:/.test(label))
|
|
1672
|
-
ctx.ui.setWidget("rig-worker-capabilities", undefined);
|
|
1673
|
-
},
|
|
1674
|
-
onConnectionChange: (nextConnected) => {
|
|
1675
|
-
connected = nextConnected;
|
|
1676
|
-
setStatus(ctx, nextConnected ? "worker session live" : "worker session disconnected", false);
|
|
1677
|
-
},
|
|
1678
|
-
onError: (message2) => ctx.ui.notify(message2, "error"),
|
|
1679
|
-
onExtensionUiRequest: (request) => {
|
|
1680
|
-
answerExtensionUiRequest(options, ctx, request);
|
|
1681
|
-
},
|
|
1682
|
-
onCatchUp: (messages, commands, capabilities) => {
|
|
1683
|
-
if (nativeUi.appendSessionMessages)
|
|
1684
|
-
nativeUi.appendSessionMessages(messages);
|
|
1685
|
-
const cwd = options.controller.status.cwd;
|
|
1686
|
-
if (nativeUi.setDisplayCwd && cwd)
|
|
1687
|
-
nativeUi.setDisplayCwd(cwd);
|
|
1688
|
-
registerDaemonCommands(pi, options, ctx, commands, registeredDaemonCommands);
|
|
1689
|
-
if (capabilities) {
|
|
1690
|
-
capabilityLines = renderWorkerCapabilities(capabilities);
|
|
1691
|
-
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
});
|
|
1695
|
-
options.controller.connect().catch((error) => {
|
|
1696
|
-
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
1697
|
-
});
|
|
1698
|
-
});
|
|
1699
|
-
pi.on("session_shutdown", () => {
|
|
1700
|
-
if (spinnerTimer)
|
|
1701
|
-
clearInterval(spinnerTimer);
|
|
1702
|
-
options.controller.close();
|
|
1703
|
-
});
|
|
1704
|
-
};
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
|
-
// packages/cli/src/commands/_pi-frontend.ts
|
|
1032
|
+
import { main as runPiMain } from "@earendil-works/pi-coding-agent";
|
|
1033
|
+
import createPiRigExtension from "@rig/pi-rig";
|
|
1708
1034
|
function setTemporaryEnv(updates) {
|
|
1709
1035
|
const previous = new Map;
|
|
1710
1036
|
for (const [key, value] of Object.entries(updates)) {
|
|
@@ -1720,51 +1046,38 @@ function setTemporaryEnv(updates) {
|
|
|
1720
1046
|
}
|
|
1721
1047
|
};
|
|
1722
1048
|
}
|
|
1049
|
+
function buildOperatorPiEnv(input) {
|
|
1050
|
+
return {
|
|
1051
|
+
PI_CODING_AGENT_SESSION_DIR: input.sessionDir,
|
|
1052
|
+
PI_SKIP_VERSION_CHECK: "1",
|
|
1053
|
+
RIG_PI_OPERATOR_SESSION: "1",
|
|
1054
|
+
RIG_RUN_ID: input.runId,
|
|
1055
|
+
RIG_SERVER_URL: input.serverUrl,
|
|
1056
|
+
...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
|
|
1057
|
+
...input.serverProjectRoot ? { RIG_PROJECT_ROOT: input.serverProjectRoot } : {}
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1723
1060
|
async function attachRunBundledPiFrontend(context, input) {
|
|
1724
1061
|
const tempSessionDir = mkdtempSync(join(tmpdir(), "rig-pi-frontend-sessions-"));
|
|
1725
|
-
const
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
resourceLoaderOptions: {
|
|
1736
|
-
extensionFactories: [
|
|
1737
|
-
createRigWorkerPiBridgeExtension({
|
|
1738
|
-
context,
|
|
1739
|
-
controller,
|
|
1740
|
-
runId: input.runId,
|
|
1741
|
-
initialMessageSent: input.steered === true
|
|
1742
|
-
})
|
|
1743
|
-
],
|
|
1744
|
-
noExtensions: true,
|
|
1745
|
-
noSkills: true,
|
|
1746
|
-
noPromptTemplates: true,
|
|
1747
|
-
noContextFiles: true
|
|
1748
|
-
}
|
|
1749
|
-
});
|
|
1750
|
-
const created = await createAgentSessionFromServices({
|
|
1751
|
-
services,
|
|
1752
|
-
sessionManager,
|
|
1753
|
-
sessionStartEvent,
|
|
1754
|
-
noTools: "all",
|
|
1755
|
-
sessionFactory: (config) => new RigRemoteAgentSession(config, controller)
|
|
1756
|
-
});
|
|
1757
|
-
return { ...created, services, diagnostics: services.diagnostics };
|
|
1062
|
+
const server = await ensureServerForCli(context.projectRoot);
|
|
1063
|
+
const restoreEnv = setTemporaryEnv(buildOperatorPiEnv({
|
|
1064
|
+
runId: input.runId,
|
|
1065
|
+
serverUrl: server.baseUrl,
|
|
1066
|
+
authToken: server.authToken,
|
|
1067
|
+
serverProjectRoot: server.serverProjectRoot,
|
|
1068
|
+
sessionDir: tempSessionDir
|
|
1069
|
+
}));
|
|
1070
|
+
const piRigExtensionFactory = (pi) => {
|
|
1071
|
+
createPiRigExtension(pi);
|
|
1758
1072
|
};
|
|
1759
1073
|
let detached = false;
|
|
1760
1074
|
try {
|
|
1761
1075
|
await runPiMain([], {
|
|
1762
|
-
|
|
1076
|
+
extensionFactories: [piRigExtensionFactory]
|
|
1763
1077
|
});
|
|
1764
1078
|
detached = true;
|
|
1765
1079
|
} finally {
|
|
1766
1080
|
restoreEnv();
|
|
1767
|
-
controller.close();
|
|
1768
1081
|
rmSync(tempSessionDir, { recursive: true, force: true });
|
|
1769
1082
|
}
|
|
1770
1083
|
let run = { runId: input.runId, status: "unknown" };
|
|
@@ -1778,12 +1091,12 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
1778
1091
|
timelineCursor: null,
|
|
1779
1092
|
steered: input.steered === true,
|
|
1780
1093
|
detached,
|
|
1781
|
-
rendered: "
|
|
1094
|
+
rendered: "stock Pi operator console with the pi-rig extension"
|
|
1782
1095
|
};
|
|
1783
1096
|
}
|
|
1784
1097
|
|
|
1785
1098
|
// packages/cli/src/commands/_operator-view.ts
|
|
1786
|
-
var
|
|
1099
|
+
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
1787
1100
|
function runStatusFromPayload(payload) {
|
|
1788
1101
|
const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
|
|
1789
1102
|
return String(run.status ?? "unknown").toLowerCase();
|
|
@@ -1860,7 +1173,7 @@ async function attachRunOperatorView(context, input) {
|
|
|
1860
1173
|
}
|
|
1861
1174
|
const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
|
|
1862
1175
|
let timelineCursor = snapshot.timelineCursor;
|
|
1863
|
-
while (!detached && !
|
|
1176
|
+
while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
|
|
1864
1177
|
await Bun.sleep(pollMs);
|
|
1865
1178
|
snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
|
|
1866
1179
|
timelineCursor = snapshot.timelineCursor;
|
|
@@ -2042,55 +1355,91 @@ function formatSubmittedRun(input) {
|
|
|
2042
1355
|
`);
|
|
2043
1356
|
}
|
|
2044
1357
|
|
|
1358
|
+
// packages/cli/src/commands/inbox.ts
|
|
1359
|
+
async function listInboxRecords(context, kind, filters) {
|
|
1360
|
+
const params = new URLSearchParams;
|
|
1361
|
+
if (filters.run)
|
|
1362
|
+
params.set("runId", filters.run);
|
|
1363
|
+
if (filters.task)
|
|
1364
|
+
params.set("taskId", filters.task);
|
|
1365
|
+
const query = params.size > 0 ? `?${params.toString()}` : "";
|
|
1366
|
+
const payload = await requestServerJson(context, `/api/inbox/${kind}${query}`);
|
|
1367
|
+
const records = Array.isArray(payload) ? payload : [];
|
|
1368
|
+
return filters.pendingOnly ? records.filter((entry) => (entry.status ?? "pending") !== "resolved") : records;
|
|
1369
|
+
}
|
|
1370
|
+
async function readPendingInboxCounts(context) {
|
|
1371
|
+
try {
|
|
1372
|
+
const [approvals, inputs] = await Promise.all([
|
|
1373
|
+
listInboxRecords(context, "approvals", { pendingOnly: true }),
|
|
1374
|
+
listInboxRecords(context, "inputs", { pendingOnly: true })
|
|
1375
|
+
]);
|
|
1376
|
+
return { approvals: approvals.length, inputs: inputs.length };
|
|
1377
|
+
} catch {
|
|
1378
|
+
return null;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
async function printPendingInboxFooter(context) {
|
|
1382
|
+
if (context.outputMode !== "text")
|
|
1383
|
+
return;
|
|
1384
|
+
const counts = await readPendingInboxCounts(context);
|
|
1385
|
+
if (!counts || counts.approvals === 0 && counts.inputs === 0)
|
|
1386
|
+
return;
|
|
1387
|
+
const parts = [];
|
|
1388
|
+
if (counts.approvals > 0)
|
|
1389
|
+
parts.push(`${counts.approvals} approval${counts.approvals === 1 ? "" : "s"}`);
|
|
1390
|
+
if (counts.inputs > 0)
|
|
1391
|
+
parts.push(`${counts.inputs} input request${counts.inputs === 1 ? "" : "s"}`);
|
|
1392
|
+
console.log(`
|
|
1393
|
+
\u26A0 ${parts.join(" and ")} pending \u2014 run \`rig inbox\` to review.`);
|
|
1394
|
+
}
|
|
1395
|
+
|
|
2045
1396
|
// packages/cli/src/commands/_help-catalog.ts
|
|
2046
1397
|
import { intro, log as log2, note as note2, outro } from "@clack/prompts";
|
|
2047
1398
|
import pc2 from "picocolors";
|
|
2048
1399
|
var TOP_LEVEL_SECTIONS = [
|
|
2049
1400
|
{
|
|
2050
1401
|
title: "Start here",
|
|
2051
|
-
subtitle: "one-time setup
|
|
1402
|
+
subtitle: "one-time setup, pick a server",
|
|
2052
1403
|
commands: [
|
|
2053
1404
|
{ command: "rig init", description: "Wizard: config, GitHub auth, task source, server, Pi wiring." },
|
|
2054
|
-
{ command: "rig
|
|
2055
|
-
{ command: "rig server
|
|
1405
|
+
{ command: "rig server use <alias|local>", description: "Pick which Rig server owns this repo (`rig server list` to see them)." },
|
|
1406
|
+
{ command: "rig server status", description: "Show the selected server for this repo." }
|
|
2056
1407
|
]
|
|
2057
1408
|
},
|
|
2058
1409
|
{
|
|
2059
1410
|
title: "Work",
|
|
2060
|
-
subtitle: "find a task
|
|
1411
|
+
subtitle: "find a task, put an agent on it, answer what it asks",
|
|
2061
1412
|
commands: [
|
|
2062
1413
|
{ command: "rig task list", description: "What's on the board (from the selected source/server)." },
|
|
2063
|
-
{ command: "rig task next", description: "The next ready task, as a card." },
|
|
2064
1414
|
{ command: "rig task run --next", description: "Dispatch an agent; interactive mode opens the native Pi session." },
|
|
2065
|
-
{ command: "rig
|
|
1415
|
+
{ command: "rig run status", description: "Active and recent runs at a glance." },
|
|
1416
|
+
{ command: "rig run attach <id> --follow", description: "Join the live Pi session (worker keeps running if you /detach)." },
|
|
1417
|
+
{ command: "rig inbox approvals", description: "Approvals workers are waiting on (then `rig inbox approve \u2026`)." }
|
|
2066
1418
|
]
|
|
2067
1419
|
},
|
|
2068
1420
|
{
|
|
2069
|
-
title: "Watch
|
|
2070
|
-
subtitle: "
|
|
1421
|
+
title: "Watch",
|
|
1422
|
+
subtitle: "fleet metrics and per-task forensics",
|
|
2071
1423
|
commands: [
|
|
2072
|
-
{ command: "rig
|
|
2073
|
-
{ command: "rig
|
|
2074
|
-
{ command: "rig
|
|
2075
|
-
{ command: "rig run stop <id>", description: "Cancel a running worker." }
|
|
1424
|
+
{ command: "rig stats [--since 7d]", description: "Fleet metrics: completion/failure rates, median run time, steering, stalls." },
|
|
1425
|
+
{ command: "rig inspect logs --task <id>", description: "Latest run log for a task." },
|
|
1426
|
+
{ command: "rig inspect diff --task <id>", description: "Changed files for a task." }
|
|
2076
1427
|
]
|
|
2077
1428
|
},
|
|
2078
1429
|
{
|
|
2079
|
-
title: "Unblock
|
|
2080
|
-
subtitle: "
|
|
1430
|
+
title: "Unblock",
|
|
1431
|
+
subtitle: "diagnose wiring, fix auth",
|
|
2081
1432
|
commands: [
|
|
2082
|
-
{ command: "rig
|
|
2083
|
-
{ command: "rig
|
|
2084
|
-
{ command: "rig review show|set", description: "The completion review gate (Greptile is mandatory on merges)." }
|
|
1433
|
+
{ command: "rig doctor", description: "Check every part of the wiring \u2014 run this when anything feels off." },
|
|
1434
|
+
{ command: "rig github auth status", description: "GitHub auth state on the selected server." }
|
|
2085
1435
|
]
|
|
2086
1436
|
},
|
|
2087
1437
|
{
|
|
2088
1438
|
title: "Extend",
|
|
2089
|
-
subtitle: "
|
|
1439
|
+
subtitle: "plugins contribute validators, hooks, task sources, commands",
|
|
2090
1440
|
commands: [
|
|
2091
|
-
{ command: "rig
|
|
2092
|
-
{ command: "rig
|
|
2093
|
-
{ command: "rig plugin list", description: "What the rig.config.ts plugins contribute." }
|
|
1441
|
+
{ command: "rig plugin list", description: "What the rig.config.ts plugins contribute." },
|
|
1442
|
+
{ command: "rig plugin run <command-id>", description: "Execute a plugin-contributed CLI command." }
|
|
2094
1443
|
]
|
|
2095
1444
|
}
|
|
2096
1445
|
];
|
|
@@ -2114,22 +1463,20 @@ var PRIMARY_GROUPS = [
|
|
|
2114
1463
|
"rig server use local",
|
|
2115
1464
|
"rig server start --port 3773"
|
|
2116
1465
|
],
|
|
2117
|
-
next: ["Use `rig task list` to see server-owned work.", "Use `rig run list` or `rig run attach <id> --follow` to monitor runs."]
|
|
2118
|
-
advanced: ["Compatibility alias: `rig connect ...` remains callable."]
|
|
1466
|
+
next: ["Use `rig task list` to see server-owned work.", "Use `rig run list` or `rig run attach <id> --follow` to monitor runs."]
|
|
2119
1467
|
},
|
|
2120
1468
|
{
|
|
2121
1469
|
name: "task",
|
|
2122
1470
|
summary: "Find work, start Pi-backed runs, and validate task results.",
|
|
2123
1471
|
usage: ["rig task <list|next|show|run> [options]"],
|
|
2124
1472
|
commands: [
|
|
2125
|
-
{ command: "list [--assignee <login|@me>] [--state open|closed]", description: "List tasks from the selected server/source.", primary: true },
|
|
1473
|
+
{ command: "list [--assignee <login|me|@me>] [--state open|closed]", description: "List tasks from the selected server/source.", primary: true },
|
|
2126
1474
|
{ command: "next [filters]", description: "Render the next matching task as a selected-task card.", primary: true },
|
|
2127
1475
|
{ command: "show <id>|--task <id> [--raw]", description: "Show a human task summary; --raw prints the full payload.", primary: true },
|
|
2128
1476
|
{ command: "run [#<issue>|<task-id>|--next|--task <id>]", description: "Submit a task run; interactive follows with bundled Pi.", primary: true },
|
|
2129
1477
|
{ command: "validate|verify [--task <id>]", description: "Run configured task checks/review gates." },
|
|
2130
1478
|
{ command: "details --task <id>", description: "Show full task info from the configured source." },
|
|
2131
1479
|
{ command: "reopen [--task <id> | --all] [--reason <text>]", description: "Reopen closed task(s) in the configured source." },
|
|
2132
|
-
{ command: "reset --task <id>", description: "Compatibility spelling of `reopen --task <id>`." },
|
|
2133
1480
|
{ command: "artifacts|artifact-dir|artifact-write", description: "Inspect or write task artifacts." },
|
|
2134
1481
|
{ command: "report-bug", description: "Create a structured bug report/task." }
|
|
2135
1482
|
],
|
|
@@ -2187,15 +1534,54 @@ var PRIMARY_GROUPS = [
|
|
|
2187
1534
|
next: ["Rejoin the run after resolving a block: `rig run attach <run-id> --follow`."]
|
|
2188
1535
|
},
|
|
2189
1536
|
{
|
|
2190
|
-
name: "
|
|
2191
|
-
summary: "
|
|
2192
|
-
usage: ["rig
|
|
1537
|
+
name: "stats",
|
|
1538
|
+
summary: "Fleet metrics computed from on-disk run journals (no server required).",
|
|
1539
|
+
usage: ["rig stats [show] [--since <7d|30d|ISO date>]"],
|
|
2193
1540
|
commands: [
|
|
2194
|
-
{ command: "show", description: "
|
|
2195
|
-
{ command: "set <off|advisory|required> [--provider greptile]", description: "Change review strictness/provider.", primary: true }
|
|
1541
|
+
{ command: "show [--since <window>]", description: "Total runs, completion/failure/needs-attention rates, median run time, steering, stalls, approvals.", primary: true }
|
|
2196
1542
|
],
|
|
2197
|
-
examples: [
|
|
2198
|
-
|
|
1543
|
+
examples: [
|
|
1544
|
+
"rig stats",
|
|
1545
|
+
"rig stats --since 7d",
|
|
1546
|
+
"rig stats --since 2026-06-01 --json"
|
|
1547
|
+
],
|
|
1548
|
+
next: ["Inspect outliers with `rig run list` and `rig run show <run-id>`.", "Use `--json` for the schema'd envelope (see docs/cli-json.md)."]
|
|
1549
|
+
},
|
|
1550
|
+
{
|
|
1551
|
+
name: "inspect",
|
|
1552
|
+
summary: "Inspect logs, artifacts, graphs, failures for a task.",
|
|
1553
|
+
usage: ["rig inspect <logs|artifacts|failures|graph|audit|diff> --task <id>"],
|
|
1554
|
+
commands: [
|
|
1555
|
+
{ command: "logs --task <id>", description: "Latest run log for a task (local or selected server).", primary: true },
|
|
1556
|
+
{ command: "artifacts --task <id>", description: "List the task's completion artifacts.", primary: true },
|
|
1557
|
+
{ command: "failures --task <id>", description: "Recorded failures for a task.", primary: true },
|
|
1558
|
+
{ command: "diff --task <id>", description: "Changed files for a task.", primary: true },
|
|
1559
|
+
{ command: "graph", description: "Task dependency graph." },
|
|
1560
|
+
{ command: "audit", description: "Controlled-command audit trail." }
|
|
1561
|
+
],
|
|
1562
|
+
examples: ["rig inspect logs --task <id>", "rig inspect diff --task <id>"],
|
|
1563
|
+
next: ["Use `rig stats` for fleet-level metrics across runs."]
|
|
1564
|
+
},
|
|
1565
|
+
{
|
|
1566
|
+
name: "repo",
|
|
1567
|
+
summary: "Repository sync/baseline helpers for the Rig-managed checkout.",
|
|
1568
|
+
usage: ["rig repo <sync|reset-baseline>"],
|
|
1569
|
+
commands: [
|
|
1570
|
+
{ command: "sync", description: "Sync project repository state.", primary: true },
|
|
1571
|
+
{ command: "reset-baseline", description: "Reset the managed baseline for the repo." }
|
|
1572
|
+
],
|
|
1573
|
+
examples: ["rig repo sync"]
|
|
1574
|
+
},
|
|
1575
|
+
{
|
|
1576
|
+
name: "plugin",
|
|
1577
|
+
summary: "Plugin listing, validation, and plugin-contributed commands.",
|
|
1578
|
+
usage: ["rig plugin <list|validate|run> [options]"],
|
|
1579
|
+
commands: [
|
|
1580
|
+
{ command: "list", description: "List plugins declared in rig.config.ts and their contributions.", primary: true },
|
|
1581
|
+
{ command: "validate --task <id>", description: "Run plugin-contributed validators for a task.", primary: true },
|
|
1582
|
+
{ command: "run <command-id> [args...]", description: "Execute a plugin-contributed CLI command (also callable as `rig <command-id>`)." }
|
|
1583
|
+
],
|
|
1584
|
+
examples: ["rig plugin list", "rig plugin run <command-id>"]
|
|
2199
1585
|
},
|
|
2200
1586
|
{
|
|
2201
1587
|
name: "init",
|
|
@@ -2203,12 +1589,14 @@ var PRIMARY_GROUPS = [
|
|
|
2203
1589
|
usage: ["rig init [--yes] [--server local|remote] [--repo owner/repo] [--remote-url <url>]"],
|
|
2204
1590
|
commands: [
|
|
2205
1591
|
{ command: "init", description: "Interactive setup wizard for a new or existing Rig repo.", primary: true },
|
|
1592
|
+
{ command: "init --demo", description: "Offline demo project: files task source + 3 sample tasks, zero GitHub.", primary: true },
|
|
2206
1593
|
{ command: "init --yes", description: "Non-interactive setup using detected/default settings.", primary: true },
|
|
2207
1594
|
{ command: "init --server remote --remote-url <url>", description: "Link this repo to a remote Rig server.", primary: true },
|
|
2208
1595
|
{ command: "init --repair", description: "Repair missing private state without replacing project config." }
|
|
2209
1596
|
],
|
|
2210
1597
|
examples: [
|
|
2211
1598
|
"rig init",
|
|
1599
|
+
"rig init --demo",
|
|
2212
1600
|
"rig init --yes --repo humanity-org/humanwork",
|
|
2213
1601
|
"rig init --server remote --remote-url https://where.rig-does.work --repo owner/repo"
|
|
2214
1602
|
],
|
|
@@ -2239,23 +1627,19 @@ var PRIMARY_GROUPS = [
|
|
|
2239
1627
|
}
|
|
2240
1628
|
];
|
|
2241
1629
|
var ADVANCED_GROUPS = [
|
|
2242
|
-
{ name: "connect", summary: "Compatibility alias for `rig server` selection commands.", usage: ["rig connect <status|list|add|use>"], commands: [{ command: "status|list|add|use", description: "Use `rig server ...` for the primary UX." }] },
|
|
2243
1630
|
{ name: "setup", summary: "Bootstrap/check local setup.", usage: ["rig setup <bootstrap|check|preflight>"], commands: [{ command: "bootstrap|check|preflight", description: "Setup helpers." }] },
|
|
1631
|
+
{ name: "profile", summary: "Runtime profile/model defaults.", usage: ["rig profile <show|set>"], commands: [{ command: "show", description: "Show active profile." }] },
|
|
2244
1632
|
{
|
|
2245
|
-
name: "
|
|
2246
|
-
summary: "Inspect
|
|
2247
|
-
usage: ["rig
|
|
1633
|
+
name: "review",
|
|
1634
|
+
summary: "Inspect or change completion review gate policy.",
|
|
1635
|
+
usage: ["rig review <show|set>"],
|
|
2248
1636
|
commands: [
|
|
2249
|
-
{ command: "
|
|
2250
|
-
{ command: "
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
{ command: "diff --task <id>", description: "Changed files for a task." }
|
|
2255
|
-
]
|
|
1637
|
+
{ command: "show", description: "Show current review gate settings." },
|
|
1638
|
+
{ command: "set <off|advisory|required> [--provider greptile]", description: "Change review strictness/provider." }
|
|
1639
|
+
],
|
|
1640
|
+
examples: ["rig review show", "rig review set required --provider greptile"],
|
|
1641
|
+
next: ["Use `rig inbox approvals` for blocked run handoffs."]
|
|
2256
1642
|
},
|
|
2257
|
-
{ name: "repo", summary: "Repository sync/baseline helpers.", usage: ["rig repo <sync|reset-baseline>"], commands: [{ command: "sync", description: "Sync project repository state." }] },
|
|
2258
|
-
{ name: "profile", summary: "Runtime profile/model defaults.", usage: ["rig profile <show|set>"], commands: [{ command: "show", description: "Show active profile." }] },
|
|
2259
1643
|
{
|
|
2260
1644
|
name: "browser",
|
|
2261
1645
|
summary: "Browser/app diagnostics for browser-required tasks.",
|
|
@@ -2281,16 +1665,6 @@ var ADVANCED_GROUPS = [
|
|
|
2281
1665
|
examples: ["rig pi search subagents", "rig pi add pi-subagents", "rig pi list"],
|
|
2282
1666
|
next: ["Config-managed extensions: declare `runtime: { pi: { packages: [...] } }` in rig.config.ts \u2014 workers pick them up automatically."]
|
|
2283
1667
|
},
|
|
2284
|
-
{
|
|
2285
|
-
name: "plugin",
|
|
2286
|
-
summary: "Plugin listing, validation, and plugin-contributed commands.",
|
|
2287
|
-
usage: ["rig plugin <list|validate|run> [options]"],
|
|
2288
|
-
commands: [
|
|
2289
|
-
{ command: "list", description: "List plugins declared in rig.config.ts and their contributions." },
|
|
2290
|
-
{ command: "validate --task <id>", description: "Run plugin-contributed validators for a task." },
|
|
2291
|
-
{ command: "run <command-id> [args...]", description: "Execute a plugin-contributed CLI command (also callable as `rig <command-id>`)." }
|
|
2292
|
-
]
|
|
2293
|
-
},
|
|
2294
1668
|
{ name: "queue", summary: "Run task queues locally.", usage: ["rig queue run [options]"], commands: [{ command: "run", description: "Process queue work." }] },
|
|
2295
1669
|
{ name: "agent", summary: "Runtime agent workspace helpers.", usage: ["rig agent <list|prepare|run|cleanup>"], commands: [{ command: "list", description: "List prepared agents." }] },
|
|
2296
1670
|
{ name: "inspector", summary: "Event stream and drift scanners.", usage: ["rig inspector <stream|scan-upstream-drift>"], commands: [{ command: "stream", description: "Stream events." }] },
|
|
@@ -2357,7 +1731,7 @@ function renderGroup(group) {
|
|
|
2357
1731
|
function renderTopLevelHelp() {
|
|
2358
1732
|
return [
|
|
2359
1733
|
`${heading("rig")} ${pc2.dim("\u2014 server-owned task/run control plane for Pi-backed engineering work")}`,
|
|
2360
|
-
pc2.dim("Current path: select a server, choose a task, submit a run, attach with native Pi, clear inbox
|
|
1734
|
+
pc2.dim("Current path: select a server, choose a task, submit a run, attach with native Pi, clear inbox gates."),
|
|
2361
1735
|
"",
|
|
2362
1736
|
...TOP_LEVEL_SECTIONS.flatMap((section) => [
|
|
2363
1737
|
`${pc2.bold(pc2.magenta(`\u25C7 ${section.title}`))} \u2014 ${pc2.dim(section.subtitle)}`,
|
|
@@ -2409,7 +1783,7 @@ function printTopLevelHelp(state = {}) {
|
|
|
2409
1783
|
commandLine("--dry-run", "Print the command plan without mutating state.")
|
|
2410
1784
|
].join(`
|
|
2411
1785
|
`), "Global options");
|
|
2412
|
-
outro("init \u2192 task run \u2192 watch
|
|
1786
|
+
outro("init \u2192 task run \u2192 watch \u2192 inbox \u2192 merged.");
|
|
2413
1787
|
}
|
|
2414
1788
|
function printGroupHelpDocument(groupName) {
|
|
2415
1789
|
const rendered = renderGroupHelp(groupName) ?? renderTopLevelHelp();
|
|
@@ -2450,7 +1824,7 @@ async function readStdin() {
|
|
|
2450
1824
|
}
|
|
2451
1825
|
return Buffer.concat(chunks).toString("utf-8");
|
|
2452
1826
|
}
|
|
2453
|
-
function
|
|
1827
|
+
function normalizeAssigneeAlias(value) {
|
|
2454
1828
|
if (!value)
|
|
2455
1829
|
return;
|
|
2456
1830
|
return value.trim().toLowerCase() === "me" ? "@me" : value;
|
|
@@ -2459,25 +1833,19 @@ function parseTaskFilters(args) {
|
|
|
2459
1833
|
let pending = args;
|
|
2460
1834
|
const assigneeResult = takeOption(pending, "--assignee");
|
|
2461
1835
|
pending = assigneeResult.rest;
|
|
2462
|
-
const assignedToResult = takeOption(pending, "--assigned-to");
|
|
2463
|
-
pending = assignedToResult.rest;
|
|
2464
1836
|
const stateResult = takeOption(pending, "--state");
|
|
2465
1837
|
pending = stateResult.rest;
|
|
2466
1838
|
const statusResult = takeOption(pending, "--status");
|
|
2467
1839
|
pending = statusResult.rest;
|
|
2468
1840
|
const limitResult = takeOption(pending, "--limit");
|
|
2469
1841
|
pending = limitResult.rest;
|
|
2470
|
-
const
|
|
2471
|
-
if (assigneeResult.value && normalizedAssignedTo && assigneeResult.value !== normalizedAssignedTo) {
|
|
2472
|
-
throw new CliError2("--assignee and --assigned-to cannot specify different assignees.", 2);
|
|
2473
|
-
}
|
|
2474
|
-
const assignee = normalizedAssignedTo ?? assigneeResult.value;
|
|
1842
|
+
const assignee = normalizeAssigneeAlias(assigneeResult.value);
|
|
2475
1843
|
const limit = (() => {
|
|
2476
1844
|
if (!limitResult.value)
|
|
2477
1845
|
return;
|
|
2478
1846
|
const parsed = Number.parseInt(limitResult.value, 10);
|
|
2479
1847
|
if (!Number.isFinite(parsed) || parsed < 1) {
|
|
2480
|
-
throw new
|
|
1848
|
+
throw new CliError("--limit must be a positive integer.", 2, { hint: "Re-run with a positive number, e.g. `rig task list --limit 20`." });
|
|
2481
1849
|
}
|
|
2482
1850
|
return parsed;
|
|
2483
1851
|
})();
|
|
@@ -2512,7 +1880,7 @@ function normalizePrMode(value) {
|
|
|
2512
1880
|
return;
|
|
2513
1881
|
if (value === "auto" || value === "ask" || value === "off")
|
|
2514
1882
|
return value;
|
|
2515
|
-
throw new
|
|
1883
|
+
throw new CliError("--pr must be auto, ask, or off.", 2, { hint: "Re-run with `--pr auto|ask|off`, or pass `--no-pr` to disable the PR." });
|
|
2516
1884
|
}
|
|
2517
1885
|
function detectLocalDirtyState(projectRoot) {
|
|
2518
1886
|
const result = spawnSync("git", ["-C", projectRoot, "status", "--porcelain"], { encoding: "utf8", timeout: 5000 });
|
|
@@ -2535,7 +1903,7 @@ function selectedServerKind(projectRoot) {
|
|
|
2535
1903
|
}
|
|
2536
1904
|
async function resolveDirtyBaselineForTaskRun(context, explicit) {
|
|
2537
1905
|
if (explicit && explicit !== "head" && explicit !== "dirty-snapshot") {
|
|
2538
|
-
throw new
|
|
1906
|
+
throw new CliError("--dirty-baseline must be head or dirty-snapshot.", 2, { hint: "Re-run with `--dirty-baseline head` or `--dirty-baseline dirty-snapshot`." });
|
|
2539
1907
|
}
|
|
2540
1908
|
if (selectedServerKind(context.projectRoot) !== "local") {
|
|
2541
1909
|
return { mode: explicit === "dirty-snapshot" ? "dirty-snapshot" : "head", state: null };
|
|
@@ -2555,7 +1923,7 @@ async function resolveDirtyBaselineForTaskRun(context, explicit) {
|
|
|
2555
1923
|
});
|
|
2556
1924
|
if (isCancel2(answer)) {
|
|
2557
1925
|
cancel2("Run cancelled.");
|
|
2558
|
-
throw new
|
|
1926
|
+
throw new CliError("Run cancelled by user.", 1);
|
|
2559
1927
|
}
|
|
2560
1928
|
return { mode: answer ? "dirty-snapshot" : "head", state };
|
|
2561
1929
|
}
|
|
@@ -2610,11 +1978,12 @@ async function executeTask(context, args, options) {
|
|
|
2610
1978
|
const rawResult = takeFlag(pending, "--raw");
|
|
2611
1979
|
pending = rawResult.rest;
|
|
2612
1980
|
const { filters, rest: remaining } = parseTaskFilters(pending);
|
|
2613
|
-
requireNoExtraArgs(remaining, "rig task list [--raw] [--assignee <login
|
|
1981
|
+
requireNoExtraArgs(remaining, "rig task list [--raw] [--assignee <login|me|@me>] [--state open|closed] [--status <status>] [--limit <n>]");
|
|
2614
1982
|
const tasks = await listWorkspaceTasksViaServer(context, filters);
|
|
2615
1983
|
if (context.outputMode === "text") {
|
|
2616
1984
|
const renderedTasks = rawResult.value ? tasks.map((task) => summarizeTask(task, { raw: true })) : tasks.map((task) => summarizeTask(task));
|
|
2617
1985
|
printFormattedOutput(formatTaskList(renderedTasks, { raw: rawResult.value }));
|
|
1986
|
+
await printPendingInboxFooter(context);
|
|
2618
1987
|
}
|
|
2619
1988
|
return {
|
|
2620
1989
|
ok: true,
|
|
@@ -2633,10 +2002,10 @@ async function executeTask(context, args, options) {
|
|
|
2633
2002
|
requireNoExtraArgs(remaining, "rig task show <id>|--task <id> [--raw]");
|
|
2634
2003
|
const taskId3 = normalizeTaskRunTaskId(taskOption.value ?? positional);
|
|
2635
2004
|
if (!taskId3)
|
|
2636
|
-
throw new
|
|
2005
|
+
throw new CliError("task show requires a task id.", 2, { hint: "Run `rig task list` to find ids, then `rig task show <id>`." });
|
|
2637
2006
|
const task = await getWorkspaceTaskViaServer(context, taskId3);
|
|
2638
2007
|
if (!task)
|
|
2639
|
-
throw new
|
|
2008
|
+
throw new CliError(`Task not found: ${taskId3}`, 3, { hint: "Run `rig task list` to see available tasks." });
|
|
2640
2009
|
const summary = summarizeTask(task, { raw: true });
|
|
2641
2010
|
if (context.outputMode === "text") {
|
|
2642
2011
|
printFormattedOutput(rawResult.value ? JSON.stringify(summary, null, 2) : formatTaskDetails(summary));
|
|
@@ -2645,7 +2014,7 @@ async function executeTask(context, args, options) {
|
|
|
2645
2014
|
}
|
|
2646
2015
|
case "next": {
|
|
2647
2016
|
const { filters, rest: remaining } = parseTaskFilters(rest);
|
|
2648
|
-
requireNoExtraArgs(remaining, "rig task next [--assignee <login
|
|
2017
|
+
requireNoExtraArgs(remaining, "rig task next [--assignee <login|me|@me>] [--state open|closed] [--status <status>] [--limit <n>]");
|
|
2649
2018
|
const selected = await selectNextWorkspaceTaskViaServer(context, filters);
|
|
2650
2019
|
if (context.outputMode === "text") {
|
|
2651
2020
|
if (selected.task) {
|
|
@@ -2700,7 +2069,7 @@ async function executeTask(context, args, options) {
|
|
|
2700
2069
|
}
|
|
2701
2070
|
case "artifact-write": {
|
|
2702
2071
|
if (rest.length < 1) {
|
|
2703
|
-
throw new
|
|
2072
|
+
throw new CliError(`Usage: rig task artifact-write <filename> [--file <path>]
|
|
2704
2073
|
` + ` Reads content from stdin (or --file), writes to the active task artifact dir.
|
|
2705
2074
|
` + " Example: echo '...' | rig task artifact-write collection-audit.md");
|
|
2706
2075
|
}
|
|
@@ -2713,7 +2082,7 @@ async function executeTask(context, args, options) {
|
|
|
2713
2082
|
content = await readStdin();
|
|
2714
2083
|
}
|
|
2715
2084
|
if (!artifactFilename) {
|
|
2716
|
-
throw new
|
|
2085
|
+
throw new CliError("Usage: rig task artifact-write <filename> [--file path]");
|
|
2717
2086
|
}
|
|
2718
2087
|
withMutedConsole(context.outputMode === "json", () => taskArtifactWrite(context.projectRoot, artifactFilename, content));
|
|
2719
2088
|
return { ok: true, group: "task", command, details: { filename: artifactFilename } };
|
|
@@ -2722,11 +2091,11 @@ async function executeTask(context, args, options) {
|
|
|
2722
2091
|
return options.executeTaskReportBug(context, rest);
|
|
2723
2092
|
case "lookup": {
|
|
2724
2093
|
if (rest.length !== 1) {
|
|
2725
|
-
throw new
|
|
2094
|
+
throw new CliError("Usage: rig task lookup <task-id>");
|
|
2726
2095
|
}
|
|
2727
2096
|
const lookupId = rest[0];
|
|
2728
2097
|
if (!lookupId) {
|
|
2729
|
-
throw new
|
|
2098
|
+
throw new CliError("Usage: rig task lookup <task-id>");
|
|
2730
2099
|
}
|
|
2731
2100
|
const result = taskLookup(context.projectRoot, lookupId);
|
|
2732
2101
|
if (context.outputMode === "text") {
|
|
@@ -2736,11 +2105,11 @@ async function executeTask(context, args, options) {
|
|
|
2736
2105
|
}
|
|
2737
2106
|
case "record": {
|
|
2738
2107
|
if (rest.length < 2) {
|
|
2739
|
-
throw new
|
|
2108
|
+
throw new CliError("Usage: rig task record <decision|failure> <text>");
|
|
2740
2109
|
}
|
|
2741
2110
|
const type = rest[0];
|
|
2742
2111
|
if (type !== "decision" && type !== "failure") {
|
|
2743
|
-
throw new
|
|
2112
|
+
throw new CliError("Usage: rig task record <decision|failure> <text>");
|
|
2744
2113
|
}
|
|
2745
2114
|
withMutedConsole(context.outputMode === "json", () => taskRecord(context.projectRoot, type, rest.slice(1).join(" ")));
|
|
2746
2115
|
return { ok: true, group: "task", command, details: { type: rest[0] } };
|
|
@@ -2783,15 +2152,15 @@ async function executeTask(context, args, options) {
|
|
|
2783
2152
|
if (positionalTaskId) {
|
|
2784
2153
|
pending = pending.slice(1);
|
|
2785
2154
|
}
|
|
2786
|
-
requireNoExtraArgs(pending, "rig task run [#<issue>|<task-id>] [--next] [--task <id>] [--detach] [--assignee <login
|
|
2155
|
+
requireNoExtraArgs(pending, "rig task run [#<issue>|<task-id>] [--next] [--task <id>] [--detach] [--assignee <login|me|@me>] [--state open|closed] [--status <status>] [--limit <n>] [--title <text>] [--runtime-adapter claude-code|codex|pi] [--model <model>] [--runtime-mode <mode>] [--interaction-mode <mode>] [--initial-prompt <text>] [--pr auto|ask|off] [--no-pr] [--dirty-baseline head|dirty-snapshot] [--skip-project-sync]");
|
|
2787
2156
|
if (nextResult.value && (taskResult.value || positionalTaskId)) {
|
|
2788
|
-
throw new
|
|
2157
|
+
throw new CliError("task run cannot combine --next with an explicit task id.", 2, { hint: "Use either `rig task run --next` or `rig task run <id>`." });
|
|
2789
2158
|
}
|
|
2790
2159
|
if (taskResult.value && positionalTaskId) {
|
|
2791
|
-
throw new
|
|
2160
|
+
throw new CliError("task run cannot combine positional task id with --task <id>.", 2, { hint: "Pass the id once, e.g. `rig task run <id>`." });
|
|
2792
2161
|
}
|
|
2793
2162
|
if (prResult.value && noPrResult.value) {
|
|
2794
|
-
throw new
|
|
2163
|
+
throw new CliError("task run cannot combine --pr with --no-pr.", 2, { hint: "Use `--pr auto|ask|off` or `--no-pr`, not both." });
|
|
2795
2164
|
}
|
|
2796
2165
|
let selectedTask = null;
|
|
2797
2166
|
let selectedTaskId = normalizeTaskRunTaskId(taskResult.value) ?? positionalTaskId;
|
|
@@ -2800,18 +2169,18 @@ async function executeTask(context, args, options) {
|
|
|
2800
2169
|
selectedTask = selected.task;
|
|
2801
2170
|
selectedTaskId = selected.task ? readTaskId(selected.task) : null;
|
|
2802
2171
|
if (!selectedTaskId) {
|
|
2803
|
-
throw new
|
|
2172
|
+
throw new CliError("No matching task found for task run --next.", 3, { hint: "Run `rig task list` to inspect available work, or relax the filters." });
|
|
2804
2173
|
}
|
|
2805
2174
|
}
|
|
2806
2175
|
if (!selectedTaskId && !initialPromptResult.value && !titleResult.value) {
|
|
2807
2176
|
if (context.outputMode !== "text" || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
2808
|
-
throw new
|
|
2177
|
+
throw new CliError("task run requires an interactive terminal to pick a task; pass --next, --task <id>, or --initial-prompt/--title for an ad hoc run.", 2);
|
|
2809
2178
|
}
|
|
2810
2179
|
const tasks = await listWorkspaceTasksViaServer(context, filterResult.filters);
|
|
2811
2180
|
selectedTask = await selectTaskWithTextPicker(tasks);
|
|
2812
2181
|
selectedTaskId = selectedTask ? readTaskId(selectedTask) : null;
|
|
2813
2182
|
if (!selectedTaskId) {
|
|
2814
|
-
throw new
|
|
2183
|
+
throw new CliError("No task selected.", 3, { hint: "Run `rig task run --next` for the next ready task, or `rig task run --task <id>`." });
|
|
2815
2184
|
}
|
|
2816
2185
|
}
|
|
2817
2186
|
await runProjectMainSyncPreflight(context, { disabled: skipProjectSyncResult.value });
|
|
@@ -2885,7 +2254,7 @@ async function executeTask(context, args, options) {
|
|
|
2885
2254
|
}
|
|
2886
2255
|
const ok = await withMutedConsole(context.outputMode === "json", async () => taskValidate(context.projectRoot, task || undefined, await validatorRegistryForTaskCommands(context.projectRoot)));
|
|
2887
2256
|
if (!ok) {
|
|
2888
|
-
throw new
|
|
2257
|
+
throw new CliError(`Validation failed for ${task || "active task"}.`, 2, { hint: "Inspect failures with `rig inspect failures --task <id>`, fix, then re-run `rig task validate --task <id>`." });
|
|
2889
2258
|
}
|
|
2890
2259
|
return { ok: true, group: "task", command, details: { task: task || "active" } };
|
|
2891
2260
|
}
|
|
@@ -2898,21 +2267,10 @@ async function executeTask(context, args, options) {
|
|
|
2898
2267
|
}
|
|
2899
2268
|
const ok = await withMutedConsole(context.outputMode === "json", () => taskVerify(context.projectRoot, task || undefined));
|
|
2900
2269
|
if (!ok) {
|
|
2901
|
-
throw new
|
|
2270
|
+
throw new CliError(`Verification rejected for ${task || "active task"}.`, 2, { hint: "Check `rig inspect logs --task <id>`, address the rejection, then re-run `rig task verify --task <id>`." });
|
|
2902
2271
|
}
|
|
2903
2272
|
return { ok: true, group: "task", command, details: { task: task || "active" } };
|
|
2904
2273
|
}
|
|
2905
|
-
case "reset": {
|
|
2906
|
-
const { value: task, rest: remaining } = takeOption(rest, "--task");
|
|
2907
|
-
requireNoExtraArgs(remaining, "rig task reset --task <task-id>");
|
|
2908
|
-
const requiredTask = requireTask(task, "rig task reset --task <task-id>");
|
|
2909
|
-
const summary = withMutedConsole(context.outputMode === "json", () => taskReopen(context.projectRoot, {
|
|
2910
|
-
all: false,
|
|
2911
|
-
taskId: requiredTask,
|
|
2912
|
-
dryRun: false
|
|
2913
|
-
}));
|
|
2914
|
-
return { ok: true, group: "task", command, details: summary };
|
|
2915
|
-
}
|
|
2916
2274
|
case "details": {
|
|
2917
2275
|
const { value: task, rest: remaining } = takeOption(rest, "--task");
|
|
2918
2276
|
requireNoExtraArgs(remaining, "rig task details --task <task-id>");
|
|
@@ -2926,7 +2284,7 @@ async function executeTask(context, args, options) {
|
|
|
2926
2284
|
const { rest: remaining } = takeOption(allFlag.rest, "--reason");
|
|
2927
2285
|
requireNoExtraArgs(remaining, "rig task reopen [--task <id> | --all] [--reason <text>]");
|
|
2928
2286
|
if (!allFlag.value && !task) {
|
|
2929
|
-
throw new
|
|
2287
|
+
throw new CliError("Usage: rig task reopen [--task <id> | --all] [--reason <text>]");
|
|
2930
2288
|
}
|
|
2931
2289
|
const summary = withMutedConsole(context.outputMode === "json", () => taskReopen(context.projectRoot, {
|
|
2932
2290
|
all: allFlag.value,
|
|
@@ -2936,7 +2294,10 @@ async function executeTask(context, args, options) {
|
|
|
2936
2294
|
return { ok: true, group: "task", command, details: summary };
|
|
2937
2295
|
}
|
|
2938
2296
|
default:
|
|
2939
|
-
|
|
2297
|
+
if (command === "reset") {
|
|
2298
|
+
throw new CliError("Unknown task command: reset", 1, { hint: "Use `rig task reopen --task <id>`." });
|
|
2299
|
+
}
|
|
2300
|
+
throw new CliError(`Unknown task command: ${command}`, 1, { hint: "Run `rig task --help` to list available task commands." });
|
|
2940
2301
|
}
|
|
2941
2302
|
}
|
|
2942
2303
|
export {
|