@h-rig/cli 0.0.6-alpha.63 → 0.0.6-alpha.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/rig.js +1349 -1599
- package/dist/src/commands/_connection-state.js +14 -5
- package/dist/src/commands/_doctor-checks.js +71 -11
- package/dist/src/commands/_help-catalog.js +99 -60
- package/dist/src/commands/_json-output.js +56 -0
- package/dist/src/commands/_operator-view.js +97 -784
- package/dist/src/commands/_parsers.js +18 -9
- package/dist/src/commands/_pi-frontend.js +96 -788
- package/dist/src/commands/_policy.js +12 -3
- package/dist/src/commands/_preflight.js +73 -13
- package/dist/src/commands/_run-driver-helpers.js +31 -5
- package/dist/src/commands/_run-replay.js +2 -2
- package/dist/src/commands/_server-client.js +76 -16
- package/dist/src/commands/_snapshot-upload.js +70 -10
- package/dist/src/commands/agent.js +21 -11
- package/dist/src/commands/browser.js +24 -15
- package/dist/src/commands/connect.js +22 -17
- package/dist/src/commands/dist.js +15 -6
- package/dist/src/commands/doctor.js +70 -10
- package/dist/src/commands/github.js +79 -19
- package/dist/src/commands/inbox.js +215 -131
- package/dist/src/commands/init.js +202 -35
- package/dist/src/commands/inspect.js +77 -17
- package/dist/src/commands/inspector.js +18 -9
- package/dist/src/commands/pi.js +18 -9
- package/dist/src/commands/plugin.js +19 -10
- package/dist/src/commands/profile-and-review.js +22 -13
- package/dist/src/commands/queue.js +19 -9
- package/dist/src/commands/remote.js +25 -16
- package/dist/src/commands/repo-git-harness.js +18 -9
- package/dist/src/commands/run.js +158 -805
- package/dist/src/commands/server.js +85 -25
- package/dist/src/commands/setup.js +73 -13
- package/dist/src/commands/stats.js +681 -0
- package/dist/src/commands/task-report-bug.js +24 -15
- package/dist/src/commands/task-run-driver.js +121 -49
- package/dist/src/commands/task.js +254 -893
- package/dist/src/commands/test.js +12 -3
- package/dist/src/commands/workspace.js +14 -5
- package/dist/src/commands.js +1339 -1650
- package/dist/src/index.js +1349 -1599
- package/dist/src/launcher.js +72 -10
- package/dist/src/runner.js +14 -5
- package/package.json +10 -7
- package/dist/src/commands/_pi-remote-session.js +0 -771
- package/dist/src/commands/_pi-worker-bridge-extension.js +0 -834
|
@@ -5,10 +5,19 @@ import { resolve } from "path";
|
|
|
5
5
|
|
|
6
6
|
// packages/cli/src/runner.ts
|
|
7
7
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
8
|
-
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
8
|
+
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
9
9
|
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
10
10
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
11
|
-
|
|
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
|
+
}
|
|
12
21
|
function formatCommand(parts) {
|
|
13
22
|
return parts.map((part) => /[^a-zA-Z0-9_./:-]/.test(part) ? JSON.stringify(part) : part).join(" ");
|
|
14
23
|
}
|
|
@@ -67,7 +76,7 @@ async function enforceNativeCommandPolicy(context, args, options) {
|
|
|
67
76
|
appendControlledBashAudit(context, mode, command, args, matchedRuleIds, action === "block" ? "blocked" : action === "warn" ? "warn" : undefined);
|
|
68
77
|
}
|
|
69
78
|
if (action === "block") {
|
|
70
|
-
throw new
|
|
79
|
+
throw new CliError(`Policy blocked command: ${command}`, 126, { hint: "Review rig/policy/policy.json, or re-run with `--policy-mode observe` to log instead of block." });
|
|
71
80
|
}
|
|
72
81
|
}
|
|
73
82
|
export {
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/cli/src/runner.ts
|
|
3
3
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
4
|
-
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
4
|
+
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
5
5
|
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
6
6
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
7
|
-
|
|
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
|
+
}
|
|
8
17
|
|
|
9
18
|
// packages/cli/src/commands/_preflight.ts
|
|
10
19
|
import { ensureProjectMainFreshBeforeRun } from "@rig/runtime/control-plane/project-main-pre-run-sync";
|
|
@@ -31,7 +40,7 @@ function readJsonFile(path) {
|
|
|
31
40
|
try {
|
|
32
41
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
33
42
|
} catch (error) {
|
|
34
|
-
throw new
|
|
43
|
+
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>`." });
|
|
35
44
|
}
|
|
36
45
|
}
|
|
37
46
|
function writeJsonFile(path, value) {
|
|
@@ -95,7 +104,7 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
95
104
|
const global = readGlobalConnections(options);
|
|
96
105
|
const connection = global.connections[repo.selected];
|
|
97
106
|
if (!connection) {
|
|
98
|
-
throw new
|
|
107
|
+
throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
99
108
|
}
|
|
100
109
|
return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
|
|
101
110
|
}
|
|
@@ -171,7 +180,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
171
180
|
};
|
|
172
181
|
} catch (error) {
|
|
173
182
|
if (error instanceof Error) {
|
|
174
|
-
throw new
|
|
183
|
+
throw new CliError(error.message, 1);
|
|
175
184
|
}
|
|
176
185
|
throw error;
|
|
177
186
|
}
|
|
@@ -221,15 +230,64 @@ function diagnosticMessage(payload) {
|
|
|
221
230
|
});
|
|
222
231
|
return messages.length > 0 ? messages.join("; ") : null;
|
|
223
232
|
}
|
|
233
|
+
var serverReachabilityCache = new Map;
|
|
234
|
+
async function probeServerReachability(baseUrl, authToken) {
|
|
235
|
+
try {
|
|
236
|
+
const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
|
|
237
|
+
headers: mergeHeaders(undefined, authToken),
|
|
238
|
+
signal: AbortSignal.timeout(1500)
|
|
239
|
+
});
|
|
240
|
+
return response.ok;
|
|
241
|
+
} catch {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function cachedServerReachability(projectRoot, baseUrl, authToken) {
|
|
246
|
+
const key = resolve2(projectRoot);
|
|
247
|
+
const cached = serverReachabilityCache.get(key);
|
|
248
|
+
if (cached)
|
|
249
|
+
return cached;
|
|
250
|
+
const probe = probeServerReachability(baseUrl, authToken);
|
|
251
|
+
serverReachabilityCache.set(key, probe);
|
|
252
|
+
return probe;
|
|
253
|
+
}
|
|
254
|
+
function describeSelectedServer(projectRoot, server) {
|
|
255
|
+
try {
|
|
256
|
+
const selected = resolveSelectedConnection(projectRoot);
|
|
257
|
+
if (selected) {
|
|
258
|
+
return {
|
|
259
|
+
alias: selected.alias,
|
|
260
|
+
target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
} catch {}
|
|
264
|
+
return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
|
|
265
|
+
}
|
|
266
|
+
async function buildServerFailureContext(projectRoot, server) {
|
|
267
|
+
const { alias, target } = describeSelectedServer(projectRoot, server);
|
|
268
|
+
const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
|
|
269
|
+
const reachability = reachable ? "server is reachable" : "server is unreachable";
|
|
270
|
+
return {
|
|
271
|
+
contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
|
|
272
|
+
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
273
|
+
};
|
|
274
|
+
}
|
|
224
275
|
async function requestServerJson(context, pathname, init = {}) {
|
|
225
276
|
const server = await ensureServerForCli(context.projectRoot);
|
|
226
277
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
227
278
|
if (server.serverProjectRoot)
|
|
228
279
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
280
|
+
let response;
|
|
281
|
+
try {
|
|
282
|
+
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
283
|
+
...init,
|
|
284
|
+
headers
|
|
285
|
+
});
|
|
286
|
+
} catch (error) {
|
|
287
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
288
|
+
throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
|
|
289
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
290
|
+
}
|
|
233
291
|
const text = await response.text();
|
|
234
292
|
const payload = text.trim().length > 0 ? (() => {
|
|
235
293
|
try {
|
|
@@ -241,7 +299,9 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
241
299
|
if (!response.ok) {
|
|
242
300
|
const diagnostics = diagnosticMessage(payload);
|
|
243
301
|
const detail = diagnostics ?? (text || response.statusText);
|
|
244
|
-
|
|
302
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
303
|
+
throw new CliError(`Rig server request failed (${response.status}): ${detail}
|
|
304
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
245
305
|
}
|
|
246
306
|
return payload;
|
|
247
307
|
}
|
|
@@ -411,9 +471,9 @@ async function runFastTaskRunPreflight(context, options = {}) {
|
|
|
411
471
|
if (failures.length > 0) {
|
|
412
472
|
const summary = failures.map((check) => `${check.label}${check.detail ? `: ${check.detail}` : ""}`).join("; ");
|
|
413
473
|
if (failures.some((check) => check.id === "duplicate-active-run") && taskId) {
|
|
414
|
-
throw new
|
|
474
|
+
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>\`.` });
|
|
415
475
|
}
|
|
416
|
-
throw new
|
|
476
|
+
throw new CliError(`Task run preflight failed: ${summary}`, 1, { hint: "Run `rig doctor` to diagnose, then retry `rig task run`." });
|
|
417
477
|
}
|
|
418
478
|
return { ok: true, checks };
|
|
419
479
|
}
|
|
@@ -430,7 +490,7 @@ async function runProjectMainSyncPreflight(context, options) {
|
|
|
430
490
|
runBootstrap: async () => {
|
|
431
491
|
const bootstrap = await context.runCommand(["bun", "run", "bootstrap"]);
|
|
432
492
|
if (bootstrap.exitCode !== 0) {
|
|
433
|
-
throw new
|
|
493
|
+
throw new CliError(bootstrap.stderr || bootstrap.stdout || "bun run bootstrap failed during project pre-run sync", bootstrap.exitCode || 1);
|
|
434
494
|
}
|
|
435
495
|
}
|
|
436
496
|
});
|
|
@@ -5,19 +5,29 @@ import { resolve as resolve3 } from "path";
|
|
|
5
5
|
|
|
6
6
|
// packages/cli/src/runner.ts
|
|
7
7
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
8
|
-
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
8
|
+
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
9
9
|
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
10
10
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
11
|
-
|
|
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
|
+
}
|
|
12
21
|
|
|
13
22
|
// packages/cli/src/commands/_run-driver-helpers.ts
|
|
14
23
|
import {
|
|
15
|
-
|
|
24
|
+
appendRunJournalEvent,
|
|
16
25
|
appendRunLogEntry,
|
|
17
26
|
appendRunTimelineEntry,
|
|
18
27
|
patchAuthorityRunRecord,
|
|
19
28
|
readAuthorityRun as readAuthorityRun2,
|
|
20
29
|
resolveAuthorityRunDir,
|
|
30
|
+
setAuthorityRunStatus,
|
|
21
31
|
writeJsonFile
|
|
22
32
|
} from "@rig/runtime/control-plane/authority-files";
|
|
23
33
|
import { taskLookup } from "@rig/runtime/control-plane/native/task-ops";
|
|
@@ -67,7 +77,17 @@ function readLatestBeadRecord(projectRoot, taskId) {
|
|
|
67
77
|
function patchAuthorityRun(projectRoot, runId, patch) {
|
|
68
78
|
const next = patchAuthorityRunRecord(projectRoot, runId, patch);
|
|
69
79
|
if (!next) {
|
|
70
|
-
throw new
|
|
80
|
+
throw new CliError(`Run not found: ${runId}`, 2, { hint: "Run `rig run list` to see recorded runs." });
|
|
81
|
+
}
|
|
82
|
+
return next;
|
|
83
|
+
}
|
|
84
|
+
function setRunStatusOrFail(projectRoot, runId, to, options = {}) {
|
|
85
|
+
const next = setAuthorityRunStatus(projectRoot, runId, to, {
|
|
86
|
+
...options,
|
|
87
|
+
actor: { kind: "agent" }
|
|
88
|
+
});
|
|
89
|
+
if (!next) {
|
|
90
|
+
throw new CliError(`Run not found: ${runId}`, 2, { hint: "Run `rig run list` to see recorded runs." });
|
|
71
91
|
}
|
|
72
92
|
return next;
|
|
73
93
|
}
|
|
@@ -172,7 +192,12 @@ function emitServerRunEvent(event) {
|
|
|
172
192
|
console.log(`__RIG_RUN_EVENT__${JSON.stringify({ ...event, at: new Date().toISOString() })}`);
|
|
173
193
|
if (runEventSinkProjectRoot) {
|
|
174
194
|
try {
|
|
175
|
-
|
|
195
|
+
if (event.type === "status" || event.type === "completed" || event.type === "failed") {
|
|
196
|
+
appendRunJournalEvent(runEventSinkProjectRoot, event.runId, {
|
|
197
|
+
type: "timeline-entry",
|
|
198
|
+
payload: { kind: "driver-event", ...event }
|
|
199
|
+
});
|
|
200
|
+
}
|
|
176
201
|
} catch {}
|
|
177
202
|
ringServerRunDoorbell(event.runId);
|
|
178
203
|
}
|
|
@@ -306,6 +331,7 @@ function renderSourceScopeValidation(task, validation) {
|
|
|
306
331
|
export {
|
|
307
332
|
touchAuthorityRun,
|
|
308
333
|
startRunAction,
|
|
334
|
+
setRunStatusOrFail,
|
|
309
335
|
patchAuthorityRun,
|
|
310
336
|
emitServerRunEvent,
|
|
311
337
|
configureRunEventSink,
|
|
@@ -5,7 +5,7 @@ import { join, resolve } from "path";
|
|
|
5
5
|
import {
|
|
6
6
|
readAuthorityRun,
|
|
7
7
|
readJsonlFile,
|
|
8
|
-
|
|
8
|
+
runJournalPath
|
|
9
9
|
} from "@rig/runtime/control-plane/authority-files";
|
|
10
10
|
function asRecord(value) {
|
|
11
11
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
@@ -103,7 +103,7 @@ function resolveRunSessionFile(record) {
|
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
function buildRunReplay(projectRoot, runId, options = {}) {
|
|
106
|
-
const logPath =
|
|
106
|
+
const logPath = runJournalPath(projectRoot, runId);
|
|
107
107
|
const record = readAuthorityRun(projectRoot, runId);
|
|
108
108
|
const lifecycleEntries = readJsonlFile(logPath).map((entry) => asRecord(entry)).filter((entry) => entry !== null);
|
|
109
109
|
const merged = lifecycleEntries.map((entry) => ({
|
|
@@ -5,10 +5,19 @@ import { resolve as resolve2 } from "path";
|
|
|
5
5
|
|
|
6
6
|
// packages/cli/src/runner.ts
|
|
7
7
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
8
|
-
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
8
|
+
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
9
9
|
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
10
10
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
11
|
-
|
|
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
|
+
}
|
|
12
21
|
|
|
13
22
|
// packages/cli/src/commands/_server-client.ts
|
|
14
23
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
@@ -35,7 +44,7 @@ function readJsonFile(path) {
|
|
|
35
44
|
try {
|
|
36
45
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
37
46
|
} catch (error) {
|
|
38
|
-
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>`." });
|
|
39
48
|
}
|
|
40
49
|
}
|
|
41
50
|
function writeJsonFile(path, value) {
|
|
@@ -99,7 +108,7 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
99
108
|
const global = readGlobalConnections(options);
|
|
100
109
|
const connection = global.connections[repo.selected];
|
|
101
110
|
if (!connection) {
|
|
102
|
-
throw new
|
|
111
|
+
throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
103
112
|
}
|
|
104
113
|
return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
|
|
105
114
|
}
|
|
@@ -176,7 +185,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
176
185
|
};
|
|
177
186
|
} catch (error) {
|
|
178
187
|
if (error instanceof Error) {
|
|
179
|
-
throw new
|
|
188
|
+
throw new CliError(error.message, 1);
|
|
180
189
|
}
|
|
181
190
|
throw error;
|
|
182
191
|
}
|
|
@@ -236,15 +245,64 @@ function diagnosticMessage(payload) {
|
|
|
236
245
|
});
|
|
237
246
|
return messages.length > 0 ? messages.join("; ") : null;
|
|
238
247
|
}
|
|
248
|
+
var serverReachabilityCache = new Map;
|
|
249
|
+
async function probeServerReachability(baseUrl, authToken) {
|
|
250
|
+
try {
|
|
251
|
+
const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
|
|
252
|
+
headers: mergeHeaders(undefined, authToken),
|
|
253
|
+
signal: AbortSignal.timeout(1500)
|
|
254
|
+
});
|
|
255
|
+
return response.ok;
|
|
256
|
+
} catch {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function cachedServerReachability(projectRoot, baseUrl, authToken) {
|
|
261
|
+
const key = resolve2(projectRoot);
|
|
262
|
+
const cached = serverReachabilityCache.get(key);
|
|
263
|
+
if (cached)
|
|
264
|
+
return cached;
|
|
265
|
+
const probe = probeServerReachability(baseUrl, authToken);
|
|
266
|
+
serverReachabilityCache.set(key, probe);
|
|
267
|
+
return probe;
|
|
268
|
+
}
|
|
269
|
+
function describeSelectedServer(projectRoot, server) {
|
|
270
|
+
try {
|
|
271
|
+
const selected = resolveSelectedConnection(projectRoot);
|
|
272
|
+
if (selected) {
|
|
273
|
+
return {
|
|
274
|
+
alias: selected.alias,
|
|
275
|
+
target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
} catch {}
|
|
279
|
+
return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
|
|
280
|
+
}
|
|
281
|
+
async function buildServerFailureContext(projectRoot, server) {
|
|
282
|
+
const { alias, target } = describeSelectedServer(projectRoot, server);
|
|
283
|
+
const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
|
|
284
|
+
const reachability = reachable ? "server is reachable" : "server is unreachable";
|
|
285
|
+
return {
|
|
286
|
+
contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
|
|
287
|
+
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
288
|
+
};
|
|
289
|
+
}
|
|
239
290
|
async function requestServerJson(context, pathname, init = {}) {
|
|
240
291
|
const server = await ensureServerForCli(context.projectRoot);
|
|
241
292
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
242
293
|
if (server.serverProjectRoot)
|
|
243
294
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
295
|
+
let response;
|
|
296
|
+
try {
|
|
297
|
+
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
298
|
+
...init,
|
|
299
|
+
headers
|
|
300
|
+
});
|
|
301
|
+
} catch (error) {
|
|
302
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
303
|
+
throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
|
|
304
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
305
|
+
}
|
|
248
306
|
const text = await response.text();
|
|
249
307
|
const payload = text.trim().length > 0 ? (() => {
|
|
250
308
|
try {
|
|
@@ -256,7 +314,9 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
256
314
|
if (!response.ok) {
|
|
257
315
|
const diagnostics = diagnosticMessage(payload);
|
|
258
316
|
const detail = diagnostics ?? (text || response.statusText);
|
|
259
|
-
|
|
317
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
318
|
+
throw new CliError(`Rig server request failed (${response.status}): ${detail}
|
|
319
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
260
320
|
}
|
|
261
321
|
return payload;
|
|
262
322
|
}
|
|
@@ -265,7 +325,7 @@ async function listWorkspaceTasksViaServer(context, filters = {}) {
|
|
|
265
325
|
appendTaskFilterParams(url, filters);
|
|
266
326
|
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
267
327
|
if (!Array.isArray(payload)) {
|
|
268
|
-
throw new
|
|
328
|
+
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`." });
|
|
269
329
|
}
|
|
270
330
|
return payload.flatMap((entry) => entry && typeof entry === "object" && !Array.isArray(entry) ? [entry] : []);
|
|
271
331
|
}
|
|
@@ -281,7 +341,7 @@ async function selectNextWorkspaceTaskViaServer(context, filters = {}) {
|
|
|
281
341
|
appendTaskFilterParams(url, filters);
|
|
282
342
|
const payload = await requestServerJson(context, `${url.pathname}${url.search}`);
|
|
283
343
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
284
|
-
throw new
|
|
344
|
+
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." });
|
|
285
345
|
}
|
|
286
346
|
const record = payload;
|
|
287
347
|
const rawTask = record.task;
|
|
@@ -348,14 +408,14 @@ async function switchServerProjectRootViaServer(context, projectRoot, options =
|
|
|
348
408
|
}
|
|
349
409
|
}
|
|
350
410
|
if (!switched) {
|
|
351
|
-
throw new
|
|
411
|
+
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);
|
|
352
412
|
}
|
|
353
413
|
const record = switched && typeof switched === "object" && !Array.isArray(switched) ? switched : {};
|
|
354
414
|
if (record.ok === true) {
|
|
355
415
|
writeRepoServerProjectRoot(context.projectRoot, projectRoot);
|
|
356
416
|
return { ok: true, switched: record };
|
|
357
417
|
}
|
|
358
|
-
throw new
|
|
418
|
+
throw new CliError(`Rig server rejected project-root scope ${projectRoot}: ${String(record.message ?? record.error ?? "unknown error")}`, 1);
|
|
359
419
|
}
|
|
360
420
|
async function listRunsViaServer(context, options = {}) {
|
|
361
421
|
const url = new URL("http://rig.local/api/runs");
|
|
@@ -514,11 +574,11 @@ async function submitTaskRunViaServer(context, input) {
|
|
|
514
574
|
})
|
|
515
575
|
});
|
|
516
576
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
517
|
-
throw new
|
|
577
|
+
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." });
|
|
518
578
|
}
|
|
519
579
|
const runId = payload.runId;
|
|
520
580
|
if (typeof runId !== "string" || runId.trim().length === 0) {
|
|
521
|
-
throw new
|
|
581
|
+
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." });
|
|
522
582
|
}
|
|
523
583
|
return { runId };
|
|
524
584
|
}
|
|
@@ -9,10 +9,19 @@ import { resolve as resolve2 } from "path";
|
|
|
9
9
|
|
|
10
10
|
// packages/cli/src/runner.ts
|
|
11
11
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
12
|
-
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
12
|
+
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
13
13
|
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
14
14
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
class CliError extends RuntimeCliError {
|
|
17
|
+
hint;
|
|
18
|
+
constructor(message, exitCode = 1, options = {}) {
|
|
19
|
+
super(message, exitCode);
|
|
20
|
+
if (options.hint?.trim()) {
|
|
21
|
+
this.hint = options.hint.trim();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
16
25
|
|
|
17
26
|
// packages/cli/src/commands/_server-client.ts
|
|
18
27
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
@@ -39,7 +48,7 @@ function readJsonFile(path) {
|
|
|
39
48
|
try {
|
|
40
49
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
41
50
|
} catch (error) {
|
|
42
|
-
throw new
|
|
51
|
+
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>`." });
|
|
43
52
|
}
|
|
44
53
|
}
|
|
45
54
|
function writeJsonFile(path, value) {
|
|
@@ -103,7 +112,7 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
103
112
|
const global = readGlobalConnections(options);
|
|
104
113
|
const connection = global.connections[repo.selected];
|
|
105
114
|
if (!connection) {
|
|
106
|
-
throw new
|
|
115
|
+
throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
107
116
|
}
|
|
108
117
|
return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
|
|
109
118
|
}
|
|
@@ -176,7 +185,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
176
185
|
};
|
|
177
186
|
} catch (error) {
|
|
178
187
|
if (error instanceof Error) {
|
|
179
|
-
throw new
|
|
188
|
+
throw new CliError(error.message, 1);
|
|
180
189
|
}
|
|
181
190
|
throw error;
|
|
182
191
|
}
|
|
@@ -226,15 +235,64 @@ function diagnosticMessage(payload) {
|
|
|
226
235
|
});
|
|
227
236
|
return messages.length > 0 ? messages.join("; ") : null;
|
|
228
237
|
}
|
|
238
|
+
var serverReachabilityCache = new Map;
|
|
239
|
+
async function probeServerReachability(baseUrl, authToken) {
|
|
240
|
+
try {
|
|
241
|
+
const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
|
|
242
|
+
headers: mergeHeaders(undefined, authToken),
|
|
243
|
+
signal: AbortSignal.timeout(1500)
|
|
244
|
+
});
|
|
245
|
+
return response.ok;
|
|
246
|
+
} catch {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function cachedServerReachability(projectRoot, baseUrl, authToken) {
|
|
251
|
+
const key = resolve2(projectRoot);
|
|
252
|
+
const cached = serverReachabilityCache.get(key);
|
|
253
|
+
if (cached)
|
|
254
|
+
return cached;
|
|
255
|
+
const probe = probeServerReachability(baseUrl, authToken);
|
|
256
|
+
serverReachabilityCache.set(key, probe);
|
|
257
|
+
return probe;
|
|
258
|
+
}
|
|
259
|
+
function describeSelectedServer(projectRoot, server) {
|
|
260
|
+
try {
|
|
261
|
+
const selected = resolveSelectedConnection(projectRoot);
|
|
262
|
+
if (selected) {
|
|
263
|
+
return {
|
|
264
|
+
alias: selected.alias,
|
|
265
|
+
target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
} catch {}
|
|
269
|
+
return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
|
|
270
|
+
}
|
|
271
|
+
async function buildServerFailureContext(projectRoot, server) {
|
|
272
|
+
const { alias, target } = describeSelectedServer(projectRoot, server);
|
|
273
|
+
const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
|
|
274
|
+
const reachability = reachable ? "server is reachable" : "server is unreachable";
|
|
275
|
+
return {
|
|
276
|
+
contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
|
|
277
|
+
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
278
|
+
};
|
|
279
|
+
}
|
|
229
280
|
async function requestServerJson(context, pathname, init = {}) {
|
|
230
281
|
const server = await ensureServerForCli(context.projectRoot);
|
|
231
282
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
232
283
|
if (server.serverProjectRoot)
|
|
233
284
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
285
|
+
let response;
|
|
286
|
+
try {
|
|
287
|
+
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
288
|
+
...init,
|
|
289
|
+
headers
|
|
290
|
+
});
|
|
291
|
+
} catch (error) {
|
|
292
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
293
|
+
throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
|
|
294
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
295
|
+
}
|
|
238
296
|
const text = await response.text();
|
|
239
297
|
const payload = text.trim().length > 0 ? (() => {
|
|
240
298
|
try {
|
|
@@ -246,7 +304,9 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
246
304
|
if (!response.ok) {
|
|
247
305
|
const diagnostics = diagnosticMessage(payload);
|
|
248
306
|
const detail = diagnostics ?? (text || response.statusText);
|
|
249
|
-
|
|
307
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
308
|
+
throw new CliError(`Rig server request failed (${response.status}): ${detail}
|
|
309
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
250
310
|
}
|
|
251
311
|
return payload;
|
|
252
312
|
}
|