@h-rig/cli 0.0.6-alpha.10 → 0.0.6-alpha.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -19
- package/dist/bin/build-rig-binaries.d.ts +2 -0
- package/dist/bin/build-rig-binaries.js +22 -10
- package/dist/bin/rig.d.ts +72 -0
- package/dist/bin/rig.js +15683 -7361
- package/dist/config/rig-default-config.yml +5 -0
- package/dist/src/app/drone-ui.d.ts +34 -0
- package/dist/src/app/drone-ui.js +278 -0
- package/dist/src/commands/_async-ui.d.ts +10 -0
- package/dist/src/commands/_async-ui.js +121 -0
- package/dist/src/commands/_cli-format.d.ts +56 -0
- package/dist/src/commands/_cli-format.js +319 -0
- package/dist/src/commands/_config-env.d.ts +18 -0
- package/dist/src/commands/_config-env.js +22 -0
- package/dist/src/commands/_connection-state.d.ts +54 -0
- package/dist/src/commands/_connection-state.js +75 -11
- package/dist/src/commands/_doctor-checks.d.ts +46 -0
- package/dist/src/commands/_doctor-checks.js +100 -267
- package/dist/src/commands/_help-catalog.d.ts +29 -0
- package/dist/src/commands/_help-catalog.js +146 -0
- package/dist/src/commands/_inprocess-services.d.ts +33 -0
- package/dist/src/commands/_inprocess-services.js +102 -0
- package/dist/src/commands/_json-output.d.ts +11 -0
- package/dist/src/commands/_json-output.js +60 -0
- package/dist/src/commands/_lazy-reconcile.d.ts +34 -0
- package/dist/src/commands/_lazy-reconcile.js +102 -0
- package/dist/src/commands/_parsers.d.ts +15 -0
- package/dist/src/commands/_parsers.js +18 -11
- package/dist/src/commands/_paths.d.ts +11 -0
- package/dist/src/commands/_paths.js +1 -1
- package/dist/src/commands/_pi-frontend.d.ts +35 -0
- package/dist/src/commands/_pi-frontend.js +64 -0
- package/dist/src/commands/_pi-install.d.ts +42 -0
- package/dist/src/commands/_pi-install.js +19 -36
- package/dist/src/commands/_policy.d.ts +8 -0
- package/dist/src/commands/_policy.js +67 -19
- package/dist/src/commands/_probes.d.ts +1 -0
- package/dist/src/commands/_run-bridge.d.ts +114 -0
- package/dist/src/commands/_run-bridge.js +387 -0
- package/dist/src/commands/_run-diagnostics.d.ts +9 -0
- package/dist/src/commands/_run-diagnostics.js +51 -0
- package/dist/src/commands/_run-driver-helpers.d.ts +26 -0
- package/dist/src/commands/_run-driver-helpers.js +79 -230
- package/dist/src/commands/_run-projection.d.ts +50 -0
- package/dist/src/commands/_run-projection.js +349 -0
- package/dist/src/commands/_run-subcommands.d.ts +3 -0
- package/dist/src/commands/_run-subcommands.js +31 -0
- package/dist/src/commands/_spinner.d.ts +25 -0
- package/dist/src/commands/_spinner.js +65 -0
- package/dist/src/commands/agent.d.ts +3 -0
- package/dist/src/commands/agent.js +8690 -236
- package/dist/src/commands/config.d.ts +3 -0
- package/dist/src/commands/config.js +181 -0
- package/dist/src/commands/dist.d.ts +28 -0
- package/dist/src/commands/dist.js +44 -29
- package/dist/src/commands/doctor.d.ts +3 -0
- package/dist/src/commands/doctor.js +219 -268
- package/dist/src/commands/github.d.ts +3 -0
- package/dist/src/commands/github.js +236 -188
- package/dist/src/commands/inbox.d.ts +28 -0
- package/dist/src/commands/inbox.js +530 -111
- package/dist/src/commands/init.d.ts +64 -0
- package/dist/src/commands/init.js +912 -705
- package/dist/src/commands/inspect.d.ts +20 -0
- package/dist/src/commands/inspect.js +709 -119
- package/dist/src/commands/pi.d.ts +3 -0
- package/dist/src/commands/pi.js +177 -0
- package/dist/src/commands/plugin.d.ts +16 -0
- package/dist/src/commands/plugin.js +576 -29
- package/dist/src/commands/profile-and-review.d.ts +4 -0
- package/dist/src/commands/profile-and-review.js +112 -67
- package/dist/src/commands/queue.d.ts +3 -0
- package/dist/src/commands/queue.js +20 -20
- package/dist/src/commands/remote.d.ts +3 -0
- package/dist/src/commands/remote.js +883 -50
- package/dist/src/commands/repo-git-harness.d.ts +5 -0
- package/dist/src/commands/repo-git-harness.js +78 -28
- package/dist/src/commands/run.d.ts +21 -0
- package/dist/src/commands/run.js +17731 -661
- package/dist/src/commands/server.d.ts +3 -0
- package/dist/src/commands/server.js +166 -303
- package/dist/src/commands/setup.d.ts +16 -0
- package/dist/src/commands/setup.js +244 -299
- package/dist/src/commands/stats.d.ts +15 -0
- package/dist/src/commands/stats.js +632 -0
- package/dist/src/commands/task-run-driver.d.ts +94 -0
- package/dist/src/commands/task-run-driver.js +116 -1961
- package/dist/src/commands/task.d.ts +35 -0
- package/dist/src/commands/task.js +676 -1201
- package/dist/src/commands/test.d.ts +3 -0
- package/dist/src/commands/test.js +15 -8
- package/dist/src/commands/triage.d.ts +11 -0
- package/dist/src/commands/triage.js +227 -0
- package/dist/src/commands/workspace.d.ts +3 -0
- package/dist/src/commands/workspace.js +18 -11
- package/dist/src/commands.d.ts +13 -0
- package/dist/src/commands.js +16812 -8045
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +16817 -8377
- package/dist/src/launcher.d.ts +61 -0
- package/dist/src/launcher.js +77 -11
- package/dist/src/operator-cli.d.ts +2 -0
- package/dist/src/operator-cli.js +17867 -0
- package/dist/src/operator-entry.d.ts +1 -0
- package/dist/src/operator-entry.js +3 -0
- package/dist/src/runner.d.ts +47 -0
- package/dist/src/runner.js +16 -22
- package/dist/src/withMutedConsole.d.ts +2 -0
- package/package.json +21 -8
- package/dist/src/commands/_authority-runs.js +0 -111
- package/dist/src/commands/_operator-view.js +0 -340
- package/dist/src/commands/_preflight.js +0 -478
- package/dist/src/commands/_server-client.js +0 -382
- package/dist/src/commands/_snapshot-upload.js +0 -331
- package/dist/src/commands/_task-picker.js +0 -48
- package/dist/src/commands/browser.js +0 -890
- package/dist/src/commands/connect.js +0 -180
- package/dist/src/commands/inspector.js +0 -256
- package/dist/src/commands/task-report-bug.js +0 -1083
- package/dist/src/report-bug.js +0 -260
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RigStatsData } from "@rig/contracts";
|
|
2
|
+
import type { CommandOutcome } from "@rig/runtime";
|
|
3
|
+
import { type RunnerContext } from "../runner";
|
|
4
|
+
import { listRunProjections } from "./_run-projection";
|
|
5
|
+
type StatsCommandDeps = {
|
|
6
|
+
listRuns?: typeof listRunProjections;
|
|
7
|
+
};
|
|
8
|
+
export declare function parseSinceOption(value: string | undefined, now?: Date): Date | null;
|
|
9
|
+
export declare function computeRigStats(projectRoot: string, options?: {
|
|
10
|
+
since?: Date | null;
|
|
11
|
+
listRuns?: typeof listRunProjections;
|
|
12
|
+
}): Promise<RigStatsData>;
|
|
13
|
+
export declare function formatStatsRows(stats: RigStatsData): Array<[string, unknown]>;
|
|
14
|
+
export declare function executeStats(context: RunnerContext, args: string[], deps?: StatsCommandDeps): Promise<CommandOutcome>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli/src/runner.ts
|
|
3
|
+
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
4
|
+
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
5
|
+
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
6
|
+
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
7
|
+
|
|
8
|
+
class CliError extends RuntimeCliError {
|
|
9
|
+
hint;
|
|
10
|
+
constructor(message, exitCode = 1, options = {}) {
|
|
11
|
+
super(message, exitCode);
|
|
12
|
+
if (options.hint?.trim()) {
|
|
13
|
+
this.hint = options.hint.trim();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function takeOption(args, option) {
|
|
18
|
+
const rest = [];
|
|
19
|
+
let value;
|
|
20
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
21
|
+
const current = args[index];
|
|
22
|
+
if (current === option) {
|
|
23
|
+
const next = args[index + 1];
|
|
24
|
+
if (!next || next.startsWith("-")) {
|
|
25
|
+
throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
|
|
26
|
+
}
|
|
27
|
+
value = next;
|
|
28
|
+
index += 1;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (current !== undefined) {
|
|
32
|
+
rest.push(current);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { value, rest };
|
|
36
|
+
}
|
|
37
|
+
function requireNoExtraArgs(args, usage) {
|
|
38
|
+
if (args.length > 0) {
|
|
39
|
+
throw new CliError(`Unexpected arguments: ${args.join(" ")}
|
|
40
|
+
Usage: ${usage}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// packages/cli/src/commands/_cli-format.ts
|
|
45
|
+
import pc from "picocolors";
|
|
46
|
+
var dim = pc.dim;
|
|
47
|
+
var faintBar = pc.dim("\u2502");
|
|
48
|
+
var accent = pc.cyan;
|
|
49
|
+
function compactValue(value) {
|
|
50
|
+
if (value === null || value === undefined)
|
|
51
|
+
return "\u2014";
|
|
52
|
+
if (typeof value === "string")
|
|
53
|
+
return value;
|
|
54
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
55
|
+
return String(value);
|
|
56
|
+
return JSON.stringify(value);
|
|
57
|
+
}
|
|
58
|
+
function printFormattedOutput(message) {
|
|
59
|
+
console.log(message);
|
|
60
|
+
}
|
|
61
|
+
function formatStatsTable(rows) {
|
|
62
|
+
return rows.map(([label, value]) => `${faintBar} ${dim(label.padEnd(20))} ${compactValue(value)}`).join(`
|
|
63
|
+
`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// packages/cli/src/commands/_help-catalog.ts
|
|
67
|
+
import pc2 from "picocolors";
|
|
68
|
+
import {
|
|
69
|
+
ADVANCED_COMMANDS,
|
|
70
|
+
ADVANCED_GROUPS,
|
|
71
|
+
ALL_GROUPS,
|
|
72
|
+
helpCatalog,
|
|
73
|
+
TOP_LEVEL_SECTIONS
|
|
74
|
+
} from "@rig/contracts";
|
|
75
|
+
function heading(title) {
|
|
76
|
+
return pc2.bold(pc2.cyan(title));
|
|
77
|
+
}
|
|
78
|
+
function commandLine(command, description) {
|
|
79
|
+
const commandColumn = command.length >= 38 ? `${command} ` : command.padEnd(38);
|
|
80
|
+
return `${pc2.dim("\u2502")} ${pc2.bold(commandColumn)} ${description}`;
|
|
81
|
+
}
|
|
82
|
+
function renderCommandBlock(commands) {
|
|
83
|
+
return commands.map((entry) => commandLine(entry.command, entry.description)).join(`
|
|
84
|
+
`);
|
|
85
|
+
}
|
|
86
|
+
function renderGroup(group) {
|
|
87
|
+
const lines = [
|
|
88
|
+
`${heading(`rig ${group.name}`)} \u2014 ${group.summary}`,
|
|
89
|
+
"",
|
|
90
|
+
pc2.bold("Usage"),
|
|
91
|
+
...group.usage.map((line) => ` ${line}`),
|
|
92
|
+
"",
|
|
93
|
+
pc2.bold("Commands"),
|
|
94
|
+
...group.commands.map((entry) => commandLine(entry.command, entry.description))
|
|
95
|
+
];
|
|
96
|
+
if (group.examples?.length) {
|
|
97
|
+
lines.push("", pc2.bold("Examples"), ...group.examples.map((line) => ` ${pc2.dim("$")} ${line}`));
|
|
98
|
+
}
|
|
99
|
+
if (group.next?.length) {
|
|
100
|
+
lines.push("", pc2.bold("Next steps"), ...group.next.map((line) => ` ${pc2.dim("\u203A")} ${line}`));
|
|
101
|
+
}
|
|
102
|
+
if (group.advanced?.length) {
|
|
103
|
+
lines.push("", pc2.bold("Compatibility / advanced"), ...group.advanced.map((line) => ` ${pc2.dim("\u203A")} ${line}`));
|
|
104
|
+
}
|
|
105
|
+
return lines.join(`
|
|
106
|
+
`);
|
|
107
|
+
}
|
|
108
|
+
function renderTopLevelHelp() {
|
|
109
|
+
return [
|
|
110
|
+
`${heading("rig")} ${pc2.dim("\u2014 Rig Cockpit for autonomous coding agents")}`,
|
|
111
|
+
pc2.dim("The loop: bare `rig` \u2192 Cockpit \u2192 Server target \u2192 Tasks \u2192 Task detail \u2192 Dispatch/Attach; every run uses OMP collaboration as the session substrate."),
|
|
112
|
+
"",
|
|
113
|
+
...TOP_LEVEL_SECTIONS.flatMap((section) => [
|
|
114
|
+
`${pc2.bold(pc2.magenta(`\u25C7 ${section.title}`))} \u2014 ${pc2.dim(section.subtitle)}`,
|
|
115
|
+
renderCommandBlock(section.commands),
|
|
116
|
+
""
|
|
117
|
+
]),
|
|
118
|
+
pc2.dim("More: `rig help --advanced` for fenced legacy/diagnostic commands; `rig <group> --help` for per-group compatibility help; `rig --version` for the installed version."),
|
|
119
|
+
"",
|
|
120
|
+
pc2.bold("Root options"),
|
|
121
|
+
commandLine("--workspace <path>", "Open Rig Cockpit for a workspace instead of the current directory."),
|
|
122
|
+
commandLine("--json", "Compatibility flag for fenced legacy/diagnostic subcommands."),
|
|
123
|
+
commandLine("--dry-run", "Compatibility flag for fenced legacy/diagnostic subcommands.")
|
|
124
|
+
].join(`
|
|
125
|
+
`).trimEnd();
|
|
126
|
+
}
|
|
127
|
+
function renderGroupHelp(groupName) {
|
|
128
|
+
const group = ALL_GROUPS.find((candidate) => candidate.name === groupName);
|
|
129
|
+
return group ? renderGroup(group) : null;
|
|
130
|
+
}
|
|
131
|
+
function printGroupHelpDocument(groupName) {
|
|
132
|
+
console.log(renderGroupHelp(groupName) ?? renderTopLevelHelp());
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// packages/cli/src/commands/_run-projection.ts
|
|
136
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2 } from "fs";
|
|
137
|
+
import { resolve as resolve2 } from "path";
|
|
138
|
+
import {
|
|
139
|
+
foldRunSessionEntries,
|
|
140
|
+
sessionIdFromSessionFile as sessionIdFromSessionFile2
|
|
141
|
+
} from "@rig/contracts";
|
|
142
|
+
import { listAgentRuntimes as listAgentRuntimes2 } from "@rig/runtime/control-plane/runtime/isolation";
|
|
143
|
+
|
|
144
|
+
// packages/cli/src/commands/_run-bridge.ts
|
|
145
|
+
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
146
|
+
import { dirname, resolve } from "path";
|
|
147
|
+
import { CUSTOM_TYPE_FOR, sessionIdFromSessionFile } from "@rig/contracts";
|
|
148
|
+
import { createPluginHost } from "@rig/core";
|
|
149
|
+
import { loadConfig } from "@rig/core/load-config";
|
|
150
|
+
import { resolveOwnerNamespaceKey, resolveRegistryBaseUrl, resolveRegistrySecret, resolveSelectedRemote } from "@rig/runtime/control-plane/remote";
|
|
151
|
+
import { listAgentRuntimes } from "@rig/runtime/control-plane/runtime/isolation";
|
|
152
|
+
import { createRegistryClient } from "@rig/relay-registry";
|
|
153
|
+
import {
|
|
154
|
+
listActiveCollabSessions as ompListActiveCollabSessions
|
|
155
|
+
} from "@oh-my-pi/pi-coding-agent/collab/api";
|
|
156
|
+
function registrySecret(env = process.env) {
|
|
157
|
+
return resolveRegistrySecret(process.cwd(), env) ?? null;
|
|
158
|
+
}
|
|
159
|
+
function registryBaseUrl(env = process.env) {
|
|
160
|
+
return resolveRegistryBaseUrl(process.cwd(), env);
|
|
161
|
+
}
|
|
162
|
+
function collabFromRegistryEntry(entry) {
|
|
163
|
+
return {
|
|
164
|
+
sessionId: entry.roomId,
|
|
165
|
+
sessionPath: "",
|
|
166
|
+
cwd: "",
|
|
167
|
+
title: entry.title,
|
|
168
|
+
joinLink: entry.joinLink,
|
|
169
|
+
webLink: entry.webLink,
|
|
170
|
+
relayUrl: entry.relayUrl,
|
|
171
|
+
owner: entry.owner,
|
|
172
|
+
selectedRepo: entry.repo,
|
|
173
|
+
startedAt: entry.startedAt,
|
|
174
|
+
updatedAt: entry.heartbeatAt,
|
|
175
|
+
...entry.pid === undefined ? {} : { pid: entry.pid },
|
|
176
|
+
stale: entry.stale
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function isRecord(value) {
|
|
180
|
+
return typeof value === "object" && value !== null;
|
|
181
|
+
}
|
|
182
|
+
function readLocalRunCollab(runtime) {
|
|
183
|
+
if (!runtime.sessionDir || !existsSync(runtime.sessionDir))
|
|
184
|
+
return null;
|
|
185
|
+
let files;
|
|
186
|
+
try {
|
|
187
|
+
files = readdirSync(runtime.sessionDir).filter((f) => f.endsWith(".jsonl")).sort().reverse();
|
|
188
|
+
} catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
for (const file of files) {
|
|
192
|
+
let lines;
|
|
193
|
+
try {
|
|
194
|
+
lines = readFileSync(resolve(runtime.sessionDir, file), "utf8").split(`
|
|
195
|
+
`);
|
|
196
|
+
} catch {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
let host = null;
|
|
200
|
+
for (const line of lines) {
|
|
201
|
+
if (!line.includes("collab-host-started"))
|
|
202
|
+
continue;
|
|
203
|
+
let entry;
|
|
204
|
+
try {
|
|
205
|
+
entry = JSON.parse(line);
|
|
206
|
+
} catch {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (!isRecord(entry) || entry.customType !== CUSTOM_TYPE_FOR["timeline-entry"] || !isRecord(entry.data))
|
|
210
|
+
continue;
|
|
211
|
+
const payload = isRecord(entry.data.payload) ? entry.data.payload : entry.data;
|
|
212
|
+
if (payload.type !== "collab-host-started")
|
|
213
|
+
continue;
|
|
214
|
+
host = {
|
|
215
|
+
roomId: typeof payload.roomId === "string" ? payload.roomId : undefined,
|
|
216
|
+
joinLink: typeof payload.joinLink === "string" ? payload.joinLink : undefined,
|
|
217
|
+
webLink: typeof payload.webLink === "string" ? payload.webLink : undefined,
|
|
218
|
+
relayUrl: typeof payload.relayUrl === "string" ? payload.relayUrl : undefined,
|
|
219
|
+
at: typeof entry.data.at === "string" ? entry.data.at : undefined
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (!host?.joinLink)
|
|
223
|
+
continue;
|
|
224
|
+
const at = host.at ?? "";
|
|
225
|
+
const sessionId = host.roomId ?? sessionIdFromSessionFile(resolve(runtime.sessionDir, file)) ?? runtime.id;
|
|
226
|
+
return {
|
|
227
|
+
sessionId,
|
|
228
|
+
sessionPath: resolve(runtime.sessionDir, file),
|
|
229
|
+
cwd: runtime.workspaceDir,
|
|
230
|
+
joinLink: host.joinLink,
|
|
231
|
+
webLink: host.webLink ?? "",
|
|
232
|
+
relayUrl: host.relayUrl ?? "",
|
|
233
|
+
title: runtime.taskId ? `[${runtime.taskId}] Rig run ${sessionId}` : `Rig run ${sessionId}`,
|
|
234
|
+
startedAt: at,
|
|
235
|
+
updatedAt: at,
|
|
236
|
+
stale: false
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
async function listLocalRunCollabSessions(projectRoot) {
|
|
242
|
+
let runtimes;
|
|
243
|
+
try {
|
|
244
|
+
runtimes = await listAgentRuntimes(projectRoot);
|
|
245
|
+
} catch {
|
|
246
|
+
return [];
|
|
247
|
+
}
|
|
248
|
+
const out = [];
|
|
249
|
+
for (const runtime of runtimes) {
|
|
250
|
+
const collab = readLocalRunCollab(runtime);
|
|
251
|
+
if (collab)
|
|
252
|
+
out.push(collab);
|
|
253
|
+
}
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
async function listActiveRunCollab(filter) {
|
|
257
|
+
return defaultListActiveCollabSessions(filter);
|
|
258
|
+
}
|
|
259
|
+
async function defaultListActiveCollabSessions(filter) {
|
|
260
|
+
const projectRoot = process.cwd();
|
|
261
|
+
const local = await listLocalRunCollabSessions(projectRoot);
|
|
262
|
+
const namespaceKey = filter.namespaceKey ?? resolveOwnerNamespaceKey(projectRoot);
|
|
263
|
+
const secret = registrySecret();
|
|
264
|
+
const remote = namespaceKey && secret ? (await createRegistryClient({ baseUrl: registryBaseUrl(), namespaceKey, secret }).listRoomsByOwner(filter)).map(collabFromRegistryEntry) : await ompListActiveCollabSessions(filter);
|
|
265
|
+
const seen = new Set(local.map((c) => c.sessionId));
|
|
266
|
+
return [...local, ...remote.filter((c) => !seen.has(c.sessionId))];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// packages/cli/src/commands/_run-diagnostics.ts
|
|
270
|
+
function normalizeString(value) {
|
|
271
|
+
if (typeof value !== "string")
|
|
272
|
+
return null;
|
|
273
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
274
|
+
return normalized.length > 0 ? normalized : null;
|
|
275
|
+
}
|
|
276
|
+
function isGenericRunFailure(value) {
|
|
277
|
+
const text = normalizeString(value);
|
|
278
|
+
return Boolean(text && /^Task run failed \([^)]*\)$/i.test(text));
|
|
279
|
+
}
|
|
280
|
+
function appendCandidate(candidates, value) {
|
|
281
|
+
const text = normalizeString(value);
|
|
282
|
+
if (text && !candidates.includes(text))
|
|
283
|
+
candidates.push(text);
|
|
284
|
+
}
|
|
285
|
+
function categorizeUsefulRunError(lines) {
|
|
286
|
+
const taskSourceFailure = lines.find((line) => /failed to update task source/i.test(line));
|
|
287
|
+
if (taskSourceFailure)
|
|
288
|
+
return `Task source update failed: ${taskSourceFailure}`;
|
|
289
|
+
const moduleFailure = lines.find((line) => /cannot find module/i.test(line));
|
|
290
|
+
if (moduleFailure)
|
|
291
|
+
return `Runtime module resolution failed: ${moduleFailure}`;
|
|
292
|
+
const providerFailure = lines.find((line) => /no api key found|unauthorized|authentication failed|invalid api key/i.test(line));
|
|
293
|
+
if (providerFailure)
|
|
294
|
+
return `Provider authentication failed: ${providerFailure}`;
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
function summarizeUsefulRunError(projection) {
|
|
298
|
+
if (projection.status !== "failed")
|
|
299
|
+
return null;
|
|
300
|
+
const candidates = [];
|
|
301
|
+
for (const anomaly of projection.anomalies) {
|
|
302
|
+
appendCandidate(candidates, `Journal anomaly (${anomaly.kind}): ${anomaly.detail}`);
|
|
303
|
+
}
|
|
304
|
+
for (const phase of projection.closeoutPhases) {
|
|
305
|
+
if (phase.outcome === "failed")
|
|
306
|
+
appendCandidate(candidates, phase.detail);
|
|
307
|
+
}
|
|
308
|
+
for (const entry of projection.statusHistory) {
|
|
309
|
+
appendCandidate(candidates, entry.reason);
|
|
310
|
+
}
|
|
311
|
+
appendCandidate(candidates, projection.record.statusDetail);
|
|
312
|
+
appendCandidate(candidates, projection.record.errorText);
|
|
313
|
+
const nonGeneric = candidates.filter((candidate) => !isGenericRunFailure(candidate));
|
|
314
|
+
return categorizeUsefulRunError(nonGeneric) ?? nonGeneric.at(-1) ?? normalizeString(projection.record.errorText);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// packages/cli/src/commands/_run-projection.ts
|
|
318
|
+
var EMPTY_PROJECTION = foldRunSessionEntries([], "");
|
|
319
|
+
function stringOrNull(value) {
|
|
320
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
321
|
+
}
|
|
322
|
+
function newestSessionFile(sessionDir) {
|
|
323
|
+
if (!sessionDir || !existsSync2(sessionDir))
|
|
324
|
+
return null;
|
|
325
|
+
try {
|
|
326
|
+
const files = readdirSync2(sessionDir).filter((file) => file.endsWith(".jsonl")).sort().reverse();
|
|
327
|
+
return files[0] ? resolve2(sessionDir, files[0]) : null;
|
|
328
|
+
} catch {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function readSessionRunEntries(sessionPath) {
|
|
333
|
+
if (!sessionPath || !existsSync2(sessionPath))
|
|
334
|
+
return [];
|
|
335
|
+
let raw;
|
|
336
|
+
try {
|
|
337
|
+
raw = readFileSync2(sessionPath, "utf8");
|
|
338
|
+
} catch {
|
|
339
|
+
return [];
|
|
340
|
+
}
|
|
341
|
+
const entries = [];
|
|
342
|
+
for (const line of raw.split(`
|
|
343
|
+
`)) {
|
|
344
|
+
if (!line.trim())
|
|
345
|
+
continue;
|
|
346
|
+
let parsed;
|
|
347
|
+
try {
|
|
348
|
+
parsed = JSON.parse(line);
|
|
349
|
+
} catch {
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
353
|
+
continue;
|
|
354
|
+
entries.push({
|
|
355
|
+
type: "type" in parsed && typeof parsed.type === "string" ? parsed.type : "",
|
|
356
|
+
..."customType" in parsed && typeof parsed.customType === "string" ? { customType: parsed.customType } : {},
|
|
357
|
+
..."data" in parsed ? { data: parsed.data } : {}
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
return entries;
|
|
361
|
+
}
|
|
362
|
+
function recordFromProjection(runId, projection, presence) {
|
|
363
|
+
const record = projection.record;
|
|
364
|
+
const status = projection.status ?? (presence.stale ? "stale" : presence.live ? "running" : "unknown");
|
|
365
|
+
const taskId = stringOrNull(record.taskId) ?? presence.fallbackTaskId ?? null;
|
|
366
|
+
const title = stringOrNull(record.title) ?? stringOrNull(presence.title) ?? (taskId ? `Run ${taskId}` : `Rig run ${runId}`);
|
|
367
|
+
return {
|
|
368
|
+
runId,
|
|
369
|
+
taskId,
|
|
370
|
+
title,
|
|
371
|
+
status,
|
|
372
|
+
source: presence.source,
|
|
373
|
+
live: presence.live,
|
|
374
|
+
stale: presence.stale,
|
|
375
|
+
startedAt: stringOrNull(record.startedAt) ?? presence.startedAt ?? null,
|
|
376
|
+
updatedAt: stringOrNull(record.updatedAt) ?? presence.updatedAt ?? projection.lastEventAt ?? null,
|
|
377
|
+
completedAt: stringOrNull(record.completedAt),
|
|
378
|
+
joinLink: presence.joinLink ?? null,
|
|
379
|
+
webLink: presence.webLink ?? null,
|
|
380
|
+
relayUrl: presence.relayUrl ?? null,
|
|
381
|
+
sessionPath: stringOrNull(record.sessionPath) ?? presence.sessionPath ?? null,
|
|
382
|
+
prUrl: stringOrNull(record.prUrl),
|
|
383
|
+
worktreePath: stringOrNull(record.worktreePath),
|
|
384
|
+
pendingApprovals: projection.pendingApprovals.length,
|
|
385
|
+
pendingInputs: projection.pendingUserInputs.length,
|
|
386
|
+
steeringCount: projection.steeringCount,
|
|
387
|
+
stallCount: projection.stallCount,
|
|
388
|
+
errorSummary: summarizeUsefulRunError(projection),
|
|
389
|
+
projection
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function recordFromLiveCollab(collab) {
|
|
393
|
+
const runId = collab.sessionId;
|
|
394
|
+
const sessionPath = stringOrNull(collab.sessionPath);
|
|
395
|
+
const projection = sessionPath ? foldRunSessionEntries(readSessionRunEntries(sessionPath), runId) : EMPTY_PROJECTION;
|
|
396
|
+
return recordFromProjection(runId, projection, {
|
|
397
|
+
source: sessionPath ? "local" : "remote",
|
|
398
|
+
live: !collab.stale,
|
|
399
|
+
stale: Boolean(collab.stale),
|
|
400
|
+
title: collab.title,
|
|
401
|
+
joinLink: stringOrNull(collab.joinLink),
|
|
402
|
+
webLink: stringOrNull(collab.webLink),
|
|
403
|
+
relayUrl: stringOrNull(collab.relayUrl),
|
|
404
|
+
sessionPath,
|
|
405
|
+
startedAt: stringOrNull(collab.startedAt),
|
|
406
|
+
updatedAt: stringOrNull(collab.updatedAt)
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
function recordFromRuntime(runtime) {
|
|
410
|
+
const sessionPath = newestSessionFile(runtime.sessionDir ?? "");
|
|
411
|
+
if (!sessionPath)
|
|
412
|
+
return null;
|
|
413
|
+
const projection = foldRunSessionEntries(readSessionRunEntries(sessionPath), runtime.id);
|
|
414
|
+
if (projection.lastSeq === 0)
|
|
415
|
+
return null;
|
|
416
|
+
return recordFromProjection(sessionIdFromSessionFile2(sessionPath) ?? runtime.id, projection, {
|
|
417
|
+
source: "local",
|
|
418
|
+
live: false,
|
|
419
|
+
stale: false,
|
|
420
|
+
sessionPath,
|
|
421
|
+
fallbackTaskId: stringOrNull(runtime.taskId)
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
function sortByRecency(records) {
|
|
425
|
+
return [...records].sort((a, b) => {
|
|
426
|
+
const at = Date.parse(b.updatedAt ?? b.startedAt ?? "") || 0;
|
|
427
|
+
const bt = Date.parse(a.updatedAt ?? a.startedAt ?? "") || 0;
|
|
428
|
+
return at - bt;
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
async function listRunProjections(projectRoot, filter = {}) {
|
|
432
|
+
const byRunId = new Map;
|
|
433
|
+
let live = [];
|
|
434
|
+
try {
|
|
435
|
+
live = await listActiveRunCollab(filter);
|
|
436
|
+
} catch {
|
|
437
|
+
live = [];
|
|
438
|
+
}
|
|
439
|
+
for (const collab of live) {
|
|
440
|
+
const record = recordFromLiveCollab(collab);
|
|
441
|
+
byRunId.set(record.runId, record);
|
|
442
|
+
}
|
|
443
|
+
let runtimes = [];
|
|
444
|
+
try {
|
|
445
|
+
runtimes = await listAgentRuntimes2(projectRoot);
|
|
446
|
+
} catch {
|
|
447
|
+
runtimes = [];
|
|
448
|
+
}
|
|
449
|
+
for (const runtime of runtimes) {
|
|
450
|
+
const record = recordFromRuntime(runtime);
|
|
451
|
+
if (!record)
|
|
452
|
+
continue;
|
|
453
|
+
const sessionId = sessionIdFromSessionFile2(record.sessionPath);
|
|
454
|
+
const liveMatch = sessionId ? byRunId.get(sessionId) : undefined;
|
|
455
|
+
if (liveMatch) {
|
|
456
|
+
if (liveMatch.source === "remote" || !liveMatch.sessionPath) {
|
|
457
|
+
byRunId.set(liveMatch.runId, { ...liveMatch, sessionPath: record.sessionPath, source: "local" });
|
|
458
|
+
}
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (byRunId.has(runtime.id))
|
|
462
|
+
continue;
|
|
463
|
+
byRunId.set(record.runId, record);
|
|
464
|
+
}
|
|
465
|
+
return sortByRecency([...byRunId.values()]);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// packages/cli/src/commands/stats.ts
|
|
469
|
+
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
470
|
+
function parseSinceOption(value, now = new Date) {
|
|
471
|
+
if (!value?.trim())
|
|
472
|
+
return null;
|
|
473
|
+
const trimmed = value.trim();
|
|
474
|
+
const relative = trimmed.match(/^(\d+)d$/i);
|
|
475
|
+
if (relative?.[1]) {
|
|
476
|
+
const days = Number.parseInt(relative[1], 10);
|
|
477
|
+
if (!Number.isFinite(days) || days <= 0) {
|
|
478
|
+
throw new CliError(`Invalid --since value: ${value}`, 2, {
|
|
479
|
+
hint: "Use an ISO date or a relative window like `7d`."
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
return new Date(now.getTime() - days * DAY_MS);
|
|
483
|
+
}
|
|
484
|
+
const parsed = Date.parse(trimmed);
|
|
485
|
+
if (!Number.isFinite(parsed)) {
|
|
486
|
+
throw new CliError(`Invalid --since value: ${value}`, 2, {
|
|
487
|
+
hint: "Use an ISO date or a relative window like `7d`."
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return new Date(parsed);
|
|
491
|
+
}
|
|
492
|
+
function parseTimestamp(value) {
|
|
493
|
+
if (!value)
|
|
494
|
+
return null;
|
|
495
|
+
const parsed = Date.parse(value);
|
|
496
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
497
|
+
}
|
|
498
|
+
function median(values) {
|
|
499
|
+
if (values.length === 0)
|
|
500
|
+
return null;
|
|
501
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
502
|
+
const middle = Math.floor(sorted.length / 2);
|
|
503
|
+
return sorted.length % 2 === 1 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
|
|
504
|
+
}
|
|
505
|
+
function rate(part, total) {
|
|
506
|
+
return total === 0 ? null : part / total;
|
|
507
|
+
}
|
|
508
|
+
function isNeedsAttention(run) {
|
|
509
|
+
return run.status === "needs-attention" || run.pendingApprovals + run.pendingInputs > 0 || run.stallCount > 0;
|
|
510
|
+
}
|
|
511
|
+
function completedDuration(run) {
|
|
512
|
+
if (run.status !== "completed")
|
|
513
|
+
return null;
|
|
514
|
+
const startedAt = parseTimestamp(run.startedAt);
|
|
515
|
+
const completedAt = parseTimestamp(run.completedAt);
|
|
516
|
+
if (startedAt === null || completedAt === null || completedAt < startedAt)
|
|
517
|
+
return null;
|
|
518
|
+
return completedAt - startedAt;
|
|
519
|
+
}
|
|
520
|
+
async function computeRigStats(projectRoot, options = {}) {
|
|
521
|
+
const sinceMs = options.since ? options.since.getTime() : null;
|
|
522
|
+
const allRuns = await (options.listRuns ?? listRunProjections)(projectRoot);
|
|
523
|
+
const runs = allRuns.filter((run) => {
|
|
524
|
+
if (sinceMs === null)
|
|
525
|
+
return true;
|
|
526
|
+
const startedAt = parseTimestamp(run.startedAt);
|
|
527
|
+
return startedAt !== null && startedAt >= sinceMs;
|
|
528
|
+
});
|
|
529
|
+
const statusCounts = {};
|
|
530
|
+
const completionDurations = [];
|
|
531
|
+
let completedRuns = 0;
|
|
532
|
+
let failedRuns = 0;
|
|
533
|
+
let needsAttentionRuns = 0;
|
|
534
|
+
let steeringTotal = 0;
|
|
535
|
+
let stallTotal = 0;
|
|
536
|
+
let approvalsPending = 0;
|
|
537
|
+
for (const run of runs) {
|
|
538
|
+
const status = run.status || "unknown";
|
|
539
|
+
statusCounts[status] = (statusCounts[status] ?? 0) + 1;
|
|
540
|
+
if (status === "completed")
|
|
541
|
+
completedRuns += 1;
|
|
542
|
+
if (status === "failed")
|
|
543
|
+
failedRuns += 1;
|
|
544
|
+
if (isNeedsAttention(run))
|
|
545
|
+
needsAttentionRuns += 1;
|
|
546
|
+
const duration = completedDuration(run);
|
|
547
|
+
if (duration !== null)
|
|
548
|
+
completionDurations.push(duration);
|
|
549
|
+
steeringTotal += run.steeringCount;
|
|
550
|
+
stallTotal += run.stallCount;
|
|
551
|
+
approvalsPending += run.pendingApprovals;
|
|
552
|
+
}
|
|
553
|
+
const totalRuns = runs.length;
|
|
554
|
+
return {
|
|
555
|
+
since: options.since ? options.since.toISOString() : null,
|
|
556
|
+
totalRuns,
|
|
557
|
+
statusCounts,
|
|
558
|
+
completedRuns,
|
|
559
|
+
failedRuns,
|
|
560
|
+
needsAttentionRuns,
|
|
561
|
+
completionRate: rate(completedRuns, totalRuns),
|
|
562
|
+
failureRate: rate(failedRuns, totalRuns),
|
|
563
|
+
needsAttentionRate: rate(needsAttentionRuns, totalRuns),
|
|
564
|
+
medianCompletionMs: median(completionDurations),
|
|
565
|
+
steeringTotal,
|
|
566
|
+
steeringPerRun: totalRuns === 0 ? null : steeringTotal / totalRuns,
|
|
567
|
+
stallTotal,
|
|
568
|
+
approvalsRequested: approvalsPending,
|
|
569
|
+
approvalsApproved: 0,
|
|
570
|
+
approvalsRejected: 0,
|
|
571
|
+
approvalsPending
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
function formatPercent(value) {
|
|
575
|
+
return value === null ? "\u2014" : `${Math.round(value * 100)}%`;
|
|
576
|
+
}
|
|
577
|
+
function formatDuration(ms) {
|
|
578
|
+
if (ms === null)
|
|
579
|
+
return "\u2014";
|
|
580
|
+
const seconds = Math.round(ms / 1000);
|
|
581
|
+
if (seconds < 60)
|
|
582
|
+
return `${seconds}s`;
|
|
583
|
+
const minutes = Math.round(ms / 60000);
|
|
584
|
+
if (minutes < 60)
|
|
585
|
+
return `${minutes}m`;
|
|
586
|
+
const hours = Math.floor(minutes / 60);
|
|
587
|
+
const remainder = minutes % 60;
|
|
588
|
+
return remainder === 0 ? `${hours}h` : `${hours}h ${remainder}m`;
|
|
589
|
+
}
|
|
590
|
+
function formatStatsRows(stats) {
|
|
591
|
+
return [
|
|
592
|
+
["window", stats.since ? `since ${stats.since}` : "all runs"],
|
|
593
|
+
["total runs", stats.totalRuns],
|
|
594
|
+
["completed", `${stats.completedRuns} (${formatPercent(stats.completionRate)})`],
|
|
595
|
+
["failed", `${stats.failedRuns} (${formatPercent(stats.failureRate)})`],
|
|
596
|
+
["needs attention", `${stats.needsAttentionRuns} (${formatPercent(stats.needsAttentionRate)})`],
|
|
597
|
+
["median run time", formatDuration(stats.medianCompletionMs)],
|
|
598
|
+
["steering", stats.steeringTotal],
|
|
599
|
+
["stalls", stats.stallTotal],
|
|
600
|
+
["pending approvals", stats.approvalsPending]
|
|
601
|
+
];
|
|
602
|
+
}
|
|
603
|
+
async function executeStats(context, args, deps = {}) {
|
|
604
|
+
const [first = "show", ...rest] = args;
|
|
605
|
+
const command = first.startsWith("-") ? "show" : first;
|
|
606
|
+
const commandArgs = first.startsWith("-") ? args : rest;
|
|
607
|
+
if (command === "help") {
|
|
608
|
+
printGroupHelpDocument("stats");
|
|
609
|
+
return { ok: true, group: "stats", command };
|
|
610
|
+
}
|
|
611
|
+
if (command !== "show") {
|
|
612
|
+
throw new CliError(`Unknown stats command: ${command}`, 1, {
|
|
613
|
+
hint: "Run `rig stats --help` \u2014 the default command is `show`."
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
const sinceResult = takeOption(commandArgs, "--since");
|
|
617
|
+
requireNoExtraArgs(sinceResult.rest, "rig stats [show] [--since <7d|30d|ISO date>]");
|
|
618
|
+
const details = await computeRigStats(context.projectRoot, {
|
|
619
|
+
since: parseSinceOption(sinceResult.value),
|
|
620
|
+
listRuns: deps.listRuns
|
|
621
|
+
});
|
|
622
|
+
if (context.outputMode === "text") {
|
|
623
|
+
printFormattedOutput(formatStatsTable(formatStatsRows(details)));
|
|
624
|
+
}
|
|
625
|
+
return { ok: true, group: "stats", command, details };
|
|
626
|
+
}
|
|
627
|
+
export {
|
|
628
|
+
parseSinceOption,
|
|
629
|
+
formatStatsRows,
|
|
630
|
+
executeStats,
|
|
631
|
+
computeRigStats
|
|
632
|
+
};
|