@h-rig/cli 0.0.6-alpha.32 → 0.0.6-alpha.34

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 CHANGED
@@ -7120,7 +7120,23 @@ function createPiRunStreamRenderer(output = process.stdout) {
7120
7120
  }
7121
7121
  if (entry.type === "timeline_warning") {
7122
7122
  writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
7123
+ continue;
7124
+ }
7125
+ if (entry.type === "action") {
7126
+ const text2 = String(entry.detail ?? entry.message ?? entry.title ?? "").trim();
7127
+ if (text2)
7128
+ writeLine(`[Rig action] ${text2}`);
7129
+ continue;
7123
7130
  }
7131
+ if (entry.type === "user_message") {
7132
+ const text2 = String(entry.text ?? entry.message ?? entry.detail ?? "").trim();
7133
+ if (text2)
7134
+ writeLine(`[Operator] ${text2}`);
7135
+ continue;
7136
+ }
7137
+ const fallback = String(entry.detail ?? entry.message ?? entry.text ?? entry.title ?? "").trim();
7138
+ if (fallback)
7139
+ writeLine(`[${String(entry.type ?? "timeline")}] ${fallback}`);
7124
7140
  }
7125
7141
  },
7126
7142
  renderLogs(entries) {
@@ -7273,12 +7289,12 @@ function parseExtensionUiRequest(value) {
7273
7289
  }
7274
7290
  function renderBridgeWidget(state) {
7275
7291
  const statusParts = [
7276
- state.wsConnected ? "live WS" : "WS pending",
7292
+ state.wsConnected ? "live" : "connecting",
7277
7293
  state.status,
7278
7294
  state.model,
7279
7295
  state.cwd
7280
7296
  ].filter(Boolean);
7281
- const lines = [`Worker Pi daemon bridge \xB7 ${statusParts.join(" \xB7 ")}`];
7297
+ const lines = [`Rig worker session \xB7 ${statusParts.join(" \xB7 ")}`];
7282
7298
  if (state.activity)
7283
7299
  lines.push(state.activity);
7284
7300
  if (state.commands.length > 0) {
@@ -7288,27 +7304,35 @@ function renderBridgeWidget(state) {
7288
7304
  if (state.transcript.length > 0) {
7289
7305
  lines.push(...state.transcript.slice(-MAX_TRANSCRIPT_LINES));
7290
7306
  } else {
7291
- lines.push("Waiting for worker Pi daemon transcript\u2026");
7307
+ lines.push("Waiting for the worker session transcript\u2026 (/detach exits and leaves the worker running)");
7292
7308
  }
7293
7309
  if (state.pendingUi) {
7294
7310
  lines.push("");
7295
- lines.push(`Extension UI request \xB7 ${state.pendingUi.method}`);
7311
+ lines.push(`Worker needs input \xB7 ${state.pendingUi.method}`);
7296
7312
  lines.push(state.pendingUi.prompt);
7297
7313
  state.pendingUi.options.forEach((option, index) => lines.push(`${index + 1}. ${option}`));
7298
- lines.push("Reply in the Pi editor. /cancel cancels this request.");
7314
+ lines.push("Reply in the editor below. /cancel dismisses this request.");
7299
7315
  }
7300
7316
  return lines;
7301
7317
  }
7318
+ function reportBridgeError(ctx, state, message2) {
7319
+ appendTranscript(state, "Error", message2);
7320
+ state.status = message2;
7321
+ try {
7322
+ ctx.ui.notify(message2, "error");
7323
+ } catch {}
7324
+ updatePiUi(ctx, state);
7325
+ }
7302
7326
  function updatePiUi(ctx, state) {
7303
- ctx.ui.setTitle("Pi \xB7 Rig worker daemon");
7304
- ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker Pi WS live" : state.status);
7327
+ ctx.ui.setTitle("Rig \xB7 worker session");
7328
+ ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker session live" : state.status);
7305
7329
  syncNativeDisplayCwd(ctx, state);
7306
7330
  if (state.nativeStream && nativePiUi(ctx)) {
7307
7331
  ctx.ui.setWidget("rig-worker-pi-transcript", undefined);
7308
7332
  return;
7309
7333
  }
7310
7334
  ctx.ui.setWorkingVisible(false);
7311
- ctx.ui.setWidget("rig-worker-pi-transcript", [`Worker Pi daemon bridge \xB7 degraded widget transcript`, ...renderBridgeWidget(state)], { placement: "aboveEditor" });
7335
+ ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
7312
7336
  }
7313
7337
  function applyStatus(state, payload) {
7314
7338
  const status = recordOf(payload.status) ?? payload;
@@ -7453,19 +7477,45 @@ function applyEnvelope(ctx, state, envelopeValue) {
7453
7477
  }
7454
7478
  syncNativeDisplayCwd(ctx, state);
7455
7479
  }
7480
+ function resolveAttachReadyTimeoutMs() {
7481
+ const raw = Number.parseInt(process.env.RIG_PI_ATTACH_TIMEOUT_MS ?? "", 10);
7482
+ return Number.isFinite(raw) && raw > 0 ? raw : 10 * 60000;
7483
+ }
7484
+ function formatElapsed(sinceMs) {
7485
+ const totalSeconds = Math.floor((Date.now() - sinceMs) / 1000);
7486
+ const minutes = Math.floor(totalSeconds / 60);
7487
+ const seconds = totalSeconds % 60;
7488
+ return minutes > 0 ? `${minutes}m${String(seconds).padStart(2, "0")}s` : `${seconds}s`;
7489
+ }
7456
7490
  async function waitForWorkerReady(options, ctx, state) {
7491
+ const startedAt = Date.now();
7492
+ const deadline = startedAt + resolveAttachReadyTimeoutMs();
7493
+ let consecutiveFailures = 0;
7457
7494
  while (true) {
7458
- const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => ({
7459
- ready: false,
7460
- status: error instanceof Error ? error.message : String(error),
7461
- retryAfterMs: 1000
7462
- }));
7495
+ let requestFailed = false;
7496
+ const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => {
7497
+ requestFailed = true;
7498
+ return {
7499
+ ready: false,
7500
+ status: error instanceof Error ? error.message : String(error),
7501
+ retryAfterMs: 1000
7502
+ };
7503
+ });
7463
7504
  if (session.ready === false) {
7505
+ consecutiveFailures = requestFailed ? consecutiveFailures + 1 : 0;
7464
7506
  const status = String(session.status ?? "starting");
7465
- state.status = `waiting for worker Pi daemon \xB7 ${status}`;
7466
- updatePiUi(ctx, state);
7467
7507
  if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
7468
- appendTranscript(state, "Error", `Run ended before worker Pi daemon became ready: ${status}`);
7508
+ reportBridgeError(ctx, state, `Run ended before worker Pi daemon became ready: ${status}. Inspect with \`rig run show ${options.runId}\`; restart with \`rig task run --task <id>\`.`);
7509
+ return false;
7510
+ }
7511
+ if (consecutiveFailures >= 5) {
7512
+ state.status = `Rig server unreachable \xB7 retrying (${formatElapsed(startedAt)}) \xB7 /detach to exit`;
7513
+ } else {
7514
+ state.status = `waiting for worker Pi daemon \xB7 ${status} \xB7 ${formatElapsed(startedAt)} \xB7 /detach to exit`;
7515
+ }
7516
+ updatePiUi(ctx, state);
7517
+ if (Date.now() >= deadline) {
7518
+ reportBridgeError(ctx, state, `Worker Pi daemon did not become ready within ${formatElapsed(startedAt)} (last status: ${status}). ` + `Check \`rig run show ${options.runId}\` and \`rig doctor\`; the run may have been restarted by a server deploy. ` + "Set RIG_PI_ATTACH_TIMEOUT_MS to wait longer.");
7469
7519
  return false;
7470
7520
  }
7471
7521
  await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
@@ -7673,7 +7723,7 @@ function createRigWorkerPiBridgeExtension(options) {
7673
7723
  nativePiUiContextAvailable = Boolean(nativePiUi(ctx));
7674
7724
  state.nativeStream = nativePiUiContextAvailable;
7675
7725
  updatePiUi(ctx, state);
7676
- ctx.ui.notify(nativePiUiContextAvailable ? "Rig worker Pi native stream bridge loaded" : "Rig worker Pi bridge extension loaded (degraded widget fallback)", "info");
7726
+ ctx.ui.notify(nativePiUiContextAvailable ? "Connected to the Rig worker session (native stream). /detach exits, /stop cancels the run." : "Connected to the Rig worker session (transcript view). /detach exits, /stop cancels the run.", "info");
7677
7727
  ctx.ui.onTerminalInput((data) => {
7678
7728
  if (data.includes("\x04")) {
7679
7729
  ctx.shutdown();
@@ -124,7 +124,23 @@ function createPiRunStreamRenderer(output = process.stdout) {
124
124
  }
125
125
  if (entry.type === "timeline_warning") {
126
126
  writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
127
+ continue;
128
+ }
129
+ if (entry.type === "action") {
130
+ const text = String(entry.detail ?? entry.message ?? entry.title ?? "").trim();
131
+ if (text)
132
+ writeLine(`[Rig action] ${text}`);
133
+ continue;
134
+ }
135
+ if (entry.type === "user_message") {
136
+ const text = String(entry.text ?? entry.message ?? entry.detail ?? "").trim();
137
+ if (text)
138
+ writeLine(`[Operator] ${text}`);
139
+ continue;
127
140
  }
141
+ const fallback = String(entry.detail ?? entry.message ?? entry.text ?? entry.title ?? "").trim();
142
+ if (fallback)
143
+ writeLine(`[${String(entry.type ?? "timeline")}] ${fallback}`);
128
144
  }
129
145
  },
130
146
  renderLogs(entries) {
@@ -410,7 +410,23 @@ function createPiRunStreamRenderer(output = process.stdout) {
410
410
  }
411
411
  if (entry.type === "timeline_warning") {
412
412
  writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
413
+ continue;
414
+ }
415
+ if (entry.type === "action") {
416
+ const text = String(entry.detail ?? entry.message ?? entry.title ?? "").trim();
417
+ if (text)
418
+ writeLine(`[Rig action] ${text}`);
419
+ continue;
420
+ }
421
+ if (entry.type === "user_message") {
422
+ const text = String(entry.text ?? entry.message ?? entry.detail ?? "").trim();
423
+ if (text)
424
+ writeLine(`[Operator] ${text}`);
425
+ continue;
413
426
  }
427
+ const fallback = String(entry.detail ?? entry.message ?? entry.text ?? entry.title ?? "").trim();
428
+ if (fallback)
429
+ writeLine(`[${String(entry.type ?? "timeline")}] ${fallback}`);
414
430
  }
415
431
  },
416
432
  renderLogs(entries) {
@@ -543,12 +559,12 @@ function parseExtensionUiRequest(value) {
543
559
  }
544
560
  function renderBridgeWidget(state) {
545
561
  const statusParts = [
546
- state.wsConnected ? "live WS" : "WS pending",
562
+ state.wsConnected ? "live" : "connecting",
547
563
  state.status,
548
564
  state.model,
549
565
  state.cwd
550
566
  ].filter(Boolean);
551
- const lines = [`Worker Pi daemon bridge \xB7 ${statusParts.join(" \xB7 ")}`];
567
+ const lines = [`Rig worker session \xB7 ${statusParts.join(" \xB7 ")}`];
552
568
  if (state.activity)
553
569
  lines.push(state.activity);
554
570
  if (state.commands.length > 0) {
@@ -558,27 +574,35 @@ function renderBridgeWidget(state) {
558
574
  if (state.transcript.length > 0) {
559
575
  lines.push(...state.transcript.slice(-MAX_TRANSCRIPT_LINES));
560
576
  } else {
561
- lines.push("Waiting for worker Pi daemon transcript\u2026");
577
+ lines.push("Waiting for the worker session transcript\u2026 (/detach exits and leaves the worker running)");
562
578
  }
563
579
  if (state.pendingUi) {
564
580
  lines.push("");
565
- lines.push(`Extension UI request \xB7 ${state.pendingUi.method}`);
581
+ lines.push(`Worker needs input \xB7 ${state.pendingUi.method}`);
566
582
  lines.push(state.pendingUi.prompt);
567
583
  state.pendingUi.options.forEach((option, index) => lines.push(`${index + 1}. ${option}`));
568
- lines.push("Reply in the Pi editor. /cancel cancels this request.");
584
+ lines.push("Reply in the editor below. /cancel dismisses this request.");
569
585
  }
570
586
  return lines;
571
587
  }
588
+ function reportBridgeError(ctx, state, message) {
589
+ appendTranscript(state, "Error", message);
590
+ state.status = message;
591
+ try {
592
+ ctx.ui.notify(message, "error");
593
+ } catch {}
594
+ updatePiUi(ctx, state);
595
+ }
572
596
  function updatePiUi(ctx, state) {
573
- ctx.ui.setTitle("Pi \xB7 Rig worker daemon");
574
- ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker Pi WS live" : state.status);
597
+ ctx.ui.setTitle("Rig \xB7 worker session");
598
+ ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker session live" : state.status);
575
599
  syncNativeDisplayCwd(ctx, state);
576
600
  if (state.nativeStream && nativePiUi(ctx)) {
577
601
  ctx.ui.setWidget("rig-worker-pi-transcript", undefined);
578
602
  return;
579
603
  }
580
604
  ctx.ui.setWorkingVisible(false);
581
- ctx.ui.setWidget("rig-worker-pi-transcript", [`Worker Pi daemon bridge \xB7 degraded widget transcript`, ...renderBridgeWidget(state)], { placement: "aboveEditor" });
605
+ ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
582
606
  }
583
607
  function applyStatus(state, payload) {
584
608
  const status = recordOf(payload.status) ?? payload;
@@ -723,19 +747,45 @@ function applyEnvelope(ctx, state, envelopeValue) {
723
747
  }
724
748
  syncNativeDisplayCwd(ctx, state);
725
749
  }
750
+ function resolveAttachReadyTimeoutMs() {
751
+ const raw = Number.parseInt(process.env.RIG_PI_ATTACH_TIMEOUT_MS ?? "", 10);
752
+ return Number.isFinite(raw) && raw > 0 ? raw : 10 * 60000;
753
+ }
754
+ function formatElapsed(sinceMs) {
755
+ const totalSeconds = Math.floor((Date.now() - sinceMs) / 1000);
756
+ const minutes = Math.floor(totalSeconds / 60);
757
+ const seconds = totalSeconds % 60;
758
+ return minutes > 0 ? `${minutes}m${String(seconds).padStart(2, "0")}s` : `${seconds}s`;
759
+ }
726
760
  async function waitForWorkerReady(options, ctx, state) {
761
+ const startedAt = Date.now();
762
+ const deadline = startedAt + resolveAttachReadyTimeoutMs();
763
+ let consecutiveFailures = 0;
727
764
  while (true) {
728
- const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => ({
729
- ready: false,
730
- status: error instanceof Error ? error.message : String(error),
731
- retryAfterMs: 1000
732
- }));
765
+ let requestFailed = false;
766
+ const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => {
767
+ requestFailed = true;
768
+ return {
769
+ ready: false,
770
+ status: error instanceof Error ? error.message : String(error),
771
+ retryAfterMs: 1000
772
+ };
773
+ });
733
774
  if (session.ready === false) {
775
+ consecutiveFailures = requestFailed ? consecutiveFailures + 1 : 0;
734
776
  const status = String(session.status ?? "starting");
735
- state.status = `waiting for worker Pi daemon \xB7 ${status}`;
736
- updatePiUi(ctx, state);
737
777
  if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
738
- appendTranscript(state, "Error", `Run ended before worker Pi daemon became ready: ${status}`);
778
+ reportBridgeError(ctx, state, `Run ended before worker Pi daemon became ready: ${status}. Inspect with \`rig run show ${options.runId}\`; restart with \`rig task run --task <id>\`.`);
779
+ return false;
780
+ }
781
+ if (consecutiveFailures >= 5) {
782
+ state.status = `Rig server unreachable \xB7 retrying (${formatElapsed(startedAt)}) \xB7 /detach to exit`;
783
+ } else {
784
+ state.status = `waiting for worker Pi daemon \xB7 ${status} \xB7 ${formatElapsed(startedAt)} \xB7 /detach to exit`;
785
+ }
786
+ updatePiUi(ctx, state);
787
+ if (Date.now() >= deadline) {
788
+ reportBridgeError(ctx, state, `Worker Pi daemon did not become ready within ${formatElapsed(startedAt)} (last status: ${status}). ` + `Check \`rig run show ${options.runId}\` and \`rig doctor\`; the run may have been restarted by a server deploy. ` + "Set RIG_PI_ATTACH_TIMEOUT_MS to wait longer.");
739
789
  return false;
740
790
  }
741
791
  await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
@@ -943,7 +993,7 @@ function createRigWorkerPiBridgeExtension(options) {
943
993
  nativePiUiContextAvailable = Boolean(nativePiUi(ctx));
944
994
  state.nativeStream = nativePiUiContextAvailable;
945
995
  updatePiUi(ctx, state);
946
- ctx.ui.notify(nativePiUiContextAvailable ? "Rig worker Pi native stream bridge loaded" : "Rig worker Pi bridge extension loaded (degraded widget fallback)", "info");
996
+ ctx.ui.notify(nativePiUiContextAvailable ? "Connected to the Rig worker session (native stream). /detach exits, /stop cancels the run." : "Connected to the Rig worker session (transcript view). /detach exits, /stop cancels the run.", "info");
947
997
  ctx.ui.onTerminalInput((data) => {
948
998
  if (data.includes("\x04")) {
949
999
  ctx.shutdown();
@@ -333,12 +333,12 @@ function parseExtensionUiRequest(value) {
333
333
  }
334
334
  function renderBridgeWidget(state) {
335
335
  const statusParts = [
336
- state.wsConnected ? "live WS" : "WS pending",
336
+ state.wsConnected ? "live" : "connecting",
337
337
  state.status,
338
338
  state.model,
339
339
  state.cwd
340
340
  ].filter(Boolean);
341
- const lines = [`Worker Pi daemon bridge \xB7 ${statusParts.join(" \xB7 ")}`];
341
+ const lines = [`Rig worker session \xB7 ${statusParts.join(" \xB7 ")}`];
342
342
  if (state.activity)
343
343
  lines.push(state.activity);
344
344
  if (state.commands.length > 0) {
@@ -348,27 +348,35 @@ function renderBridgeWidget(state) {
348
348
  if (state.transcript.length > 0) {
349
349
  lines.push(...state.transcript.slice(-MAX_TRANSCRIPT_LINES));
350
350
  } else {
351
- lines.push("Waiting for worker Pi daemon transcript\u2026");
351
+ lines.push("Waiting for the worker session transcript\u2026 (/detach exits and leaves the worker running)");
352
352
  }
353
353
  if (state.pendingUi) {
354
354
  lines.push("");
355
- lines.push(`Extension UI request \xB7 ${state.pendingUi.method}`);
355
+ lines.push(`Worker needs input \xB7 ${state.pendingUi.method}`);
356
356
  lines.push(state.pendingUi.prompt);
357
357
  state.pendingUi.options.forEach((option, index) => lines.push(`${index + 1}. ${option}`));
358
- lines.push("Reply in the Pi editor. /cancel cancels this request.");
358
+ lines.push("Reply in the editor below. /cancel dismisses this request.");
359
359
  }
360
360
  return lines;
361
361
  }
362
+ function reportBridgeError(ctx, state, message) {
363
+ appendTranscript(state, "Error", message);
364
+ state.status = message;
365
+ try {
366
+ ctx.ui.notify(message, "error");
367
+ } catch {}
368
+ updatePiUi(ctx, state);
369
+ }
362
370
  function updatePiUi(ctx, state) {
363
- ctx.ui.setTitle("Pi \xB7 Rig worker daemon");
364
- ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker Pi WS live" : state.status);
371
+ ctx.ui.setTitle("Rig \xB7 worker session");
372
+ ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker session live" : state.status);
365
373
  syncNativeDisplayCwd(ctx, state);
366
374
  if (state.nativeStream && nativePiUi(ctx)) {
367
375
  ctx.ui.setWidget("rig-worker-pi-transcript", undefined);
368
376
  return;
369
377
  }
370
378
  ctx.ui.setWorkingVisible(false);
371
- ctx.ui.setWidget("rig-worker-pi-transcript", [`Worker Pi daemon bridge \xB7 degraded widget transcript`, ...renderBridgeWidget(state)], { placement: "aboveEditor" });
379
+ ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
372
380
  }
373
381
  function applyStatus(state, payload) {
374
382
  const status = recordOf(payload.status) ?? payload;
@@ -513,19 +521,45 @@ function applyEnvelope(ctx, state, envelopeValue) {
513
521
  }
514
522
  syncNativeDisplayCwd(ctx, state);
515
523
  }
524
+ function resolveAttachReadyTimeoutMs() {
525
+ const raw = Number.parseInt(process.env.RIG_PI_ATTACH_TIMEOUT_MS ?? "", 10);
526
+ return Number.isFinite(raw) && raw > 0 ? raw : 10 * 60000;
527
+ }
528
+ function formatElapsed(sinceMs) {
529
+ const totalSeconds = Math.floor((Date.now() - sinceMs) / 1000);
530
+ const minutes = Math.floor(totalSeconds / 60);
531
+ const seconds = totalSeconds % 60;
532
+ return minutes > 0 ? `${minutes}m${String(seconds).padStart(2, "0")}s` : `${seconds}s`;
533
+ }
516
534
  async function waitForWorkerReady(options, ctx, state) {
535
+ const startedAt = Date.now();
536
+ const deadline = startedAt + resolveAttachReadyTimeoutMs();
537
+ let consecutiveFailures = 0;
517
538
  while (true) {
518
- const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => ({
519
- ready: false,
520
- status: error instanceof Error ? error.message : String(error),
521
- retryAfterMs: 1000
522
- }));
539
+ let requestFailed = false;
540
+ const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => {
541
+ requestFailed = true;
542
+ return {
543
+ ready: false,
544
+ status: error instanceof Error ? error.message : String(error),
545
+ retryAfterMs: 1000
546
+ };
547
+ });
523
548
  if (session.ready === false) {
549
+ consecutiveFailures = requestFailed ? consecutiveFailures + 1 : 0;
524
550
  const status = String(session.status ?? "starting");
525
- state.status = `waiting for worker Pi daemon \xB7 ${status}`;
526
- updatePiUi(ctx, state);
527
551
  if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
528
- appendTranscript(state, "Error", `Run ended before worker Pi daemon became ready: ${status}`);
552
+ reportBridgeError(ctx, state, `Run ended before worker Pi daemon became ready: ${status}. Inspect with \`rig run show ${options.runId}\`; restart with \`rig task run --task <id>\`.`);
553
+ return false;
554
+ }
555
+ if (consecutiveFailures >= 5) {
556
+ state.status = `Rig server unreachable \xB7 retrying (${formatElapsed(startedAt)}) \xB7 /detach to exit`;
557
+ } else {
558
+ state.status = `waiting for worker Pi daemon \xB7 ${status} \xB7 ${formatElapsed(startedAt)} \xB7 /detach to exit`;
559
+ }
560
+ updatePiUi(ctx, state);
561
+ if (Date.now() >= deadline) {
562
+ reportBridgeError(ctx, state, `Worker Pi daemon did not become ready within ${formatElapsed(startedAt)} (last status: ${status}). ` + `Check \`rig run show ${options.runId}\` and \`rig doctor\`; the run may have been restarted by a server deploy. ` + "Set RIG_PI_ATTACH_TIMEOUT_MS to wait longer.");
529
563
  return false;
530
564
  }
531
565
  await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
@@ -733,7 +767,7 @@ function createRigWorkerPiBridgeExtension(options) {
733
767
  nativePiUiContextAvailable = Boolean(nativePiUi(ctx));
734
768
  state.nativeStream = nativePiUiContextAvailable;
735
769
  updatePiUi(ctx, state);
736
- ctx.ui.notify(nativePiUiContextAvailable ? "Rig worker Pi native stream bridge loaded" : "Rig worker Pi bridge extension loaded (degraded widget fallback)", "info");
770
+ ctx.ui.notify(nativePiUiContextAvailable ? "Connected to the Rig worker session (native stream). /detach exits, /stop cancels the run." : "Connected to the Rig worker session (transcript view). /detach exits, /stop cancels the run.", "info");
737
771
  ctx.ui.onTerminalInput((data) => {
738
772
  if (data.includes("\x04")) {
739
773
  ctx.shutdown();
@@ -323,12 +323,12 @@ function parseExtensionUiRequest(value) {
323
323
  }
324
324
  function renderBridgeWidget(state) {
325
325
  const statusParts = [
326
- state.wsConnected ? "live WS" : "WS pending",
326
+ state.wsConnected ? "live" : "connecting",
327
327
  state.status,
328
328
  state.model,
329
329
  state.cwd
330
330
  ].filter(Boolean);
331
- const lines = [`Worker Pi daemon bridge \xB7 ${statusParts.join(" \xB7 ")}`];
331
+ const lines = [`Rig worker session \xB7 ${statusParts.join(" \xB7 ")}`];
332
332
  if (state.activity)
333
333
  lines.push(state.activity);
334
334
  if (state.commands.length > 0) {
@@ -338,27 +338,35 @@ function renderBridgeWidget(state) {
338
338
  if (state.transcript.length > 0) {
339
339
  lines.push(...state.transcript.slice(-MAX_TRANSCRIPT_LINES));
340
340
  } else {
341
- lines.push("Waiting for worker Pi daemon transcript\u2026");
341
+ lines.push("Waiting for the worker session transcript\u2026 (/detach exits and leaves the worker running)");
342
342
  }
343
343
  if (state.pendingUi) {
344
344
  lines.push("");
345
- lines.push(`Extension UI request \xB7 ${state.pendingUi.method}`);
345
+ lines.push(`Worker needs input \xB7 ${state.pendingUi.method}`);
346
346
  lines.push(state.pendingUi.prompt);
347
347
  state.pendingUi.options.forEach((option, index) => lines.push(`${index + 1}. ${option}`));
348
- lines.push("Reply in the Pi editor. /cancel cancels this request.");
348
+ lines.push("Reply in the editor below. /cancel dismisses this request.");
349
349
  }
350
350
  return lines;
351
351
  }
352
+ function reportBridgeError(ctx, state, message) {
353
+ appendTranscript(state, "Error", message);
354
+ state.status = message;
355
+ try {
356
+ ctx.ui.notify(message, "error");
357
+ } catch {}
358
+ updatePiUi(ctx, state);
359
+ }
352
360
  function updatePiUi(ctx, state) {
353
- ctx.ui.setTitle("Pi \xB7 Rig worker daemon");
354
- ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker Pi WS live" : state.status);
361
+ ctx.ui.setTitle("Rig \xB7 worker session");
362
+ ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker session live" : state.status);
355
363
  syncNativeDisplayCwd(ctx, state);
356
364
  if (state.nativeStream && nativePiUi(ctx)) {
357
365
  ctx.ui.setWidget("rig-worker-pi-transcript", undefined);
358
366
  return;
359
367
  }
360
368
  ctx.ui.setWorkingVisible(false);
361
- ctx.ui.setWidget("rig-worker-pi-transcript", [`Worker Pi daemon bridge \xB7 degraded widget transcript`, ...renderBridgeWidget(state)], { placement: "aboveEditor" });
369
+ ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
362
370
  }
363
371
  function applyStatus(state, payload) {
364
372
  const status = recordOf(payload.status) ?? payload;
@@ -503,19 +511,45 @@ function applyEnvelope(ctx, state, envelopeValue) {
503
511
  }
504
512
  syncNativeDisplayCwd(ctx, state);
505
513
  }
514
+ function resolveAttachReadyTimeoutMs() {
515
+ const raw = Number.parseInt(process.env.RIG_PI_ATTACH_TIMEOUT_MS ?? "", 10);
516
+ return Number.isFinite(raw) && raw > 0 ? raw : 10 * 60000;
517
+ }
518
+ function formatElapsed(sinceMs) {
519
+ const totalSeconds = Math.floor((Date.now() - sinceMs) / 1000);
520
+ const minutes = Math.floor(totalSeconds / 60);
521
+ const seconds = totalSeconds % 60;
522
+ return minutes > 0 ? `${minutes}m${String(seconds).padStart(2, "0")}s` : `${seconds}s`;
523
+ }
506
524
  async function waitForWorkerReady(options, ctx, state) {
525
+ const startedAt = Date.now();
526
+ const deadline = startedAt + resolveAttachReadyTimeoutMs();
527
+ let consecutiveFailures = 0;
507
528
  while (true) {
508
- const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => ({
509
- ready: false,
510
- status: error instanceof Error ? error.message : String(error),
511
- retryAfterMs: 1000
512
- }));
529
+ let requestFailed = false;
530
+ const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => {
531
+ requestFailed = true;
532
+ return {
533
+ ready: false,
534
+ status: error instanceof Error ? error.message : String(error),
535
+ retryAfterMs: 1000
536
+ };
537
+ });
513
538
  if (session.ready === false) {
539
+ consecutiveFailures = requestFailed ? consecutiveFailures + 1 : 0;
514
540
  const status = String(session.status ?? "starting");
515
- state.status = `waiting for worker Pi daemon \xB7 ${status}`;
516
- updatePiUi(ctx, state);
517
541
  if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
518
- appendTranscript(state, "Error", `Run ended before worker Pi daemon became ready: ${status}`);
542
+ reportBridgeError(ctx, state, `Run ended before worker Pi daemon became ready: ${status}. Inspect with \`rig run show ${options.runId}\`; restart with \`rig task run --task <id>\`.`);
543
+ return false;
544
+ }
545
+ if (consecutiveFailures >= 5) {
546
+ state.status = `Rig server unreachable \xB7 retrying (${formatElapsed(startedAt)}) \xB7 /detach to exit`;
547
+ } else {
548
+ state.status = `waiting for worker Pi daemon \xB7 ${status} \xB7 ${formatElapsed(startedAt)} \xB7 /detach to exit`;
549
+ }
550
+ updatePiUi(ctx, state);
551
+ if (Date.now() >= deadline) {
552
+ reportBridgeError(ctx, state, `Worker Pi daemon did not become ready within ${formatElapsed(startedAt)} (last status: ${status}). ` + `Check \`rig run show ${options.runId}\` and \`rig doctor\`; the run may have been restarted by a server deploy. ` + "Set RIG_PI_ATTACH_TIMEOUT_MS to wait longer.");
519
553
  return false;
520
554
  }
521
555
  await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
@@ -723,7 +757,7 @@ function createRigWorkerPiBridgeExtension(options) {
723
757
  nativePiUiContextAvailable = Boolean(nativePiUi(ctx));
724
758
  state.nativeStream = nativePiUiContextAvailable;
725
759
  updatePiUi(ctx, state);
726
- ctx.ui.notify(nativePiUiContextAvailable ? "Rig worker Pi native stream bridge loaded" : "Rig worker Pi bridge extension loaded (degraded widget fallback)", "info");
760
+ ctx.ui.notify(nativePiUiContextAvailable ? "Connected to the Rig worker session (native stream). /detach exits, /stop cancels the run." : "Connected to the Rig worker session (transcript view). /detach exits, /stop cancels the run.", "info");
727
761
  ctx.ui.onTerminalInput((data) => {
728
762
  if (data.includes("\x04")) {
729
763
  ctx.shutdown();
@@ -488,7 +488,23 @@ function createPiRunStreamRenderer(output = process.stdout) {
488
488
  }
489
489
  if (entry.type === "timeline_warning") {
490
490
  writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
491
+ continue;
492
+ }
493
+ if (entry.type === "action") {
494
+ const text = String(entry.detail ?? entry.message ?? entry.title ?? "").trim();
495
+ if (text)
496
+ writeLine(`[Rig action] ${text}`);
497
+ continue;
498
+ }
499
+ if (entry.type === "user_message") {
500
+ const text = String(entry.text ?? entry.message ?? entry.detail ?? "").trim();
501
+ if (text)
502
+ writeLine(`[Operator] ${text}`);
503
+ continue;
491
504
  }
505
+ const fallback = String(entry.detail ?? entry.message ?? entry.text ?? entry.title ?? "").trim();
506
+ if (fallback)
507
+ writeLine(`[${String(entry.type ?? "timeline")}] ${fallback}`);
492
508
  }
493
509
  },
494
510
  renderLogs(entries) {
@@ -621,12 +637,12 @@ function parseExtensionUiRequest(value) {
621
637
  }
622
638
  function renderBridgeWidget(state) {
623
639
  const statusParts = [
624
- state.wsConnected ? "live WS" : "WS pending",
640
+ state.wsConnected ? "live" : "connecting",
625
641
  state.status,
626
642
  state.model,
627
643
  state.cwd
628
644
  ].filter(Boolean);
629
- const lines = [`Worker Pi daemon bridge \xB7 ${statusParts.join(" \xB7 ")}`];
645
+ const lines = [`Rig worker session \xB7 ${statusParts.join(" \xB7 ")}`];
630
646
  if (state.activity)
631
647
  lines.push(state.activity);
632
648
  if (state.commands.length > 0) {
@@ -636,27 +652,35 @@ function renderBridgeWidget(state) {
636
652
  if (state.transcript.length > 0) {
637
653
  lines.push(...state.transcript.slice(-MAX_TRANSCRIPT_LINES));
638
654
  } else {
639
- lines.push("Waiting for worker Pi daemon transcript\u2026");
655
+ lines.push("Waiting for the worker session transcript\u2026 (/detach exits and leaves the worker running)");
640
656
  }
641
657
  if (state.pendingUi) {
642
658
  lines.push("");
643
- lines.push(`Extension UI request \xB7 ${state.pendingUi.method}`);
659
+ lines.push(`Worker needs input \xB7 ${state.pendingUi.method}`);
644
660
  lines.push(state.pendingUi.prompt);
645
661
  state.pendingUi.options.forEach((option, index) => lines.push(`${index + 1}. ${option}`));
646
- lines.push("Reply in the Pi editor. /cancel cancels this request.");
662
+ lines.push("Reply in the editor below. /cancel dismisses this request.");
647
663
  }
648
664
  return lines;
649
665
  }
666
+ function reportBridgeError(ctx, state, message) {
667
+ appendTranscript(state, "Error", message);
668
+ state.status = message;
669
+ try {
670
+ ctx.ui.notify(message, "error");
671
+ } catch {}
672
+ updatePiUi(ctx, state);
673
+ }
650
674
  function updatePiUi(ctx, state) {
651
- ctx.ui.setTitle("Pi \xB7 Rig worker daemon");
652
- ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker Pi WS live" : state.status);
675
+ ctx.ui.setTitle("Rig \xB7 worker session");
676
+ ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker session live" : state.status);
653
677
  syncNativeDisplayCwd(ctx, state);
654
678
  if (state.nativeStream && nativePiUi(ctx)) {
655
679
  ctx.ui.setWidget("rig-worker-pi-transcript", undefined);
656
680
  return;
657
681
  }
658
682
  ctx.ui.setWorkingVisible(false);
659
- ctx.ui.setWidget("rig-worker-pi-transcript", [`Worker Pi daemon bridge \xB7 degraded widget transcript`, ...renderBridgeWidget(state)], { placement: "aboveEditor" });
683
+ ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
660
684
  }
661
685
  function applyStatus(state, payload) {
662
686
  const status = recordOf(payload.status) ?? payload;
@@ -801,19 +825,45 @@ function applyEnvelope(ctx, state, envelopeValue) {
801
825
  }
802
826
  syncNativeDisplayCwd(ctx, state);
803
827
  }
828
+ function resolveAttachReadyTimeoutMs() {
829
+ const raw = Number.parseInt(process.env.RIG_PI_ATTACH_TIMEOUT_MS ?? "", 10);
830
+ return Number.isFinite(raw) && raw > 0 ? raw : 10 * 60000;
831
+ }
832
+ function formatElapsed(sinceMs) {
833
+ const totalSeconds = Math.floor((Date.now() - sinceMs) / 1000);
834
+ const minutes = Math.floor(totalSeconds / 60);
835
+ const seconds = totalSeconds % 60;
836
+ return minutes > 0 ? `${minutes}m${String(seconds).padStart(2, "0")}s` : `${seconds}s`;
837
+ }
804
838
  async function waitForWorkerReady(options, ctx, state) {
839
+ const startedAt = Date.now();
840
+ const deadline = startedAt + resolveAttachReadyTimeoutMs();
841
+ let consecutiveFailures = 0;
805
842
  while (true) {
806
- const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => ({
807
- ready: false,
808
- status: error instanceof Error ? error.message : String(error),
809
- retryAfterMs: 1000
810
- }));
843
+ let requestFailed = false;
844
+ const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => {
845
+ requestFailed = true;
846
+ return {
847
+ ready: false,
848
+ status: error instanceof Error ? error.message : String(error),
849
+ retryAfterMs: 1000
850
+ };
851
+ });
811
852
  if (session.ready === false) {
853
+ consecutiveFailures = requestFailed ? consecutiveFailures + 1 : 0;
812
854
  const status = String(session.status ?? "starting");
813
- state.status = `waiting for worker Pi daemon \xB7 ${status}`;
814
- updatePiUi(ctx, state);
815
855
  if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
816
- appendTranscript(state, "Error", `Run ended before worker Pi daemon became ready: ${status}`);
856
+ reportBridgeError(ctx, state, `Run ended before worker Pi daemon became ready: ${status}. Inspect with \`rig run show ${options.runId}\`; restart with \`rig task run --task <id>\`.`);
857
+ return false;
858
+ }
859
+ if (consecutiveFailures >= 5) {
860
+ state.status = `Rig server unreachable \xB7 retrying (${formatElapsed(startedAt)}) \xB7 /detach to exit`;
861
+ } else {
862
+ state.status = `waiting for worker Pi daemon \xB7 ${status} \xB7 ${formatElapsed(startedAt)} \xB7 /detach to exit`;
863
+ }
864
+ updatePiUi(ctx, state);
865
+ if (Date.now() >= deadline) {
866
+ reportBridgeError(ctx, state, `Worker Pi daemon did not become ready within ${formatElapsed(startedAt)} (last status: ${status}). ` + `Check \`rig run show ${options.runId}\` and \`rig doctor\`; the run may have been restarted by a server deploy. ` + "Set RIG_PI_ATTACH_TIMEOUT_MS to wait longer.");
817
867
  return false;
818
868
  }
819
869
  await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
@@ -1021,7 +1071,7 @@ function createRigWorkerPiBridgeExtension(options) {
1021
1071
  nativePiUiContextAvailable = Boolean(nativePiUi(ctx));
1022
1072
  state.nativeStream = nativePiUiContextAvailable;
1023
1073
  updatePiUi(ctx, state);
1024
- ctx.ui.notify(nativePiUiContextAvailable ? "Rig worker Pi native stream bridge loaded" : "Rig worker Pi bridge extension loaded (degraded widget fallback)", "info");
1074
+ ctx.ui.notify(nativePiUiContextAvailable ? "Connected to the Rig worker session (native stream). /detach exits, /stop cancels the run." : "Connected to the Rig worker session (transcript view). /detach exits, /stop cancels the run.", "info");
1025
1075
  ctx.ui.onTerminalInput((data) => {
1026
1076
  if (data.includes("\x04")) {
1027
1077
  ctx.shutdown();
@@ -825,7 +825,23 @@ function createPiRunStreamRenderer(output = process.stdout) {
825
825
  }
826
826
  if (entry.type === "timeline_warning") {
827
827
  writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
828
+ continue;
829
+ }
830
+ if (entry.type === "action") {
831
+ const text = String(entry.detail ?? entry.message ?? entry.title ?? "").trim();
832
+ if (text)
833
+ writeLine(`[Rig action] ${text}`);
834
+ continue;
835
+ }
836
+ if (entry.type === "user_message") {
837
+ const text = String(entry.text ?? entry.message ?? entry.detail ?? "").trim();
838
+ if (text)
839
+ writeLine(`[Operator] ${text}`);
840
+ continue;
828
841
  }
842
+ const fallback = String(entry.detail ?? entry.message ?? entry.text ?? entry.title ?? "").trim();
843
+ if (fallback)
844
+ writeLine(`[${String(entry.type ?? "timeline")}] ${fallback}`);
829
845
  }
830
846
  },
831
847
  renderLogs(entries) {
@@ -1024,12 +1040,12 @@ function parseExtensionUiRequest(value) {
1024
1040
  }
1025
1041
  function renderBridgeWidget(state) {
1026
1042
  const statusParts = [
1027
- state.wsConnected ? "live WS" : "WS pending",
1043
+ state.wsConnected ? "live" : "connecting",
1028
1044
  state.status,
1029
1045
  state.model,
1030
1046
  state.cwd
1031
1047
  ].filter(Boolean);
1032
- const lines = [`Worker Pi daemon bridge \xB7 ${statusParts.join(" \xB7 ")}`];
1048
+ const lines = [`Rig worker session \xB7 ${statusParts.join(" \xB7 ")}`];
1033
1049
  if (state.activity)
1034
1050
  lines.push(state.activity);
1035
1051
  if (state.commands.length > 0) {
@@ -1039,27 +1055,35 @@ function renderBridgeWidget(state) {
1039
1055
  if (state.transcript.length > 0) {
1040
1056
  lines.push(...state.transcript.slice(-MAX_TRANSCRIPT_LINES));
1041
1057
  } else {
1042
- lines.push("Waiting for worker Pi daemon transcript\u2026");
1058
+ lines.push("Waiting for the worker session transcript\u2026 (/detach exits and leaves the worker running)");
1043
1059
  }
1044
1060
  if (state.pendingUi) {
1045
1061
  lines.push("");
1046
- lines.push(`Extension UI request \xB7 ${state.pendingUi.method}`);
1062
+ lines.push(`Worker needs input \xB7 ${state.pendingUi.method}`);
1047
1063
  lines.push(state.pendingUi.prompt);
1048
1064
  state.pendingUi.options.forEach((option, index) => lines.push(`${index + 1}. ${option}`));
1049
- lines.push("Reply in the Pi editor. /cancel cancels this request.");
1065
+ lines.push("Reply in the editor below. /cancel dismisses this request.");
1050
1066
  }
1051
1067
  return lines;
1052
1068
  }
1069
+ function reportBridgeError(ctx, state, message2) {
1070
+ appendTranscript(state, "Error", message2);
1071
+ state.status = message2;
1072
+ try {
1073
+ ctx.ui.notify(message2, "error");
1074
+ } catch {}
1075
+ updatePiUi(ctx, state);
1076
+ }
1053
1077
  function updatePiUi(ctx, state) {
1054
- ctx.ui.setTitle("Pi \xB7 Rig worker daemon");
1055
- ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker Pi WS live" : state.status);
1078
+ ctx.ui.setTitle("Rig \xB7 worker session");
1079
+ ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker session live" : state.status);
1056
1080
  syncNativeDisplayCwd(ctx, state);
1057
1081
  if (state.nativeStream && nativePiUi(ctx)) {
1058
1082
  ctx.ui.setWidget("rig-worker-pi-transcript", undefined);
1059
1083
  return;
1060
1084
  }
1061
1085
  ctx.ui.setWorkingVisible(false);
1062
- ctx.ui.setWidget("rig-worker-pi-transcript", [`Worker Pi daemon bridge \xB7 degraded widget transcript`, ...renderBridgeWidget(state)], { placement: "aboveEditor" });
1086
+ ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
1063
1087
  }
1064
1088
  function applyStatus(state, payload) {
1065
1089
  const status = recordOf(payload.status) ?? payload;
@@ -1204,19 +1228,45 @@ function applyEnvelope(ctx, state, envelopeValue) {
1204
1228
  }
1205
1229
  syncNativeDisplayCwd(ctx, state);
1206
1230
  }
1231
+ function resolveAttachReadyTimeoutMs() {
1232
+ const raw = Number.parseInt(process.env.RIG_PI_ATTACH_TIMEOUT_MS ?? "", 10);
1233
+ return Number.isFinite(raw) && raw > 0 ? raw : 10 * 60000;
1234
+ }
1235
+ function formatElapsed(sinceMs) {
1236
+ const totalSeconds = Math.floor((Date.now() - sinceMs) / 1000);
1237
+ const minutes = Math.floor(totalSeconds / 60);
1238
+ const seconds = totalSeconds % 60;
1239
+ return minutes > 0 ? `${minutes}m${String(seconds).padStart(2, "0")}s` : `${seconds}s`;
1240
+ }
1207
1241
  async function waitForWorkerReady(options, ctx, state) {
1242
+ const startedAt = Date.now();
1243
+ const deadline = startedAt + resolveAttachReadyTimeoutMs();
1244
+ let consecutiveFailures = 0;
1208
1245
  while (true) {
1209
- const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => ({
1210
- ready: false,
1211
- status: error instanceof Error ? error.message : String(error),
1212
- retryAfterMs: 1000
1213
- }));
1246
+ let requestFailed = false;
1247
+ const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => {
1248
+ requestFailed = true;
1249
+ return {
1250
+ ready: false,
1251
+ status: error instanceof Error ? error.message : String(error),
1252
+ retryAfterMs: 1000
1253
+ };
1254
+ });
1214
1255
  if (session.ready === false) {
1256
+ consecutiveFailures = requestFailed ? consecutiveFailures + 1 : 0;
1215
1257
  const status = String(session.status ?? "starting");
1216
- state.status = `waiting for worker Pi daemon \xB7 ${status}`;
1217
- updatePiUi(ctx, state);
1218
1258
  if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
1219
- appendTranscript(state, "Error", `Run ended before worker Pi daemon became ready: ${status}`);
1259
+ reportBridgeError(ctx, state, `Run ended before worker Pi daemon became ready: ${status}. Inspect with \`rig run show ${options.runId}\`; restart with \`rig task run --task <id>\`.`);
1260
+ return false;
1261
+ }
1262
+ if (consecutiveFailures >= 5) {
1263
+ state.status = `Rig server unreachable \xB7 retrying (${formatElapsed(startedAt)}) \xB7 /detach to exit`;
1264
+ } else {
1265
+ state.status = `waiting for worker Pi daemon \xB7 ${status} \xB7 ${formatElapsed(startedAt)} \xB7 /detach to exit`;
1266
+ }
1267
+ updatePiUi(ctx, state);
1268
+ if (Date.now() >= deadline) {
1269
+ reportBridgeError(ctx, state, `Worker Pi daemon did not become ready within ${formatElapsed(startedAt)} (last status: ${status}). ` + `Check \`rig run show ${options.runId}\` and \`rig doctor\`; the run may have been restarted by a server deploy. ` + "Set RIG_PI_ATTACH_TIMEOUT_MS to wait longer.");
1220
1270
  return false;
1221
1271
  }
1222
1272
  await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
@@ -1424,7 +1474,7 @@ function createRigWorkerPiBridgeExtension(options) {
1424
1474
  nativePiUiContextAvailable = Boolean(nativePiUi(ctx));
1425
1475
  state.nativeStream = nativePiUiContextAvailable;
1426
1476
  updatePiUi(ctx, state);
1427
- ctx.ui.notify(nativePiUiContextAvailable ? "Rig worker Pi native stream bridge loaded" : "Rig worker Pi bridge extension loaded (degraded widget fallback)", "info");
1477
+ ctx.ui.notify(nativePiUiContextAvailable ? "Connected to the Rig worker session (native stream). /detach exits, /stop cancels the run." : "Connected to the Rig worker session (transcript view). /detach exits, /stop cancels the run.", "info");
1428
1478
  ctx.ui.onTerminalInput((data) => {
1429
1479
  if (data.includes("\x04")) {
1430
1480
  ctx.shutdown();
@@ -6926,7 +6926,23 @@ function createPiRunStreamRenderer(output = process.stdout) {
6926
6926
  }
6927
6927
  if (entry.type === "timeline_warning") {
6928
6928
  writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
6929
+ continue;
6930
+ }
6931
+ if (entry.type === "action") {
6932
+ const text2 = String(entry.detail ?? entry.message ?? entry.title ?? "").trim();
6933
+ if (text2)
6934
+ writeLine(`[Rig action] ${text2}`);
6935
+ continue;
6929
6936
  }
6937
+ if (entry.type === "user_message") {
6938
+ const text2 = String(entry.text ?? entry.message ?? entry.detail ?? "").trim();
6939
+ if (text2)
6940
+ writeLine(`[Operator] ${text2}`);
6941
+ continue;
6942
+ }
6943
+ const fallback = String(entry.detail ?? entry.message ?? entry.text ?? entry.title ?? "").trim();
6944
+ if (fallback)
6945
+ writeLine(`[${String(entry.type ?? "timeline")}] ${fallback}`);
6930
6946
  }
6931
6947
  },
6932
6948
  renderLogs(entries) {
@@ -7079,12 +7095,12 @@ function parseExtensionUiRequest(value) {
7079
7095
  }
7080
7096
  function renderBridgeWidget(state) {
7081
7097
  const statusParts = [
7082
- state.wsConnected ? "live WS" : "WS pending",
7098
+ state.wsConnected ? "live" : "connecting",
7083
7099
  state.status,
7084
7100
  state.model,
7085
7101
  state.cwd
7086
7102
  ].filter(Boolean);
7087
- const lines = [`Worker Pi daemon bridge \xB7 ${statusParts.join(" \xB7 ")}`];
7103
+ const lines = [`Rig worker session \xB7 ${statusParts.join(" \xB7 ")}`];
7088
7104
  if (state.activity)
7089
7105
  lines.push(state.activity);
7090
7106
  if (state.commands.length > 0) {
@@ -7094,27 +7110,35 @@ function renderBridgeWidget(state) {
7094
7110
  if (state.transcript.length > 0) {
7095
7111
  lines.push(...state.transcript.slice(-MAX_TRANSCRIPT_LINES));
7096
7112
  } else {
7097
- lines.push("Waiting for worker Pi daemon transcript\u2026");
7113
+ lines.push("Waiting for the worker session transcript\u2026 (/detach exits and leaves the worker running)");
7098
7114
  }
7099
7115
  if (state.pendingUi) {
7100
7116
  lines.push("");
7101
- lines.push(`Extension UI request \xB7 ${state.pendingUi.method}`);
7117
+ lines.push(`Worker needs input \xB7 ${state.pendingUi.method}`);
7102
7118
  lines.push(state.pendingUi.prompt);
7103
7119
  state.pendingUi.options.forEach((option, index) => lines.push(`${index + 1}. ${option}`));
7104
- lines.push("Reply in the Pi editor. /cancel cancels this request.");
7120
+ lines.push("Reply in the editor below. /cancel dismisses this request.");
7105
7121
  }
7106
7122
  return lines;
7107
7123
  }
7124
+ function reportBridgeError(ctx, state, message2) {
7125
+ appendTranscript(state, "Error", message2);
7126
+ state.status = message2;
7127
+ try {
7128
+ ctx.ui.notify(message2, "error");
7129
+ } catch {}
7130
+ updatePiUi(ctx, state);
7131
+ }
7108
7132
  function updatePiUi(ctx, state) {
7109
- ctx.ui.setTitle("Pi \xB7 Rig worker daemon");
7110
- ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker Pi WS live" : state.status);
7133
+ ctx.ui.setTitle("Rig \xB7 worker session");
7134
+ ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker session live" : state.status);
7111
7135
  syncNativeDisplayCwd(ctx, state);
7112
7136
  if (state.nativeStream && nativePiUi(ctx)) {
7113
7137
  ctx.ui.setWidget("rig-worker-pi-transcript", undefined);
7114
7138
  return;
7115
7139
  }
7116
7140
  ctx.ui.setWorkingVisible(false);
7117
- ctx.ui.setWidget("rig-worker-pi-transcript", [`Worker Pi daemon bridge \xB7 degraded widget transcript`, ...renderBridgeWidget(state)], { placement: "aboveEditor" });
7141
+ ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
7118
7142
  }
7119
7143
  function applyStatus(state, payload) {
7120
7144
  const status = recordOf(payload.status) ?? payload;
@@ -7259,19 +7283,45 @@ function applyEnvelope(ctx, state, envelopeValue) {
7259
7283
  }
7260
7284
  syncNativeDisplayCwd(ctx, state);
7261
7285
  }
7286
+ function resolveAttachReadyTimeoutMs() {
7287
+ const raw = Number.parseInt(process.env.RIG_PI_ATTACH_TIMEOUT_MS ?? "", 10);
7288
+ return Number.isFinite(raw) && raw > 0 ? raw : 10 * 60000;
7289
+ }
7290
+ function formatElapsed(sinceMs) {
7291
+ const totalSeconds = Math.floor((Date.now() - sinceMs) / 1000);
7292
+ const minutes = Math.floor(totalSeconds / 60);
7293
+ const seconds = totalSeconds % 60;
7294
+ return minutes > 0 ? `${minutes}m${String(seconds).padStart(2, "0")}s` : `${seconds}s`;
7295
+ }
7262
7296
  async function waitForWorkerReady(options, ctx, state) {
7297
+ const startedAt = Date.now();
7298
+ const deadline = startedAt + resolveAttachReadyTimeoutMs();
7299
+ let consecutiveFailures = 0;
7263
7300
  while (true) {
7264
- const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => ({
7265
- ready: false,
7266
- status: error instanceof Error ? error.message : String(error),
7267
- retryAfterMs: 1000
7268
- }));
7301
+ let requestFailed = false;
7302
+ const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => {
7303
+ requestFailed = true;
7304
+ return {
7305
+ ready: false,
7306
+ status: error instanceof Error ? error.message : String(error),
7307
+ retryAfterMs: 1000
7308
+ };
7309
+ });
7269
7310
  if (session.ready === false) {
7311
+ consecutiveFailures = requestFailed ? consecutiveFailures + 1 : 0;
7270
7312
  const status = String(session.status ?? "starting");
7271
- state.status = `waiting for worker Pi daemon \xB7 ${status}`;
7272
- updatePiUi(ctx, state);
7273
7313
  if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
7274
- appendTranscript(state, "Error", `Run ended before worker Pi daemon became ready: ${status}`);
7314
+ reportBridgeError(ctx, state, `Run ended before worker Pi daemon became ready: ${status}. Inspect with \`rig run show ${options.runId}\`; restart with \`rig task run --task <id>\`.`);
7315
+ return false;
7316
+ }
7317
+ if (consecutiveFailures >= 5) {
7318
+ state.status = `Rig server unreachable \xB7 retrying (${formatElapsed(startedAt)}) \xB7 /detach to exit`;
7319
+ } else {
7320
+ state.status = `waiting for worker Pi daemon \xB7 ${status} \xB7 ${formatElapsed(startedAt)} \xB7 /detach to exit`;
7321
+ }
7322
+ updatePiUi(ctx, state);
7323
+ if (Date.now() >= deadline) {
7324
+ reportBridgeError(ctx, state, `Worker Pi daemon did not become ready within ${formatElapsed(startedAt)} (last status: ${status}). ` + `Check \`rig run show ${options.runId}\` and \`rig doctor\`; the run may have been restarted by a server deploy. ` + "Set RIG_PI_ATTACH_TIMEOUT_MS to wait longer.");
7275
7325
  return false;
7276
7326
  }
7277
7327
  await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
@@ -7479,7 +7529,7 @@ function createRigWorkerPiBridgeExtension(options) {
7479
7529
  nativePiUiContextAvailable = Boolean(nativePiUi(ctx));
7480
7530
  state.nativeStream = nativePiUiContextAvailable;
7481
7531
  updatePiUi(ctx, state);
7482
- ctx.ui.notify(nativePiUiContextAvailable ? "Rig worker Pi native stream bridge loaded" : "Rig worker Pi bridge extension loaded (degraded widget fallback)", "info");
7532
+ ctx.ui.notify(nativePiUiContextAvailable ? "Connected to the Rig worker session (native stream). /detach exits, /stop cancels the run." : "Connected to the Rig worker session (transcript view). /detach exits, /stop cancels the run.", "info");
7483
7533
  ctx.ui.onTerminalInput((data) => {
7484
7534
  if (data.includes("\x04")) {
7485
7535
  ctx.shutdown();
package/dist/src/index.js CHANGED
@@ -7116,7 +7116,23 @@ function createPiRunStreamRenderer(output = process.stdout) {
7116
7116
  }
7117
7117
  if (entry.type === "timeline_warning") {
7118
7118
  writeLine(`[Rig timeline] ${String(entry.detail ?? entry.message ?? "timeline unavailable")}`);
7119
+ continue;
7120
+ }
7121
+ if (entry.type === "action") {
7122
+ const text2 = String(entry.detail ?? entry.message ?? entry.title ?? "").trim();
7123
+ if (text2)
7124
+ writeLine(`[Rig action] ${text2}`);
7125
+ continue;
7119
7126
  }
7127
+ if (entry.type === "user_message") {
7128
+ const text2 = String(entry.text ?? entry.message ?? entry.detail ?? "").trim();
7129
+ if (text2)
7130
+ writeLine(`[Operator] ${text2}`);
7131
+ continue;
7132
+ }
7133
+ const fallback = String(entry.detail ?? entry.message ?? entry.text ?? entry.title ?? "").trim();
7134
+ if (fallback)
7135
+ writeLine(`[${String(entry.type ?? "timeline")}] ${fallback}`);
7120
7136
  }
7121
7137
  },
7122
7138
  renderLogs(entries) {
@@ -7269,12 +7285,12 @@ function parseExtensionUiRequest(value) {
7269
7285
  }
7270
7286
  function renderBridgeWidget(state) {
7271
7287
  const statusParts = [
7272
- state.wsConnected ? "live WS" : "WS pending",
7288
+ state.wsConnected ? "live" : "connecting",
7273
7289
  state.status,
7274
7290
  state.model,
7275
7291
  state.cwd
7276
7292
  ].filter(Boolean);
7277
- const lines = [`Worker Pi daemon bridge \xB7 ${statusParts.join(" \xB7 ")}`];
7293
+ const lines = [`Rig worker session \xB7 ${statusParts.join(" \xB7 ")}`];
7278
7294
  if (state.activity)
7279
7295
  lines.push(state.activity);
7280
7296
  if (state.commands.length > 0) {
@@ -7284,27 +7300,35 @@ function renderBridgeWidget(state) {
7284
7300
  if (state.transcript.length > 0) {
7285
7301
  lines.push(...state.transcript.slice(-MAX_TRANSCRIPT_LINES));
7286
7302
  } else {
7287
- lines.push("Waiting for worker Pi daemon transcript\u2026");
7303
+ lines.push("Waiting for the worker session transcript\u2026 (/detach exits and leaves the worker running)");
7288
7304
  }
7289
7305
  if (state.pendingUi) {
7290
7306
  lines.push("");
7291
- lines.push(`Extension UI request \xB7 ${state.pendingUi.method}`);
7307
+ lines.push(`Worker needs input \xB7 ${state.pendingUi.method}`);
7292
7308
  lines.push(state.pendingUi.prompt);
7293
7309
  state.pendingUi.options.forEach((option, index) => lines.push(`${index + 1}. ${option}`));
7294
- lines.push("Reply in the Pi editor. /cancel cancels this request.");
7310
+ lines.push("Reply in the editor below. /cancel dismisses this request.");
7295
7311
  }
7296
7312
  return lines;
7297
7313
  }
7314
+ function reportBridgeError(ctx, state, message2) {
7315
+ appendTranscript(state, "Error", message2);
7316
+ state.status = message2;
7317
+ try {
7318
+ ctx.ui.notify(message2, "error");
7319
+ } catch {}
7320
+ updatePiUi(ctx, state);
7321
+ }
7298
7322
  function updatePiUi(ctx, state) {
7299
- ctx.ui.setTitle("Pi \xB7 Rig worker daemon");
7300
- ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker Pi WS live" : state.status);
7323
+ ctx.ui.setTitle("Rig \xB7 worker session");
7324
+ ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker session live" : state.status);
7301
7325
  syncNativeDisplayCwd(ctx, state);
7302
7326
  if (state.nativeStream && nativePiUi(ctx)) {
7303
7327
  ctx.ui.setWidget("rig-worker-pi-transcript", undefined);
7304
7328
  return;
7305
7329
  }
7306
7330
  ctx.ui.setWorkingVisible(false);
7307
- ctx.ui.setWidget("rig-worker-pi-transcript", [`Worker Pi daemon bridge \xB7 degraded widget transcript`, ...renderBridgeWidget(state)], { placement: "aboveEditor" });
7331
+ ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
7308
7332
  }
7309
7333
  function applyStatus(state, payload) {
7310
7334
  const status = recordOf(payload.status) ?? payload;
@@ -7449,19 +7473,45 @@ function applyEnvelope(ctx, state, envelopeValue) {
7449
7473
  }
7450
7474
  syncNativeDisplayCwd(ctx, state);
7451
7475
  }
7476
+ function resolveAttachReadyTimeoutMs() {
7477
+ const raw = Number.parseInt(process.env.RIG_PI_ATTACH_TIMEOUT_MS ?? "", 10);
7478
+ return Number.isFinite(raw) && raw > 0 ? raw : 10 * 60000;
7479
+ }
7480
+ function formatElapsed(sinceMs) {
7481
+ const totalSeconds = Math.floor((Date.now() - sinceMs) / 1000);
7482
+ const minutes = Math.floor(totalSeconds / 60);
7483
+ const seconds = totalSeconds % 60;
7484
+ return minutes > 0 ? `${minutes}m${String(seconds).padStart(2, "0")}s` : `${seconds}s`;
7485
+ }
7452
7486
  async function waitForWorkerReady(options, ctx, state) {
7487
+ const startedAt = Date.now();
7488
+ const deadline = startedAt + resolveAttachReadyTimeoutMs();
7489
+ let consecutiveFailures = 0;
7453
7490
  while (true) {
7454
- const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => ({
7455
- ready: false,
7456
- status: error instanceof Error ? error.message : String(error),
7457
- retryAfterMs: 1000
7458
- }));
7491
+ let requestFailed = false;
7492
+ const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => {
7493
+ requestFailed = true;
7494
+ return {
7495
+ ready: false,
7496
+ status: error instanceof Error ? error.message : String(error),
7497
+ retryAfterMs: 1000
7498
+ };
7499
+ });
7459
7500
  if (session.ready === false) {
7501
+ consecutiveFailures = requestFailed ? consecutiveFailures + 1 : 0;
7460
7502
  const status = String(session.status ?? "starting");
7461
- state.status = `waiting for worker Pi daemon \xB7 ${status}`;
7462
- updatePiUi(ctx, state);
7463
7503
  if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
7464
- appendTranscript(state, "Error", `Run ended before worker Pi daemon became ready: ${status}`);
7504
+ reportBridgeError(ctx, state, `Run ended before worker Pi daemon became ready: ${status}. Inspect with \`rig run show ${options.runId}\`; restart with \`rig task run --task <id>\`.`);
7505
+ return false;
7506
+ }
7507
+ if (consecutiveFailures >= 5) {
7508
+ state.status = `Rig server unreachable \xB7 retrying (${formatElapsed(startedAt)}) \xB7 /detach to exit`;
7509
+ } else {
7510
+ state.status = `waiting for worker Pi daemon \xB7 ${status} \xB7 ${formatElapsed(startedAt)} \xB7 /detach to exit`;
7511
+ }
7512
+ updatePiUi(ctx, state);
7513
+ if (Date.now() >= deadline) {
7514
+ reportBridgeError(ctx, state, `Worker Pi daemon did not become ready within ${formatElapsed(startedAt)} (last status: ${status}). ` + `Check \`rig run show ${options.runId}\` and \`rig doctor\`; the run may have been restarted by a server deploy. ` + "Set RIG_PI_ATTACH_TIMEOUT_MS to wait longer.");
7465
7515
  return false;
7466
7516
  }
7467
7517
  await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
@@ -7669,7 +7719,7 @@ function createRigWorkerPiBridgeExtension(options) {
7669
7719
  nativePiUiContextAvailable = Boolean(nativePiUi(ctx));
7670
7720
  state.nativeStream = nativePiUiContextAvailable;
7671
7721
  updatePiUi(ctx, state);
7672
- ctx.ui.notify(nativePiUiContextAvailable ? "Rig worker Pi native stream bridge loaded" : "Rig worker Pi bridge extension loaded (degraded widget fallback)", "info");
7722
+ ctx.ui.notify(nativePiUiContextAvailable ? "Connected to the Rig worker session (native stream). /detach exits, /stop cancels the run." : "Connected to the Rig worker session (transcript view). /detach exits, /stop cancels the run.", "info");
7673
7723
  ctx.ui.onTerminalInput((data) => {
7674
7724
  if (data.includes("\x04")) {
7675
7725
  ctx.shutdown();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h-rig/cli",
3
- "version": "0.0.6-alpha.32",
3
+ "version": "0.0.6-alpha.34",
4
4
  "type": "module",
5
5
  "description": "Rig package",
6
6
  "license": "UNLICENSED",
@@ -23,11 +23,11 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@clack/prompts": "^1.2.0",
26
- "@earendil-works/pi-coding-agent": "npm:@h-rig/pi-coding-agent@0.0.6-alpha.32",
27
- "@rig/core": "npm:@h-rig/core@0.0.6-alpha.32",
28
- "@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.32",
29
- "@rig/client": "npm:@h-rig/client@0.0.6-alpha.32",
30
- "@rig/server": "npm:@h-rig/server@0.0.6-alpha.32",
26
+ "@earendil-works/pi-coding-agent": "npm:@h-rig/pi-coding-agent@0.0.6-alpha.34",
27
+ "@rig/core": "npm:@h-rig/core@0.0.6-alpha.34",
28
+ "@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.34",
29
+ "@rig/client": "npm:@h-rig/client@0.0.6-alpha.34",
30
+ "@rig/server": "npm:@h-rig/server@0.0.6-alpha.34",
31
31
  "picocolors": "^1.1.1"
32
32
  }
33
33
  }