@h-rig/pi-rig 0.0.6-alpha.2 → 0.0.6-alpha.21

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.
@@ -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.RIG_GITHUB_TOKEN ?? env.GITHUB_TOKEN ?? env.GH_TOKEN ?? discovered.authToken;
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 [];
@@ -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.run && typeof run.run === "object" ? run.run : 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
- notify("Usage: /rig status | /rig task list | /rig task run [id] | /rig attach [run-id]", "error");
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.RIG_GITHUB_TOKEN ?? env.GITHUB_TOKEN ?? env.GH_TOKEN ?? discovered.authToken;
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.run && typeof run.run === "object" ? run.run : 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
- notify("Usage: /rig status | /rig task list | /rig task run [id] | /rig attach [run-id]", "error");
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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h-rig/pi-rig",
3
- "version": "0.0.6-alpha.2",
3
+ "version": "0.0.6-alpha.21",
4
4
  "type": "module",
5
5
  "description": "Rig package",
6
6
  "license": "UNLICENSED",