@h-rig/cli 0.0.6-alpha.7 → 0.0.6-alpha.70
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 +1 -1
- package/dist/bin/rig.js +4507 -1506
- package/dist/src/commands/_async-ui.js +152 -0
- package/dist/src/commands/_authority-runs.js +2 -3
- package/dist/src/commands/_cli-format.js +369 -0
- package/dist/src/commands/_connection-state.js +30 -11
- package/dist/src/commands/_doctor-checks.js +177 -43
- package/dist/src/commands/_help-catalog.js +485 -0
- package/dist/src/commands/_json-output.js +56 -0
- package/dist/src/commands/_operator-surface.js +220 -0
- package/dist/src/commands/_operator-view.js +595 -72
- package/dist/src/commands/_parsers.js +18 -11
- package/dist/src/commands/_pi-frontend.js +411 -0
- package/dist/src/commands/_pi-install.js +4 -3
- package/dist/src/commands/_policy.js +12 -5
- package/dist/src/commands/_preflight.js +187 -127
- package/dist/src/commands/_run-driver-helpers.js +75 -22
- package/dist/src/commands/_run-replay.js +142 -0
- package/dist/src/commands/_server-client.js +343 -60
- package/dist/src/commands/_snapshot-upload.js +160 -38
- package/dist/src/commands/_spinner.js +65 -0
- package/dist/src/commands/_task-picker.js +44 -16
- package/dist/src/commands/agent.js +39 -20
- package/dist/src/commands/browser.js +28 -21
- package/dist/src/commands/connect.js +146 -33
- package/dist/src/commands/dist.js +19 -12
- package/dist/src/commands/doctor.js +304 -44
- package/dist/src/commands/github.js +301 -52
- package/dist/src/commands/inbox.js +679 -72
- package/dist/src/commands/init.js +622 -118
- package/dist/src/commands/inspect.js +515 -32
- package/dist/src/commands/inspector.js +20 -13
- package/dist/src/commands/pi.js +177 -0
- package/dist/src/commands/plugin.js +95 -27
- package/dist/src/commands/profile-and-review.js +26 -19
- package/dist/src/commands/queue.js +32 -12
- package/dist/src/commands/remote.js +43 -36
- package/dist/src/commands/repo-git-harness.js +22 -15
- package/dist/src/commands/run.js +1162 -158
- package/dist/src/commands/server.js +373 -56
- package/dist/src/commands/setup.js +316 -62
- package/dist/src/commands/stats.js +1030 -0
- package/dist/src/commands/task-report-bug.js +29 -22
- package/dist/src/commands/task-run-driver.js +862 -129
- package/dist/src/commands/task.js +1423 -311
- package/dist/src/commands/test.js +15 -8
- package/dist/src/commands/workspace.js +18 -11
- package/dist/src/commands.js +4446 -1499
- package/dist/src/index.js +4502 -1504
- package/dist/src/launcher.js +77 -13
- package/dist/src/report-bug.js +3 -3
- package/dist/src/runner.js +16 -22
- package/package.json +10 -5
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/cli/src/commands/_server-client.ts
|
|
3
|
-
import { spawnSync } from "child_process";
|
|
4
3
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
5
4
|
import { resolve as resolve2 } from "path";
|
|
6
5
|
|
|
7
6
|
// packages/cli/src/runner.ts
|
|
8
7
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
9
|
-
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
8
|
+
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
10
9
|
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
11
|
-
import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
|
|
12
|
-
import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
|
|
13
10
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
14
|
-
|
|
11
|
+
|
|
12
|
+
class CliError extends RuntimeCliError {
|
|
13
|
+
hint;
|
|
14
|
+
constructor(message, exitCode = 1, options = {}) {
|
|
15
|
+
super(message, exitCode);
|
|
16
|
+
if (options.hint?.trim()) {
|
|
17
|
+
this.hint = options.hint.trim();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
15
21
|
|
|
16
22
|
// packages/cli/src/commands/_server-client.ts
|
|
17
23
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
@@ -38,9 +44,14 @@ function readJsonFile(path) {
|
|
|
38
44
|
try {
|
|
39
45
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
40
46
|
} catch (error) {
|
|
41
|
-
throw new
|
|
47
|
+
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>`." });
|
|
42
48
|
}
|
|
43
49
|
}
|
|
50
|
+
function writeJsonFile(path, value) {
|
|
51
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
52
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
53
|
+
`, "utf8");
|
|
54
|
+
}
|
|
44
55
|
function normalizeConnection(value) {
|
|
45
56
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
46
57
|
return null;
|
|
@@ -81,31 +92,51 @@ function readRepoConnection(projectRoot) {
|
|
|
81
92
|
return {
|
|
82
93
|
selected,
|
|
83
94
|
project: typeof record.project === "string" ? record.project : undefined,
|
|
84
|
-
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined
|
|
95
|
+
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
|
|
96
|
+
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
|
|
85
97
|
};
|
|
86
98
|
}
|
|
99
|
+
function writeRepoConnection(projectRoot, state) {
|
|
100
|
+
writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
|
|
101
|
+
}
|
|
87
102
|
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
88
103
|
const repo = readRepoConnection(projectRoot);
|
|
89
104
|
if (!repo)
|
|
90
105
|
return null;
|
|
91
106
|
if (repo.selected === "local")
|
|
92
|
-
return { alias: "local", connection: { kind: "local", mode: "auto" } };
|
|
107
|
+
return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
|
|
93
108
|
const global = readGlobalConnections(options);
|
|
94
109
|
const connection = global.connections[repo.selected];
|
|
95
110
|
if (!connection) {
|
|
96
|
-
throw new
|
|
111
|
+
throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
97
112
|
}
|
|
98
|
-
return { alias: repo.selected, connection };
|
|
113
|
+
return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
|
|
114
|
+
}
|
|
115
|
+
function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
116
|
+
const repo = readRepoConnection(projectRoot);
|
|
117
|
+
if (!repo)
|
|
118
|
+
return;
|
|
119
|
+
writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
|
|
99
120
|
}
|
|
100
121
|
|
|
101
122
|
// packages/cli/src/commands/_server-client.ts
|
|
102
|
-
var
|
|
123
|
+
var scopedGitHubBearerTokens = new Map;
|
|
124
|
+
var serverPhaseListener = null;
|
|
125
|
+
function setServerPhaseListener(listener) {
|
|
126
|
+
const previous = serverPhaseListener;
|
|
127
|
+
serverPhaseListener = listener;
|
|
128
|
+
return previous;
|
|
129
|
+
}
|
|
130
|
+
function reportServerPhase(label) {
|
|
131
|
+
serverPhaseListener?.(label);
|
|
132
|
+
}
|
|
103
133
|
function cleanToken(value) {
|
|
104
134
|
const trimmed = value?.trim();
|
|
105
135
|
return trimmed ? trimmed : null;
|
|
106
136
|
}
|
|
107
|
-
function setGitHubBearerTokenForCurrentProcess(token) {
|
|
108
|
-
|
|
137
|
+
function setGitHubBearerTokenForCurrentProcess(token, projectRoot) {
|
|
138
|
+
const scopedKey = resolve2(projectRoot ?? process.cwd());
|
|
139
|
+
scopedGitHubBearerTokens.set(scopedKey, cleanToken(token ?? undefined));
|
|
109
140
|
}
|
|
110
141
|
function readPrivateRemoteSessionToken(projectRoot) {
|
|
111
142
|
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
@@ -119,49 +150,80 @@ function readPrivateRemoteSessionToken(projectRoot) {
|
|
|
119
150
|
}
|
|
120
151
|
}
|
|
121
152
|
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
122
|
-
|
|
123
|
-
|
|
153
|
+
const scopedKey = resolve2(projectRoot);
|
|
154
|
+
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
155
|
+
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
124
156
|
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
125
|
-
if (privateSession)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return
|
|
157
|
+
if (privateSession)
|
|
158
|
+
return privateSession;
|
|
159
|
+
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
160
|
+
}
|
|
161
|
+
function readStoredGitHubAuthToken(projectRoot) {
|
|
162
|
+
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
163
|
+
if (!existsSync2(path))
|
|
164
|
+
return null;
|
|
165
|
+
try {
|
|
166
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
167
|
+
return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
|
|
168
|
+
} catch {
|
|
169
|
+
return null;
|
|
133
170
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
138
|
-
});
|
|
139
|
-
cachedGitHubBearerToken = result.status === 0 ? cleanToken(result.stdout) : null;
|
|
140
|
-
return cachedGitHubBearerToken;
|
|
171
|
+
}
|
|
172
|
+
function readLocalConnectionFallbackToken(projectRoot) {
|
|
173
|
+
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
141
174
|
}
|
|
142
175
|
async function ensureServerForCli(projectRoot) {
|
|
143
176
|
try {
|
|
144
177
|
const selected = resolveSelectedConnection(projectRoot);
|
|
145
178
|
if (selected?.connection.kind === "remote") {
|
|
179
|
+
reportServerPhase(`Connecting to ${selected.alias}\u2026`);
|
|
180
|
+
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
181
|
+
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
146
182
|
return {
|
|
147
183
|
baseUrl: selected.connection.baseUrl,
|
|
148
|
-
authToken
|
|
149
|
-
connectionKind: "remote"
|
|
184
|
+
authToken,
|
|
185
|
+
connectionKind: "remote",
|
|
186
|
+
serverProjectRoot
|
|
150
187
|
};
|
|
151
188
|
}
|
|
189
|
+
reportServerPhase("Starting local Rig server\u2026");
|
|
152
190
|
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
153
191
|
return {
|
|
154
192
|
baseUrl: connection.baseUrl,
|
|
155
|
-
authToken: connection.authToken,
|
|
156
|
-
connectionKind: "local"
|
|
193
|
+
authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
|
|
194
|
+
connectionKind: "local",
|
|
195
|
+
serverProjectRoot: resolve2(projectRoot)
|
|
157
196
|
};
|
|
158
197
|
} catch (error) {
|
|
159
198
|
if (error instanceof Error) {
|
|
160
|
-
throw new
|
|
199
|
+
throw new CliError(error.message, 1);
|
|
161
200
|
}
|
|
162
201
|
throw error;
|
|
163
202
|
}
|
|
164
203
|
}
|
|
204
|
+
async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
|
|
205
|
+
const repo = readRepoConnection(projectRoot);
|
|
206
|
+
const slug = repo?.project?.trim();
|
|
207
|
+
if (!slug)
|
|
208
|
+
return null;
|
|
209
|
+
try {
|
|
210
|
+
const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
|
|
211
|
+
headers: mergeHeaders(undefined, authToken)
|
|
212
|
+
});
|
|
213
|
+
if (!response.ok)
|
|
214
|
+
return null;
|
|
215
|
+
const payload = await response.json();
|
|
216
|
+
const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
|
|
217
|
+
const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
|
|
218
|
+
const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
|
|
219
|
+
const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
|
|
220
|
+
if (path)
|
|
221
|
+
writeRepoServerProjectRoot(projectRoot, path);
|
|
222
|
+
return path;
|
|
223
|
+
} catch {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
165
227
|
function appendTaskFilterParams(url, filters) {
|
|
166
228
|
if (filters.assignee)
|
|
167
229
|
url.searchParams.set("assignee", filters.assignee);
|
|
@@ -194,12 +256,65 @@ function diagnosticMessage(payload) {
|
|
|
194
256
|
});
|
|
195
257
|
return messages.length > 0 ? messages.join("; ") : null;
|
|
196
258
|
}
|
|
259
|
+
var serverReachabilityCache = new Map;
|
|
260
|
+
async function probeServerReachability(baseUrl, authToken) {
|
|
261
|
+
try {
|
|
262
|
+
const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
|
|
263
|
+
headers: mergeHeaders(undefined, authToken),
|
|
264
|
+
signal: AbortSignal.timeout(1500)
|
|
265
|
+
});
|
|
266
|
+
return response.ok;
|
|
267
|
+
} catch {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function cachedServerReachability(projectRoot, baseUrl, authToken) {
|
|
272
|
+
const key = resolve2(projectRoot);
|
|
273
|
+
const cached = serverReachabilityCache.get(key);
|
|
274
|
+
if (cached)
|
|
275
|
+
return cached;
|
|
276
|
+
const probe = probeServerReachability(baseUrl, authToken);
|
|
277
|
+
serverReachabilityCache.set(key, probe);
|
|
278
|
+
return probe;
|
|
279
|
+
}
|
|
280
|
+
function describeSelectedServer(projectRoot, server) {
|
|
281
|
+
try {
|
|
282
|
+
const selected = resolveSelectedConnection(projectRoot);
|
|
283
|
+
if (selected) {
|
|
284
|
+
return {
|
|
285
|
+
alias: selected.alias,
|
|
286
|
+
target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
} catch {}
|
|
290
|
+
return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
|
|
291
|
+
}
|
|
292
|
+
async function buildServerFailureContext(projectRoot, server) {
|
|
293
|
+
const { alias, target } = describeSelectedServer(projectRoot, server);
|
|
294
|
+
const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
|
|
295
|
+
const reachability = reachable ? "server is reachable" : "server is unreachable";
|
|
296
|
+
return {
|
|
297
|
+
contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
|
|
298
|
+
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
299
|
+
};
|
|
300
|
+
}
|
|
197
301
|
async function requestServerJson(context, pathname, init = {}) {
|
|
198
302
|
const server = await ensureServerForCli(context.projectRoot);
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
headers
|
|
202
|
-
});
|
|
303
|
+
const headers = mergeHeaders(init.headers, server.authToken);
|
|
304
|
+
if (server.serverProjectRoot)
|
|
305
|
+
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
306
|
+
reportServerPhase(`${(init.method ?? "GET").toUpperCase()} ${pathname.split("?")[0]}\u2026`);
|
|
307
|
+
let response;
|
|
308
|
+
try {
|
|
309
|
+
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
310
|
+
...init,
|
|
311
|
+
headers
|
|
312
|
+
});
|
|
313
|
+
} catch (error) {
|
|
314
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
315
|
+
throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
|
|
316
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
317
|
+
}
|
|
203
318
|
const text = await response.text();
|
|
204
319
|
const payload = text.trim().length > 0 ? (() => {
|
|
205
320
|
try {
|
|
@@ -211,7 +326,9 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
211
326
|
if (!response.ok) {
|
|
212
327
|
const diagnostics = diagnosticMessage(payload);
|
|
213
328
|
const detail = diagnostics ?? (text || response.statusText);
|
|
214
|
-
|
|
329
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
330
|
+
throw new CliError(`Rig server request failed (${response.status}): ${detail}
|
|
331
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
215
332
|
}
|
|
216
333
|
return payload;
|
|
217
334
|
}
|
|
@@ -220,7 +337,7 @@ async function listWorkspaceTasksViaServer(context, filters = {}) {
|
|
|
220
337
|
appendTaskFilterParams(url, filters);
|
|
221
338
|
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
222
339
|
if (!Array.isArray(payload)) {
|
|
223
|
-
throw new
|
|
340
|
+
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`." });
|
|
224
341
|
}
|
|
225
342
|
return payload.flatMap((entry) => entry && typeof entry === "object" && !Array.isArray(entry) ? [entry] : []);
|
|
226
343
|
}
|
|
@@ -236,7 +353,7 @@ async function selectNextWorkspaceTaskViaServer(context, filters = {}) {
|
|
|
236
353
|
appendTaskFilterParams(url, filters);
|
|
237
354
|
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
238
355
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
239
|
-
throw new
|
|
356
|
+
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." });
|
|
240
357
|
}
|
|
241
358
|
const record = payload;
|
|
242
359
|
const rawTask = record.task;
|
|
@@ -275,32 +392,50 @@ async function registerProjectViaServer(context, input) {
|
|
|
275
392
|
function sleep(ms) {
|
|
276
393
|
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
277
394
|
}
|
|
395
|
+
function isRetryableProjectRootSwitchError(error) {
|
|
396
|
+
if (!(error instanceof Error))
|
|
397
|
+
return false;
|
|
398
|
+
const message = error.message.toLowerCase();
|
|
399
|
+
return message.includes("rig server request failed (401): auth-required") || message.includes("rig server request failed (401): github-token-required") || message.includes("rig server request failed (502)") || message.includes("rig server request failed (503)") || message.includes("bad gateway") || message.includes("fetch failed") || message.includes("econnrefused") || message.includes("connection refused");
|
|
400
|
+
}
|
|
278
401
|
async function switchServerProjectRootViaServer(context, projectRoot, options = {}) {
|
|
279
|
-
const switched = await requestServerJson(context, "/api/server/project-root", {
|
|
280
|
-
method: "POST",
|
|
281
|
-
headers: { "content-type": "application/json" },
|
|
282
|
-
body: JSON.stringify({ projectRoot })
|
|
283
|
-
});
|
|
284
402
|
const timeoutMs = options.timeoutMs ?? 30000;
|
|
285
403
|
const pollMs = options.pollMs ?? 1000;
|
|
286
404
|
const deadline = Date.now() + timeoutMs;
|
|
287
405
|
let lastError;
|
|
406
|
+
let switched = null;
|
|
288
407
|
while (Date.now() < deadline) {
|
|
289
408
|
try {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
lastError = `server projectRoot=${String(record.projectRoot ?? "unknown")}`;
|
|
297
|
-
}
|
|
409
|
+
switched = await requestServerJson(context, "/api/server/project-root", {
|
|
410
|
+
method: "POST",
|
|
411
|
+
headers: { "content-type": "application/json" },
|
|
412
|
+
body: JSON.stringify({ projectRoot })
|
|
413
|
+
});
|
|
414
|
+
break;
|
|
298
415
|
} catch (error) {
|
|
299
416
|
lastError = error;
|
|
417
|
+
if (!isRetryableProjectRootSwitchError(error))
|
|
418
|
+
throw error;
|
|
419
|
+
await sleep(pollMs);
|
|
300
420
|
}
|
|
301
|
-
await sleep(pollMs);
|
|
302
421
|
}
|
|
303
|
-
|
|
422
|
+
if (!switched) {
|
|
423
|
+
throw new CliError(`Rig server did not accept project-root switch to ${projectRoot} before timeout (${lastError instanceof Error ? lastError.message : String(lastError ?? "no response")}).`, 1);
|
|
424
|
+
}
|
|
425
|
+
const record = switched && typeof switched === "object" && !Array.isArray(switched) ? switched : {};
|
|
426
|
+
if (record.ok === true) {
|
|
427
|
+
writeRepoServerProjectRoot(context.projectRoot, projectRoot);
|
|
428
|
+
return { ok: true, switched: record };
|
|
429
|
+
}
|
|
430
|
+
throw new CliError(`Rig server rejected project-root scope ${projectRoot}: ${String(record.message ?? record.error ?? "unknown error")}`, 1);
|
|
431
|
+
}
|
|
432
|
+
async function listRunsViaServer(context, options = {}) {
|
|
433
|
+
const url = new URL("http://rig.local/api/runs");
|
|
434
|
+
if (options.limit !== undefined)
|
|
435
|
+
url.searchParams.set("limit", String(options.limit));
|
|
436
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
437
|
+
const runs = Array.isArray(payload) ? payload : payload && typeof payload === "object" && !Array.isArray(payload) && Array.isArray(payload.runs) ? payload.runs : [];
|
|
438
|
+
return runs.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
|
|
304
439
|
}
|
|
305
440
|
async function getRunDetailsViaServer(context, runId) {
|
|
306
441
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}`);
|
|
@@ -315,6 +450,69 @@ async function getRunLogsViaServer(context, runId, options = {}) {
|
|
|
315
450
|
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
316
451
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
317
452
|
}
|
|
453
|
+
async function getRunTimelineViaServer(context, runId, options = {}) {
|
|
454
|
+
const url = new URL(`http://rig.local/api/runs/${encodeURIComponent(runId)}/timeline`);
|
|
455
|
+
if (options.limit !== undefined)
|
|
456
|
+
url.searchParams.set("limit", String(options.limit));
|
|
457
|
+
if (options.cursor)
|
|
458
|
+
url.searchParams.set("cursor", options.cursor);
|
|
459
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
460
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { entries: [] };
|
|
461
|
+
}
|
|
462
|
+
async function ensureTaskLabelsViaServer(context) {
|
|
463
|
+
const payload = await requestServerJson(context, "/api/workspace/task-labels", { method: "POST" });
|
|
464
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
465
|
+
}
|
|
466
|
+
async function listGitHubProjectsViaServer(context, owner) {
|
|
467
|
+
const url = new URL("http://rig.local/api/github/projects");
|
|
468
|
+
url.searchParams.set("owner", owner);
|
|
469
|
+
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
470
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { projects: [] };
|
|
471
|
+
}
|
|
472
|
+
async function getGitHubProjectStatusFieldViaServer(context, projectId) {
|
|
473
|
+
const payload = await requestServerJson(context, `/api/github/projects/${encodeURIComponent(projectId)}/status-field`);
|
|
474
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
475
|
+
}
|
|
476
|
+
async function updateWorkspaceTaskViaServer(context, input) {
|
|
477
|
+
const payload = await requestServerJson(context, "/api/tasks/update", {
|
|
478
|
+
method: "POST",
|
|
479
|
+
headers: { "content-type": "application/json" },
|
|
480
|
+
body: JSON.stringify(input)
|
|
481
|
+
});
|
|
482
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
483
|
+
}
|
|
484
|
+
var RESUMABLE_RUN_STATUSES = new Set([
|
|
485
|
+
"created",
|
|
486
|
+
"preparing",
|
|
487
|
+
"running",
|
|
488
|
+
"validating",
|
|
489
|
+
"reviewing",
|
|
490
|
+
"stopped",
|
|
491
|
+
"failed",
|
|
492
|
+
"needs-attention",
|
|
493
|
+
"needs_attention"
|
|
494
|
+
]);
|
|
495
|
+
async function resumeRunViaServer(context, runId, options) {
|
|
496
|
+
let targetRunId = runId?.trim() || null;
|
|
497
|
+
if (!targetRunId) {
|
|
498
|
+
const candidates = (await listRunsViaServer(context)).filter((run) => RESUMABLE_RUN_STATUSES.has(String(run.status ?? ""))).sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")));
|
|
499
|
+
targetRunId = typeof candidates[0]?.runId === "string" ? candidates[0].runId : null;
|
|
500
|
+
}
|
|
501
|
+
if (!targetRunId) {
|
|
502
|
+
throw new CliError(options.restart ? "No run is available to restart." : "No resumable run is available.", 2, { hint: "List runs with `rig run list`, then pass an explicit id: `rig run resume <run-id>`." });
|
|
503
|
+
}
|
|
504
|
+
const payload = await requestServerJson(context, "/api/runs/resume", {
|
|
505
|
+
method: "POST",
|
|
506
|
+
headers: { "content-type": "application/json" },
|
|
507
|
+
body: JSON.stringify({ runId: targetRunId, createdAt: new Date().toISOString(), restart: options.restart })
|
|
508
|
+
});
|
|
509
|
+
const record = payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
510
|
+
if (record.ok === false) {
|
|
511
|
+
const message = typeof record.error === "string" && record.error.trim() ? record.error : "run resume failed";
|
|
512
|
+
throw new CliError(`${options.restart ? "restart" : "resume"} failed for ${targetRunId}: ${message}`, 1);
|
|
513
|
+
}
|
|
514
|
+
return { ok: true, runId: targetRunId, ...record };
|
|
515
|
+
}
|
|
318
516
|
async function stopRunViaServer(context, runId) {
|
|
319
517
|
const payload = await requestServerJson(context, "/api/runs/stop", {
|
|
320
518
|
method: "POST",
|
|
@@ -331,6 +529,72 @@ async function steerRunViaServer(context, runId, message) {
|
|
|
331
529
|
});
|
|
332
530
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
333
531
|
}
|
|
532
|
+
async function getRunPiSessionViaServer(context, runId) {
|
|
533
|
+
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi`);
|
|
534
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
535
|
+
}
|
|
536
|
+
async function getRunPiMessagesViaServer(context, runId) {
|
|
537
|
+
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/messages`);
|
|
538
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { messages: [] };
|
|
539
|
+
}
|
|
540
|
+
async function getRunPiStatusViaServer(context, runId) {
|
|
541
|
+
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/status`);
|
|
542
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
543
|
+
}
|
|
544
|
+
async function getRunPiCommandsViaServer(context, runId) {
|
|
545
|
+
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
|
|
546
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
|
|
547
|
+
}
|
|
548
|
+
async function getRunPiCapabilitiesViaServer(context, runId) {
|
|
549
|
+
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
|
|
550
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
|
|
551
|
+
}
|
|
552
|
+
async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior) {
|
|
553
|
+
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
|
|
554
|
+
method: "POST",
|
|
555
|
+
headers: { "content-type": "application/json" },
|
|
556
|
+
body: JSON.stringify({ text, streamingBehavior })
|
|
557
|
+
});
|
|
558
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
|
|
559
|
+
}
|
|
560
|
+
async function sendRunPiShellViaServer(context, runId, text) {
|
|
561
|
+
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/shell`, {
|
|
562
|
+
method: "POST",
|
|
563
|
+
headers: { "content-type": "application/json" },
|
|
564
|
+
body: JSON.stringify({ text })
|
|
565
|
+
});
|
|
566
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
|
|
567
|
+
}
|
|
568
|
+
async function runRunPiCommandViaServer(context, runId, text) {
|
|
569
|
+
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands/run`, {
|
|
570
|
+
method: "POST",
|
|
571
|
+
headers: { "content-type": "application/json" },
|
|
572
|
+
body: JSON.stringify({ text })
|
|
573
|
+
});
|
|
574
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { type: "done" };
|
|
575
|
+
}
|
|
576
|
+
async function respondRunPiExtensionUiViaServer(context, runId, requestId, valueOrCancel) {
|
|
577
|
+
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/extension-ui/respond`, {
|
|
578
|
+
method: "POST",
|
|
579
|
+
headers: { "content-type": "application/json" },
|
|
580
|
+
body: JSON.stringify({ requestId, ...valueOrCancel })
|
|
581
|
+
});
|
|
582
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
|
|
583
|
+
}
|
|
584
|
+
async function abortRunPiViaServer(context, runId) {
|
|
585
|
+
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/abort`, { method: "POST" });
|
|
586
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { aborted: true };
|
|
587
|
+
}
|
|
588
|
+
async function buildRunPiEventsWebSocketUrl(context, runId) {
|
|
589
|
+
const server = await ensureServerForCli(context.projectRoot);
|
|
590
|
+
const url = new URL(`${server.baseUrl.replace(/\/+$/, "")}/api/runs/${encodeURIComponent(runId)}/pi/events`);
|
|
591
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
592
|
+
if (server.authToken)
|
|
593
|
+
url.searchParams.set("token", server.authToken);
|
|
594
|
+
if (server.serverProjectRoot)
|
|
595
|
+
url.searchParams.set("rigProjectRoot", server.serverProjectRoot);
|
|
596
|
+
return url.toString();
|
|
597
|
+
}
|
|
334
598
|
async function submitTaskRunViaServer(context, input) {
|
|
335
599
|
const isTaskRun = Boolean(input.taskId);
|
|
336
600
|
const endpoint = isTaskRun ? "/api/runs/task" : "/api/runs/adhoc";
|
|
@@ -354,29 +618,48 @@ async function submitTaskRunViaServer(context, input) {
|
|
|
354
618
|
})
|
|
355
619
|
});
|
|
356
620
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
357
|
-
throw new
|
|
621
|
+
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." });
|
|
358
622
|
}
|
|
359
623
|
const runId = payload.runId;
|
|
360
624
|
if (typeof runId !== "string" || runId.trim().length === 0) {
|
|
361
|
-
throw new
|
|
625
|
+
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." });
|
|
362
626
|
}
|
|
363
627
|
return { runId };
|
|
364
628
|
}
|
|
365
629
|
export {
|
|
630
|
+
updateWorkspaceTaskViaServer,
|
|
366
631
|
switchServerProjectRootViaServer,
|
|
367
632
|
submitTaskRunViaServer,
|
|
368
633
|
stopRunViaServer,
|
|
369
634
|
steerRunViaServer,
|
|
635
|
+
setServerPhaseListener,
|
|
370
636
|
setGitHubBearerTokenForCurrentProcess,
|
|
637
|
+
sendRunPiShellViaServer,
|
|
638
|
+
sendRunPiPromptViaServer,
|
|
371
639
|
selectNextWorkspaceTaskViaServer,
|
|
640
|
+
runRunPiCommandViaServer,
|
|
641
|
+
resumeRunViaServer,
|
|
642
|
+
respondRunPiExtensionUiViaServer,
|
|
372
643
|
requestServerJson,
|
|
373
644
|
registerProjectViaServer,
|
|
374
645
|
prepareRemoteCheckoutViaServer,
|
|
375
646
|
postGitHubTokenViaServer,
|
|
376
647
|
listWorkspaceTasksViaServer,
|
|
648
|
+
listRunsViaServer,
|
|
649
|
+
listGitHubProjectsViaServer,
|
|
377
650
|
getWorkspaceTaskViaServer,
|
|
651
|
+
getRunTimelineViaServer,
|
|
652
|
+
getRunPiStatusViaServer,
|
|
653
|
+
getRunPiSessionViaServer,
|
|
654
|
+
getRunPiMessagesViaServer,
|
|
655
|
+
getRunPiCommandsViaServer,
|
|
656
|
+
getRunPiCapabilitiesViaServer,
|
|
378
657
|
getRunLogsViaServer,
|
|
379
658
|
getRunDetailsViaServer,
|
|
659
|
+
getGitHubProjectStatusFieldViaServer,
|
|
380
660
|
getGitHubAuthStatusViaServer,
|
|
381
|
-
|
|
661
|
+
ensureTaskLabelsViaServer,
|
|
662
|
+
ensureServerForCli,
|
|
663
|
+
buildRunPiEventsWebSocketUrl,
|
|
664
|
+
abortRunPiViaServer
|
|
382
665
|
};
|