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

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
@@ -7273,12 +7273,12 @@ function parseExtensionUiRequest(value) {
7273
7273
  }
7274
7274
  function renderBridgeWidget(state) {
7275
7275
  const statusParts = [
7276
- state.wsConnected ? "live WS" : "WS pending",
7276
+ state.wsConnected ? "live" : "connecting",
7277
7277
  state.status,
7278
7278
  state.model,
7279
7279
  state.cwd
7280
7280
  ].filter(Boolean);
7281
- const lines = [`Worker Pi daemon bridge \xB7 ${statusParts.join(" \xB7 ")}`];
7281
+ const lines = [`Rig worker session \xB7 ${statusParts.join(" \xB7 ")}`];
7282
7282
  if (state.activity)
7283
7283
  lines.push(state.activity);
7284
7284
  if (state.commands.length > 0) {
@@ -7288,27 +7288,35 @@ function renderBridgeWidget(state) {
7288
7288
  if (state.transcript.length > 0) {
7289
7289
  lines.push(...state.transcript.slice(-MAX_TRANSCRIPT_LINES));
7290
7290
  } else {
7291
- lines.push("Waiting for worker Pi daemon transcript\u2026");
7291
+ lines.push("Waiting for the worker session transcript\u2026 (/detach exits and leaves the worker running)");
7292
7292
  }
7293
7293
  if (state.pendingUi) {
7294
7294
  lines.push("");
7295
- lines.push(`Extension UI request \xB7 ${state.pendingUi.method}`);
7295
+ lines.push(`Worker needs input \xB7 ${state.pendingUi.method}`);
7296
7296
  lines.push(state.pendingUi.prompt);
7297
7297
  state.pendingUi.options.forEach((option, index) => lines.push(`${index + 1}. ${option}`));
7298
- lines.push("Reply in the Pi editor. /cancel cancels this request.");
7298
+ lines.push("Reply in the editor below. /cancel dismisses this request.");
7299
7299
  }
7300
7300
  return lines;
7301
7301
  }
7302
+ function reportBridgeError(ctx, state, message2) {
7303
+ appendTranscript(state, "Error", message2);
7304
+ state.status = message2;
7305
+ try {
7306
+ ctx.ui.notify(message2, "error");
7307
+ } catch {}
7308
+ updatePiUi(ctx, state);
7309
+ }
7302
7310
  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);
7311
+ ctx.ui.setTitle("Rig \xB7 worker session");
7312
+ ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker session live" : state.status);
7305
7313
  syncNativeDisplayCwd(ctx, state);
7306
7314
  if (state.nativeStream && nativePiUi(ctx)) {
7307
7315
  ctx.ui.setWidget("rig-worker-pi-transcript", undefined);
7308
7316
  return;
7309
7317
  }
7310
7318
  ctx.ui.setWorkingVisible(false);
7311
- ctx.ui.setWidget("rig-worker-pi-transcript", [`Worker Pi daemon bridge \xB7 degraded widget transcript`, ...renderBridgeWidget(state)], { placement: "aboveEditor" });
7319
+ ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
7312
7320
  }
7313
7321
  function applyStatus(state, payload) {
7314
7322
  const status = recordOf(payload.status) ?? payload;
@@ -7453,19 +7461,45 @@ function applyEnvelope(ctx, state, envelopeValue) {
7453
7461
  }
7454
7462
  syncNativeDisplayCwd(ctx, state);
7455
7463
  }
7464
+ function resolveAttachReadyTimeoutMs() {
7465
+ const raw = Number.parseInt(process.env.RIG_PI_ATTACH_TIMEOUT_MS ?? "", 10);
7466
+ return Number.isFinite(raw) && raw > 0 ? raw : 10 * 60000;
7467
+ }
7468
+ function formatElapsed(sinceMs) {
7469
+ const totalSeconds = Math.floor((Date.now() - sinceMs) / 1000);
7470
+ const minutes = Math.floor(totalSeconds / 60);
7471
+ const seconds = totalSeconds % 60;
7472
+ return minutes > 0 ? `${minutes}m${String(seconds).padStart(2, "0")}s` : `${seconds}s`;
7473
+ }
7456
7474
  async function waitForWorkerReady(options, ctx, state) {
7475
+ const startedAt = Date.now();
7476
+ const deadline = startedAt + resolveAttachReadyTimeoutMs();
7477
+ let consecutiveFailures = 0;
7457
7478
  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
- }));
7479
+ let requestFailed = false;
7480
+ const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => {
7481
+ requestFailed = true;
7482
+ return {
7483
+ ready: false,
7484
+ status: error instanceof Error ? error.message : String(error),
7485
+ retryAfterMs: 1000
7486
+ };
7487
+ });
7463
7488
  if (session.ready === false) {
7489
+ consecutiveFailures = requestFailed ? consecutiveFailures + 1 : 0;
7464
7490
  const status = String(session.status ?? "starting");
7465
- state.status = `waiting for worker Pi daemon \xB7 ${status}`;
7466
- updatePiUi(ctx, state);
7467
7491
  if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
7468
- appendTranscript(state, "Error", `Run ended before worker Pi daemon became ready: ${status}`);
7492
+ 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>\`.`);
7493
+ return false;
7494
+ }
7495
+ if (consecutiveFailures >= 5) {
7496
+ state.status = `Rig server unreachable \xB7 retrying (${formatElapsed(startedAt)}) \xB7 /detach to exit`;
7497
+ } else {
7498
+ state.status = `waiting for worker Pi daemon \xB7 ${status} \xB7 ${formatElapsed(startedAt)} \xB7 /detach to exit`;
7499
+ }
7500
+ updatePiUi(ctx, state);
7501
+ if (Date.now() >= deadline) {
7502
+ 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
7503
  return false;
7470
7504
  }
7471
7505
  await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
@@ -7673,7 +7707,7 @@ function createRigWorkerPiBridgeExtension(options) {
7673
7707
  nativePiUiContextAvailable = Boolean(nativePiUi(ctx));
7674
7708
  state.nativeStream = nativePiUiContextAvailable;
7675
7709
  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");
7710
+ 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
7711
  ctx.ui.onTerminalInput((data) => {
7678
7712
  if (data.includes("\x04")) {
7679
7713
  ctx.shutdown();
@@ -543,12 +543,12 @@ function parseExtensionUiRequest(value) {
543
543
  }
544
544
  function renderBridgeWidget(state) {
545
545
  const statusParts = [
546
- state.wsConnected ? "live WS" : "WS pending",
546
+ state.wsConnected ? "live" : "connecting",
547
547
  state.status,
548
548
  state.model,
549
549
  state.cwd
550
550
  ].filter(Boolean);
551
- const lines = [`Worker Pi daemon bridge \xB7 ${statusParts.join(" \xB7 ")}`];
551
+ const lines = [`Rig worker session \xB7 ${statusParts.join(" \xB7 ")}`];
552
552
  if (state.activity)
553
553
  lines.push(state.activity);
554
554
  if (state.commands.length > 0) {
@@ -558,27 +558,35 @@ function renderBridgeWidget(state) {
558
558
  if (state.transcript.length > 0) {
559
559
  lines.push(...state.transcript.slice(-MAX_TRANSCRIPT_LINES));
560
560
  } else {
561
- lines.push("Waiting for worker Pi daemon transcript\u2026");
561
+ lines.push("Waiting for the worker session transcript\u2026 (/detach exits and leaves the worker running)");
562
562
  }
563
563
  if (state.pendingUi) {
564
564
  lines.push("");
565
- lines.push(`Extension UI request \xB7 ${state.pendingUi.method}`);
565
+ lines.push(`Worker needs input \xB7 ${state.pendingUi.method}`);
566
566
  lines.push(state.pendingUi.prompt);
567
567
  state.pendingUi.options.forEach((option, index) => lines.push(`${index + 1}. ${option}`));
568
- lines.push("Reply in the Pi editor. /cancel cancels this request.");
568
+ lines.push("Reply in the editor below. /cancel dismisses this request.");
569
569
  }
570
570
  return lines;
571
571
  }
572
+ function reportBridgeError(ctx, state, message) {
573
+ appendTranscript(state, "Error", message);
574
+ state.status = message;
575
+ try {
576
+ ctx.ui.notify(message, "error");
577
+ } catch {}
578
+ updatePiUi(ctx, state);
579
+ }
572
580
  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);
581
+ ctx.ui.setTitle("Rig \xB7 worker session");
582
+ ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker session live" : state.status);
575
583
  syncNativeDisplayCwd(ctx, state);
576
584
  if (state.nativeStream && nativePiUi(ctx)) {
577
585
  ctx.ui.setWidget("rig-worker-pi-transcript", undefined);
578
586
  return;
579
587
  }
580
588
  ctx.ui.setWorkingVisible(false);
581
- ctx.ui.setWidget("rig-worker-pi-transcript", [`Worker Pi daemon bridge \xB7 degraded widget transcript`, ...renderBridgeWidget(state)], { placement: "aboveEditor" });
589
+ ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
582
590
  }
583
591
  function applyStatus(state, payload) {
584
592
  const status = recordOf(payload.status) ?? payload;
@@ -723,19 +731,45 @@ function applyEnvelope(ctx, state, envelopeValue) {
723
731
  }
724
732
  syncNativeDisplayCwd(ctx, state);
725
733
  }
734
+ function resolveAttachReadyTimeoutMs() {
735
+ const raw = Number.parseInt(process.env.RIG_PI_ATTACH_TIMEOUT_MS ?? "", 10);
736
+ return Number.isFinite(raw) && raw > 0 ? raw : 10 * 60000;
737
+ }
738
+ function formatElapsed(sinceMs) {
739
+ const totalSeconds = Math.floor((Date.now() - sinceMs) / 1000);
740
+ const minutes = Math.floor(totalSeconds / 60);
741
+ const seconds = totalSeconds % 60;
742
+ return minutes > 0 ? `${minutes}m${String(seconds).padStart(2, "0")}s` : `${seconds}s`;
743
+ }
726
744
  async function waitForWorkerReady(options, ctx, state) {
745
+ const startedAt = Date.now();
746
+ const deadline = startedAt + resolveAttachReadyTimeoutMs();
747
+ let consecutiveFailures = 0;
727
748
  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
- }));
749
+ let requestFailed = false;
750
+ const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => {
751
+ requestFailed = true;
752
+ return {
753
+ ready: false,
754
+ status: error instanceof Error ? error.message : String(error),
755
+ retryAfterMs: 1000
756
+ };
757
+ });
733
758
  if (session.ready === false) {
759
+ consecutiveFailures = requestFailed ? consecutiveFailures + 1 : 0;
734
760
  const status = String(session.status ?? "starting");
735
- state.status = `waiting for worker Pi daemon \xB7 ${status}`;
736
- updatePiUi(ctx, state);
737
761
  if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
738
- appendTranscript(state, "Error", `Run ended before worker Pi daemon became ready: ${status}`);
762
+ 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>\`.`);
763
+ return false;
764
+ }
765
+ if (consecutiveFailures >= 5) {
766
+ state.status = `Rig server unreachable \xB7 retrying (${formatElapsed(startedAt)}) \xB7 /detach to exit`;
767
+ } else {
768
+ state.status = `waiting for worker Pi daemon \xB7 ${status} \xB7 ${formatElapsed(startedAt)} \xB7 /detach to exit`;
769
+ }
770
+ updatePiUi(ctx, state);
771
+ if (Date.now() >= deadline) {
772
+ 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
773
  return false;
740
774
  }
741
775
  await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
@@ -943,7 +977,7 @@ function createRigWorkerPiBridgeExtension(options) {
943
977
  nativePiUiContextAvailable = Boolean(nativePiUi(ctx));
944
978
  state.nativeStream = nativePiUiContextAvailable;
945
979
  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");
980
+ 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
981
  ctx.ui.onTerminalInput((data) => {
948
982
  if (data.includes("\x04")) {
949
983
  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();
@@ -621,12 +621,12 @@ function parseExtensionUiRequest(value) {
621
621
  }
622
622
  function renderBridgeWidget(state) {
623
623
  const statusParts = [
624
- state.wsConnected ? "live WS" : "WS pending",
624
+ state.wsConnected ? "live" : "connecting",
625
625
  state.status,
626
626
  state.model,
627
627
  state.cwd
628
628
  ].filter(Boolean);
629
- const lines = [`Worker Pi daemon bridge \xB7 ${statusParts.join(" \xB7 ")}`];
629
+ const lines = [`Rig worker session \xB7 ${statusParts.join(" \xB7 ")}`];
630
630
  if (state.activity)
631
631
  lines.push(state.activity);
632
632
  if (state.commands.length > 0) {
@@ -636,27 +636,35 @@ function renderBridgeWidget(state) {
636
636
  if (state.transcript.length > 0) {
637
637
  lines.push(...state.transcript.slice(-MAX_TRANSCRIPT_LINES));
638
638
  } else {
639
- lines.push("Waiting for worker Pi daemon transcript\u2026");
639
+ lines.push("Waiting for the worker session transcript\u2026 (/detach exits and leaves the worker running)");
640
640
  }
641
641
  if (state.pendingUi) {
642
642
  lines.push("");
643
- lines.push(`Extension UI request \xB7 ${state.pendingUi.method}`);
643
+ lines.push(`Worker needs input \xB7 ${state.pendingUi.method}`);
644
644
  lines.push(state.pendingUi.prompt);
645
645
  state.pendingUi.options.forEach((option, index) => lines.push(`${index + 1}. ${option}`));
646
- lines.push("Reply in the Pi editor. /cancel cancels this request.");
646
+ lines.push("Reply in the editor below. /cancel dismisses this request.");
647
647
  }
648
648
  return lines;
649
649
  }
650
+ function reportBridgeError(ctx, state, message) {
651
+ appendTranscript(state, "Error", message);
652
+ state.status = message;
653
+ try {
654
+ ctx.ui.notify(message, "error");
655
+ } catch {}
656
+ updatePiUi(ctx, state);
657
+ }
650
658
  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);
659
+ ctx.ui.setTitle("Rig \xB7 worker session");
660
+ ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker session live" : state.status);
653
661
  syncNativeDisplayCwd(ctx, state);
654
662
  if (state.nativeStream && nativePiUi(ctx)) {
655
663
  ctx.ui.setWidget("rig-worker-pi-transcript", undefined);
656
664
  return;
657
665
  }
658
666
  ctx.ui.setWorkingVisible(false);
659
- ctx.ui.setWidget("rig-worker-pi-transcript", [`Worker Pi daemon bridge \xB7 degraded widget transcript`, ...renderBridgeWidget(state)], { placement: "aboveEditor" });
667
+ ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
660
668
  }
661
669
  function applyStatus(state, payload) {
662
670
  const status = recordOf(payload.status) ?? payload;
@@ -801,19 +809,45 @@ function applyEnvelope(ctx, state, envelopeValue) {
801
809
  }
802
810
  syncNativeDisplayCwd(ctx, state);
803
811
  }
812
+ function resolveAttachReadyTimeoutMs() {
813
+ const raw = Number.parseInt(process.env.RIG_PI_ATTACH_TIMEOUT_MS ?? "", 10);
814
+ return Number.isFinite(raw) && raw > 0 ? raw : 10 * 60000;
815
+ }
816
+ function formatElapsed(sinceMs) {
817
+ const totalSeconds = Math.floor((Date.now() - sinceMs) / 1000);
818
+ const minutes = Math.floor(totalSeconds / 60);
819
+ const seconds = totalSeconds % 60;
820
+ return minutes > 0 ? `${minutes}m${String(seconds).padStart(2, "0")}s` : `${seconds}s`;
821
+ }
804
822
  async function waitForWorkerReady(options, ctx, state) {
823
+ const startedAt = Date.now();
824
+ const deadline = startedAt + resolveAttachReadyTimeoutMs();
825
+ let consecutiveFailures = 0;
805
826
  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
- }));
827
+ let requestFailed = false;
828
+ const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => {
829
+ requestFailed = true;
830
+ return {
831
+ ready: false,
832
+ status: error instanceof Error ? error.message : String(error),
833
+ retryAfterMs: 1000
834
+ };
835
+ });
811
836
  if (session.ready === false) {
837
+ consecutiveFailures = requestFailed ? consecutiveFailures + 1 : 0;
812
838
  const status = String(session.status ?? "starting");
813
- state.status = `waiting for worker Pi daemon \xB7 ${status}`;
814
- updatePiUi(ctx, state);
815
839
  if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
816
- appendTranscript(state, "Error", `Run ended before worker Pi daemon became ready: ${status}`);
840
+ 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>\`.`);
841
+ return false;
842
+ }
843
+ if (consecutiveFailures >= 5) {
844
+ state.status = `Rig server unreachable \xB7 retrying (${formatElapsed(startedAt)}) \xB7 /detach to exit`;
845
+ } else {
846
+ state.status = `waiting for worker Pi daemon \xB7 ${status} \xB7 ${formatElapsed(startedAt)} \xB7 /detach to exit`;
847
+ }
848
+ updatePiUi(ctx, state);
849
+ if (Date.now() >= deadline) {
850
+ 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
851
  return false;
818
852
  }
819
853
  await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
@@ -1021,7 +1055,7 @@ function createRigWorkerPiBridgeExtension(options) {
1021
1055
  nativePiUiContextAvailable = Boolean(nativePiUi(ctx));
1022
1056
  state.nativeStream = nativePiUiContextAvailable;
1023
1057
  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");
1058
+ 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
1059
  ctx.ui.onTerminalInput((data) => {
1026
1060
  if (data.includes("\x04")) {
1027
1061
  ctx.shutdown();
@@ -1024,12 +1024,12 @@ function parseExtensionUiRequest(value) {
1024
1024
  }
1025
1025
  function renderBridgeWidget(state) {
1026
1026
  const statusParts = [
1027
- state.wsConnected ? "live WS" : "WS pending",
1027
+ state.wsConnected ? "live" : "connecting",
1028
1028
  state.status,
1029
1029
  state.model,
1030
1030
  state.cwd
1031
1031
  ].filter(Boolean);
1032
- const lines = [`Worker Pi daemon bridge \xB7 ${statusParts.join(" \xB7 ")}`];
1032
+ const lines = [`Rig worker session \xB7 ${statusParts.join(" \xB7 ")}`];
1033
1033
  if (state.activity)
1034
1034
  lines.push(state.activity);
1035
1035
  if (state.commands.length > 0) {
@@ -1039,27 +1039,35 @@ function renderBridgeWidget(state) {
1039
1039
  if (state.transcript.length > 0) {
1040
1040
  lines.push(...state.transcript.slice(-MAX_TRANSCRIPT_LINES));
1041
1041
  } else {
1042
- lines.push("Waiting for worker Pi daemon transcript\u2026");
1042
+ lines.push("Waiting for the worker session transcript\u2026 (/detach exits and leaves the worker running)");
1043
1043
  }
1044
1044
  if (state.pendingUi) {
1045
1045
  lines.push("");
1046
- lines.push(`Extension UI request \xB7 ${state.pendingUi.method}`);
1046
+ lines.push(`Worker needs input \xB7 ${state.pendingUi.method}`);
1047
1047
  lines.push(state.pendingUi.prompt);
1048
1048
  state.pendingUi.options.forEach((option, index) => lines.push(`${index + 1}. ${option}`));
1049
- lines.push("Reply in the Pi editor. /cancel cancels this request.");
1049
+ lines.push("Reply in the editor below. /cancel dismisses this request.");
1050
1050
  }
1051
1051
  return lines;
1052
1052
  }
1053
+ function reportBridgeError(ctx, state, message2) {
1054
+ appendTranscript(state, "Error", message2);
1055
+ state.status = message2;
1056
+ try {
1057
+ ctx.ui.notify(message2, "error");
1058
+ } catch {}
1059
+ updatePiUi(ctx, state);
1060
+ }
1053
1061
  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);
1062
+ ctx.ui.setTitle("Rig \xB7 worker session");
1063
+ ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker session live" : state.status);
1056
1064
  syncNativeDisplayCwd(ctx, state);
1057
1065
  if (state.nativeStream && nativePiUi(ctx)) {
1058
1066
  ctx.ui.setWidget("rig-worker-pi-transcript", undefined);
1059
1067
  return;
1060
1068
  }
1061
1069
  ctx.ui.setWorkingVisible(false);
1062
- ctx.ui.setWidget("rig-worker-pi-transcript", [`Worker Pi daemon bridge \xB7 degraded widget transcript`, ...renderBridgeWidget(state)], { placement: "aboveEditor" });
1070
+ ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
1063
1071
  }
1064
1072
  function applyStatus(state, payload) {
1065
1073
  const status = recordOf(payload.status) ?? payload;
@@ -1204,19 +1212,45 @@ function applyEnvelope(ctx, state, envelopeValue) {
1204
1212
  }
1205
1213
  syncNativeDisplayCwd(ctx, state);
1206
1214
  }
1215
+ function resolveAttachReadyTimeoutMs() {
1216
+ const raw = Number.parseInt(process.env.RIG_PI_ATTACH_TIMEOUT_MS ?? "", 10);
1217
+ return Number.isFinite(raw) && raw > 0 ? raw : 10 * 60000;
1218
+ }
1219
+ function formatElapsed(sinceMs) {
1220
+ const totalSeconds = Math.floor((Date.now() - sinceMs) / 1000);
1221
+ const minutes = Math.floor(totalSeconds / 60);
1222
+ const seconds = totalSeconds % 60;
1223
+ return minutes > 0 ? `${minutes}m${String(seconds).padStart(2, "0")}s` : `${seconds}s`;
1224
+ }
1207
1225
  async function waitForWorkerReady(options, ctx, state) {
1226
+ const startedAt = Date.now();
1227
+ const deadline = startedAt + resolveAttachReadyTimeoutMs();
1228
+ let consecutiveFailures = 0;
1208
1229
  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
- }));
1230
+ let requestFailed = false;
1231
+ const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => {
1232
+ requestFailed = true;
1233
+ return {
1234
+ ready: false,
1235
+ status: error instanceof Error ? error.message : String(error),
1236
+ retryAfterMs: 1000
1237
+ };
1238
+ });
1214
1239
  if (session.ready === false) {
1240
+ consecutiveFailures = requestFailed ? consecutiveFailures + 1 : 0;
1215
1241
  const status = String(session.status ?? "starting");
1216
- state.status = `waiting for worker Pi daemon \xB7 ${status}`;
1217
- updatePiUi(ctx, state);
1218
1242
  if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
1219
- appendTranscript(state, "Error", `Run ended before worker Pi daemon became ready: ${status}`);
1243
+ 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>\`.`);
1244
+ return false;
1245
+ }
1246
+ if (consecutiveFailures >= 5) {
1247
+ state.status = `Rig server unreachable \xB7 retrying (${formatElapsed(startedAt)}) \xB7 /detach to exit`;
1248
+ } else {
1249
+ state.status = `waiting for worker Pi daemon \xB7 ${status} \xB7 ${formatElapsed(startedAt)} \xB7 /detach to exit`;
1250
+ }
1251
+ updatePiUi(ctx, state);
1252
+ if (Date.now() >= deadline) {
1253
+ 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
1254
  return false;
1221
1255
  }
1222
1256
  await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
@@ -1424,7 +1458,7 @@ function createRigWorkerPiBridgeExtension(options) {
1424
1458
  nativePiUiContextAvailable = Boolean(nativePiUi(ctx));
1425
1459
  state.nativeStream = nativePiUiContextAvailable;
1426
1460
  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");
1461
+ 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
1462
  ctx.ui.onTerminalInput((data) => {
1429
1463
  if (data.includes("\x04")) {
1430
1464
  ctx.shutdown();
@@ -7079,12 +7079,12 @@ function parseExtensionUiRequest(value) {
7079
7079
  }
7080
7080
  function renderBridgeWidget(state) {
7081
7081
  const statusParts = [
7082
- state.wsConnected ? "live WS" : "WS pending",
7082
+ state.wsConnected ? "live" : "connecting",
7083
7083
  state.status,
7084
7084
  state.model,
7085
7085
  state.cwd
7086
7086
  ].filter(Boolean);
7087
- const lines = [`Worker Pi daemon bridge \xB7 ${statusParts.join(" \xB7 ")}`];
7087
+ const lines = [`Rig worker session \xB7 ${statusParts.join(" \xB7 ")}`];
7088
7088
  if (state.activity)
7089
7089
  lines.push(state.activity);
7090
7090
  if (state.commands.length > 0) {
@@ -7094,27 +7094,35 @@ function renderBridgeWidget(state) {
7094
7094
  if (state.transcript.length > 0) {
7095
7095
  lines.push(...state.transcript.slice(-MAX_TRANSCRIPT_LINES));
7096
7096
  } else {
7097
- lines.push("Waiting for worker Pi daemon transcript\u2026");
7097
+ lines.push("Waiting for the worker session transcript\u2026 (/detach exits and leaves the worker running)");
7098
7098
  }
7099
7099
  if (state.pendingUi) {
7100
7100
  lines.push("");
7101
- lines.push(`Extension UI request \xB7 ${state.pendingUi.method}`);
7101
+ lines.push(`Worker needs input \xB7 ${state.pendingUi.method}`);
7102
7102
  lines.push(state.pendingUi.prompt);
7103
7103
  state.pendingUi.options.forEach((option, index) => lines.push(`${index + 1}. ${option}`));
7104
- lines.push("Reply in the Pi editor. /cancel cancels this request.");
7104
+ lines.push("Reply in the editor below. /cancel dismisses this request.");
7105
7105
  }
7106
7106
  return lines;
7107
7107
  }
7108
+ function reportBridgeError(ctx, state, message2) {
7109
+ appendTranscript(state, "Error", message2);
7110
+ state.status = message2;
7111
+ try {
7112
+ ctx.ui.notify(message2, "error");
7113
+ } catch {}
7114
+ updatePiUi(ctx, state);
7115
+ }
7108
7116
  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);
7117
+ ctx.ui.setTitle("Rig \xB7 worker session");
7118
+ ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker session live" : state.status);
7111
7119
  syncNativeDisplayCwd(ctx, state);
7112
7120
  if (state.nativeStream && nativePiUi(ctx)) {
7113
7121
  ctx.ui.setWidget("rig-worker-pi-transcript", undefined);
7114
7122
  return;
7115
7123
  }
7116
7124
  ctx.ui.setWorkingVisible(false);
7117
- ctx.ui.setWidget("rig-worker-pi-transcript", [`Worker Pi daemon bridge \xB7 degraded widget transcript`, ...renderBridgeWidget(state)], { placement: "aboveEditor" });
7125
+ ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
7118
7126
  }
7119
7127
  function applyStatus(state, payload) {
7120
7128
  const status = recordOf(payload.status) ?? payload;
@@ -7259,19 +7267,45 @@ function applyEnvelope(ctx, state, envelopeValue) {
7259
7267
  }
7260
7268
  syncNativeDisplayCwd(ctx, state);
7261
7269
  }
7270
+ function resolveAttachReadyTimeoutMs() {
7271
+ const raw = Number.parseInt(process.env.RIG_PI_ATTACH_TIMEOUT_MS ?? "", 10);
7272
+ return Number.isFinite(raw) && raw > 0 ? raw : 10 * 60000;
7273
+ }
7274
+ function formatElapsed(sinceMs) {
7275
+ const totalSeconds = Math.floor((Date.now() - sinceMs) / 1000);
7276
+ const minutes = Math.floor(totalSeconds / 60);
7277
+ const seconds = totalSeconds % 60;
7278
+ return minutes > 0 ? `${minutes}m${String(seconds).padStart(2, "0")}s` : `${seconds}s`;
7279
+ }
7262
7280
  async function waitForWorkerReady(options, ctx, state) {
7281
+ const startedAt = Date.now();
7282
+ const deadline = startedAt + resolveAttachReadyTimeoutMs();
7283
+ let consecutiveFailures = 0;
7263
7284
  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
- }));
7285
+ let requestFailed = false;
7286
+ const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => {
7287
+ requestFailed = true;
7288
+ return {
7289
+ ready: false,
7290
+ status: error instanceof Error ? error.message : String(error),
7291
+ retryAfterMs: 1000
7292
+ };
7293
+ });
7269
7294
  if (session.ready === false) {
7295
+ consecutiveFailures = requestFailed ? consecutiveFailures + 1 : 0;
7270
7296
  const status = String(session.status ?? "starting");
7271
- state.status = `waiting for worker Pi daemon \xB7 ${status}`;
7272
- updatePiUi(ctx, state);
7273
7297
  if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
7274
- appendTranscript(state, "Error", `Run ended before worker Pi daemon became ready: ${status}`);
7298
+ 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>\`.`);
7299
+ return false;
7300
+ }
7301
+ if (consecutiveFailures >= 5) {
7302
+ state.status = `Rig server unreachable \xB7 retrying (${formatElapsed(startedAt)}) \xB7 /detach to exit`;
7303
+ } else {
7304
+ state.status = `waiting for worker Pi daemon \xB7 ${status} \xB7 ${formatElapsed(startedAt)} \xB7 /detach to exit`;
7305
+ }
7306
+ updatePiUi(ctx, state);
7307
+ if (Date.now() >= deadline) {
7308
+ 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
7309
  return false;
7276
7310
  }
7277
7311
  await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
@@ -7479,7 +7513,7 @@ function createRigWorkerPiBridgeExtension(options) {
7479
7513
  nativePiUiContextAvailable = Boolean(nativePiUi(ctx));
7480
7514
  state.nativeStream = nativePiUiContextAvailable;
7481
7515
  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");
7516
+ 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
7517
  ctx.ui.onTerminalInput((data) => {
7484
7518
  if (data.includes("\x04")) {
7485
7519
  ctx.shutdown();
package/dist/src/index.js CHANGED
@@ -7269,12 +7269,12 @@ function parseExtensionUiRequest(value) {
7269
7269
  }
7270
7270
  function renderBridgeWidget(state) {
7271
7271
  const statusParts = [
7272
- state.wsConnected ? "live WS" : "WS pending",
7272
+ state.wsConnected ? "live" : "connecting",
7273
7273
  state.status,
7274
7274
  state.model,
7275
7275
  state.cwd
7276
7276
  ].filter(Boolean);
7277
- const lines = [`Worker Pi daemon bridge \xB7 ${statusParts.join(" \xB7 ")}`];
7277
+ const lines = [`Rig worker session \xB7 ${statusParts.join(" \xB7 ")}`];
7278
7278
  if (state.activity)
7279
7279
  lines.push(state.activity);
7280
7280
  if (state.commands.length > 0) {
@@ -7284,27 +7284,35 @@ function renderBridgeWidget(state) {
7284
7284
  if (state.transcript.length > 0) {
7285
7285
  lines.push(...state.transcript.slice(-MAX_TRANSCRIPT_LINES));
7286
7286
  } else {
7287
- lines.push("Waiting for worker Pi daemon transcript\u2026");
7287
+ lines.push("Waiting for the worker session transcript\u2026 (/detach exits and leaves the worker running)");
7288
7288
  }
7289
7289
  if (state.pendingUi) {
7290
7290
  lines.push("");
7291
- lines.push(`Extension UI request \xB7 ${state.pendingUi.method}`);
7291
+ lines.push(`Worker needs input \xB7 ${state.pendingUi.method}`);
7292
7292
  lines.push(state.pendingUi.prompt);
7293
7293
  state.pendingUi.options.forEach((option, index) => lines.push(`${index + 1}. ${option}`));
7294
- lines.push("Reply in the Pi editor. /cancel cancels this request.");
7294
+ lines.push("Reply in the editor below. /cancel dismisses this request.");
7295
7295
  }
7296
7296
  return lines;
7297
7297
  }
7298
+ function reportBridgeError(ctx, state, message2) {
7299
+ appendTranscript(state, "Error", message2);
7300
+ state.status = message2;
7301
+ try {
7302
+ ctx.ui.notify(message2, "error");
7303
+ } catch {}
7304
+ updatePiUi(ctx, state);
7305
+ }
7298
7306
  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);
7307
+ ctx.ui.setTitle("Rig \xB7 worker session");
7308
+ ctx.ui.setStatus("rig-worker-pi", state.wsConnected ? "worker session live" : state.status);
7301
7309
  syncNativeDisplayCwd(ctx, state);
7302
7310
  if (state.nativeStream && nativePiUi(ctx)) {
7303
7311
  ctx.ui.setWidget("rig-worker-pi-transcript", undefined);
7304
7312
  return;
7305
7313
  }
7306
7314
  ctx.ui.setWorkingVisible(false);
7307
- ctx.ui.setWidget("rig-worker-pi-transcript", [`Worker Pi daemon bridge \xB7 degraded widget transcript`, ...renderBridgeWidget(state)], { placement: "aboveEditor" });
7315
+ ctx.ui.setWidget("rig-worker-pi-transcript", renderBridgeWidget(state), { placement: "aboveEditor" });
7308
7316
  }
7309
7317
  function applyStatus(state, payload) {
7310
7318
  const status = recordOf(payload.status) ?? payload;
@@ -7449,19 +7457,45 @@ function applyEnvelope(ctx, state, envelopeValue) {
7449
7457
  }
7450
7458
  syncNativeDisplayCwd(ctx, state);
7451
7459
  }
7460
+ function resolveAttachReadyTimeoutMs() {
7461
+ const raw = Number.parseInt(process.env.RIG_PI_ATTACH_TIMEOUT_MS ?? "", 10);
7462
+ return Number.isFinite(raw) && raw > 0 ? raw : 10 * 60000;
7463
+ }
7464
+ function formatElapsed(sinceMs) {
7465
+ const totalSeconds = Math.floor((Date.now() - sinceMs) / 1000);
7466
+ const minutes = Math.floor(totalSeconds / 60);
7467
+ const seconds = totalSeconds % 60;
7468
+ return minutes > 0 ? `${minutes}m${String(seconds).padStart(2, "0")}s` : `${seconds}s`;
7469
+ }
7452
7470
  async function waitForWorkerReady(options, ctx, state) {
7471
+ const startedAt = Date.now();
7472
+ const deadline = startedAt + resolveAttachReadyTimeoutMs();
7473
+ let consecutiveFailures = 0;
7453
7474
  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
- }));
7475
+ let requestFailed = false;
7476
+ const session = await getRunPiSessionViaServer(options.context, options.runId).catch((error) => {
7477
+ requestFailed = true;
7478
+ return {
7479
+ ready: false,
7480
+ status: error instanceof Error ? error.message : String(error),
7481
+ retryAfterMs: 1000
7482
+ };
7483
+ });
7459
7484
  if (session.ready === false) {
7485
+ consecutiveFailures = requestFailed ? consecutiveFailures + 1 : 0;
7460
7486
  const status = String(session.status ?? "starting");
7461
- state.status = `waiting for worker Pi daemon \xB7 ${status}`;
7462
- updatePiUi(ctx, state);
7463
7487
  if (TERMINAL_RUN_STATUSES.has(status.toLowerCase())) {
7464
- appendTranscript(state, "Error", `Run ended before worker Pi daemon became ready: ${status}`);
7488
+ 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>\`.`);
7489
+ return false;
7490
+ }
7491
+ if (consecutiveFailures >= 5) {
7492
+ state.status = `Rig server unreachable \xB7 retrying (${formatElapsed(startedAt)}) \xB7 /detach to exit`;
7493
+ } else {
7494
+ state.status = `waiting for worker Pi daemon \xB7 ${status} \xB7 ${formatElapsed(startedAt)} \xB7 /detach to exit`;
7495
+ }
7496
+ updatePiUi(ctx, state);
7497
+ if (Date.now() >= deadline) {
7498
+ 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
7499
  return false;
7466
7500
  }
7467
7501
  await Bun.sleep(typeof session.retryAfterMs === "number" ? session.retryAfterMs : 750);
@@ -7669,7 +7703,7 @@ function createRigWorkerPiBridgeExtension(options) {
7669
7703
  nativePiUiContextAvailable = Boolean(nativePiUi(ctx));
7670
7704
  state.nativeStream = nativePiUiContextAvailable;
7671
7705
  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");
7706
+ 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
7707
  ctx.ui.onTerminalInput((data) => {
7674
7708
  if (data.includes("\x04")) {
7675
7709
  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.33",
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.33",
27
+ "@rig/core": "npm:@h-rig/core@0.0.6-alpha.33",
28
+ "@rig/runtime": "npm:@h-rig/runtime@0.0.6-alpha.33",
29
+ "@rig/client": "npm:@h-rig/client@0.0.6-alpha.33",
30
+ "@rig/server": "npm:@h-rig/server@0.0.6-alpha.33",
31
31
  "picocolors": "^1.1.1"
32
32
  }
33
33
  }