@h-rig/cli 0.0.6-alpha.63 → 0.0.6-alpha.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/rig.js +1349 -1599
- package/dist/src/commands/_connection-state.js +14 -5
- package/dist/src/commands/_doctor-checks.js +71 -11
- package/dist/src/commands/_help-catalog.js +99 -60
- package/dist/src/commands/_json-output.js +56 -0
- package/dist/src/commands/_operator-view.js +97 -784
- package/dist/src/commands/_parsers.js +18 -9
- package/dist/src/commands/_pi-frontend.js +96 -788
- package/dist/src/commands/_policy.js +12 -3
- package/dist/src/commands/_preflight.js +73 -13
- package/dist/src/commands/_run-driver-helpers.js +31 -5
- package/dist/src/commands/_run-replay.js +2 -2
- package/dist/src/commands/_server-client.js +76 -16
- package/dist/src/commands/_snapshot-upload.js +70 -10
- package/dist/src/commands/agent.js +21 -11
- package/dist/src/commands/browser.js +24 -15
- package/dist/src/commands/connect.js +22 -17
- package/dist/src/commands/dist.js +15 -6
- package/dist/src/commands/doctor.js +70 -10
- package/dist/src/commands/github.js +79 -19
- package/dist/src/commands/inbox.js +215 -131
- package/dist/src/commands/init.js +202 -35
- package/dist/src/commands/inspect.js +77 -17
- package/dist/src/commands/inspector.js +18 -9
- package/dist/src/commands/pi.js +18 -9
- package/dist/src/commands/plugin.js +19 -10
- package/dist/src/commands/profile-and-review.js +22 -13
- package/dist/src/commands/queue.js +19 -9
- package/dist/src/commands/remote.js +25 -16
- package/dist/src/commands/repo-git-harness.js +18 -9
- package/dist/src/commands/run.js +158 -805
- package/dist/src/commands/server.js +85 -25
- package/dist/src/commands/setup.js +73 -13
- package/dist/src/commands/stats.js +681 -0
- package/dist/src/commands/task-report-bug.js +24 -15
- package/dist/src/commands/task-run-driver.js +121 -49
- package/dist/src/commands/task.js +254 -893
- package/dist/src/commands/test.js +12 -3
- package/dist/src/commands/workspace.js +14 -5
- package/dist/src/commands.js +1339 -1650
- package/dist/src/index.js +1349 -1599
- package/dist/src/launcher.js +72 -10
- package/dist/src/runner.js +14 -5
- package/package.json +10 -7
- package/dist/src/commands/_pi-remote-session.js +0 -771
- package/dist/src/commands/_pi-worker-bridge-extension.js +0 -834
|
@@ -1,834 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// packages/cli/src/commands/_server-client.ts
|
|
3
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
4
|
-
import { resolve as resolve2 } from "path";
|
|
5
|
-
|
|
6
|
-
// packages/cli/src/runner.ts
|
|
7
|
-
import { EventBus } from "@rig/runtime/control-plane/runtime/events";
|
|
8
|
-
import { CliError } from "@rig/runtime/control-plane/errors";
|
|
9
|
-
import { evaluate, loadPolicy, resolveAction } from "@rig/runtime/control-plane/runtime/guard";
|
|
10
|
-
import { buildBinary } from "@rig/runtime/control-plane/runtime/isolation";
|
|
11
|
-
import { CliError as CliError2 } from "@rig/runtime/control-plane/errors";
|
|
12
|
-
|
|
13
|
-
// packages/cli/src/commands/_server-client.ts
|
|
14
|
-
import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
|
|
15
|
-
|
|
16
|
-
// packages/cli/src/commands/_connection-state.ts
|
|
17
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
18
|
-
import { homedir } from "os";
|
|
19
|
-
import { dirname, resolve } from "path";
|
|
20
|
-
function resolveGlobalConnectionsPath(env = process.env) {
|
|
21
|
-
const explicit = env.RIG_CONNECTIONS_FILE?.trim();
|
|
22
|
-
if (explicit)
|
|
23
|
-
return resolve(explicit);
|
|
24
|
-
const stateDir = env.RIG_GLOBAL_STATE_DIR?.trim();
|
|
25
|
-
if (stateDir)
|
|
26
|
-
return resolve(stateDir, "connections.json");
|
|
27
|
-
return resolve(homedir(), ".rig", "connections.json");
|
|
28
|
-
}
|
|
29
|
-
function resolveRepoConnectionPath(projectRoot) {
|
|
30
|
-
return resolve(projectRoot, ".rig", "state", "connection.json");
|
|
31
|
-
}
|
|
32
|
-
function readJsonFile(path) {
|
|
33
|
-
if (!existsSync(path))
|
|
34
|
-
return null;
|
|
35
|
-
try {
|
|
36
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
37
|
-
} catch (error) {
|
|
38
|
-
throw new CliError2(`Invalid Rig connection state at ${path}: ${error instanceof Error ? error.message : String(error)}`, 1);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function writeJsonFile(path, value) {
|
|
42
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
43
|
-
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
44
|
-
`, "utf8");
|
|
45
|
-
}
|
|
46
|
-
function normalizeConnection(value) {
|
|
47
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
48
|
-
return null;
|
|
49
|
-
const record = value;
|
|
50
|
-
if (record.kind === "local")
|
|
51
|
-
return { kind: "local", mode: "auto" };
|
|
52
|
-
if (record.kind === "remote" && typeof record.baseUrl === "string" && record.baseUrl.trim()) {
|
|
53
|
-
const baseUrl = record.baseUrl.trim().replace(/\/+$/, "");
|
|
54
|
-
return { kind: "remote", baseUrl };
|
|
55
|
-
}
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
function readGlobalConnections(options = {}) {
|
|
59
|
-
const path = resolveGlobalConnectionsPath(options.env ?? process.env);
|
|
60
|
-
const payload = readJsonFile(path);
|
|
61
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
62
|
-
return { connections: {} };
|
|
63
|
-
}
|
|
64
|
-
const rawConnections = payload.connections;
|
|
65
|
-
const connections = {};
|
|
66
|
-
if (rawConnections && typeof rawConnections === "object" && !Array.isArray(rawConnections)) {
|
|
67
|
-
for (const [alias, raw] of Object.entries(rawConnections)) {
|
|
68
|
-
const connection = normalizeConnection(raw);
|
|
69
|
-
if (connection)
|
|
70
|
-
connections[alias] = connection;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return { connections };
|
|
74
|
-
}
|
|
75
|
-
function readRepoConnection(projectRoot) {
|
|
76
|
-
const payload = readJsonFile(resolveRepoConnectionPath(projectRoot));
|
|
77
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
78
|
-
return null;
|
|
79
|
-
const record = payload;
|
|
80
|
-
const selected = typeof record.selected === "string" ? record.selected.trim() : "";
|
|
81
|
-
if (!selected)
|
|
82
|
-
return null;
|
|
83
|
-
return {
|
|
84
|
-
selected,
|
|
85
|
-
project: typeof record.project === "string" ? record.project : undefined,
|
|
86
|
-
linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
|
|
87
|
-
serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
function writeRepoConnection(projectRoot, state) {
|
|
91
|
-
writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
|
|
92
|
-
}
|
|
93
|
-
function resolveSelectedConnection(projectRoot, options = {}) {
|
|
94
|
-
const repo = readRepoConnection(projectRoot);
|
|
95
|
-
if (!repo)
|
|
96
|
-
return null;
|
|
97
|
-
if (repo.selected === "local")
|
|
98
|
-
return { alias: "local", connection: { kind: "local", mode: "auto" }, serverProjectRoot: repo.serverProjectRoot };
|
|
99
|
-
const global = readGlobalConnections(options);
|
|
100
|
-
const connection = global.connections[repo.selected];
|
|
101
|
-
if (!connection) {
|
|
102
|
-
throw new CliError2(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
|
|
103
|
-
}
|
|
104
|
-
return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
|
|
105
|
-
}
|
|
106
|
-
function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
|
|
107
|
-
const repo = readRepoConnection(projectRoot);
|
|
108
|
-
if (!repo)
|
|
109
|
-
return;
|
|
110
|
-
writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// packages/cli/src/commands/_server-client.ts
|
|
114
|
-
var scopedGitHubBearerTokens = new Map;
|
|
115
|
-
function cleanToken(value) {
|
|
116
|
-
const trimmed = value?.trim();
|
|
117
|
-
return trimmed ? trimmed : null;
|
|
118
|
-
}
|
|
119
|
-
function readPrivateRemoteSessionToken(projectRoot) {
|
|
120
|
-
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
121
|
-
if (!existsSync2(path))
|
|
122
|
-
return null;
|
|
123
|
-
try {
|
|
124
|
-
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
125
|
-
return cleanToken(typeof parsed.apiSessionToken === "string" ? parsed.apiSessionToken : typeof parsed.sessionToken === "string" ? parsed.sessionToken : undefined);
|
|
126
|
-
} catch {
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
function readGitHubBearerTokenForRemote(projectRoot) {
|
|
131
|
-
const scopedKey = resolve2(projectRoot);
|
|
132
|
-
if (scopedGitHubBearerTokens.has(scopedKey))
|
|
133
|
-
return scopedGitHubBearerTokens.get(scopedKey) ?? null;
|
|
134
|
-
const privateSession = readPrivateRemoteSessionToken(projectRoot);
|
|
135
|
-
if (privateSession)
|
|
136
|
-
return privateSession;
|
|
137
|
-
return cleanToken(process.env.RIG_SERVER_AUTH_TOKEN) ?? cleanToken(process.env.RIG_REMOTE_AUTH_TOKEN);
|
|
138
|
-
}
|
|
139
|
-
function readStoredGitHubAuthToken(projectRoot) {
|
|
140
|
-
const path = resolve2(projectRoot, ".rig", "state", "github-auth.json");
|
|
141
|
-
if (!existsSync2(path))
|
|
142
|
-
return null;
|
|
143
|
-
try {
|
|
144
|
-
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
145
|
-
return cleanToken(typeof parsed.token === "string" ? parsed.token : undefined);
|
|
146
|
-
} catch {
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
function readLocalConnectionFallbackToken(projectRoot) {
|
|
151
|
-
return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
|
|
152
|
-
}
|
|
153
|
-
async function ensureServerForCli(projectRoot) {
|
|
154
|
-
try {
|
|
155
|
-
const selected = resolveSelectedConnection(projectRoot);
|
|
156
|
-
if (selected?.connection.kind === "remote") {
|
|
157
|
-
const authToken = readGitHubBearerTokenForRemote(projectRoot);
|
|
158
|
-
const serverProjectRoot = selected.serverProjectRoot ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
|
|
159
|
-
return {
|
|
160
|
-
baseUrl: selected.connection.baseUrl,
|
|
161
|
-
authToken,
|
|
162
|
-
connectionKind: "remote",
|
|
163
|
-
serverProjectRoot
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
const connection = await ensureLocalRigServerConnection(projectRoot);
|
|
167
|
-
return {
|
|
168
|
-
baseUrl: connection.baseUrl,
|
|
169
|
-
authToken: connection.authToken ?? readLocalConnectionFallbackToken(projectRoot),
|
|
170
|
-
connectionKind: "local",
|
|
171
|
-
serverProjectRoot: resolve2(projectRoot)
|
|
172
|
-
};
|
|
173
|
-
} catch (error) {
|
|
174
|
-
if (error instanceof Error) {
|
|
175
|
-
throw new CliError2(error.message, 1);
|
|
176
|
-
}
|
|
177
|
-
throw error;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
|
|
181
|
-
const repo = readRepoConnection(projectRoot);
|
|
182
|
-
const slug = repo?.project?.trim();
|
|
183
|
-
if (!slug)
|
|
184
|
-
return null;
|
|
185
|
-
try {
|
|
186
|
-
const response = await fetch(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`, {
|
|
187
|
-
headers: mergeHeaders(undefined, authToken)
|
|
188
|
-
});
|
|
189
|
-
if (!response.ok)
|
|
190
|
-
return null;
|
|
191
|
-
const payload = await response.json();
|
|
192
|
-
const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
|
|
193
|
-
const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
|
|
194
|
-
const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
|
|
195
|
-
const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
|
|
196
|
-
if (path)
|
|
197
|
-
writeRepoServerProjectRoot(projectRoot, path);
|
|
198
|
-
return path;
|
|
199
|
-
} catch {
|
|
200
|
-
return null;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
function mergeHeaders(headers, authToken) {
|
|
204
|
-
const merged = new Headers(headers);
|
|
205
|
-
if (authToken) {
|
|
206
|
-
merged.set("authorization", `Bearer ${authToken}`);
|
|
207
|
-
}
|
|
208
|
-
return merged;
|
|
209
|
-
}
|
|
210
|
-
function diagnosticMessage(payload) {
|
|
211
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
212
|
-
return null;
|
|
213
|
-
const record = payload;
|
|
214
|
-
const diagnostics = Array.isArray(record.diagnostics) ? record.diagnostics : [];
|
|
215
|
-
const messages = diagnostics.flatMap((entry) => {
|
|
216
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
217
|
-
return [];
|
|
218
|
-
const diagnostic = entry;
|
|
219
|
-
const kind = typeof diagnostic.kind === "string" ? diagnostic.kind : "task-source";
|
|
220
|
-
const message = typeof diagnostic.message === "string" ? diagnostic.message : null;
|
|
221
|
-
return message ? [`${kind}: ${message}`] : [];
|
|
222
|
-
});
|
|
223
|
-
return messages.length > 0 ? messages.join("; ") : null;
|
|
224
|
-
}
|
|
225
|
-
async function requestServerJson(context, pathname, init = {}) {
|
|
226
|
-
const server = await ensureServerForCli(context.projectRoot);
|
|
227
|
-
const headers = mergeHeaders(init.headers, server.authToken);
|
|
228
|
-
if (server.serverProjectRoot)
|
|
229
|
-
headers.set("x-rig-project-root", server.serverProjectRoot);
|
|
230
|
-
const response = await fetch(`${server.baseUrl}${pathname}`, {
|
|
231
|
-
...init,
|
|
232
|
-
headers
|
|
233
|
-
});
|
|
234
|
-
const text = await response.text();
|
|
235
|
-
const payload = text.trim().length > 0 ? (() => {
|
|
236
|
-
try {
|
|
237
|
-
return JSON.parse(text);
|
|
238
|
-
} catch {
|
|
239
|
-
return null;
|
|
240
|
-
}
|
|
241
|
-
})() : null;
|
|
242
|
-
if (!response.ok) {
|
|
243
|
-
const diagnostics = diagnosticMessage(payload);
|
|
244
|
-
const detail = diagnostics ?? (text || response.statusText);
|
|
245
|
-
throw new CliError2(`Rig server request failed (${response.status}): ${detail}`, 1);
|
|
246
|
-
}
|
|
247
|
-
return payload;
|
|
248
|
-
}
|
|
249
|
-
async function getRunPiSessionViaServer(context, runId) {
|
|
250
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi`);
|
|
251
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
252
|
-
}
|
|
253
|
-
async function getRunPiMessagesViaServer(context, runId) {
|
|
254
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/messages`);
|
|
255
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { messages: [] };
|
|
256
|
-
}
|
|
257
|
-
async function getRunPiStatusViaServer(context, runId) {
|
|
258
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/status`);
|
|
259
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
260
|
-
}
|
|
261
|
-
async function getRunPiCommandsViaServer(context, runId) {
|
|
262
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands`);
|
|
263
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { commands: [] };
|
|
264
|
-
}
|
|
265
|
-
async function getRunPiCapabilitiesViaServer(context, runId) {
|
|
266
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/capabilities`);
|
|
267
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { capabilities: null };
|
|
268
|
-
}
|
|
269
|
-
async function sendRunPiPromptViaServer(context, runId, text, streamingBehavior) {
|
|
270
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/prompt`, {
|
|
271
|
-
method: "POST",
|
|
272
|
-
headers: { "content-type": "application/json" },
|
|
273
|
-
body: JSON.stringify({ text, streamingBehavior })
|
|
274
|
-
});
|
|
275
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
|
|
276
|
-
}
|
|
277
|
-
async function sendRunPiShellViaServer(context, runId, text) {
|
|
278
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/shell`, {
|
|
279
|
-
method: "POST",
|
|
280
|
-
headers: { "content-type": "application/json" },
|
|
281
|
-
body: JSON.stringify({ text })
|
|
282
|
-
});
|
|
283
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
|
|
284
|
-
}
|
|
285
|
-
async function runRunPiCommandViaServer(context, runId, text) {
|
|
286
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/commands/run`, {
|
|
287
|
-
method: "POST",
|
|
288
|
-
headers: { "content-type": "application/json" },
|
|
289
|
-
body: JSON.stringify({ text })
|
|
290
|
-
});
|
|
291
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { type: "done" };
|
|
292
|
-
}
|
|
293
|
-
async function respondRunPiExtensionUiViaServer(context, runId, requestId, valueOrCancel) {
|
|
294
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/extension-ui/respond`, {
|
|
295
|
-
method: "POST",
|
|
296
|
-
headers: { "content-type": "application/json" },
|
|
297
|
-
body: JSON.stringify({ requestId, ...valueOrCancel })
|
|
298
|
-
});
|
|
299
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { accepted: true };
|
|
300
|
-
}
|
|
301
|
-
async function abortRunPiViaServer(context, runId) {
|
|
302
|
-
const payload = await requestServerJson(context, `/api/runs/${encodeURIComponent(runId)}/pi/abort`, { method: "POST" });
|
|
303
|
-
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { aborted: true };
|
|
304
|
-
}
|
|
305
|
-
async function buildRunPiEventsWebSocketUrl(context, runId) {
|
|
306
|
-
const server = await ensureServerForCli(context.projectRoot);
|
|
307
|
-
const url = new URL(`${server.baseUrl.replace(/\/+$/, "")}/api/runs/${encodeURIComponent(runId)}/pi/events`);
|
|
308
|
-
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
309
|
-
if (server.authToken)
|
|
310
|
-
url.searchParams.set("token", server.authToken);
|
|
311
|
-
if (server.serverProjectRoot)
|
|
312
|
-
url.searchParams.set("rigProjectRoot", server.serverProjectRoot);
|
|
313
|
-
return url.toString();
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// packages/cli/src/commands/_pi-remote-session.ts
|
|
317
|
-
import { AgentSession as PiAgentSession, expandPromptTemplate } from "@earendil-works/pi-coding-agent";
|
|
318
|
-
var TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "stopped", "cancelled", "canceled", "closed", "merged", "needs_attention", "needs-attention"]);
|
|
319
|
-
function defaultTransport() {
|
|
320
|
-
return {
|
|
321
|
-
getSession: getRunPiSessionViaServer,
|
|
322
|
-
getMessages: getRunPiMessagesViaServer,
|
|
323
|
-
getStatus: getRunPiStatusViaServer,
|
|
324
|
-
getCommands: getRunPiCommandsViaServer,
|
|
325
|
-
getCapabilities: getRunPiCapabilitiesViaServer,
|
|
326
|
-
sendPrompt: sendRunPiPromptViaServer,
|
|
327
|
-
sendShell: sendRunPiShellViaServer,
|
|
328
|
-
runCommand: runRunPiCommandViaServer,
|
|
329
|
-
abort: abortRunPiViaServer,
|
|
330
|
-
buildEventsWebSocketUrl: buildRunPiEventsWebSocketUrl
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
function recordOf(value) {
|
|
334
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
335
|
-
}
|
|
336
|
-
function emptyRemoteStatus() {
|
|
337
|
-
return {
|
|
338
|
-
isStreaming: false,
|
|
339
|
-
isCompacting: false,
|
|
340
|
-
isBashRunning: false,
|
|
341
|
-
pendingMessageCount: 0,
|
|
342
|
-
steeringMessages: [],
|
|
343
|
-
followUpMessages: [],
|
|
344
|
-
model: null,
|
|
345
|
-
thinkingLevel: null,
|
|
346
|
-
sessionName: null,
|
|
347
|
-
cwd: null,
|
|
348
|
-
stats: null,
|
|
349
|
-
contextUsage: null
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
function resolveAttachReadyTimeoutMs() {
|
|
353
|
-
const raw = Number.parseInt(process.env.RIG_PI_ATTACH_TIMEOUT_MS ?? "", 10);
|
|
354
|
-
return Number.isFinite(raw) && raw > 0 ? raw : 10 * 60000;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
class RigRemoteSessionController {
|
|
358
|
-
context;
|
|
359
|
-
runId;
|
|
360
|
-
status = emptyRemoteStatus();
|
|
361
|
-
transport;
|
|
362
|
-
hooks = {};
|
|
363
|
-
session = null;
|
|
364
|
-
socket = null;
|
|
365
|
-
closed = false;
|
|
366
|
-
pendingShells = [];
|
|
367
|
-
pendingCompactions = [];
|
|
368
|
-
constructor(input) {
|
|
369
|
-
this.context = input.context;
|
|
370
|
-
this.runId = input.runId;
|
|
371
|
-
this.transport = input.transport ?? defaultTransport();
|
|
372
|
-
}
|
|
373
|
-
ingestEnvelope(envelopeValue) {
|
|
374
|
-
this.applyEnvelope(envelopeValue);
|
|
375
|
-
}
|
|
376
|
-
bindSession(session) {
|
|
377
|
-
this.session = session;
|
|
378
|
-
}
|
|
379
|
-
setUiHooks(hooks) {
|
|
380
|
-
this.hooks = hooks;
|
|
381
|
-
}
|
|
382
|
-
async connect() {
|
|
383
|
-
const ready = await this.waitForReady();
|
|
384
|
-
if (!ready || this.closed)
|
|
385
|
-
return;
|
|
386
|
-
let catchupDone = false;
|
|
387
|
-
const buffered = [];
|
|
388
|
-
const wsUrl = await this.transport.buildEventsWebSocketUrl(this.context, this.runId);
|
|
389
|
-
const socket = new WebSocket(wsUrl);
|
|
390
|
-
this.socket = socket;
|
|
391
|
-
socket.onopen = () => {
|
|
392
|
-
this.hooks.onConnectionChange?.(true);
|
|
393
|
-
this.hooks.onStatusText?.("worker session live");
|
|
394
|
-
};
|
|
395
|
-
socket.onmessage = (message) => {
|
|
396
|
-
try {
|
|
397
|
-
const payload = typeof message.data === "string" ? JSON.parse(message.data) : JSON.parse(Buffer.from(message.data).toString("utf8"));
|
|
398
|
-
if (!catchupDone)
|
|
399
|
-
buffered.push(payload);
|
|
400
|
-
else
|
|
401
|
-
this.applyEnvelope(payload);
|
|
402
|
-
} catch (error) {
|
|
403
|
-
this.hooks.onError?.(`Unparseable worker Pi event: ${error instanceof Error ? error.message : String(error)}`);
|
|
404
|
-
}
|
|
405
|
-
};
|
|
406
|
-
socket.onerror = () => socket.close();
|
|
407
|
-
socket.onclose = () => {
|
|
408
|
-
this.hooks.onConnectionChange?.(false);
|
|
409
|
-
if (!this.closed)
|
|
410
|
-
this.hooks.onStatusText?.("worker Pi WebSocket disconnected");
|
|
411
|
-
};
|
|
412
|
-
try {
|
|
413
|
-
const [messagesPayload, statusPayload, commandsPayload, capabilitiesPayload] = await Promise.all([
|
|
414
|
-
this.transport.getMessages(this.context, this.runId),
|
|
415
|
-
this.transport.getStatus(this.context, this.runId),
|
|
416
|
-
this.transport.getCommands(this.context, this.runId),
|
|
417
|
-
this.transport.getCapabilities(this.context, this.runId).catch(() => ({ capabilities: null }))
|
|
418
|
-
]);
|
|
419
|
-
const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
|
|
420
|
-
this.applyStatusPayload(statusPayload);
|
|
421
|
-
this.session?.replaceRemoteMessages(messages);
|
|
422
|
-
const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
|
|
423
|
-
const capabilities = capabilitiesPayload.capabilities && typeof capabilitiesPayload.capabilities === "object" && !Array.isArray(capabilitiesPayload.capabilities) ? capabilitiesPayload.capabilities : null;
|
|
424
|
-
this.hooks.onCatchUp?.(messages, commands, capabilities);
|
|
425
|
-
catchupDone = true;
|
|
426
|
-
for (const payload of buffered.splice(0))
|
|
427
|
-
this.applyEnvelope(payload);
|
|
428
|
-
} catch (error) {
|
|
429
|
-
catchupDone = true;
|
|
430
|
-
this.hooks.onError?.(`Worker Pi catch-up failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
close() {
|
|
434
|
-
this.closed = true;
|
|
435
|
-
this.socket?.close();
|
|
436
|
-
for (const shell of this.pendingShells.splice(0))
|
|
437
|
-
shell.reject(new Error("Remote session closed."));
|
|
438
|
-
for (const compaction of this.pendingCompactions.splice(0))
|
|
439
|
-
compaction.reject(new Error("Remote session closed."));
|
|
440
|
-
}
|
|
441
|
-
async sendPrompt(text, streamingBehavior) {
|
|
442
|
-
await this.transport.sendPrompt(this.context, this.runId, text, streamingBehavior);
|
|
443
|
-
}
|
|
444
|
-
async sendCommand(text) {
|
|
445
|
-
const result = await this.transport.runCommand(this.context, this.runId, text);
|
|
446
|
-
return typeof result.message === "string" ? result.message : "worker command accepted";
|
|
447
|
-
}
|
|
448
|
-
async sendShell(text) {
|
|
449
|
-
await this.transport.sendShell(this.context, this.runId, text);
|
|
450
|
-
}
|
|
451
|
-
async abort() {
|
|
452
|
-
await this.transport.abort(this.context, this.runId);
|
|
453
|
-
}
|
|
454
|
-
registerPendingShell(shell) {
|
|
455
|
-
this.pendingShells.push(shell);
|
|
456
|
-
}
|
|
457
|
-
failPendingShell(shell, error) {
|
|
458
|
-
const index = this.pendingShells.indexOf(shell);
|
|
459
|
-
if (index !== -1)
|
|
460
|
-
this.pendingShells.splice(index, 1);
|
|
461
|
-
shell.reject(error);
|
|
462
|
-
}
|
|
463
|
-
registerPendingCompaction(pending) {
|
|
464
|
-
this.pendingCompactions.push(pending);
|
|
465
|
-
}
|
|
466
|
-
async waitForReady() {
|
|
467
|
-
const startedAt = Date.now();
|
|
468
|
-
const deadline = startedAt + resolveAttachReadyTimeoutMs();
|
|
469
|
-
let consecutiveFailures = 0;
|
|
470
|
-
while (!this.closed) {
|
|
471
|
-
let requestFailed = false;
|
|
472
|
-
const session = await this.transport.getSession(this.context, this.runId).catch((error) => {
|
|
473
|
-
requestFailed = true;
|
|
474
|
-
return { ready: false, status: error instanceof Error ? error.message : String(error), retryAfterMs: 1000 };
|
|
475
|
-
});
|
|
476
|
-
if (session.ready !== false)
|
|
477
|
-
return true;
|
|
478
|
-
consecutiveFailures = requestFailed ? consecutiveFailures + 1 : 0;
|
|
479
|
-
const status = String(session.status ?? "starting");
|
|
480
|
-
if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
|
|
481
|
-
this.hooks.onError?.(`Run ended before the worker Pi daemon became ready: ${status}. Inspect with \`rig run show ${this.runId}\`.`);
|
|
482
|
-
return false;
|
|
483
|
-
}
|
|
484
|
-
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`);
|
|
485
|
-
if (Date.now() >= deadline) {
|
|
486
|
-
this.hooks.onError?.(`Worker Pi daemon did not become ready (last status: ${status}). Set RIG_PI_ATTACH_TIMEOUT_MS to wait longer.`);
|
|
487
|
-
return false;
|
|
488
|
-
}
|
|
489
|
-
await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
|
|
490
|
-
}
|
|
491
|
-
return false;
|
|
492
|
-
}
|
|
493
|
-
applyEnvelope(envelopeValue) {
|
|
494
|
-
const envelope = recordOf(envelopeValue);
|
|
495
|
-
if (!envelope)
|
|
496
|
-
return;
|
|
497
|
-
const type = String(envelope.type ?? "");
|
|
498
|
-
if (type === "status.update") {
|
|
499
|
-
this.applyStatusPayload(envelope);
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
if (type === "activity.update") {
|
|
503
|
-
const activity = recordOf(envelope.activity);
|
|
504
|
-
this.hooks.onActivity?.(String(activity?.label ?? ""), activity?.detail ? String(activity.detail) : undefined);
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
if (type === "extension_ui_request") {
|
|
508
|
-
const request = recordOf(envelope.request);
|
|
509
|
-
if (request)
|
|
510
|
-
this.hooks.onExtensionUiRequest?.(request);
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
if (type === "pi.ui_event") {
|
|
514
|
-
this.applyShellUiEvent(envelope.event);
|
|
515
|
-
return;
|
|
516
|
-
}
|
|
517
|
-
if (type === "pi.event") {
|
|
518
|
-
this.session?.handleRemoteSessionEvent(envelope.event);
|
|
519
|
-
this.settlePendingCompaction(envelope.event);
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
522
|
-
if (type === "error") {
|
|
523
|
-
this.hooks.onError?.(String(envelope.message ?? envelope.detail ?? "unknown worker error"));
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
applyStatusPayload(payload) {
|
|
527
|
-
const status = recordOf(payload.status) ?? payload;
|
|
528
|
-
const next = this.status;
|
|
529
|
-
next.isStreaming = status.isStreaming === true;
|
|
530
|
-
next.isCompacting = status.isCompacting === true;
|
|
531
|
-
next.isBashRunning = status.isBashRunning === true;
|
|
532
|
-
next.pendingMessageCount = typeof status.pendingMessageCount === "number" ? status.pendingMessageCount : 0;
|
|
533
|
-
next.steeringMessages = Array.isArray(status.steeringMessages) ? status.steeringMessages.map(String) : [];
|
|
534
|
-
next.followUpMessages = Array.isArray(status.followUpMessages) ? status.followUpMessages.map(String) : [];
|
|
535
|
-
next.model = recordOf(status.model) ?? next.model;
|
|
536
|
-
next.thinkingLevel = typeof status.thinkingLevel === "string" ? status.thinkingLevel : next.thinkingLevel;
|
|
537
|
-
next.sessionName = typeof status.sessionName === "string" ? status.sessionName : next.sessionName;
|
|
538
|
-
next.cwd = typeof status.cwd === "string" ? status.cwd : next.cwd;
|
|
539
|
-
next.stats = recordOf(status.stats) ?? next.stats;
|
|
540
|
-
next.contextUsage = recordOf(status.contextUsage) ?? next.contextUsage;
|
|
541
|
-
}
|
|
542
|
-
applyShellUiEvent(value) {
|
|
543
|
-
const event = recordOf(value);
|
|
544
|
-
if (!event)
|
|
545
|
-
return;
|
|
546
|
-
const type = String(event.type ?? "");
|
|
547
|
-
const pending = this.pendingShells[0];
|
|
548
|
-
if (type === "shell.chunk" && pending) {
|
|
549
|
-
pending.sawChunk = true;
|
|
550
|
-
pending.onData(Buffer.from(String(event.chunk ?? "")));
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
if (type === "shell.end" && pending) {
|
|
554
|
-
const output = String(event.output ?? "");
|
|
555
|
-
if (output && !pending.sawChunk)
|
|
556
|
-
pending.onData(Buffer.from(output));
|
|
557
|
-
const index = this.pendingShells.indexOf(pending);
|
|
558
|
-
if (index !== -1)
|
|
559
|
-
this.pendingShells.splice(index, 1);
|
|
560
|
-
pending.resolve({ exitCode: typeof event.exitCode === "number" ? event.exitCode : event.isError === true ? 1 : 0 });
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
settlePendingCompaction(eventValue) {
|
|
564
|
-
const event = recordOf(eventValue);
|
|
565
|
-
if (!event)
|
|
566
|
-
return;
|
|
567
|
-
const type = String(event.type ?? "");
|
|
568
|
-
if (type !== "compaction_end" || this.pendingCompactions.length === 0)
|
|
569
|
-
return;
|
|
570
|
-
const pending = this.pendingCompactions.shift();
|
|
571
|
-
const result = recordOf(event.result);
|
|
572
|
-
if (result)
|
|
573
|
-
pending.resolve(result);
|
|
574
|
-
else if (event.aborted === true)
|
|
575
|
-
pending.reject(new Error("Compaction aborted on the worker."));
|
|
576
|
-
else
|
|
577
|
-
pending.resolve({});
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
function createRemoteBashOperations(controller, excludeFromContext) {
|
|
581
|
-
return {
|
|
582
|
-
exec(command, _cwd, execOptions) {
|
|
583
|
-
return new Promise((resolve3, reject) => {
|
|
584
|
-
const pending = { onData: execOptions.onData, resolve: resolve3, reject, sawChunk: false };
|
|
585
|
-
const cleanup = () => {
|
|
586
|
-
execOptions.signal?.removeEventListener("abort", onAbort);
|
|
587
|
-
if (timer)
|
|
588
|
-
clearTimeout(timer);
|
|
589
|
-
};
|
|
590
|
-
const onAbort = () => {
|
|
591
|
-
cleanup();
|
|
592
|
-
controller.failPendingShell(pending, new Error("Remote worker shell command aborted locally."));
|
|
593
|
-
};
|
|
594
|
-
const timeoutMs = typeof execOptions.timeout === "number" && execOptions.timeout > 0 ? execOptions.timeout : 0;
|
|
595
|
-
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
596
|
-
cleanup();
|
|
597
|
-
controller.failPendingShell(pending, new Error(`Remote worker shell command timed out after ${timeoutMs}ms.`));
|
|
598
|
-
}, timeoutMs) : null;
|
|
599
|
-
const wrappedResolve = pending.resolve;
|
|
600
|
-
const wrappedReject = pending.reject;
|
|
601
|
-
pending.resolve = (result) => {
|
|
602
|
-
cleanup();
|
|
603
|
-
wrappedResolve(result);
|
|
604
|
-
};
|
|
605
|
-
pending.reject = (error) => {
|
|
606
|
-
cleanup();
|
|
607
|
-
wrappedReject(error);
|
|
608
|
-
};
|
|
609
|
-
execOptions.signal?.addEventListener("abort", onAbort, { once: true });
|
|
610
|
-
controller.registerPendingShell(pending);
|
|
611
|
-
controller.sendShell(`${excludeFromContext ? "!!" : "!"}${command}`).catch((error) => {
|
|
612
|
-
cleanup();
|
|
613
|
-
controller.failPendingShell(pending, error instanceof Error ? error : new Error(String(error)));
|
|
614
|
-
});
|
|
615
|
-
});
|
|
616
|
-
}
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// packages/cli/src/commands/_spinner.ts
|
|
621
|
-
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
622
|
-
|
|
623
|
-
// packages/cli/src/commands/_pi-worker-bridge-extension.ts
|
|
624
|
-
function recordOf2(value) {
|
|
625
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
626
|
-
}
|
|
627
|
-
function asText(value) {
|
|
628
|
-
if (typeof value === "string")
|
|
629
|
-
return value;
|
|
630
|
-
if (value === null || value === undefined)
|
|
631
|
-
return "";
|
|
632
|
-
if (typeof value === "number" || typeof value === "boolean")
|
|
633
|
-
return String(value);
|
|
634
|
-
try {
|
|
635
|
-
return JSON.stringify(value);
|
|
636
|
-
} catch {
|
|
637
|
-
return String(value);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
function names(value, key = "name") {
|
|
641
|
-
if (!Array.isArray(value))
|
|
642
|
-
return [];
|
|
643
|
-
return value.flatMap((entry) => {
|
|
644
|
-
if (typeof entry === "string")
|
|
645
|
-
return [entry];
|
|
646
|
-
const record = recordOf2(entry);
|
|
647
|
-
const name = record?.[key];
|
|
648
|
-
return typeof name === "string" ? [name] : [];
|
|
649
|
-
});
|
|
650
|
-
}
|
|
651
|
-
function renderWorkerCapabilities(capabilities) {
|
|
652
|
-
const lines = ["Worker session capabilities (in effect for this run)"];
|
|
653
|
-
const tools = Array.isArray(capabilities.tools) ? capabilities.tools : [];
|
|
654
|
-
const activeTools = tools.flatMap((tool) => {
|
|
655
|
-
const record = recordOf2(tool);
|
|
656
|
-
return record && record.active === true && typeof record.name === "string" ? [record.name] : [];
|
|
657
|
-
});
|
|
658
|
-
const inactiveCount = tools.length - activeTools.length;
|
|
659
|
-
lines.push(` tools ${activeTools.join(", ") || "(none)"}${inactiveCount > 0 ? ` (+${inactiveCount} inactive)` : ""}`);
|
|
660
|
-
const extensions = Array.isArray(capabilities.extensions) ? capabilities.extensions : [];
|
|
661
|
-
const extensionLabels = extensions.flatMap((entry) => {
|
|
662
|
-
const record = recordOf2(entry);
|
|
663
|
-
if (!record)
|
|
664
|
-
return [];
|
|
665
|
-
const path = typeof record.path === "string" ? record.path : "";
|
|
666
|
-
const short = path.split("/").filter(Boolean).slice(-2).join("/") || path || "extension";
|
|
667
|
-
return [short];
|
|
668
|
-
});
|
|
669
|
-
lines.push(` extensions ${extensionLabels.join(", ") || "(none)"}`);
|
|
670
|
-
const hookEvents = names(capabilities.hookEvents);
|
|
671
|
-
const hookList = Array.isArray(capabilities.hookEvents) ? capabilities.hookEvents.map(asText).filter(Boolean) : hookEvents;
|
|
672
|
-
lines.push(` hooks ${hookList.join(", ") || "(none)"}`);
|
|
673
|
-
lines.push(` skills ${names(capabilities.skills).join(", ") || "(none)"}`);
|
|
674
|
-
lines.push(` prompts ${names(capabilities.prompts).join(", ") || "(none)"}`);
|
|
675
|
-
const model = typeof capabilities.model === "string" ? capabilities.model : "(unknown)";
|
|
676
|
-
const thinking = typeof capabilities.thinkingLevel === "string" ? capabilities.thinkingLevel : "";
|
|
677
|
-
const cwd = typeof capabilities.cwd === "string" ? capabilities.cwd : "";
|
|
678
|
-
lines.push(` model ${model}${thinking ? ` \xB7 ${thinking}` : ""}`);
|
|
679
|
-
if (cwd)
|
|
680
|
-
lines.push(` cwd ${cwd}`);
|
|
681
|
-
lines.push(" (worker commands are in your palette as [worker \u2026] \xB7 /worker shows this again)");
|
|
682
|
-
return lines;
|
|
683
|
-
}
|
|
684
|
-
async function answerExtensionUiRequest(options, ctx, request) {
|
|
685
|
-
const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
|
|
686
|
-
const method = String(request.method ?? request.type ?? "input");
|
|
687
|
-
const prompt = asText(request.prompt ?? request.message ?? request.title ?? method);
|
|
688
|
-
const rawOptions = Array.isArray(request.options) ? request.options : Array.isArray(request.items) ? request.items : [];
|
|
689
|
-
const choices = rawOptions.map((option) => {
|
|
690
|
-
const record = recordOf2(option);
|
|
691
|
-
return record ? asText(record.label ?? record.value ?? record.name ?? option) : asText(option);
|
|
692
|
-
}).filter(Boolean);
|
|
693
|
-
try {
|
|
694
|
-
if (method === "confirm") {
|
|
695
|
-
const confirmed = await ctx.ui.confirm("Worker request", prompt);
|
|
696
|
-
await respondRunPiExtensionUiViaServer(options.context, options.runId, requestId, { value: confirmed, confirmed });
|
|
697
|
-
return;
|
|
698
|
-
}
|
|
699
|
-
if (choices.length > 0) {
|
|
700
|
-
const selected = await ctx.ui.select(prompt, choices);
|
|
701
|
-
await respondRunPiExtensionUiViaServer(options.context, options.runId, requestId, selected === undefined ? { cancelled: true } : { value: selected });
|
|
702
|
-
return;
|
|
703
|
-
}
|
|
704
|
-
const value = await ctx.ui.input("Worker request", prompt);
|
|
705
|
-
await respondRunPiExtensionUiViaServer(options.context, options.runId, requestId, value === undefined ? { cancelled: true } : { value });
|
|
706
|
-
} catch (error) {
|
|
707
|
-
ctx.ui.notify(`Failed to answer worker UI request: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
var LOCALLY_OWNED_COMMANDS = new Set(["detach", "quit", "q", "stop", "worker", "settings", "model", "thinking", "compact", "session", "name", "reload"]);
|
|
711
|
-
function registerDaemonCommands(pi, options, ctx, commands, registered) {
|
|
712
|
-
for (const command of commands) {
|
|
713
|
-
const record = recordOf2(command);
|
|
714
|
-
const name = typeof record?.name === "string" ? record.name : "";
|
|
715
|
-
const source = typeof record?.source === "string" ? record.source : "worker";
|
|
716
|
-
if (!name || source === "builtin" || registered.has(name) || LOCALLY_OWNED_COMMANDS.has(name))
|
|
717
|
-
continue;
|
|
718
|
-
registered.add(name);
|
|
719
|
-
const description = typeof record?.description === "string" ? record.description : undefined;
|
|
720
|
-
try {
|
|
721
|
-
pi.registerCommand(name, {
|
|
722
|
-
description: `[worker ${source}] ${description ?? ""}`.trim(),
|
|
723
|
-
handler: async (args) => {
|
|
724
|
-
try {
|
|
725
|
-
const message = await options.controller.sendCommand(`/${name}${args ? ` ${args}` : ""}`);
|
|
726
|
-
ctx.ui.notify(message, "info");
|
|
727
|
-
} catch (error) {
|
|
728
|
-
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
});
|
|
732
|
-
} catch {}
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
function createRigWorkerPiBridgeExtension(options) {
|
|
736
|
-
return (pi) => {
|
|
737
|
-
const registeredDaemonCommands = new Set;
|
|
738
|
-
let capabilityLines = null;
|
|
739
|
-
let statusText = "connecting to worker session";
|
|
740
|
-
let busy = true;
|
|
741
|
-
let connected = false;
|
|
742
|
-
let frame = 0;
|
|
743
|
-
let spinnerTimer = null;
|
|
744
|
-
const renderStatus = (ctx) => {
|
|
745
|
-
const prefix = busy ? `${SPINNER_FRAMES[frame]} ` : connected ? "\u25CF " : "\u25CB ";
|
|
746
|
-
ctx.ui.setStatus("rig-worker-pi", `${prefix}${statusText}`);
|
|
747
|
-
};
|
|
748
|
-
const setStatus = (ctx, text, isBusy) => {
|
|
749
|
-
statusText = text;
|
|
750
|
-
busy = isBusy;
|
|
751
|
-
renderStatus(ctx);
|
|
752
|
-
};
|
|
753
|
-
pi.registerCommand("detach", {
|
|
754
|
-
description: "Detach from this run; the worker keeps going",
|
|
755
|
-
handler: async (_args, ctx) => {
|
|
756
|
-
ctx.ui.notify("Detached locally; the worker Pi daemon continues.", "info");
|
|
757
|
-
ctx.shutdown();
|
|
758
|
-
}
|
|
759
|
-
});
|
|
760
|
-
pi.registerCommand("stop", {
|
|
761
|
-
description: "Stop the worker Pi run and detach",
|
|
762
|
-
handler: async (_args, ctx) => {
|
|
763
|
-
await options.controller.abort();
|
|
764
|
-
ctx.ui.notify("Stop requested for the worker Pi daemon.", "info");
|
|
765
|
-
ctx.shutdown();
|
|
766
|
-
}
|
|
767
|
-
});
|
|
768
|
-
pi.registerCommand("worker", {
|
|
769
|
-
description: "Show the worker session's real capabilities (tools, extensions, hooks, skills)",
|
|
770
|
-
handler: async (_args, ctx) => {
|
|
771
|
-
if (capabilityLines)
|
|
772
|
-
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
773
|
-
else
|
|
774
|
-
ctx.ui.notify("Worker capabilities are not available yet (still connecting).", "info");
|
|
775
|
-
}
|
|
776
|
-
});
|
|
777
|
-
pi.on("user_bash", (event) => ({
|
|
778
|
-
operations: createRemoteBashOperations(options.controller, event.excludeFromContext === true)
|
|
779
|
-
}));
|
|
780
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
781
|
-
ctx.ui.setTitle("Rig \xB7 enriched bundled Pi");
|
|
782
|
-
setStatus(ctx, "waiting for worker Pi daemon", true);
|
|
783
|
-
spinnerTimer = setInterval(() => {
|
|
784
|
-
frame = (frame + 1) % SPINNER_FRAMES.length;
|
|
785
|
-
if (busy)
|
|
786
|
-
renderStatus(ctx);
|
|
787
|
-
}, 150);
|
|
788
|
-
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");
|
|
789
|
-
if (options.initialMessageSent)
|
|
790
|
-
ctx.ui.notify("Initial message sent to the worker Pi daemon.", "info");
|
|
791
|
-
const nativeUi = ctx.ui;
|
|
792
|
-
options.controller.setUiHooks({
|
|
793
|
-
onStatusText: (text) => setStatus(ctx, text, !connected),
|
|
794
|
-
onActivity: (label, detail) => {
|
|
795
|
-
const active = label !== "idle" && !/complete|ready/i.test(label);
|
|
796
|
-
setStatus(ctx, [label, detail].filter(Boolean).join(" \u2014 "), active);
|
|
797
|
-
if (/agent running|tool:/.test(label))
|
|
798
|
-
ctx.ui.setWidget("rig-worker-capabilities", undefined);
|
|
799
|
-
},
|
|
800
|
-
onConnectionChange: (nextConnected) => {
|
|
801
|
-
connected = nextConnected;
|
|
802
|
-
setStatus(ctx, nextConnected ? "worker session live" : "worker session disconnected", false);
|
|
803
|
-
},
|
|
804
|
-
onError: (message) => ctx.ui.notify(message, "error"),
|
|
805
|
-
onExtensionUiRequest: (request) => {
|
|
806
|
-
answerExtensionUiRequest(options, ctx, request);
|
|
807
|
-
},
|
|
808
|
-
onCatchUp: (messages, commands, capabilities) => {
|
|
809
|
-
if (nativeUi.appendSessionMessages)
|
|
810
|
-
nativeUi.appendSessionMessages(messages);
|
|
811
|
-
const cwd = options.controller.status.cwd;
|
|
812
|
-
if (nativeUi.setDisplayCwd && cwd)
|
|
813
|
-
nativeUi.setDisplayCwd(cwd);
|
|
814
|
-
registerDaemonCommands(pi, options, ctx, commands, registeredDaemonCommands);
|
|
815
|
-
if (capabilities) {
|
|
816
|
-
capabilityLines = renderWorkerCapabilities(capabilities);
|
|
817
|
-
ctx.ui.setWidget("rig-worker-capabilities", capabilityLines, { placement: "aboveEditor" });
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
});
|
|
821
|
-
options.controller.connect().catch((error) => {
|
|
822
|
-
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
823
|
-
});
|
|
824
|
-
});
|
|
825
|
-
pi.on("session_shutdown", () => {
|
|
826
|
-
if (spinnerTimer)
|
|
827
|
-
clearInterval(spinnerTimer);
|
|
828
|
-
options.controller.close();
|
|
829
|
-
});
|
|
830
|
-
};
|
|
831
|
-
}
|
|
832
|
-
export {
|
|
833
|
-
createRigWorkerPiBridgeExtension
|
|
834
|
-
};
|