@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.
@@ -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.start")
428
- appendTranscript(state, "Tool", `$ ${asText(event.command)}`);
429
- else if (type === "shell.chunk")
430
- appendTranscript(state, "Tool", asText(event.chunk));
431
- else if (type === "shell.end")
432
- appendTranscript(state, event.isError === true ? "Error" : "Tool", asText(event.output ?? `exit ${String(event.exitCode ?? "")}`));
433
- else
434
- appendTranscript(state, "System", `${type}: ${asText(event)}`);
435
- }
436
- function applyEnvelope(state, envelopeValue) {
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
- appendTranscript(state, "System", "Connected to worker Pi daemon.");
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
- for (const message of messages)
535
- applyMessage(state, message);
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
- wsConnected: false
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.start")
418
- appendTranscript(state, "Tool", `$ ${asText(event.command)}`);
419
- else if (type === "shell.chunk")
420
- appendTranscript(state, "Tool", asText(event.chunk));
421
- else if (type === "shell.end")
422
- appendTranscript(state, event.isError === true ? "Error" : "Tool", asText(event.output ?? `exit ${String(event.exitCode ?? "")}`));
423
- else
424
- appendTranscript(state, "System", `${type}: ${asText(event)}`);
425
- }
426
- function applyEnvelope(state, envelopeValue) {
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
- appendTranscript(state, "System", "Connected to worker Pi daemon.");
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
- for (const message of messages)
525
- applyMessage(state, message);
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
- wsConnected: false
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));