@h-rig/cli 0.0.6-alpha.94 → 0.0.6-alpha.96
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 +6 -27
- package/bin/rig.js +16 -0
- package/package.json +14 -40
- package/dist/bin/build-rig-binaries.d.ts +0 -2
- package/dist/bin/build-rig-binaries.js +0 -119
- package/dist/bin/rig.d.ts +0 -72
- package/dist/bin/rig.js +0 -17855
- package/dist/config/rig-default-config.yml +0 -5
- package/dist/src/app/drone-ui.d.ts +0 -34
- package/dist/src/app/drone-ui.js +0 -278
- package/dist/src/commands/_async-ui.d.ts +0 -10
- package/dist/src/commands/_async-ui.js +0 -121
- package/dist/src/commands/_cli-format.d.ts +0 -56
- package/dist/src/commands/_cli-format.js +0 -319
- package/dist/src/commands/_connection-state.d.ts +0 -54
- package/dist/src/commands/_connection-state.js +0 -187
- package/dist/src/commands/_doctor-checks.d.ts +0 -46
- package/dist/src/commands/_doctor-checks.js +0 -352
- package/dist/src/commands/_help-catalog.d.ts +0 -51
- package/dist/src/commands/_help-catalog.js +0 -502
- package/dist/src/commands/_inprocess-services.d.ts +0 -33
- package/dist/src/commands/_inprocess-services.js +0 -102
- package/dist/src/commands/_json-output.d.ts +0 -11
- package/dist/src/commands/_json-output.js +0 -60
- package/dist/src/commands/_lazy-reconcile.d.ts +0 -34
- package/dist/src/commands/_lazy-reconcile.js +0 -102
- package/dist/src/commands/_parsers.d.ts +0 -15
- package/dist/src/commands/_parsers.js +0 -114
- package/dist/src/commands/_paths.d.ts +0 -11
- package/dist/src/commands/_paths.js +0 -50
- package/dist/src/commands/_pi-frontend.d.ts +0 -35
- package/dist/src/commands/_pi-frontend.js +0 -64
- package/dist/src/commands/_pi-install.d.ts +0 -42
- package/dist/src/commands/_pi-install.js +0 -167
- package/dist/src/commands/_policy.d.ts +0 -8
- package/dist/src/commands/_policy.js +0 -127
- package/dist/src/commands/_probes.d.ts +0 -1
- package/dist/src/commands/_probes.js +0 -13
- package/dist/src/commands/_run-bridge.d.ts +0 -114
- package/dist/src/commands/_run-bridge.js +0 -387
- package/dist/src/commands/_run-diagnostics.d.ts +0 -9
- package/dist/src/commands/_run-diagnostics.js +0 -51
- package/dist/src/commands/_run-driver-helpers.d.ts +0 -26
- package/dist/src/commands/_run-driver-helpers.js +0 -138
- package/dist/src/commands/_run-projection.d.ts +0 -50
- package/dist/src/commands/_run-projection.js +0 -349
- package/dist/src/commands/_run-subcommands.d.ts +0 -3
- package/dist/src/commands/_run-subcommands.js +0 -31
- package/dist/src/commands/_spinner.d.ts +0 -25
- package/dist/src/commands/_spinner.js +0 -65
- package/dist/src/commands/agent.d.ts +0 -3
- package/dist/src/commands/agent.js +0 -8837
- package/dist/src/commands/dist.d.ts +0 -28
- package/dist/src/commands/dist.js +0 -417
- package/dist/src/commands/doctor.d.ts +0 -3
- package/dist/src/commands/doctor.js +0 -480
- package/dist/src/commands/github.d.ts +0 -3
- package/dist/src/commands/github.js +0 -342
- package/dist/src/commands/inbox.d.ts +0 -28
- package/dist/src/commands/inbox.js +0 -579
- package/dist/src/commands/init.d.ts +0 -64
- package/dist/src/commands/init.js +0 -1541
- package/dist/src/commands/inspect.d.ts +0 -20
- package/dist/src/commands/inspect.js +0 -764
- package/dist/src/commands/pi.d.ts +0 -3
- package/dist/src/commands/pi.js +0 -177
- package/dist/src/commands/plugin.d.ts +0 -16
- package/dist/src/commands/plugin.js +0 -714
- package/dist/src/commands/profile-and-review.d.ts +0 -4
- package/dist/src/commands/profile-and-review.js +0 -223
- package/dist/src/commands/queue.d.ts +0 -3
- package/dist/src/commands/queue.js +0 -197
- package/dist/src/commands/remote.d.ts +0 -3
- package/dist/src/commands/remote.js +0 -1337
- package/dist/src/commands/repo-git-harness.d.ts +0 -5
- package/dist/src/commands/repo-git-harness.js +0 -271
- package/dist/src/commands/run.d.ts +0 -21
- package/dist/src/commands/run.js +0 -17847
- package/dist/src/commands/server.d.ts +0 -3
- package/dist/src/commands/server.js +0 -249
- package/dist/src/commands/setup.d.ts +0 -16
- package/dist/src/commands/setup.js +0 -644
- package/dist/src/commands/stats.d.ts +0 -15
- package/dist/src/commands/stats.js +0 -979
- package/dist/src/commands/task-run-driver.d.ts +0 -94
- package/dist/src/commands/task-run-driver.js +0 -203
- package/dist/src/commands/task.d.ts +0 -35
- package/dist/src/commands/task.js +0 -818
- package/dist/src/commands/test.d.ts +0 -3
- package/dist/src/commands/test.js +0 -46
- package/dist/src/commands/triage.d.ts +0 -11
- package/dist/src/commands/triage.js +0 -227
- package/dist/src/commands/workspace.d.ts +0 -3
- package/dist/src/commands/workspace.js +0 -130
- package/dist/src/commands.d.ts +0 -13
- package/dist/src/commands.js +0 -17979
- package/dist/src/index.d.ts +0 -4
- package/dist/src/index.js +0 -17991
- package/dist/src/launcher.d.ts +0 -61
- package/dist/src/launcher.js +0 -199
- package/dist/src/operator-cli.d.ts +0 -2
- package/dist/src/operator-cli.js +0 -17847
- package/dist/src/operator-entry.d.ts +0 -1
- package/dist/src/operator-entry.js +0 -3
- package/dist/src/runner.d.ts +0 -47
- package/dist/src/runner.js +0 -267
- package/dist/src/withMutedConsole.d.ts +0 -2
- package/dist/src/withMutedConsole.js +0 -42
|
@@ -1,1337 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// packages/cli/src/runner.ts
|
|
3
|
-
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
4
|
-
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
5
|
-
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
6
|
-
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
7
|
-
|
|
8
|
-
class CliError extends RuntimeCliError {
|
|
9
|
-
hint;
|
|
10
|
-
constructor(message, exitCode = 1, options = {}) {
|
|
11
|
-
super(message, exitCode);
|
|
12
|
-
if (options.hint?.trim()) {
|
|
13
|
-
this.hint = options.hint.trim();
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
function takeFlag(args, flag) {
|
|
18
|
-
const rest = [];
|
|
19
|
-
let value = false;
|
|
20
|
-
for (const arg of args) {
|
|
21
|
-
if (arg === flag) {
|
|
22
|
-
value = true;
|
|
23
|
-
continue;
|
|
24
|
-
}
|
|
25
|
-
rest.push(arg);
|
|
26
|
-
}
|
|
27
|
-
return { value, rest };
|
|
28
|
-
}
|
|
29
|
-
function takeOption(args, option) {
|
|
30
|
-
const rest = [];
|
|
31
|
-
let value;
|
|
32
|
-
for (let index = 0;index < args.length; index += 1) {
|
|
33
|
-
const current = args[index];
|
|
34
|
-
if (current === option) {
|
|
35
|
-
const next = args[index + 1];
|
|
36
|
-
if (!next || next.startsWith("-")) {
|
|
37
|
-
throw new CliError(`Missing value for ${option}`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} <value>\`.` });
|
|
38
|
-
}
|
|
39
|
-
value = next;
|
|
40
|
-
index += 1;
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
if (current !== undefined) {
|
|
44
|
-
rest.push(current);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return { value, rest };
|
|
48
|
-
}
|
|
49
|
-
function requireNoExtraArgs(args, usage) {
|
|
50
|
-
if (args.length > 0) {
|
|
51
|
-
throw new CliError(`Unexpected arguments: ${args.join(" ")}
|
|
52
|
-
Usage: ${usage}`);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
function requireTask(taskId, usage) {
|
|
56
|
-
if (!taskId) {
|
|
57
|
-
throw new CliError(`Missing --task option.
|
|
58
|
-
Usage: ${usage}`);
|
|
59
|
-
}
|
|
60
|
-
return taskId;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// packages/runtime/src/control-plane/remote.ts
|
|
64
|
-
import { randomUUID } from "crypto";
|
|
65
|
-
import { chmodSync, existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
66
|
-
import { homedir } 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_TIMEOUTS = {
|
|
126
|
-
connectMs: 5000,
|
|
127
|
-
authMs: 5000,
|
|
128
|
-
requestMs: 30000,
|
|
129
|
-
subscriptionMs: 1e4
|
|
130
|
-
};
|
|
131
|
-
var REMOTES_CONFIG_PATH = join(homedir(), ".config", "rig", "remotes.toml");
|
|
132
|
-
|
|
133
|
-
class RemoteCliError extends Error {
|
|
134
|
-
code;
|
|
135
|
-
exitCode;
|
|
136
|
-
details;
|
|
137
|
-
constructor(code, message, exitCode = 1, details) {
|
|
138
|
-
super(message);
|
|
139
|
-
this.name = "RemoteCliError";
|
|
140
|
-
this.code = code;
|
|
141
|
-
this.exitCode = exitCode;
|
|
142
|
-
this.details = details;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
function loadRemotesConfig(configPath = REMOTES_CONFIG_PATH) {
|
|
146
|
-
if (!existsSync2(configPath)) {
|
|
147
|
-
return { version: 1, remotes: {} };
|
|
148
|
-
}
|
|
149
|
-
try {
|
|
150
|
-
const content = readFileSync(configPath, "utf-8");
|
|
151
|
-
if (!content.trim()) {
|
|
152
|
-
return { version: 1, remotes: {} };
|
|
153
|
-
}
|
|
154
|
-
const parsed = parseToml(content);
|
|
155
|
-
return {
|
|
156
|
-
version: parsed.version ?? 1,
|
|
157
|
-
remotes: parsed.remotes ?? {}
|
|
158
|
-
};
|
|
159
|
-
} catch (error) {
|
|
160
|
-
throw new RemoteCliError("RIG_REMOTE_CONFIG_PARSE_ERROR", `Failed to parse remotes config at ${configPath}: ${error instanceof Error ? error.message : String(error)}`, 2, { configPath });
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
function saveRemotesConfig(config, configPath = REMOTES_CONFIG_PATH) {
|
|
164
|
-
mkdirSync(dirname2(configPath), { recursive: true });
|
|
165
|
-
writeFileSync(configPath, `${stringifyToml(config).trimEnd()}
|
|
166
|
-
`, "utf-8");
|
|
167
|
-
try {
|
|
168
|
-
chmodSync(configPath, 384);
|
|
169
|
-
} catch {}
|
|
170
|
-
}
|
|
171
|
-
function readTomlFile(path, fallback) {
|
|
172
|
-
if (!existsSync2(path))
|
|
173
|
-
return fallback;
|
|
174
|
-
const raw = readFileSync(path, "utf8");
|
|
175
|
-
if (!raw.trim())
|
|
176
|
-
return fallback;
|
|
177
|
-
return parseToml(raw);
|
|
178
|
-
}
|
|
179
|
-
function writeTomlFile(path, value) {
|
|
180
|
-
mkdirSync(dirname2(path), { recursive: true });
|
|
181
|
-
writeFileSync(path, `${stringifyToml(value).trimEnd()}
|
|
182
|
-
`, "utf8");
|
|
183
|
-
}
|
|
184
|
-
function normalizeStringArray(value) {
|
|
185
|
-
if (!Array.isArray(value))
|
|
186
|
-
return [];
|
|
187
|
-
return value.map((entry) => normalizeString(entry)).filter((entry) => entry !== "");
|
|
188
|
-
}
|
|
189
|
-
function normalizePort(value) {
|
|
190
|
-
const port = typeof value === "number" ? value : Number(value);
|
|
191
|
-
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : DEFAULT_REMOTE_PORT;
|
|
192
|
-
}
|
|
193
|
-
function loadRemoteEndpointsToml(projectRoot) {
|
|
194
|
-
const paths = resolveAuthorityPaths(projectRoot);
|
|
195
|
-
const parsed = readTomlFile(paths.remoteEndpointsPath, { version: 1, endpoints: {} });
|
|
196
|
-
return {
|
|
197
|
-
version: parsed.version ?? 1,
|
|
198
|
-
endpoints: parsed.endpoints ?? {}
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
function loadRemoteSecretsToml(projectRoot) {
|
|
202
|
-
const paths = resolveAuthorityPaths(projectRoot);
|
|
203
|
-
const parsed = readTomlFile(paths.remoteSecretsPath, { version: 1, secrets: {} });
|
|
204
|
-
return {
|
|
205
|
-
version: parsed.version ?? 1,
|
|
206
|
-
secrets: parsed.secrets ?? {}
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
function saveRemoteEndpointsToml(projectRoot, payload) {
|
|
210
|
-
writeTomlFile(resolveAuthorityPaths(projectRoot).remoteEndpointsPath, payload);
|
|
211
|
-
}
|
|
212
|
-
function saveRemoteSecretsToml(projectRoot, payload) {
|
|
213
|
-
const paths = resolveAuthorityPaths(projectRoot);
|
|
214
|
-
writeTomlFile(paths.remoteSecretsPath, payload);
|
|
215
|
-
try {
|
|
216
|
-
chmodSync(paths.remoteSecretsPath, 384);
|
|
217
|
-
} catch {}
|
|
218
|
-
}
|
|
219
|
-
function listAuthorityRemoteEndpoints(projectRoot) {
|
|
220
|
-
const endpoints = loadRemoteEndpointsToml(projectRoot).endpoints ?? {};
|
|
221
|
-
const secrets = loadRemoteSecretsToml(projectRoot).secrets ?? {};
|
|
222
|
-
return Object.values(endpoints).map((entry) => {
|
|
223
|
-
const token = secrets[entry.secret_ref] ?? "";
|
|
224
|
-
return {
|
|
225
|
-
id: entry.id,
|
|
226
|
-
alias: entry.alias,
|
|
227
|
-
host: entry.host,
|
|
228
|
-
port: normalizePort(entry.port),
|
|
229
|
-
token,
|
|
230
|
-
autoConnect: Boolean(entry.auto_connect),
|
|
231
|
-
addedAt: entry.created_at,
|
|
232
|
-
updatedAt: entry.updated_at,
|
|
233
|
-
lastConnectedAt: normalizeString(entry.last_connected_at) || null,
|
|
234
|
-
labels: normalizeStringArray(entry.labels),
|
|
235
|
-
capabilities: normalizeStringArray(entry.capabilities),
|
|
236
|
-
transport: normalizeString(entry.transport) || "websocket",
|
|
237
|
-
secretRef: entry.secret_ref
|
|
238
|
-
};
|
|
239
|
-
}).sort((left, right) => left.alias.localeCompare(right.alias));
|
|
240
|
-
}
|
|
241
|
-
function nextRemoteEndpointRecord(input) {
|
|
242
|
-
const timestamp = new Date().toISOString();
|
|
243
|
-
const secretRef = input.existing?.secret_ref ?? randomUUID();
|
|
244
|
-
return {
|
|
245
|
-
record: {
|
|
246
|
-
id: input.endpointId ?? input.existing?.id ?? randomUUID(),
|
|
247
|
-
alias: input.alias,
|
|
248
|
-
host: input.host,
|
|
249
|
-
port: normalizePort(input.port),
|
|
250
|
-
transport: normalizeString(input.transport) || normalizeString(input.existing?.transport) || "websocket",
|
|
251
|
-
auto_connect: input.autoConnect ?? input.existing?.auto_connect ?? false,
|
|
252
|
-
labels: input.labels ?? normalizeStringArray(input.existing?.labels),
|
|
253
|
-
capabilities: input.capabilities ?? normalizeStringArray(input.existing?.capabilities),
|
|
254
|
-
secret_ref: secretRef,
|
|
255
|
-
created_at: input.existing?.created_at ?? timestamp,
|
|
256
|
-
updated_at: timestamp,
|
|
257
|
-
last_connected_at: input.existing?.last_connected_at ?? null
|
|
258
|
-
},
|
|
259
|
-
secretRef
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
function upsertAuthorityRemoteEndpoint(projectRoot, input) {
|
|
263
|
-
const endpointsToml = loadRemoteEndpointsToml(projectRoot);
|
|
264
|
-
const secretsToml = loadRemoteSecretsToml(projectRoot);
|
|
265
|
-
const existing = Object.values(endpointsToml.endpoints ?? {}).find((entry) => entry.alias === input.alias) ?? null;
|
|
266
|
-
const { record, secretRef } = nextRemoteEndpointRecord({
|
|
267
|
-
endpointId: input.endpointId,
|
|
268
|
-
existing,
|
|
269
|
-
alias: input.alias,
|
|
270
|
-
host: input.host,
|
|
271
|
-
port: input.port,
|
|
272
|
-
token: input.token,
|
|
273
|
-
autoConnect: input.autoConnect,
|
|
274
|
-
transport: input.transport,
|
|
275
|
-
labels: input.labels,
|
|
276
|
-
capabilities: input.capabilities
|
|
277
|
-
});
|
|
278
|
-
endpointsToml.endpoints = {
|
|
279
|
-
...endpointsToml.endpoints ?? {},
|
|
280
|
-
[record.id]: record
|
|
281
|
-
};
|
|
282
|
-
secretsToml.secrets = {
|
|
283
|
-
...secretsToml.secrets ?? {},
|
|
284
|
-
[secretRef]: input.token
|
|
285
|
-
};
|
|
286
|
-
saveRemoteEndpointsToml(projectRoot, endpointsToml);
|
|
287
|
-
saveRemoteSecretsToml(projectRoot, secretsToml);
|
|
288
|
-
return listAuthorityRemoteEndpoints(projectRoot).find((entry) => entry.id === record.id);
|
|
289
|
-
}
|
|
290
|
-
function updateAuthorityRemoteEndpoint(projectRoot, input) {
|
|
291
|
-
const endpointsToml = loadRemoteEndpointsToml(projectRoot);
|
|
292
|
-
const secretsToml = loadRemoteSecretsToml(projectRoot);
|
|
293
|
-
const current = Object.values(endpointsToml.endpoints ?? {}).find((entry) => input.endpointId && entry.id === input.endpointId || input.alias && entry.alias === input.alias);
|
|
294
|
-
if (!current)
|
|
295
|
-
return null;
|
|
296
|
-
const record = {
|
|
297
|
-
...current,
|
|
298
|
-
alias: normalizeString(input.alias) || current.alias,
|
|
299
|
-
host: normalizeString(input.host) || current.host,
|
|
300
|
-
port: input.port !== undefined ? normalizePort(input.port) : current.port,
|
|
301
|
-
auto_connect: input.autoConnect ?? current.auto_connect,
|
|
302
|
-
transport: normalizeString(input.transport) || current.transport,
|
|
303
|
-
labels: input.labels ?? normalizeStringArray(current.labels),
|
|
304
|
-
capabilities: input.capabilities ?? normalizeStringArray(current.capabilities),
|
|
305
|
-
updated_at: new Date().toISOString()
|
|
306
|
-
};
|
|
307
|
-
endpointsToml.endpoints = {
|
|
308
|
-
...endpointsToml.endpoints ?? {},
|
|
309
|
-
[record.id]: record
|
|
310
|
-
};
|
|
311
|
-
if (input.token !== undefined) {
|
|
312
|
-
secretsToml.secrets = {
|
|
313
|
-
...secretsToml.secrets ?? {},
|
|
314
|
-
[record.secret_ref]: input.token
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
saveRemoteEndpointsToml(projectRoot, endpointsToml);
|
|
318
|
-
saveRemoteSecretsToml(projectRoot, secretsToml);
|
|
319
|
-
return listAuthorityRemoteEndpoints(projectRoot).find((entry) => entry.id === record.id) ?? null;
|
|
320
|
-
}
|
|
321
|
-
function removeAuthorityRemoteEndpoint(projectRoot, endpointIdOrAlias) {
|
|
322
|
-
const endpointsToml = loadRemoteEndpointsToml(projectRoot);
|
|
323
|
-
const secretsToml = loadRemoteSecretsToml(projectRoot);
|
|
324
|
-
const entry = Object.values(endpointsToml.endpoints ?? {}).find((candidate) => candidate.id === endpointIdOrAlias || candidate.alias === endpointIdOrAlias);
|
|
325
|
-
if (!entry)
|
|
326
|
-
return false;
|
|
327
|
-
const nextEndpoints = { ...endpointsToml.endpoints ?? {} };
|
|
328
|
-
delete nextEndpoints[entry.id];
|
|
329
|
-
const nextSecrets = { ...secretsToml.secrets ?? {} };
|
|
330
|
-
delete nextSecrets[entry.secret_ref];
|
|
331
|
-
saveRemoteEndpointsToml(projectRoot, { version: endpointsToml.version ?? 1, endpoints: nextEndpoints });
|
|
332
|
-
saveRemoteSecretsToml(projectRoot, { version: secretsToml.version ?? 1, secrets: nextSecrets });
|
|
333
|
-
return true;
|
|
334
|
-
}
|
|
335
|
-
function markAuthorityRemoteEndpointConnected(projectRoot, endpointId, connectedAt = new Date().toISOString()) {
|
|
336
|
-
const endpointsToml = loadRemoteEndpointsToml(projectRoot);
|
|
337
|
-
const entry = endpointsToml.endpoints?.[endpointId];
|
|
338
|
-
if (!entry)
|
|
339
|
-
return;
|
|
340
|
-
endpointsToml.endpoints = {
|
|
341
|
-
...endpointsToml.endpoints ?? {},
|
|
342
|
-
[endpointId]: {
|
|
343
|
-
...entry,
|
|
344
|
-
last_connected_at: connectedAt,
|
|
345
|
-
updated_at: connectedAt
|
|
346
|
-
}
|
|
347
|
-
};
|
|
348
|
-
saveRemoteEndpointsToml(projectRoot, endpointsToml);
|
|
349
|
-
}
|
|
350
|
-
function importLegacyRemoteEndpoints(projectRoot, legacyPath = REMOTES_CONFIG_PATH) {
|
|
351
|
-
if (!existsSync2(legacyPath)) {
|
|
352
|
-
return { imported: 0, skipped: 0, sourcePath: legacyPath };
|
|
353
|
-
}
|
|
354
|
-
const parsed = readTomlFile(legacyPath, { version: 1, remotes: {} });
|
|
355
|
-
let imported = 0;
|
|
356
|
-
let skipped = 0;
|
|
357
|
-
for (const [alias, entry] of Object.entries(parsed.remotes ?? {})) {
|
|
358
|
-
const host = normalizeString(entry.host);
|
|
359
|
-
const token = normalizeString(entry.token);
|
|
360
|
-
if (!host || !token) {
|
|
361
|
-
skipped += 1;
|
|
362
|
-
continue;
|
|
363
|
-
}
|
|
364
|
-
upsertAuthorityRemoteEndpoint(projectRoot, {
|
|
365
|
-
alias,
|
|
366
|
-
host,
|
|
367
|
-
port: normalizePort(entry.port),
|
|
368
|
-
token
|
|
369
|
-
});
|
|
370
|
-
imported += 1;
|
|
371
|
-
}
|
|
372
|
-
return { imported, skipped, sourcePath: legacyPath };
|
|
373
|
-
}
|
|
374
|
-
function doctorAuthorityRemoteEndpoints(projectRoot) {
|
|
375
|
-
const paths = resolveAuthorityPaths(projectRoot);
|
|
376
|
-
const endpoints = loadRemoteEndpointsToml(projectRoot).endpoints ?? {};
|
|
377
|
-
const secrets = loadRemoteSecretsToml(projectRoot).secrets ?? {};
|
|
378
|
-
const missingSecrets = Object.values(endpoints).filter((entry) => !normalizeString(secrets[entry.secret_ref])).map((entry) => entry.alias);
|
|
379
|
-
const warnings = [];
|
|
380
|
-
if (!existsSync2(paths.remoteEndpointsPath)) {
|
|
381
|
-
warnings.push("Remote endpoint manifest is missing.");
|
|
382
|
-
}
|
|
383
|
-
if (!existsSync2(paths.remoteSecretsPath)) {
|
|
384
|
-
warnings.push("Remote secret store is missing.");
|
|
385
|
-
}
|
|
386
|
-
return {
|
|
387
|
-
manifestPath: paths.remoteEndpointsPath,
|
|
388
|
-
secretsPath: paths.remoteSecretsPath,
|
|
389
|
-
endpointCount: Object.keys(endpoints).length,
|
|
390
|
-
missingSecrets,
|
|
391
|
-
warnings
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
function listManagedRemoteEndpoints(configPath = REMOTES_CONFIG_PATH, projectRoot) {
|
|
395
|
-
if (projectRoot) {
|
|
396
|
-
return listAuthorityRemoteEndpoints(projectRoot).map((entry) => ({
|
|
397
|
-
id: entry.id,
|
|
398
|
-
alias: entry.alias,
|
|
399
|
-
host: entry.host,
|
|
400
|
-
port: entry.port,
|
|
401
|
-
token: entry.token,
|
|
402
|
-
addedAt: entry.addedAt,
|
|
403
|
-
lastConnected: entry.lastConnectedAt
|
|
404
|
-
}));
|
|
405
|
-
}
|
|
406
|
-
const config = loadRemotesConfig(configPath);
|
|
407
|
-
return Object.entries(config.remotes ?? {}).map(([alias, entry]) => ({
|
|
408
|
-
id: alias,
|
|
409
|
-
alias,
|
|
410
|
-
host: normalizeString(entry.host) || "",
|
|
411
|
-
port: Number.isFinite(entry.port) ? Number(entry.port) : DEFAULT_REMOTE_PORT,
|
|
412
|
-
token: normalizeString(entry.token) || "",
|
|
413
|
-
addedAt: normalizeString(entry.addedAt) || null,
|
|
414
|
-
lastConnected: normalizeString(entry.lastConnected) || null
|
|
415
|
-
})).sort((left, right) => left.alias.localeCompare(right.alias));
|
|
416
|
-
}
|
|
417
|
-
function upsertManagedRemoteEndpoint(input, configPath = REMOTES_CONFIG_PATH, projectRoot) {
|
|
418
|
-
if (projectRoot) {
|
|
419
|
-
const saved = upsertAuthorityRemoteEndpoint(projectRoot, {
|
|
420
|
-
...input,
|
|
421
|
-
token: input.token ?? ""
|
|
422
|
-
});
|
|
423
|
-
return {
|
|
424
|
-
id: saved.id,
|
|
425
|
-
alias: saved.alias,
|
|
426
|
-
host: saved.host,
|
|
427
|
-
port: saved.port,
|
|
428
|
-
token: saved.token,
|
|
429
|
-
addedAt: saved.addedAt,
|
|
430
|
-
lastConnected: saved.lastConnectedAt
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
const config = loadRemotesConfig(configPath);
|
|
434
|
-
const existing = config.remotes?.[input.alias];
|
|
435
|
-
const nextEntry = {
|
|
436
|
-
host: input.host,
|
|
437
|
-
port: input.port,
|
|
438
|
-
token: input.token,
|
|
439
|
-
addedAt: normalizeString(existing?.addedAt) || new Date().toISOString(),
|
|
440
|
-
...normalizeString(existing?.lastConnected) ? { lastConnected: normalizeString(existing?.lastConnected) } : {}
|
|
441
|
-
};
|
|
442
|
-
const nextConfig = {
|
|
443
|
-
version: config.version ?? 1,
|
|
444
|
-
remotes: {
|
|
445
|
-
...config.remotes ?? {},
|
|
446
|
-
[input.alias]: nextEntry
|
|
447
|
-
}
|
|
448
|
-
};
|
|
449
|
-
saveRemotesConfig(nextConfig, configPath);
|
|
450
|
-
return {
|
|
451
|
-
id: input.alias,
|
|
452
|
-
alias: input.alias,
|
|
453
|
-
host: input.host,
|
|
454
|
-
port: input.port,
|
|
455
|
-
token: input.token ?? "",
|
|
456
|
-
addedAt: nextEntry.addedAt ?? null,
|
|
457
|
-
lastConnected: nextEntry.lastConnected ?? null
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
function removeManagedRemoteEndpoint(alias, configPath = REMOTES_CONFIG_PATH, projectRoot) {
|
|
461
|
-
if (projectRoot) {
|
|
462
|
-
return removeAuthorityRemoteEndpoint(projectRoot, alias);
|
|
463
|
-
}
|
|
464
|
-
const config = loadRemotesConfig(configPath);
|
|
465
|
-
if (!config.remotes?.[alias]) {
|
|
466
|
-
return false;
|
|
467
|
-
}
|
|
468
|
-
const nextRemotes = { ...config.remotes ?? {} };
|
|
469
|
-
delete nextRemotes[alias];
|
|
470
|
-
saveRemotesConfig({
|
|
471
|
-
version: config.version ?? 1,
|
|
472
|
-
remotes: nextRemotes
|
|
473
|
-
}, configPath);
|
|
474
|
-
return true;
|
|
475
|
-
}
|
|
476
|
-
function resolveRemoteEndpoint(options) {
|
|
477
|
-
const env = options.env ?? process.env;
|
|
478
|
-
const envHost = normalizeString(env.RIG_REMOTE_HOST);
|
|
479
|
-
const envToken = normalizeString(env.RIG_REMOTE_TOKEN);
|
|
480
|
-
const envPort = parsePort(normalizeString(env.RIG_REMOTE_PORT), "RIG_REMOTE_PORT");
|
|
481
|
-
const envAlias = normalizeString(env.RIG_REMOTE_ALIAS);
|
|
482
|
-
const explicitHost = normalizeString(options.host);
|
|
483
|
-
const explicitToken = normalizeString(options.token);
|
|
484
|
-
const explicitPort = parsePort(normalizeString(options.port), "--port");
|
|
485
|
-
const explicitProvided = Boolean(explicitHost || explicitToken || explicitPort !== null);
|
|
486
|
-
const requestedAlias = normalizeString(options.remoteAlias) || envAlias;
|
|
487
|
-
let endpoint = {
|
|
488
|
-
source: "env",
|
|
489
|
-
host: envHost || "",
|
|
490
|
-
port: envPort ?? DEFAULT_REMOTE_PORT,
|
|
491
|
-
token: envToken || "",
|
|
492
|
-
configPath: REMOTES_CONFIG_PATH
|
|
493
|
-
};
|
|
494
|
-
if (requestedAlias) {
|
|
495
|
-
if (options.projectRoot) {
|
|
496
|
-
const remote = listAuthorityRemoteEndpoints(options.projectRoot).find((entry) => entry.alias === requestedAlias);
|
|
497
|
-
if (!remote) {
|
|
498
|
-
const configPath = resolveAuthorityPaths(options.projectRoot).remoteEndpointsPath;
|
|
499
|
-
throw new RemoteCliError("RIG_REMOTE_ALIAS_NOT_FOUND", `Remote alias '${requestedAlias}' was not found in ${configPath}.`, 2, { alias: requestedAlias, configPath });
|
|
500
|
-
}
|
|
501
|
-
endpoint = {
|
|
502
|
-
source: "alias",
|
|
503
|
-
alias: requestedAlias,
|
|
504
|
-
host: remote.host,
|
|
505
|
-
port: remote.port,
|
|
506
|
-
token: remote.token,
|
|
507
|
-
configPath: resolveAuthorityPaths(options.projectRoot).remoteEndpointsPath
|
|
508
|
-
};
|
|
509
|
-
} else {
|
|
510
|
-
const config = loadRemotesConfig();
|
|
511
|
-
const remote = config.remotes?.[requestedAlias];
|
|
512
|
-
if (!remote) {
|
|
513
|
-
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 });
|
|
514
|
-
}
|
|
515
|
-
endpoint = {
|
|
516
|
-
source: "alias",
|
|
517
|
-
alias: requestedAlias,
|
|
518
|
-
host: normalizeString(remote.host) || "",
|
|
519
|
-
port: Number.isFinite(remote.port) ? Number(remote.port) : DEFAULT_REMOTE_PORT,
|
|
520
|
-
token: normalizeString(remote.token) || "",
|
|
521
|
-
configPath: REMOTES_CONFIG_PATH
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
if (explicitProvided) {
|
|
526
|
-
endpoint = {
|
|
527
|
-
...endpoint,
|
|
528
|
-
source: "flags",
|
|
529
|
-
host: explicitHost || endpoint.host,
|
|
530
|
-
port: explicitPort ?? endpoint.port,
|
|
531
|
-
token: explicitToken || endpoint.token
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
|
-
if (!endpoint.host) {
|
|
535
|
-
throw new RemoteCliError("RIG_REMOTE_HOST_REQUIRED", "Remote host is required. Pass --host, --remote <alias>, or set RIG_REMOTE_HOST.", 2);
|
|
536
|
-
}
|
|
537
|
-
if (!Number.isFinite(endpoint.port) || endpoint.port <= 0 || endpoint.port > 65535) {
|
|
538
|
-
throw new RemoteCliError("RIG_REMOTE_INVALID_PORT", `Invalid remote port: ${endpoint.port}.`, 2);
|
|
539
|
-
}
|
|
540
|
-
return endpoint;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
class RemoteWsClient {
|
|
544
|
-
endpoint;
|
|
545
|
-
ws = null;
|
|
546
|
-
connected = false;
|
|
547
|
-
pending = new Map;
|
|
548
|
-
onEvent;
|
|
549
|
-
requestTimeoutMs;
|
|
550
|
-
connectTimeoutMs;
|
|
551
|
-
authTimeoutMs;
|
|
552
|
-
subscriptionTimeoutMs;
|
|
553
|
-
connectionToken = null;
|
|
554
|
-
connectionTokenExpiresAt = null;
|
|
555
|
-
tokenRefreshTimer = null;
|
|
556
|
-
constructor(endpoint, options = {}) {
|
|
557
|
-
this.endpoint = endpoint;
|
|
558
|
-
this.onEvent = options.onEvent;
|
|
559
|
-
this.connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_TIMEOUTS.connectMs;
|
|
560
|
-
this.authTimeoutMs = options.authTimeoutMs ?? DEFAULT_TIMEOUTS.authMs;
|
|
561
|
-
this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_TIMEOUTS.requestMs;
|
|
562
|
-
this.subscriptionTimeoutMs = options.subscriptionTimeoutMs ?? DEFAULT_TIMEOUTS.subscriptionMs;
|
|
563
|
-
}
|
|
564
|
-
setEventHandler(handler) {
|
|
565
|
-
this.onEvent = handler;
|
|
566
|
-
}
|
|
567
|
-
async connect() {
|
|
568
|
-
if (this.connected) {
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
const url = `ws://${this.endpoint.host}:${this.endpoint.port}`;
|
|
572
|
-
await new Promise((resolve3, reject) => {
|
|
573
|
-
let authResolved = false;
|
|
574
|
-
let connectTimer = setTimeout(() => {
|
|
575
|
-
connectTimer = null;
|
|
576
|
-
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 }));
|
|
577
|
-
}, this.connectTimeoutMs);
|
|
578
|
-
const ws = new WebSocket(url);
|
|
579
|
-
this.ws = ws;
|
|
580
|
-
ws.onopen = () => {
|
|
581
|
-
const authMessage = {
|
|
582
|
-
type: "auth",
|
|
583
|
-
id: crypto.randomUUID(),
|
|
584
|
-
timestamp: new Date().toISOString(),
|
|
585
|
-
token: this.connectionToken || this.endpoint.token,
|
|
586
|
-
tokenType: this.connectionToken ? "connection" : "server"
|
|
587
|
-
};
|
|
588
|
-
let authTimer = setTimeout(() => {
|
|
589
|
-
authTimer = null;
|
|
590
|
-
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 }));
|
|
591
|
-
}, this.authTimeoutMs);
|
|
592
|
-
ws.send(JSON.stringify(authMessage));
|
|
593
|
-
const originalOnMessage = ws.onmessage;
|
|
594
|
-
ws.onmessage = (event) => {
|
|
595
|
-
const message = parseIncomingMessage(event.data);
|
|
596
|
-
if (!message) {
|
|
597
|
-
return;
|
|
598
|
-
}
|
|
599
|
-
if (message.type === "auth_response" && !authResolved) {
|
|
600
|
-
authResolved = true;
|
|
601
|
-
if (connectTimer) {
|
|
602
|
-
clearTimeout(connectTimer);
|
|
603
|
-
connectTimer = null;
|
|
604
|
-
}
|
|
605
|
-
if (authTimer) {
|
|
606
|
-
clearTimeout(authTimer);
|
|
607
|
-
authTimer = null;
|
|
608
|
-
}
|
|
609
|
-
const auth = message;
|
|
610
|
-
if (!auth.success) {
|
|
611
|
-
reject(new RemoteCliError("RIG_REMOTE_AUTH_FAILED", auth.error || "Remote authentication failed.", 3, { host: this.endpoint.host, port: this.endpoint.port }));
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
|
-
if (auth.connectionToken && auth.connectionTokenExpiresAt) {
|
|
615
|
-
this.connectionToken = auth.connectionToken;
|
|
616
|
-
this.connectionTokenExpiresAt = auth.connectionTokenExpiresAt;
|
|
617
|
-
this.scheduleTokenRefresh();
|
|
618
|
-
}
|
|
619
|
-
if (this.endpoint.configPath.endsWith("endpoints.toml")) {
|
|
620
|
-
const projectRoot = dirname2(dirname2(dirname2(this.endpoint.configPath)));
|
|
621
|
-
try {
|
|
622
|
-
markAuthorityRemoteEndpointConnected(projectRoot, this.endpoint.alias ? listAuthorityRemoteEndpoints(projectRoot).find((entry) => entry.alias === this.endpoint.alias)?.id ?? "" : "");
|
|
623
|
-
} catch {}
|
|
624
|
-
}
|
|
625
|
-
this.connected = true;
|
|
626
|
-
ws.onmessage = (event2) => {
|
|
627
|
-
const next = parseIncomingMessage(event2.data);
|
|
628
|
-
if (!next) {
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
this.routeMessage(next);
|
|
632
|
-
};
|
|
633
|
-
resolve3();
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
636
|
-
if (typeof originalOnMessage === "function") {
|
|
637
|
-
originalOnMessage.call(ws, event);
|
|
638
|
-
}
|
|
639
|
-
};
|
|
640
|
-
};
|
|
641
|
-
ws.onerror = () => {
|
|
642
|
-
if (connectTimer) {
|
|
643
|
-
clearTimeout(connectTimer);
|
|
644
|
-
connectTimer = null;
|
|
645
|
-
}
|
|
646
|
-
if (!authResolved) {
|
|
647
|
-
reject(new RemoteCliError("RIG_REMOTE_CONNECT_FAILED", `Failed to connect to ${url}.`, 3, { host: this.endpoint.host, port: this.endpoint.port }));
|
|
648
|
-
}
|
|
649
|
-
};
|
|
650
|
-
ws.onclose = () => {
|
|
651
|
-
this.connected = false;
|
|
652
|
-
this.clearPending("Connection closed");
|
|
653
|
-
};
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
disconnect() {
|
|
657
|
-
this.connected = false;
|
|
658
|
-
this.clearTokenRefreshTimer();
|
|
659
|
-
this.clearPending("Connection closed");
|
|
660
|
-
if (this.ws) {
|
|
661
|
-
try {
|
|
662
|
-
this.ws.close();
|
|
663
|
-
} catch {}
|
|
664
|
-
this.ws = null;
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
async subscribe(eventTypes = []) {
|
|
668
|
-
const message = { type: "subscribe" };
|
|
669
|
-
if (eventTypes.length > 0) {
|
|
670
|
-
message.eventTypes = eventTypes;
|
|
671
|
-
}
|
|
672
|
-
const response = await this.request(message, this.subscriptionTimeoutMs);
|
|
673
|
-
return expectType(response, "operation_result");
|
|
674
|
-
}
|
|
675
|
-
async unsubscribe() {
|
|
676
|
-
return expectType(await this.request({ type: "unsubscribe" }), "operation_result");
|
|
677
|
-
}
|
|
678
|
-
async getState() {
|
|
679
|
-
return expectType(await this.request({ type: "get_state" }), "state_response");
|
|
680
|
-
}
|
|
681
|
-
async getTasks() {
|
|
682
|
-
return expectType(await this.request({ type: "get_tasks" }), "tasks_response");
|
|
683
|
-
}
|
|
684
|
-
async pause() {
|
|
685
|
-
return expectType(await this.request({ type: "pause" }), "operation_result");
|
|
686
|
-
}
|
|
687
|
-
async resume() {
|
|
688
|
-
return expectType(await this.request({ type: "resume" }), "operation_result");
|
|
689
|
-
}
|
|
690
|
-
async interrupt() {
|
|
691
|
-
return expectType(await this.request({ type: "interrupt" }), "operation_result");
|
|
692
|
-
}
|
|
693
|
-
async continueExecution() {
|
|
694
|
-
return expectType(await this.request({ type: "continue" }), "operation_result");
|
|
695
|
-
}
|
|
696
|
-
async refreshTasks() {
|
|
697
|
-
return expectType(await this.request({ type: "refresh_tasks" }), "operation_result");
|
|
698
|
-
}
|
|
699
|
-
async addIterations(count) {
|
|
700
|
-
return expectType(await this.request({ type: "add_iterations", count }), "operation_result");
|
|
701
|
-
}
|
|
702
|
-
async removeIterations(count) {
|
|
703
|
-
return expectType(await this.request({ type: "remove_iterations", count }), "operation_result");
|
|
704
|
-
}
|
|
705
|
-
async getPromptPreview(taskId) {
|
|
706
|
-
return expectType(await this.request({ type: "get_prompt_preview", taskId }), "prompt_preview_response");
|
|
707
|
-
}
|
|
708
|
-
async getIterationOutput(taskId) {
|
|
709
|
-
return expectType(await this.request({ type: "get_iteration_output", taskId }), "iteration_output_response");
|
|
710
|
-
}
|
|
711
|
-
async startOrchestration(options) {
|
|
712
|
-
return expectType(await this.request({
|
|
713
|
-
type: "orchestrate:start",
|
|
714
|
-
maxWorkers: options.maxWorkers,
|
|
715
|
-
maxIterations: options.maxIterations,
|
|
716
|
-
directMerge: options.directMerge
|
|
717
|
-
}), "orchestrate:start_response");
|
|
718
|
-
}
|
|
719
|
-
async pauseOrchestration(orchestrationId) {
|
|
720
|
-
return expectType(await this.request({ type: "orchestrate:pause", orchestrationId }), "operation_result");
|
|
721
|
-
}
|
|
722
|
-
async resumeOrchestration(orchestrationId) {
|
|
723
|
-
return expectType(await this.request({ type: "orchestrate:resume", orchestrationId }), "operation_result");
|
|
724
|
-
}
|
|
725
|
-
async stopOrchestration(orchestrationId) {
|
|
726
|
-
return expectType(await this.request({ type: "orchestrate:stop", orchestrationId }), "operation_result");
|
|
727
|
-
}
|
|
728
|
-
async getOrchestrationState(orchestrationId) {
|
|
729
|
-
return expectType(await this.request({ type: "orchestrate:get_state", orchestrationId }), "orchestrate:state_response");
|
|
730
|
-
}
|
|
731
|
-
async ping() {
|
|
732
|
-
return this.request({ type: "ping" });
|
|
733
|
-
}
|
|
734
|
-
async request(payload, timeoutMs = this.requestTimeoutMs) {
|
|
735
|
-
if (!this.connected || !this.ws) {
|
|
736
|
-
throw new RemoteCliError("RIG_REMOTE_NOT_CONNECTED", "Remote client is not connected.", 3);
|
|
737
|
-
}
|
|
738
|
-
const id = crypto.randomUUID();
|
|
739
|
-
const message = {
|
|
740
|
-
...payload,
|
|
741
|
-
id,
|
|
742
|
-
timestamp: new Date().toISOString()
|
|
743
|
-
};
|
|
744
|
-
return await new Promise((resolve3, reject) => {
|
|
745
|
-
const timeout = setTimeout(() => {
|
|
746
|
-
this.pending.delete(id);
|
|
747
|
-
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 }));
|
|
748
|
-
}, timeoutMs);
|
|
749
|
-
this.pending.set(id, { resolve: resolve3, reject, timeout });
|
|
750
|
-
this.ws?.send(JSON.stringify(message));
|
|
751
|
-
});
|
|
752
|
-
}
|
|
753
|
-
routeMessage(message) {
|
|
754
|
-
if (message.id && this.pending.has(message.id)) {
|
|
755
|
-
const pending = this.pending.get(message.id);
|
|
756
|
-
if (pending) {
|
|
757
|
-
clearTimeout(pending.timeout);
|
|
758
|
-
this.pending.delete(message.id);
|
|
759
|
-
pending.resolve(message);
|
|
760
|
-
}
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
|
-
if (message.type === "token_refresh_response") {
|
|
764
|
-
const token = normalizeString(message.connectionToken);
|
|
765
|
-
const expiresAt = normalizeString(message.connectionTokenExpiresAt);
|
|
766
|
-
if (token && expiresAt) {
|
|
767
|
-
this.connectionToken = token;
|
|
768
|
-
this.connectionTokenExpiresAt = expiresAt;
|
|
769
|
-
this.scheduleTokenRefresh();
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
this.onEvent?.({ receivedAt: new Date().toISOString(), message });
|
|
773
|
-
}
|
|
774
|
-
scheduleTokenRefresh() {
|
|
775
|
-
this.clearTokenRefreshTimer();
|
|
776
|
-
if (!this.connectionToken || !this.connectionTokenExpiresAt || !this.ws || !this.connected) {
|
|
777
|
-
return;
|
|
778
|
-
}
|
|
779
|
-
const expiresAt = new Date(this.connectionTokenExpiresAt).getTime();
|
|
780
|
-
if (!Number.isFinite(expiresAt)) {
|
|
781
|
-
return;
|
|
782
|
-
}
|
|
783
|
-
const refreshAt = expiresAt - 60 * 60 * 1000;
|
|
784
|
-
const delay = Math.max(0, refreshAt - Date.now());
|
|
785
|
-
this.tokenRefreshTimer = setTimeout(() => {
|
|
786
|
-
if (!this.connected || !this.ws || !this.connectionToken) {
|
|
787
|
-
return;
|
|
788
|
-
}
|
|
789
|
-
const refreshMessage = {
|
|
790
|
-
type: "token_refresh",
|
|
791
|
-
id: crypto.randomUUID(),
|
|
792
|
-
timestamp: new Date().toISOString(),
|
|
793
|
-
connectionToken: this.connectionToken
|
|
794
|
-
};
|
|
795
|
-
this.ws.send(JSON.stringify(refreshMessage));
|
|
796
|
-
}, delay);
|
|
797
|
-
}
|
|
798
|
-
clearTokenRefreshTimer() {
|
|
799
|
-
if (this.tokenRefreshTimer) {
|
|
800
|
-
clearTimeout(this.tokenRefreshTimer);
|
|
801
|
-
this.tokenRefreshTimer = null;
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
clearPending(reason) {
|
|
805
|
-
for (const [requestId, pending] of this.pending.entries()) {
|
|
806
|
-
clearTimeout(pending.timeout);
|
|
807
|
-
pending.reject(new RemoteCliError("RIG_REMOTE_REQUEST_CANCELLED", `${reason} (request ${requestId}).`, 3, { requestId }));
|
|
808
|
-
this.pending.delete(requestId);
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
function matchesEventFilter(message, expectedType) {
|
|
813
|
-
if (!expectedType) {
|
|
814
|
-
return true;
|
|
815
|
-
}
|
|
816
|
-
if (message.type === expectedType) {
|
|
817
|
-
return true;
|
|
818
|
-
}
|
|
819
|
-
if (message.type === "engine_event") {
|
|
820
|
-
const eventType = normalizeString(message.event?.type);
|
|
821
|
-
return eventType === expectedType;
|
|
822
|
-
}
|
|
823
|
-
if (message.type === "parallel_event") {
|
|
824
|
-
const eventType = normalizeString(message.event?.type);
|
|
825
|
-
return eventType === expectedType;
|
|
826
|
-
}
|
|
827
|
-
return false;
|
|
828
|
-
}
|
|
829
|
-
function summarizeMessage(message) {
|
|
830
|
-
if (message.type === "pong") {
|
|
831
|
-
return "pong";
|
|
832
|
-
}
|
|
833
|
-
if (message.type === "engine_event") {
|
|
834
|
-
const eventType = normalizeString(message.event?.type);
|
|
835
|
-
return eventType ? `engine_event:${eventType}` : "engine_event";
|
|
836
|
-
}
|
|
837
|
-
if (message.type === "parallel_event") {
|
|
838
|
-
const eventType = normalizeString(message.event?.type);
|
|
839
|
-
const orchestrationId = normalizeString(message.orchestrationId);
|
|
840
|
-
return orchestrationId ? `parallel_event:${eventType || "unknown"}:${orchestrationId}` : `parallel_event:${eventType || "unknown"}`;
|
|
841
|
-
}
|
|
842
|
-
return message.type;
|
|
843
|
-
}
|
|
844
|
-
function migrateManagedRemoteEndpoints(projectRoot, legacyPath = REMOTES_CONFIG_PATH) {
|
|
845
|
-
return importLegacyRemoteEndpoints(projectRoot, legacyPath);
|
|
846
|
-
}
|
|
847
|
-
function doctorManagedRemoteEndpoints(projectRoot) {
|
|
848
|
-
return doctorAuthorityRemoteEndpoints(projectRoot);
|
|
849
|
-
}
|
|
850
|
-
function updateManagedRemoteEndpointInAuthority(projectRoot, input) {
|
|
851
|
-
const updated = updateAuthorityRemoteEndpoint(projectRoot, input);
|
|
852
|
-
if (!updated)
|
|
853
|
-
return null;
|
|
854
|
-
return {
|
|
855
|
-
id: updated.id,
|
|
856
|
-
alias: updated.alias,
|
|
857
|
-
host: updated.host,
|
|
858
|
-
port: updated.port,
|
|
859
|
-
token: updated.token,
|
|
860
|
-
addedAt: updated.addedAt,
|
|
861
|
-
lastConnected: updated.lastConnectedAt
|
|
862
|
-
};
|
|
863
|
-
}
|
|
864
|
-
function normalizeString(value) {
|
|
865
|
-
if (!value) {
|
|
866
|
-
return "";
|
|
867
|
-
}
|
|
868
|
-
return value.trim();
|
|
869
|
-
}
|
|
870
|
-
function parsePort(value, label) {
|
|
871
|
-
if (!value) {
|
|
872
|
-
return null;
|
|
873
|
-
}
|
|
874
|
-
const parsed = Number.parseInt(value, 10);
|
|
875
|
-
if (!Number.isFinite(parsed) || parsed <= 0 || parsed > 65535) {
|
|
876
|
-
throw new RemoteCliError("RIG_REMOTE_INVALID_PORT", `Invalid port for ${label}: ${value}.`, 2, { label, value });
|
|
877
|
-
}
|
|
878
|
-
return parsed;
|
|
879
|
-
}
|
|
880
|
-
function parseIncomingMessage(raw) {
|
|
881
|
-
if (typeof raw !== "string") {
|
|
882
|
-
return null;
|
|
883
|
-
}
|
|
884
|
-
try {
|
|
885
|
-
const parsed = JSON.parse(raw);
|
|
886
|
-
if (!parsed || typeof parsed !== "object" || typeof parsed.type !== "string") {
|
|
887
|
-
return null;
|
|
888
|
-
}
|
|
889
|
-
return parsed;
|
|
890
|
-
} catch {
|
|
891
|
-
return null;
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
function expectType(message, expectedType) {
|
|
895
|
-
if (message.type !== expectedType) {
|
|
896
|
-
throw new RemoteCliError("RIG_REMOTE_UNEXPECTED_RESPONSE", `Unexpected response type: ${message.type}. Expected ${expectedType}.`, 3, { expectedType, actualType: message.type, message });
|
|
897
|
-
}
|
|
898
|
-
return message;
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
// packages/cli/src/commands/_parsers.ts
|
|
902
|
-
function parseOptionalPositiveInt(value, option) {
|
|
903
|
-
if (!value) {
|
|
904
|
-
return;
|
|
905
|
-
}
|
|
906
|
-
const parsed = Number.parseInt(value, 10);
|
|
907
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
908
|
-
throw new CliError(`Invalid ${option} value: ${value}`, 1, { hint: `Pass a positive integer, e.g. \`${option} 10\`.` });
|
|
909
|
-
}
|
|
910
|
-
return parsed;
|
|
911
|
-
}
|
|
912
|
-
function parseRequiredPositiveInt(value, option) {
|
|
913
|
-
if (!value) {
|
|
914
|
-
throw new CliError(`Missing value for ${option}.`, 1, { hint: `Provide a value after ${option}, e.g. \`${option} 10\`.` });
|
|
915
|
-
}
|
|
916
|
-
const parsed = Number.parseInt(value, 10);
|
|
917
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
918
|
-
throw new CliError(`Invalid ${option} value: ${value}`, 1, { hint: `Pass a positive integer, e.g. \`${option} 10\`.` });
|
|
919
|
-
}
|
|
920
|
-
return parsed;
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
// packages/cli/src/commands/remote.ts
|
|
924
|
-
function assertRemoteOperationSuccess(command, response) {
|
|
925
|
-
if (!("success" in response)) {
|
|
926
|
-
return;
|
|
927
|
-
}
|
|
928
|
-
if (response.success === false) {
|
|
929
|
-
const error = typeof response.error === "string" && response.error || `${command} failed without explicit error detail from remote server.`;
|
|
930
|
-
throw new RemoteCliError("RIG_REMOTE_OPERATION_FAILED", error, 3, { command, response: redactSecretFields(response) });
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
function redactRemoteEndpoint(endpoint) {
|
|
934
|
-
const { token, ...rest } = endpoint;
|
|
935
|
-
return {
|
|
936
|
-
...rest,
|
|
937
|
-
token: "",
|
|
938
|
-
tokenConfigured: token.trim().length > 0
|
|
939
|
-
};
|
|
940
|
-
}
|
|
941
|
-
function isRecord(value) {
|
|
942
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
943
|
-
}
|
|
944
|
-
function isSecretKey(key) {
|
|
945
|
-
const normalized = key.toLowerCase();
|
|
946
|
-
return normalized === "token" || normalized === "authtoken" || normalized === "authorization" || normalized === "connectiontoken" || normalized === "access_token" || normalized === "secret";
|
|
947
|
-
}
|
|
948
|
-
function redactSecretFields(value) {
|
|
949
|
-
if (Array.isArray(value)) {
|
|
950
|
-
return value.map((entry) => redactSecretFields(entry));
|
|
951
|
-
}
|
|
952
|
-
if (!isRecord(value)) {
|
|
953
|
-
return value;
|
|
954
|
-
}
|
|
955
|
-
const redacted = {};
|
|
956
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
957
|
-
redacted[key] = isSecretKey(key) && typeof entry === "string" ? entry.trim().length > 0 ? "[redacted]" : "" : redactSecretFields(entry);
|
|
958
|
-
}
|
|
959
|
-
return redacted;
|
|
960
|
-
}
|
|
961
|
-
async function executeRemote(context, args) {
|
|
962
|
-
const [command = "status", ...rest] = args;
|
|
963
|
-
if (command === "endpoint" || command === "endpoints") {
|
|
964
|
-
const [subcommand = "list", ...subRest] = rest;
|
|
965
|
-
switch (subcommand) {
|
|
966
|
-
case "list": {
|
|
967
|
-
requireNoExtraArgs(subRest, "rig remote endpoint list");
|
|
968
|
-
const endpoints = listManagedRemoteEndpoints(undefined, context.projectRoot);
|
|
969
|
-
const redactedEndpoints = endpoints.map((endpoint2) => redactRemoteEndpoint(endpoint2));
|
|
970
|
-
if (context.outputMode === "text") {
|
|
971
|
-
if (endpoints.length === 0) {
|
|
972
|
-
console.log("No remote endpoints configured.");
|
|
973
|
-
} else {
|
|
974
|
-
for (const endpoint2 of endpoints) {
|
|
975
|
-
console.log(`- ${endpoint2.alias} \xB7 ${endpoint2.host}:${endpoint2.port}${endpoint2.lastConnected ? ` \xB7 last connected ${endpoint2.lastConnected}` : ""}`);
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
return { ok: true, group: "remote", command: "endpoint list", details: { endpoints: redactedEndpoints } };
|
|
980
|
-
}
|
|
981
|
-
case "add": {
|
|
982
|
-
let pending2 = subRest;
|
|
983
|
-
const alias = takeOption(pending2, "--alias");
|
|
984
|
-
pending2 = alias.rest;
|
|
985
|
-
const host = takeOption(pending2, "--host");
|
|
986
|
-
pending2 = host.rest;
|
|
987
|
-
const port = takeOption(pending2, "--port");
|
|
988
|
-
pending2 = port.rest;
|
|
989
|
-
const token = takeOption(pending2, "--token");
|
|
990
|
-
pending2 = token.rest;
|
|
991
|
-
requireNoExtraArgs(pending2, "rig remote endpoint add --alias <a> --host <h> --port <n> --token <t>");
|
|
992
|
-
if (!alias.value || !host.value || !token.value || !port.value) {
|
|
993
|
-
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>`." });
|
|
994
|
-
}
|
|
995
|
-
const created = upsertManagedRemoteEndpoint({
|
|
996
|
-
alias: alias.value,
|
|
997
|
-
host: host.value,
|
|
998
|
-
port: parseRequiredPositiveInt(port.value, "--port"),
|
|
999
|
-
token: token.value
|
|
1000
|
-
}, undefined, context.projectRoot);
|
|
1001
|
-
if (context.outputMode === "text") {
|
|
1002
|
-
console.log(`Saved remote endpoint ${created.alias} -> ${created.host}:${created.port}`);
|
|
1003
|
-
}
|
|
1004
|
-
return { ok: true, group: "remote", command: "endpoint add", details: redactRemoteEndpoint(created) };
|
|
1005
|
-
}
|
|
1006
|
-
case "update": {
|
|
1007
|
-
let pending2 = subRest;
|
|
1008
|
-
const endpointId = takeOption(pending2, "--id");
|
|
1009
|
-
pending2 = endpointId.rest;
|
|
1010
|
-
const alias = takeOption(pending2, "--alias");
|
|
1011
|
-
pending2 = alias.rest;
|
|
1012
|
-
const host = takeOption(pending2, "--host");
|
|
1013
|
-
pending2 = host.rest;
|
|
1014
|
-
const port = takeOption(pending2, "--port");
|
|
1015
|
-
pending2 = port.rest;
|
|
1016
|
-
const token = takeOption(pending2, "--token");
|
|
1017
|
-
pending2 = token.rest;
|
|
1018
|
-
requireNoExtraArgs(pending2, "rig remote endpoint update --id <id> [--alias <a>] [--host <h>] [--port <n>] [--token <t>]");
|
|
1019
|
-
if (!endpointId.value && !alias.value) {
|
|
1020
|
-
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." });
|
|
1021
|
-
}
|
|
1022
|
-
const updated = updateManagedRemoteEndpointInAuthority(context.projectRoot, {
|
|
1023
|
-
endpointId: endpointId.value || undefined,
|
|
1024
|
-
alias: alias.value || undefined,
|
|
1025
|
-
host: host.value || undefined,
|
|
1026
|
-
port: port.value ? parseRequiredPositiveInt(port.value, "--port") : undefined,
|
|
1027
|
-
token: token.value || undefined
|
|
1028
|
-
});
|
|
1029
|
-
if (!updated) {
|
|
1030
|
-
throw new CliError("Remote endpoint not found.", 2, { hint: "Run `rig remote endpoint list` to see configured endpoints." });
|
|
1031
|
-
}
|
|
1032
|
-
if (context.outputMode === "text") {
|
|
1033
|
-
console.log(`Updated remote endpoint ${updated.alias} -> ${updated.host}:${updated.port}`);
|
|
1034
|
-
}
|
|
1035
|
-
return { ok: true, group: "remote", command: "endpoint update", details: redactRemoteEndpoint(updated) };
|
|
1036
|
-
}
|
|
1037
|
-
case "remove": {
|
|
1038
|
-
let pending2 = subRest;
|
|
1039
|
-
const alias = takeOption(pending2, "--alias");
|
|
1040
|
-
pending2 = alias.rest;
|
|
1041
|
-
requireNoExtraArgs(pending2, "rig remote endpoint remove --alias <a>");
|
|
1042
|
-
if (!alias.value) {
|
|
1043
|
-
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>`." });
|
|
1044
|
-
}
|
|
1045
|
-
const removed = removeManagedRemoteEndpoint(alias.value, undefined, context.projectRoot);
|
|
1046
|
-
if (!removed) {
|
|
1047
|
-
throw new CliError(`Remote endpoint alias not found: ${alias.value}`, 2, { hint: "Run `rig remote endpoint list` to see configured endpoint aliases." });
|
|
1048
|
-
}
|
|
1049
|
-
if (context.outputMode === "text") {
|
|
1050
|
-
console.log(`Removed remote endpoint ${alias.value}`);
|
|
1051
|
-
}
|
|
1052
|
-
return { ok: true, group: "remote", command: "endpoint remove", details: { alias: alias.value } };
|
|
1053
|
-
}
|
|
1054
|
-
case "test": {
|
|
1055
|
-
let pending2 = subRest;
|
|
1056
|
-
const alias = takeOption(pending2, "--alias");
|
|
1057
|
-
pending2 = alias.rest;
|
|
1058
|
-
requireNoExtraArgs(pending2, "rig remote endpoint test --alias <a>");
|
|
1059
|
-
if (!alias.value) {
|
|
1060
|
-
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>`." });
|
|
1061
|
-
}
|
|
1062
|
-
const endpoint2 = resolveRemoteEndpoint({ remoteAlias: alias.value, projectRoot: context.projectRoot });
|
|
1063
|
-
const client = new RemoteWsClient(endpoint2);
|
|
1064
|
-
const startedAt = Date.now();
|
|
1065
|
-
try {
|
|
1066
|
-
await client.connect();
|
|
1067
|
-
const response = await client.ping();
|
|
1068
|
-
const latencyMs = Date.now() - startedAt;
|
|
1069
|
-
if (context.outputMode === "text") {
|
|
1070
|
-
console.log(`Remote endpoint ${alias.value} responded in ${latencyMs}ms.`);
|
|
1071
|
-
}
|
|
1072
|
-
return {
|
|
1073
|
-
ok: true,
|
|
1074
|
-
group: "remote",
|
|
1075
|
-
command: "endpoint test",
|
|
1076
|
-
details: {
|
|
1077
|
-
alias: alias.value,
|
|
1078
|
-
host: endpoint2.host,
|
|
1079
|
-
port: endpoint2.port,
|
|
1080
|
-
latencyMs,
|
|
1081
|
-
response: redactSecretFields(response)
|
|
1082
|
-
}
|
|
1083
|
-
};
|
|
1084
|
-
} finally {
|
|
1085
|
-
client.disconnect();
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
case "migrate": {
|
|
1089
|
-
requireNoExtraArgs(subRest, "rig remote endpoint migrate");
|
|
1090
|
-
const result = migrateManagedRemoteEndpoints(context.projectRoot);
|
|
1091
|
-
if (context.outputMode === "text") {
|
|
1092
|
-
console.log(`Imported ${result.imported} endpoint(s) from ${result.sourcePath}${result.skipped > 0 ? `, skipped ${result.skipped}` : ""}.`);
|
|
1093
|
-
}
|
|
1094
|
-
return { ok: true, group: "remote", command: "endpoint migrate", details: result };
|
|
1095
|
-
}
|
|
1096
|
-
case "doctor": {
|
|
1097
|
-
requireNoExtraArgs(subRest, "rig remote endpoint doctor");
|
|
1098
|
-
const result = doctorManagedRemoteEndpoints(context.projectRoot);
|
|
1099
|
-
if (context.outputMode === "text") {
|
|
1100
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1101
|
-
}
|
|
1102
|
-
return { ok: true, group: "remote", command: "endpoint doctor", details: result };
|
|
1103
|
-
}
|
|
1104
|
-
default:
|
|
1105
|
-
throw new CliError(`Unknown remote endpoint command: ${subcommand}`, 1, { hint: "Run `rig remote --help` for endpoint commands (list|add|update|remove|test)." });
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
let pending = rest;
|
|
1109
|
-
const remoteAliasResult = takeOption(pending, "--remote");
|
|
1110
|
-
pending = remoteAliasResult.rest;
|
|
1111
|
-
const hostResult = takeOption(pending, "--host");
|
|
1112
|
-
pending = hostResult.rest;
|
|
1113
|
-
const portResult = takeOption(pending, "--port");
|
|
1114
|
-
pending = portResult.rest;
|
|
1115
|
-
const tokenResult = takeOption(pending, "--token");
|
|
1116
|
-
pending = tokenResult.rest;
|
|
1117
|
-
const endpoint = resolveRemoteEndpoint({
|
|
1118
|
-
host: hostResult.value,
|
|
1119
|
-
port: portResult.value,
|
|
1120
|
-
token: tokenResult.value,
|
|
1121
|
-
remoteAlias: remoteAliasResult.value,
|
|
1122
|
-
projectRoot: context.projectRoot
|
|
1123
|
-
});
|
|
1124
|
-
const remoteMeta = {
|
|
1125
|
-
source: endpoint.source,
|
|
1126
|
-
alias: endpoint.alias || null,
|
|
1127
|
-
host: endpoint.host,
|
|
1128
|
-
port: endpoint.port
|
|
1129
|
-
};
|
|
1130
|
-
const withClient = async (runner) => {
|
|
1131
|
-
const client = new RemoteWsClient(endpoint);
|
|
1132
|
-
try {
|
|
1133
|
-
await client.connect();
|
|
1134
|
-
return await runner(client);
|
|
1135
|
-
} finally {
|
|
1136
|
-
client.disconnect();
|
|
1137
|
-
}
|
|
1138
|
-
};
|
|
1139
|
-
const toDetails = (response) => ({
|
|
1140
|
-
command: `remote ${command}`,
|
|
1141
|
-
remote: remoteMeta,
|
|
1142
|
-
requestId: response.id || null,
|
|
1143
|
-
messageType: response.type,
|
|
1144
|
-
payload: redactSecretFields(response),
|
|
1145
|
-
error: null
|
|
1146
|
-
});
|
|
1147
|
-
try {
|
|
1148
|
-
switch (command) {
|
|
1149
|
-
case "test": {
|
|
1150
|
-
requireNoExtraArgs(pending, "rig remote test [--remote <alias>] [--host <host>] [--port <n>] [--token <token>]");
|
|
1151
|
-
const response = await withClient((client) => client.ping());
|
|
1152
|
-
return { ok: true, group: "remote", command, details: toDetails(response) };
|
|
1153
|
-
}
|
|
1154
|
-
case "status": {
|
|
1155
|
-
requireNoExtraArgs(pending, "rig remote status [--remote <alias>] [--host <host>] [--port <n>] [--token <token>]");
|
|
1156
|
-
const response = await withClient((client) => client.getState());
|
|
1157
|
-
return { ok: true, group: "remote", command, details: toDetails(response) };
|
|
1158
|
-
}
|
|
1159
|
-
case "tasks": {
|
|
1160
|
-
requireNoExtraArgs(pending, "rig remote tasks [--remote <alias>] [--host <host>] [--port <n>] [--token <token>]");
|
|
1161
|
-
const response = await withClient((client) => client.getTasks());
|
|
1162
|
-
return { ok: true, group: "remote", command, details: toDetails(response) };
|
|
1163
|
-
}
|
|
1164
|
-
case "watch": {
|
|
1165
|
-
let watchPending = pending;
|
|
1166
|
-
const secondsResult = takeOption(watchPending, "--seconds");
|
|
1167
|
-
watchPending = secondsResult.rest;
|
|
1168
|
-
const eventResult = takeOption(watchPending, "--event");
|
|
1169
|
-
watchPending = eventResult.rest;
|
|
1170
|
-
requireNoExtraArgs(watchPending, "rig remote watch [--seconds <n>] [--event <type>] [--remote <alias>] [--host <host>] [--port <n>] [--token <token>]");
|
|
1171
|
-
const seconds = parseOptionalPositiveInt(secondsResult.value, "--seconds");
|
|
1172
|
-
const eventFilter = eventResult.value || undefined;
|
|
1173
|
-
if (context.outputMode === "json" && !seconds) {
|
|
1174
|
-
throw new CliError("--json remote watch requires --seconds <n> to produce bounded output.", 2, { hint: "Re-run as `rig remote watch --seconds 10 --json`." });
|
|
1175
|
-
}
|
|
1176
|
-
const captured = [];
|
|
1177
|
-
let dropped = 0;
|
|
1178
|
-
let received = 0;
|
|
1179
|
-
let stop = false;
|
|
1180
|
-
const onSignal = () => {
|
|
1181
|
-
stop = true;
|
|
1182
|
-
};
|
|
1183
|
-
process.once("SIGINT", onSignal);
|
|
1184
|
-
process.once("SIGTERM", onSignal);
|
|
1185
|
-
try {
|
|
1186
|
-
await withClient(async (client) => {
|
|
1187
|
-
client.setEventHandler((event) => {
|
|
1188
|
-
if (!matchesEventFilter(event.message, eventFilter)) {
|
|
1189
|
-
return;
|
|
1190
|
-
}
|
|
1191
|
-
received += 1;
|
|
1192
|
-
if (context.outputMode === "text") {
|
|
1193
|
-
console.log(`[${event.receivedAt}] ${summarizeMessage(event.message)}`);
|
|
1194
|
-
} else if (captured.length < 200) {
|
|
1195
|
-
captured.push(event.message);
|
|
1196
|
-
} else {
|
|
1197
|
-
dropped += 1;
|
|
1198
|
-
}
|
|
1199
|
-
});
|
|
1200
|
-
const subscribed = await client.subscribe(eventFilter ? [eventFilter] : []);
|
|
1201
|
-
if (!subscribed.success) {
|
|
1202
|
-
throw new RemoteCliError("RIG_REMOTE_SUBSCRIBE_FAILED", subscribed.error || "Failed to subscribe to remote events.", 3);
|
|
1203
|
-
}
|
|
1204
|
-
if (seconds) {
|
|
1205
|
-
await Bun.sleep(seconds * 1000);
|
|
1206
|
-
return;
|
|
1207
|
-
}
|
|
1208
|
-
while (!stop) {
|
|
1209
|
-
await Bun.sleep(250);
|
|
1210
|
-
}
|
|
1211
|
-
});
|
|
1212
|
-
} finally {
|
|
1213
|
-
process.off("SIGINT", onSignal);
|
|
1214
|
-
process.off("SIGTERM", onSignal);
|
|
1215
|
-
}
|
|
1216
|
-
return {
|
|
1217
|
-
ok: true,
|
|
1218
|
-
group: "remote",
|
|
1219
|
-
command,
|
|
1220
|
-
details: {
|
|
1221
|
-
command: "remote watch",
|
|
1222
|
-
remote: remoteMeta,
|
|
1223
|
-
messageType: "stream",
|
|
1224
|
-
requestId: null,
|
|
1225
|
-
payload: context.outputMode === "json" ? captured : null,
|
|
1226
|
-
error: null,
|
|
1227
|
-
received,
|
|
1228
|
-
dropped,
|
|
1229
|
-
event: eventFilter || null,
|
|
1230
|
-
seconds: seconds || null
|
|
1231
|
-
}
|
|
1232
|
-
};
|
|
1233
|
-
}
|
|
1234
|
-
case "pause":
|
|
1235
|
-
case "resume":
|
|
1236
|
-
case "stop":
|
|
1237
|
-
case "continue":
|
|
1238
|
-
case "refresh": {
|
|
1239
|
-
requireNoExtraArgs(pending, `rig remote ${command} [--remote <alias>] [--host <host>] [--port <n>] [--token <token>]`);
|
|
1240
|
-
const response = await withClient(async (client) => {
|
|
1241
|
-
switch (command) {
|
|
1242
|
-
case "pause":
|
|
1243
|
-
return await client.pause();
|
|
1244
|
-
case "resume":
|
|
1245
|
-
return await client.resume();
|
|
1246
|
-
case "stop":
|
|
1247
|
-
return await client.interrupt();
|
|
1248
|
-
case "continue":
|
|
1249
|
-
return await client.continueExecution();
|
|
1250
|
-
default:
|
|
1251
|
-
return await client.refreshTasks();
|
|
1252
|
-
}
|
|
1253
|
-
});
|
|
1254
|
-
assertRemoteOperationSuccess(command, response);
|
|
1255
|
-
return { ok: true, group: "remote", command, details: toDetails(response) };
|
|
1256
|
-
}
|
|
1257
|
-
case "add-iterations":
|
|
1258
|
-
case "remove-iterations": {
|
|
1259
|
-
let countPending = pending;
|
|
1260
|
-
const countResult = takeOption(countPending, "--count");
|
|
1261
|
-
countPending = countResult.rest;
|
|
1262
|
-
requireNoExtraArgs(countPending, `rig remote ${command} --count <n> [--remote <alias>] [--host <host>] [--port <n>] [--token <token>]`);
|
|
1263
|
-
const count = parseRequiredPositiveInt(countResult.value, "--count");
|
|
1264
|
-
const response = await withClient(async (client) => command === "add-iterations" ? await client.addIterations(count) : await client.removeIterations(count));
|
|
1265
|
-
assertRemoteOperationSuccess(command, response);
|
|
1266
|
-
return { ok: true, group: "remote", command, details: toDetails(response) };
|
|
1267
|
-
}
|
|
1268
|
-
case "prompt-preview":
|
|
1269
|
-
case "iteration-output": {
|
|
1270
|
-
let taskPending = pending;
|
|
1271
|
-
const taskResult = takeOption(taskPending, "--task");
|
|
1272
|
-
taskPending = taskResult.rest;
|
|
1273
|
-
requireNoExtraArgs(taskPending, `rig remote ${command} --task <id> [--remote <alias>] [--host <host>] [--port <n>] [--token <token>]`);
|
|
1274
|
-
const taskId = requireTask(taskResult.value, `rig remote ${command} --task <id>`);
|
|
1275
|
-
const response = await withClient(async (client) => command === "prompt-preview" ? await client.getPromptPreview(taskId) : await client.getIterationOutput(taskId));
|
|
1276
|
-
assertRemoteOperationSuccess(command, response);
|
|
1277
|
-
return { ok: true, group: "remote", command, details: toDetails(response) };
|
|
1278
|
-
}
|
|
1279
|
-
case "orchestrate-start": {
|
|
1280
|
-
let orchestrationPending = pending;
|
|
1281
|
-
const maxWorkersResult = takeOption(orchestrationPending, "--max-workers");
|
|
1282
|
-
orchestrationPending = maxWorkersResult.rest;
|
|
1283
|
-
const maxIterationsResult = takeOption(orchestrationPending, "--max-iterations");
|
|
1284
|
-
orchestrationPending = maxIterationsResult.rest;
|
|
1285
|
-
const directMergeResult = takeFlag(orchestrationPending, "--direct-merge");
|
|
1286
|
-
orchestrationPending = directMergeResult.rest;
|
|
1287
|
-
requireNoExtraArgs(orchestrationPending, "rig remote orchestrate-start [--max-workers <n>] [--max-iterations <n>] [--direct-merge]");
|
|
1288
|
-
const response = await withClient((client) => client.startOrchestration({
|
|
1289
|
-
maxWorkers: parseOptionalPositiveInt(maxWorkersResult.value, "--max-workers"),
|
|
1290
|
-
maxIterations: parseOptionalPositiveInt(maxIterationsResult.value, "--max-iterations"),
|
|
1291
|
-
directMerge: directMergeResult.value
|
|
1292
|
-
}));
|
|
1293
|
-
assertRemoteOperationSuccess(command, response);
|
|
1294
|
-
return {
|
|
1295
|
-
ok: true,
|
|
1296
|
-
group: "remote",
|
|
1297
|
-
command,
|
|
1298
|
-
details: toDetails(response)
|
|
1299
|
-
};
|
|
1300
|
-
}
|
|
1301
|
-
case "orchestrate-pause":
|
|
1302
|
-
case "orchestrate-resume":
|
|
1303
|
-
case "orchestrate-stop":
|
|
1304
|
-
case "orchestrate-state": {
|
|
1305
|
-
let orchestrationPending = pending;
|
|
1306
|
-
const idResult = takeOption(orchestrationPending, "--id");
|
|
1307
|
-
orchestrationPending = idResult.rest;
|
|
1308
|
-
requireNoExtraArgs(orchestrationPending, `rig remote ${command} --id <orch-id> [--remote <alias>] [--host <host>] [--port <n>] [--token <token>]`);
|
|
1309
|
-
const orchestrationId = requireTask(idResult.value, `rig remote ${command} --id <orch-id> [--remote <alias>] [--host <host>] [--port <n>] [--token <token>]`);
|
|
1310
|
-
const response = await withClient(async (client) => {
|
|
1311
|
-
switch (command) {
|
|
1312
|
-
case "orchestrate-pause":
|
|
1313
|
-
return await client.pauseOrchestration(orchestrationId);
|
|
1314
|
-
case "orchestrate-resume":
|
|
1315
|
-
return await client.resumeOrchestration(orchestrationId);
|
|
1316
|
-
case "orchestrate-stop":
|
|
1317
|
-
return await client.stopOrchestration(orchestrationId);
|
|
1318
|
-
default:
|
|
1319
|
-
return await client.getOrchestrationState(orchestrationId);
|
|
1320
|
-
}
|
|
1321
|
-
});
|
|
1322
|
-
assertRemoteOperationSuccess(command, response);
|
|
1323
|
-
return { ok: true, group: "remote", command, details: toDetails(response) };
|
|
1324
|
-
}
|
|
1325
|
-
default:
|
|
1326
|
-
throw new CliError(`Unknown remote command: ${command}`, 1, { hint: "Run `rig remote --help` to list remote commands." });
|
|
1327
|
-
}
|
|
1328
|
-
} catch (error) {
|
|
1329
|
-
if (error instanceof RemoteCliError) {
|
|
1330
|
-
throw new CliError(`[${error.code}] ${error.message}`, error.exitCode);
|
|
1331
|
-
}
|
|
1332
|
-
throw error;
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
export {
|
|
1336
|
-
executeRemote
|
|
1337
|
-
};
|