@h-rig/cli 0.0.6-alpha.1 → 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 +15742 -7203
- 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 +95 -244
- 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 -235
- 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 +214 -245
- package/dist/src/commands/github.d.ts +3 -0
- package/dist/src/commands/github.js +237 -171
- 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 +941 -654
- 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 +17732 -618
- package/dist/src/commands/server.d.ts +3 -0
- package/dist/src/commands/server.js +166 -285
- package/dist/src/commands/setup.d.ts +16 -0
- package/dist/src/commands/setup.js +230 -267
- 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 +120 -1856
- package/dist/src/commands/task.d.ts +35 -0
- package/dist/src/commands/task.js +676 -1183
- 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 +16760 -7779
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +16880 -8223
- package/dist/src/launcher.d.ts +61 -0
- package/dist/src/launcher.js +81 -13
- 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 +19 -24
- package/dist/src/withMutedConsole.d.ts +2 -0
- package/package.json +21 -7
- package/dist/src/commands/_authority-runs.js +0 -110
- package/dist/src/commands/_operator-view.js +0 -322
- package/dist/src/commands/_preflight.js +0 -460
- package/dist/src/commands/_server-client.js +0 -364
- package/dist/src/commands/_snapshot-upload.js +0 -313
- 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
|
@@ -1,12 +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
|
-
import { PluginManager } from "@rig/runtime/control-plane/runtime/plugins";
|
|
7
|
-
import { loadRuntimeContextFromEnv } from "@rig/runtime/control-plane/runtime/context";
|
|
8
6
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
9
|
-
|
|
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
|
+
}
|
|
10
17
|
function takeFlag(args, flag) {
|
|
11
18
|
const rest = [];
|
|
12
19
|
let value = false;
|
|
@@ -27,7 +34,7 @@ function takeOption(args, option) {
|
|
|
27
34
|
if (current === option) {
|
|
28
35
|
const next = args[index + 1];
|
|
29
36
|
if (!next || next.startsWith("-")) {
|
|
30
|
-
throw new CliError(`Missing value for ${option}`);
|
|
37
|
+
throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
|
|
31
38
|
}
|
|
32
39
|
value = next;
|
|
33
40
|
index += 1;
|
|
@@ -53,20 +60,846 @@ Usage: ${usage}`);
|
|
|
53
60
|
return taskId;
|
|
54
61
|
}
|
|
55
62
|
|
|
56
|
-
// packages/
|
|
57
|
-
import {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
63
|
+
// packages/runtime/src/control-plane/remote.ts
|
|
64
|
+
import { randomUUID } from "crypto";
|
|
65
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "fs";
|
|
66
|
+
import { homedir, tmpdir } from "os";
|
|
67
|
+
import { dirname as dirname2, join, resolve as resolve2 } from "path";
|
|
68
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
69
|
+
|
|
70
|
+
// packages/runtime/src/control-plane/server-paths.ts
|
|
71
|
+
import { existsSync } from "fs";
|
|
72
|
+
import { dirname, resolve, relative } from "path";
|
|
73
|
+
import { assertPathInsideRoot, assertSafeRunId, safePathSegment } from "@rig/shared/safe-identifiers";
|
|
74
|
+
function normalizeOptionalString(value) {
|
|
75
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
76
|
+
}
|
|
77
|
+
function resolveAuthorityPaths(projectRoot) {
|
|
78
|
+
const normalizedRoot = resolve(projectRoot);
|
|
79
|
+
const stateRoot = resolveAuthorityStateRoot(normalizedRoot);
|
|
80
|
+
const stateDir = resolveAuthorityStateDir(normalizedRoot);
|
|
81
|
+
return {
|
|
82
|
+
projectRoot: normalizedRoot,
|
|
83
|
+
harnessRoot: resolve(normalizedRoot, "rig"),
|
|
84
|
+
runsDir: resolve(stateRoot, "runs"),
|
|
85
|
+
remoteDir: resolve(stateRoot, "remote"),
|
|
86
|
+
stateDir,
|
|
87
|
+
remoteEndpointsPath: resolve(stateRoot, "remote", "endpoints.toml"),
|
|
88
|
+
remoteSecretsPath: resolve(stateDir, "remote-secrets.toml")
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function resolveAuthorityStateRoot(projectRoot) {
|
|
92
|
+
const normalizedRoot = resolve(projectRoot);
|
|
93
|
+
const taskWorkspace = normalizeOptionalString(process.env.RIG_TASK_WORKSPACE);
|
|
94
|
+
if (taskWorkspace) {
|
|
95
|
+
return resolve(taskWorkspace, ".rig");
|
|
96
|
+
}
|
|
97
|
+
const stateDir = normalizeOptionalString(process.env.RIG_STATE_DIR);
|
|
98
|
+
if (stateDir) {
|
|
99
|
+
return dirname(resolve(stateDir));
|
|
100
|
+
}
|
|
101
|
+
const logsDir = normalizeOptionalString(process.env.RIG_LOGS_DIR);
|
|
102
|
+
if (logsDir) {
|
|
103
|
+
return dirname(resolve(logsDir));
|
|
104
|
+
}
|
|
105
|
+
const sessionFile = normalizeOptionalString(process.env.RIG_SESSION_FILE);
|
|
106
|
+
if (sessionFile) {
|
|
107
|
+
return dirname(dirname(resolve(sessionFile)));
|
|
108
|
+
}
|
|
109
|
+
const projectStateRoot = resolve(normalizedRoot, ".rig");
|
|
110
|
+
if (existsSync(projectStateRoot)) {
|
|
111
|
+
return projectStateRoot;
|
|
112
|
+
}
|
|
113
|
+
return resolve(normalizedRoot, ".rig");
|
|
114
|
+
}
|
|
115
|
+
function resolveAuthorityStateDir(projectRoot) {
|
|
116
|
+
const explicit = normalizeOptionalString(process.env.RIG_STATE_DIR);
|
|
117
|
+
if (explicit) {
|
|
118
|
+
return resolve(explicit);
|
|
119
|
+
}
|
|
120
|
+
return resolve(resolveAuthorityStateRoot(projectRoot), "state");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// packages/runtime/src/control-plane/remote.ts
|
|
124
|
+
var DEFAULT_REMOTE_PORT = 7890;
|
|
125
|
+
var DEFAULT_RIG_BACKBONE_HOST = "where.rig-does.work";
|
|
126
|
+
var DEFAULT_RIG_RELAY_URL = `wss://${DEFAULT_RIG_BACKBONE_HOST}`;
|
|
127
|
+
var DEFAULT_RIG_REGISTRY_URL = `https://${DEFAULT_RIG_BACKBONE_HOST}/registry`;
|
|
128
|
+
var DEFAULT_TIMEOUTS = {
|
|
129
|
+
connectMs: 5000,
|
|
130
|
+
authMs: 5000,
|
|
131
|
+
requestMs: 30000,
|
|
132
|
+
subscriptionMs: 1e4
|
|
133
|
+
};
|
|
134
|
+
var REMOTES_CONFIG_PATH = join(homedir(), ".config", "rig", "remotes.toml");
|
|
135
|
+
|
|
136
|
+
class RemoteCliError extends Error {
|
|
137
|
+
code;
|
|
138
|
+
exitCode;
|
|
139
|
+
details;
|
|
140
|
+
constructor(code, message, exitCode = 1, details) {
|
|
141
|
+
super(message);
|
|
142
|
+
this.name = "RemoteCliError";
|
|
143
|
+
this.code = code;
|
|
144
|
+
this.exitCode = exitCode;
|
|
145
|
+
this.details = details;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function loadRemotesConfig(configPath = REMOTES_CONFIG_PATH) {
|
|
149
|
+
if (!existsSync2(configPath)) {
|
|
150
|
+
return { version: 1, remotes: {} };
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const content = readFileSync(configPath, "utf-8");
|
|
154
|
+
if (!content.trim()) {
|
|
155
|
+
return { version: 1, remotes: {} };
|
|
156
|
+
}
|
|
157
|
+
const parsed = parseToml(content);
|
|
158
|
+
return {
|
|
159
|
+
version: parsed.version ?? 1,
|
|
160
|
+
remotes: parsed.remotes ?? {}
|
|
161
|
+
};
|
|
162
|
+
} catch (error) {
|
|
163
|
+
throw new RemoteCliError("RIG_REMOTE_CONFIG_PARSE_ERROR", `Failed to parse remotes config at ${configPath}: ${error instanceof Error ? error.message : String(error)}`, 2, { configPath });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function saveRemotesConfig(config, configPath = REMOTES_CONFIG_PATH) {
|
|
167
|
+
mkdirSync(dirname2(configPath), { recursive: true });
|
|
168
|
+
writeFileSync(configPath, `${stringifyToml(config).trimEnd()}
|
|
169
|
+
`, "utf-8");
|
|
170
|
+
try {
|
|
171
|
+
chmodSync(configPath, 384);
|
|
172
|
+
} catch {}
|
|
173
|
+
}
|
|
174
|
+
function readTomlFile(path, fallback) {
|
|
175
|
+
if (!existsSync2(path))
|
|
176
|
+
return fallback;
|
|
177
|
+
const raw = readFileSync(path, "utf8");
|
|
178
|
+
if (!raw.trim())
|
|
179
|
+
return fallback;
|
|
180
|
+
return parseToml(raw);
|
|
181
|
+
}
|
|
182
|
+
function writeTomlFile(path, value) {
|
|
183
|
+
mkdirSync(dirname2(path), { recursive: true });
|
|
184
|
+
writeFileSync(path, `${stringifyToml(value).trimEnd()}
|
|
185
|
+
`, "utf8");
|
|
186
|
+
}
|
|
187
|
+
function normalizeStringArray(value) {
|
|
188
|
+
if (!Array.isArray(value))
|
|
189
|
+
return [];
|
|
190
|
+
return value.map((entry) => normalizeString(entry)).filter((entry) => entry !== "");
|
|
191
|
+
}
|
|
192
|
+
function normalizePort(value) {
|
|
193
|
+
const port = typeof value === "number" ? value : Number(value);
|
|
194
|
+
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : DEFAULT_REMOTE_PORT;
|
|
195
|
+
}
|
|
196
|
+
function loadRemoteEndpointsToml(projectRoot) {
|
|
197
|
+
const paths = resolveAuthorityPaths(projectRoot);
|
|
198
|
+
const parsed = readTomlFile(paths.remoteEndpointsPath, { version: 1, endpoints: {} });
|
|
199
|
+
return {
|
|
200
|
+
version: parsed.version ?? 1,
|
|
201
|
+
endpoints: parsed.endpoints ?? {}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function loadRemoteSecretsToml(projectRoot) {
|
|
205
|
+
const paths = resolveAuthorityPaths(projectRoot);
|
|
206
|
+
const parsed = readTomlFile(paths.remoteSecretsPath, { version: 1, secrets: {} });
|
|
207
|
+
return {
|
|
208
|
+
version: parsed.version ?? 1,
|
|
209
|
+
secrets: parsed.secrets ?? {}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function saveRemoteEndpointsToml(projectRoot, payload) {
|
|
213
|
+
writeTomlFile(resolveAuthorityPaths(projectRoot).remoteEndpointsPath, payload);
|
|
214
|
+
}
|
|
215
|
+
function saveRemoteSecretsToml(projectRoot, payload) {
|
|
216
|
+
const paths = resolveAuthorityPaths(projectRoot);
|
|
217
|
+
writeTomlFile(paths.remoteSecretsPath, payload);
|
|
218
|
+
try {
|
|
219
|
+
chmodSync(paths.remoteSecretsPath, 384);
|
|
220
|
+
} catch {}
|
|
221
|
+
}
|
|
222
|
+
function listAuthorityRemoteEndpoints(projectRoot) {
|
|
223
|
+
const endpoints = loadRemoteEndpointsToml(projectRoot).endpoints ?? {};
|
|
224
|
+
const secrets = loadRemoteSecretsToml(projectRoot).secrets ?? {};
|
|
225
|
+
return Object.values(endpoints).map((entry) => {
|
|
226
|
+
const token = secrets[entry.secret_ref] ?? "";
|
|
227
|
+
return {
|
|
228
|
+
id: entry.id,
|
|
229
|
+
alias: entry.alias,
|
|
230
|
+
host: entry.host,
|
|
231
|
+
port: normalizePort(entry.port),
|
|
232
|
+
token,
|
|
233
|
+
autoConnect: Boolean(entry.auto_connect),
|
|
234
|
+
addedAt: entry.created_at,
|
|
235
|
+
updatedAt: entry.updated_at,
|
|
236
|
+
lastConnectedAt: normalizeString(entry.last_connected_at) || null,
|
|
237
|
+
labels: normalizeStringArray(entry.labels),
|
|
238
|
+
capabilities: normalizeStringArray(entry.capabilities),
|
|
239
|
+
transport: normalizeString(entry.transport) || "websocket",
|
|
240
|
+
secretRef: entry.secret_ref
|
|
241
|
+
};
|
|
242
|
+
}).sort((left, right) => left.alias.localeCompare(right.alias));
|
|
243
|
+
}
|
|
244
|
+
function nextRemoteEndpointRecord(input) {
|
|
245
|
+
const timestamp = new Date().toISOString();
|
|
246
|
+
const secretRef = input.existing?.secret_ref ?? randomUUID();
|
|
247
|
+
return {
|
|
248
|
+
record: {
|
|
249
|
+
id: input.endpointId ?? input.existing?.id ?? randomUUID(),
|
|
250
|
+
alias: input.alias,
|
|
251
|
+
host: input.host,
|
|
252
|
+
port: normalizePort(input.port),
|
|
253
|
+
transport: normalizeString(input.transport) || normalizeString(input.existing?.transport) || "websocket",
|
|
254
|
+
auto_connect: input.autoConnect ?? input.existing?.auto_connect ?? false,
|
|
255
|
+
labels: input.labels ?? normalizeStringArray(input.existing?.labels),
|
|
256
|
+
capabilities: input.capabilities ?? normalizeStringArray(input.existing?.capabilities),
|
|
257
|
+
secret_ref: secretRef,
|
|
258
|
+
created_at: input.existing?.created_at ?? timestamp,
|
|
259
|
+
updated_at: timestamp,
|
|
260
|
+
last_connected_at: input.existing?.last_connected_at ?? null
|
|
261
|
+
},
|
|
262
|
+
secretRef
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function upsertAuthorityRemoteEndpoint(projectRoot, input) {
|
|
266
|
+
const endpointsToml = loadRemoteEndpointsToml(projectRoot);
|
|
267
|
+
const secretsToml = loadRemoteSecretsToml(projectRoot);
|
|
268
|
+
const existing = Object.values(endpointsToml.endpoints ?? {}).find((entry) => entry.alias === input.alias) ?? null;
|
|
269
|
+
const { record, secretRef } = nextRemoteEndpointRecord({
|
|
270
|
+
endpointId: input.endpointId,
|
|
271
|
+
existing,
|
|
272
|
+
alias: input.alias,
|
|
273
|
+
host: input.host,
|
|
274
|
+
port: input.port,
|
|
275
|
+
token: input.token,
|
|
276
|
+
autoConnect: input.autoConnect,
|
|
277
|
+
transport: input.transport,
|
|
278
|
+
labels: input.labels,
|
|
279
|
+
capabilities: input.capabilities
|
|
280
|
+
});
|
|
281
|
+
endpointsToml.endpoints = {
|
|
282
|
+
...endpointsToml.endpoints ?? {},
|
|
283
|
+
[record.id]: record
|
|
284
|
+
};
|
|
285
|
+
secretsToml.secrets = {
|
|
286
|
+
...secretsToml.secrets ?? {},
|
|
287
|
+
[secretRef]: input.token
|
|
288
|
+
};
|
|
289
|
+
saveRemoteEndpointsToml(projectRoot, endpointsToml);
|
|
290
|
+
saveRemoteSecretsToml(projectRoot, secretsToml);
|
|
291
|
+
return listAuthorityRemoteEndpoints(projectRoot).find((entry) => entry.id === record.id);
|
|
292
|
+
}
|
|
293
|
+
function updateAuthorityRemoteEndpoint(projectRoot, input) {
|
|
294
|
+
const endpointsToml = loadRemoteEndpointsToml(projectRoot);
|
|
295
|
+
const secretsToml = loadRemoteSecretsToml(projectRoot);
|
|
296
|
+
const current = Object.values(endpointsToml.endpoints ?? {}).find((entry) => input.endpointId && entry.id === input.endpointId || input.alias && entry.alias === input.alias);
|
|
297
|
+
if (!current)
|
|
298
|
+
return null;
|
|
299
|
+
const record = {
|
|
300
|
+
...current,
|
|
301
|
+
alias: normalizeString(input.alias) || current.alias,
|
|
302
|
+
host: normalizeString(input.host) || current.host,
|
|
303
|
+
port: input.port !== undefined ? normalizePort(input.port) : current.port,
|
|
304
|
+
auto_connect: input.autoConnect ?? current.auto_connect,
|
|
305
|
+
transport: normalizeString(input.transport) || current.transport,
|
|
306
|
+
labels: input.labels ?? normalizeStringArray(current.labels),
|
|
307
|
+
capabilities: input.capabilities ?? normalizeStringArray(current.capabilities),
|
|
308
|
+
updated_at: new Date().toISOString()
|
|
309
|
+
};
|
|
310
|
+
endpointsToml.endpoints = {
|
|
311
|
+
...endpointsToml.endpoints ?? {},
|
|
312
|
+
[record.id]: record
|
|
313
|
+
};
|
|
314
|
+
if (input.token !== undefined) {
|
|
315
|
+
secretsToml.secrets = {
|
|
316
|
+
...secretsToml.secrets ?? {},
|
|
317
|
+
[record.secret_ref]: input.token
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
saveRemoteEndpointsToml(projectRoot, endpointsToml);
|
|
321
|
+
saveRemoteSecretsToml(projectRoot, secretsToml);
|
|
322
|
+
return listAuthorityRemoteEndpoints(projectRoot).find((entry) => entry.id === record.id) ?? null;
|
|
323
|
+
}
|
|
324
|
+
function removeAuthorityRemoteEndpoint(projectRoot, endpointIdOrAlias) {
|
|
325
|
+
const endpointsToml = loadRemoteEndpointsToml(projectRoot);
|
|
326
|
+
const secretsToml = loadRemoteSecretsToml(projectRoot);
|
|
327
|
+
const entry = Object.values(endpointsToml.endpoints ?? {}).find((candidate) => candidate.id === endpointIdOrAlias || candidate.alias === endpointIdOrAlias);
|
|
328
|
+
if (!entry)
|
|
329
|
+
return false;
|
|
330
|
+
const nextEndpoints = { ...endpointsToml.endpoints ?? {} };
|
|
331
|
+
delete nextEndpoints[entry.id];
|
|
332
|
+
const nextSecrets = { ...secretsToml.secrets ?? {} };
|
|
333
|
+
delete nextSecrets[entry.secret_ref];
|
|
334
|
+
saveRemoteEndpointsToml(projectRoot, { version: endpointsToml.version ?? 1, endpoints: nextEndpoints });
|
|
335
|
+
saveRemoteSecretsToml(projectRoot, { version: secretsToml.version ?? 1, secrets: nextSecrets });
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
function markAuthorityRemoteEndpointConnected(projectRoot, endpointId, connectedAt = new Date().toISOString()) {
|
|
339
|
+
const endpointsToml = loadRemoteEndpointsToml(projectRoot);
|
|
340
|
+
const entry = endpointsToml.endpoints?.[endpointId];
|
|
341
|
+
if (!entry)
|
|
342
|
+
return;
|
|
343
|
+
endpointsToml.endpoints = {
|
|
344
|
+
...endpointsToml.endpoints ?? {},
|
|
345
|
+
[endpointId]: {
|
|
346
|
+
...entry,
|
|
347
|
+
last_connected_at: connectedAt,
|
|
348
|
+
updated_at: connectedAt
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
saveRemoteEndpointsToml(projectRoot, endpointsToml);
|
|
352
|
+
}
|
|
353
|
+
function importLegacyRemoteEndpoints(projectRoot, legacyPath = REMOTES_CONFIG_PATH) {
|
|
354
|
+
if (!existsSync2(legacyPath)) {
|
|
355
|
+
return { imported: 0, skipped: 0, sourcePath: legacyPath };
|
|
356
|
+
}
|
|
357
|
+
const parsed = readTomlFile(legacyPath, { version: 1, remotes: {} });
|
|
358
|
+
let imported = 0;
|
|
359
|
+
let skipped = 0;
|
|
360
|
+
for (const [alias, entry] of Object.entries(parsed.remotes ?? {})) {
|
|
361
|
+
const host = normalizeString(entry.host);
|
|
362
|
+
const token = normalizeString(entry.token);
|
|
363
|
+
if (!host || !token) {
|
|
364
|
+
skipped += 1;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
upsertAuthorityRemoteEndpoint(projectRoot, {
|
|
368
|
+
alias,
|
|
369
|
+
host,
|
|
370
|
+
port: normalizePort(entry.port),
|
|
371
|
+
token
|
|
372
|
+
});
|
|
373
|
+
imported += 1;
|
|
374
|
+
}
|
|
375
|
+
return { imported, skipped, sourcePath: legacyPath };
|
|
376
|
+
}
|
|
377
|
+
function doctorAuthorityRemoteEndpoints(projectRoot) {
|
|
378
|
+
const paths = resolveAuthorityPaths(projectRoot);
|
|
379
|
+
const endpoints = loadRemoteEndpointsToml(projectRoot).endpoints ?? {};
|
|
380
|
+
const secrets = loadRemoteSecretsToml(projectRoot).secrets ?? {};
|
|
381
|
+
const missingSecrets = Object.values(endpoints).filter((entry) => !normalizeString(secrets[entry.secret_ref])).map((entry) => entry.alias);
|
|
382
|
+
const warnings = [];
|
|
383
|
+
if (!existsSync2(paths.remoteEndpointsPath)) {
|
|
384
|
+
warnings.push("Remote endpoint manifest is missing.");
|
|
385
|
+
}
|
|
386
|
+
if (!existsSync2(paths.remoteSecretsPath)) {
|
|
387
|
+
warnings.push("Remote secret store is missing.");
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
manifestPath: paths.remoteEndpointsPath,
|
|
391
|
+
secretsPath: paths.remoteSecretsPath,
|
|
392
|
+
endpointCount: Object.keys(endpoints).length,
|
|
393
|
+
missingSecrets,
|
|
394
|
+
warnings
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function listManagedRemoteEndpoints(configPath = REMOTES_CONFIG_PATH, projectRoot) {
|
|
398
|
+
if (projectRoot) {
|
|
399
|
+
return listAuthorityRemoteEndpoints(projectRoot).map((entry) => ({
|
|
400
|
+
id: entry.id,
|
|
401
|
+
alias: entry.alias,
|
|
402
|
+
host: entry.host,
|
|
403
|
+
port: entry.port,
|
|
404
|
+
token: entry.token,
|
|
405
|
+
addedAt: entry.addedAt,
|
|
406
|
+
lastConnected: entry.lastConnectedAt
|
|
407
|
+
}));
|
|
408
|
+
}
|
|
409
|
+
const config = loadRemotesConfig(configPath);
|
|
410
|
+
return Object.entries(config.remotes ?? {}).map(([alias, entry]) => ({
|
|
411
|
+
id: alias,
|
|
412
|
+
alias,
|
|
413
|
+
host: normalizeString(entry.host) || "",
|
|
414
|
+
port: Number.isFinite(entry.port) ? Number(entry.port) : DEFAULT_REMOTE_PORT,
|
|
415
|
+
token: normalizeString(entry.token) || "",
|
|
416
|
+
addedAt: normalizeString(entry.addedAt) || null,
|
|
417
|
+
lastConnected: normalizeString(entry.lastConnected) || null
|
|
418
|
+
})).sort((left, right) => left.alias.localeCompare(right.alias));
|
|
419
|
+
}
|
|
420
|
+
function upsertManagedRemoteEndpoint(input, configPath = REMOTES_CONFIG_PATH, projectRoot) {
|
|
421
|
+
if (projectRoot) {
|
|
422
|
+
const saved = upsertAuthorityRemoteEndpoint(projectRoot, {
|
|
423
|
+
...input,
|
|
424
|
+
token: input.token ?? ""
|
|
425
|
+
});
|
|
426
|
+
return {
|
|
427
|
+
id: saved.id,
|
|
428
|
+
alias: saved.alias,
|
|
429
|
+
host: saved.host,
|
|
430
|
+
port: saved.port,
|
|
431
|
+
token: saved.token,
|
|
432
|
+
addedAt: saved.addedAt,
|
|
433
|
+
lastConnected: saved.lastConnectedAt
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
const config = loadRemotesConfig(configPath);
|
|
437
|
+
const existing = config.remotes?.[input.alias];
|
|
438
|
+
const nextEntry = {
|
|
439
|
+
host: input.host,
|
|
440
|
+
port: input.port,
|
|
441
|
+
token: input.token,
|
|
442
|
+
addedAt: normalizeString(existing?.addedAt) || new Date().toISOString(),
|
|
443
|
+
...normalizeString(existing?.lastConnected) ? { lastConnected: normalizeString(existing?.lastConnected) } : {}
|
|
444
|
+
};
|
|
445
|
+
const nextConfig = {
|
|
446
|
+
version: config.version ?? 1,
|
|
447
|
+
remotes: {
|
|
448
|
+
...config.remotes ?? {},
|
|
449
|
+
[input.alias]: nextEntry
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
saveRemotesConfig(nextConfig, configPath);
|
|
453
|
+
return {
|
|
454
|
+
id: input.alias,
|
|
455
|
+
alias: input.alias,
|
|
456
|
+
host: input.host,
|
|
457
|
+
port: input.port,
|
|
458
|
+
token: input.token ?? "",
|
|
459
|
+
addedAt: nextEntry.addedAt ?? null,
|
|
460
|
+
lastConnected: nextEntry.lastConnected ?? null
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function removeManagedRemoteEndpoint(alias, configPath = REMOTES_CONFIG_PATH, projectRoot) {
|
|
464
|
+
if (projectRoot) {
|
|
465
|
+
return removeAuthorityRemoteEndpoint(projectRoot, alias);
|
|
466
|
+
}
|
|
467
|
+
const config = loadRemotesConfig(configPath);
|
|
468
|
+
if (!config.remotes?.[alias]) {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
const nextRemotes = { ...config.remotes ?? {} };
|
|
472
|
+
delete nextRemotes[alias];
|
|
473
|
+
saveRemotesConfig({
|
|
474
|
+
version: config.version ?? 1,
|
|
475
|
+
remotes: nextRemotes
|
|
476
|
+
}, configPath);
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
function resolveRemoteEndpoint(options) {
|
|
480
|
+
const env = options.env ?? process.env;
|
|
481
|
+
const envHost = normalizeString(env.RIG_REMOTE_HOST);
|
|
482
|
+
const envToken = normalizeString(env.RIG_REMOTE_TOKEN);
|
|
483
|
+
const envPort = parsePort(normalizeString(env.RIG_REMOTE_PORT), "RIG_REMOTE_PORT");
|
|
484
|
+
const envAlias = normalizeString(env.RIG_REMOTE_ALIAS);
|
|
485
|
+
const explicitHost = normalizeString(options.host);
|
|
486
|
+
const explicitToken = normalizeString(options.token);
|
|
487
|
+
const explicitPort = parsePort(normalizeString(options.port), "--port");
|
|
488
|
+
const explicitProvided = Boolean(explicitHost || explicitToken || explicitPort !== null);
|
|
489
|
+
const requestedAlias = normalizeString(options.remoteAlias) || envAlias;
|
|
490
|
+
let endpoint = {
|
|
491
|
+
source: "env",
|
|
492
|
+
host: envHost || "",
|
|
493
|
+
port: envPort ?? DEFAULT_REMOTE_PORT,
|
|
494
|
+
token: envToken || "",
|
|
495
|
+
configPath: REMOTES_CONFIG_PATH
|
|
496
|
+
};
|
|
497
|
+
if (requestedAlias) {
|
|
498
|
+
if (options.projectRoot) {
|
|
499
|
+
const remote = listAuthorityRemoteEndpoints(options.projectRoot).find((entry) => entry.alias === requestedAlias);
|
|
500
|
+
if (!remote) {
|
|
501
|
+
const configPath = resolveAuthorityPaths(options.projectRoot).remoteEndpointsPath;
|
|
502
|
+
throw new RemoteCliError("RIG_REMOTE_ALIAS_NOT_FOUND", `Remote alias '${requestedAlias}' was not found in ${configPath}.`, 2, { alias: requestedAlias, configPath });
|
|
503
|
+
}
|
|
504
|
+
endpoint = {
|
|
505
|
+
source: "alias",
|
|
506
|
+
alias: requestedAlias,
|
|
507
|
+
host: remote.host,
|
|
508
|
+
port: remote.port,
|
|
509
|
+
token: remote.token,
|
|
510
|
+
configPath: resolveAuthorityPaths(options.projectRoot).remoteEndpointsPath
|
|
511
|
+
};
|
|
512
|
+
} else {
|
|
513
|
+
const config = loadRemotesConfig();
|
|
514
|
+
const remote = config.remotes?.[requestedAlias];
|
|
515
|
+
if (!remote) {
|
|
516
|
+
throw new RemoteCliError("RIG_REMOTE_ALIAS_NOT_FOUND", `Remote alias '${requestedAlias}' was not found in ${REMOTES_CONFIG_PATH}.`, 2, { alias: requestedAlias, configPath: REMOTES_CONFIG_PATH });
|
|
517
|
+
}
|
|
518
|
+
endpoint = {
|
|
519
|
+
source: "alias",
|
|
520
|
+
alias: requestedAlias,
|
|
521
|
+
host: normalizeString(remote.host) || "",
|
|
522
|
+
port: Number.isFinite(remote.port) ? Number(remote.port) : DEFAULT_REMOTE_PORT,
|
|
523
|
+
token: normalizeString(remote.token) || "",
|
|
524
|
+
configPath: REMOTES_CONFIG_PATH
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if (explicitProvided) {
|
|
529
|
+
endpoint = {
|
|
530
|
+
...endpoint,
|
|
531
|
+
source: "flags",
|
|
532
|
+
host: explicitHost || endpoint.host,
|
|
533
|
+
port: explicitPort ?? endpoint.port,
|
|
534
|
+
token: explicitToken || endpoint.token
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
if (!endpoint.host) {
|
|
538
|
+
throw new RemoteCliError("RIG_REMOTE_HOST_REQUIRED", "Remote host is required. Pass --host, --remote <alias>, or set RIG_REMOTE_HOST.", 2);
|
|
539
|
+
}
|
|
540
|
+
if (!Number.isFinite(endpoint.port) || endpoint.port <= 0 || endpoint.port > 65535) {
|
|
541
|
+
throw new RemoteCliError("RIG_REMOTE_INVALID_PORT", `Invalid remote port: ${endpoint.port}.`, 2);
|
|
542
|
+
}
|
|
543
|
+
return endpoint;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
class RemoteWsClient {
|
|
547
|
+
endpoint;
|
|
548
|
+
ws = null;
|
|
549
|
+
connected = false;
|
|
550
|
+
pending = new Map;
|
|
551
|
+
onEvent;
|
|
552
|
+
requestTimeoutMs;
|
|
553
|
+
connectTimeoutMs;
|
|
554
|
+
authTimeoutMs;
|
|
555
|
+
subscriptionTimeoutMs;
|
|
556
|
+
connectionToken = null;
|
|
557
|
+
connectionTokenExpiresAt = null;
|
|
558
|
+
tokenRefreshTimer = null;
|
|
559
|
+
constructor(endpoint, options = {}) {
|
|
560
|
+
this.endpoint = endpoint;
|
|
561
|
+
this.onEvent = options.onEvent;
|
|
562
|
+
this.connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_TIMEOUTS.connectMs;
|
|
563
|
+
this.authTimeoutMs = options.authTimeoutMs ?? DEFAULT_TIMEOUTS.authMs;
|
|
564
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_TIMEOUTS.requestMs;
|
|
565
|
+
this.subscriptionTimeoutMs = options.subscriptionTimeoutMs ?? DEFAULT_TIMEOUTS.subscriptionMs;
|
|
566
|
+
}
|
|
567
|
+
setEventHandler(handler) {
|
|
568
|
+
this.onEvent = handler;
|
|
569
|
+
}
|
|
570
|
+
async connect() {
|
|
571
|
+
if (this.connected) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
const url = `ws://${this.endpoint.host}:${this.endpoint.port}`;
|
|
575
|
+
await new Promise((resolve3, reject) => {
|
|
576
|
+
let authResolved = false;
|
|
577
|
+
let connectTimer = setTimeout(() => {
|
|
578
|
+
connectTimer = null;
|
|
579
|
+
reject(new RemoteCliError("RIG_REMOTE_CONNECT_TIMEOUT", `Timed out connecting to ${url} after ${this.connectTimeoutMs}ms.`, 3, { host: this.endpoint.host, port: this.endpoint.port, timeoutMs: this.connectTimeoutMs }));
|
|
580
|
+
}, this.connectTimeoutMs);
|
|
581
|
+
const ws = new WebSocket(url);
|
|
582
|
+
this.ws = ws;
|
|
583
|
+
ws.onopen = () => {
|
|
584
|
+
const authMessage = {
|
|
585
|
+
type: "auth",
|
|
586
|
+
id: crypto.randomUUID(),
|
|
587
|
+
timestamp: new Date().toISOString(),
|
|
588
|
+
token: this.connectionToken || this.endpoint.token,
|
|
589
|
+
tokenType: this.connectionToken ? "connection" : "server"
|
|
590
|
+
};
|
|
591
|
+
let authTimer = setTimeout(() => {
|
|
592
|
+
authTimer = null;
|
|
593
|
+
reject(new RemoteCliError("RIG_REMOTE_AUTH_TIMEOUT", `Timed out waiting for auth response from ${url} after ${this.authTimeoutMs}ms.`, 3, { host: this.endpoint.host, port: this.endpoint.port, timeoutMs: this.authTimeoutMs }));
|
|
594
|
+
}, this.authTimeoutMs);
|
|
595
|
+
ws.send(JSON.stringify(authMessage));
|
|
596
|
+
const originalOnMessage = ws.onmessage;
|
|
597
|
+
ws.onmessage = (event) => {
|
|
598
|
+
const message = parseIncomingMessage(event.data);
|
|
599
|
+
if (!message) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
if (message.type === "auth_response" && !authResolved) {
|
|
603
|
+
authResolved = true;
|
|
604
|
+
if (connectTimer) {
|
|
605
|
+
clearTimeout(connectTimer);
|
|
606
|
+
connectTimer = null;
|
|
607
|
+
}
|
|
608
|
+
if (authTimer) {
|
|
609
|
+
clearTimeout(authTimer);
|
|
610
|
+
authTimer = null;
|
|
611
|
+
}
|
|
612
|
+
const auth = message;
|
|
613
|
+
if (!auth.success) {
|
|
614
|
+
reject(new RemoteCliError("RIG_REMOTE_AUTH_FAILED", auth.error || "Remote authentication failed.", 3, { host: this.endpoint.host, port: this.endpoint.port }));
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (auth.connectionToken && auth.connectionTokenExpiresAt) {
|
|
618
|
+
this.connectionToken = auth.connectionToken;
|
|
619
|
+
this.connectionTokenExpiresAt = auth.connectionTokenExpiresAt;
|
|
620
|
+
this.scheduleTokenRefresh();
|
|
621
|
+
}
|
|
622
|
+
if (this.endpoint.configPath.endsWith("endpoints.toml")) {
|
|
623
|
+
const projectRoot = dirname2(dirname2(dirname2(this.endpoint.configPath)));
|
|
624
|
+
try {
|
|
625
|
+
markAuthorityRemoteEndpointConnected(projectRoot, this.endpoint.alias ? listAuthorityRemoteEndpoints(projectRoot).find((entry) => entry.alias === this.endpoint.alias)?.id ?? "" : "");
|
|
626
|
+
} catch {}
|
|
627
|
+
}
|
|
628
|
+
this.connected = true;
|
|
629
|
+
ws.onmessage = (event2) => {
|
|
630
|
+
const next = parseIncomingMessage(event2.data);
|
|
631
|
+
if (!next) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
this.routeMessage(next);
|
|
635
|
+
};
|
|
636
|
+
resolve3();
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
if (typeof originalOnMessage === "function") {
|
|
640
|
+
originalOnMessage.call(ws, event);
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
};
|
|
644
|
+
ws.onerror = () => {
|
|
645
|
+
if (connectTimer) {
|
|
646
|
+
clearTimeout(connectTimer);
|
|
647
|
+
connectTimer = null;
|
|
648
|
+
}
|
|
649
|
+
if (!authResolved) {
|
|
650
|
+
reject(new RemoteCliError("RIG_REMOTE_CONNECT_FAILED", `Failed to connect to ${url}.`, 3, { host: this.endpoint.host, port: this.endpoint.port }));
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
ws.onclose = () => {
|
|
654
|
+
this.connected = false;
|
|
655
|
+
this.clearPending("Connection closed");
|
|
656
|
+
};
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
disconnect() {
|
|
660
|
+
this.connected = false;
|
|
661
|
+
this.clearTokenRefreshTimer();
|
|
662
|
+
this.clearPending("Connection closed");
|
|
663
|
+
if (this.ws) {
|
|
664
|
+
try {
|
|
665
|
+
this.ws.close();
|
|
666
|
+
} catch {}
|
|
667
|
+
this.ws = null;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
async subscribe(eventTypes = []) {
|
|
671
|
+
const message = { type: "subscribe" };
|
|
672
|
+
if (eventTypes.length > 0) {
|
|
673
|
+
message.eventTypes = eventTypes;
|
|
674
|
+
}
|
|
675
|
+
const response = await this.request(message, this.subscriptionTimeoutMs);
|
|
676
|
+
return expectType(response, "operation_result");
|
|
677
|
+
}
|
|
678
|
+
async unsubscribe() {
|
|
679
|
+
return expectType(await this.request({ type: "unsubscribe" }), "operation_result");
|
|
680
|
+
}
|
|
681
|
+
async getState() {
|
|
682
|
+
return expectType(await this.request({ type: "get_state" }), "state_response");
|
|
683
|
+
}
|
|
684
|
+
async getTasks() {
|
|
685
|
+
return expectType(await this.request({ type: "get_tasks" }), "tasks_response");
|
|
686
|
+
}
|
|
687
|
+
async pause() {
|
|
688
|
+
return expectType(await this.request({ type: "pause" }), "operation_result");
|
|
689
|
+
}
|
|
690
|
+
async resume() {
|
|
691
|
+
return expectType(await this.request({ type: "resume" }), "operation_result");
|
|
692
|
+
}
|
|
693
|
+
async interrupt() {
|
|
694
|
+
return expectType(await this.request({ type: "interrupt" }), "operation_result");
|
|
695
|
+
}
|
|
696
|
+
async continueExecution() {
|
|
697
|
+
return expectType(await this.request({ type: "continue" }), "operation_result");
|
|
698
|
+
}
|
|
699
|
+
async refreshTasks() {
|
|
700
|
+
return expectType(await this.request({ type: "refresh_tasks" }), "operation_result");
|
|
701
|
+
}
|
|
702
|
+
async addIterations(count) {
|
|
703
|
+
return expectType(await this.request({ type: "add_iterations", count }), "operation_result");
|
|
704
|
+
}
|
|
705
|
+
async removeIterations(count) {
|
|
706
|
+
return expectType(await this.request({ type: "remove_iterations", count }), "operation_result");
|
|
707
|
+
}
|
|
708
|
+
async getPromptPreview(taskId) {
|
|
709
|
+
return expectType(await this.request({ type: "get_prompt_preview", taskId }), "prompt_preview_response");
|
|
710
|
+
}
|
|
711
|
+
async getIterationOutput(taskId) {
|
|
712
|
+
return expectType(await this.request({ type: "get_iteration_output", taskId }), "iteration_output_response");
|
|
713
|
+
}
|
|
714
|
+
async startOrchestration(options) {
|
|
715
|
+
return expectType(await this.request({
|
|
716
|
+
type: "orchestrate:start",
|
|
717
|
+
maxWorkers: options.maxWorkers,
|
|
718
|
+
maxIterations: options.maxIterations,
|
|
719
|
+
directMerge: options.directMerge
|
|
720
|
+
}), "orchestrate:start_response");
|
|
721
|
+
}
|
|
722
|
+
async pauseOrchestration(orchestrationId) {
|
|
723
|
+
return expectType(await this.request({ type: "orchestrate:pause", orchestrationId }), "operation_result");
|
|
724
|
+
}
|
|
725
|
+
async resumeOrchestration(orchestrationId) {
|
|
726
|
+
return expectType(await this.request({ type: "orchestrate:resume", orchestrationId }), "operation_result");
|
|
727
|
+
}
|
|
728
|
+
async stopOrchestration(orchestrationId) {
|
|
729
|
+
return expectType(await this.request({ type: "orchestrate:stop", orchestrationId }), "operation_result");
|
|
730
|
+
}
|
|
731
|
+
async getOrchestrationState(orchestrationId) {
|
|
732
|
+
return expectType(await this.request({ type: "orchestrate:get_state", orchestrationId }), "orchestrate:state_response");
|
|
733
|
+
}
|
|
734
|
+
async ping() {
|
|
735
|
+
return this.request({ type: "ping" });
|
|
736
|
+
}
|
|
737
|
+
async request(payload, timeoutMs = this.requestTimeoutMs) {
|
|
738
|
+
if (!this.connected || !this.ws) {
|
|
739
|
+
throw new RemoteCliError("RIG_REMOTE_NOT_CONNECTED", "Remote client is not connected.", 3);
|
|
740
|
+
}
|
|
741
|
+
const id = crypto.randomUUID();
|
|
742
|
+
const message = {
|
|
743
|
+
...payload,
|
|
744
|
+
id,
|
|
745
|
+
timestamp: new Date().toISOString()
|
|
746
|
+
};
|
|
747
|
+
return await new Promise((resolve3, reject) => {
|
|
748
|
+
const timeout = setTimeout(() => {
|
|
749
|
+
this.pending.delete(id);
|
|
750
|
+
reject(new RemoteCliError("RIG_REMOTE_REQUEST_TIMEOUT", `Timed out waiting for response to '${String(payload.type)}' after ${timeoutMs}ms.`, 3, { requestType: payload.type, requestId: id, timeoutMs }));
|
|
751
|
+
}, timeoutMs);
|
|
752
|
+
this.pending.set(id, { resolve: resolve3, reject, timeout });
|
|
753
|
+
this.ws?.send(JSON.stringify(message));
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
routeMessage(message) {
|
|
757
|
+
if (message.id && this.pending.has(message.id)) {
|
|
758
|
+
const pending = this.pending.get(message.id);
|
|
759
|
+
if (pending) {
|
|
760
|
+
clearTimeout(pending.timeout);
|
|
761
|
+
this.pending.delete(message.id);
|
|
762
|
+
pending.resolve(message);
|
|
763
|
+
}
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
if (message.type === "token_refresh_response") {
|
|
767
|
+
const token = normalizeString(message.connectionToken);
|
|
768
|
+
const expiresAt = normalizeString(message.connectionTokenExpiresAt);
|
|
769
|
+
if (token && expiresAt) {
|
|
770
|
+
this.connectionToken = token;
|
|
771
|
+
this.connectionTokenExpiresAt = expiresAt;
|
|
772
|
+
this.scheduleTokenRefresh();
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
this.onEvent?.({ receivedAt: new Date().toISOString(), message });
|
|
776
|
+
}
|
|
777
|
+
scheduleTokenRefresh() {
|
|
778
|
+
this.clearTokenRefreshTimer();
|
|
779
|
+
if (!this.connectionToken || !this.connectionTokenExpiresAt || !this.ws || !this.connected) {
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
const expiresAt = new Date(this.connectionTokenExpiresAt).getTime();
|
|
783
|
+
if (!Number.isFinite(expiresAt)) {
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
const refreshAt = expiresAt - 60 * 60 * 1000;
|
|
787
|
+
const delay = Math.max(0, refreshAt - Date.now());
|
|
788
|
+
this.tokenRefreshTimer = setTimeout(() => {
|
|
789
|
+
if (!this.connected || !this.ws || !this.connectionToken) {
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
const refreshMessage = {
|
|
793
|
+
type: "token_refresh",
|
|
794
|
+
id: crypto.randomUUID(),
|
|
795
|
+
timestamp: new Date().toISOString(),
|
|
796
|
+
connectionToken: this.connectionToken
|
|
797
|
+
};
|
|
798
|
+
this.ws.send(JSON.stringify(refreshMessage));
|
|
799
|
+
}, delay);
|
|
800
|
+
}
|
|
801
|
+
clearTokenRefreshTimer() {
|
|
802
|
+
if (this.tokenRefreshTimer) {
|
|
803
|
+
clearTimeout(this.tokenRefreshTimer);
|
|
804
|
+
this.tokenRefreshTimer = null;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
clearPending(reason) {
|
|
808
|
+
for (const [requestId, pending] of this.pending.entries()) {
|
|
809
|
+
clearTimeout(pending.timeout);
|
|
810
|
+
pending.reject(new RemoteCliError("RIG_REMOTE_REQUEST_CANCELLED", `${reason} (request ${requestId}).`, 3, { requestId }));
|
|
811
|
+
this.pending.delete(requestId);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
function matchesEventFilter(message, expectedType) {
|
|
816
|
+
if (!expectedType) {
|
|
817
|
+
return true;
|
|
818
|
+
}
|
|
819
|
+
if (message.type === expectedType) {
|
|
820
|
+
return true;
|
|
821
|
+
}
|
|
822
|
+
if (message.type === "engine_event") {
|
|
823
|
+
const eventType = normalizeString(message.event?.type);
|
|
824
|
+
return eventType === expectedType;
|
|
825
|
+
}
|
|
826
|
+
if (message.type === "parallel_event") {
|
|
827
|
+
const eventType = normalizeString(message.event?.type);
|
|
828
|
+
return eventType === expectedType;
|
|
829
|
+
}
|
|
830
|
+
return false;
|
|
831
|
+
}
|
|
832
|
+
function summarizeMessage(message) {
|
|
833
|
+
if (message.type === "pong") {
|
|
834
|
+
return "pong";
|
|
835
|
+
}
|
|
836
|
+
if (message.type === "engine_event") {
|
|
837
|
+
const eventType = normalizeString(message.event?.type);
|
|
838
|
+
return eventType ? `engine_event:${eventType}` : "engine_event";
|
|
839
|
+
}
|
|
840
|
+
if (message.type === "parallel_event") {
|
|
841
|
+
const eventType = normalizeString(message.event?.type);
|
|
842
|
+
const orchestrationId = normalizeString(message.orchestrationId);
|
|
843
|
+
return orchestrationId ? `parallel_event:${eventType || "unknown"}:${orchestrationId}` : `parallel_event:${eventType || "unknown"}`;
|
|
844
|
+
}
|
|
845
|
+
return message.type;
|
|
846
|
+
}
|
|
847
|
+
function migrateManagedRemoteEndpoints(projectRoot, legacyPath = REMOTES_CONFIG_PATH) {
|
|
848
|
+
return importLegacyRemoteEndpoints(projectRoot, legacyPath);
|
|
849
|
+
}
|
|
850
|
+
function doctorManagedRemoteEndpoints(projectRoot) {
|
|
851
|
+
return doctorAuthorityRemoteEndpoints(projectRoot);
|
|
852
|
+
}
|
|
853
|
+
function updateManagedRemoteEndpointInAuthority(projectRoot, input) {
|
|
854
|
+
const updated = updateAuthorityRemoteEndpoint(projectRoot, input);
|
|
855
|
+
if (!updated)
|
|
856
|
+
return null;
|
|
857
|
+
return {
|
|
858
|
+
id: updated.id,
|
|
859
|
+
alias: updated.alias,
|
|
860
|
+
host: updated.host,
|
|
861
|
+
port: updated.port,
|
|
862
|
+
token: updated.token,
|
|
863
|
+
addedAt: updated.addedAt,
|
|
864
|
+
lastConnected: updated.lastConnectedAt
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
function normalizeString(value) {
|
|
868
|
+
if (!value) {
|
|
869
|
+
return "";
|
|
870
|
+
}
|
|
871
|
+
return value.trim();
|
|
872
|
+
}
|
|
873
|
+
function parsePort(value, label) {
|
|
874
|
+
if (!value) {
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
const parsed = Number.parseInt(value, 10);
|
|
878
|
+
if (!Number.isFinite(parsed) || parsed <= 0 || parsed > 65535) {
|
|
879
|
+
throw new RemoteCliError("RIG_REMOTE_INVALID_PORT", `Invalid port for ${label}: ${value}.`, 2, { label, value });
|
|
880
|
+
}
|
|
881
|
+
return parsed;
|
|
882
|
+
}
|
|
883
|
+
function parseIncomingMessage(raw) {
|
|
884
|
+
if (typeof raw !== "string") {
|
|
885
|
+
return null;
|
|
886
|
+
}
|
|
887
|
+
try {
|
|
888
|
+
const parsed = JSON.parse(raw);
|
|
889
|
+
if (!parsed || typeof parsed !== "object" || typeof parsed.type !== "string") {
|
|
890
|
+
return null;
|
|
891
|
+
}
|
|
892
|
+
return parsed;
|
|
893
|
+
} catch {
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
function expectType(message, expectedType) {
|
|
898
|
+
if (message.type !== expectedType) {
|
|
899
|
+
throw new RemoteCliError("RIG_REMOTE_UNEXPECTED_RESPONSE", `Unexpected response type: ${message.type}. Expected ${expectedType}.`, 3, { expectedType, actualType: message.type, message });
|
|
900
|
+
}
|
|
901
|
+
return message;
|
|
902
|
+
}
|
|
70
903
|
|
|
71
904
|
// packages/cli/src/commands/_parsers.ts
|
|
72
905
|
function parseOptionalPositiveInt(value, option) {
|
|
@@ -75,17 +908,17 @@ function parseOptionalPositiveInt(value, option) {
|
|
|
75
908
|
}
|
|
76
909
|
const parsed = Number.parseInt(value, 10);
|
|
77
910
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
78
|
-
throw new
|
|
911
|
+
throw new CliError(`Invalid ${option} value: ${value}`, 1, { hint: `Pass a positive integer, e.g. \`${option} 10\`.` });
|
|
79
912
|
}
|
|
80
913
|
return parsed;
|
|
81
914
|
}
|
|
82
915
|
function parseRequiredPositiveInt(value, option) {
|
|
83
916
|
if (!value) {
|
|
84
|
-
throw new
|
|
917
|
+
throw new CliError(`Missing value for ${option}.`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} 10\`.` });
|
|
85
918
|
}
|
|
86
919
|
const parsed = Number.parseInt(value, 10);
|
|
87
920
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
88
|
-
throw new
|
|
921
|
+
throw new CliError(`Invalid ${option} value: ${value}`, 1, { hint: `Pass a positive integer, e.g. \`${option} 10\`.` });
|
|
89
922
|
}
|
|
90
923
|
return parsed;
|
|
91
924
|
}
|
|
@@ -134,7 +967,7 @@ async function executeRemote(context, args) {
|
|
|
134
967
|
const [subcommand = "list", ...subRest] = rest;
|
|
135
968
|
switch (subcommand) {
|
|
136
969
|
case "list": {
|
|
137
|
-
requireNoExtraArgs(subRest, "
|
|
970
|
+
requireNoExtraArgs(subRest, "rig remote endpoint list");
|
|
138
971
|
const endpoints = listManagedRemoteEndpoints(undefined, context.projectRoot);
|
|
139
972
|
const redactedEndpoints = endpoints.map((endpoint2) => redactRemoteEndpoint(endpoint2));
|
|
140
973
|
if (context.outputMode === "text") {
|
|
@@ -158,9 +991,9 @@ async function executeRemote(context, args) {
|
|
|
158
991
|
pending2 = port.rest;
|
|
159
992
|
const token = takeOption(pending2, "--token");
|
|
160
993
|
pending2 = token.rest;
|
|
161
|
-
requireNoExtraArgs(pending2, "
|
|
994
|
+
requireNoExtraArgs(pending2, "rig remote endpoint add --alias <a> --host <h> --port <n> --token <t>");
|
|
162
995
|
if (!alias.value || !host.value || !token.value || !port.value) {
|
|
163
|
-
throw new
|
|
996
|
+
throw new CliError("remote endpoint add requires --alias, --host, --port, and --token.", 1, { hint: "Re-run as `rig remote endpoint add --alias <a> --host <h> --port <n> --token <t>`." });
|
|
164
997
|
}
|
|
165
998
|
const created = upsertManagedRemoteEndpoint({
|
|
166
999
|
alias: alias.value,
|
|
@@ -185,9 +1018,9 @@ async function executeRemote(context, args) {
|
|
|
185
1018
|
pending2 = port.rest;
|
|
186
1019
|
const token = takeOption(pending2, "--token");
|
|
187
1020
|
pending2 = token.rest;
|
|
188
|
-
requireNoExtraArgs(pending2, "
|
|
1021
|
+
requireNoExtraArgs(pending2, "rig remote endpoint update --id <id> [--alias <a>] [--host <h>] [--port <n>] [--token <t>]");
|
|
189
1022
|
if (!endpointId.value && !alias.value) {
|
|
190
|
-
throw new
|
|
1023
|
+
throw new CliError("remote endpoint update requires --id <id> or --alias <a>.", 1, { hint: "List endpoints with `rig remote endpoint list`, then pass --id or --alias." });
|
|
191
1024
|
}
|
|
192
1025
|
const updated = updateManagedRemoteEndpointInAuthority(context.projectRoot, {
|
|
193
1026
|
endpointId: endpointId.value || undefined,
|
|
@@ -197,7 +1030,7 @@ async function executeRemote(context, args) {
|
|
|
197
1030
|
token: token.value || undefined
|
|
198
1031
|
});
|
|
199
1032
|
if (!updated) {
|
|
200
|
-
throw new
|
|
1033
|
+
throw new CliError("Remote endpoint not found.", 2, { hint: "Run `rig remote endpoint list` to see configured endpoints." });
|
|
201
1034
|
}
|
|
202
1035
|
if (context.outputMode === "text") {
|
|
203
1036
|
console.log(`Updated remote endpoint ${updated.alias} -> ${updated.host}:${updated.port}`);
|
|
@@ -208,13 +1041,13 @@ async function executeRemote(context, args) {
|
|
|
208
1041
|
let pending2 = subRest;
|
|
209
1042
|
const alias = takeOption(pending2, "--alias");
|
|
210
1043
|
pending2 = alias.rest;
|
|
211
|
-
requireNoExtraArgs(pending2, "
|
|
1044
|
+
requireNoExtraArgs(pending2, "rig remote endpoint remove --alias <a>");
|
|
212
1045
|
if (!alias.value) {
|
|
213
|
-
throw new
|
|
1046
|
+
throw new CliError("remote endpoint remove requires --alias.", 1, { hint: "Run `rig remote endpoint list` to find the alias, then `rig remote endpoint remove --alias <a>`." });
|
|
214
1047
|
}
|
|
215
1048
|
const removed = removeManagedRemoteEndpoint(alias.value, undefined, context.projectRoot);
|
|
216
1049
|
if (!removed) {
|
|
217
|
-
throw new
|
|
1050
|
+
throw new CliError(`Remote endpoint alias not found: ${alias.value}`, 2, { hint: "Run `rig remote endpoint list` to see configured endpoint aliases." });
|
|
218
1051
|
}
|
|
219
1052
|
if (context.outputMode === "text") {
|
|
220
1053
|
console.log(`Removed remote endpoint ${alias.value}`);
|
|
@@ -225,9 +1058,9 @@ async function executeRemote(context, args) {
|
|
|
225
1058
|
let pending2 = subRest;
|
|
226
1059
|
const alias = takeOption(pending2, "--alias");
|
|
227
1060
|
pending2 = alias.rest;
|
|
228
|
-
requireNoExtraArgs(pending2, "
|
|
1061
|
+
requireNoExtraArgs(pending2, "rig remote endpoint test --alias <a>");
|
|
229
1062
|
if (!alias.value) {
|
|
230
|
-
throw new
|
|
1063
|
+
throw new CliError("remote endpoint test requires --alias.", 1, { hint: "Run `rig remote endpoint list` to find the alias, then `rig remote endpoint test --alias <a>`." });
|
|
231
1064
|
}
|
|
232
1065
|
const endpoint2 = resolveRemoteEndpoint({ remoteAlias: alias.value, projectRoot: context.projectRoot });
|
|
233
1066
|
const client = new RemoteWsClient(endpoint2);
|
|
@@ -256,7 +1089,7 @@ async function executeRemote(context, args) {
|
|
|
256
1089
|
}
|
|
257
1090
|
}
|
|
258
1091
|
case "migrate": {
|
|
259
|
-
requireNoExtraArgs(subRest, "
|
|
1092
|
+
requireNoExtraArgs(subRest, "rig remote endpoint migrate");
|
|
260
1093
|
const result = migrateManagedRemoteEndpoints(context.projectRoot);
|
|
261
1094
|
if (context.outputMode === "text") {
|
|
262
1095
|
console.log(`Imported ${result.imported} endpoint(s) from ${result.sourcePath}${result.skipped > 0 ? `, skipped ${result.skipped}` : ""}.`);
|
|
@@ -264,7 +1097,7 @@ async function executeRemote(context, args) {
|
|
|
264
1097
|
return { ok: true, group: "remote", command: "endpoint migrate", details: result };
|
|
265
1098
|
}
|
|
266
1099
|
case "doctor": {
|
|
267
|
-
requireNoExtraArgs(subRest, "
|
|
1100
|
+
requireNoExtraArgs(subRest, "rig remote endpoint doctor");
|
|
268
1101
|
const result = doctorManagedRemoteEndpoints(context.projectRoot);
|
|
269
1102
|
if (context.outputMode === "text") {
|
|
270
1103
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -272,7 +1105,7 @@ async function executeRemote(context, args) {
|
|
|
272
1105
|
return { ok: true, group: "remote", command: "endpoint doctor", details: result };
|
|
273
1106
|
}
|
|
274
1107
|
default:
|
|
275
|
-
throw new
|
|
1108
|
+
throw new CliError(`Unknown remote endpoint command: ${subcommand}`, 1, { hint: "Run `rig remote --help` for endpoint commands (list|add|update|remove|test)." });
|
|
276
1109
|
}
|
|
277
1110
|
}
|
|
278
1111
|
let pending = rest;
|
|
@@ -317,17 +1150,17 @@ async function executeRemote(context, args) {
|
|
|
317
1150
|
try {
|
|
318
1151
|
switch (command) {
|
|
319
1152
|
case "test": {
|
|
320
|
-
requireNoExtraArgs(pending, "
|
|
1153
|
+
requireNoExtraArgs(pending, "rig remote test [--remote <alias>] [--host <host>] [--port <n>] [--token <token>]");
|
|
321
1154
|
const response = await withClient((client) => client.ping());
|
|
322
1155
|
return { ok: true, group: "remote", command, details: toDetails(response) };
|
|
323
1156
|
}
|
|
324
1157
|
case "status": {
|
|
325
|
-
requireNoExtraArgs(pending, "
|
|
1158
|
+
requireNoExtraArgs(pending, "rig remote status [--remote <alias>] [--host <host>] [--port <n>] [--token <token>]");
|
|
326
1159
|
const response = await withClient((client) => client.getState());
|
|
327
1160
|
return { ok: true, group: "remote", command, details: toDetails(response) };
|
|
328
1161
|
}
|
|
329
1162
|
case "tasks": {
|
|
330
|
-
requireNoExtraArgs(pending, "
|
|
1163
|
+
requireNoExtraArgs(pending, "rig remote tasks [--remote <alias>] [--host <host>] [--port <n>] [--token <token>]");
|
|
331
1164
|
const response = await withClient((client) => client.getTasks());
|
|
332
1165
|
return { ok: true, group: "remote", command, details: toDetails(response) };
|
|
333
1166
|
}
|
|
@@ -337,11 +1170,11 @@ async function executeRemote(context, args) {
|
|
|
337
1170
|
watchPending = secondsResult.rest;
|
|
338
1171
|
const eventResult = takeOption(watchPending, "--event");
|
|
339
1172
|
watchPending = eventResult.rest;
|
|
340
|
-
requireNoExtraArgs(watchPending, "
|
|
1173
|
+
requireNoExtraArgs(watchPending, "rig remote watch [--seconds <n>] [--event <type>] [--remote <alias>] [--host <host>] [--port <n>] [--token <token>]");
|
|
341
1174
|
const seconds = parseOptionalPositiveInt(secondsResult.value, "--seconds");
|
|
342
1175
|
const eventFilter = eventResult.value || undefined;
|
|
343
1176
|
if (context.outputMode === "json" && !seconds) {
|
|
344
|
-
throw new
|
|
1177
|
+
throw new CliError("--json remote watch requires --seconds <n> to produce bounded output.", 2, { hint: "Re-run as `rig remote watch --seconds 10 --json`." });
|
|
345
1178
|
}
|
|
346
1179
|
const captured = [];
|
|
347
1180
|
let dropped = 0;
|
|
@@ -406,7 +1239,7 @@ async function executeRemote(context, args) {
|
|
|
406
1239
|
case "stop":
|
|
407
1240
|
case "continue":
|
|
408
1241
|
case "refresh": {
|
|
409
|
-
requireNoExtraArgs(pending, `
|
|
1242
|
+
requireNoExtraArgs(pending, `rig remote ${command} [--remote <alias>] [--host <host>] [--port <n>] [--token <token>]`);
|
|
410
1243
|
const response = await withClient(async (client) => {
|
|
411
1244
|
switch (command) {
|
|
412
1245
|
case "pause":
|
|
@@ -429,7 +1262,7 @@ async function executeRemote(context, args) {
|
|
|
429
1262
|
let countPending = pending;
|
|
430
1263
|
const countResult = takeOption(countPending, "--count");
|
|
431
1264
|
countPending = countResult.rest;
|
|
432
|
-
requireNoExtraArgs(countPending, `
|
|
1265
|
+
requireNoExtraArgs(countPending, `rig remote ${command} --count <n> [--remote <alias>] [--host <host>] [--port <n>] [--token <token>]`);
|
|
433
1266
|
const count = parseRequiredPositiveInt(countResult.value, "--count");
|
|
434
1267
|
const response = await withClient(async (client) => command === "add-iterations" ? await client.addIterations(count) : await client.removeIterations(count));
|
|
435
1268
|
assertRemoteOperationSuccess(command, response);
|
|
@@ -440,8 +1273,8 @@ async function executeRemote(context, args) {
|
|
|
440
1273
|
let taskPending = pending;
|
|
441
1274
|
const taskResult = takeOption(taskPending, "--task");
|
|
442
1275
|
taskPending = taskResult.rest;
|
|
443
|
-
requireNoExtraArgs(taskPending, `
|
|
444
|
-
const taskId = requireTask(taskResult.value, `
|
|
1276
|
+
requireNoExtraArgs(taskPending, `rig remote ${command} --task <id> [--remote <alias>] [--host <host>] [--port <n>] [--token <token>]`);
|
|
1277
|
+
const taskId = requireTask(taskResult.value, `rig remote ${command} --task <id>`);
|
|
445
1278
|
const response = await withClient(async (client) => command === "prompt-preview" ? await client.getPromptPreview(taskId) : await client.getIterationOutput(taskId));
|
|
446
1279
|
assertRemoteOperationSuccess(command, response);
|
|
447
1280
|
return { ok: true, group: "remote", command, details: toDetails(response) };
|
|
@@ -454,7 +1287,7 @@ async function executeRemote(context, args) {
|
|
|
454
1287
|
orchestrationPending = maxIterationsResult.rest;
|
|
455
1288
|
const directMergeResult = takeFlag(orchestrationPending, "--direct-merge");
|
|
456
1289
|
orchestrationPending = directMergeResult.rest;
|
|
457
|
-
requireNoExtraArgs(orchestrationPending, "
|
|
1290
|
+
requireNoExtraArgs(orchestrationPending, "rig remote orchestrate-start [--max-workers <n>] [--max-iterations <n>] [--direct-merge]");
|
|
458
1291
|
const response = await withClient((client) => client.startOrchestration({
|
|
459
1292
|
maxWorkers: parseOptionalPositiveInt(maxWorkersResult.value, "--max-workers"),
|
|
460
1293
|
maxIterations: parseOptionalPositiveInt(maxIterationsResult.value, "--max-iterations"),
|
|
@@ -475,8 +1308,8 @@ async function executeRemote(context, args) {
|
|
|
475
1308
|
let orchestrationPending = pending;
|
|
476
1309
|
const idResult = takeOption(orchestrationPending, "--id");
|
|
477
1310
|
orchestrationPending = idResult.rest;
|
|
478
|
-
requireNoExtraArgs(orchestrationPending, `
|
|
479
|
-
const orchestrationId = requireTask(idResult.value, `
|
|
1311
|
+
requireNoExtraArgs(orchestrationPending, `rig remote ${command} --id <orch-id> [--remote <alias>] [--host <host>] [--port <n>] [--token <token>]`);
|
|
1312
|
+
const orchestrationId = requireTask(idResult.value, `rig remote ${command} --id <orch-id> [--remote <alias>] [--host <host>] [--port <n>] [--token <token>]`);
|
|
480
1313
|
const response = await withClient(async (client) => {
|
|
481
1314
|
switch (command) {
|
|
482
1315
|
case "orchestrate-pause":
|
|
@@ -493,11 +1326,11 @@ async function executeRemote(context, args) {
|
|
|
493
1326
|
return { ok: true, group: "remote", command, details: toDetails(response) };
|
|
494
1327
|
}
|
|
495
1328
|
default:
|
|
496
|
-
throw new
|
|
1329
|
+
throw new CliError(`Unknown remote command: ${command}`, 1, { hint: "Run `rig remote --help` to list remote commands." });
|
|
497
1330
|
}
|
|
498
1331
|
} catch (error) {
|
|
499
1332
|
if (error instanceof RemoteCliError) {
|
|
500
|
-
throw new
|
|
1333
|
+
throw new CliError(`[${error.code}] ${error.message}`, error.exitCode);
|
|
501
1334
|
}
|
|
502
1335
|
throw error;
|
|
503
1336
|
}
|