@h-rig/pi-rig 0.0.6-alpha.19 → 0.0.6-alpha.20
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/src/client.js +28 -2
- package/dist/src/commands.js +33 -3
- package/dist/src/index.js +130 -7
- package/package.json +1 -1
package/dist/src/client.js
CHANGED
|
@@ -79,8 +79,9 @@ function createRigContextFromEnv(env = process.env) {
|
|
|
79
79
|
const discovered = discoverRigContext(env);
|
|
80
80
|
const serverUrl = env.RIG_SERVER_URL ?? env.RIG_SERVER_BASE_URL ?? discovered.serverUrl;
|
|
81
81
|
const projectRoot = env.RIG_PROJECT_ROOT ?? env.PROJECT_RIG_ROOT ?? discovered.projectRoot;
|
|
82
|
-
const authToken = env.RIG_AUTH_TOKEN ?? env.
|
|
82
|
+
const authToken = env.RIG_AUTH_TOKEN ?? env.RIG_SERVER_AUTH_TOKEN ?? discovered.authToken;
|
|
83
83
|
const steeringPollMs = cleanNonNegativeInteger(env.RIG_STEERING_POLL_MS);
|
|
84
|
+
const operatorSession = env.RIG_PI_OPERATOR_SESSION === "1" || env.RIG_PI_OPERATOR_SESSION === "true";
|
|
84
85
|
const active = Boolean(runId || taskId || serverUrl || projectRoot);
|
|
85
86
|
if (!active)
|
|
86
87
|
return { active: false };
|
|
@@ -91,7 +92,8 @@ function createRigContextFromEnv(env = process.env) {
|
|
|
91
92
|
...serverUrl ? { serverUrl } : {},
|
|
92
93
|
...projectRoot ? { projectRoot } : {},
|
|
93
94
|
...authToken ? { authToken } : {},
|
|
94
|
-
...steeringPollMs !== null ? { steeringPollMs } : {}
|
|
95
|
+
...steeringPollMs !== null ? { steeringPollMs } : {},
|
|
96
|
+
...operatorSession ? { operatorSession } : {}
|
|
95
97
|
};
|
|
96
98
|
}
|
|
97
99
|
function joinUrl(baseUrl, pathname) {
|
|
@@ -157,6 +159,20 @@ class RigBridgeClient {
|
|
|
157
159
|
const payload = await this.request(`/api/runs/${encodeURIComponent(runId)}`);
|
|
158
160
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { runId };
|
|
159
161
|
}
|
|
162
|
+
async runLogs(runId = this.context.runId, limit = 20) {
|
|
163
|
+
if (!runId)
|
|
164
|
+
throw new Error("runId is required");
|
|
165
|
+
const payload = await this.request(`/api/runs/${encodeURIComponent(runId)}/logs?limit=${encodeURIComponent(String(limit))}`);
|
|
166
|
+
const entries = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.entries : null;
|
|
167
|
+
return Array.isArray(entries) ? entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
168
|
+
}
|
|
169
|
+
async runTimeline(runId = this.context.runId, limit = 20) {
|
|
170
|
+
if (!runId)
|
|
171
|
+
throw new Error("runId is required");
|
|
172
|
+
const payload = await this.request(`/api/runs/${encodeURIComponent(runId)}/timeline?limit=${encodeURIComponent(String(limit))}`);
|
|
173
|
+
const entries = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.entries : null;
|
|
174
|
+
return Array.isArray(entries) ? entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
175
|
+
}
|
|
160
176
|
async steer(message, runId = this.context.runId) {
|
|
161
177
|
if (!runId)
|
|
162
178
|
throw new Error("runId is required");
|
|
@@ -167,6 +183,16 @@ class RigBridgeClient {
|
|
|
167
183
|
});
|
|
168
184
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
169
185
|
}
|
|
186
|
+
async stop(runId = this.context.runId) {
|
|
187
|
+
if (!runId)
|
|
188
|
+
throw new Error("runId is required");
|
|
189
|
+
const payload = await this.request("/api/runs/stop", {
|
|
190
|
+
method: "POST",
|
|
191
|
+
headers: { "content-type": "application/json" },
|
|
192
|
+
body: JSON.stringify({ runId })
|
|
193
|
+
});
|
|
194
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true, runId };
|
|
195
|
+
}
|
|
170
196
|
async pollSteering(runId = this.context.runId) {
|
|
171
197
|
if (!runId)
|
|
172
198
|
return [];
|
package/dist/src/commands.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/pi-rig/src/commands.ts
|
|
3
|
+
function runRecordFromPayload(payload) {
|
|
4
|
+
return payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
|
|
5
|
+
}
|
|
6
|
+
function formatEntry(entry) {
|
|
7
|
+
const type = String(entry.type ?? entry.title ?? "event");
|
|
8
|
+
const text = typeof entry.text === "string" ? entry.text : typeof entry.detail === "string" ? entry.detail : typeof entry.message === "string" ? entry.message : JSON.stringify(entry);
|
|
9
|
+
return `${type}: ${text}`;
|
|
10
|
+
}
|
|
3
11
|
function createRigSlashCommands(input) {
|
|
4
12
|
const notify = input.notify ?? (() => {});
|
|
5
13
|
async function handleRig(args) {
|
|
@@ -28,18 +36,40 @@ function createRigSlashCommands(input) {
|
|
|
28
36
|
}
|
|
29
37
|
if (first === "attach") {
|
|
30
38
|
const run = await input.client.attach(second);
|
|
31
|
-
const runRecord = run
|
|
39
|
+
const runRecord = runRecordFromPayload(run);
|
|
32
40
|
notify(`Attached to ${String(runRecord.runId ?? second ?? input.context.runId ?? "run")}: ${String(runRecord.status ?? "unknown")}`, "info");
|
|
33
41
|
return;
|
|
34
42
|
}
|
|
35
|
-
|
|
43
|
+
if (first === "timeline" || first === "logs") {
|
|
44
|
+
const runId = second || input.context.runId;
|
|
45
|
+
const entries = first === "timeline" ? await input.client.runTimeline(runId, 20) : await input.client.runLogs(runId, 20);
|
|
46
|
+
notify(entries.length > 0 ? entries.slice(-10).map(formatEntry).join(`
|
|
47
|
+
`) : `No ${first} entries for ${runId ?? "run"}.`, "info");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (first === "steer") {
|
|
51
|
+
const message = args.trim().slice("steer".length).trim();
|
|
52
|
+
if (!message) {
|
|
53
|
+
notify("Usage: /rig steer <message>", "error");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
await input.client.steer(message);
|
|
57
|
+
notify("Rig steering message queued.", "info");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (first === "stop") {
|
|
61
|
+
await input.client.stop(second || input.context.runId);
|
|
62
|
+
notify("Rig stop requested.", "info");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
notify("Usage: /rig status | /rig task list | /rig task run [id] | /rig attach [run-id] | /rig timeline [run-id] | /rig logs [run-id] | /rig steer <message> | /rig stop [run-id]", "error");
|
|
36
66
|
} catch (error) {
|
|
37
67
|
notify(error instanceof Error ? error.message : String(error), "error");
|
|
38
68
|
}
|
|
39
69
|
}
|
|
40
70
|
return {
|
|
41
71
|
rig: {
|
|
42
|
-
description: "Rig control-plane commands: status, task list, task run, attach",
|
|
72
|
+
description: "Rig control-plane commands: status, task list, task run, attach, timeline, logs, steer, stop",
|
|
43
73
|
handler: handleRig
|
|
44
74
|
}
|
|
45
75
|
};
|
package/dist/src/index.js
CHANGED
|
@@ -79,8 +79,9 @@ function createRigContextFromEnv(env = process.env) {
|
|
|
79
79
|
const discovered = discoverRigContext(env);
|
|
80
80
|
const serverUrl = env.RIG_SERVER_URL ?? env.RIG_SERVER_BASE_URL ?? discovered.serverUrl;
|
|
81
81
|
const projectRoot = env.RIG_PROJECT_ROOT ?? env.PROJECT_RIG_ROOT ?? discovered.projectRoot;
|
|
82
|
-
const authToken = env.RIG_AUTH_TOKEN ?? env.
|
|
82
|
+
const authToken = env.RIG_AUTH_TOKEN ?? env.RIG_SERVER_AUTH_TOKEN ?? discovered.authToken;
|
|
83
83
|
const steeringPollMs = cleanNonNegativeInteger(env.RIG_STEERING_POLL_MS);
|
|
84
|
+
const operatorSession = env.RIG_PI_OPERATOR_SESSION === "1" || env.RIG_PI_OPERATOR_SESSION === "true";
|
|
84
85
|
const active = Boolean(runId || taskId || serverUrl || projectRoot);
|
|
85
86
|
if (!active)
|
|
86
87
|
return { active: false };
|
|
@@ -91,7 +92,8 @@ function createRigContextFromEnv(env = process.env) {
|
|
|
91
92
|
...serverUrl ? { serverUrl } : {},
|
|
92
93
|
...projectRoot ? { projectRoot } : {},
|
|
93
94
|
...authToken ? { authToken } : {},
|
|
94
|
-
...steeringPollMs !== null ? { steeringPollMs } : {}
|
|
95
|
+
...steeringPollMs !== null ? { steeringPollMs } : {},
|
|
96
|
+
...operatorSession ? { operatorSession } : {}
|
|
95
97
|
};
|
|
96
98
|
}
|
|
97
99
|
function joinUrl(baseUrl, pathname) {
|
|
@@ -157,6 +159,20 @@ class RigBridgeClient {
|
|
|
157
159
|
const payload = await this.request(`/api/runs/${encodeURIComponent(runId)}`);
|
|
158
160
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { runId };
|
|
159
161
|
}
|
|
162
|
+
async runLogs(runId = this.context.runId, limit = 20) {
|
|
163
|
+
if (!runId)
|
|
164
|
+
throw new Error("runId is required");
|
|
165
|
+
const payload = await this.request(`/api/runs/${encodeURIComponent(runId)}/logs?limit=${encodeURIComponent(String(limit))}`);
|
|
166
|
+
const entries = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.entries : null;
|
|
167
|
+
return Array.isArray(entries) ? entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
168
|
+
}
|
|
169
|
+
async runTimeline(runId = this.context.runId, limit = 20) {
|
|
170
|
+
if (!runId)
|
|
171
|
+
throw new Error("runId is required");
|
|
172
|
+
const payload = await this.request(`/api/runs/${encodeURIComponent(runId)}/timeline?limit=${encodeURIComponent(String(limit))}`);
|
|
173
|
+
const entries = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.entries : null;
|
|
174
|
+
return Array.isArray(entries) ? entries.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry))) : [];
|
|
175
|
+
}
|
|
160
176
|
async steer(message, runId = this.context.runId) {
|
|
161
177
|
if (!runId)
|
|
162
178
|
throw new Error("runId is required");
|
|
@@ -167,6 +183,16 @@ class RigBridgeClient {
|
|
|
167
183
|
});
|
|
168
184
|
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true };
|
|
169
185
|
}
|
|
186
|
+
async stop(runId = this.context.runId) {
|
|
187
|
+
if (!runId)
|
|
188
|
+
throw new Error("runId is required");
|
|
189
|
+
const payload = await this.request("/api/runs/stop", {
|
|
190
|
+
method: "POST",
|
|
191
|
+
headers: { "content-type": "application/json" },
|
|
192
|
+
body: JSON.stringify({ runId })
|
|
193
|
+
});
|
|
194
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true, runId };
|
|
195
|
+
}
|
|
170
196
|
async pollSteering(runId = this.context.runId) {
|
|
171
197
|
if (!runId)
|
|
172
198
|
return [];
|
|
@@ -201,6 +227,14 @@ class RigBridgeClient {
|
|
|
201
227
|
}
|
|
202
228
|
|
|
203
229
|
// packages/pi-rig/src/commands.ts
|
|
230
|
+
function runRecordFromPayload(payload) {
|
|
231
|
+
return payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
|
|
232
|
+
}
|
|
233
|
+
function formatEntry(entry) {
|
|
234
|
+
const type = String(entry.type ?? entry.title ?? "event");
|
|
235
|
+
const text = typeof entry.text === "string" ? entry.text : typeof entry.detail === "string" ? entry.detail : typeof entry.message === "string" ? entry.message : JSON.stringify(entry);
|
|
236
|
+
return `${type}: ${text}`;
|
|
237
|
+
}
|
|
204
238
|
function createRigSlashCommands(input) {
|
|
205
239
|
const notify = input.notify ?? (() => {});
|
|
206
240
|
async function handleRig(args) {
|
|
@@ -229,18 +263,40 @@ function createRigSlashCommands(input) {
|
|
|
229
263
|
}
|
|
230
264
|
if (first === "attach") {
|
|
231
265
|
const run = await input.client.attach(second);
|
|
232
|
-
const runRecord = run
|
|
266
|
+
const runRecord = runRecordFromPayload(run);
|
|
233
267
|
notify(`Attached to ${String(runRecord.runId ?? second ?? input.context.runId ?? "run")}: ${String(runRecord.status ?? "unknown")}`, "info");
|
|
234
268
|
return;
|
|
235
269
|
}
|
|
236
|
-
|
|
270
|
+
if (first === "timeline" || first === "logs") {
|
|
271
|
+
const runId = second || input.context.runId;
|
|
272
|
+
const entries = first === "timeline" ? await input.client.runTimeline(runId, 20) : await input.client.runLogs(runId, 20);
|
|
273
|
+
notify(entries.length > 0 ? entries.slice(-10).map(formatEntry).join(`
|
|
274
|
+
`) : `No ${first} entries for ${runId ?? "run"}.`, "info");
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (first === "steer") {
|
|
278
|
+
const message = args.trim().slice("steer".length).trim();
|
|
279
|
+
if (!message) {
|
|
280
|
+
notify("Usage: /rig steer <message>", "error");
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
await input.client.steer(message);
|
|
284
|
+
notify("Rig steering message queued.", "info");
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (first === "stop") {
|
|
288
|
+
await input.client.stop(second || input.context.runId);
|
|
289
|
+
notify("Rig stop requested.", "info");
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
notify("Usage: /rig status | /rig task list | /rig task run [id] | /rig attach [run-id] | /rig timeline [run-id] | /rig logs [run-id] | /rig steer <message> | /rig stop [run-id]", "error");
|
|
237
293
|
} catch (error) {
|
|
238
294
|
notify(error instanceof Error ? error.message : String(error), "error");
|
|
239
295
|
}
|
|
240
296
|
}
|
|
241
297
|
return {
|
|
242
298
|
rig: {
|
|
243
|
-
description: "Rig control-plane commands: status, task list, task run, attach",
|
|
299
|
+
description: "Rig control-plane commands: status, task list, task run, attach, timeline, logs, steer, stop",
|
|
244
300
|
handler: handleRig
|
|
245
301
|
}
|
|
246
302
|
};
|
|
@@ -318,6 +374,13 @@ function notify(ctx, message, level = "info") {
|
|
|
318
374
|
notifyFn.call(ui, message, level);
|
|
319
375
|
}
|
|
320
376
|
}
|
|
377
|
+
function setWidget(ctx, id, lines) {
|
|
378
|
+
const ui = ctx && typeof ctx === "object" ? ctx.ui : null;
|
|
379
|
+
const setWidgetFn = ui && typeof ui === "object" ? ui.setWidget : null;
|
|
380
|
+
if (typeof setWidgetFn === "function") {
|
|
381
|
+
setWidgetFn.call(ui, id, lines);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
321
384
|
function steeringText(message) {
|
|
322
385
|
const text = typeof message.message === "string" ? message.message.trim() : "";
|
|
323
386
|
if (!text)
|
|
@@ -327,7 +390,7 @@ function steeringText(message) {
|
|
|
327
390
|
${text}`;
|
|
328
391
|
}
|
|
329
392
|
async function consumeQueuedSteering(pi, state, ctx) {
|
|
330
|
-
if (!state.active || !state.runId || typeof pi.sendUserMessage !== "function")
|
|
393
|
+
if (state.operatorSession || !state.active || !state.runId || typeof pi.sendUserMessage !== "function")
|
|
331
394
|
return;
|
|
332
395
|
try {
|
|
333
396
|
const messages = await state.client.consumeSteering(state.runId);
|
|
@@ -344,8 +407,66 @@ async function consumeQueuedSteering(pi, state, ctx) {
|
|
|
344
407
|
notify(ctx, `Rig steering sync failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
345
408
|
}
|
|
346
409
|
}
|
|
410
|
+
function inputText(event) {
|
|
411
|
+
if (!event || typeof event !== "object" || Array.isArray(event))
|
|
412
|
+
return null;
|
|
413
|
+
const text = event.text;
|
|
414
|
+
return typeof text === "string" && text.trim() ? text.trim() : null;
|
|
415
|
+
}
|
|
416
|
+
async function handleOperatorInput(event, state, ctx) {
|
|
417
|
+
if (!state.operatorSession || !state.active || !state.runId)
|
|
418
|
+
return;
|
|
419
|
+
const text = inputText(event);
|
|
420
|
+
if (!text || text.startsWith("/"))
|
|
421
|
+
return;
|
|
422
|
+
try {
|
|
423
|
+
await state.client.steer(text, state.runId);
|
|
424
|
+
notify(ctx, "Rig steering message queued.");
|
|
425
|
+
} catch (error) {
|
|
426
|
+
notify(ctx, `Rig steering failed: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
427
|
+
}
|
|
428
|
+
return { action: "handled" };
|
|
429
|
+
}
|
|
430
|
+
function shortEntry(entry) {
|
|
431
|
+
const type = String(entry.type ?? entry.title ?? "event");
|
|
432
|
+
const text = typeof entry.text === "string" ? entry.text : typeof entry.detail === "string" ? entry.detail : typeof entry.message === "string" ? entry.message : "";
|
|
433
|
+
return `${type}: ${text}`.slice(0, 160);
|
|
434
|
+
}
|
|
435
|
+
function runPayload(payload) {
|
|
436
|
+
return payload.run && typeof payload.run === "object" && !Array.isArray(payload.run) ? payload.run : payload;
|
|
437
|
+
}
|
|
438
|
+
function startOperatorRunWidget(state, ctx) {
|
|
439
|
+
if (!state.operatorSession || !state.active || !state.runId)
|
|
440
|
+
return;
|
|
441
|
+
let inFlight = false;
|
|
442
|
+
const refresh = async () => {
|
|
443
|
+
if (inFlight)
|
|
444
|
+
return;
|
|
445
|
+
inFlight = true;
|
|
446
|
+
try {
|
|
447
|
+
const [runPayloadRecord, timeline] = await Promise.all([
|
|
448
|
+
state.client.attach(state.runId),
|
|
449
|
+
state.client.runTimeline(state.runId, 8).catch(() => [])
|
|
450
|
+
]);
|
|
451
|
+
const run = runPayload(runPayloadRecord);
|
|
452
|
+
const header = `Rig ${String(run.runId ?? state.runId)} \xB7 ${String(run.status ?? "unknown")}`;
|
|
453
|
+
const detail = typeof run.statusDetail === "string" && run.statusDetail.trim() ? run.statusDetail.trim() : String(run.title ?? run.taskId ?? "");
|
|
454
|
+
const lines = [header, ...detail ? [detail.slice(0, 160)] : [], ...timeline.slice(-5).map(shortEntry)];
|
|
455
|
+
setWidget(ctx, "rig-run", lines);
|
|
456
|
+
} catch (error) {
|
|
457
|
+
setWidget(ctx, "rig-run", [`Rig ${state.runId} \xB7 unavailable`, error instanceof Error ? error.message : String(error)]);
|
|
458
|
+
} finally {
|
|
459
|
+
inFlight = false;
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
refresh();
|
|
463
|
+
const timer = setInterval(() => void refresh(), 2000);
|
|
464
|
+
if (typeof timer.unref === "function") {
|
|
465
|
+
timer.unref();
|
|
466
|
+
}
|
|
467
|
+
}
|
|
347
468
|
function startLiveSteeringPoll(pi, state, ctx) {
|
|
348
|
-
if (!state.active || !state.runId || typeof pi.sendUserMessage !== "function")
|
|
469
|
+
if (state.operatorSession || !state.active || !state.runId || typeof pi.sendUserMessage !== "function")
|
|
349
470
|
return;
|
|
350
471
|
const intervalMs = state.steeringPollMs ?? 1000;
|
|
351
472
|
if (intervalMs <= 0)
|
|
@@ -389,6 +510,7 @@ function createPiRigExtension(pi, options = {}) {
|
|
|
389
510
|
}
|
|
390
511
|
startLiveSteeringPoll(pi, state, globalThis);
|
|
391
512
|
}
|
|
513
|
+
pi.on?.("input", async (event, ctx) => handleOperatorInput(event, state, ctx));
|
|
392
514
|
pi.on?.("session_start", async (_event, ctx) => {
|
|
393
515
|
if (!state.active || !state.runId)
|
|
394
516
|
return;
|
|
@@ -397,6 +519,7 @@ function createPiRigExtension(pi, options = {}) {
|
|
|
397
519
|
if (typeof setStatus === "function") {
|
|
398
520
|
setStatus.call(ui, "rig", `Rig ${state.runId}`);
|
|
399
521
|
}
|
|
522
|
+
startOperatorRunWidget(state, ctx);
|
|
400
523
|
await consumeQueuedSteering(pi, state, ctx);
|
|
401
524
|
});
|
|
402
525
|
pi.on?.("turn_end", async (_event, ctx) => {
|