@h-rig/cli 0.0.6-alpha.23 → 0.0.6-alpha.25
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 +144 -28
- package/dist/src/commands/_operator-view.js +145 -29
- package/dist/src/commands/_pi-frontend.js +145 -29
- package/dist/src/commands/_pi-worker-bridge-extension.js +145 -29
- package/dist/src/commands/run.js +145 -29
- package/dist/src/commands/task.js +145 -29
- package/dist/src/commands.js +144 -28
- package/dist/src/index.js +144 -28
- package/package.json +6 -6
|
@@ -312,6 +312,15 @@ function appendTranscript(state, label, text) {
|
|
|
312
312
|
state.transcript.splice(0, state.transcript.length - MAX_TRANSCRIPT_LINES);
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
|
+
function nativePiUi(ctx) {
|
|
316
|
+
const ui = ctx.ui;
|
|
317
|
+
return typeof ui.emitSessionEvent === "function" && typeof ui.appendSessionMessages === "function" ? ui : null;
|
|
318
|
+
}
|
|
319
|
+
function syncNativeDisplayCwd(ctx, state) {
|
|
320
|
+
const ui = nativePiUi(ctx);
|
|
321
|
+
if (ui?.setDisplayCwd && state.cwd)
|
|
322
|
+
ui.setDisplayCwd(state.cwd);
|
|
323
|
+
}
|
|
315
324
|
function parseExtensionUiRequest(value) {
|
|
316
325
|
const request = recordOf(value) ?? {};
|
|
317
326
|
const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
|
|
@@ -355,8 +364,13 @@ function renderBridgeWidget(state) {
|
|
|
355
364
|
function updatePiUi(ctx, state) {
|
|
356
365
|
ctx.ui.setTitle("Pi \xB7 Rig worker daemon");
|
|
357
366
|
ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker Pi WS live" : state.status);
|
|
367
|
+
syncNativeDisplayCwd(ctx, state);
|
|
368
|
+
if (state.nativeStream && nativePiUi(ctx)) {
|
|
369
|
+
ctx.ui.setWidget("rig-worker-pi-transcript", undefined);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
358
372
|
ctx.ui.setWorkingVisible(false);
|
|
359
|
-
ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
|
|
373
|
+
ctx.ui.setWidget("rig-worker-pi-transcript", [`Worker Pi daemon bridge \xB7 degraded widget transcript`, ...renderBridgeWidget(state)], { placement: "aboveEditor" });
|
|
360
374
|
}
|
|
361
375
|
function applyStatus(state, payload) {
|
|
362
376
|
const status = recordOf(payload.status) ?? payload;
|
|
@@ -374,7 +388,7 @@ function applyMessage(state, message) {
|
|
|
374
388
|
const label = role === "assistant" ? "Pi" : role === "user" ? "You" : role === "tool" || role === "toolResult" ? "Tool" : "System";
|
|
375
389
|
appendTranscript(state, label, textFromContent(record.content ?? record.message ?? record.text ?? ""));
|
|
376
390
|
}
|
|
377
|
-
function applyPiEvent(state, eventValue) {
|
|
391
|
+
function applyPiEvent(ctx, state, eventValue) {
|
|
378
392
|
const event = recordOf(eventValue);
|
|
379
393
|
if (!event)
|
|
380
394
|
return;
|
|
@@ -382,11 +396,20 @@ function applyPiEvent(state, eventValue) {
|
|
|
382
396
|
if (type === "agent_start") {
|
|
383
397
|
state.streaming = true;
|
|
384
398
|
state.status = "streaming";
|
|
399
|
+
} else if (type === "agent_end") {
|
|
400
|
+
state.streaming = false;
|
|
401
|
+
state.status = "idle";
|
|
402
|
+
} else if (type === "queue_update") {
|
|
403
|
+
const steering = Array.isArray(event.steering) ? event.steering.length : 0;
|
|
404
|
+
const followUp = Array.isArray(event.followUp) ? event.followUp.length : 0;
|
|
405
|
+
state.status = `queued \xB7 steer ${steering} \xB7 follow-up ${followUp}`;
|
|
406
|
+
}
|
|
407
|
+
const native = nativePiUi(ctx);
|
|
408
|
+
if (state.nativeStream && native?.emitSessionEvent) {
|
|
409
|
+
native.emitSessionEvent(eventValue);
|
|
385
410
|
return;
|
|
386
411
|
}
|
|
387
412
|
if (type === "agent_end") {
|
|
388
|
-
state.streaming = false;
|
|
389
|
-
state.status = "idle";
|
|
390
413
|
appendTranscript(state, "System", "Agent turn complete.");
|
|
391
414
|
return;
|
|
392
415
|
}
|
|
@@ -411,29 +434,60 @@ function applyPiEvent(state, eventValue) {
|
|
|
411
434
|
}
|
|
412
435
|
if (type === "tool_execution_end") {
|
|
413
436
|
appendTranscript(state, event.isError === true ? "Error" : "Tool", asText(event.result ?? `${String(event.toolName ?? "tool")} complete`));
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
if (type === "queue_update") {
|
|
417
|
-
const steering = Array.isArray(event.steering) ? event.steering.length : 0;
|
|
418
|
-
const followUp = Array.isArray(event.followUp) ? event.followUp.length : 0;
|
|
419
|
-
state.status = `queued \xB7 steer ${steering} \xB7 follow-up ${followUp}`;
|
|
420
437
|
}
|
|
421
438
|
}
|
|
439
|
+
function firstPendingShell(state) {
|
|
440
|
+
return state.pendingShells[0];
|
|
441
|
+
}
|
|
442
|
+
function finishPendingShell(state, shell, result) {
|
|
443
|
+
const index = state.pendingShells.indexOf(shell);
|
|
444
|
+
if (index !== -1)
|
|
445
|
+
state.pendingShells.splice(index, 1);
|
|
446
|
+
shell.resolve(result);
|
|
447
|
+
}
|
|
448
|
+
function failPendingShell(state, shell, error) {
|
|
449
|
+
const index = state.pendingShells.indexOf(shell);
|
|
450
|
+
if (index !== -1)
|
|
451
|
+
state.pendingShells.splice(index, 1);
|
|
452
|
+
shell.reject(error);
|
|
453
|
+
}
|
|
422
454
|
function applyUiEvent(state, value) {
|
|
423
455
|
const event = recordOf(value);
|
|
424
456
|
if (!event)
|
|
425
457
|
return;
|
|
426
458
|
const type = String(event.type ?? "ui");
|
|
427
|
-
if (type === "shell.
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
|
|
459
|
+
if (type === "shell.chunk") {
|
|
460
|
+
const pending = firstPendingShell(state);
|
|
461
|
+
const chunk = asText(event.chunk);
|
|
462
|
+
if (pending) {
|
|
463
|
+
pending.sawChunk = true;
|
|
464
|
+
pending.onData(Buffer.from(chunk));
|
|
465
|
+
} else {
|
|
466
|
+
appendTranscript(state, "Tool", chunk);
|
|
467
|
+
}
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (type === "shell.end") {
|
|
471
|
+
const pending = firstPendingShell(state);
|
|
472
|
+
const output = asText(event.output ?? "");
|
|
473
|
+
const exitCode = typeof event.exitCode === "number" ? event.exitCode : event.isError === true ? 1 : 0;
|
|
474
|
+
if (pending) {
|
|
475
|
+
if (output && !pending.sawChunk)
|
|
476
|
+
pending.onData(Buffer.from(output));
|
|
477
|
+
finishPendingShell(state, pending, { exitCode });
|
|
478
|
+
} else {
|
|
479
|
+
appendTranscript(state, event.isError === true ? "Error" : "Tool", output || `exit ${String(exitCode)}`);
|
|
480
|
+
}
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (type === "shell.start") {
|
|
484
|
+
if (!firstPendingShell(state))
|
|
485
|
+
appendTranscript(state, "Tool", `$ ${asText(event.command)}`);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
appendTranscript(state, "System", `${type}: ${asText(event)}`);
|
|
489
|
+
}
|
|
490
|
+
function applyEnvelope(ctx, state, envelopeValue) {
|
|
437
491
|
const envelope = recordOf(envelopeValue);
|
|
438
492
|
if (!envelope)
|
|
439
493
|
return;
|
|
@@ -442,7 +496,8 @@ function applyEnvelope(state, envelopeValue) {
|
|
|
442
496
|
const metadata = recordOf(envelope.metadata);
|
|
443
497
|
state.cwd = typeof metadata?.cwd === "string" ? metadata.cwd : state.cwd;
|
|
444
498
|
state.status = "worker Pi daemon ready";
|
|
445
|
-
|
|
499
|
+
if (!state.nativeStream)
|
|
500
|
+
appendTranscript(state, "System", "Connected to worker Pi daemon.");
|
|
446
501
|
} else if (type === "status.update") {
|
|
447
502
|
applyStatus(state, envelope);
|
|
448
503
|
} else if (type === "activity.update") {
|
|
@@ -454,10 +509,11 @@ function applyEnvelope(state, envelopeValue) {
|
|
|
454
509
|
} else if (type === "pi.ui_event") {
|
|
455
510
|
applyUiEvent(state, envelope.event);
|
|
456
511
|
} else if (type === "pi.event") {
|
|
457
|
-
applyPiEvent(state, envelope.event);
|
|
512
|
+
applyPiEvent(ctx, state, envelope.event);
|
|
458
513
|
} else if (type === "error") {
|
|
459
514
|
appendTranscript(state, "Error", asText(envelope.message ?? envelope.detail ?? "unknown error"));
|
|
460
515
|
}
|
|
516
|
+
syncNativeDisplayCwd(ctx, state);
|
|
461
517
|
}
|
|
462
518
|
async function waitForWorkerReady(options, ctx, state) {
|
|
463
519
|
while (true) {
|
|
@@ -478,7 +534,7 @@ async function waitForWorkerReady(options, ctx, state) {
|
|
|
478
534
|
continue;
|
|
479
535
|
}
|
|
480
536
|
const sessionRecord = recordOf(session) ?? {};
|
|
481
|
-
applyEnvelope(state, { type: "ready", metadata: sessionRecord.metadata ?? sessionRecord });
|
|
537
|
+
applyEnvelope(ctx, state, { type: "ready", metadata: sessionRecord.metadata ?? sessionRecord });
|
|
482
538
|
updatePiUi(ctx, state);
|
|
483
539
|
return true;
|
|
484
540
|
}
|
|
@@ -508,7 +564,7 @@ async function connectWorkerStream(options, ctx, state) {
|
|
|
508
564
|
if (!catchupDone)
|
|
509
565
|
buffered.push(payload);
|
|
510
566
|
else {
|
|
511
|
-
applyEnvelope(state, payload);
|
|
567
|
+
applyEnvelope(ctx, state, payload);
|
|
512
568
|
updatePiUi(ctx, state);
|
|
513
569
|
}
|
|
514
570
|
} catch (error) {
|
|
@@ -531,8 +587,12 @@ async function connectWorkerStream(options, ctx, state) {
|
|
|
531
587
|
getRunPiCommandsViaServer(options.context, options.runId)
|
|
532
588
|
]);
|
|
533
589
|
const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
|
|
534
|
-
|
|
535
|
-
|
|
590
|
+
const native = nativePiUi(ctx);
|
|
591
|
+
if (state.nativeStream && native?.appendSessionMessages)
|
|
592
|
+
native.appendSessionMessages(messages);
|
|
593
|
+
else
|
|
594
|
+
for (const message of messages)
|
|
595
|
+
applyMessage(state, message);
|
|
536
596
|
applyStatus(state, statusPayload);
|
|
537
597
|
const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
|
|
538
598
|
state.commands = commands.flatMap((command) => {
|
|
@@ -541,7 +601,7 @@ async function connectWorkerStream(options, ctx, state) {
|
|
|
541
601
|
});
|
|
542
602
|
catchupDone = true;
|
|
543
603
|
for (const payload of buffered.splice(0))
|
|
544
|
-
applyEnvelope(state, payload);
|
|
604
|
+
applyEnvelope(ctx, state, payload);
|
|
545
605
|
updatePiUi(ctx, state);
|
|
546
606
|
} catch (error) {
|
|
547
607
|
appendTranscript(state, "Error", `Worker Pi catch-up failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -550,6 +610,51 @@ async function connectWorkerStream(options, ctx, state) {
|
|
|
550
610
|
}
|
|
551
611
|
await closePromise;
|
|
552
612
|
}
|
|
613
|
+
function createRemoteBashOperations(options, state, excludeFromContext) {
|
|
614
|
+
return {
|
|
615
|
+
exec(command, _cwd, execOptions) {
|
|
616
|
+
return new Promise((resolve3, reject) => {
|
|
617
|
+
const pending = {
|
|
618
|
+
command,
|
|
619
|
+
onData: execOptions.onData,
|
|
620
|
+
resolve: resolve3,
|
|
621
|
+
reject,
|
|
622
|
+
sawChunk: false
|
|
623
|
+
};
|
|
624
|
+
const cleanup = () => {
|
|
625
|
+
execOptions.signal?.removeEventListener("abort", onAbort);
|
|
626
|
+
if (timer)
|
|
627
|
+
clearTimeout(timer);
|
|
628
|
+
};
|
|
629
|
+
const onAbort = () => {
|
|
630
|
+
cleanup();
|
|
631
|
+
failPendingShell(state, pending, new Error("Remote worker shell command aborted locally."));
|
|
632
|
+
};
|
|
633
|
+
const timeoutMs = typeof execOptions.timeout === "number" && execOptions.timeout > 0 ? execOptions.timeout : 0;
|
|
634
|
+
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
635
|
+
cleanup();
|
|
636
|
+
failPendingShell(state, pending, new Error(`Remote worker shell command timed out after ${timeoutMs}ms.`));
|
|
637
|
+
}, timeoutMs) : null;
|
|
638
|
+
const wrappedResolve = pending.resolve;
|
|
639
|
+
const wrappedReject = pending.reject;
|
|
640
|
+
pending.resolve = (result) => {
|
|
641
|
+
cleanup();
|
|
642
|
+
wrappedResolve(result);
|
|
643
|
+
};
|
|
644
|
+
pending.reject = (error) => {
|
|
645
|
+
cleanup();
|
|
646
|
+
wrappedReject(error);
|
|
647
|
+
};
|
|
648
|
+
execOptions.signal?.addEventListener("abort", onAbort, { once: true });
|
|
649
|
+
state.pendingShells.push(pending);
|
|
650
|
+
sendRunPiShellViaServer(options.context, options.runId, `${excludeFromContext ? "!!" : "!"}${command}`).catch((error) => {
|
|
651
|
+
cleanup();
|
|
652
|
+
failPendingShell(state, pending, error instanceof Error ? error : new Error(String(error)));
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
}
|
|
553
658
|
async function answerPendingUi(options, state, line) {
|
|
554
659
|
const pending = state.pendingUi;
|
|
555
660
|
if (!pending)
|
|
@@ -615,13 +720,22 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
615
720
|
commands: [],
|
|
616
721
|
streaming: false,
|
|
617
722
|
pendingUi: null,
|
|
618
|
-
|
|
723
|
+
pendingShells: [],
|
|
724
|
+
wsConnected: false,
|
|
725
|
+
nativeStream: false
|
|
619
726
|
};
|
|
620
727
|
if (options.initialMessageSent)
|
|
621
728
|
appendTranscript(state, "System", "Initial message sent to worker Pi daemon.");
|
|
729
|
+
let nativePiUiContextAvailable = false;
|
|
730
|
+
pi.on("user_bash", (event) => {
|
|
731
|
+
state.nativeStream = Boolean(state.nativeStream || nativePiUiContextAvailable);
|
|
732
|
+
return { operations: createRemoteBashOperations(options, state, event.excludeFromContext === true) };
|
|
733
|
+
});
|
|
622
734
|
pi.on("session_start", async (_event, ctx) => {
|
|
735
|
+
nativePiUiContextAvailable = Boolean(nativePiUi(ctx));
|
|
736
|
+
state.nativeStream = nativePiUiContextAvailable;
|
|
623
737
|
updatePiUi(ctx, state);
|
|
624
|
-
ctx.ui.notify("Rig worker Pi bridge extension loaded", "info");
|
|
738
|
+
ctx.ui.notify(nativePiUiContextAvailable ? "Rig worker Pi native stream bridge loaded" : "Rig worker Pi bridge extension loaded (degraded widget fallback)", "info");
|
|
625
739
|
ctx.ui.onTerminalInput((data) => {
|
|
626
740
|
if (data.includes("\x04")) {
|
|
627
741
|
ctx.shutdown();
|
|
@@ -635,6 +749,8 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
635
749
|
const text = [editorText, inlineText].filter(Boolean).join(" ").trim();
|
|
636
750
|
if (!text)
|
|
637
751
|
return;
|
|
752
|
+
if (text.startsWith("!"))
|
|
753
|
+
return;
|
|
638
754
|
ctx.ui.setEditorText("");
|
|
639
755
|
routeInput(options, ctx, state, text).catch((error) => {
|
|
640
756
|
appendTranscript(state, "Error", error instanceof Error ? error.message : String(error));
|
|
@@ -302,6 +302,15 @@ function appendTranscript(state, label, text) {
|
|
|
302
302
|
state.transcript.splice(0, state.transcript.length - MAX_TRANSCRIPT_LINES);
|
|
303
303
|
}
|
|
304
304
|
}
|
|
305
|
+
function nativePiUi(ctx) {
|
|
306
|
+
const ui = ctx.ui;
|
|
307
|
+
return typeof ui.emitSessionEvent === "function" && typeof ui.appendSessionMessages === "function" ? ui : null;
|
|
308
|
+
}
|
|
309
|
+
function syncNativeDisplayCwd(ctx, state) {
|
|
310
|
+
const ui = nativePiUi(ctx);
|
|
311
|
+
if (ui?.setDisplayCwd && state.cwd)
|
|
312
|
+
ui.setDisplayCwd(state.cwd);
|
|
313
|
+
}
|
|
305
314
|
function parseExtensionUiRequest(value) {
|
|
306
315
|
const request = recordOf(value) ?? {};
|
|
307
316
|
const requestId = String(request.requestId ?? request.id ?? `ui-${Date.now()}`);
|
|
@@ -345,8 +354,13 @@ function renderBridgeWidget(state) {
|
|
|
345
354
|
function updatePiUi(ctx, state) {
|
|
346
355
|
ctx.ui.setTitle("Pi \xB7 Rig worker daemon");
|
|
347
356
|
ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker Pi WS live" : state.status);
|
|
357
|
+
syncNativeDisplayCwd(ctx, state);
|
|
358
|
+
if (state.nativeStream && nativePiUi(ctx)) {
|
|
359
|
+
ctx.ui.setWidget("rig-worker-pi-transcript", undefined);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
348
362
|
ctx.ui.setWorkingVisible(false);
|
|
349
|
-
ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
|
|
363
|
+
ctx.ui.setWidget("rig-worker-pi-transcript", [`Worker Pi daemon bridge \xB7 degraded widget transcript`, ...renderBridgeWidget(state)], { placement: "aboveEditor" });
|
|
350
364
|
}
|
|
351
365
|
function applyStatus(state, payload) {
|
|
352
366
|
const status = recordOf(payload.status) ?? payload;
|
|
@@ -364,7 +378,7 @@ function applyMessage(state, message) {
|
|
|
364
378
|
const label = role === "assistant" ? "Pi" : role === "user" ? "You" : role === "tool" || role === "toolResult" ? "Tool" : "System";
|
|
365
379
|
appendTranscript(state, label, textFromContent(record.content ?? record.message ?? record.text ?? ""));
|
|
366
380
|
}
|
|
367
|
-
function applyPiEvent(state, eventValue) {
|
|
381
|
+
function applyPiEvent(ctx, state, eventValue) {
|
|
368
382
|
const event = recordOf(eventValue);
|
|
369
383
|
if (!event)
|
|
370
384
|
return;
|
|
@@ -372,11 +386,20 @@ function applyPiEvent(state, eventValue) {
|
|
|
372
386
|
if (type === "agent_start") {
|
|
373
387
|
state.streaming = true;
|
|
374
388
|
state.status = "streaming";
|
|
389
|
+
} else if (type === "agent_end") {
|
|
390
|
+
state.streaming = false;
|
|
391
|
+
state.status = "idle";
|
|
392
|
+
} else if (type === "queue_update") {
|
|
393
|
+
const steering = Array.isArray(event.steering) ? event.steering.length : 0;
|
|
394
|
+
const followUp = Array.isArray(event.followUp) ? event.followUp.length : 0;
|
|
395
|
+
state.status = `queued \xB7 steer ${steering} \xB7 follow-up ${followUp}`;
|
|
396
|
+
}
|
|
397
|
+
const native = nativePiUi(ctx);
|
|
398
|
+
if (state.nativeStream && native?.emitSessionEvent) {
|
|
399
|
+
native.emitSessionEvent(eventValue);
|
|
375
400
|
return;
|
|
376
401
|
}
|
|
377
402
|
if (type === "agent_end") {
|
|
378
|
-
state.streaming = false;
|
|
379
|
-
state.status = "idle";
|
|
380
403
|
appendTranscript(state, "System", "Agent turn complete.");
|
|
381
404
|
return;
|
|
382
405
|
}
|
|
@@ -401,29 +424,60 @@ function applyPiEvent(state, eventValue) {
|
|
|
401
424
|
}
|
|
402
425
|
if (type === "tool_execution_end") {
|
|
403
426
|
appendTranscript(state, event.isError === true ? "Error" : "Tool", asText(event.result ?? `${String(event.toolName ?? "tool")} complete`));
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
if (type === "queue_update") {
|
|
407
|
-
const steering = Array.isArray(event.steering) ? event.steering.length : 0;
|
|
408
|
-
const followUp = Array.isArray(event.followUp) ? event.followUp.length : 0;
|
|
409
|
-
state.status = `queued \xB7 steer ${steering} \xB7 follow-up ${followUp}`;
|
|
410
427
|
}
|
|
411
428
|
}
|
|
429
|
+
function firstPendingShell(state) {
|
|
430
|
+
return state.pendingShells[0];
|
|
431
|
+
}
|
|
432
|
+
function finishPendingShell(state, shell, result) {
|
|
433
|
+
const index = state.pendingShells.indexOf(shell);
|
|
434
|
+
if (index !== -1)
|
|
435
|
+
state.pendingShells.splice(index, 1);
|
|
436
|
+
shell.resolve(result);
|
|
437
|
+
}
|
|
438
|
+
function failPendingShell(state, shell, error) {
|
|
439
|
+
const index = state.pendingShells.indexOf(shell);
|
|
440
|
+
if (index !== -1)
|
|
441
|
+
state.pendingShells.splice(index, 1);
|
|
442
|
+
shell.reject(error);
|
|
443
|
+
}
|
|
412
444
|
function applyUiEvent(state, value) {
|
|
413
445
|
const event = recordOf(value);
|
|
414
446
|
if (!event)
|
|
415
447
|
return;
|
|
416
448
|
const type = String(event.type ?? "ui");
|
|
417
|
-
if (type === "shell.
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
426
|
-
|
|
449
|
+
if (type === "shell.chunk") {
|
|
450
|
+
const pending = firstPendingShell(state);
|
|
451
|
+
const chunk = asText(event.chunk);
|
|
452
|
+
if (pending) {
|
|
453
|
+
pending.sawChunk = true;
|
|
454
|
+
pending.onData(Buffer.from(chunk));
|
|
455
|
+
} else {
|
|
456
|
+
appendTranscript(state, "Tool", chunk);
|
|
457
|
+
}
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (type === "shell.end") {
|
|
461
|
+
const pending = firstPendingShell(state);
|
|
462
|
+
const output = asText(event.output ?? "");
|
|
463
|
+
const exitCode = typeof event.exitCode === "number" ? event.exitCode : event.isError === true ? 1 : 0;
|
|
464
|
+
if (pending) {
|
|
465
|
+
if (output && !pending.sawChunk)
|
|
466
|
+
pending.onData(Buffer.from(output));
|
|
467
|
+
finishPendingShell(state, pending, { exitCode });
|
|
468
|
+
} else {
|
|
469
|
+
appendTranscript(state, event.isError === true ? "Error" : "Tool", output || `exit ${String(exitCode)}`);
|
|
470
|
+
}
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (type === "shell.start") {
|
|
474
|
+
if (!firstPendingShell(state))
|
|
475
|
+
appendTranscript(state, "Tool", `$ ${asText(event.command)}`);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
appendTranscript(state, "System", `${type}: ${asText(event)}`);
|
|
479
|
+
}
|
|
480
|
+
function applyEnvelope(ctx, state, envelopeValue) {
|
|
427
481
|
const envelope = recordOf(envelopeValue);
|
|
428
482
|
if (!envelope)
|
|
429
483
|
return;
|
|
@@ -432,7 +486,8 @@ function applyEnvelope(state, envelopeValue) {
|
|
|
432
486
|
const metadata = recordOf(envelope.metadata);
|
|
433
487
|
state.cwd = typeof metadata?.cwd === "string" ? metadata.cwd : state.cwd;
|
|
434
488
|
state.status = "worker Pi daemon ready";
|
|
435
|
-
|
|
489
|
+
if (!state.nativeStream)
|
|
490
|
+
appendTranscript(state, "System", "Connected to worker Pi daemon.");
|
|
436
491
|
} else if (type === "status.update") {
|
|
437
492
|
applyStatus(state, envelope);
|
|
438
493
|
} else if (type === "activity.update") {
|
|
@@ -444,10 +499,11 @@ function applyEnvelope(state, envelopeValue) {
|
|
|
444
499
|
} else if (type === "pi.ui_event") {
|
|
445
500
|
applyUiEvent(state, envelope.event);
|
|
446
501
|
} else if (type === "pi.event") {
|
|
447
|
-
applyPiEvent(state, envelope.event);
|
|
502
|
+
applyPiEvent(ctx, state, envelope.event);
|
|
448
503
|
} else if (type === "error") {
|
|
449
504
|
appendTranscript(state, "Error", asText(envelope.message ?? envelope.detail ?? "unknown error"));
|
|
450
505
|
}
|
|
506
|
+
syncNativeDisplayCwd(ctx, state);
|
|
451
507
|
}
|
|
452
508
|
async function waitForWorkerReady(options, ctx, state) {
|
|
453
509
|
while (true) {
|
|
@@ -468,7 +524,7 @@ async function waitForWorkerReady(options, ctx, state) {
|
|
|
468
524
|
continue;
|
|
469
525
|
}
|
|
470
526
|
const sessionRecord = recordOf(session) ?? {};
|
|
471
|
-
applyEnvelope(state, { type: "ready", metadata: sessionRecord.metadata ?? sessionRecord });
|
|
527
|
+
applyEnvelope(ctx, state, { type: "ready", metadata: sessionRecord.metadata ?? sessionRecord });
|
|
472
528
|
updatePiUi(ctx, state);
|
|
473
529
|
return true;
|
|
474
530
|
}
|
|
@@ -498,7 +554,7 @@ async function connectWorkerStream(options, ctx, state) {
|
|
|
498
554
|
if (!catchupDone)
|
|
499
555
|
buffered.push(payload);
|
|
500
556
|
else {
|
|
501
|
-
applyEnvelope(state, payload);
|
|
557
|
+
applyEnvelope(ctx, state, payload);
|
|
502
558
|
updatePiUi(ctx, state);
|
|
503
559
|
}
|
|
504
560
|
} catch (error) {
|
|
@@ -521,8 +577,12 @@ async function connectWorkerStream(options, ctx, state) {
|
|
|
521
577
|
getRunPiCommandsViaServer(options.context, options.runId)
|
|
522
578
|
]);
|
|
523
579
|
const messages = Array.isArray(messagesPayload.messages) ? messagesPayload.messages : [];
|
|
524
|
-
|
|
525
|
-
|
|
580
|
+
const native = nativePiUi(ctx);
|
|
581
|
+
if (state.nativeStream && native?.appendSessionMessages)
|
|
582
|
+
native.appendSessionMessages(messages);
|
|
583
|
+
else
|
|
584
|
+
for (const message of messages)
|
|
585
|
+
applyMessage(state, message);
|
|
526
586
|
applyStatus(state, statusPayload);
|
|
527
587
|
const commands = Array.isArray(commandsPayload.commands) ? commandsPayload.commands : [];
|
|
528
588
|
state.commands = commands.flatMap((command) => {
|
|
@@ -531,7 +591,7 @@ async function connectWorkerStream(options, ctx, state) {
|
|
|
531
591
|
});
|
|
532
592
|
catchupDone = true;
|
|
533
593
|
for (const payload of buffered.splice(0))
|
|
534
|
-
applyEnvelope(state, payload);
|
|
594
|
+
applyEnvelope(ctx, state, payload);
|
|
535
595
|
updatePiUi(ctx, state);
|
|
536
596
|
} catch (error) {
|
|
537
597
|
appendTranscript(state, "Error", `Worker Pi catch-up failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -540,6 +600,51 @@ async function connectWorkerStream(options, ctx, state) {
|
|
|
540
600
|
}
|
|
541
601
|
await closePromise;
|
|
542
602
|
}
|
|
603
|
+
function createRemoteBashOperations(options, state, excludeFromContext) {
|
|
604
|
+
return {
|
|
605
|
+
exec(command, _cwd, execOptions) {
|
|
606
|
+
return new Promise((resolve3, reject) => {
|
|
607
|
+
const pending = {
|
|
608
|
+
command,
|
|
609
|
+
onData: execOptions.onData,
|
|
610
|
+
resolve: resolve3,
|
|
611
|
+
reject,
|
|
612
|
+
sawChunk: false
|
|
613
|
+
};
|
|
614
|
+
const cleanup = () => {
|
|
615
|
+
execOptions.signal?.removeEventListener("abort", onAbort);
|
|
616
|
+
if (timer)
|
|
617
|
+
clearTimeout(timer);
|
|
618
|
+
};
|
|
619
|
+
const onAbort = () => {
|
|
620
|
+
cleanup();
|
|
621
|
+
failPendingShell(state, pending, new Error("Remote worker shell command aborted locally."));
|
|
622
|
+
};
|
|
623
|
+
const timeoutMs = typeof execOptions.timeout === "number" && execOptions.timeout > 0 ? execOptions.timeout : 0;
|
|
624
|
+
const timer = timeoutMs > 0 ? setTimeout(() => {
|
|
625
|
+
cleanup();
|
|
626
|
+
failPendingShell(state, pending, new Error(`Remote worker shell command timed out after ${timeoutMs}ms.`));
|
|
627
|
+
}, timeoutMs) : null;
|
|
628
|
+
const wrappedResolve = pending.resolve;
|
|
629
|
+
const wrappedReject = pending.reject;
|
|
630
|
+
pending.resolve = (result) => {
|
|
631
|
+
cleanup();
|
|
632
|
+
wrappedResolve(result);
|
|
633
|
+
};
|
|
634
|
+
pending.reject = (error) => {
|
|
635
|
+
cleanup();
|
|
636
|
+
wrappedReject(error);
|
|
637
|
+
};
|
|
638
|
+
execOptions.signal?.addEventListener("abort", onAbort, { once: true });
|
|
639
|
+
state.pendingShells.push(pending);
|
|
640
|
+
sendRunPiShellViaServer(options.context, options.runId, `${excludeFromContext ? "!!" : "!"}${command}`).catch((error) => {
|
|
641
|
+
cleanup();
|
|
642
|
+
failPendingShell(state, pending, error instanceof Error ? error : new Error(String(error)));
|
|
643
|
+
});
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
}
|
|
543
648
|
async function answerPendingUi(options, state, line) {
|
|
544
649
|
const pending = state.pendingUi;
|
|
545
650
|
if (!pending)
|
|
@@ -605,13 +710,22 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
605
710
|
commands: [],
|
|
606
711
|
streaming: false,
|
|
607
712
|
pendingUi: null,
|
|
608
|
-
|
|
713
|
+
pendingShells: [],
|
|
714
|
+
wsConnected: false,
|
|
715
|
+
nativeStream: false
|
|
609
716
|
};
|
|
610
717
|
if (options.initialMessageSent)
|
|
611
718
|
appendTranscript(state, "System", "Initial message sent to worker Pi daemon.");
|
|
719
|
+
let nativePiUiContextAvailable = false;
|
|
720
|
+
pi.on("user_bash", (event) => {
|
|
721
|
+
state.nativeStream = Boolean(state.nativeStream || nativePiUiContextAvailable);
|
|
722
|
+
return { operations: createRemoteBashOperations(options, state, event.excludeFromContext === true) };
|
|
723
|
+
});
|
|
612
724
|
pi.on("session_start", async (_event, ctx) => {
|
|
725
|
+
nativePiUiContextAvailable = Boolean(nativePiUi(ctx));
|
|
726
|
+
state.nativeStream = nativePiUiContextAvailable;
|
|
613
727
|
updatePiUi(ctx, state);
|
|
614
|
-
ctx.ui.notify("Rig worker Pi bridge extension loaded", "info");
|
|
728
|
+
ctx.ui.notify(nativePiUiContextAvailable ? "Rig worker Pi native stream bridge loaded" : "Rig worker Pi bridge extension loaded (degraded widget fallback)", "info");
|
|
615
729
|
ctx.ui.onTerminalInput((data) => {
|
|
616
730
|
if (data.includes("\x04")) {
|
|
617
731
|
ctx.shutdown();
|
|
@@ -625,6 +739,8 @@ function createRigWorkerPiBridgeExtension(options) {
|
|
|
625
739
|
const text = [editorText, inlineText].filter(Boolean).join(" ").trim();
|
|
626
740
|
if (!text)
|
|
627
741
|
return;
|
|
742
|
+
if (text.startsWith("!"))
|
|
743
|
+
return;
|
|
628
744
|
ctx.ui.setEditorText("");
|
|
629
745
|
routeInput(options, ctx, state, text).catch((error) => {
|
|
630
746
|
appendTranscript(state, "Error", error instanceof Error ? error.message : String(error));
|