@h-rig/cli 0.0.6-alpha.64 → 0.0.6-alpha.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/rig.js +1349 -1599
- package/dist/src/commands/_connection-state.js +14 -5
- package/dist/src/commands/_doctor-checks.js +71 -11
- package/dist/src/commands/_help-catalog.js +99 -60
- package/dist/src/commands/_json-output.js +56 -0
- package/dist/src/commands/_operator-view.js +97 -784
- package/dist/src/commands/_parsers.js +18 -9
- package/dist/src/commands/_pi-frontend.js +96 -788
- package/dist/src/commands/_policy.js +12 -3
- package/dist/src/commands/_preflight.js +73 -13
- package/dist/src/commands/_run-driver-helpers.js +31 -5
- package/dist/src/commands/_run-replay.js +2 -2
- package/dist/src/commands/_server-client.js +76 -16
- package/dist/src/commands/_snapshot-upload.js +70 -10
- package/dist/src/commands/agent.js +21 -11
- package/dist/src/commands/browser.js +24 -15
- package/dist/src/commands/connect.js +22 -17
- package/dist/src/commands/dist.js +15 -6
- package/dist/src/commands/doctor.js +70 -10
- package/dist/src/commands/github.js +79 -19
- package/dist/src/commands/inbox.js +215 -131
- package/dist/src/commands/init.js +202 -35
- package/dist/src/commands/inspect.js +77 -17
- package/dist/src/commands/inspector.js +18 -9
- package/dist/src/commands/pi.js +18 -9
- package/dist/src/commands/plugin.js +19 -10
- package/dist/src/commands/profile-and-review.js +22 -13
- package/dist/src/commands/queue.js +19 -9
- package/dist/src/commands/remote.js +25 -16
- package/dist/src/commands/repo-git-harness.js +18 -9
- package/dist/src/commands/run.js +158 -805
- package/dist/src/commands/server.js +85 -25
- package/dist/src/commands/setup.js +73 -13
- package/dist/src/commands/stats.js +681 -0
- package/dist/src/commands/task-report-bug.js +24 -15
- package/dist/src/commands/task-run-driver.js +121 -49
- package/dist/src/commands/task.js +254 -893
- package/dist/src/commands/test.js +12 -3
- package/dist/src/commands/workspace.js +14 -5
- package/dist/src/commands.js +1339 -1650
- package/dist/src/index.js +1349 -1599
- package/dist/src/launcher.js +72 -10
- package/dist/src/runner.js +14 -5
- package/package.json +10 -7
- package/dist/src/commands/_pi-remote-session.js +0 -771
- package/dist/src/commands/_pi-worker-bridge-extension.js +0 -834
|
@@ -5,10 +5,19 @@ import { resolve as resolve2 } from "path";
|
|
|
5
5
|
|
|
6
6
|
// packages/cli/src/runner.ts
|
|
7
7
|
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
8
|
-
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
8
|
+
import { CliError as RuntimeCliError } from "@rig/runtime/control-plane/errors";
|
|
9
9
|
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
10
10
|
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
class CliError extends RuntimeCliError {
|
|
13
|
+
hint;
|
|
14
|
+
constructor(message, exitCode = 1, options = {}) {
|
|
15
|
+
super(message, exitCode);
|
|
16
|
+
if (options.hint?.trim()) {
|
|
17
|
+
this.hint = options.hint.trim();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
12
21
|
|
|
13
22
|
// packages/cli/src/commands/_server-client.ts
|
|
14
23
|
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
@@ -35,7 +44,7 @@ function readJsonFile(path) {
|
|
|
35
44
|
try {
|
|
36
45
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
37
46
|
} catch (error) {
|
|
38
|
-
throw new
|
|
47
|
+
throw new CliError(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1, { hint: "Fix or delete that file, then re-select a server with `rig server use <alias|local>`." });
|
|
39
48
|
}
|
|
40
49
|
}
|
|
41
50
|
function writeJsonFile(path, value) {
|
|
@@ -99,7 +108,7 @@ function resolveSelectedConnection(projectRoot, options = {}) {
|
|
|
99
108
|
const global = readGlobalConnections(options);
|
|
100
109
|
const connection = global.connections[repo.selected];
|
|
101
110
|
if (!connection) {
|
|
102
|
-
throw new
|
|
111
|
+
throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
103
112
|
}
|
|
104
113
|
return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
|
|
105
114
|
}
|
|
@@ -172,7 +181,7 @@ async function ensureServerForCli(projectRoot) {
|
|
|
172
181
|
};
|
|
173
182
|
} catch (error) {
|
|
174
183
|
if (error instanceof Error) {
|
|
175
|
-
throw new
|
|
184
|
+
throw new CliError(error.message, 1);
|
|
176
185
|
}
|
|
177
186
|
throw error;
|
|
178
187
|
}
|
|
@@ -222,15 +231,64 @@ function diagnosticMessage(payload) {
|
|
|
222
231
|
});
|
|
223
232
|
return messages.length > 0 ? messages.join("; ") : null;
|
|
224
233
|
}
|
|
234
|
+
var serverReachabilityCache = new Map;
|
|
235
|
+
async function probeServerReachability(baseUrl, authToken) {
|
|
236
|
+
try {
|
|
237
|
+
const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/server/status`, {
|
|
238
|
+
headers: mergeHeaders(undefined, authToken),
|
|
239
|
+
signal: AbortSignal.timeout(1500)
|
|
240
|
+
});
|
|
241
|
+
return response.ok;
|
|
242
|
+
} catch {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function cachedServerReachability(projectRoot, baseUrl, authToken) {
|
|
247
|
+
const key = resolve2(projectRoot);
|
|
248
|
+
const cached = serverReachabilityCache.get(key);
|
|
249
|
+
if (cached)
|
|
250
|
+
return cached;
|
|
251
|
+
const probe = probeServerReachability(baseUrl, authToken);
|
|
252
|
+
serverReachabilityCache.set(key, probe);
|
|
253
|
+
return probe;
|
|
254
|
+
}
|
|
255
|
+
function describeSelectedServer(projectRoot, server) {
|
|
256
|
+
try {
|
|
257
|
+
const selected = resolveSelectedConnection(projectRoot);
|
|
258
|
+
if (selected) {
|
|
259
|
+
return {
|
|
260
|
+
alias: selected.alias,
|
|
261
|
+
target: selected.connection.kind === "remote" ? selected.connection.baseUrl : server.baseUrl
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
} catch {}
|
|
265
|
+
return { alias: server.connectionKind === "remote" ? "remote" : "local", target: server.baseUrl };
|
|
266
|
+
}
|
|
267
|
+
async function buildServerFailureContext(projectRoot, server) {
|
|
268
|
+
const { alias, target } = describeSelectedServer(projectRoot, server);
|
|
269
|
+
const reachable = await cachedServerReachability(projectRoot, server.baseUrl, server.authToken);
|
|
270
|
+
const reachability = reachable ? "server is reachable" : "server is unreachable";
|
|
271
|
+
return {
|
|
272
|
+
contextLine: `Currently connected to: ${alias} at ${target} (${reachability}).`,
|
|
273
|
+
hint: "Check the selected server with `rig server status`, or switch with `rig server use <alias|local>`."
|
|
274
|
+
};
|
|
275
|
+
}
|
|
225
276
|
async function requestServerJson(context, pathname, init = {}) {
|
|
226
277
|
const server = await ensureServerForCli(context.projectRoot);
|
|
227
278
|
const headers = mergeHeaders(init.headers, server.authToken);
|
|
228
279
|
if (server.serverProjectRoot)
|
|
229
280
|
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
281
|
+
let response;
|
|
282
|
+
try {
|
|
283
|
+
response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
284
|
+
...init,
|
|
285
|
+
headers
|
|
286
|
+
});
|
|
287
|
+
} catch (error) {
|
|
288
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
289
|
+
throw new CliError(`Rig server request failed: ${error instanceof Error ? error.message : String(error)}
|
|
290
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
291
|
+
}
|
|
234
292
|
const text = await response.text();
|
|
235
293
|
const payload = text.trim().length > 0 ? (() => {
|
|
236
294
|
try {
|
|
@@ -242,7 +300,9 @@ async function requestServerJson(context, pathname, init = {}) {
|
|
|
242
300
|
if (!response.ok) {
|
|
243
301
|
const diagnostics = diagnosticMessage(payload);
|
|
244
302
|
const detail = diagnostics ?? (text || response.statusText);
|
|
245
|
-
|
|
303
|
+
const failure = await buildServerFailureContext(context.projectRoot, server);
|
|
304
|
+
throw new CliError(`Rig server request failed (${response.status}): ${detail}
|
|
305
|
+
${failure.contextLine}`, 1, { hint: failure.hint });
|
|
246
306
|
}
|
|
247
307
|
return payload;
|
|
248
308
|
}
|
|
@@ -284,26 +344,6 @@ async function steerRunViaServer(context, runId, message) {
|
|
|
284
344
|
});
|
|
285
345
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
286
346
|
}
|
|
287
|
-
async function getRunPiSessionViaServer(context, runId) {
|
|
288
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi`);
|
|
289
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
290
|
-
}
|
|
291
|
-
async function getRunPiMessagesViaServer(context, runId) {
|
|
292
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/messages`);
|
|
293
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { messages: [] };
|
|
294
|
-
}
|
|
295
|
-
async function getRunPiStatusViaServer(context, runId) {
|
|
296
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/status`);
|
|
297
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
298
|
-
}
|
|
299
|
-
async function getRunPiCommandsViaServer(context, runId) {
|
|
300
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
|
|
301
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
|
|
302
|
-
}
|
|
303
|
-
async function getRunPiCapabilitiesViaServer(context, runId) {
|
|
304
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
|
|
305
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
|
|
306
|
-
}
|
|
307
347
|
async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior) {
|
|
308
348
|
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
|
|
309
349
|
method: "POST",
|
|
@@ -312,44 +352,6 @@ async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior)
|
|
|
312
352
|
});
|
|
313
353
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
|
|
314
354
|
}
|
|
315
|
-
async function sendRunPiShellViaServer(context, runId, text) {
|
|
316
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/shell`, {
|
|
317
|
-
method: "POST",
|
|
318
|
-
headers: { "content-type": "application/json" },
|
|
319
|
-
body: JSON.stringify({ text })
|
|
320
|
-
});
|
|
321
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
|
|
322
|
-
}
|
|
323
|
-
async function runRunPiCommandViaServer(context, runId, text) {
|
|
324
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands/run`, {
|
|
325
|
-
method: "POST",
|
|
326
|
-
headers: { "content-type": "application/json" },
|
|
327
|
-
body: JSON.stringify({ text })
|
|
328
|
-
});
|
|
329
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { type: "done" };
|
|
330
|
-
}
|
|
331
|
-
async function respondRunPiExtensionUiViaServer(context, runId, requestId, valueOrCancel) {
|
|
332
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/extension-ui/respond`, {
|
|
333
|
-
method: "POST",
|
|
334
|
-
headers: { "content-type": "application/json" },
|
|
335
|
-
body: JSON.stringify({ requestId, ...valueOrCancel })
|
|
336
|
-
});
|
|
337
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
|
|
338
|
-
}
|
|
339
|
-
async function abortRunPiViaServer(context, runId) {
|
|
340
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/abort`, { method: "POST" });
|
|
341
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { aborted: true };
|
|
342
|
-
}
|
|
343
|
-
async function buildRunPiEventsWebSocketUrl(context, runId) {
|
|
344
|
-
const server = await ensureServerForCli(context.projectRoot);
|
|
345
|
-
const url = new URL(`${server.baseUrl.replace(/\/+$/, "")}/api/runs/${encodeURIComponent(runId)}/pi/events`);
|
|
346
|
-
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
347
|
-
if (server.authToken)
|
|
348
|
-
url.searchParams.set("token", server.authToken);
|
|
349
|
-
if (server.serverProjectRoot)
|
|
350
|
-
url.searchParams.set("rigProjectRoot", server.serverProjectRoot);
|
|
351
|
-
return url.toString();
|
|
352
|
-
}
|
|
353
355
|
|
|
354
356
|
// packages/cli/src/commands/_operator-surface.ts
|
|
355
357
|
import { createInterface } from "readline";
|
|
@@ -547,684 +549,8 @@ function createOperatorSurface(options = {}) {
|
|
|
547
549
|
import { mkdtempSync, rmSync } from "fs";
|
|
548
550
|
import { tmpdir } from "os";
|
|
549
551
|
import { join } from "path";
|
|
550
|
-
import {
|
|
551
|
-
|
|
552
|
-
createAgentSessionServices,
|
|
553
|
-
main as runPiMain
|
|
554
|
-
} from "@earendil-works/pi-coding-agent";
|
|
555
|
-
|
|
556
|
-
// packages/cli/src/commands/_pi-remote-session.ts
|
|
557
|
-
import { AgentSession as PiAgentSession, expandPromptTemplate } from "@earendil-works/pi-coding-agent";
|
|
558
|
-
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
559
|
-
function defaultTransport() {
|
|
560
|
-
return {
|
|
561
|
-
getSession: getRunPiSessionViaServer,
|
|
562
|
-
getMessages: getRunPiMessagesViaServer,
|
|
563
|
-
getStatus: getRunPiStatusViaServer,
|
|
564
|
-
getCommands: getRunPiCommandsViaServer,
|
|
565
|
-
getCapabilities: getRunPiCapabilitiesViaServer,
|
|
566
|
-
sendPrompt: sendRunPiPromptViaServer,
|
|
567
|
-
sendShell: sendRunPiShellViaServer,
|
|
568
|
-
runCommand: runRunPiCommandViaServer,
|
|
569
|
-
abort: abortRunPiViaServer,
|
|
570
|
-
buildEventsWebSocketUrl: buildRunPiEventsWebSocketUrl
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
function recordOf(value) {
|
|
574
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
575
|
-
}
|
|
576
|
-
function emptyRemoteStatus() {
|
|
577
|
-
return {
|
|
578
|
-
isStreaming: false,
|
|
579
|
-
isCompacting: false,
|
|
580
|
-
isBashRunning: false,
|
|
581
|
-
pendingMessageCount: 0,
|
|
582
|
-
steeringMessages: [],
|
|
583
|
-
followUpMessages: [],
|
|
584
|
-
model: null,
|
|
585
|
-
thinkingLevel: null,
|
|
586
|
-
sessionName: null,
|
|
587
|
-
cwd: null,
|
|
588
|
-
stats: null,
|
|
589
|
-
contextUsage: null
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
function resolveAttachReadyTimeoutMs() {
|
|
593
|
-
const raw = Number.parseInt(process.env.RIG_PI_ATTACH_TIMEOUT_MS ?? "", 10);
|
|
594
|
-
return Number.isFinite(raw) && raw > 0 ? raw : 10 * 60000;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
class RigRemoteSessionController {
|
|
598
|
-
context;
|
|
599
|
-
runId;
|
|
600
|
-
status = emptyRemoteStatus();
|
|
601
|
-
transport;
|
|
602
|
-
hooks = {};
|
|
603
|
-
session = null;
|
|
604
|
-
socket = null;
|
|
605
|
-
closed = false;
|
|
606
|
-
pendingShells = [];
|
|
607
|
-
pendingCompactions = [];
|
|
608
|
-
constructor(input) {
|
|
609
|
-
this.context = input.context;
|
|
610
|
-
this.runId = input.runId;
|
|
611
|
-
this.transport = input.transport ?? defaultTransport();
|
|
612
|
-
}
|
|
613
|
-
ingestEnvelope(envelopeValue) {
|
|
614
|
-
this.applyEnvelope(envelopeValue);
|
|
615
|
-
}
|
|
616
|
-
bindSession(session) {
|
|
617
|
-
this.session = session;
|
|
618
|
-
}
|
|
619
|
-
setUiHooks(hooks) {
|
|
620
|
-
this.hooks = hooks;
|
|
621
|
-
}
|
|
622
|
-
async connect() {
|
|
623
|
-
const ready = await this.waitForReady();
|
|
624
|
-
if (!ready || this.closed)
|
|
625
|
-
return;
|
|
626
|
-
let catchupDone = false;
|
|
627
|
-
const buffered = [];
|
|
628
|
-
const wsUrl = await this.transport.buildEventsWebSocketUrl(this.context, this.runId);
|
|
629
|
-
const socket = new WebSocket(wsUrl);
|
|
630
|
-
this.socket = socket;
|
|
631
|
-
socket.onopen = () => {
|
|
632
|
-
this.hooks.onConnectionChange?.(true);
|
|
633
|
-
this.hooks.onStatusText?.("worker session live");
|
|
634
|
-
};
|
|
635
|
-
socket.onmessage = (message) => {
|
|
636
|
-
try {
|
|
637
|
-
const payload = typeof message.data === "string" ? JSON.parse(message.data) : JSON.parse(Buffer.from(message.data).toString("utf8"));
|
|
638
|
-
if (!catchupDone)
|
|
639
|
-
buffered.push(payload);
|
|
640
|
-
else
|
|
641
|
-
this.applyEnvelope(payload);
|
|
642
|
-
} catch (error) {
|
|
643
|
-
this.hooks.onError?.(`Unparseable worker Pi event: ${error instanceof Error ? error.message : String(error)}`);
|
|
644
|
-
}
|
|
645
|
-
};
|
|
646
|
-
socket.onerror = () => socket.close();
|
|
647
|
-
socket.onclose = () => {
|
|
648
|
-
this.hooks.onConnectionChange?.(false);
|
|
649
|
-
if (!this.closed)
|
|
650
|
-
this.hooks.onStatusText?.("worker Pi WebSocket disconnected");
|
|
651
|
-
};
|
|
652
|
-
try {
|
|
653
|
-
const [messagesPayload, statusPayload, commandsPayload, capabilitiesPayload] = await Promise.all([
|
|
654
|
-
this.transport.getMessages(this.context, this.runId),
|
|
655
|
-
this.transport.getStatus(this.context, this.runId),
|
|
656
|
-
this.transport.getCommands(this.context, this.runId),
|
|
657
|
-
this.transport.getCapabilities(this.context, this.runId).catch(() => ({ capabilities: null }))
|
|
658
|
-
]);
|
|
659
|
-
const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
|
|
660
|
-
this.applyStatusPayload(statusPayload);
|
|
661
|
-
this.session?.replaceRemoteMessages(messages);
|
|
662
|
-
const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
|
|
663
|
-
const capabilities = capabilitiesPayload.capabilities && typeof capabilitiesPayload.capabilities === "object" && !Array.isArray(capabilitiesPayload.capabilities) ? capabilitiesPayload.capabilities : null;
|
|
664
|
-
this.hooks.onCatchUp?.(messages, commands, capabilities);
|
|
665
|
-
catchupDone = true;
|
|
666
|
-
for (const payload of buffered.splice(0))
|
|
667
|
-
this.applyEnvelope(payload);
|
|
668
|
-
} catch (error) {
|
|
669
|
-
catchupDone = true;
|
|
670
|
-
this.hooks.onError?.(`Worker Pi catch-up failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
close() {
|
|
674
|
-
this.closed = true;
|
|
675
|
-
this.socket?.close();
|
|
676
|
-
for (const shell of this.pendingShells.splice(0))
|
|
677
|
-
shell.reject(new Error("Remote session closed."));
|
|
678
|
-
for (const compaction of this.pendingCompactions.splice(0))
|
|
679
|
-
compaction.reject(new Error("Remote session closed."));
|
|
680
|
-
}
|
|
681
|
-
async sendPrompt(text, streamingBehavior) {
|
|
682
|
-
await this.transport.sendPrompt(this.context, this.runId, text, streamingBehavior);
|
|
683
|
-
}
|
|
684
|
-
async sendCommand(text) {
|
|
685
|
-
const result = await this.transport.runCommand(this.context, this.runId, text);
|
|
686
|
-
return typeof result.message === "string" ? result.message : "worker command accepted";
|
|
687
|
-
}
|
|
688
|
-
async sendShell(text) {
|
|
689
|
-
await this.transport.sendShell(this.context, this.runId, text);
|
|
690
|
-
}
|
|
691
|
-
async abort() {
|
|
692
|
-
await this.transport.abort(this.context, this.runId);
|
|
693
|
-
}
|
|
694
|
-
registerPendingShell(shell) {
|
|
695
|
-
this.pendingShells.push(shell);
|
|
696
|
-
}
|
|
697
|
-
failPendingShell(shell, error) {
|
|
698
|
-
const index = this.pendingShells.indexOf(shell);
|
|
699
|
-
if (index !== -1)
|
|
700
|
-
this.pendingShells.splice(index, 1);
|
|
701
|
-
shell.reject(error);
|
|
702
|
-
}
|
|
703
|
-
registerPendingCompaction(pending) {
|
|
704
|
-
this.pendingCompactions.push(pending);
|
|
705
|
-
}
|
|
706
|
-
async waitForReady() {
|
|
707
|
-
const startedAt = Date.now();
|
|
708
|
-
const deadline = startedAt + resolveAttachReadyTimeoutMs();
|
|
709
|
-
let consecutiveFailures = 0;
|
|
710
|
-
while (!this.closed) {
|
|
711
|
-
let requestFailed = false;
|
|
712
|
-
const session = await this.transport.getSession(this.context, this.runId).catch((error) => {
|
|
713
|
-
requestFailed = true;
|
|
714
|
-
return { ready: false, status: error instanceof Error ? error.message : String(error), retryAfterMs: 1000 };
|
|
715
|
-
});
|
|
716
|
-
if (session.ready !== false)
|
|
717
|
-
return true;
|
|
718
|
-
consecutiveFailures = requestFailed ? consecutiveFailures + 1 : 0;
|
|
719
|
-
const status = String(session.status ?? "starting");
|
|
720
|
-
if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
|
|
721
|
-
this.hooks.onError?.(`Run ended before the worker Pi daemon became ready: ${status}. Inspect with \`rig run show ${this.runId}\`.`);
|
|
722
|
-
return false;
|
|
723
|
-
}
|
|
724
|
-
this.hooks.onStatusText?.(consecutiveFailures >= 5 ? "Rig server unreachable \xB7 retrying \xB7 /detach to exit" : `waiting for worker Pi daemon \xB7 ${status} \xB7 /detach to exit`);
|
|
725
|
-
if (Date.now() >= deadline) {
|
|
726
|
-
this.hooks.onError?.(`Worker Pi daemon did not become ready (last status: ${status}). Set RIG_PI_ATTACH_TIMEOUT_MS to wait longer.`);
|
|
727
|
-
return false;
|
|
728
|
-
}
|
|
729
|
-
await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
|
|
730
|
-
}
|
|
731
|
-
return false;
|
|
732
|
-
}
|
|
733
|
-
applyEnvelope(envelopeValue) {
|
|
734
|
-
const envelope = recordOf(envelopeValue);
|
|
735
|
-
if (!envelope)
|
|
736
|
-
return;
|
|
737
|
-
const type = String(envelope.type ?? "");
|
|
738
|
-
if (type === "status.update") {
|
|
739
|
-
this.applyStatusPayload(envelope);
|
|
740
|
-
return;
|
|
741
|
-
}
|
|
742
|
-
if (type === "activity.update") {
|
|
743
|
-
const activity = recordOf(envelope.activity);
|
|
744
|
-
this.hooks.onActivity?.(String(activity?.label ?? ""), activity?.detail ? String(activity.detail) : undefined);
|
|
745
|
-
return;
|
|
746
|
-
}
|
|
747
|
-
if (type === "extension_ui_request") {
|
|
748
|
-
const request = recordOf(envelope.request);
|
|
749
|
-
if (request)
|
|
750
|
-
this.hooks.onExtensionUiRequest?.(request);
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
|
-
if (type === "pi.ui_event") {
|
|
754
|
-
this.applyShellUiEvent(envelope.event);
|
|
755
|
-
return;
|
|
756
|
-
}
|
|
757
|
-
if (type === "pi.event") {
|
|
758
|
-
this.session?.handleRemoteSessionEvent(envelope.event);
|
|
759
|
-
this.settlePendingCompaction(envelope.event);
|
|
760
|
-
return;
|
|
761
|
-
}
|
|
762
|
-
if (type === "error") {
|
|
763
|
-
this.hooks.onError?.(String(envelope.message ?? envelope.detail ?? "unknown worker error"));
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
applyStatusPayload(payload) {
|
|
767
|
-
const status = recordOf(payload.status) ?? payload;
|
|
768
|
-
const next = this.status;
|
|
769
|
-
next.isStreaming = status.isStreaming === true;
|
|
770
|
-
next.isCompacting = status.isCompacting === true;
|
|
771
|
-
next.isBashRunning = status.isBashRunning === true;
|
|
772
|
-
next.pendingMessageCount = typeof status.pendingMessageCount === "number" ? status.pendingMessageCount : 0;
|
|
773
|
-
next.steeringMessages = Array.isArray(status.steeringMessages) ? status.steeringMessages.map(String) : [];
|
|
774
|
-
next.followUpMessages = Array.isArray(status.followUpMessages) ? status.followUpMessages.map(String) : [];
|
|
775
|
-
next.model = recordOf(status.model) ?? next.model;
|
|
776
|
-
next.thinkingLevel = typeof status.thinkingLevel === "string" ? status.thinkingLevel : next.thinkingLevel;
|
|
777
|
-
next.sessionName = typeof status.sessionName === "string" ? status.sessionName : next.sessionName;
|
|
778
|
-
next.cwd = typeof status.cwd === "string" ? status.cwd : next.cwd;
|
|
779
|
-
next.stats = recordOf(status.stats) ?? next.stats;
|
|
780
|
-
next.contextUsage = recordOf(status.contextUsage) ?? next.contextUsage;
|
|
781
|
-
}
|
|
782
|
-
applyShellUiEvent(value) {
|
|
783
|
-
const event = recordOf(value);
|
|
784
|
-
if (!event)
|
|
785
|
-
return;
|
|
786
|
-
const type = String(event.type ?? "");
|
|
787
|
-
const pending = this.pendingShells[0];
|
|
788
|
-
if (type === "shell.chunk" && pending) {
|
|
789
|
-
pending.sawChunk = true;
|
|
790
|
-
pending.onData(Buffer.from(String(event.chunk ?? "")));
|
|
791
|
-
return;
|
|
792
|
-
}
|
|
793
|
-
if (type === "shell.end" && pending) {
|
|
794
|
-
const output = String(event.output ?? "");
|
|
795
|
-
if (output && !pending.sawChunk)
|
|
796
|
-
pending.onData(Buffer.from(output));
|
|
797
|
-
const index = this.pendingShells.indexOf(pending);
|
|
798
|
-
if (index !== -1)
|
|
799
|
-
this.pendingShells.splice(index, 1);
|
|
800
|
-
pending.resolve({ exitCode: typeof event.exitCode === "number" ? event.exitCode : event.isError === true ? 1 : 0 });
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
settlePendingCompaction(eventValue) {
|
|
804
|
-
const event = recordOf(eventValue);
|
|
805
|
-
if (!event)
|
|
806
|
-
return;
|
|
807
|
-
const type = String(event.type ?? "");
|
|
808
|
-
if (type !== "compaction_end" || this.pendingCompactions.length === 0)
|
|
809
|
-
return;
|
|
810
|
-
const pending = this.pendingCompactions.shift();
|
|
811
|
-
const result = recordOf(event.result);
|
|
812
|
-
if (result)
|
|
813
|
-
pending.resolve(result);
|
|
814
|
-
else if (event.aborted === true)
|
|
815
|
-
pending.reject(new Error("Compaction aborted on the worker."));
|
|
816
|
-
else
|
|
817
|
-
pending.resolve({});
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
class RigRemoteAgentSession extends PiAgentSession {
|
|
822
|
-
remote;
|
|
823
|
-
constructor(config, remote) {
|
|
824
|
-
super(config);
|
|
825
|
-
this.remote = remote;
|
|
826
|
-
remote.bindSession(this);
|
|
827
|
-
}
|
|
828
|
-
handleRemoteSessionEvent(eventValue) {
|
|
829
|
-
const event = recordOf(eventValue);
|
|
830
|
-
if (!event)
|
|
831
|
-
return;
|
|
832
|
-
const type = String(event.type ?? "");
|
|
833
|
-
if ((type === "message_end" || type === "turn_end") && recordOf(event.message)) {
|
|
834
|
-
this.agent.state.messages = [...this.agent.state.messages, event.message];
|
|
835
|
-
}
|
|
836
|
-
this._emit(eventValue);
|
|
837
|
-
}
|
|
838
|
-
replaceRemoteMessages(messages) {
|
|
839
|
-
this.agent.state.messages = messages;
|
|
840
|
-
}
|
|
841
|
-
async expandLocalInput(text) {
|
|
842
|
-
let current = text;
|
|
843
|
-
const runner = this.extensionRunner;
|
|
844
|
-
if (runner.hasHandlers("input")) {
|
|
845
|
-
const result = await runner.emitInput(current, undefined, "interactive");
|
|
846
|
-
if (result.action === "handled")
|
|
847
|
-
return null;
|
|
848
|
-
if (result.action === "transform")
|
|
849
|
-
current = result.text;
|
|
850
|
-
}
|
|
851
|
-
current = this._expandSkillCommand(current);
|
|
852
|
-
current = expandPromptTemplate(current, [...this.promptTemplates]);
|
|
853
|
-
return current;
|
|
854
|
-
}
|
|
855
|
-
async prompt(text, options) {
|
|
856
|
-
const trimmed = text.trim();
|
|
857
|
-
if (!trimmed)
|
|
858
|
-
return;
|
|
859
|
-
if (trimmed.startsWith("/")) {
|
|
860
|
-
if (await this._tryExecuteExtensionCommand(trimmed))
|
|
861
|
-
return;
|
|
862
|
-
const expanded2 = await this.expandLocalInput(trimmed);
|
|
863
|
-
if (expanded2 === null)
|
|
864
|
-
return;
|
|
865
|
-
if (expanded2 !== trimmed) {
|
|
866
|
-
const behavior2 = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
|
|
867
|
-
options?.preflightResult?.(true);
|
|
868
|
-
await this.remote.sendPrompt(expanded2, behavior2);
|
|
869
|
-
return;
|
|
870
|
-
}
|
|
871
|
-
await this.remote.sendCommand(trimmed);
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
if (trimmed.startsWith("!")) {
|
|
875
|
-
await this.remote.sendShell(trimmed);
|
|
876
|
-
return;
|
|
877
|
-
}
|
|
878
|
-
const expanded = await this.expandLocalInput(trimmed);
|
|
879
|
-
if (expanded === null)
|
|
880
|
-
return;
|
|
881
|
-
const behavior = options?.streamingBehavior ?? (this.isStreaming || this.isCompacting ? "steer" : undefined);
|
|
882
|
-
options?.preflightResult?.(true);
|
|
883
|
-
await this.remote.sendPrompt(expanded, behavior);
|
|
884
|
-
}
|
|
885
|
-
async steer(text) {
|
|
886
|
-
const trimmed = text.trim();
|
|
887
|
-
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
888
|
-
return;
|
|
889
|
-
const expanded = await this.expandLocalInput(trimmed);
|
|
890
|
-
if (expanded === null)
|
|
891
|
-
return;
|
|
892
|
-
await this.remote.sendPrompt(expanded, "steer");
|
|
893
|
-
}
|
|
894
|
-
async followUp(text) {
|
|
895
|
-
const trimmed = text.trim();
|
|
896
|
-
if (trimmed.startsWith("/") && await this._tryExecuteExtensionCommand(trimmed))
|
|
897
|
-
return;
|
|
898
|
-
const expanded = await this.expandLocalInput(trimmed);
|
|
899
|
-
if (expanded === null)
|
|
900
|
-
return;
|
|
901
|
-
await this.remote.sendPrompt(expanded, "followUp");
|
|
902
|
-
}
|
|
903
|
-
async abort() {
|
|
904
|
-
await this.remote.abort();
|
|
905
|
-
}
|
|
906
|
-
async compact(customInstructions) {
|
|
907
|
-
const pending = new Promise((resolve3, reject) => {
|
|
908
|
-
this.remote.registerPendingCompaction({ resolve: resolve3, reject });
|
|
909
|
-
});
|
|
910
|
-
await this.remote.sendCommand(`/compact${customInstructions ? ` ${customInstructions}` : ""}`);
|
|
911
|
-
return pending;
|
|
912
|
-
}
|
|
913
|
-
clearQueue() {
|
|
914
|
-
const cleared = {
|
|
915
|
-
steering: [...this.remote.status.steeringMessages],
|
|
916
|
-
followUp: [...this.remote.status.followUpMessages]
|
|
917
|
-
};
|
|
918
|
-
this.remote.status.steeringMessages = [];
|
|
919
|
-
this.remote.status.followUpMessages = [];
|
|
920
|
-
this.remote.status.pendingMessageCount = 0;
|
|
921
|
-
this.remote.sendCommand("/queue-clear").catch(() => {});
|
|
922
|
-
return cleared;
|
|
923
|
-
}
|
|
924
|
-
get isStreaming() {
|
|
925
|
-
return this.remote.status.isStreaming;
|
|
926
|
-
}
|
|
927
|
-
get isCompacting() {
|
|
928
|
-
return this.remote.status.isCompacting;
|
|
929
|
-
}
|
|
930
|
-
get isBashRunning() {
|
|
931
|
-
return this.remote.status.isBashRunning;
|
|
932
|
-
}
|
|
933
|
-
get pendingMessageCount() {
|
|
934
|
-
return this.remote.status.pendingMessageCount;
|
|
935
|
-
}
|
|
936
|
-
getSteeringMessages() {
|
|
937
|
-
return this.remote.status.steeringMessages;
|
|
938
|
-
}
|
|
939
|
-
getFollowUpMessages() {
|
|
940
|
-
return this.remote.status.followUpMessages;
|
|
941
|
-
}
|
|
942
|
-
get model() {
|
|
943
|
-
return this.remote.status.model ?? super.model;
|
|
944
|
-
}
|
|
945
|
-
async setModel(model) {
|
|
946
|
-
await this.remote.sendCommand(`/model ${model.provider}/${model.id}`);
|
|
947
|
-
this.remote.status.model = model;
|
|
948
|
-
}
|
|
949
|
-
get thinkingLevel() {
|
|
950
|
-
return this.remote.status.thinkingLevel ?? super.thinkingLevel;
|
|
951
|
-
}
|
|
952
|
-
setThinkingLevel(level) {
|
|
953
|
-
this.remote.status.thinkingLevel = level;
|
|
954
|
-
this.remote.sendCommand(`/thinking ${level}`).catch(() => {});
|
|
955
|
-
}
|
|
956
|
-
get sessionName() {
|
|
957
|
-
return this.remote.status.sessionName ?? super.sessionName;
|
|
958
|
-
}
|
|
959
|
-
setSessionName(name) {
|
|
960
|
-
this.remote.status.sessionName = name;
|
|
961
|
-
this.remote.sendCommand(`/name ${name}`).catch(() => {});
|
|
962
|
-
}
|
|
963
|
-
getSessionStats() {
|
|
964
|
-
return this.remote.status.stats ?? super.getSessionStats();
|
|
965
|
-
}
|
|
966
|
-
getContextUsage() {
|
|
967
|
-
return this.remote.status.contextUsage ?? super.getContextUsage();
|
|
968
|
-
}
|
|
969
|
-
dispose() {
|
|
970
|
-
this.remote.close();
|
|
971
|
-
super.dispose();
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
function createRemoteBashOperations(controller, excludeFromContext) {
|
|
975
|
-
return {
|
|
976
|
-
exec(command, _cwd, execOptions) {
|
|
977
|
-
return new Promise((resolve3, reject) => {
|
|
978
|
-
const pending = { onData: execOptions.onData, resolve: resolve3, reject, sawChunk: false };
|
|
979
|
-
const cleanup = () => {
|
|
980
|
-
execOptions.signal?.removeEventListener("abort", onAbort);
|
|
981
|
-
if (timer)
|
|
982
|
-
clearTimeout(timer);
|
|
983
|
-
};
|
|
984
|
-
const onAbort = () => {
|
|
985
|
-
cleanup();
|
|
986
|
-
controller.failPendingShell(pending, new Error("Remote worker shell command aborted locally."));
|
|
987
|
-
};
|
|
988
|
-
const timeoutMs = typeof execOptions.timeout === "number" && execOptions.timeout > 0 ? execOptions.timeout : 0;
|
|
989
|
-
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
990
|
-
cleanup();
|
|
991
|
-
controller.failPendingShell(pending, new Error(`Remote worker shell command timed out after ${timeoutMs}ms.`));
|
|
992
|
-
}, timeoutMs) : null;
|
|
993
|
-
const wrappedResolve = pending.resolve;
|
|
994
|
-
const wrappedReject = pending.reject;
|
|
995
|
-
pending.resolve = (result) => {
|
|
996
|
-
cleanup();
|
|
997
|
-
wrappedResolve(result);
|
|
998
|
-
};
|
|
999
|
-
pending.reject = (error) => {
|
|
1000
|
-
cleanup();
|
|
1001
|
-
wrappedReject(error);
|
|
1002
|
-
};
|
|
1003
|
-
execOptions.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1004
|
-
controller.registerPendingShell(pending);
|
|
1005
|
-
controller.sendShell(`${excludeFromContext ? "!!" : "!"}${command}`).catch((error) => {
|
|
1006
|
-
cleanup();
|
|
1007
|
-
controller.failPendingShell(pending, error instanceof Error ? error : new Error(String(error)));
|
|
1008
|
-
});
|
|
1009
|
-
});
|
|
1010
|
-
}
|
|
1011
|
-
};
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
// packages/cli/src/commands/_spinner.ts
|
|
1015
|
-
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1016
|
-
|
|
1017
|
-
// packages/cli/src/commands/_pi-worker-bridge-extension.ts
|
|
1018
|
-
function recordOf2(value) {
|
|
1019
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1020
|
-
}
|
|
1021
|
-
function asText(value) {
|
|
1022
|
-
if (typeof value === "string")
|
|
1023
|
-
return value;
|
|
1024
|
-
if (value === null || value === undefined)
|
|
1025
|
-
return "";
|
|
1026
|
-
if (typeof value === "number" || typeof value === "boolean")
|
|
1027
|
-
return String(value);
|
|
1028
|
-
try {
|
|
1029
|
-
return JSON.stringify(value);
|
|
1030
|
-
} catch {
|
|
1031
|
-
return String(value);
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
function names(value, key = "name") {
|
|
1035
|
-
if (!Array.isArray(value))
|
|
1036
|
-
return [];
|
|
1037
|
-
return value.flatMap((entry) => {
|
|
1038
|
-
if (typeof entry === "string")
|
|
1039
|
-
return [entry];
|
|
1040
|
-
const record = recordOf2(entry);
|
|
1041
|
-
const name = record?.[key];
|
|
1042
|
-
return typeof name === "string" ? [name] : [];
|
|
1043
|
-
});
|
|
1044
|
-
}
|
|
1045
|
-
function renderWorkerCapabilities(capabilities) {
|
|
1046
|
-
const lines = ["Worker session capabilities (in effect for this run)"];
|
|
1047
|
-
const tools = Array.isArray(capabilities.tools) ? capabilities.tools : [];
|
|
1048
|
-
const activeTools = tools.flatMap((tool) => {
|
|
1049
|
-
const record = recordOf2(tool);
|
|
1050
|
-
return record && record.active === true && typeof record.name === "string" ? [record.name] : [];
|
|
1051
|
-
});
|
|
1052
|
-
const inactiveCount = tools.length - activeTools.length;
|
|
1053
|
-
lines.push(` tools ${activeTools.join(", ") || "(none)"}${inactiveCount > 0 ? ` (+${inactiveCount} inactive)` : ""}`);
|
|
1054
|
-
const extensions = Array.isArray(capabilities.extensions) ? capabilities.extensions : [];
|
|
1055
|
-
const extensionLabels = extensions.flatMap((entry) => {
|
|
1056
|
-
const record = recordOf2(entry);
|
|
1057
|
-
if (!record)
|
|
1058
|
-
return [];
|
|
1059
|
-
const path = typeof record.path === "string" ? record.path : "";
|
|
1060
|
-
const short = path.split("/").filter(Boolean).slice(-2).join("/") || path || "extension";
|
|
1061
|
-
return [short];
|
|
1062
|
-
});
|
|
1063
|
-
lines.push(` extensions ${extensionLabels.join(", ") || "(none)"}`);
|
|
1064
|
-
const hookEvents = names(capabilities.hookEvents);
|
|
1065
|
-
const hookList = Array.isArray(capabilities.hookEvents) ? capabilities.hookEvents.map(asText).filter(Boolean) : hookEvents;
|
|
1066
|
-
lines.push(` hooks ${hookList.join(", ") || "(none)"}`);
|
|
1067
|
-
lines.push(` skills ${names(capabilities.skills).join(", ") || "(none)"}`);
|
|
1068
|
-
lines.push(` prompts ${names(capabilities.prompts).join(", ") || "(none)"}`);
|
|
1069
|
-
const model = typeof capabilities.model === "string" ? capabilities.model : "(unknown)";
|
|
1070
|
-
const thinking = typeof capabilities.thinkingLevel === "string" ? capabilities.thinkingLevel : "";
|
|
1071
|
-
const cwd = typeof capabilities.cwd === "string" ? capabilities.cwd : "";
|
|
1072
|
-
lines.push(` model ${model}${thinking ? ` \xB7 ${thinking}` : ""}`);
|
|
1073
|
-
if (cwd)
|
|
1074
|
-
lines.push(` cwd ${cwd}`);
|
|
1075
|
-
lines.push(" (worker commands are in your palette as [worker \u2026] \xB7 /worker shows this again)");
|
|
1076
|
-
return lines;
|
|
1077
|
-
}
|
|
1078
|
-
async function answerExtensionUiRequest(options, ctx, request) {
|
|
1079
|
-
const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
|
|
1080
|
-
const method = String(request.method ?? request.type ?? "input");
|
|
1081
|
-
const prompt = asText(request.prompt ?? request.message ?? request.title ?? method);
|
|
1082
|
-
const rawOptions = Array.isArray(request.options) ? request.options : Array.isArray(request.items) ? request.items : [];
|
|
1083
|
-
const choices = rawOptions.map((option) => {
|
|
1084
|
-
const record = recordOf2(option);
|
|
1085
|
-
return record ? asText(record.label ?? record.value ?? record.name ?? option) : asText(option);
|
|
1086
|
-
}).filter(Boolean);
|
|
1087
|
-
try {
|
|
1088
|
-
if (method === "confirm") {
|
|
1089
|
-
const confirmed = await ctx.ui.confirm("Worker request", prompt);
|
|
1090
|
-
await respondRunPiExtensionUiViaServer(options.context, options.runId, requestId, { value: confirmed, confirmed });
|
|
1091
|
-
return;
|
|
1092
|
-
}
|
|
1093
|
-
if (choices.length > 0) {
|
|
1094
|
-
const selected = await ctx.ui.select(prompt, choices);
|
|
1095
|
-
await respondRunPiExtensionUiViaServer(options.context, options.runId, requestId, selected === undefined ? { cancelled: true } : { value: selected });
|
|
1096
|
-
return;
|
|
1097
|
-
}
|
|
1098
|
-
const value = await ctx.ui.input("Worker request", prompt);
|
|
1099
|
-
await respondRunPiExtensionUiViaServer(options.context, options.runId, requestId, value === undefined ? { cancelled: true } : { value });
|
|
1100
|
-
} catch (error) {
|
|
1101
|
-
ctx.ui.notify(`Failed to answer worker UI request: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "worker", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
|
|
1105
|
-
function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
1106
|
-
for (const command of commands) {
|
|
1107
|
-
const record = recordOf2(command);
|
|
1108
|
-
const name = typeof record?.name === "string" ? record.name : "";
|
|
1109
|
-
const source = typeof record?.source === "string" ? record.source : "worker";
|
|
1110
|
-
if (!name || source === "builtin" || registered.has(name) || LOCALLY_OWNED_COMMANDS.has(name))
|
|
1111
|
-
continue;
|
|
1112
|
-
registered.add(name);
|
|
1113
|
-
const description = typeof record?.description === "string" ? record.description : undefined;
|
|
1114
|
-
try {
|
|
1115
|
-
pi.registerCommand(name, {
|
|
1116
|
-
description: `[worker ${source}] ${description ?? ""}`.trim(),
|
|
1117
|
-
handler: async (args) => {
|
|
1118
|
-
try {
|
|
1119
|
-
const message = await options.controller.sendCommand(`/${name}${args ? ` ${args}` : ""}`);
|
|
1120
|
-
ctx.ui.notify(message, "info");
|
|
1121
|
-
} catch (error) {
|
|
1122
|
-
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
});
|
|
1126
|
-
} catch {}
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
function createRigWorkerPiBridgeExtension(options) {
|
|
1130
|
-
return (pi) => {
|
|
1131
|
-
const registeredDaemonCommands = new Set;
|
|
1132
|
-
let capabilityLines = null;
|
|
1133
|
-
let statusText = "connecting to worker session";
|
|
1134
|
-
let busy = true;
|
|
1135
|
-
let connected = false;
|
|
1136
|
-
let frame = 0;
|
|
1137
|
-
let spinnerTimer = null;
|
|
1138
|
-
const renderStatus = (ctx) => {
|
|
1139
|
-
const prefix = busy ? `${SPINNER_FRAMES[frame]} ` : connected ? "\u25CF " : "\u25CB ";
|
|
1140
|
-
ctx.ui.setStatus("rig-worker-pi", `${prefix}${statusText}`);
|
|
1141
|
-
};
|
|
1142
|
-
const setStatus = (ctx, text, isBusy) => {
|
|
1143
|
-
statusText = text;
|
|
1144
|
-
busy = isBusy;
|
|
1145
|
-
renderStatus(ctx);
|
|
1146
|
-
};
|
|
1147
|
-
pi.registerCommand("detach", {
|
|
1148
|
-
description: "Detach from this run; the worker keeps going",
|
|
1149
|
-
handler: async (_args, ctx) => {
|
|
1150
|
-
ctx.ui.notify("Detached locally; the worker Pi daemon continues.", "info");
|
|
1151
|
-
ctx.shutdown();
|
|
1152
|
-
}
|
|
1153
|
-
});
|
|
1154
|
-
pi.registerCommand("stop", {
|
|
1155
|
-
description: "Stop the worker Pi run and detach",
|
|
1156
|
-
handler: async (_args, ctx) => {
|
|
1157
|
-
await options.controller.abort();
|
|
1158
|
-
ctx.ui.notify("Stop requested for the worker Pi daemon.", "info");
|
|
1159
|
-
ctx.shutdown();
|
|
1160
|
-
}
|
|
1161
|
-
});
|
|
1162
|
-
pi.registerCommand("worker", {
|
|
1163
|
-
description: "Show the worker session's real capabilities (tools, extensions, hooks, skills)",
|
|
1164
|
-
handler: async (_args, ctx) => {
|
|
1165
|
-
if (capabilityLines)
|
|
1166
|
-
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
1167
|
-
else
|
|
1168
|
-
ctx.ui.notify("Worker capabilities are not available yet (still connecting).", "info");
|
|
1169
|
-
}
|
|
1170
|
-
});
|
|
1171
|
-
pi.on("user_bash", (event) => ({
|
|
1172
|
-
operations: createRemoteBashOperations(options.controller, event.excludeFromContext === true)
|
|
1173
|
-
}));
|
|
1174
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
1175
|
-
ctx.ui.setTitle("Rig \xB7 enriched bundled Pi");
|
|
1176
|
-
setStatus(ctx, "waiting for worker Pi daemon", true);
|
|
1177
|
-
spinnerTimer = setInterval(() => {
|
|
1178
|
-
frame = (frame + 1) % SPINNER_FRAMES.length;
|
|
1179
|
-
if (busy)
|
|
1180
|
-
renderStatus(ctx);
|
|
1181
|
-
}, 150);
|
|
1182
|
-
ctx.ui.notify(`Enriched bundled Pi \u2014 native UI + Rig layers, worker brain. Attached to run ${options.runId}. /worker shows live capabilities \xB7 /detach exits \xB7 /stop cancels.`, "info");
|
|
1183
|
-
if (options.initialMessageSent)
|
|
1184
|
-
ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
|
|
1185
|
-
const nativeUi = ctx.ui;
|
|
1186
|
-
options.controller.setUiHooks({
|
|
1187
|
-
onStatusText: (text) => setStatus(ctx, text, !connected),
|
|
1188
|
-
onActivity: (label, detail) => {
|
|
1189
|
-
const active = label !== "idle" && !/complete|ready/i.test(label);
|
|
1190
|
-
setStatus(ctx, [label, detail].filter(Boolean).join(" \u2014 "), active);
|
|
1191
|
-
if (/agent running|tool:/.test(label))
|
|
1192
|
-
ctx.ui.setWidget("rig-worker-capabilities", undefined);
|
|
1193
|
-
},
|
|
1194
|
-
onConnectionChange: (nextConnected) => {
|
|
1195
|
-
connected = nextConnected;
|
|
1196
|
-
setStatus(ctx, nextConnected ? "worker session live" : "worker session disconnected", false);
|
|
1197
|
-
},
|
|
1198
|
-
onError: (message) => ctx.ui.notify(message, "error"),
|
|
1199
|
-
onExtensionUiRequest: (request) => {
|
|
1200
|
-
answerExtensionUiRequest(options, ctx, request);
|
|
1201
|
-
},
|
|
1202
|
-
onCatchUp: (messages, commands, capabilities) => {
|
|
1203
|
-
if (nativeUi.appendSessionMessages)
|
|
1204
|
-
nativeUi.appendSessionMessages(messages);
|
|
1205
|
-
const cwd = options.controller.status.cwd;
|
|
1206
|
-
if (nativeUi.setDisplayCwd && cwd)
|
|
1207
|
-
nativeUi.setDisplayCwd(cwd);
|
|
1208
|
-
registerDaemonCommands(pi, options, ctx, commands, registeredDaemonCommands);
|
|
1209
|
-
if (capabilities) {
|
|
1210
|
-
capabilityLines = renderWorkerCapabilities(capabilities);
|
|
1211
|
-
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
});
|
|
1215
|
-
options.controller.connect().catch((error) => {
|
|
1216
|
-
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
1217
|
-
});
|
|
1218
|
-
});
|
|
1219
|
-
pi.on("session_shutdown", () => {
|
|
1220
|
-
if (spinnerTimer)
|
|
1221
|
-
clearInterval(spinnerTimer);
|
|
1222
|
-
options.controller.close();
|
|
1223
|
-
});
|
|
1224
|
-
};
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
// packages/cli/src/commands/_pi-frontend.ts
|
|
552
|
+
import { main as runPiMain } from "@earendil-works/pi-coding-agent";
|
|
553
|
+
import createPiRigExtension from "@rig/pi-rig";
|
|
1228
554
|
function setTemporaryEnv(updates) {
|
|
1229
555
|
const previous = new Map;
|
|
1230
556
|
for (const [key, value] of Object.entries(updates)) {
|
|
@@ -1240,51 +566,38 @@ function setTemporaryEnv(updates) {
|
|
|
1240
566
|
}
|
|
1241
567
|
};
|
|
1242
568
|
}
|
|
569
|
+
function buildOperatorPiEnv(input) {
|
|
570
|
+
return {
|
|
571
|
+
PI_CODING_AGENT_SESSION_DIR: input.sessionDir,
|
|
572
|
+
PI_SKIP_VERSION_CHECK: "1",
|
|
573
|
+
RIG_PI_OPERATOR_SESSION: "1",
|
|
574
|
+
RIG_RUN_ID: input.runId,
|
|
575
|
+
RIG_SERVER_URL: input.serverUrl,
|
|
576
|
+
...input.authToken ? { RIG_AUTH_TOKEN: input.authToken } : {},
|
|
577
|
+
...input.serverProjectRoot ? { RIG_PROJECT_ROOT: input.serverProjectRoot } : {}
|
|
578
|
+
};
|
|
579
|
+
}
|
|
1243
580
|
async function attachRunBundledPiFrontend(context, input) {
|
|
1244
581
|
const tempSessionDir = mkdtempSync(join(tmpdir(), "rig-pi-frontend-sessions-"));
|
|
1245
|
-
const
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
resourceLoaderOptions: {
|
|
1256
|
-
extensionFactories: [
|
|
1257
|
-
createRigWorkerPiBridgeExtension({
|
|
1258
|
-
context,
|
|
1259
|
-
controller,
|
|
1260
|
-
runId: input.runId,
|
|
1261
|
-
initialMessageSent: input.steered === true
|
|
1262
|
-
})
|
|
1263
|
-
],
|
|
1264
|
-
noExtensions: true,
|
|
1265
|
-
noSkills: true,
|
|
1266
|
-
noPromptTemplates: true,
|
|
1267
|
-
noContextFiles: true
|
|
1268
|
-
}
|
|
1269
|
-
});
|
|
1270
|
-
const created = await createAgentSessionFromServices({
|
|
1271
|
-
services,
|
|
1272
|
-
sessionManager,
|
|
1273
|
-
sessionStartEvent,
|
|
1274
|
-
noTools: "all",
|
|
1275
|
-
sessionFactory: (config) => new RigRemoteAgentSession(config, controller)
|
|
1276
|
-
});
|
|
1277
|
-
return { ...created, services, diagnostics: services.diagnostics };
|
|
582
|
+
const server = await ensureServerForCli(context.projectRoot);
|
|
583
|
+
const restoreEnv = setTemporaryEnv(buildOperatorPiEnv({
|
|
584
|
+
runId: input.runId,
|
|
585
|
+
serverUrl: server.baseUrl,
|
|
586
|
+
authToken: server.authToken,
|
|
587
|
+
serverProjectRoot: server.serverProjectRoot,
|
|
588
|
+
sessionDir: tempSessionDir
|
|
589
|
+
}));
|
|
590
|
+
const piRigExtensionFactory = (pi) => {
|
|
591
|
+
createPiRigExtension(pi);
|
|
1278
592
|
};
|
|
1279
593
|
let detached = false;
|
|
1280
594
|
try {
|
|
1281
595
|
await runPiMain([], {
|
|
1282
|
-
|
|
596
|
+
extensionFactories: [piRigExtensionFactory]
|
|
1283
597
|
});
|
|
1284
598
|
detached = true;
|
|
1285
599
|
} finally {
|
|
1286
600
|
restoreEnv();
|
|
1287
|
-
controller.close();
|
|
1288
601
|
rmSync(tempSessionDir, { recursive: true, force: true });
|
|
1289
602
|
}
|
|
1290
603
|
let run = { runId: input.runId, status: "unknown" };
|
|
@@ -1298,12 +611,12 @@ async function attachRunBundledPiFrontend(context, input) {
|
|
|
1298
611
|
timelineCursor: null,
|
|
1299
612
|
steered: input.steered === true,
|
|
1300
613
|
detached,
|
|
1301
|
-
rendered: "
|
|
614
|
+
rendered: "stock Pi operator console with the pi-rig extension"
|
|
1302
615
|
};
|
|
1303
616
|
}
|
|
1304
617
|
|
|
1305
618
|
// packages/cli/src/commands/_operator-view.ts
|
|
1306
|
-
var
|
|
619
|
+
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
1307
620
|
function runStatusFromPayload(payload) {
|
|
1308
621
|
const run = payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
|
|
1309
622
|
return String(run.status ?? "unknown").toLowerCase();
|
|
@@ -1380,7 +693,7 @@ async function attachRunOperatorView(context, input) {
|
|
|
1380
693
|
}
|
|
1381
694
|
const pollMs = Math.max(250, Math.trunc(input.pollMs ?? 2000));
|
|
1382
695
|
let timelineCursor = snapshot.timelineCursor;
|
|
1383
|
-
while (!detached && !
|
|
696
|
+
while (!detached && !TERMINAL_RUN_STATUSES.has(runStatusFromPayload(snapshot.run))) {
|
|
1384
697
|
await Bun.sleep(pollMs);
|
|
1385
698
|
snapshot = await readOperatorSnapshot(context, input.runId, { timelineCursor });
|
|
1386
699
|
timelineCursor = snapshot.timelineCursor;
|