@h-rig/cli 0.0.6-alpha.87 → 0.0.6-alpha.89

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.
Files changed (78) hide show
  1. package/dist/bin/rig.js +1380 -865
  2. package/dist/src/app/board.js +462 -48
  3. package/dist/src/app-opentui/adapters/common.d.ts +3 -0
  4. package/dist/src/app-opentui/adapters/common.js +4 -0
  5. package/dist/src/app-opentui/adapters/doctor.js +458 -46
  6. package/dist/src/app-opentui/adapters/family.js +701 -151
  7. package/dist/src/app-opentui/adapters/fleet.js +477 -46
  8. package/dist/src/app-opentui/adapters/inbox.js +477 -46
  9. package/dist/src/app-opentui/adapters/init.js +497 -74
  10. package/dist/src/app-opentui/adapters/inspect.js +477 -46
  11. package/dist/src/app-opentui/adapters/pi-attach.d.ts +7 -0
  12. package/dist/src/app-opentui/adapters/pi-attach.js +1004 -519
  13. package/dist/src/app-opentui/adapters/run-detail.js +477 -46
  14. package/dist/src/app-opentui/adapters/server.d.ts +26 -0
  15. package/dist/src/app-opentui/adapters/server.js +676 -59
  16. package/dist/src/app-opentui/adapters/tasks.js +621 -549
  17. package/dist/src/app-opentui/autocomplete.js +4 -2
  18. package/dist/src/app-opentui/bootstrap.js +1376 -861
  19. package/dist/src/app-opentui/command-palette.js +37 -10
  20. package/dist/src/app-opentui/index.js +632 -528
  21. package/dist/src/app-opentui/intent.js +33 -8
  22. package/dist/src/app-opentui/keymap.js +43 -414
  23. package/dist/src/app-opentui/pi-host-child.js +496 -57
  24. package/dist/src/app-opentui/pi-pty-host.d.ts +14 -64
  25. package/dist/src/app-opentui/pi-pty-host.js +3 -397
  26. package/dist/src/app-opentui/react/App.js +144 -469
  27. package/dist/src/app-opentui/react/ChromeHost.js +44 -415
  28. package/dist/src/app-opentui/react/launch.js +659 -552
  29. package/dist/src/app-opentui/react/nav.js +1 -1
  30. package/dist/src/app-opentui/registry.js +1181 -742
  31. package/dist/src/app-opentui/remote-link.d.ts +10 -0
  32. package/dist/src/app-opentui/remote-link.js +47 -0
  33. package/dist/src/app-opentui/render/terminal-handoff.d.ts +16 -0
  34. package/dist/src/app-opentui/render/terminal-handoff.js +14 -0
  35. package/dist/src/app-opentui/runtime.js +632 -528
  36. package/dist/src/app-opentui/scenes/doctor.js +1 -1
  37. package/dist/src/app-opentui/scenes/error.js +50 -4
  38. package/dist/src/app-opentui/scenes/family.js +60 -6
  39. package/dist/src/app-opentui/scenes/fleet.js +65 -13
  40. package/dist/src/app-opentui/scenes/help.js +4 -2
  41. package/dist/src/app-opentui/scenes/init.js +12 -12
  42. package/dist/src/app-opentui/scenes/main.js +7 -7
  43. package/dist/src/app-opentui/scenes/server.js +83 -11
  44. package/dist/src/app-opentui/scenes/tasks.js +79 -16
  45. package/dist/src/app-opentui/state.js +25 -5
  46. package/dist/src/app-opentui/surface-catalog.js +4 -2
  47. package/dist/src/app-opentui/types.d.ts +1 -1
  48. package/dist/src/commands/_cli-format.d.ts +10 -1
  49. package/dist/src/commands/_cli-format.js +5 -2
  50. package/dist/src/commands/_connection-state.d.ts +11 -1
  51. package/dist/src/commands/_connection-state.js +50 -5
  52. package/dist/src/commands/_doctor-checks.js +458 -46
  53. package/dist/src/commands/_help-catalog.js +4 -2
  54. package/dist/src/commands/_json-output.js +4 -0
  55. package/dist/src/commands/_operator-view.js +496 -57
  56. package/dist/src/commands/_pi-frontend.d.ts +25 -0
  57. package/dist/src/commands/_pi-frontend.js +497 -57
  58. package/dist/src/commands/_preflight.js +509 -72
  59. package/dist/src/commands/_server-client.d.ts +33 -0
  60. package/dist/src/commands/_server-client.js +477 -46
  61. package/dist/src/commands/_server-events.js +446 -41
  62. package/dist/src/commands/_snapshot-upload.js +460 -48
  63. package/dist/src/commands/connect.js +620 -15
  64. package/dist/src/commands/doctor.js +458 -46
  65. package/dist/src/commands/github.js +462 -50
  66. package/dist/src/commands/inbox.js +458 -46
  67. package/dist/src/commands/init.js +497 -74
  68. package/dist/src/commands/inspect.js +458 -46
  69. package/dist/src/commands/run.js +496 -57
  70. package/dist/src/commands/server.js +647 -163
  71. package/dist/src/commands/setup.js +463 -51
  72. package/dist/src/commands/stats.js +462 -48
  73. package/dist/src/commands/task-run-driver.js +464 -52
  74. package/dist/src/commands/task.js +551 -85
  75. package/dist/src/commands.js +701 -151
  76. package/dist/src/index.js +705 -151
  77. package/dist/src/launcher.js +4 -0
  78. package/package.json +8 -8
@@ -91,6 +91,21 @@ async function captureConsole(fn) {
91
91
  console.error = original.error;
92
92
  }
93
93
  }
94
+ async function releaseRendererForExternalTui(ctx) {
95
+ const renderer = ctx.renderer;
96
+ if (!renderer)
97
+ return;
98
+ if (renderer.suspend) {
99
+ await renderer.suspend();
100
+ return;
101
+ }
102
+ if (renderer.destroy) {
103
+ await renderer.destroy();
104
+ }
105
+ }
106
+ async function resumeRendererAfterExternalTui(ctx) {
107
+ await ctx.renderer?.resume?.();
108
+ }
94
109
  function arrayFromPayload(value) {
95
110
  if (Array.isArray(value)) {
96
111
  return value.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)));
@@ -200,7 +215,8 @@ __export(exports__connection_state, {
200
215
  resolveGlobalConnectionsPath: () => resolveGlobalConnectionsPath,
201
216
  readRepoConnection: () => readRepoConnection,
202
217
  readGlobalConnections: () => readGlobalConnections,
203
- isRemoteConnectionSelected: () => isRemoteConnectionSelected
218
+ isRemoteConnectionSelected: () => isRemoteConnectionSelected,
219
+ clearRepoServerProjectRoot: () => clearRepoServerProjectRoot
204
220
  });
205
221
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
206
222
  import { homedir } from "os";
@@ -284,12 +300,28 @@ function readRepoConnection(projectRoot) {
284
300
  selected,
285
301
  project: typeof record.project === "string" ? record.project : undefined,
286
302
  linkedAt: typeof record.linkedAt === "string" ? record.linkedAt : undefined,
287
- serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined
303
+ serverProjectRoot: typeof record.serverProjectRoot === "string" && record.serverProjectRoot.trim() ? record.serverProjectRoot.trim() : undefined,
304
+ serverProjectRootAlias: typeof record.serverProjectRootAlias === "string" && record.serverProjectRootAlias.trim() ? record.serverProjectRootAlias.trim() : undefined,
305
+ serverProjectRootBaseUrl: typeof record.serverProjectRootBaseUrl === "string" && record.serverProjectRootBaseUrl.trim() ? record.serverProjectRootBaseUrl.trim().replace(/\/+$/, "") : undefined
288
306
  };
289
307
  }
290
308
  function writeRepoConnection(projectRoot, state) {
291
309
  writeJsonFile(resolveRepoConnectionPath(projectRoot), state);
292
310
  }
311
+ function rootAllowedForSelection(repo, connection) {
312
+ const root = repo.serverProjectRoot?.trim();
313
+ if (!root)
314
+ return;
315
+ if (connection.kind === "remote") {
316
+ if (repo.serverProjectRootAlias !== repo.selected)
317
+ return;
318
+ if (repo.serverProjectRootBaseUrl !== connection.baseUrl)
319
+ return;
320
+ } else if (repo.serverProjectRootAlias && repo.serverProjectRootAlias !== repo.selected) {
321
+ return;
322
+ }
323
+ return root;
324
+ }
293
325
  function resolveSelectedConnection(projectRoot, options = {}) {
294
326
  const repo = readRepoConnection(projectRoot);
295
327
  if (!repo)
@@ -301,13 +333,41 @@ function resolveSelectedConnection(projectRoot, options = {}) {
301
333
  if (!connection) {
302
334
  throw new CliError(`Selected Rig server "${repo.selected}" was not found. Run \`rig server list\` or \`rig server use local\`.`, 1);
303
335
  }
304
- return { alias: repo.selected, connection, serverProjectRoot: repo.serverProjectRoot };
336
+ return { alias: repo.selected, connection, serverProjectRoot: rootAllowedForSelection(repo, connection) };
337
+ }
338
+ function writeRepoServerProjectRoot(projectRoot, serverProjectRoot, metadata = {}) {
339
+ const repo = readRepoConnection(projectRoot);
340
+ if (!repo)
341
+ return;
342
+ let inferred = metadata;
343
+ if (!inferred.alias || !inferred.baseUrl) {
344
+ try {
345
+ const selected = resolveSelectedConnection(projectRoot);
346
+ if (selected?.connection.kind === "remote") {
347
+ inferred = {
348
+ alias: inferred.alias ?? selected.alias,
349
+ baseUrl: inferred.baseUrl ?? selected.connection.baseUrl
350
+ };
351
+ }
352
+ } catch {}
353
+ }
354
+ writeRepoConnection(projectRoot, {
355
+ ...repo,
356
+ ...metadata.project ? { project: metadata.project } : {},
357
+ serverProjectRoot,
358
+ ...inferred.alias ? { serverProjectRootAlias: inferred.alias } : {},
359
+ ...inferred.baseUrl ? { serverProjectRootBaseUrl: inferred.baseUrl.replace(/\/+$/, "") } : {}
360
+ });
305
361
  }
306
- function writeRepoServerProjectRoot(projectRoot, serverProjectRoot) {
362
+ function clearRepoServerProjectRoot(projectRoot) {
307
363
  const repo = readRepoConnection(projectRoot);
308
364
  if (!repo)
309
365
  return;
310
- writeRepoConnection(projectRoot, { ...repo, serverProjectRoot });
366
+ writeRepoConnection(projectRoot, {
367
+ selected: repo.selected,
368
+ ...repo.project ? { project: repo.project } : {},
369
+ ...repo.linkedAt ? { linkedAt: repo.linkedAt } : {}
370
+ });
311
371
  }
312
372
  function isRemoteConnectionSelected(projectRoot) {
313
373
  return resolveSelectedConnection(projectRoot)?.connection.kind === "remote";
@@ -334,12 +394,16 @@ __export(exports__server_client, {
334
394
  respondRunPiExtensionUiViaServer: () => respondRunPiExtensionUiViaServer,
335
395
  resolveServerConnectionLabel: () => resolveServerConnectionLabel,
336
396
  requestServerJson: () => requestServerJson,
397
+ repairRemoteProjectRootLink: () => repairRemoteProjectRootLink,
337
398
  registerProjectViaServer: () => registerProjectViaServer,
338
399
  prepareRemoteCheckoutViaServer: () => prepareRemoteCheckoutViaServer,
339
400
  postGitHubTokenViaServer: () => postGitHubTokenViaServer,
401
+ normalizeRepoSlug: () => normalizeRepoSlug,
340
402
  listWorkspaceTasksViaServer: () => listWorkspaceTasksViaServer,
341
403
  listRunsViaServer: () => listRunsViaServer,
342
404
  listGitHubProjectsViaServer: () => listGitHubProjectsViaServer,
405
+ isRemoteProjectRootLinkError: () => isRemoteProjectRootLinkError,
406
+ inspectRemoteProjectLink: () => inspectRemoteProjectLink,
343
407
  getWorkspaceTaskViaServer: () => getWorkspaceTaskViaServer,
344
408
  getRunTimelineViaServer: () => getRunTimelineViaServer,
345
409
  getRunPiStatusViaServer: () => getRunPiStatusViaServer,
@@ -351,13 +415,15 @@ __export(exports__server_client, {
351
415
  getRunDetailsViaServer: () => getRunDetailsViaServer,
352
416
  getGitHubProjectStatusFieldViaServer: () => getGitHubProjectStatusFieldViaServer,
353
417
  getGitHubAuthStatusViaServer: () => getGitHubAuthStatusViaServer,
418
+ formatRemoteProjectLinkHint: () => formatRemoteProjectLinkHint,
354
419
  ensureTaskLabelsViaServer: () => ensureTaskLabelsViaServer,
355
420
  ensureServerForCli: () => ensureServerForCli,
421
+ ensureRemoteProjectRootLink: () => ensureRemoteProjectRootLink,
356
422
  buildRunPiEventsWebSocketUrl: () => buildRunPiEventsWebSocketUrl,
357
423
  abortRunPiViaServer: () => abortRunPiViaServer
358
424
  });
359
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
360
- import { resolve as resolve2 } from "path";
425
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
426
+ import { dirname as dirname2, isAbsolute, resolve as resolve2 } from "path";
361
427
  import { ensureLocalRigServerConnection } from "@rig/runtime/local-server";
362
428
  function setServerPhaseListener(listener) {
363
429
  const previous = serverPhaseListener;
@@ -403,11 +469,10 @@ function readStoredGitHubAuthToken(projectRoot) {
403
469
  const parsed = readRemoteAuthState(projectRoot);
404
470
  return parsed ? cleanToken(typeof parsed.token === "string" ? parsed.token : undefined) : null;
405
471
  }
406
- function inferRemoteServerProjectRootFromAuthState(projectRoot) {
407
- const repo = readRepoConnection(projectRoot);
408
- const slug = repo?.project?.trim();
409
- if (!slug || !/^[^/]+\/[^/]+$/.test(slug))
472
+ function inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug) {
473
+ if (!/^[^/]+\/[^/]+$/.test(repoSlug))
410
474
  return null;
475
+ const repo = readRepoConnection(projectRoot);
411
476
  const auth = readRemoteAuthState(projectRoot);
412
477
  const authUpdatedAt = typeof auth?.updatedAt === "string" ? Date.parse(auth.updatedAt) : Number.NaN;
413
478
  const repoLinkedAt = typeof repo?.linkedAt === "string" ? Date.parse(repo.linkedAt) : Number.NaN;
@@ -416,25 +481,426 @@ function inferRemoteServerProjectRootFromAuthState(projectRoot) {
416
481
  const checkoutBaseDir = typeof auth?.checkoutBaseDir === "string" && auth.checkoutBaseDir.trim() ? auth.checkoutBaseDir.trim() : null;
417
482
  if (!checkoutBaseDir)
418
483
  return null;
419
- const inferred = `${checkoutBaseDir.replace(/\/+$/, "")}/${slug}`;
420
- writeRepoServerProjectRoot(projectRoot, inferred);
421
- return inferred;
484
+ return `${checkoutBaseDir.replace(/\/+$/, "")}/${repoSlug}`;
422
485
  }
423
486
  function readLocalConnectionFallbackToken(projectRoot) {
424
487
  return readGitHubBearerTokenForRemote(projectRoot) ?? cleanToken(process.env.RIG_GITHUB_TOKEN) ?? readStoredGitHubAuthToken(projectRoot);
425
488
  }
489
+ function normalizeRepoSlug(value) {
490
+ const slug = value?.trim();
491
+ return slug && /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
492
+ }
493
+ function readProjectLinkSlug(projectRoot) {
494
+ const path = resolve2(projectRoot, ".rig", "state", "project-link.json");
495
+ if (!existsSync2(path))
496
+ return null;
497
+ try {
498
+ const parsed = JSON.parse(readFileSync2(path, "utf8"));
499
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
500
+ return null;
501
+ return normalizeRepoSlug(typeof parsed.repoSlug === "string" ? String(parsed.repoSlug) : null);
502
+ } catch {
503
+ return null;
504
+ }
505
+ }
506
+ function writeProjectLinkConnection(projectRoot, repoSlug, alias) {
507
+ const path = resolve2(projectRoot, ".rig", "state", "project-link.json");
508
+ mkdirSync2(dirname2(path), { recursive: true });
509
+ writeFileSync2(path, `${JSON.stringify({ repoSlug, connection: alias, linkedAt: new Date().toISOString() }, null, 2)}
510
+ `, "utf8");
511
+ }
512
+ function remoteLinkRepairCommand(repoSlug) {
513
+ return repoSlug ? `rig server repair-link --repo ${repoSlug}` : "rig server repair-link --repo owner/repo";
514
+ }
515
+ function isRemoteProjectRootLinkError(error) {
516
+ const text = error instanceof Error ? error.message : String(error ?? "");
517
+ return /no server-host project root link|remote project(?:-root)? link|x-rig-project-root|serverProjectRoot/i.test(text);
518
+ }
519
+ function formatRemoteProjectLinkHint(resolution) {
520
+ const repair = remoteLinkRepairCommand(resolution.repoSlug);
521
+ if (resolution.status === "auth_required") {
522
+ return `Authenticate the selected remote${resolution.alias ? ` (${resolution.alias})` : ""}, then run \`${repair}\`. Use \`rig github auth import-gh\` or \`rig init --repair --server remote --github-auth device${resolution.repoSlug ? ` --repo ${resolution.repoSlug}` : ""}\`.`;
523
+ }
524
+ if (resolution.status === "missing_project") {
525
+ return `Record the repo slug, then run \`${repair}\` (or \`rig init --repo owner/repo --server remote --github-auth device\`).`;
526
+ }
527
+ if (resolution.status === "not_remote")
528
+ return "Select a remote server with `rig server use <alias>` before repairing a remote project link.";
529
+ return `Run \`${repair}\` to backfill or prepare a server-host checkout for the selected remote${resolution.alias ? ` (${resolution.alias})` : ""}.`;
530
+ }
531
+ function remoteProjectLinkFailure(input) {
532
+ const partial = {
533
+ status: input.status,
534
+ alias: input.alias,
535
+ baseUrl: input.baseUrl,
536
+ repoSlug: input.repoSlug
537
+ };
538
+ return {
539
+ ok: false,
540
+ ...input,
541
+ hint: formatRemoteProjectLinkHint(partial),
542
+ next: remoteLinkRepairCommand(input.repoSlug)
543
+ };
544
+ }
545
+ function remoteProjectLinkSuccess(input) {
546
+ return {
547
+ ok: true,
548
+ status: input.status,
549
+ alias: input.alias,
550
+ baseUrl: input.baseUrl,
551
+ repoSlug: input.repoSlug,
552
+ serverProjectRoot: input.serverProjectRoot,
553
+ source: input.source,
554
+ prepared: input.prepared ?? input.status === "prepared",
555
+ validated: input.validated ?? false,
556
+ message: input.message ?? `Remote project link ready for ${input.repoSlug}.`,
557
+ hint: "Remote-scoped task/run/status requests will send x-rig-project-root for this server-host checkout.",
558
+ next: "rig task list"
559
+ };
560
+ }
561
+ function localCheckoutPathCandidate(projectRoot, candidate) {
562
+ try {
563
+ const local = resolve2(projectRoot);
564
+ const resolved = resolve2(candidate);
565
+ return resolved === local || resolved.startsWith(`${local}/`);
566
+ } catch {
567
+ return false;
568
+ }
569
+ }
570
+ async function requestRemoteProjectLinkJson(baseUrl, pathname, authToken, init = {}) {
571
+ const requestUrl = new URL(`${baseUrl}${pathname}`);
572
+ if (authToken && queryAuthFallbackEnabled())
573
+ requestUrl.searchParams.set("rt", authToken);
574
+ const response = await fetch(requestUrl, {
575
+ ...init,
576
+ headers: mergeHeaders(init.headers, authToken)
577
+ });
578
+ const text = await response.text();
579
+ const payload = text.trim().length > 0 ? (() => {
580
+ try {
581
+ return JSON.parse(text);
582
+ } catch {
583
+ return null;
584
+ }
585
+ })() : null;
586
+ return { ok: response.ok, status: response.status, payload, text };
587
+ }
588
+ function payloadError(payload, fallback) {
589
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
590
+ const record = payload;
591
+ const value = record.error ?? record.message ?? record.reason;
592
+ if (typeof value === "string" && value.trim())
593
+ return value.trim();
594
+ }
595
+ return fallback;
596
+ }
597
+ function checkoutPathsFromProjectPayload(payload) {
598
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
599
+ return [];
600
+ const project = payload.project;
601
+ if (!project || typeof project !== "object" || Array.isArray(project))
602
+ return [];
603
+ const checkouts = project.checkouts;
604
+ if (!Array.isArray(checkouts))
605
+ return [];
606
+ return [...checkouts].reverse().flatMap((entry) => {
607
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
608
+ return [];
609
+ const path = entry.path;
610
+ return typeof path === "string" && path.trim() ? [path.trim()] : [];
611
+ });
612
+ }
613
+ function checkoutPathFromPreparePayload(payload) {
614
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
615
+ return null;
616
+ const checkout = payload.checkout;
617
+ if (!checkout || typeof checkout !== "object" || Array.isArray(checkout))
618
+ return null;
619
+ const path = checkout.path;
620
+ return typeof path === "string" && path.trim() ? path.trim() : null;
621
+ }
622
+ async function validateAndPersistRemoteRoot(input) {
623
+ const candidate = input.candidate.trim();
624
+ if (!candidate || !isAbsolute(candidate)) {
625
+ return remoteProjectLinkFailure({
626
+ status: "invalid_root",
627
+ alias: input.alias,
628
+ baseUrl: input.baseUrl,
629
+ repoSlug: input.repoSlug,
630
+ message: `Remote project root candidate is not an absolute server-host path: ${candidate || "(empty)"}.`
631
+ });
632
+ }
633
+ if (localCheckoutPathCandidate(input.projectRoot, candidate)) {
634
+ return remoteProjectLinkFailure({
635
+ status: "invalid_root",
636
+ alias: input.alias,
637
+ baseUrl: input.baseUrl,
638
+ repoSlug: input.repoSlug,
639
+ message: `Refusing to use local checkout path ${candidate} as a remote server project root.`
640
+ });
641
+ }
642
+ let response;
643
+ try {
644
+ response = await requestRemoteProjectLinkJson(input.baseUrl, "/api/server/project-root", input.authToken, {
645
+ method: "POST",
646
+ headers: { "content-type": "application/json" },
647
+ body: JSON.stringify({ projectRoot: candidate })
648
+ });
649
+ } catch (error) {
650
+ return remoteProjectLinkFailure({
651
+ status: "error",
652
+ alias: input.alias,
653
+ baseUrl: input.baseUrl,
654
+ repoSlug: input.repoSlug,
655
+ message: `Could not validate remote project root ${candidate}: ${error instanceof Error ? error.message : String(error)}`
656
+ });
657
+ }
658
+ if (response.status === 401 || response.status === 403) {
659
+ return remoteProjectLinkFailure({
660
+ status: "auth_required",
661
+ alias: input.alias,
662
+ baseUrl: input.baseUrl,
663
+ repoSlug: input.repoSlug,
664
+ statusCode: response.status,
665
+ message: `Remote server ${input.alias} requires authentication before validating project root ${candidate}.`
666
+ });
667
+ }
668
+ if (!response.ok) {
669
+ return remoteProjectLinkFailure({
670
+ status: "invalid_root",
671
+ alias: input.alias,
672
+ baseUrl: input.baseUrl,
673
+ repoSlug: input.repoSlug,
674
+ statusCode: response.status,
675
+ message: `Remote server rejected project root ${candidate} (${response.status}): ${payloadError(response.payload, response.text || "project-root validation failed")}`
676
+ });
677
+ }
678
+ const record = response.payload && typeof response.payload === "object" && !Array.isArray(response.payload) ? response.payload : {};
679
+ if (record.ok !== true) {
680
+ return remoteProjectLinkFailure({
681
+ status: "invalid_root",
682
+ alias: input.alias,
683
+ baseUrl: input.baseUrl,
684
+ repoSlug: input.repoSlug,
685
+ message: `Remote server did not accept project root ${candidate}: ${payloadError(response.payload, "project-root validation failed")}`
686
+ });
687
+ }
688
+ const accepted = typeof record.projectRoot === "string" && record.projectRoot.trim() ? record.projectRoot.trim() : typeof record.requestedProjectRoot === "string" && record.requestedProjectRoot.trim() ? record.requestedProjectRoot.trim() : candidate;
689
+ writeRepoServerProjectRoot(input.projectRoot, accepted, { alias: input.alias, baseUrl: input.baseUrl, project: input.repoSlug });
690
+ writeProjectLinkConnection(input.projectRoot, input.repoSlug, input.alias);
691
+ const status = input.status ?? (input.prepared ? "prepared" : "backfilled");
692
+ return remoteProjectLinkSuccess({
693
+ status,
694
+ alias: input.alias,
695
+ baseUrl: input.baseUrl,
696
+ repoSlug: input.repoSlug,
697
+ serverProjectRoot: accepted,
698
+ source: input.source,
699
+ prepared: input.prepared,
700
+ validated: true,
701
+ message: status === "ready" ? `Remote project link already points to ${accepted} and was validated on ${input.alias}.` : input.prepared ? `Prepared and linked remote checkout ${accepted} for ${input.repoSlug}.` : `Backfilled remote checkout ${accepted} for ${input.repoSlug}.`
702
+ });
703
+ }
704
+ async function ensureRemoteProjectRootLink(projectRoot, options = {}) {
705
+ let selected;
706
+ try {
707
+ selected = resolveSelectedConnection(projectRoot);
708
+ } catch (error) {
709
+ return remoteProjectLinkFailure({ status: "error", message: error instanceof Error ? error.message : String(error) });
710
+ }
711
+ if (!selected || selected.connection.kind !== "remote") {
712
+ return remoteProjectLinkFailure({ status: "not_remote", message: "No remote Rig server is selected for this repo." });
713
+ }
714
+ const repo = readRepoConnection(projectRoot);
715
+ const explicitRepoRequested = options.repoSlug !== undefined;
716
+ const explicitRepoSlug = explicitRepoRequested ? normalizeRepoSlug(options.repoSlug) : null;
717
+ const repoProjectSlug = normalizeRepoSlug(repo?.project);
718
+ const repoSlug = explicitRepoSlug ?? repoProjectSlug ?? readProjectLinkSlug(projectRoot);
719
+ const alias = selected.alias;
720
+ const baseUrl = selected.connection.baseUrl;
721
+ const authToken = options.authToken === undefined ? readGitHubBearerTokenForRemote(projectRoot) : options.authToken;
722
+ const mode = options.mode ?? "backfill-only";
723
+ if (explicitRepoRequested && !explicitRepoSlug) {
724
+ return remoteProjectLinkFailure({
725
+ status: "missing_project",
726
+ alias,
727
+ baseUrl,
728
+ message: `Invalid --repo value ${JSON.stringify(options.repoSlug)}. Expected owner/repo.`
729
+ });
730
+ }
731
+ if (!repoSlug) {
732
+ return remoteProjectLinkFailure({
733
+ status: "missing_project",
734
+ alias,
735
+ baseUrl,
736
+ message: `Remote server ${alias} is selected, but this checkout has no GitHub repo slug recorded.`
737
+ });
738
+ }
739
+ const skippedCandidates = [];
740
+ const storedRoot = selected.serverProjectRoot?.trim() || null;
741
+ const storedRootMatchesResolvedRepo = repoProjectSlug === repoSlug;
742
+ if (storedRoot && storedRootMatchesResolvedRepo) {
743
+ if (localCheckoutPathCandidate(projectRoot, storedRoot)) {
744
+ clearRepoServerProjectRoot(projectRoot);
745
+ skippedCandidates.push(storedRoot);
746
+ } else {
747
+ const storedResult = await validateAndPersistRemoteRoot({
748
+ projectRoot,
749
+ alias,
750
+ baseUrl,
751
+ authToken,
752
+ repoSlug,
753
+ candidate: storedRoot,
754
+ source: "stored",
755
+ status: "ready"
756
+ });
757
+ if (storedResult.ok || storedResult.status === "auth_required" || storedResult.status === "error")
758
+ return storedResult;
759
+ clearRepoServerProjectRoot(projectRoot);
760
+ skippedCandidates.push(storedRoot);
761
+ }
762
+ } else if (storedRoot) {
763
+ skippedCandidates.push(storedRoot);
764
+ }
765
+ const authCandidate = inferRemoteServerProjectRootCandidateFromAuthState(projectRoot, repoSlug);
766
+ if (authCandidate) {
767
+ const authResult = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: authCandidate, source: "auth-state" });
768
+ if (authResult.ok || authResult.status === "auth_required")
769
+ return authResult;
770
+ }
771
+ let registryResponse;
772
+ try {
773
+ registryResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}`, authToken);
774
+ } catch (error) {
775
+ return remoteProjectLinkFailure({
776
+ status: "error",
777
+ alias,
778
+ baseUrl,
779
+ repoSlug,
780
+ message: `Could not read remote project registry for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
781
+ });
782
+ }
783
+ if (registryResponse.status === 401 || registryResponse.status === 403) {
784
+ return remoteProjectLinkFailure({
785
+ status: "auth_required",
786
+ alias,
787
+ baseUrl,
788
+ repoSlug,
789
+ statusCode: registryResponse.status,
790
+ message: `Remote server ${alias} requires authentication before reading the ${repoSlug} project registry.`
791
+ });
792
+ }
793
+ if (registryResponse.ok) {
794
+ const candidates = checkoutPathsFromProjectPayload(registryResponse.payload);
795
+ for (const candidate of candidates) {
796
+ const result = await validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate, source: "registry" });
797
+ if (result.ok || result.status === "auth_required")
798
+ return result;
799
+ skippedCandidates.push(candidate);
800
+ }
801
+ if (mode === "backfill-only") {
802
+ return remoteProjectLinkFailure({
803
+ status: candidates.length > 0 ? "invalid_root" : "no_server_checkout",
804
+ alias,
805
+ baseUrl,
806
+ repoSlug,
807
+ message: candidates.length > 0 ? `Remote registry has checkout candidates for ${repoSlug}, but none validated for ${alias}.` : `Remote registry has ${repoSlug}, but no server checkout path is linked yet.`,
808
+ skippedCandidates
809
+ });
810
+ }
811
+ } else if (registryResponse.status === 404) {
812
+ if (mode === "backfill-only") {
813
+ return remoteProjectLinkFailure({
814
+ status: "project_not_registered",
815
+ alias,
816
+ baseUrl,
817
+ repoSlug,
818
+ statusCode: registryResponse.status,
819
+ message: `Remote server ${alias} has no registered project record for ${repoSlug}.`
820
+ });
821
+ }
822
+ } else {
823
+ return remoteProjectLinkFailure({
824
+ status: "error",
825
+ alias,
826
+ baseUrl,
827
+ repoSlug,
828
+ statusCode: registryResponse.status,
829
+ message: `Could not backfill ${repoSlug} from remote registry (${registryResponse.status}): ${payloadError(registryResponse.payload, registryResponse.text || "registry lookup failed")}`,
830
+ skippedCandidates
831
+ });
832
+ }
833
+ let prepareResponse;
834
+ try {
835
+ prepareResponse = await requestRemoteProjectLinkJson(baseUrl, `/api/projects/${encodeURIComponent(repoSlug)}/prepare-checkout`, authToken, {
836
+ method: "POST",
837
+ headers: { "content-type": "application/json" },
838
+ body: JSON.stringify({ checkout: { kind: "managed-clone" }, repoUrl: `https://github.com/${repoSlug}.git` })
839
+ });
840
+ } catch (error) {
841
+ return remoteProjectLinkFailure({
842
+ status: "error",
843
+ alias,
844
+ baseUrl,
845
+ repoSlug,
846
+ message: `Could not prepare remote checkout for ${repoSlug} on ${alias}: ${error instanceof Error ? error.message : String(error)}`
847
+ });
848
+ }
849
+ if (prepareResponse.status === 401 || prepareResponse.status === 403) {
850
+ return remoteProjectLinkFailure({
851
+ status: "auth_required",
852
+ alias,
853
+ baseUrl,
854
+ repoSlug,
855
+ statusCode: prepareResponse.status,
856
+ message: `Remote server ${alias} requires authentication before preparing ${repoSlug}.`
857
+ });
858
+ }
859
+ if (!prepareResponse.ok) {
860
+ return remoteProjectLinkFailure({
861
+ status: "error",
862
+ alias,
863
+ baseUrl,
864
+ repoSlug,
865
+ statusCode: prepareResponse.status,
866
+ message: `Remote checkout prepare failed for ${repoSlug} (${prepareResponse.status}): ${payloadError(prepareResponse.payload, prepareResponse.text || "prepare-checkout failed")}`,
867
+ skippedCandidates
868
+ });
869
+ }
870
+ const preparedPath = checkoutPathFromPreparePayload(prepareResponse.payload);
871
+ if (!preparedPath) {
872
+ return remoteProjectLinkFailure({
873
+ status: "invalid_root",
874
+ alias,
875
+ baseUrl,
876
+ repoSlug,
877
+ message: `Remote checkout prepare for ${repoSlug} did not return a checkout.path.`,
878
+ skippedCandidates
879
+ });
880
+ }
881
+ return validateAndPersistRemoteRoot({ projectRoot, alias, baseUrl, authToken, repoSlug, candidate: preparedPath, source: "prepare", prepared: true });
882
+ }
883
+ async function inspectRemoteProjectLink(projectRoot) {
884
+ return ensureRemoteProjectRootLink(projectRoot, { mode: "backfill-only" });
885
+ }
886
+ async function repairRemoteProjectRootLink(context, options = {}) {
887
+ const resolution = await ensureRemoteProjectRootLink(context.projectRoot, { mode: options.mode ?? "prepare-if-missing", repoSlug: options.repoSlug });
888
+ if (!resolution.ok)
889
+ throw new CliError(resolution.message, 1, { hint: resolution.hint });
890
+ return resolution;
891
+ }
426
892
  async function ensureServerForCli(projectRoot) {
427
893
  try {
428
894
  const selected = resolveSelectedConnection(projectRoot);
429
895
  if (selected?.connection.kind === "remote") {
430
896
  reportServerPhase(`Connecting to ${selected.alias}\u2026`);
431
897
  const authToken = readGitHubBearerTokenForRemote(projectRoot);
432
- const serverProjectRoot = selected.serverProjectRoot ?? inferRemoteServerProjectRootFromAuthState(projectRoot) ?? await backfillRemoteServerProjectRoot(projectRoot, selected.connection.baseUrl, authToken);
898
+ const link = await ensureRemoteProjectRootLink(projectRoot, { mode: "backfill-only", authToken });
433
899
  return {
434
900
  baseUrl: selected.connection.baseUrl,
435
901
  authToken,
436
902
  connectionKind: "remote",
437
- serverProjectRoot
903
+ serverProjectRoot: link.ok ? link.serverProjectRoot ?? null : null
438
904
  };
439
905
  }
440
906
  reportServerPhase("Starting local Rig server\u2026");
@@ -452,32 +918,6 @@ async function ensureServerForCli(projectRoot) {
452
918
  throw error;
453
919
  }
454
920
  }
455
- async function backfillRemoteServerProjectRoot(projectRoot, baseUrl, authToken) {
456
- const repo = readRepoConnection(projectRoot);
457
- const slug = repo?.project?.trim();
458
- if (!slug)
459
- return null;
460
- try {
461
- const url = new URL(`${baseUrl}/api/projects/${encodeURIComponent(slug)}`);
462
- if (authToken && queryAuthFallbackEnabled())
463
- url.searchParams.set("rt", authToken);
464
- const response = await fetch(url, {
465
- headers: mergeHeaders(undefined, authToken)
466
- });
467
- if (!response.ok)
468
- return null;
469
- const payload = await response.json();
470
- const project = payload.project && typeof payload.project === "object" && !Array.isArray(payload.project) ? payload.project : null;
471
- const checkouts = Array.isArray(project?.checkouts) ? project.checkouts : [];
472
- const latestCheckout = [...checkouts].reverse().find((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.path === "string"));
473
- const path = typeof latestCheckout?.path === "string" && latestCheckout.path.trim() ? latestCheckout.path.trim() : null;
474
- if (path)
475
- writeRepoServerProjectRoot(projectRoot, path);
476
- return path;
477
- } catch {
478
- return null;
479
- }
480
- }
481
921
  function appendTaskFilterParams(url, filters) {
482
922
  if (filters.assignee)
483
923
  url.searchParams.set("assignee", filters.assignee);
@@ -578,13 +1018,20 @@ function canUseRemoteWithoutProjectRoot(pathname) {
578
1018
  async function requestServerJson(context, pathname, init = {}) {
579
1019
  const server = await ensureServerForCli(context.projectRoot);
580
1020
  const requestUrl = new URL(`${server.baseUrl}${pathname}`);
581
- if (server.connectionKind === "remote" && !server.serverProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
582
- const repo = readRepoConnection(context.projectRoot);
583
- throw new CliError(`Remote server is selected for ${repo?.project ?? "this repo"}, but this checkout has no server-host project root link.`, 1, { hint: "Run `rig init --repair` or `rig init --yes --repo owner/repo --server remote` to repair the remote project link before task/run commands." });
1021
+ let scopedServerProjectRoot = server.serverProjectRoot;
1022
+ if (server.connectionKind === "remote" && !scopedServerProjectRoot && !canUseRemoteWithoutProjectRoot(requestUrl.pathname) && (server.authToken || !isLoopbackRemoteBaseUrl(server.baseUrl))) {
1023
+ const link = await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only", authToken: server.authToken });
1024
+ if (link.ok && link.serverProjectRoot) {
1025
+ scopedServerProjectRoot = link.serverProjectRoot;
1026
+ } else {
1027
+ const repo = readRepoConnection(context.projectRoot);
1028
+ const target = link.alias && link.baseUrl ? `${link.alias} (${link.baseUrl})` : server.baseUrl;
1029
+ throw new CliError(`Remote server ${target} is selected for ${link.repoSlug ?? repo?.project ?? "this repo"}, but this checkout has no server-host project root link. ${link.message}`, 1, { hint: link.hint });
1030
+ }
584
1031
  }
585
1032
  const headers = mergeHeaders(init.headers, server.authToken);
586
- if (server.serverProjectRoot)
587
- headers.set("x-rig-project-root", server.serverProjectRoot);
1033
+ if (scopedServerProjectRoot)
1034
+ headers.set("x-rig-project-root", scopedServerProjectRoot);
588
1035
  if (server.connectionKind === "remote" && server.authToken && queryAuthFallbackEnabled()) {
589
1036
  requestUrl.searchParams.set("rt", server.authToken);
590
1037
  }
@@ -1629,12 +2076,13 @@ var init__help_catalog = __esm(() => {
1629
2076
  {
1630
2077
  name: "server",
1631
2078
  summary: "Choose, inspect, and start the Rig server that owns tasks and runs.",
1632
- usage: ["rig server <status|list|add|use|start> [options]"],
2079
+ usage: ["rig server <status|list|add|use|repair-link|start> [options]"],
1633
2080
  commands: [
1634
- { command: "status", description: "Show the selected server for this repo.", primary: true },
2081
+ { command: "status", description: "Show the selected server and remote project-root link for this repo.", primary: true },
1635
2082
  { command: "use local", description: "Switch this repo to the local Rig server.", primary: true },
1636
2083
  { command: "add <alias> <url>", description: "Save a remote Rig server URL.", primary: true },
1637
2084
  { command: "use <alias>", description: "Select a saved remote server alias.", primary: true },
2085
+ { command: "repair-link [--prepare|--backfill-only] [--repo owner/repo]", description: "Backfill or prepare the selected remote's server-host checkout link.", primary: true },
1638
2086
  { command: "list", description: "List saved local/remote server aliases.", primary: true },
1639
2087
  { command: "start [--host <host>] [--port <n>]", description: "Start a local rig-server process." }
1640
2088
  ],
@@ -1642,6 +2090,7 @@ var init__help_catalog = __esm(() => {
1642
2090
  "rig server status",
1643
2091
  "rig server add prod https://where.rig-does.work",
1644
2092
  "rig server use prod",
2093
+ "rig server repair-link --repo owner/repo",
1645
2094
  "rig server use local",
1646
2095
  "rig server start --port 3773"
1647
2096
  ],
@@ -2352,7 +2801,7 @@ function resolveControlPlaneDefinitionRoot(projectRoot) {
2352
2801
  var init__paths = () => {};
2353
2802
 
2354
2803
  // packages/cli/src/report-bug.ts
2355
- import { copyFileSync, existsSync as existsSync5, mkdirSync as mkdirSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
2804
+ import { copyFileSync, existsSync as existsSync5, mkdirSync as mkdirSync3, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
2356
2805
  import { basename as basename2, extname, join, resolve as resolve7 } from "path";
2357
2806
  function slugifyBugTitle(value) {
2358
2807
  const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80).replace(/-+$/g, "");
@@ -2391,8 +2840,8 @@ function createBugReportFiles(input) {
2391
2840
  }
2392
2841
  rmSync2(reportDir, { recursive: true, force: true });
2393
2842
  }
2394
- mkdirSync2(assetDir, { recursive: true });
2395
- mkdirSync2(screenshotDir, { recursive: true });
2843
+ mkdirSync3(assetDir, { recursive: true });
2844
+ mkdirSync3(screenshotDir, { recursive: true });
2396
2845
  const copiedScreenshots = copyEvidenceFiles(input.projectRoot, screenshotDir, input.screenshots ?? [], "screenshot");
2397
2846
  const copiedAssets = copyEvidenceFiles(input.projectRoot, assetDir, input.assets ?? [], "asset");
2398
2847
  const manifestAssets = [
@@ -2422,15 +2871,15 @@ function createBugReportFiles(input) {
2422
2871
  platform: process.platform,
2423
2872
  arch: process.arch
2424
2873
  };
2425
- writeFileSync2(join(assetDir, "README.md"), buildAssetReadme(input.title), "utf8");
2426
- writeFileSync2(join(screenshotDir, "README.md"), buildScreenshotReadme(input.title), "utf8");
2874
+ writeFileSync3(join(assetDir, "README.md"), buildAssetReadme(input.title), "utf8");
2875
+ writeFileSync3(join(screenshotDir, "README.md"), buildScreenshotReadme(input.title), "utf8");
2427
2876
  if (browserPath && browser) {
2428
- writeFileSync2(browserPath, `${JSON.stringify(browser, null, 2)}
2877
+ writeFileSync3(browserPath, `${JSON.stringify(browser, null, 2)}
2429
2878
  `, "utf8");
2430
2879
  }
2431
- writeFileSync2(manifestPath, `${JSON.stringify(manifest, null, 2)}
2880
+ writeFileSync3(manifestPath, `${JSON.stringify(manifest, null, 2)}
2432
2881
  `, "utf8");
2433
- writeFileSync2(taskPath, buildBugReportMarkdown(input, browser, copiedScreenshots, copiedAssets), "utf8");
2882
+ writeFileSync3(taskPath, buildBugReportMarkdown(input, browser, copiedScreenshots, copiedAssets), "utf8");
2434
2883
  return {
2435
2884
  slug,
2436
2885
  reportDir,
@@ -2607,7 +3056,7 @@ function formatAssetMarkdown(name, path) {
2607
3056
  var init_report_bug = () => {};
2608
3057
 
2609
3058
  // packages/cli/src/commands/task-report-bug.ts
2610
- import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
3059
+ import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
2611
3060
  import { resolve as resolve8 } from "path";
2612
3061
  import pc2 from "picocolors";
2613
3062
  import {
@@ -2969,7 +3418,7 @@ function appendTaskConfigEntryPreservingText(taskConfigPath, issueId, entry) {
2969
3418
  `).map((line) => ` ${line}`).join(`
2970
3419
  `);
2971
3420
  if (!trimmed || trimmed === "{}") {
2972
- writeFileSync3(taskConfigPath, `{
3421
+ writeFileSync4(taskConfigPath, `{
2973
3422
  ${serializedEntry}
2974
3423
  }
2975
3424
  `, "utf8");
@@ -2985,7 +3434,7 @@ ${serializedEntry}
2985
3434
  throw new CliError(`Invalid trailing content in task config: ${taskConfigPath}`, 1, { hint: "Remove the trailing content so the file is a single JSON object, then retry." });
2986
3435
  }
2987
3436
  const comma = before.trim() === "{" ? "" : ",";
2988
- writeFileSync3(taskConfigPath, `${before}${comma}
3437
+ writeFileSync4(taskConfigPath, `${before}${comma}
2989
3438
  ${serializedEntry}
2990
3439
  }
2991
3440
  `, "utf8");
@@ -3370,7 +3819,7 @@ var init_task_report_bug = __esm(() => {
3370
3819
  });
3371
3820
 
3372
3821
  // packages/cli/src/commands/browser.ts
3373
- import { mkdirSync as mkdirSync3, rmSync as rmSync3 } from "fs";
3822
+ import { mkdirSync as mkdirSync4, rmSync as rmSync3 } from "fs";
3374
3823
  import { resolve as resolve9 } from "path";
3375
3824
  import { spawn } from "child_process";
3376
3825
  import { emitKeypressEvents } from "readline";
@@ -3948,7 +4397,7 @@ function readBrowserDemoCommandLine(command) {
3948
4397
  }
3949
4398
  async function launchBrowserDemo(runtime) {
3950
4399
  rmSync3(resolve9(runtime.browserDir, runtime.stateDir), { recursive: true, force: true });
3951
- mkdirSync3(resolve9(runtime.browserDir, runtime.stateDir), { recursive: true });
4400
+ mkdirSync4(resolve9(runtime.browserDir, runtime.stateDir), { recursive: true });
3952
4401
  const launcherPath = resolve9(runtime.browserDir, "scripts/electron-launcher.mjs");
3953
4402
  const launcher = await import(pathToFileURL(launcherPath).href);
3954
4403
  const electronPath = await launcher.resolveElectronPath();
@@ -4297,7 +4746,7 @@ var init_profile_and_review = __esm(() => {
4297
4746
  });
4298
4747
 
4299
4748
  // packages/cli/src/commands/_policy.ts
4300
- import { appendFileSync, mkdirSync as mkdirSync4 } from "fs";
4749
+ import { appendFileSync, mkdirSync as mkdirSync5 } from "fs";
4301
4750
  import { resolve as resolve10 } from "path";
4302
4751
  import { evaluate as evaluate2, loadPolicy as loadPolicy2, resolveAction as resolveAction2 } from "@rig/runtime/control-plane/runtime/guard";
4303
4752
  import { resolveHarnessPaths } from "@rig/runtime/control-plane/native/utils";
@@ -4314,7 +4763,7 @@ function appendControlledBashAudit(context, mode, command, args, matchedRuleIds,
4314
4763
  }
4315
4764
  const logsDir = resolveHarnessPaths(context.projectRoot).logsDir;
4316
4765
  const logFile = resolve10(logsDir, "controlled-bash.jsonl");
4317
- mkdirSync4(logsDir, { recursive: true });
4766
+ mkdirSync5(logsDir, { recursive: true });
4318
4767
  const payload = {
4319
4768
  timestamp: new Date().toISOString(),
4320
4769
  mode,
@@ -4608,9 +5057,9 @@ var exports_pi = {};
4608
5057
  __export(exports_pi, {
4609
5058
  executePi: () => executePi
4610
5059
  });
4611
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
5060
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
4612
5061
  import { homedir as homedir4 } from "os";
4613
- import { dirname as dirname2, resolve as resolve12 } from "path";
5062
+ import { dirname as dirname3, resolve as resolve12 } from "path";
4614
5063
  function settingsPath(root) {
4615
5064
  return resolve12(root, ".pi", "settings.json");
4616
5065
  }
@@ -4635,8 +5084,8 @@ function packageKey(entry) {
4635
5084
  return JSON.stringify(entry);
4636
5085
  }
4637
5086
  function writeSettings(path, settings) {
4638
- mkdirSync5(dirname2(path), { recursive: true });
4639
- writeFileSync4(path, `${JSON.stringify(settings, null, 2)}
5087
+ mkdirSync6(dirname3(path), { recursive: true });
5088
+ writeFileSync5(path, `${JSON.stringify(settings, null, 2)}
4640
5089
  `, "utf-8");
4641
5090
  }
4642
5091
  async function searchNpmForPiExtensions(term) {
@@ -4819,6 +5268,14 @@ function permissionAllowsPr2(payload) {
4819
5268
  function isNotFoundError(error) {
4820
5269
  return /\b(404|not found)\b/i.test(message(error));
4821
5270
  }
5271
+ function isLoopbackBaseUrl(baseUrl) {
5272
+ try {
5273
+ const host = new URL(baseUrl).hostname.toLowerCase();
5274
+ return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
5275
+ } catch {
5276
+ return false;
5277
+ }
5278
+ }
4822
5279
  function projectCheckoutReady(payload) {
4823
5280
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
4824
5281
  return null;
@@ -4873,25 +5330,37 @@ async function runFastTaskRunPreflight(context, options = {}) {
4873
5330
  }
4874
5331
  const repo = readRepoConnection(context.projectRoot);
4875
5332
  const remoteWithoutProjectLink = selectedServer?.connectionKind === "remote" && !repo?.project;
4876
- checks.push(repo ? preflightCheck("project-link", "project linked to selected server", repo.project ? "pass" : remoteWithoutProjectLink ? "fail" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : " (missing project link)"}`, remoteWithoutProjectLink ? "Run `rig init --repair` or `rig init --yes --repo owner/repo --server remote` to restore the remote project link." : "Run `rig init --yes --repo owner/repo` to record the GitHub repo slug.") : preflightCheck("project-link", "project linked to selected server", legacyServerCompatibility ? "warn" : "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig server use <alias|local>`."));
5333
+ let remoteRootBlocked = false;
5334
+ checks.push(repo ? preflightCheck("project-link", "project linked to selected server", repo.project ? "pass" : remoteWithoutProjectLink ? "fail" : "warn", `${repo.selected}${repo.project ? ` -> ${repo.project}` : " (missing project link)"}`, remoteWithoutProjectLink ? "Run `rig server repair-link --repo owner/repo` or `rig init --repo owner/repo --server remote` to restore the remote project link." : "Run `rig init --yes --repo owner/repo` to record the GitHub repo slug.") : preflightCheck("project-link", "project linked to selected server", legacyServerCompatibility ? "warn" : "fail", "missing .rig/state/connection.json", "Run `rig init` or `rig server use <alias|local>`."));
5335
+ if (selectedServer?.connectionKind === "remote" && repo?.project && !selectedServer.serverProjectRoot) {
5336
+ const link = await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only", authToken: selectedServer.authToken });
5337
+ const loopbackDevRemote = !selectedServer.authToken && isLoopbackBaseUrl(selectedServer.baseUrl);
5338
+ checks.push(preflightCheck("remote-project-root-link", "remote server checkout/root linked", link.ok ? "pass" : loopbackDevRemote ? "warn" : "fail", link.ok ? `${link.repoSlug} -> ${link.serverProjectRoot}` : link.message, link.ok ? undefined : loopbackDevRemote ? "Loopback development remotes may continue without x-rig-project-root; production remotes must run `rig server repair-link`." : link.hint));
5339
+ remoteRootBlocked = !link.ok && !loopbackDevRemote;
5340
+ }
4877
5341
  try {
4878
5342
  const auth = await request("/api/github/auth/status");
4879
5343
  checks.push(isAuthenticated2(auth) ? preflightCheck("github-auth", "GitHub auth valid", "pass") : preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", "not authenticated", "Run `rig github auth import-gh` or `rig github auth token --token <token>`."));
4880
5344
  } catch (error) {
4881
5345
  checks.push(preflightCheck("github-auth", "GitHub auth valid", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix GitHub auth on the selected Rig server."));
4882
5346
  }
4883
- try {
4884
- const projection = await request("/api/workspace/task-projection");
4885
- checks.push(preflightCheck("task-projection", "task projection ready", "pass", JSON.stringify(projection).slice(0, 120)));
4886
- } catch (error) {
4887
- checks.push(preflightCheck("task-projection", "task projection ready", "warn", message(error), "Refresh task projection with `rig task list` before launching."));
4888
- }
4889
- try {
4890
- const permissions = await request("/api/github/repo/permissions");
4891
- const allowed = permissionAllowsPr2(permissions);
4892
- checks.push(allowed === false ? preflightCheck("github-repo-permissions", "GitHub PR permissions", "fail", JSON.stringify(permissions).slice(0, 120), "Grant the selected GitHub token permission to push branches and open PRs.") : preflightCheck("github-repo-permissions", "GitHub PR permissions", allowed === true ? "pass" : "warn", JSON.stringify(permissions).slice(0, 120), "Confirm the selected token can push branches and open PRs."));
4893
- } catch (error) {
4894
- checks.push(preflightCheck("github-repo-permissions", "GitHub PR permissions", "warn", message(error), "Ensure the selected token can push branches and open PRs."));
5347
+ if (!remoteRootBlocked) {
5348
+ try {
5349
+ const projection = await request("/api/workspace/task-projection");
5350
+ checks.push(preflightCheck("task-projection", "task projection ready", "pass", JSON.stringify(projection).slice(0, 120)));
5351
+ } catch (error) {
5352
+ checks.push(preflightCheck("task-projection", "task projection ready", "warn", message(error), "Refresh task projection with `rig task list` before launching."));
5353
+ }
5354
+ try {
5355
+ const permissions = await request("/api/github/repo/permissions");
5356
+ const allowed = permissionAllowsPr2(permissions);
5357
+ checks.push(allowed === false ? preflightCheck("github-repo-permissions", "GitHub PR permissions", "fail", JSON.stringify(permissions).slice(0, 120), "Grant the selected GitHub token permission to push branches and open PRs.") : preflightCheck("github-repo-permissions", "GitHub PR permissions", allowed === true ? "pass" : "warn", JSON.stringify(permissions).slice(0, 120), "Confirm the selected token can push branches and open PRs."));
5358
+ } catch (error) {
5359
+ checks.push(preflightCheck("github-repo-permissions", "GitHub PR permissions", "warn", message(error), "Ensure the selected token can push branches and open PRs."));
5360
+ }
5361
+ } else {
5362
+ checks.push(preflightCheck("task-projection", "task projection ready", "warn", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
5363
+ checks.push(preflightCheck("github-repo-permissions", "GitHub PR permissions", "warn", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
4895
5364
  }
4896
5365
  if (repo?.project) {
4897
5366
  try {
@@ -4903,19 +5372,24 @@ async function runFastTaskRunPreflight(context, options = {}) {
4903
5372
  }
4904
5373
  }
4905
5374
  if (taskId) {
4906
- try {
4907
- const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
4908
- const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
4909
- checks.push(found ? preflightCheck("issue", "task/issue accessible", "pass", taskId) : preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", taskId, "Confirm the issue exists and matches the configured task filters."));
4910
- } catch (error) {
4911
- checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
4912
- }
4913
- try {
4914
- const runs = await request("/api/runs?limit=200");
4915
- const duplicate = activeDuplicateRun(runs, taskId);
4916
- checks.push(duplicate ? preflightCheck("duplicate-active-run", "one active run per task", "fail", String(duplicate.runId ?? taskId), "Attach to or stop the existing run before starting another one for this task.") : preflightCheck("duplicate-active-run", "one active run per task", "pass", taskId));
4917
- } catch (error) {
4918
- checks.push(preflightCheck("duplicate-active-run", "one active run per task", "warn", message(error), "Could not list active runs; the server will still reject conflicting runs if configured."));
5375
+ if (!remoteRootBlocked) {
5376
+ try {
5377
+ const tasks = await request(`/api/workspace/tasks?limit=200&refresh=1`);
5378
+ const found = Array.isArray(tasks) && tasks.some((task) => taskMatchesId(task, taskId));
5379
+ checks.push(found ? preflightCheck("issue", "task/issue accessible", "pass", taskId) : preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", taskId, "Confirm the issue exists and matches the configured task filters."));
5380
+ } catch (error) {
5381
+ checks.push(preflightCheck("issue", "task/issue accessible", legacyServerCompatibility ? "warn" : "fail", message(error), "Fix the task source before launching a run."));
5382
+ }
5383
+ try {
5384
+ const runs = await request("/api/runs?limit=200");
5385
+ const duplicate = activeDuplicateRun(runs, taskId);
5386
+ checks.push(duplicate ? preflightCheck("duplicate-active-run", "one active run per task", "fail", String(duplicate.runId ?? taskId), "Attach to or stop the existing run before starting another one for this task.") : preflightCheck("duplicate-active-run", "one active run per task", "pass", taskId));
5387
+ } catch (error) {
5388
+ checks.push(preflightCheck("duplicate-active-run", "one active run per task", "warn", message(error), "Could not list active runs; the server will still reject conflicting runs if configured."));
5389
+ }
5390
+ } else {
5391
+ checks.push(preflightCheck("issue", "task/issue accessible", "fail", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
5392
+ checks.push(preflightCheck("duplicate-active-run", "one active run per task", "warn", "skipped until remote project-root link is repaired", "Run `rig server repair-link`, then retry."));
4919
5393
  }
4920
5394
  }
4921
5395
  if ((options.runtimeAdapter ?? "pi") === "pi") {
@@ -5396,7 +5870,7 @@ import {
5396
5870
  chmodSync,
5397
5871
  copyFileSync as copyFileSync2,
5398
5872
  existsSync as existsSync10,
5399
- mkdirSync as mkdirSync6,
5873
+ mkdirSync as mkdirSync7,
5400
5874
  readdirSync,
5401
5875
  readlinkSync,
5402
5876
  rmSync as rmSync4,
@@ -5501,7 +5975,7 @@ async function executeDist(context, args) {
5501
5975
  requireNoExtraArgs(pending, "rig dist install [--scope user|system] [--path <dir>]");
5502
5976
  const scope = parseInstallScope(scopeResult.value);
5503
5977
  const installDir = resolveInstallDir(scope, pathResult.value);
5504
- mkdirSync6(installDir, { recursive: true });
5978
+ mkdirSync7(installDir, { recursive: true });
5505
5979
  let source = await findLatestDistBinary(context.projectRoot);
5506
5980
  let buildDir = null;
5507
5981
  if (!source) {
@@ -5556,7 +6030,7 @@ async function executeDist(context, args) {
5556
6030
  const fp = await computeRuntimeImageFingerprint(context.projectRoot);
5557
6031
  const currentId = computeRuntimeImageId(fp);
5558
6032
  const imagesDir = resolve15(resolveControlPlaneMonorepoRuntimeDir(context.projectRoot), "images");
5559
- mkdirSync6(imagesDir, { recursive: true });
6033
+ mkdirSync7(imagesDir, { recursive: true });
5560
6034
  let pruned = 0;
5561
6035
  for (const entry of readdirSync(imagesDir, { withFileTypes: true })) {
5562
6036
  if (entry.isDirectory() && entry.name !== currentId) {
@@ -5568,9 +6042,9 @@ async function executeDist(context, args) {
5568
6042
  console.log(`Pruned ${pruned} stale image(s).`);
5569
6043
  }
5570
6044
  const imageDir = resolve15(imagesDir, currentId);
5571
- mkdirSync6(resolve15(imageDir, "bin/hooks"), { recursive: true });
5572
- mkdirSync6(resolve15(imageDir, "bin/plugins"), { recursive: true });
5573
- mkdirSync6(resolve15(imageDir, "bin/validators"), { recursive: true });
6045
+ mkdirSync7(resolve15(imageDir, "bin/hooks"), { recursive: true });
6046
+ mkdirSync7(resolve15(imageDir, "bin/plugins"), { recursive: true });
6047
+ mkdirSync7(resolve15(imageDir, "bin/validators"), { recursive: true });
5574
6048
  const hookNames = [
5575
6049
  "scope-guard",
5576
6050
  "import-guard",
@@ -5595,8 +6069,8 @@ async function executeDist(context, args) {
5595
6069
  const binPluginsDir = resolve15(resolveControlPlaneHostBinDir(context.projectRoot), "plugins");
5596
6070
  const validatorsRoot = resolve15(hostProjectRoot, "packages/runtime/src/control-plane/validators");
5597
6071
  const binValidatorsDir = resolve15(resolveControlPlaneHostBinDir(context.projectRoot), "validators");
5598
- mkdirSync6(binPluginsDir, { recursive: true });
5599
- mkdirSync6(binValidatorsDir, { recursive: true });
6072
+ mkdirSync7(binPluginsDir, { recursive: true });
6073
+ mkdirSync7(binValidatorsDir, { recursive: true });
5600
6074
  if (existsSync10(pluginsDir)) {
5601
6075
  for (const entry of readdirSync(pluginsDir, { withFileTypes: true })) {
5602
6076
  const m = entry.name.match(/^(.+)\.plugin\.(ts|js|mjs|cjs)$/);
@@ -6035,16 +6509,19 @@ function formatConnectionList(connections) {
6035
6509
  return [formatSection("Rig servers", `${rows.length} available`), `${pc4.bold(pad("ALIAS", aliasWidth))} ${pc4.bold("KIND")} ${pc4.bold("TARGET")}`, ...lines, "", ...formatNextSteps(["Select one: `rig server use <alias|local>`"])].join(`
6036
6510
  `);
6037
6511
  }
6038
- function formatConnectionStatus(selected, connections) {
6512
+ function formatConnectionStatus(selected, connections, repo, remoteProjectLink) {
6039
6513
  const connection = selected === "local" ? { kind: "local", mode: "auto" } : connections[selected];
6040
6514
  const target = !connection ? "not configured" : connection.kind === "remote" ? connection.baseUrl : "local";
6515
+ const rootLabel = remoteProjectLink ? remoteProjectLink.ok ? `ready \xB7 ${remoteProjectLink.serverProjectRoot ?? repo?.serverProjectRoot ?? "linked"}` : `${remoteProjectLink.status ?? "needs repair"} \xB7 ${remoteProjectLink.hint ?? remoteProjectLink.message ?? "run rig server repair-link"}` : repo?.serverProjectRoot ? repo.serverProjectRoot : "not required";
6041
6516
  return [
6042
6517
  formatSection("Rig server", "selected for this repo"),
6043
6518
  `${themeFaint("\u2502")} ${themeDim("selected ")} ${pc4.bold(selected)}`,
6044
6519
  `${themeFaint("\u2502")} ${themeDim("kind ")} ${formatStatusPill(connection?.kind ?? "unknown")}`,
6045
6520
  `${themeFaint("\u2502")} ${themeDim("target ")} ${target ?? "not configured"}`,
6521
+ ...repo?.project ? [`${themeFaint("\u2502")} ${themeDim("project ")} ${repo.project}`] : [],
6522
+ ...connection?.kind === "remote" ? [`${themeFaint("\u2502")} ${themeDim("root ")} ${rootLabel}`] : [],
6046
6523
  "",
6047
- ...formatNextSteps(["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
6524
+ ...formatNextSteps(remoteProjectLink && !remoteProjectLink.ok ? ["Repair remote link: `rig server repair-link`", "Authenticate first if needed: `rig github auth import-gh`"] : ["Change: `rig server use <alias|local>`", "List saved servers: `rig server list`"])
6048
6525
  ].join(`
6049
6526
  `);
6050
6527
  }
@@ -6335,7 +6812,7 @@ var init_inbox = __esm(() => {
6335
6812
 
6336
6813
  // packages/cli/src/commands/_snapshot-upload.ts
6337
6814
  import { mkdir, readdir, readFile, writeFile } from "fs/promises";
6338
- import { dirname as dirname3, resolve as resolve16, relative, sep } from "path";
6815
+ import { dirname as dirname4, resolve as resolve16, relative, sep } from "path";
6339
6816
  function toPosixPath(path) {
6340
6817
  return path.split(sep).join("/");
6341
6818
  }
@@ -6436,7 +6913,7 @@ __export(exports_init, {
6436
6913
  DEMO_TASKS_RELATIVE_DIR: () => DEMO_TASKS_RELATIVE_DIR,
6437
6914
  DEMO_TASKS: () => DEMO_TASKS
6438
6915
  });
6439
- import { appendFileSync as appendFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
6916
+ import { appendFileSync as appendFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
6440
6917
  import { spawnSync } from "child_process";
6441
6918
  import { basename as basename3, resolve as resolve17 } from "path";
6442
6919
  import { buildRigInitConfigSource } from "@rig/core";
@@ -6460,14 +6937,14 @@ function parseRepoSlug(value) {
6460
6937
  }
6461
6938
  function ensureRigPrivateDirs(projectRoot) {
6462
6939
  const rigDir = resolve17(projectRoot, ".rig");
6463
- mkdirSync7(resolve17(rigDir, "state"), { recursive: true });
6464
- mkdirSync7(resolve17(rigDir, "logs"), { recursive: true });
6465
- mkdirSync7(resolve17(rigDir, "runs"), { recursive: true });
6466
- mkdirSync7(resolve17(rigDir, "tmp"), { recursive: true });
6467
- mkdirSync7(resolve17(projectRoot, "artifacts"), { recursive: true });
6940
+ mkdirSync8(resolve17(rigDir, "state"), { recursive: true });
6941
+ mkdirSync8(resolve17(rigDir, "logs"), { recursive: true });
6942
+ mkdirSync8(resolve17(rigDir, "runs"), { recursive: true });
6943
+ mkdirSync8(resolve17(rigDir, "tmp"), { recursive: true });
6944
+ mkdirSync8(resolve17(projectRoot, "artifacts"), { recursive: true });
6468
6945
  const taskConfigPath = resolve17(rigDir, "task-config.json");
6469
6946
  if (!existsSync11(taskConfigPath))
6470
- writeFileSync5(taskConfigPath, `{}
6947
+ writeFileSync6(taskConfigPath, `{}
6471
6948
  `, "utf-8");
6472
6949
  }
6473
6950
  function ensureGitignoreEntries(projectRoot) {
@@ -6495,7 +6972,7 @@ function ensureRigConfigPackageDependencies(projectRoot) {
6495
6972
  ...existsSync11(path) ? existing : { name: "rig-project", private: true },
6496
6973
  devDependencies
6497
6974
  };
6498
- writeFileSync5(path, `${JSON.stringify(next, null, 2)}
6975
+ writeFileSync6(path, `${JSON.stringify(next, null, 2)}
6499
6976
  `, "utf8");
6500
6977
  }
6501
6978
  function applyGitHubProjectConfig(source, options) {
@@ -6521,9 +6998,9 @@ function checkoutForInit(projectRoot, serverKind, strategy) {
6521
6998
  const selected = strategy ?? { kind: "managed-clone" };
6522
6999
  switch (selected.kind) {
6523
7000
  case "managed-clone":
6524
- return { kind: "managed-clone", path: projectRoot };
7001
+ return { kind: "managed-clone" };
6525
7002
  case "current-ref":
6526
- return { kind: "current-ref", path: projectRoot, ...selected.ref ? { ref: selected.ref } : {} };
7003
+ return { kind: "current-ref", ...selected.ref ? { ref: selected.ref } : {} };
6527
7004
  case "uploaded-snapshot":
6528
7005
  return { kind: "uploaded-snapshot", path: projectRoot, source: "local-working-tree" };
6529
7006
  case "existing-path":
@@ -6776,7 +7253,7 @@ function remoteGitHubAuthMetadata(payload) {
6776
7253
  };
6777
7254
  }
6778
7255
  function writeRemoteGitHubAuthState(projectRoot, input) {
6779
- writeFileSync5(resolve17(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
7256
+ writeFileSync6(resolve17(projectRoot, ".rig", "state", "github-auth.json"), `${JSON.stringify({
6780
7257
  authenticated: true,
6781
7258
  source: input.source,
6782
7259
  storedOnServer: true,
@@ -6824,7 +7301,7 @@ function runLocalFilesInit(context, options) {
6824
7301
  console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it.");
6825
7302
  } else {
6826
7303
  const projectName = basename3(projectRoot) || "rig-project";
6827
- writeFileSync5(configTsPath, buildRigInitConfigSource({
7304
+ writeFileSync6(configTsPath, buildRigInitConfigSource({
6828
7305
  projectName,
6829
7306
  taskSource: { kind: "files", path: "tasks" },
6830
7307
  useStandardPlugin: true
@@ -6833,8 +7310,8 @@ function runLocalFilesInit(context, options) {
6833
7310
  ensureRigConfigPackageDependencies(projectRoot);
6834
7311
  const tasksDir = resolve17(projectRoot, "tasks");
6835
7312
  if (!existsSync11(tasksDir)) {
6836
- mkdirSync7(tasksDir, { recursive: true });
6837
- writeFileSync5(resolve17(tasksDir, "T-1.json"), `${JSON.stringify({ id: "T-1", title: "My first Rig task", body: "Describe the change you want an agent to make." }, null, 2)}
7313
+ mkdirSync8(tasksDir, { recursive: true });
7314
+ writeFileSync6(resolve17(tasksDir, "T-1.json"), `${JSON.stringify({ id: "T-1", title: "My first Rig task", body: "Describe the change you want an agent to make." }, null, 2)}
6838
7315
  `, "utf-8");
6839
7316
  }
6840
7317
  if (context.outputMode !== "json") {
@@ -6858,7 +7335,7 @@ function runDemoInit(context, options) {
6858
7335
  console.log("rig.config already exists; leaving it unchanged. Pass --repair to rewrite it for the demo.");
6859
7336
  }
6860
7337
  } else {
6861
- writeFileSync5(configTsPath, buildRigInitConfigSource({
7338
+ writeFileSync6(configTsPath, buildRigInitConfigSource({
6862
7339
  projectName: basename3(projectRoot) || "rig-demo",
6863
7340
  taskSource: { kind: "files", path: DEMO_TASKS_RELATIVE_DIR },
6864
7341
  useStandardPlugin: true
@@ -6867,14 +7344,14 @@ function runDemoInit(context, options) {
6867
7344
  }
6868
7345
  ensureRigConfigPackageDependencies(projectRoot);
6869
7346
  const demoTasksDir = resolve17(projectRoot, DEMO_TASKS_RELATIVE_DIR);
6870
- mkdirSync7(demoTasksDir, { recursive: true });
7347
+ mkdirSync8(demoTasksDir, { recursive: true });
6871
7348
  const taskIds = [];
6872
7349
  for (const task of DEMO_TASKS) {
6873
7350
  const id = String(task.id);
6874
7351
  taskIds.push(id);
6875
7352
  const taskPath = resolve17(demoTasksDir, `${id}.json`);
6876
7353
  if (!existsSync11(taskPath)) {
6877
- writeFileSync5(taskPath, `${JSON.stringify(task, null, 2)}
7354
+ writeFileSync6(taskPath, `${JSON.stringify(task, null, 2)}
6878
7355
  `, "utf-8");
6879
7356
  }
6880
7357
  }
@@ -6902,21 +7379,32 @@ function runDemoInit(context, options) {
6902
7379
  }
6903
7380
  async function runControlPlaneInit(context, options) {
6904
7381
  const projectRoot = context.projectRoot;
6905
- const detectedSlug = options.repoSlug ?? detectOriginRepoSlug(projectRoot);
7382
+ const existingRepoConnection = readRepoConnection(projectRoot);
7383
+ const selectedConnection = (() => {
7384
+ try {
7385
+ return resolveSelectedConnection(projectRoot);
7386
+ } catch {
7387
+ return null;
7388
+ }
7389
+ })();
7390
+ const selectedRemote = selectedConnection?.connection.kind === "remote" ? { alias: selectedConnection.alias, connection: selectedConnection.connection } : null;
7391
+ const detectedSlug = options.repoSlug ?? existingRepoConnection?.project ?? detectOriginRepoSlug(projectRoot);
7392
+ const serverKind = options.server ?? (selectedRemote ? "remote" : "local");
6906
7393
  if (!detectedSlug) {
6907
7394
  const authMethod2 = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
6908
- if ((options.server ?? "local") === "local" && authMethod2 === "skip") {
7395
+ if (serverKind === "local" && authMethod2 === "skip") {
6909
7396
  return runLocalFilesInit(context, options);
6910
7397
  }
6911
7398
  throw new CliError("Could not detect GitHub repo slug from origin. Pass --repo owner/repo \u2014 or run `rig init --yes --server local --github-auth skip` for a local files-source project without GitHub.", 1);
6912
7399
  }
6913
7400
  const repo = parseRepoSlug(detectedSlug);
6914
- const serverKind = options.server ?? "local";
6915
- const connectionAlias = options.connectionAlias ?? (serverKind === "local" ? "local" : "remote");
7401
+ const connectionAlias = options.connectionAlias ?? (serverKind === "local" ? "local" : selectedRemote?.alias ?? "remote");
7402
+ const selectedRemoteUrl = selectedRemote && (!options.connectionAlias || options.connectionAlias === selectedRemote.alias) ? selectedRemote.connection.baseUrl : undefined;
7403
+ const remoteUrl = serverKind === "remote" ? (options.remoteUrl ?? selectedRemoteUrl)?.replace(/\/+$/, "") : undefined;
6916
7404
  if (serverKind === "remote") {
6917
- if (!options.remoteUrl)
6918
- throw new CliError("Missing --remote-url for --server remote.", 1, { hint: "Re-run as `rig init --server remote --remote-url https://your-rig-server`." });
6919
- upsertGlobalConnection(connectionAlias, { kind: "remote", baseUrl: options.remoteUrl });
7405
+ if (!remoteUrl)
7406
+ throw new CliError("Missing --remote-url for --server remote.", 1, { hint: "Re-run as `rig init --server remote --remote-url https://your-rig-server`, or first select a saved remote with `rig server use <alias>`." });
7407
+ upsertGlobalConnection(connectionAlias, { kind: "remote", baseUrl: remoteUrl });
6920
7408
  }
6921
7409
  writeRepoConnection(projectRoot, {
6922
7410
  selected: connectionAlias,
@@ -6939,11 +7427,11 @@ async function runControlPlaneInit(context, options) {
6939
7427
  taskSource: { kind: "github-issues", owner: repo.owner, repo: repo.repo },
6940
7428
  useStandardPlugin: true
6941
7429
  }), options);
6942
- writeFileSync5(configTsPath, source, "utf-8");
7430
+ writeFileSync6(configTsPath, source, "utf-8");
6943
7431
  }
6944
7432
  ensureRigConfigPackageDependencies(projectRoot);
6945
7433
  }
6946
- writeFileSync5(resolve17(projectRoot, ".rig", "state", "project-link.json"), `${JSON.stringify({ repoSlug: repo.slug, connection: connectionAlias, linkedAt: new Date().toISOString() }, null, 2)}
7434
+ writeFileSync6(resolve17(projectRoot, ".rig", "state", "project-link.json"), `${JSON.stringify({ repoSlug: repo.slug, connection: connectionAlias, linkedAt: new Date().toISOString() }, null, 2)}
6947
7435
  `, "utf8");
6948
7436
  const checkout = checkoutForInit(projectRoot, serverKind, options.remoteCheckout);
6949
7437
  let uploadedSnapshot = null;
@@ -6958,7 +7446,7 @@ async function runControlPlaneInit(context, options) {
6958
7446
  let githubAuth = null;
6959
7447
  let deviceAuth = null;
6960
7448
  const authMethod = options.githubAuthMethod ?? (options.githubToken ? "token" : "skip");
6961
- const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${options.remoteUrl ?? "the remote Rig server"}.` : null;
7449
+ const remoteGhTokenWarning = serverKind === "remote" && authMethod === "gh" ? `This sends a GitHub token from this machine to ${remoteUrl ?? "the remote Rig server"}.` : null;
6962
7450
  if (remoteGhTokenWarning && !options.yes) {
6963
7451
  throw new CliError(`${remoteGhTokenWarning} Re-run with --yes to confirm this explicit token transfer.`, 1);
6964
7452
  }
@@ -7338,8 +7826,8 @@ var init_init = __esm(() => {
7338
7826
 
7339
7827
  // packages/cli/src/commands/github.ts
7340
7828
  import { spawnSync as spawnSync2 } from "child_process";
7341
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync6 } from "fs";
7342
- import { dirname as dirname4, resolve as resolve18 } from "path";
7829
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "fs";
7830
+ import { dirname as dirname5, resolve as resolve18 } from "path";
7343
7831
  function printPayload(context, payload, fallback) {
7344
7832
  if (context.outputMode === "json")
7345
7833
  console.log(JSON.stringify(payload, null, 2));
@@ -7378,8 +7866,8 @@ function persistRemoteAuthSession(context, source, result, fallbackToken) {
7378
7866
  return;
7379
7867
  const repo = readRepoConnection(context.projectRoot);
7380
7868
  const path = resolve18(context.projectRoot, ".rig", "state", "github-auth.json");
7381
- mkdirSync8(dirname4(path), { recursive: true });
7382
- writeFileSync6(path, `${JSON.stringify({
7869
+ mkdirSync9(dirname5(path), { recursive: true });
7870
+ writeFileSync7(path, `${JSON.stringify({
7383
7871
  authenticated: true,
7384
7872
  source,
7385
7873
  storedOnServer: true,
@@ -8882,7 +9370,15 @@ var init__operator_surface = __esm(() => {
8882
9370
  });
8883
9371
 
8884
9372
  // packages/cli/src/commands/_pi-frontend.ts
8885
- import { existsSync as existsSync14, mkdirSync as mkdirSync9, mkdtempSync, readFileSync as readFileSync10, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
9373
+ var exports__pi_frontend = {};
9374
+ __export(exports__pi_frontend, {
9375
+ shouldRequireOperatorTranscript: () => shouldRequireOperatorTranscript,
9376
+ runWithProcessExitGuard: () => runWithProcessExitGuard,
9377
+ missingOperatorTranscriptMessage: () => missingOperatorTranscriptMessage,
9378
+ buildOperatorPiEnv: () => buildOperatorPiEnv,
9379
+ attachRunBundledPiFrontend: () => attachRunBundledPiFrontend
9380
+ });
9381
+ import { existsSync as existsSync14, mkdirSync as mkdirSync10, mkdtempSync, readFileSync as readFileSync10, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
8886
9382
  import { homedir as homedir6, tmpdir } from "os";
8887
9383
  import { join as join3 } from "path";
8888
9384
  import { main as runPiMain } from "@earendil-works/pi-coding-agent";
@@ -8934,7 +9430,7 @@ function statusFromRunDetails(run) {
8934
9430
  async function prepareOperatorConsole(context, runId, tempSessionDir) {
8935
9431
  const server = await ensureServerForCli(context.projectRoot);
8936
9432
  const localCwd = join3(homedir6(), ".rig", "drones", runId.slice(0, 8));
8937
- mkdirSync9(localCwd, { recursive: true });
9433
+ mkdirSync10(localCwd, { recursive: true });
8938
9434
  trustDroneCwd(localCwd);
8939
9435
  installRigPiTheme();
8940
9436
  let sessionFileArg = [];
@@ -8956,7 +9452,7 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
8956
9452
  return line;
8957
9453
  }).join(`
8958
9454
  `);
8959
- writeFileSync7(localSessionPath, content);
9455
+ writeFileSync8(localSessionPath, content);
8960
9456
  sessionFileArg = ["--session", localSessionPath];
8961
9457
  }
8962
9458
  } catch {}
@@ -8972,12 +9468,12 @@ async function prepareOperatorConsole(context, runId, tempSessionDir) {
8972
9468
  function trustDroneCwd(localCwd) {
8973
9469
  try {
8974
9470
  const agentDir = join3(homedir6(), ".pi", "agent");
8975
- mkdirSync9(agentDir, { recursive: true });
9471
+ mkdirSync10(agentDir, { recursive: true });
8976
9472
  const trustPath = join3(agentDir, "trust.json");
8977
9473
  const store = existsSync14(trustPath) ? JSON.parse(readFileSync10(trustPath, "utf8")) : {};
8978
9474
  if (store[localCwd] !== true) {
8979
9475
  store[localCwd] = true;
8980
- writeFileSync7(trustPath, `${JSON.stringify(store, null, "\t")}
9476
+ writeFileSync8(trustPath, `${JSON.stringify(store, null, "\t")}
8981
9477
  `);
8982
9478
  }
8983
9479
  } catch {}
@@ -8985,15 +9481,38 @@ function trustDroneCwd(localCwd) {
8985
9481
  function installRigPiTheme() {
8986
9482
  try {
8987
9483
  const themesDir = join3(homedir6(), ".pi", "agent", "themes");
8988
- mkdirSync9(themesDir, { recursive: true });
9484
+ mkdirSync10(themesDir, { recursive: true });
8989
9485
  const themePath = join3(themesDir, "rig.json");
8990
9486
  const next = `${JSON.stringify(RIG_PI_THEME, null, "\t")}
8991
9487
  `;
8992
9488
  if (!existsSync14(themePath) || readFileSync10(themePath, "utf8") !== next) {
8993
- writeFileSync7(themePath, next);
9489
+ writeFileSync8(themePath, next);
8994
9490
  }
8995
9491
  } catch {}
8996
9492
  }
9493
+ async function runWithProcessExitGuard(body) {
9494
+ const realExit = process.exit;
9495
+ let exitCode = 0;
9496
+ let signalQuit = () => {};
9497
+ const quit = new Promise((resolve22) => {
9498
+ signalQuit = resolve22;
9499
+ });
9500
+ const guardedExit = (code) => {
9501
+ exitCode = typeof code === "number" ? code : 0;
9502
+ signalQuit();
9503
+ return;
9504
+ };
9505
+ process.exit = guardedExit;
9506
+ try {
9507
+ await Promise.race([Promise.resolve().then(body), quit]);
9508
+ } finally {
9509
+ process.exit = realExit;
9510
+ }
9511
+ return exitCode;
9512
+ }
9513
+ function runPiMainReturningOnQuit(args, options) {
9514
+ return runWithProcessExitGuard(() => runPiMain(args, options));
9515
+ }
8997
9516
  async function attachRunBundledPiFrontend(context, input) {
8998
9517
  const tempSessionDir = mkdtempSync(join3(tmpdir(), "rig-pi-frontend-sessions-"));
8999
9518
  const { server, sessionFileArg } = await withSpinner(`Opening Pi console for run ${input.runId}\u2026`, () => prepareOperatorConsole(context, input.runId, tempSessionDir), { outputMode: context.outputMode });
@@ -9009,16 +9528,20 @@ async function attachRunBundledPiFrontend(context, input) {
9009
9528
  };
9010
9529
  let detached = false;
9011
9530
  try {
9012
- await runPiMain([
9531
+ const piArgs = [
9013
9532
  "--offline",
9014
9533
  "--no-extensions",
9015
9534
  "--no-skills",
9016
9535
  "--no-prompt-templates",
9017
9536
  "--no-context-files",
9018
9537
  ...sessionFileArg
9019
- ], {
9020
- extensionFactories: [piRigExtensionFactory]
9021
- });
9538
+ ];
9539
+ const piOptions = { extensionFactories: [piRigExtensionFactory] };
9540
+ if (input.returnOnQuit) {
9541
+ await runPiMainReturningOnQuit(piArgs, piOptions);
9542
+ } else {
9543
+ await runPiMain(piArgs, piOptions);
9544
+ }
9022
9545
  detached = true;
9023
9546
  } finally {
9024
9547
  restoreEnv();
@@ -9747,13 +10270,46 @@ function parseConnection(alias, value, options) {
9747
10270
  }
9748
10271
  return { kind: "remote", baseUrl: parsed.toString().replace(/\/+$/, "") };
9749
10272
  }
9750
- function printJsonOrText(context, payload, text2) {
9751
- if (context.outputMode === "json") {
9752
- console.log(JSON.stringify(payload, null, 2));
9753
- } else {
10273
+ function printJsonOrText(context, _payload, text2) {
10274
+ if (context.outputMode !== "json") {
9754
10275
  console.log(text2);
9755
10276
  }
9756
10277
  }
10278
+ function formatRemoteProjectLinkText(result) {
10279
+ return formatSuccessCard(result.ok ? "Remote project link ready" : "Remote project link needs repair", [
10280
+ ["selected", result.alias ?? "remote"],
10281
+ ["target", result.baseUrl ?? "unknown"],
10282
+ ["repo", result.repoSlug ?? "owner/repo"],
10283
+ ["status", result.status],
10284
+ ...result.serverProjectRoot ? [["server root", result.serverProjectRoot]] : [],
10285
+ ["next", result.ok ? result.next ?? "rig task list" : result.hint]
10286
+ ]);
10287
+ }
10288
+ function parseRepairLinkArgs(rest, usage) {
10289
+ let repoSlug;
10290
+ let mode = "prepare-if-missing";
10291
+ const pending = [...rest];
10292
+ while (pending.length > 0) {
10293
+ const token = pending.shift();
10294
+ if (token === "--repo") {
10295
+ const value = pending.shift()?.trim();
10296
+ if (!value)
10297
+ throw new CliError(`Missing --repo value. Usage: ${usage}`, 1);
10298
+ const normalized = normalizeRepoSlug(value);
10299
+ if (!normalized)
10300
+ throw new CliError(`Invalid --repo value: ${value}`, 1, { hint: "Use the canonical owner/repo slug, e.g. `--repo humanity-org/humanwork`." });
10301
+ repoSlug = normalized;
10302
+ } else if (token === "--backfill-only") {
10303
+ mode = "backfill-only";
10304
+ } else if (token === "--prepare") {
10305
+ mode = "prepare-if-missing";
10306
+ } else {
10307
+ throw new CliError(`Unexpected argument: ${String(token)}
10308
+ Usage: ${usage}`, 1);
10309
+ }
10310
+ }
10311
+ return { repoSlug, mode };
10312
+ }
9757
10313
  async function promptForConnectionAlias(context) {
9758
10314
  const state = readGlobalConnections();
9759
10315
  const repo = readRepoConnection(context.projectRoot);
@@ -9807,37 +10363,67 @@ async function executeConnectionCommand(context, args, options) {
9807
10363
  }
9808
10364
  if (!alias)
9809
10365
  throw new CliError(`Missing alias. Usage: ${usageName(options)} use <alias|local>`, 1);
10366
+ let selectedConnection = { kind: "local", mode: "auto" };
9810
10367
  if (alias !== "local") {
9811
10368
  const state = readGlobalConnections();
9812
- if (!state.connections[alias])
10369
+ const connection = state.connections[alias];
10370
+ if (!connection)
9813
10371
  throw new CliError(`Unknown Rig server: ${alias}`, 1, { hint: "Run `rig server list` to see saved aliases, or save one with `rig server add <alias> <url>`." });
10372
+ selectedConnection = connection;
9814
10373
  }
9815
10374
  const previousRepo = readRepoConnection(context.projectRoot);
10375
+ const preserveRemoteRoot = Boolean(previousRepo?.serverProjectRoot && previousRepo.selected === alias && selectedConnection.kind === "remote" && previousRepo.serverProjectRootAlias === alias && previousRepo.serverProjectRootBaseUrl === selectedConnection.baseUrl);
9816
10376
  const repoState = {
9817
10377
  selected: alias,
9818
10378
  ...previousRepo?.project ? { project: previousRepo.project } : {},
9819
10379
  linkedAt: new Date().toISOString(),
9820
- ...previousRepo?.serverProjectRoot && previousRepo.selected === alias ? { serverProjectRoot: previousRepo.serverProjectRoot } : {}
10380
+ ...preserveRemoteRoot ? {
10381
+ serverProjectRoot: previousRepo.serverProjectRoot,
10382
+ serverProjectRootAlias: previousRepo.serverProjectRootAlias,
10383
+ serverProjectRootBaseUrl: previousRepo.serverProjectRootBaseUrl
10384
+ } : {}
9821
10385
  };
9822
10386
  writeRepoConnection(context.projectRoot, repoState);
9823
- printJsonOrText(context, repoState, formatSuccessCard("Rig server selected", [
10387
+ const remoteProjectLink = alias !== "local" ? await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only" }).catch((error) => ({
10388
+ ok: false,
10389
+ status: "error",
10390
+ message: error instanceof Error ? error.message : String(error),
10391
+ hint: "Run `rig server repair-link` after authenticating the selected remote."
10392
+ })) : null;
10393
+ const latestRepo = readRepoConnection(context.projectRoot);
10394
+ const details = { selected: latestRepo?.selected ?? alias, repo: latestRepo, remoteProjectLink };
10395
+ printJsonOrText(context, details, formatSuccessCard("Rig server selected", [
9824
10396
  ["selected", alias],
9825
10397
  ["scope", "this repo"],
9826
- ["next", "rig task list"]
10398
+ ...remoteProjectLink ? [["remote link", remoteProjectLink.ok ? `ready (${remoteProjectLink.serverProjectRoot ?? "linked"})` : `${remoteProjectLink.status}: ${remoteProjectLink.hint}`]] : [],
10399
+ ["next", remoteProjectLink && !remoteProjectLink.ok ? "rig server repair-link" : "rig task list"]
9827
10400
  ]));
9828
- return { ok: true, group: options.group, command: "use", details: repoState };
10401
+ return { ok: true, group: options.group, command: "use", details };
10402
+ }
10403
+ case "repair-link": {
10404
+ const parsed = parseRepairLinkArgs(rest, `${usageName(options)} repair-link [--prepare|--backfill-only] [--repo owner/repo]`);
10405
+ const result = await repairRemoteProjectRootLink(context, parsed);
10406
+ printJsonOrText(context, result, formatRemoteProjectLinkText(result));
10407
+ return { ok: true, group: options.group, command: "repair-link", details: result };
9829
10408
  }
9830
10409
  case "status": {
9831
10410
  requireNoExtraArgs(rest, `${usageName(options)} status`);
9832
10411
  const repo = readRepoConnection(context.projectRoot);
9833
10412
  const global = readGlobalConnections();
9834
- const details = { selected: repo?.selected ?? "local", repo, connections: global.connections };
9835
- printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections));
10413
+ const remoteProjectLink = repo?.selected && repo.selected !== "local" ? await ensureRemoteProjectRootLink(context.projectRoot, { mode: "backfill-only" }).catch((error) => ({
10414
+ ok: false,
10415
+ status: "error",
10416
+ message: error instanceof Error ? error.message : String(error),
10417
+ hint: "Run `rig server repair-link` after authenticating the selected remote."
10418
+ })) : null;
10419
+ const latestRepo = readRepoConnection(context.projectRoot) ?? repo;
10420
+ const details = { selected: latestRepo?.selected ?? "local", repo: latestRepo, connections: global.connections, remoteProjectLink };
10421
+ printJsonOrText(context, details, formatConnectionStatus(details.selected, global.connections, latestRepo ?? null, remoteProjectLink));
9836
10422
  return { ok: true, group: options.group, command: "status", details };
9837
10423
  }
9838
10424
  default:
9839
10425
  throw new CliError(`Unknown ${options.group} command: ${String(command)}
9840
- Usage: ${usageName(options)} <list|add|use|status>`, 1);
10426
+ Usage: ${usageName(options)} <list|add|use|status|repair-link>`, 1);
9841
10427
  }
9842
10428
  }
9843
10429
  var init_connect = __esm(() => {
@@ -9845,13 +10431,14 @@ var init_connect = __esm(() => {
9845
10431
  init_runner();
9846
10432
  init__connection_state();
9847
10433
  init__cli_format();
10434
+ init__server_client();
9848
10435
  });
9849
10436
 
9850
10437
  // packages/cli/src/commands/server.ts
9851
10438
  import { resolveRigServerCommand } from "@rig/runtime/local-server";
9852
10439
  async function executeServer(context, args, options) {
9853
10440
  const [command = "status", ...rest] = args;
9854
- if (["status", "list", "add", "use"].includes(command)) {
10441
+ if (["status", "list", "add", "use", "repair-link"].includes(command)) {
9855
10442
  return executeConnectionCommand(context, [command, ...rest], { group: "server", interactiveUse: true });
9856
10443
  }
9857
10444
  switch (command) {
@@ -10638,7 +11225,7 @@ var init_task = __esm(() => {
10638
11225
  });
10639
11226
 
10640
11227
  // packages/cli/src/commands/task-run-driver.ts
10641
- import { copyFileSync as copyFileSync3, existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync12, statSync as statSync2, writeFileSync as writeFileSync8 } from "fs";
11228
+ import { copyFileSync as copyFileSync3, existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync12, statSync as statSync2, writeFileSync as writeFileSync9 } from "fs";
10642
11229
  import { resolve as resolve23 } from "path";
10643
11230
  import { spawn as spawn2, spawnSync as spawnSync4 } from "child_process";
10644
11231
  import { createInterface as createLineInterface } from "readline";
@@ -10741,7 +11328,7 @@ function copyUntrackedDirtyFiles(sourceRoot, targetRoot) {
10741
11328
  try {
10742
11329
  if (!statSync2(sourcePath).isFile())
10743
11330
  continue;
10744
- mkdirSync10(resolve23(targetPath, ".."), { recursive: true });
11331
+ mkdirSync11(resolve23(targetPath, ".."), { recursive: true });
10745
11332
  copyFileSync3(sourcePath, targetPath);
10746
11333
  copied += 1;
10747
11334
  } catch {}
@@ -11612,8 +12199,8 @@ async function executeRigOwnedTaskRun(context, input) {
11612
12199
  artifactPath: planningClassification.planningRequired ? planningArtifactPath : null,
11613
12200
  classifiedAt: new Date().toISOString()
11614
12201
  };
11615
- mkdirSync10(resolve23(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
11616
- writeFileSync8(resolve23(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
12202
+ mkdirSync11(resolve23(context.projectRoot, ".rig", "runs", input.runId), { recursive: true });
12203
+ writeFileSync9(resolve23(context.projectRoot, ".rig", "runs", input.runId, "planning-classification.json"), `${JSON.stringify(persistedPlanning, null, 2)}
11617
12204
  `, "utf8");
11618
12205
  patchAuthorityRun(context.projectRoot, input.runId, { planning: persistedPlanning });
11619
12206
  prompt = `${prompt}
@@ -11760,8 +12347,8 @@ ${planningClassification.planningRequired ? `Before implementing, write a concis
11760
12347
  const dirty = applyDirtyBaselineSnapshot({ sourceRoot: context.projectRoot, targetRoot: latestRuntimeWorkspace });
11761
12348
  const readyFile = childEnv.RIG_DIRTY_BASELINE_READY_FILE;
11762
12349
  if (readyFile) {
11763
- mkdirSync10(resolve23(readyFile, ".."), { recursive: true });
11764
- writeFileSync8(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
12350
+ mkdirSync11(resolve23(readyFile, ".."), { recursive: true });
12351
+ writeFileSync9(readyFile, `${JSON.stringify({ ...dirty, workspaceDir: latestRuntimeWorkspace, appliedAt: new Date().toISOString() }, null, 2)}
11765
12352
  `, "utf8");
11766
12353
  }
11767
12354
  appendRunLog(context.projectRoot, input.runId, {
@@ -12707,7 +13294,7 @@ var init_test = __esm(() => {
12707
13294
  });
12708
13295
 
12709
13296
  // packages/cli/src/commands/setup.ts
12710
- import { existsSync as existsSync16, mkdirSync as mkdirSync11, readdirSync as readdirSync3, writeFileSync as writeFileSync9 } from "fs";
13297
+ import { existsSync as existsSync16, mkdirSync as mkdirSync12, readdirSync as readdirSync3, writeFileSync as writeFileSync10 } from "fs";
12711
13298
  import { resolve as resolve24 } from "path";
12712
13299
  import { createPluginHost } from "@rig/core";
12713
13300
  import {
@@ -12762,12 +13349,12 @@ function runSetupInit(projectRoot) {
12762
13349
  const stateDir = resolveControlPlaneHostStateDir(projectRoot);
12763
13350
  const logsDir = resolveControlPlaneHostLogsDir(projectRoot);
12764
13351
  const artifactsDir = resolveControlPlaneArtifactsDir(projectRoot);
12765
- mkdirSync11(stateDir, { recursive: true });
12766
- mkdirSync11(logsDir, { recursive: true });
12767
- mkdirSync11(artifactsDir, { recursive: true });
13352
+ mkdirSync12(stateDir, { recursive: true });
13353
+ mkdirSync12(logsDir, { recursive: true });
13354
+ mkdirSync12(artifactsDir, { recursive: true });
12768
13355
  const failuresPath = resolve24(stateDir, "failed_approaches.md");
12769
13356
  if (!existsSync16(failuresPath)) {
12770
- writeFileSync9(failuresPath, `# Failed Approaches
13357
+ writeFileSync10(failuresPath, `# Failed Approaches
12771
13358
 
12772
13359
  `, "utf-8");
12773
13360
  }
@@ -14284,414 +14871,30 @@ async function stopFleetRun(ctx, runId) {
14284
14871
  }
14285
14872
  var init_fleet = () => {};
14286
14873
 
14287
- // packages/cli/src/app-opentui/pi-pty-host.ts
14288
- import { fileURLToPath as fileURLToPath2 } from "url";
14289
- import { basename as basename4 } from "path";
14290
- import { RGBA, StyledText, TextAttributes } from "@opentui/core";
14291
- import { Terminal as XtermTerminal2 } from "@xterm/headless";
14292
- function clampCols2(cols) {
14293
- return Math.max(MIN_COLS2, Math.trunc(cols || 100));
14294
- }
14295
- function clampRows2(rows) {
14296
- return Math.max(MIN_ROWS2, Math.min(MAX_ROWS2, Math.trunc(rows || 35)));
14874
+ // packages/cli/src/app-opentui/adapters/inspect.ts
14875
+ var exports_inspect = {};
14876
+ __export(exports_inspect, {
14877
+ loadInspectRuns: () => loadInspectRuns,
14878
+ loadInspect: () => loadInspect,
14879
+ INSPECT_VIEWS: () => INSPECT_VIEWS
14880
+ });
14881
+ import { execFile } from "child_process";
14882
+ import { existsSync as existsSync20, readFileSync as readFileSync14, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
14883
+ import { resolve as resolve27 } from "path";
14884
+ import { promisify } from "util";
14885
+ import { resolveTaskArtifactDirs as resolveTaskArtifactDirs3 } from "@rig/runtime/control-plane/authority-files";
14886
+ import { readTaskArtifactPreview as readTaskArtifactPreview2 } from "@rig/runtime/control-plane/native/workspace-ops";
14887
+ import { resolveHarnessPaths as resolveHarnessPaths3, resolveMonorepoRoot as resolveMonorepoRoot3, runCapture as runCapture4 } from "@rig/runtime/control-plane/native/utils";
14888
+ function isFailureEntry(entry) {
14889
+ const status = `${entry.status ?? entry.level ?? entry.type ?? ""}`.toLowerCase();
14890
+ return status.includes("fail") || status.includes("error");
14297
14891
  }
14298
- function rgbaFromXtermColor(mode, value) {
14299
- if (mode === 1 || mode === 2 || mode === XTERM_COLOR_MODE_INDEXED_16 || mode === XTERM_COLOR_MODE_INDEXED_256) {
14300
- return RGBA.fromIndex(value);
14301
- }
14302
- if (mode === 3 || mode === XTERM_COLOR_MODE_RGB) {
14303
- return RGBA.fromInts(value >> 16 & 255, value >> 8 & 255, value & 255);
14304
- }
14305
- return;
14306
- }
14307
- function textAttributesFromCell(cell) {
14308
- let attributes = TextAttributes.NONE;
14309
- if (cell.isBold())
14310
- attributes |= TextAttributes.BOLD;
14311
- if (cell.isDim())
14312
- attributes |= TextAttributes.DIM;
14313
- if (cell.isItalic())
14314
- attributes |= TextAttributes.ITALIC;
14315
- if (cell.isUnderline())
14316
- attributes |= TextAttributes.UNDERLINE;
14317
- if (cell.isBlink())
14318
- attributes |= TextAttributes.BLINK;
14319
- if (cell.isInverse())
14320
- attributes |= TextAttributes.INVERSE;
14321
- if (cell.isInvisible())
14322
- attributes |= TextAttributes.HIDDEN;
14323
- if (cell.isStrikethrough())
14324
- attributes |= TextAttributes.STRIKETHROUGH;
14325
- return attributes;
14326
- }
14327
- function sameRgba(a, b) {
14328
- if (!a && !b)
14329
- return true;
14330
- return Boolean(a && b && a.equals(b));
14331
- }
14332
- function sameStyle(a, b) {
14333
- return sameRgba(a.fg, b.fg) && sameRgba(a.bg, b.bg) && (a.attributes ?? 0) === (b.attributes ?? 0);
14334
- }
14335
- function styleFromCell(cell) {
14336
- return {
14337
- fg: rgbaFromXtermColor(cell.getFgColorMode(), cell.getFgColor()),
14338
- bg: rgbaFromXtermColor(cell.getBgColorMode(), cell.getBgColor()),
14339
- attributes: textAttributesFromCell(cell)
14340
- };
14341
- }
14342
- function lineToStyledText(line, cols) {
14343
- if (!line)
14344
- return new StyledText([{ __isChunk: true, text: "" }]);
14345
- const chunks = [];
14346
- let run = "";
14347
- let runStyle = null;
14348
- const flush = () => {
14349
- if (!run)
14350
- return;
14351
- chunks.push({
14352
- __isChunk: true,
14353
- text: run,
14354
- ...runStyle?.fg ? { fg: runStyle.fg } : {},
14355
- ...runStyle?.bg ? { bg: runStyle.bg } : {},
14356
- ...(runStyle?.attributes ?? 0) !== 0 ? { attributes: runStyle.attributes } : {}
14357
- });
14358
- run = "";
14359
- };
14360
- for (let index = 0;index < cols; index += 1) {
14361
- const cell = line.getCell(index);
14362
- if (!cell) {
14363
- if (runStyle !== null)
14364
- flush();
14365
- runStyle = null;
14366
- run += " ";
14367
- continue;
14368
- }
14369
- if (cell.getWidth() === 0)
14370
- continue;
14371
- const style = styleFromCell(cell);
14372
- if (!runStyle || !sameStyle(runStyle, style)) {
14373
- flush();
14374
- runStyle = style;
14375
- }
14376
- run += cell.getChars() || " ";
14377
- }
14378
- flush();
14379
- return new StyledText(chunks.length > 0 ? chunks : [{ __isChunk: true, text: "" }]);
14380
- }
14381
- function childCommandPrefix2() {
14382
- const execName = basename4(process.execPath).toLowerCase();
14383
- const currentEntry = process.argv[1];
14384
- if (execName === "bun" || execName === "bun.exe") {
14385
- return currentEntry ? [process.execPath, currentEntry, "__opentui-pi-host"] : [process.execPath, fallbackChildScriptPath];
14386
- }
14387
- return [process.execPath, "__opentui-pi-host"];
14388
- }
14389
- function withEnv2(base) {
14390
- const env = {};
14391
- for (const [key, value] of Object.entries(base ?? process.env)) {
14392
- if (key === "RIG_PLAIN" || key === "RIG_CLI_PLAIN_HELP" || key === "NO_COLOR")
14393
- continue;
14394
- if (typeof value === "string")
14395
- env[key] = value;
14396
- }
14397
- return {
14398
- ...env,
14399
- TERM: "xterm-256color",
14400
- COLORTERM: "truecolor",
14401
- FORCE_COLOR: env.FORCE_COLOR ?? "1",
14402
- RIG_OPENTUI_PI_HOST: "1"
14403
- };
14404
- }
14405
- async function startPiPtyHost(options) {
14406
- activeHost2?.dispose("replace");
14407
- const host = new PiPtyHost(options);
14408
- activeHost2 = host;
14409
- await host.start();
14410
- return host;
14411
- }
14412
-
14413
- class PiPtyHost {
14414
- runId;
14415
- projectRoot;
14416
- onSnapshot;
14417
- onExit;
14418
- onError;
14419
- terminal;
14420
- disposables = [];
14421
- decoder = new TextDecoder("utf-8");
14422
- proc = null;
14423
- pty = null;
14424
- status = "starting";
14425
- cols;
14426
- rows;
14427
- message = "starting bundled Pi";
14428
- lastResizeError = null;
14429
- exitCode;
14430
- signal;
14431
- notifyTimer = null;
14432
- lastStreamKey = "";
14433
- _disposed = false;
14434
- constructor(options) {
14435
- this.runId = options.runId;
14436
- this.projectRoot = options.projectRoot;
14437
- this.cols = clampCols2(options.cols);
14438
- this.rows = clampRows2(options.rows);
14439
- this.onSnapshot = options.onSnapshot;
14440
- this.onExit = options.onExit;
14441
- this.onError = options.onError;
14442
- this.terminal = new XtermTerminal2({
14443
- allowProposedApi: true,
14444
- cols: this.cols,
14445
- rows: this.rows,
14446
- scrollback: 1000
14447
- });
14448
- this.registerTerminalResponders();
14449
- this.disposables.push(this.terminal.onWriteParsed(() => {
14450
- if (this._disposed)
14451
- return;
14452
- const snapshot = this.createSnapshot();
14453
- const key = snapshot.lines.join(`
14454
- `);
14455
- if (key === this.lastStreamKey)
14456
- return;
14457
- this.lastStreamKey = key;
14458
- this.onSnapshot?.(snapshot);
14459
- }));
14460
- }
14461
- get disposed() {
14462
- return this._disposed;
14463
- }
14464
- get snapshot() {
14465
- return this.createSnapshot();
14466
- }
14467
- async start() {
14468
- if (this._disposed)
14469
- throw new Error("Pi PTY host is disposed.");
14470
- if (typeof Bun.Terminal !== "function") {
14471
- throw new Error("Bun native PTY is unavailable in this runtime. Pi host requires Bun.Terminal.");
14472
- }
14473
- const spawnOptions = {
14474
- cwd: this.projectRoot,
14475
- env: withEnv2(process.env),
14476
- terminal: {
14477
- cols: this.cols,
14478
- rows: this.rows,
14479
- name: "xterm-256color",
14480
- data: (_terminal, data) => this.handlePtyData(data)
14481
- }
14482
- };
14483
- const proc = Bun.spawn([
14484
- ...childCommandPrefix2(),
14485
- "--run-id",
14486
- this.runId,
14487
- "--project-root",
14488
- this.projectRoot
14489
- ], spawnOptions);
14490
- if (!proc.terminal)
14491
- throw new Error("Bun did not attach a terminal to the bundled Pi child process.");
14492
- this.proc = proc;
14493
- this.pty = proc.terminal;
14494
- this.status = "running";
14495
- this.message = "bundled Pi running inside this app";
14496
- this.emitSnapshotSoon(0);
14497
- proc.exited.then((exitCode) => {
14498
- if (this._disposed)
14499
- return;
14500
- this.status = exitCode === 0 ? "exited" : "failed";
14501
- this.exitCode = exitCode;
14502
- this.signal = null;
14503
- this.message = exitCode === 0 ? "bundled Pi exited" : `bundled Pi exited with code ${exitCode}`;
14504
- const snapshot = this.createSnapshot();
14505
- this.onSnapshot?.(snapshot);
14506
- this.onExit?.(snapshot);
14507
- if (activeHost2 === this)
14508
- activeHost2 = null;
14509
- this.dispose("exit", { kill: false, notify: false });
14510
- }).catch((error) => {
14511
- if (this._disposed)
14512
- return;
14513
- this.status = "failed";
14514
- this.message = error instanceof Error ? error.message : String(error);
14515
- const snapshot = this.createSnapshot();
14516
- this.onSnapshot?.(snapshot);
14517
- this.onError?.(error, snapshot);
14518
- if (activeHost2 === this)
14519
- activeHost2 = null;
14520
- this.dispose("error", { kill: false, notify: false });
14521
- });
14522
- }
14523
- write(data) {
14524
- if (this._disposed || !this.pty)
14525
- return;
14526
- try {
14527
- this.pty.write(data);
14528
- } catch (error) {
14529
- this.status = "failed";
14530
- this.message = error instanceof Error ? error.message : String(error);
14531
- const snapshot = this.createSnapshot();
14532
- this.onSnapshot?.(snapshot);
14533
- this.onError?.(error, snapshot);
14534
- }
14535
- }
14536
- resize(cols, rows) {
14537
- const nextCols = clampCols2(cols);
14538
- const nextRows = clampRows2(rows);
14539
- if (nextCols === this.cols && nextRows === this.rows)
14540
- return;
14541
- this.cols = nextCols;
14542
- this.rows = nextRows;
14543
- this.terminal.resize(nextCols, nextRows);
14544
- try {
14545
- this.pty?.resize(nextCols, nextRows);
14546
- this.lastResizeError = null;
14547
- } catch (error) {
14548
- this.lastResizeError = error instanceof Error ? error.message : String(error);
14549
- }
14550
- this.emitSnapshotSoon(0);
14551
- }
14552
- detach() {
14553
- this.dispose("detach");
14554
- return this.createSnapshot("detached from bundled Pi");
14555
- }
14556
- dispose(reason = "dispose", options = {}) {
14557
- if (this._disposed)
14558
- return;
14559
- this._disposed = true;
14560
- if (this.notifyTimer)
14561
- clearTimeout(this.notifyTimer);
14562
- this.notifyTimer = null;
14563
- for (const disposable of this.disposables.splice(0)) {
14564
- try {
14565
- disposable.dispose();
14566
- } catch {}
14567
- }
14568
- if (options.kill !== false) {
14569
- try {
14570
- this.proc?.kill("SIGTERM");
14571
- } catch {}
14572
- }
14573
- try {
14574
- this.pty?.close();
14575
- } catch {}
14576
- try {
14577
- this.terminal.dispose();
14578
- } catch {}
14579
- if (activeHost2 === this)
14580
- activeHost2 = null;
14581
- if (options.notify) {
14582
- this.message = reason;
14583
- this.onSnapshot?.(this.createSnapshot(reason));
14584
- }
14585
- }
14586
- handlePtyData(data) {
14587
- if (this._disposed)
14588
- return;
14589
- const text2 = this.decoder.decode(data, { stream: true });
14590
- this.respondToRawTerminalQueries(text2);
14591
- this.terminal.write(data);
14592
- }
14593
- emitSnapshotSoon(delayMs = SNAPSHOT_DELAY_MS2) {
14594
- if (this._disposed || this.notifyTimer)
14595
- return;
14596
- this.notifyTimer = setTimeout(() => {
14597
- this.notifyTimer = null;
14598
- if (this._disposed)
14599
- return;
14600
- this.onSnapshot?.(this.createSnapshot());
14601
- }, delayMs);
14602
- }
14603
- createSnapshot(message2 = this.message) {
14604
- const buffer = this.terminal.buffer.active;
14605
- const end = buffer.length;
14606
- const start = Math.max(0, end - MAX_SNAPSHOT_LINES2);
14607
- const styledStart = Math.max(start, end - this.rows - STYLED_SNAPSHOT_MARGIN);
14608
- const lines = [];
14609
- const styledLines = [];
14610
- for (let row = start;row < end; row += 1) {
14611
- const line = buffer.getLine(row);
14612
- lines.push((line?.translateToString(false) ?? "").slice(0, this.cols));
14613
- if (row >= styledStart)
14614
- styledLines[lines.length - 1] = lineToStyledText(line, this.cols);
14615
- }
14616
- while (lines.length < this.rows) {
14617
- lines.push("");
14618
- styledLines[lines.length - 1] = new StyledText([{ __isChunk: true, text: "" }]);
14619
- }
14620
- const resolvedMessage = this.lastResizeError ? `resize degraded: ${this.lastResizeError}` : message2;
14621
- return {
14622
- runId: this.runId,
14623
- status: this.status,
14624
- cols: this.cols,
14625
- rows: this.rows,
14626
- lines,
14627
- styledLines,
14628
- message: resolvedMessage,
14629
- ...this.lastResizeError ? { resizeError: this.lastResizeError } : {},
14630
- ...this.exitCode !== undefined ? { exitCode: this.exitCode } : {},
14631
- ...this.signal !== undefined ? { signal: this.signal } : {}
14632
- };
14633
- }
14634
- registerTerminalResponders() {
14635
- this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "c" }, (params) => {
14636
- if (params.length === 0 || params[0] === 0)
14637
- this.write("\x1B[?62;22c");
14638
- return false;
14639
- }));
14640
- this.disposables.push(this.terminal.parser.registerCsiHandler({ prefix: ">", final: "c" }, (params) => {
14641
- if (params.length === 0 || params[0] === 0)
14642
- this.write("\x1B[>0;0;0c");
14643
- return false;
14644
- }));
14645
- this.disposables.push(this.terminal.parser.registerCsiHandler({ final: "n" }, (params) => {
14646
- if (params[0] === 5) {
14647
- this.write("\x1B[0n");
14648
- } else if (params[0] === 6) {
14649
- const row = this.terminal.buffer.active.cursorY + 1;
14650
- const col = this.terminal.buffer.active.cursorX + 1;
14651
- this.write(`\x1B[${row};${col}R`);
14652
- }
14653
- return false;
14654
- }));
14655
- }
14656
- respondToRawTerminalQueries(text2) {
14657
- if (!text2)
14658
- return;
14659
- const decrqm = /\x1b\[\?(\d+)\$p/g;
14660
- let match;
14661
- while ((match = decrqm.exec(text2)) !== null) {
14662
- this.write(`\x1B[?${match[1]};2$y`);
14663
- }
14664
- }
14665
- }
14666
- var MIN_COLS2 = 40, MIN_ROWS2 = 12, MAX_ROWS2 = 300, MAX_SNAPSHOT_LINES2 = 360, STYLED_SNAPSHOT_MARGIN = 6, SNAPSHOT_DELAY_MS2 = 120, fallbackChildScriptPath, activeHost2 = null, XTERM_COLOR_MODE_INDEXED_16 = 16777216, XTERM_COLOR_MODE_INDEXED_256 = 33554432, XTERM_COLOR_MODE_RGB = 50331648;
14667
- var init_pi_pty_host = __esm(() => {
14668
- fallbackChildScriptPath = fileURLToPath2(new URL("./pi-host-child.ts", import.meta.url));
14669
- });
14670
-
14671
- // packages/cli/src/app-opentui/adapters/inspect.ts
14672
- var exports_inspect = {};
14673
- __export(exports_inspect, {
14674
- loadInspectRuns: () => loadInspectRuns,
14675
- loadInspect: () => loadInspect,
14676
- INSPECT_VIEWS: () => INSPECT_VIEWS
14677
- });
14678
- import { execFile } from "child_process";
14679
- import { existsSync as existsSync20, readFileSync as readFileSync14, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
14680
- import { resolve as resolve27 } from "path";
14681
- import { promisify } from "util";
14682
- import { resolveTaskArtifactDirs as resolveTaskArtifactDirs3 } from "@rig/runtime/control-plane/authority-files";
14683
- import { readTaskArtifactPreview as readTaskArtifactPreview2 } from "@rig/runtime/control-plane/native/workspace-ops";
14684
- import { resolveHarnessPaths as resolveHarnessPaths3, resolveMonorepoRoot as resolveMonorepoRoot3, runCapture as runCapture4 } from "@rig/runtime/control-plane/native/utils";
14685
- function isFailureEntry(entry) {
14686
- const status = `${entry.status ?? entry.level ?? entry.type ?? ""}`.toLowerCase();
14687
- return status.includes("fail") || status.includes("error");
14688
- }
14689
- async function gitDiff(projectRoot) {
14690
- try {
14691
- const { stdout } = await execFileAsync("git", ["diff", "--no-color"], { cwd: projectRoot, maxBuffer: 8 * 1024 * 1024 });
14692
- return { diff: stdout };
14693
- } catch (error) {
14694
- return { diff: "", error: normalizeAppError(error).message };
14892
+ async function gitDiff(projectRoot) {
14893
+ try {
14894
+ const { stdout } = await execFileAsync("git", ["diff", "--no-color"], { cwd: projectRoot, maxBuffer: 8 * 1024 * 1024 });
14895
+ return { diff: stdout };
14896
+ } catch (error) {
14897
+ return { diff: "", error: normalizeAppError(error).message };
14695
14898
  }
14696
14899
  }
14697
14900
  function listArtifacts(projectRoot, taskId3) {
@@ -14890,14 +15093,6 @@ function recordStep(ctx, runId, label, emitLabel) {
14890
15093
  });
14891
15094
  emitProgress(ctx, emitLabel, label);
14892
15095
  }
14893
- function settleSteps(ctx, runId) {
14894
- const now = Date.now();
14895
- const previous = currentAttachState(ctx, runId);
14896
- if (!previous?.steps)
14897
- return;
14898
- const steps = previous.steps.map((step) => step.status === "running" ? { ...step, status: "done", endedAtMs: step.endedAtMs ?? now } : step);
14899
- patchData(ctx, { piAttach: { ...previous, steps } });
14900
- }
14901
15096
  async function preparePiAttachHandoff(ctx, runId) {
14902
15097
  const cleanRunId = runId.trim();
14903
15098
  const label = `Preparing Pi ${cleanRunId.slice(0, 8)}`;
@@ -14945,85 +15140,41 @@ async function attachRunWithBundledPi(ctx, runId) {
14945
15140
  throw error;
14946
15141
  }
14947
15142
  emitStarted(ctx, label, { piAttach: { runId: cleanRunId, status: "entering-pi" } });
14948
- patchData(ctx, { piAttach: { runId: cleanRunId, status: "entering-pi", message: "starting bundled Pi", steps: [] } });
15143
+ patchData(ctx, { piAttach: { runId: cleanRunId, status: "entering-pi", message: "handing the terminal to worker Pi\u2026" } });
15144
+ const projectRoot = projectRootOf(ctx);
15145
+ const outputMode = ctx.rig?.outputMode ?? "text";
15146
+ await releaseRendererForExternalTui(ctx);
15147
+ let outcome = null;
15148
+ let attachError = null;
14949
15149
  try {
14950
- recordStep(ctx, cleanRunId, "connecting to server and fetching run transcript", label);
14951
- await preparePiAttachHandoff(ctx, cleanRunId).catch((error) => {
14952
- patchData(ctx, { lastRefreshError: normalizeAppError(error).message });
14953
- return null;
14954
- });
14955
- recordStep(ctx, cleanRunId, "spawning Bun PTY host for bundled Pi", label);
14956
- const projectRoot = projectRootOf(ctx);
14957
- const cols = typeof process.stdout.columns === "number" ? process.stdout.columns : 100;
14958
- const rows = Math.max(1, (typeof process.stdout.rows === "number" ? process.stdout.rows : 35) - 1);
14959
- const patchTerminal = (snapshot) => {
14960
- const previousSteps = currentAttachState(ctx, cleanRunId)?.steps;
14961
- const failed = snapshot.status === "failed";
14962
- const reason = failed ? snapshot.message ?? `bundled Pi exited with code ${snapshot.exitCode ?? "unknown"}` : undefined;
14963
- patchData(ctx, {
14964
- piTerminal: snapshot,
14965
- piAttach: {
14966
- runId: cleanRunId,
14967
- status: failed ? "failed" : snapshot.status === "exited" ? "returned" : "entering-pi",
14968
- message: snapshot.message ?? "bundled Pi running",
14969
- result: { runId: cleanRunId, status: snapshot.status, exitCode: snapshot.exitCode },
14970
- ...previousSteps ? { steps: previousSteps } : {},
14971
- ...reason ? { failureReason: reason } : {}
14972
- }
14973
- });
14974
- };
14975
- recordStep(ctx, cleanRunId, "starting bundled Pi", label);
14976
- await startPiPtyHost({
14977
- runId: cleanRunId,
14978
- projectRoot,
14979
- cols,
14980
- rows,
14981
- onSnapshot: (snapshot) => {
14982
- if (snapshot.status === "running" && snapshot.lines.some((entry) => entry.trim().length > 0)) {
14983
- settleSteps(ctx, cleanRunId);
14984
- }
14985
- patchTerminal(snapshot);
14986
- },
14987
- onExit: (snapshot) => {
14988
- patchTerminal(snapshot);
14989
- if (snapshot.status === "failed") {
14990
- Promise.resolve().then(() => (init_inspect2(), exports_inspect)).then(({ loadInspect: loadInspect2 }) => loadInspect2(ctx, cleanRunId)).catch(() => null);
14991
- emitFailed(ctx, label, new Error(snapshot.message ?? `bundled Pi exited with code ${snapshot.exitCode ?? "unknown"}`), { runId: cleanRunId, result: { status: snapshot.status, exitCode: snapshot.exitCode } });
14992
- return;
14993
- }
14994
- emitCompleted(ctx, label, { runId: cleanRunId, result: { status: snapshot.status, exitCode: snapshot.exitCode } });
14995
- ctx.emit({ type: "scene.change", scene: "fleet", intent: { scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } } });
14996
- refreshFleet(ctx).catch((error) => emitFailed(ctx, "Refresh runs", error));
14997
- },
14998
- onError: (error, snapshot) => {
14999
- patchTerminal(snapshot);
15000
- emitFailed(ctx, label, error, { runId: cleanRunId });
15001
- }
15002
- });
15003
- const record = {
15004
- runId: cleanRunId,
15005
- status: "running",
15006
- rendered: "bundled Pi embedded via Bun.Terminal + @xterm/headless"
15007
- };
15008
- emitProgress(ctx, label, "bundled Pi is running", { piAttach: { runId: cleanRunId, status: "entering-pi", message: "bundled Pi running", result: record } });
15009
- return record;
15150
+ const { attachRunBundledPiFrontend: attachRunBundledPiFrontend2 } = await Promise.resolve().then(() => (init__pi_frontend(), exports__pi_frontend));
15151
+ outcome = await attachRunBundledPiFrontend2({ projectRoot, outputMode }, { runId: cleanRunId, returnOnQuit: true });
15010
15152
  } catch (error) {
15011
- const reason = error instanceof Error ? error.message : String(error);
15153
+ attachError = error;
15154
+ } finally {
15155
+ await resumeRendererAfterExternalTui(ctx);
15156
+ }
15157
+ if (attachError) {
15158
+ const reason = normalizeAppError(attachError).message;
15012
15159
  patchData(ctx, {
15013
- piAttach: {
15014
- runId: cleanRunId,
15015
- status: "failed",
15016
- message: reason,
15017
- failureReason: reason,
15018
- ...currentAttachState(ctx, cleanRunId)?.steps ? { steps: currentAttachState(ctx, cleanRunId).steps } : {}
15019
- }
15160
+ piTerminal: undefined,
15161
+ piAttach: { runId: cleanRunId, status: "failed", message: reason, failureReason: reason }
15020
15162
  });
15021
- emitFailed(ctx, label, error, { runId: cleanRunId });
15022
- throw error;
15163
+ Promise.resolve().then(() => (init_inspect2(), exports_inspect)).then(({ loadInspect: loadInspect2 }) => loadInspect2(ctx, cleanRunId)).catch(() => null);
15164
+ emitFailed(ctx, label, attachError, { runId: cleanRunId });
15165
+ throw attachError;
15023
15166
  }
15167
+ const detached = outcome?.detached === true;
15168
+ patchData(ctx, {
15169
+ piTerminal: undefined,
15170
+ piAttach: { runId: cleanRunId, status: "returned", message: detached ? "detached from worker Pi" : "returned from worker Pi" }
15171
+ });
15172
+ emitCompleted(ctx, label, { runId: cleanRunId, result: { runId: cleanRunId, detached } });
15173
+ ctx.emit({ type: "scene.change", scene: "fleet", intent: { scene: "fleet", argv: ["runs"], action: { kind: "refresh", label: "Opening runs" } } });
15174
+ refreshFleet(ctx).catch((error) => emitFailed(ctx, "Refresh runs", error));
15175
+ return { runId: cleanRunId, status: "returned", detached };
15024
15176
  }
15025
15177
  var init_pi_attach = __esm(() => {
15026
- init_pi_pty_host();
15027
15178
  init_fleet();
15028
15179
  });
15029
15180
  // packages/cli/src/app-opentui/command-pty-host.ts
@@ -16393,6 +16544,7 @@ async function stopRunFromDetail(ctx, runId) {
16393
16544
  }
16394
16545
 
16395
16546
  // packages/cli/src/app-opentui/adapters/server.ts
16547
+ import { spawnSync as spawnSync5 } from "child_process";
16396
16548
  var SERVER_PROBE_TIMEOUT_MS = Number(process.env.RIG_SERVER_PROBE_TIMEOUT_MS) || 5000;
16397
16549
  function withTimeout(promise, ms, label) {
16398
16550
  return new Promise((resolve28, reject) => {
@@ -16430,11 +16582,128 @@ function startLocalRequested(ctx) {
16430
16582
  const action = intent.action;
16431
16583
  return action?.payload?.startLocal === true;
16432
16584
  }
16585
+ async function serverAliases(projectRoot) {
16586
+ const { readGlobalConnections: readGlobalConnections2, readRepoConnection: readRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
16587
+ const selected = readRepoConnection2(projectRoot)?.selected ?? "local";
16588
+ const global = readGlobalConnections2();
16589
+ const aliases = [{ alias: "local", kind: "local", selected: selected === "local" }];
16590
+ for (const [alias, connection] of Object.entries(global.connections)) {
16591
+ aliases.push({
16592
+ alias,
16593
+ kind: connection.kind,
16594
+ ...connection.kind === "remote" ? { baseUrl: connection.baseUrl } : {},
16595
+ selected: alias === selected
16596
+ });
16597
+ }
16598
+ return aliases;
16599
+ }
16600
+ function firstString2(parts, offset = 0) {
16601
+ return parts.slice(offset).find((part) => part.trim() && !part.startsWith("-"))?.trim();
16602
+ }
16603
+ function normalizeUrl(value) {
16604
+ const trimmed = value?.trim().replace(/\/+$/, "");
16605
+ if (!trimmed)
16606
+ return;
16607
+ return /^https?:\/\//i.test(trimmed) ? trimmed : undefined;
16608
+ }
16609
+ async function useLocalServerFromApp(ctx) {
16610
+ const label = "Selecting local server";
16611
+ emitStarted(ctx, label);
16612
+ try {
16613
+ const projectRoot = projectRootOf(ctx);
16614
+ const { readRepoConnection: readRepoConnection2, writeRepoConnection: writeRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
16615
+ const previous = readRepoConnection2(projectRoot);
16616
+ writeRepoConnection2(projectRoot, {
16617
+ selected: "local",
16618
+ ...previous?.project ? { project: previous.project } : {},
16619
+ linkedAt: new Date().toISOString()
16620
+ });
16621
+ emitCompleted(ctx, label, { selected: "local" });
16622
+ return refreshServerStatus(ctx);
16623
+ } catch (error) {
16624
+ emitFailed(ctx, label, error);
16625
+ throw error;
16626
+ }
16627
+ }
16628
+ async function useRemoteServerFromApp(ctx, alias) {
16629
+ const label = alias ? `Selecting remote ${alias}` : "Selecting remote server";
16630
+ emitStarted(ctx, label);
16631
+ try {
16632
+ const cleanAlias = alias?.trim();
16633
+ if (!cleanAlias)
16634
+ throw new Error("Enter a saved remote alias, or add one with: add remote \u2192 <alias> <https-url>.");
16635
+ const projectRoot = projectRootOf(ctx);
16636
+ const { readGlobalConnections: readGlobalConnections2, readRepoConnection: readRepoConnection2, writeRepoConnection: writeRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
16637
+ const connection = readGlobalConnections2().connections[cleanAlias];
16638
+ if (!connection)
16639
+ throw new Error(`Remote alias ${cleanAlias} is not saved yet. Use add remote first.`);
16640
+ if (connection.kind !== "remote")
16641
+ throw new Error(`${cleanAlias} is not a remote server alias.`);
16642
+ const previous = readRepoConnection2(projectRoot);
16643
+ writeRepoConnection2(projectRoot, {
16644
+ selected: cleanAlias,
16645
+ ...previous?.project ? { project: previous.project } : {},
16646
+ linkedAt: new Date().toISOString()
16647
+ });
16648
+ emitCompleted(ctx, label, { selected: cleanAlias, baseUrl: connection.baseUrl });
16649
+ return refreshServerStatus(ctx);
16650
+ } catch (error) {
16651
+ emitFailed(ctx, label, error);
16652
+ throw error;
16653
+ }
16654
+ }
16655
+ async function addRemoteServerFromApp(ctx, args) {
16656
+ const label = "Adding remote server";
16657
+ emitStarted(ctx, label);
16658
+ try {
16659
+ const alias = firstString2(args, 2);
16660
+ const url = normalizeUrl(firstString2(args, alias ? args.indexOf(alias) + 1 : 3));
16661
+ if (!alias || !url)
16662
+ throw new Error("Add remote expects: <alias> <https-url>. Example: prod https://rig.example.com");
16663
+ const projectRoot = projectRootOf(ctx);
16664
+ const { readRepoConnection: readRepoConnection2, upsertGlobalConnection: upsertGlobalConnection2, writeRepoConnection: writeRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
16665
+ upsertGlobalConnection2(alias, { kind: "remote", baseUrl: url });
16666
+ const previous = readRepoConnection2(projectRoot);
16667
+ writeRepoConnection2(projectRoot, {
16668
+ selected: alias,
16669
+ ...previous?.project ? { project: previous.project } : {},
16670
+ linkedAt: new Date().toISOString()
16671
+ });
16672
+ emitCompleted(ctx, label, { alias, baseUrl: url });
16673
+ return refreshServerStatus(ctx);
16674
+ } catch (error) {
16675
+ emitFailed(ctx, label, error);
16676
+ throw error;
16677
+ }
16678
+ }
16679
+ async function importGitHubAuthFromGhApp(ctx) {
16680
+ const label = "Importing GitHub auth";
16681
+ emitStarted(ctx, label);
16682
+ try {
16683
+ const projectRoot = projectRootOf(ctx);
16684
+ const token = spawnSync5("gh", ["auth", "token"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 1e4 }).stdout.trim();
16685
+ if (!token)
16686
+ throw new Error("Could not read GitHub token from gh. Sign in with gh auth login, then retry import auth.");
16687
+ const runtime = await runtimeOf(ctx);
16688
+ const { postGitHubTokenViaServer: postGitHubTokenViaServer2, setGitHubBearerTokenForCurrentProcess: setGitHubBearerTokenForCurrentProcess2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
16689
+ const { readRepoConnection: readRepoConnection2 } = await Promise.resolve().then(() => (init__connection_state(), exports__connection_state));
16690
+ const selectedRepo = readRepoConnection2(projectRoot)?.project;
16691
+ const payload = await postGitHubTokenViaServer2(runtime, token, selectedRepo ? { selectedRepo } : {});
16692
+ const apiSessionToken = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.apiSessionToken : undefined;
16693
+ setGitHubBearerTokenForCurrentProcess2(typeof apiSessionToken === "string" ? apiSessionToken : token, projectRoot);
16694
+ emitCompleted(ctx, label, { selectedRepo: selectedRepo ?? null });
16695
+ return refreshServerStatus(ctx);
16696
+ } catch (error) {
16697
+ emitFailed(ctx, label, error);
16698
+ throw error;
16699
+ }
16700
+ }
16433
16701
  async function startLocalServerFromApp(ctx) {
16434
16702
  const label = "Starting local server";
16435
16703
  emitStarted(ctx, label);
16436
16704
  patchData(ctx, {
16437
16705
  lastIntent: undefined,
16706
+ remoteProjectLink: undefined,
16438
16707
  server: { label: "starting local server", reachable: false, error: "spawning local rig-server (detached)\u2026" }
16439
16708
  });
16440
16709
  try {
@@ -16459,32 +16728,61 @@ async function startLocalServerFromApp(ctx) {
16459
16728
  reachable: false,
16460
16729
  error: error instanceof Error ? error.message : String(error)
16461
16730
  };
16462
- patchData(ctx, { server: state, lastRefreshError: state.error });
16731
+ patchData(ctx, { server: state, remoteProjectLink: undefined, lastRefreshError: state.error });
16463
16732
  patchFooter(ctx, { server: "unavailable" });
16464
- emitCompleted(ctx, label, { server: state.label, reachable: false, error: state.error });
16733
+ emitCompleted(ctx, label, { serverLabel: state.label, reachable: false, error: state.error });
16465
16734
  return state;
16466
16735
  }
16467
16736
  }
16737
+ async function repairRemoteProjectRootLinkFromApp(ctx) {
16738
+ const label = "Repairing remote project link";
16739
+ emitStarted(ctx, label);
16740
+ try {
16741
+ const projectRoot = projectRootOf(ctx);
16742
+ const { ensureRemoteProjectRootLink: ensureRemoteProjectRootLink2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
16743
+ emitProgress(ctx, label, "backfilling or preparing server-host checkout");
16744
+ const result = await ensureRemoteProjectRootLink2(projectRoot, { mode: "prepare-if-missing" });
16745
+ patchData(ctx, { remoteProjectLink: result, server: { label: result.alias ?? "remote", baseUrl: result.baseUrl, kind: "remote", reachable: result.status !== "not_remote", remoteProjectLink: result } });
16746
+ patchFooter(ctx, { server: result.alias ?? "remote", message: `remote link ${result.status}` });
16747
+ if (!result.ok) {
16748
+ const error = new Error(result.message);
16749
+ error.hint = result.hint;
16750
+ patchData(ctx, { lastRefreshError: result.message });
16751
+ throw error;
16752
+ }
16753
+ emitCompleted(ctx, label, { repoSlug: result.repoSlug, serverProjectRoot: result.serverProjectRoot, status: result.status });
16754
+ return result;
16755
+ } catch (error) {
16756
+ const message2 = error instanceof Error ? error.message : String(error);
16757
+ const existing = ctx.getState().data?.remoteProjectLink;
16758
+ const remoteProjectLink = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : { ok: false, status: "error", message: message2 };
16759
+ patchData(ctx, { lastRefreshError: message2, remoteProjectLink });
16760
+ emitCompleted(ctx, label, { ok: false, error: message2, remoteProjectLink });
16761
+ throw error;
16762
+ }
16763
+ }
16468
16764
  async function refreshServerStatus(ctx) {
16469
16765
  if (startLocalRequested(ctx)) {
16470
16766
  return startLocalServerFromApp(ctx);
16471
16767
  }
16472
16768
  const label = "Checking server";
16473
16769
  emitStarted(ctx, label);
16474
- patchData(ctx, { server: { label: "checking selected server", reachable: false, error: "checking selected server\u2026" } });
16770
+ patchData(ctx, { server: { label: "checking selected server", reachable: false, error: "checking selected server\u2026" }, remoteProjectLink: undefined });
16475
16771
  const started = Date.now();
16476
16772
  try {
16477
- const { ensureServerForCli: ensureServerForCli2, requestServerJson: requestServerJson2, resolveServerConnectionLabel: resolveServerConnectionLabel2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
16773
+ const { ensureRemoteProjectRootLink: ensureRemoteProjectRootLink2, ensureServerForCli: ensureServerForCli2, requestServerJson: requestServerJson2, resolveServerConnectionLabel: resolveServerConnectionLabel2 } = await Promise.resolve().then(() => (init__server_client(), exports__server_client));
16478
16774
  const projectRoot = projectRootOf(ctx);
16775
+ const aliases = await serverAliases(projectRoot);
16479
16776
  emitProgress(ctx, label, "resolving selected server");
16480
16777
  const [connectionLabel, server] = await Promise.all([
16481
16778
  resolveServerConnectionLabel2(projectRoot),
16482
16779
  withTimeout(ensureServerForCli2(projectRoot), 5000, "server connection")
16483
16780
  ]);
16484
16781
  emitProgress(ctx, label, "requesting server status", { baseUrl: server.baseUrl, kind: server.connectionKind });
16485
- const [status, auth] = await Promise.all([
16782
+ const [status, auth, remoteProjectLink] = await Promise.all([
16486
16783
  withRetry("server status", () => withTimeout(requestServerJson2({ projectRoot }, "/api/server/status"), SERVER_PROBE_TIMEOUT_MS, "server status")).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) })),
16487
- withRetry("github auth status", () => withTimeout(requestServerJson2({ projectRoot }, "/api/github/auth/status"), SERVER_PROBE_TIMEOUT_MS, "github auth status")).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) }))
16784
+ withRetry("github auth status", () => withTimeout(requestServerJson2({ projectRoot }, "/api/github/auth/status"), SERVER_PROBE_TIMEOUT_MS, "github auth status")).catch((error) => ({ ok: false, error: error instanceof Error ? error.message : String(error) })),
16785
+ server.connectionKind === "remote" ? withTimeout(ensureRemoteProjectRootLink2(projectRoot, { mode: "backfill-only", authToken: server.authToken }), SERVER_PROBE_TIMEOUT_MS, "remote project link").catch((error) => ({ ok: false, status: "error", message: error instanceof Error ? error.message : String(error) })) : Promise.resolve(null)
16488
16786
  ]);
16489
16787
  const state = {
16490
16788
  label: connectionLabel,
@@ -16493,22 +16791,25 @@ async function refreshServerStatus(ctx) {
16493
16791
  reachable: true,
16494
16792
  latencyMs: Date.now() - started,
16495
16793
  status: status && typeof status === "object" && !Array.isArray(status) ? status : {},
16496
- auth: auth && typeof auth === "object" && !Array.isArray(auth) ? auth : {}
16794
+ auth: auth && typeof auth === "object" && !Array.isArray(auth) ? auth : {},
16795
+ aliases,
16796
+ ...remoteProjectLink && typeof remoteProjectLink === "object" && !Array.isArray(remoteProjectLink) ? { remoteProjectLink } : {}
16497
16797
  };
16498
- patchData(ctx, { server: state });
16798
+ patchData(ctx, { server: state, remoteProjectLink: state.remoteProjectLink });
16499
16799
  patchFooter(ctx, { server: connectionLabel, latency: `${state.latencyMs}ms` });
16500
- emitCompleted(ctx, label, { server: connectionLabel, latencyMs: state.latencyMs });
16800
+ emitCompleted(ctx, label, { serverLabel: connectionLabel, latencyMs: state.latencyMs });
16501
16801
  return state;
16502
16802
  } catch (error) {
16503
16803
  const state = {
16504
16804
  label: "server unavailable",
16505
16805
  reachable: false,
16506
16806
  latencyMs: Date.now() - started,
16807
+ aliases: await serverAliases(projectRootOf(ctx)).catch(() => []),
16507
16808
  error: error instanceof Error ? error.message : String(error)
16508
16809
  };
16509
- patchData(ctx, { server: state });
16810
+ patchData(ctx, { server: state, remoteProjectLink: undefined });
16510
16811
  patchFooter(ctx, { server: "unavailable" });
16511
- emitCompleted(ctx, label, { server: state.label, reachable: false, error: state.error });
16812
+ emitCompleted(ctx, label, { serverLabel: state.label, reachable: false, error: state.error });
16512
16813
  return state;
16513
16814
  }
16514
16815
  }
@@ -17038,7 +17339,7 @@ import {
17038
17339
  dim as otuiDim,
17039
17340
  fg as otuiFg,
17040
17341
  t,
17041
- TextAttributes as TextAttributes2
17342
+ TextAttributes
17042
17343
  } from "@opentui/core";
17043
17344
  var RIG_UI = {
17044
17345
  bg: "#070809",
@@ -17140,7 +17441,7 @@ function statusLabel(status) {
17140
17441
  }
17141
17442
 
17142
17443
  // packages/cli/src/app-opentui/drone.ts
17143
- import { RGBA as RGBA2, StyledText as StyledText2, TextAttributes as TextAttributes3 } from "@opentui/core";
17444
+ import { RGBA, StyledText, TextAttributes as TextAttributes2 } from "@opentui/core";
17144
17445
  var MINI_DRONE = [
17145
17446
  "(!!!) (!!!)",
17146
17447
  " \\%==%/ ",
@@ -17156,13 +17457,13 @@ var LEAD_MARK = [
17156
17457
  var BLADE_FRAMES2 = ["---", "\\\\\\", "|||", "///"];
17157
17458
  var EYE_FRAMES2 = ["o", "@", "\u2022", "."];
17158
17459
  var COLOR = {
17159
- body: RGBA2.fromHex(RIG_UI.lime),
17160
- mini: RGBA2.fromHex(RIG_UI.limeDim),
17161
- rotor: RGBA2.fromHex(RIG_UI.cyan),
17162
- path: RGBA2.fromHex(RIG_UI.cyan),
17163
- eye: RGBA2.fromHex(RIG_UI.ink),
17164
- dim: RGBA2.fromHex(RIG_UI.ink4),
17165
- ink: RGBA2.fromHex(RIG_UI.ink2)
17460
+ body: RGBA.fromHex(RIG_UI.lime),
17461
+ mini: RGBA.fromHex(RIG_UI.limeDim),
17462
+ rotor: RGBA.fromHex(RIG_UI.cyan),
17463
+ path: RGBA.fromHex(RIG_UI.cyan),
17464
+ eye: RGBA.fromHex(RIG_UI.ink),
17465
+ dim: RGBA.fromHex(RIG_UI.ink4),
17466
+ ink: RGBA.fromHex(RIG_UI.ink2)
17166
17467
  };
17167
17468
  function bladeForTick(tick, phase = 0) {
17168
17469
  return BLADE_FRAMES2[(Math.floor(tick / 3) + phase) % BLADE_FRAMES2.length];
@@ -17172,12 +17473,12 @@ function eyeForTick(tick, phase = 0) {
17172
17473
  return pulse > 0.55 ? EYE_FRAMES2[1] : pulse > 0.1 ? EYE_FRAMES2[0] : pulse > -0.45 ? EYE_FRAMES2[2] : EYE_FRAMES2[3];
17173
17474
  }
17174
17475
  function chunk(text2, fg2, bold2 = false, dim = false) {
17175
- let attributes = TextAttributes3.NONE;
17476
+ let attributes = TextAttributes2.NONE;
17176
17477
  if (bold2)
17177
- attributes |= TextAttributes3.BOLD;
17478
+ attributes |= TextAttributes2.BOLD;
17178
17479
  if (dim)
17179
- attributes |= TextAttributes3.DIM;
17180
- return { __isChunk: true, text: text2, fg: fg2, ...attributes !== TextAttributes3.NONE ? { attributes } : {} };
17480
+ attributes |= TextAttributes2.DIM;
17481
+ return { __isChunk: true, text: text2, fg: fg2, ...attributes !== TextAttributes2.NONE ? { attributes } : {} };
17181
17482
  }
17182
17483
  function styledLine(text2, colorFor) {
17183
17484
  const chunks = [];
@@ -17198,7 +17499,7 @@ function styledLine(text2, colorFor) {
17198
17499
  run += char;
17199
17500
  }
17200
17501
  flush();
17201
- return new StyledText2(chunks.length > 0 ? chunks : [chunk("", COLOR.ink)]);
17502
+ return new StyledText(chunks.length > 0 ? chunks : [chunk("", COLOR.ink)]);
17202
17503
  }
17203
17504
  function droneColor(char) {
17204
17505
  if (char === "?" || char === "o" || char === "@" || char === "\u2022")
@@ -17378,7 +17679,7 @@ function withSelectable(line2, item) {
17378
17679
  var DOCTOR_ACTIONS = [
17379
17680
  { detail: "run diagnostics now", item: { id: "run", label: "run", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "run diagnostics" } },
17380
17681
  { detail: "open server controls", item: { id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Open server controls" } }, message: "open server controls" } },
17381
- { detail: "repair project/server/auth link", item: { id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Repair project link" } }, message: "repair project link" } },
17682
+ { detail: "repair project/server/auth link", item: { id: "repair", label: "repair", intent: { scene: "init", argv: ["init", "--repair", "--yes"], action: { kind: "init-start", label: "Repair project link" } }, message: "repair project link natively" } },
17382
17683
  { detail: "open tasks", item: { id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Open tasks" } }, message: "open tasks" } }
17383
17684
  ];
17384
17685
  function checks(state) {
@@ -17451,6 +17752,48 @@ function renderDoctorScene(state, layout) {
17451
17752
  });
17452
17753
  }
17453
17754
 
17755
+ // packages/cli/src/app-opentui/remote-link.ts
17756
+ var REPAIRABLE_REMOTE_LINK_STATUSES = new Set([
17757
+ "auth_required",
17758
+ "project_not_registered",
17759
+ "no_server_checkout",
17760
+ "invalid_root",
17761
+ "needs_prepare",
17762
+ "error"
17763
+ ]);
17764
+ function remoteProjectLinkError(message2) {
17765
+ return /no server-host project root link|remote project(?:-root)? link|x-rig-project-root|serverProjectRoot/i.test(message2 ?? "");
17766
+ }
17767
+ function serverRecordForRemoteLink(state) {
17768
+ const server = state.data.server;
17769
+ return server && typeof server === "object" && !Array.isArray(server) ? server : null;
17770
+ }
17771
+ function remoteProjectLinkState(state) {
17772
+ const serverRecord = serverRecordForRemoteLink(state);
17773
+ if (!serverRecord || serverRecord.kind !== "remote")
17774
+ return null;
17775
+ const link = serverRecord.remoteProjectLink;
17776
+ if (link && typeof link === "object" && !Array.isArray(link))
17777
+ return link;
17778
+ const direct = state.data.remoteProjectLink;
17779
+ if (direct && typeof direct === "object" && !Array.isArray(direct))
17780
+ return direct;
17781
+ return null;
17782
+ }
17783
+ function shouldOfferRemoteLinkRepair(state, message2) {
17784
+ const link = remoteProjectLinkState(state);
17785
+ if (link) {
17786
+ if (link.ok === true)
17787
+ return false;
17788
+ const status = link.status ?? "";
17789
+ return REPAIRABLE_REMOTE_LINK_STATUSES.has(status);
17790
+ }
17791
+ const serverRecord = serverRecordForRemoteLink(state);
17792
+ if (serverRecord && serverRecord.kind !== "remote")
17793
+ return false;
17794
+ return remoteProjectLinkError(message2 ?? state.error?.message);
17795
+ }
17796
+
17454
17797
  // packages/cli/src/app-opentui/fleet-stats.ts
17455
17798
  var ACTIVE_STATUSES = new Set([
17456
17799
  "running",
@@ -19298,13 +19641,18 @@ function overviewBody(state, family, snap, layout) {
19298
19641
  function familyRecoveryRows(state, family, errorMessage2) {
19299
19642
  const selected = Math.max(0, state.selection.index);
19300
19643
  const intro = errorMessage2 ? line(`\u2717 couldn't load ${family || "this family"} \u2014 ${errorMessage2}`, { fg: RIG_UI.red, bold: true }) : line(`No data for ${family || "this family"} yet.`, { fg: RIG_UI.ink2 });
19301
- return [
19644
+ const repairRemote = shouldOfferRemoteLinkRepair(state, errorMessage2);
19645
+ const rows = [
19302
19646
  intro,
19303
- blank(),
19304
- selectableDeckRow({ label: "retry", detail: `reload ${family || "the family"}`, index: 0, active: selected === 0 }, { id: "family-retry", label: "retry", intent: { scene: "family", argv: [family], action: { kind: "refresh", label: `Reload ${family}`, payload: { family } } }, message: "reload this family" }),
19305
- selectableDeckRow({ label: "cockpit", detail: "back to the project cockpit", index: 1, active: selected === 1 }, { id: "family-home", label: "cockpit", intent: { scene: "main", argv: [], action: { kind: "refresh", label: "Home" } }, message: "back to the cockpit" }),
19306
- selectableDeckRow({ label: "doctor", detail: "diagnose project/setup", index: 2, active: selected === 2 }, { id: "family-doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose setup" })
19647
+ blank()
19307
19648
  ];
19649
+ let index = 0;
19650
+ if (repairRemote) {
19651
+ rows.push(selectableDeckRow({ label: "repair link", detail: "backfill/prepare selected remote checkout link", index, active: selected === index }, { id: "family-repair-link", label: "repair link", intent: { scene: "server", argv: ["server", "repair-link"], action: { kind: "remote-link-repair", label: "Repair remote link", payload: { returnScene: "family", family } } }, message: "repair remote project link" }));
19652
+ index += 1;
19653
+ }
19654
+ rows.push(selectableDeckRow({ label: "retry", detail: `reload ${family || "the family"}`, index, active: selected === index }, { id: "family-retry", label: "retry", intent: { scene: "family", argv: [family], action: { kind: "refresh", label: `Reload ${family}`, payload: { family } } }, message: "reload this family" }), selectableDeckRow({ label: "cockpit", detail: "back to the project cockpit", index: index + 1, active: selected === index + 1 }, { id: "family-home", label: "cockpit", intent: { scene: "main", argv: [], action: { kind: "refresh", label: "Home" } }, message: "back to the cockpit" }), selectableDeckRow({ label: "doctor", detail: "diagnose project/setup", index: index + 2, active: selected === index + 2 }, { id: "family-doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose setup" }));
19655
+ return rows;
19308
19656
  }
19309
19657
  function renderFamilyScene(state, layout) {
19310
19658
  const family = typeof state.data.familyName === "string" ? state.data.familyName : "";
@@ -19313,9 +19661,16 @@ function renderFamilyScene(state, layout) {
19313
19661
  const loading = state.status === "loading" && !snap;
19314
19662
  const errorMessage2 = state.error?.message ?? lastRefreshError(state) ?? undefined;
19315
19663
  const probeFailed = snap ? snap.state.ran === false && Boolean(state.error) : Boolean(state.error);
19664
+ const selected = Math.max(0, state.selection.index);
19316
19665
  const body = [
19317
19666
  ...loading ? loadingRows(family || "family", state.tick) : snap ? [
19318
- ...probeFailed && errorMessage2 ? [...errorBanner(errorMessage2), blank()] : [],
19667
+ ...probeFailed && errorMessage2 ? [
19668
+ ...errorBanner(errorMessage2),
19669
+ ...shouldOfferRemoteLinkRepair(state, errorMessage2) ? [
19670
+ selectableDeckRow({ label: "repair link", detail: "backfill/prepare selected remote checkout link", index: 0, active: selected === 0 }, { id: "family-repair-link", label: "repair link", intent: { scene: "server", argv: ["server", "repair-link"], action: { kind: "remote-link-repair", label: "Repair remote link", payload: { returnScene: "family", family } } }, message: "repair remote project link" })
19671
+ ] : [],
19672
+ blank()
19673
+ ] : [],
19319
19674
  ...current.subId ? formView(state, family, snap, current).lines : overviewBody(state, family, snap, layout)
19320
19675
  ] : familyRecoveryRows(state, family, errorMessage2)
19321
19676
  ];
@@ -19462,12 +19817,12 @@ function sortTasks2(tasks, spec) {
19462
19817
  }
19463
19818
 
19464
19819
  // packages/cli/src/app-opentui/scenes/fleet.ts
19465
- var FLEET_RECOVERY_ITEMS = [
19820
+ var BASE_FLEET_RECOVERY_ITEMS = [
19466
19821
  { id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Open tasks" } }, message: "open tasks and dispatch work" },
19467
- { id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Repair project link" } }, message: "repair project/server/auth link" },
19468
19822
  { id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Open server controls" } }, message: "server controls" },
19469
19823
  { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose project" }
19470
19824
  ];
19825
+ var FLEET_REPAIR_LINK_ITEM = { id: "repair-link", label: "repair link", intent: { scene: "server", argv: ["server", "repair-link"], action: { kind: "remote-link-repair", label: "Repair remote link", payload: { returnScene: "fleet" } } }, message: "backfill or prepare remote project link" };
19471
19826
  var COL2 = {
19472
19827
  glyph: 1,
19473
19828
  runId: 8,
@@ -19632,33 +19987,43 @@ function runRow(width, run, active, optimistic) {
19632
19987
  const item = runSelectableItem(run);
19633
19988
  return item ? withSelectable(row, item) : row;
19634
19989
  }
19635
- var RECOVERY_DECKS = [
19990
+ var BASE_RECOVERY_DECKS = [
19636
19991
  { label: "tasks", detail: "open tasks and dispatch work" },
19637
- { label: "repair", detail: "repair project/server/auth link" },
19638
19992
  { label: "server", detail: "open server controls" },
19639
19993
  { label: "doctor", detail: "diagnose project" }
19640
19994
  ];
19641
- function recoveryRows(selected, reason) {
19995
+ function fleetRecoveryItems(state) {
19996
+ if (!shouldOfferRemoteLinkRepair(state))
19997
+ return BASE_FLEET_RECOVERY_ITEMS;
19998
+ return [BASE_FLEET_RECOVERY_ITEMS[0], FLEET_REPAIR_LINK_ITEM, ...BASE_FLEET_RECOVERY_ITEMS.slice(1)];
19999
+ }
20000
+ function fleetRecoveryDecks(state) {
20001
+ if (!shouldOfferRemoteLinkRepair(state))
20002
+ return BASE_RECOVERY_DECKS;
20003
+ return [BASE_RECOVERY_DECKS[0], { label: "repair link", detail: "backfill/prepare remote project-root link" }, ...BASE_RECOVERY_DECKS.slice(1)];
20004
+ }
20005
+ function recoveryRows(state, selected, reason) {
20006
+ const items = fleetRecoveryItems(state);
19642
20007
  return [
19643
20008
  ...reason ? [line(reason, { fg: RIG_UI.yellow }), line("", { fg: RIG_UI.ink3 })] : [],
19644
20009
  line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
19645
- ...RECOVERY_DECKS.map((deck, index) => selectableDeckRow({ ...deck, index, active: selected === index }, FLEET_RECOVERY_ITEMS[index]))
20010
+ ...fleetRecoveryDecks(state).map((deck, index) => selectableDeckRow({ ...deck, index, active: selected === index }, items[index]))
19646
20011
  ];
19647
20012
  }
19648
- function emptyRows(query, total, selected) {
20013
+ function emptyRows(state, query, total, selected) {
19649
20014
  if (!query.trim() && total === 0)
19650
- return recoveryRows(selected, "No runs yet.");
20015
+ return recoveryRows(state, selected, "No runs yet.");
19651
20016
  if (query.trim() && total > 0) {
19652
20017
  return [
19653
- ...recoveryRows(selected, `No matching runs for ${JSON.stringify(query)} (${total} total).`)
20018
+ ...recoveryRows(state, selected, `No matching runs for ${JSON.stringify(query)} (${total} total).`)
19654
20019
  ];
19655
20020
  }
19656
- return recoveryRows(selected, "No runs are visible with this filter.");
20021
+ return recoveryRows(state, selected, "No runs are visible with this filter.");
19657
20022
  }
19658
20023
  function degradedRows(state, selected) {
19659
20024
  if (!state.error)
19660
20025
  return [];
19661
- return recoveryRows(selected, state.error.message);
20026
+ return recoveryRows(state, selected, state.error.message);
19662
20027
  }
19663
20028
  function renderFleetScene(state, layout) {
19664
20029
  const width = panelWidth4(layout);
@@ -19668,7 +20033,7 @@ function renderFleetScene(state, layout) {
19668
20033
  const runs = sortRuns(filterRunsForSearch(allRuns, query), spec);
19669
20034
  const selectedRun = typeof state.data.selectedRunId === "string" && runs.some((run) => run.runId === state.data.selectedRunId) ? state.data.selectedRunId : runs[0]?.runId;
19670
20035
  const selectedIndex = Math.max(0, runs.findIndex((run) => run.runId === selectedRun));
19671
- const recoveryIndex = Math.max(0, Math.min(3, state.selection.index));
20036
+ const recoveryIndex = Math.max(0, Math.min(fleetRecoveryItems(state).length - 1, state.selection.index));
19672
20037
  const optimistic = optimisticStatuses(state);
19673
20038
  const updatedAgo = updatedAgoLabel(state);
19674
20039
  const panelTop = 0;
@@ -19700,7 +20065,7 @@ function renderFleetScene(state, layout) {
19700
20065
  ...runs.map((run, index) => runRow(contentWidth, run, index === selectedIndex, optimistic.get(run.runId)))
19701
20066
  ] : state.error ? degradedRows(state, recoveryIndex) : loading ? loadingRows("runs", state.tick) : [
19702
20067
  ...query.trim() ? [line(searchSummary("runs", query, runs.length, allRuns.length), { fg: RIG_UI.ink4 }), line("", { fg: RIG_UI.ink3 })] : [],
19703
- ...emptyRows(query, allRuns.length, recoveryIndex)
20068
+ ...emptyRows(state, query, allRuns.length, recoveryIndex)
19704
20069
  ]
19705
20070
  ];
19706
20071
  return makeSceneFrame({
@@ -20255,19 +20620,19 @@ function renderInboxScene(state, layout) {
20255
20620
  }
20256
20621
 
20257
20622
  // packages/cli/src/app-opentui/scenes/init.ts
20258
- var STEP_CONFIG_RUN = { id: "step-config", label: "run setup wizard", intent: { scene: "command", argv: ["init"], action: { kind: "command-run", label: "Run setup wizard" } }, message: "full interactive setup wizard" };
20623
+ var STEP_CONFIG_RUN = { id: "step-config", label: "run init", intent: { scene: "init", argv: ["init", "--yes"], action: { kind: "init-start", label: "Run init" } }, message: "initialize without embedded CLI prompts" };
20259
20624
  var STEP_SERVER = { id: "step-server", label: "select server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Choose server" } }, message: "select local or remote server" };
20260
- var STEP_AUTH = { id: "step-auth", label: "connect GitHub", intent: { scene: "command", argv: ["github", "auth", "import-gh"], action: { kind: "command-run", label: "Connect GitHub" } }, message: "import GitHub auth into the selected server" };
20261
- var STEP_TASKS = { id: "step-tasks", label: "pick a task source", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Configure task source" } }, message: "configure or repair the task source" };
20625
+ var STEP_AUTH = { id: "step-auth", label: "connect GitHub", intent: { scene: "server", argv: ["github", "auth", "status"], action: { kind: "refresh", label: "Open GitHub auth" } }, message: "open native server/auth controls" };
20626
+ var STEP_TASKS = { id: "step-tasks", label: "repair task source", intent: { scene: "init", argv: ["init", "--repair", "--yes"], action: { kind: "init-start", label: "Repair task source" } }, message: "repair generated task-source config without embedded CLI prompts" };
20262
20627
  var SETUP_STEPS = [
20263
- { id: "config", label: "Project config", detail: "rig.config + private state", done: (s) => s.configured, cta: "Run setup wizard", item: STEP_CONFIG_RUN },
20628
+ { id: "config", label: "Project config", detail: "rig.config + private state", done: (s) => s.configured, cta: "Run init", item: STEP_CONFIG_RUN },
20264
20629
  { id: "server", label: "Server target", detail: "local or remote, reachable", done: (s) => s.serverReachable, cta: "Choose a server", item: STEP_SERVER },
20265
20630
  { id: "auth", label: "GitHub auth", detail: "signed in on the selected server", done: (s) => s.authSignedIn, cta: "Connect GitHub", item: STEP_AUTH },
20266
20631
  { id: "tasks", label: "Task source", detail: "where work comes from", done: (s) => s.taskSourceReady, cta: "Pick a task source", item: STEP_TASKS }
20267
20632
  ];
20268
20633
  var SECONDARY_ACTIONS = [
20269
- { label: "repair", detail: "reconfigure generated config and private state", item: { id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Reconfigure project" } }, message: "verify or rewrite generated config" } },
20270
- { label: "demo", detail: "seed an offline demo task source to explore", item: { id: "demo", label: "demo", intent: { scene: "command", argv: ["init", "--demo", "--repair"], action: { kind: "command-run", label: "Seed demo project" } }, message: "offline demo project" } },
20634
+ { label: "repair", detail: "reconfigure generated config and private state", item: { id: "repair", label: "repair", intent: { scene: "init", argv: ["init", "--repair", "--yes"], action: { kind: "init-start", label: "Reconfigure project" } }, message: "verify or rewrite generated config natively" } },
20635
+ { label: "demo", detail: "seed an offline demo task source to explore", item: { id: "demo", label: "demo", intent: { scene: "init", argv: ["init", "--demo", "--repair", "--yes"], action: { kind: "init-start", label: "Seed demo project" } }, message: "offline demo project without embedded CLI prompts" } },
20271
20636
  { label: "doctor", detail: "diagnose what is still missing", item: { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "verify setup" } }
20272
20637
  ];
20273
20638
  function record2(value) {
@@ -20311,11 +20676,11 @@ function renderInitScene(state, layout) {
20311
20676
  const allDone = completed === SETUP_STEPS.length;
20312
20677
  const nextStep = SETUP_STEPS.find((step) => !step.done(signals));
20313
20678
  const selected = Math.max(0, state.selection.index);
20314
- const primary = nextStep ? selectableDeckRow({ label: "next", detail: `${nextStep.cta} \u2014 recommended`, active: selected === 0 }, { ...nextStep.item, id: "init-primary" }) : selectableDeckRow({ label: "verify", detail: "Setup complete \u2014 run doctor to confirm", active: selected === 0 }, { id: "init-primary", label: "verify", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "verify setup end-to-end" });
20679
+ const primary = nextStep ? selectableDeckRow({ label: "next", detail: `${nextStep.cta} \u2014 recommended`, active: selected === 0 }, { ...nextStep.item, id: "init-primary" }) : selectableDeckRow({ label: "verify", detail: "Init complete \u2014 run doctor to confirm", active: selected === 0 }, { id: "init-primary", label: "verify", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "verify setup end-to-end" });
20315
20680
  const secondaryRows = SECONDARY_ACTIONS.map(({ label, detail, item }, index) => selectableDeckRow({ label, detail, active: selected === index + 1 }, item));
20316
20681
  const progress = allDone ? "all set" : `${completed} of ${SETUP_STEPS.length} complete`;
20317
20682
  const panelLines = [
20318
- line(allDone ? "SETUP \xB7 ready to go" : "SETUP \xB7 let's get you running", { fg: allDone ? RIG_UI.limeDim : RIG_UI.ink, bold: true }),
20683
+ line(allDone ? "INIT \xB7 ready to go" : "INIT \xB7 let's get you running", { fg: allDone ? RIG_UI.limeDim : RIG_UI.ink, bold: true }),
20319
20684
  line(`progress ${progress}`, { fg: allDone ? RIG_UI.limeDim : RIG_UI.ink2 }),
20320
20685
  blank(),
20321
20686
  line("CHECKLIST", { fg: RIG_UI.ink3, bold: true }),
@@ -20327,11 +20692,11 @@ function renderInitScene(state, layout) {
20327
20692
  line("OTHER PATHS", { fg: RIG_UI.ink3, bold: true }),
20328
20693
  ...secondaryRows,
20329
20694
  blank(),
20330
- line("enter/click runs the highlighted action \xB7 esc goes back \xB7 tab switches sections", { fg: RIG_UI.ink4 })
20695
+ line("enter/click runs the highlighted native action \xB7 esc goes back \xB7 tab switches sections", { fg: RIG_UI.ink4 })
20331
20696
  ];
20332
20697
  return makeSceneFrame({
20333
20698
  scene: "init",
20334
- title: "Setup",
20699
+ title: "Init",
20335
20700
  lines: [],
20336
20701
  panels: [{
20337
20702
  id: "init-setup",
@@ -20344,11 +20709,11 @@ function renderInitScene(state, layout) {
20344
20709
  opacity: 0.98,
20345
20710
  border: false,
20346
20711
  chrome: "ad-terminal",
20347
- headerText: "rig setup \xB7 guided onboarding",
20712
+ headerText: "rig init \xB7 guided onboarding",
20348
20713
  paddingX: 5,
20349
20714
  paddingY: 2
20350
20715
  }],
20351
- footer: { message: allDone ? "setup complete" : "setup in progress" },
20716
+ footer: { message: allDone ? "init complete" : "init in progress" },
20352
20717
  live: !allDone || state.status === "action"
20353
20718
  });
20354
20719
  }
@@ -21621,16 +21986,40 @@ function renderRunDetailScene(state, layout) {
21621
21986
  // packages/cli/src/app-opentui/scenes/server.ts
21622
21987
  var SERVER_ACTIONS = [
21623
21988
  { detail: "probe selected server, project root, and GitHub auth", item: { id: "refresh", label: "refresh", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Refresh server" } }, message: "refresh selected server" } },
21624
- { detail: "select local server for this repo", item: { id: "local", label: "use local", intent: { scene: "command", argv: ["server", "use", "local"], action: { kind: "command-run", label: "Use local server" } }, message: "switch this repo to local" } },
21625
- { detail: "choose/switch to a saved remote server alias", item: { id: "remote", label: "use remote", intent: { scene: "command", argv: ["server", "use"], action: { kind: "command-run", label: "Choose remote server" } }, message: "choose a saved remote alias" } },
21626
- { detail: "show saved local/remote aliases", item: { id: "list", label: "list servers", intent: { scene: "command", argv: ["server", "list"], action: { kind: "command-run", label: "List saved servers" } }, message: "list saved server aliases" } },
21627
- { detail: "open server add command workspace", item: { id: "add", label: "add remote", intent: { scene: "command", argv: ["server", "add"], action: { kind: "command-run", label: "Add remote server" } }, message: "open server add workspace" } },
21989
+ { detail: "select local server for this repo", item: { id: "local", label: "use local", intent: { scene: "server", argv: ["server", "use", "local"], action: { kind: "server-use-local", label: "Use local server" } }, message: "switch this repo to local" } },
21990
+ { detail: "type a saved remote alias to select", item: { id: "remote", label: "use remote", prompt: { label: "Use remote alias", scene: "server", argv: ["server", "use"], intentKind: "server-use-remote", payloadBase: {}, payloadKey: "alias" }, message: "choose a saved remote alias" } },
21991
+ { detail: "show saved local/remote aliases in the status panel", item: { id: "list", label: "list servers", intent: { scene: "server", argv: ["server", "list"], action: { kind: "refresh", label: "List servers" } }, message: "list saved server aliases" } },
21992
+ { detail: "add and select a remote: enter <alias> <https-url>", item: { id: "add", label: "add remote", prompt: { label: "Add remote", scene: "server", argv: ["server", "add"], intentKind: "server-add-remote", payloadBase: {}, payloadKey: "remote", appendValueToArgv: true }, message: "add a remote server alias" } },
21628
21993
  { detail: "spawn the local rig-server detached and return here", item: { id: "start", label: "start local", intent: { scene: "server", argv: ["server", "start"], action: { kind: "refresh", payload: { startLocal: true }, label: "Start local server" } }, message: "start local server detached" } },
21629
- { detail: "import gh token into the selected server namespace", item: { id: "auth", label: "import auth", intent: { scene: "command", argv: ["github", "auth", "import-gh"], action: { kind: "command-run", label: "Import GitHub auth" } }, message: "import gh auth to selected server" } },
21994
+ { detail: "import gh token into the selected server namespace", item: { id: "auth", label: "import auth", intent: { scene: "server", argv: ["github", "auth", "import-gh"], action: { kind: "github-auth-import", label: "Import GitHub auth" } }, message: "import gh auth to selected server" } },
21630
21995
  { detail: "diagnose server/auth/project linkage", item: { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "verify server/auth" } },
21631
21996
  { detail: "verify selected server can list task source", item: { id: "tasks", label: "tasks", intent: { scene: "tasks", argv: ["task", "list"], action: { kind: "refresh", label: "Open tasks" } }, message: "verify task source" } },
21632
21997
  { detail: "verify selected server can list recent runs", item: { id: "runs", label: "runs", intent: { scene: "fleet", argv: ["run", "status"], action: { kind: "refresh", label: "Open runs" } }, message: "open recent runs" } }
21633
21998
  ];
21999
+ function remoteAliasActions(server) {
22000
+ const aliases = (server?.aliases ?? []).filter((alias) => alias.kind === "remote");
22001
+ return aliases.map((alias) => ({
22002
+ detail: `${alias.selected ? "selected" : "select"} ${alias.baseUrl ?? "remote endpoint"}`,
22003
+ item: {
22004
+ id: `remote-alias:${alias.alias}`,
22005
+ label: alias.selected ? `\u2713 ${alias.alias}` : `use ${alias.alias}`,
22006
+ intent: { scene: "server", argv: ["server", "use", alias.alias], action: { kind: "server-use-remote", label: `Use ${alias.alias}`, payload: { alias: alias.alias } } },
22007
+ message: `select ${alias.alias}`
22008
+ }
22009
+ }));
22010
+ }
22011
+ function serverActions(state) {
22012
+ const server = serverState(state);
22013
+ const remoteRows = remoteAliasActions(server);
22014
+ let actions = remoteRows.length > 0 ? [...SERVER_ACTIONS.slice(0, 3), ...remoteRows, ...SERVER_ACTIONS.slice(3)] : [...SERVER_ACTIONS];
22015
+ if (!shouldOfferRemoteLinkRepair(state))
22016
+ return actions;
22017
+ const repair = { detail: "backfill or prepare the selected remote checkout/root link", item: { id: "repair-link", label: "repair link", intent: { scene: "server", argv: ["server", "repair-link"], action: { kind: "remote-link-repair", label: "Repair remote link" } }, message: "prepare remote checkout link" } };
22018
+ const doctorIndex = actions.findIndex((entry) => entry.item.id === "doctor");
22019
+ if (doctorIndex < 0)
22020
+ return [...actions, repair];
22021
+ return [...actions.slice(0, doctorIndex), repair, ...actions.slice(doctorIndex)];
22022
+ }
21634
22023
  function cleanVisible(value, fallback = "unknown") {
21635
22024
  return value?.trim() || fallback;
21636
22025
  }
@@ -21655,31 +22044,37 @@ function selectedIndex(state, count) {
21655
22044
  function statusRows2(server) {
21656
22045
  const auth = server?.auth;
21657
22046
  const signedIn = auth && (auth.signedIn === true || auth.authenticated === true || auth.status === "authenticated") ? "authenticated" : "auth unknown";
22047
+ const link = server?.remoteProjectLink;
22048
+ const linkText = link ? link.ok ? `ready \xB7 ${link.serverProjectRoot ?? "linked"}` : `${link.status ?? "needs repair"} \xB7 ${link.hint ?? link.message ?? "run repair link"}` : server?.kind === "remote" ? "not checked" : "not required";
22049
+ const selectedLinkSummary = link?.ok ? " \xB7 root link ready" : server?.kind === "remote" && link ? ` \xB7 root link ${link.status ?? "needs repair"}` : "";
21658
22050
  return [
21659
- line(`selected ${cleanVisible(server?.label, "not checked")}`, { fg: RIG_UI.ink2 }),
22051
+ line(`selected ${cleanVisible(server?.label, "not checked")}${selectedLinkSummary}`, { fg: RIG_UI.ink2 }),
21660
22052
  line(`target ${targetLabel(server)}`, { fg: RIG_UI.ink3 }),
21661
22053
  line(`mode ${server?.kind ?? "local or remote"}`, { fg: RIG_UI.ink3 }),
21662
22054
  line(`health ${server ? server.reachable ? "reachable" : "unreachable" : "check pending"}`, { fg: server?.reachable ? RIG_UI.limeDim : server ? RIG_UI.red : RIG_UI.yellow }),
21663
22055
  line(`github ${signedIn}`, { fg: signedIn === "authenticated" ? RIG_UI.limeDim : RIG_UI.ink4 }),
22056
+ line(`root link ${linkText}`, { fg: link?.ok ? RIG_UI.limeDim : server?.kind === "remote" ? RIG_UI.yellow : RIG_UI.ink4 }),
22057
+ ...server?.aliases && server.aliases.length > 0 ? [line(`aliases ${server.aliases.map((alias) => `${alias.selected ? "*" : ""}${alias.alias}${alias.kind === "remote" && alias.baseUrl ? `=${alias.baseUrl}` : ""}`).join(", ")}`, { fg: RIG_UI.ink4 })] : [],
21664
22058
  ...server?.error ? [line(`attention ${server.error}`, { fg: RIG_UI.yellow })] : []
21665
22059
  ];
21666
22060
  }
21667
22061
  function renderServerScene(state, layout) {
21668
22062
  const server = serverState(state);
21669
- const selected = selectedIndex(state, 10);
22063
+ const actions = serverActions(state);
22064
+ const selected = selectedIndex(state, actions.length);
21670
22065
  const loading = state.status === "loading" && !server;
21671
22066
  const errorText = state.error?.message ?? lastRefreshError(state);
21672
22067
  const panelLines = [
21673
22068
  ...errorText ? errorBanner(errorText, "select refresh or doctor below to retry") : [],
21674
22069
  line("server controls", { fg: RIG_UI.ink3 }),
21675
22070
  blank(),
21676
- line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
21677
- ...SERVER_ACTIONS.map(({ detail, item }, index) => selectableDeckRow({ label: item.label, detail, index, active: selected === index }, item)),
21678
- blank(),
21679
22071
  line("STATUS", { fg: RIG_UI.ink3, bold: true }),
21680
22072
  ...loading ? loadingRows("server status", state.tick) : statusRows2(server),
21681
22073
  blank(),
21682
- line("enter/click runs action \xB7 command rows stay inside OpenTUI PTY", { fg: RIG_UI.ink4 })
22074
+ line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
22075
+ ...actions.map(({ detail, item }, index) => selectableDeckRow({ label: item.label, detail, index, active: selected === index }, item)),
22076
+ blank(),
22077
+ line("enter/click runs native actions \xB7 add/use remote prompts stay in OpenTUI", { fg: RIG_UI.ink4 })
21683
22078
  ];
21684
22079
  return makeSceneFrame({
21685
22080
  scene: "server",
@@ -21706,13 +22101,13 @@ function renderServerScene(state, layout) {
21706
22101
  }
21707
22102
 
21708
22103
  // packages/cli/src/app-opentui/scenes/tasks.ts
21709
- var TASK_RECOVERY_ITEMS = [
22104
+ var BASE_TASK_RECOVERY_ITEMS = [
21710
22105
  { id: "refresh", label: "refresh", intent: { scene: "tasks", argv: ["tasks"], action: { kind: "refresh", label: "Refresh tasks" } }, message: "refresh task source" },
21711
- { id: "next", label: "next", intent: { scene: "command", argv: ["task", "run", "--next"], action: { kind: "command-run", label: "Run next task" } }, message: "dispatch next runnable task" },
21712
- { id: "repair", label: "repair", intent: { scene: "command", argv: ["init", "--repair"], action: { kind: "command-run", label: "Repair task source" } }, message: "repair project/task source" },
22106
+ { id: "next", label: "next", intent: { scene: "tasks", argv: ["task", "run", "--next"], action: { kind: "task-run-next", label: "Dispatch next task" } }, message: "dispatch next runnable task" },
21713
22107
  { id: "server", label: "server", intent: { scene: "server", argv: ["server", "status"], action: { kind: "refresh", label: "Open server controls" } }, message: "server controls" },
21714
22108
  { id: "doctor", label: "doctor", intent: { scene: "doctor", argv: ["doctor"], action: { kind: "doctor-run", label: "Run doctor" } }, message: "diagnose task source" }
21715
22109
  ];
22110
+ var TASK_REPAIR_LINK_ITEM = { id: "repair-link", label: "repair link", intent: { scene: "server", argv: ["server", "repair-link"], action: { kind: "remote-link-repair", label: "Repair remote link", payload: { returnScene: "tasks" } } }, message: "backfill or prepare remote project link" };
21716
22111
  var COL3 = {
21717
22112
  glyph: 1,
21718
22113
  id: 11,
@@ -21872,34 +22267,44 @@ function taskRow(width, task, active, optimistic) {
21872
22267
  const item = taskSelectableItem(task);
21873
22268
  return item ? withSelectable(row, item) : row;
21874
22269
  }
21875
- var RECOVERY_DECKS2 = [
22270
+ var BASE_RECOVERY_DECKS2 = [
21876
22271
  { label: "refresh", detail: "reload task source" },
21877
22272
  { label: "next", detail: "dispatch next runnable task" },
21878
- { label: "repair", detail: "repair project/task source link" },
21879
22273
  { label: "server", detail: "open server controls" },
21880
22274
  { label: "doctor", detail: "diagnose task source" }
21881
22275
  ];
21882
- function recoveryRows2(selected, reason) {
22276
+ function taskRecoveryItems(state) {
22277
+ if (!shouldOfferRemoteLinkRepair(state))
22278
+ return BASE_TASK_RECOVERY_ITEMS;
22279
+ return [BASE_TASK_RECOVERY_ITEMS[0], BASE_TASK_RECOVERY_ITEMS[1], TASK_REPAIR_LINK_ITEM, ...BASE_TASK_RECOVERY_ITEMS.slice(2)];
22280
+ }
22281
+ function taskRecoveryDecks(state) {
22282
+ if (!shouldOfferRemoteLinkRepair(state))
22283
+ return BASE_RECOVERY_DECKS2;
22284
+ return [BASE_RECOVERY_DECKS2[0], BASE_RECOVERY_DECKS2[1], { label: "repair link", detail: "backfill/prepare remote project-root link" }, ...BASE_RECOVERY_DECKS2.slice(2)];
22285
+ }
22286
+ function recoveryRows2(state, selected, reason) {
22287
+ const items = taskRecoveryItems(state);
21883
22288
  return [
21884
22289
  ...reason ? [line(reason, { fg: RIG_UI.yellow }), line("", { fg: RIG_UI.ink3 })] : [],
21885
22290
  line("ACTIONS", { fg: RIG_UI.ink3, bold: true }),
21886
- ...RECOVERY_DECKS2.map((deck, index) => selectableDeckRow({ ...deck, index, active: selected === index }, TASK_RECOVERY_ITEMS[index]))
22291
+ ...taskRecoveryDecks(state).map((deck, index) => selectableDeckRow({ ...deck, index, active: selected === index }, items[index]))
21887
22292
  ];
21888
22293
  }
21889
- function emptyRows2(query, total, selected) {
22294
+ function emptyRows2(state, query, total, selected) {
21890
22295
  if (!query.trim() && total === 0)
21891
- return recoveryRows2(selected, "No tasks loaded.");
22296
+ return recoveryRows2(state, selected, "No tasks loaded.");
21892
22297
  if (query.trim() && total > 0) {
21893
22298
  return [
21894
- ...recoveryRows2(selected, `No matching tasks for ${JSON.stringify(query)} (${total} total).`)
22299
+ ...recoveryRows2(state, selected, `No matching tasks for ${JSON.stringify(query)} (${total} total).`)
21895
22300
  ];
21896
22301
  }
21897
- return recoveryRows2(selected, "No tasks are visible with this filter.");
22302
+ return recoveryRows2(state, selected, "No tasks are visible with this filter.");
21898
22303
  }
21899
22304
  function degradedRows2(state, selected) {
21900
22305
  if (!state.error)
21901
22306
  return [];
21902
- return recoveryRows2(selected, state.error.message);
22307
+ return recoveryRows2(state, selected, state.error.message);
21903
22308
  }
21904
22309
  function rawString(raw, key) {
21905
22310
  const value = raw?.[key];
@@ -21923,7 +22328,10 @@ function renderTaskDetail(state, taskId3, layout) {
21923
22328
  const activeRunId = task?.activeRun?.runId?.trim();
21924
22329
  const width = panelWidth13(layout);
21925
22330
  const selected = Math.max(0, state.selection.index);
22331
+ const errorText = state.error?.message ?? lastRefreshError(state);
22332
+ const offerRepairLink = shouldOfferRemoteLinkRepair(state, errorText);
21926
22333
  const headerLines = [
22334
+ ...errorText ? errorBanner(errorText, offerRepairLink ? "select repair link below to prepare the remote checkout" : "retry dispatch after resolving the issue") : [],
21927
22335
  line(`${glyph2} ${title}`, { fg: color, bold: true }),
21928
22336
  line(`task ${taskId3} \xB7 ${status}`, { fg: RIG_UI.ink3 }),
21929
22337
  ...task && task.labels.length > 0 ? [line(`labels: ${task.labels.join(", ")}`, { fg: RIG_UI.ink4 })] : [],
@@ -21935,8 +22343,16 @@ function renderTaskDetail(state, taskId3, layout) {
21935
22343
  const actionRows2 = [];
21936
22344
  actionRows2.push(selectableDeckRow({ label: "back", detail: "return to the task list", index, active: selected === index }, { id: "task-detail-back", label: "back", data: { taskDetailId: undefined }, message: "back to task list" }));
21937
22345
  index += 1;
21938
- actionRows2.push(selectableDeckRow({ label: "dispatch", detail: "submit a Pi run for this task", index, active: selected === index }, { id: `task-detail-run:${taskId3}`, label: `dispatch ${taskId3}`, intent: { scene: "tasks", argv: ["run", taskId3], action: { kind: "task-run-id", payload: { task: taskId3 }, label: `Dispatching ${taskId3}` } }, message: `dispatch task ${taskId3}` }));
21939
- index += 1;
22346
+ if (offerRepairLink) {
22347
+ actionRows2.push(selectableDeckRow({ label: "repair link", detail: "prepare/backfill the selected remote project-root link", index, active: selected === index }, { id: `task-detail-repair-link:${taskId3}`, label: "repair link", intent: { scene: "server", argv: ["server", "repair-link"], action: { kind: "remote-link-repair", label: "Repair remote link", payload: { returnScene: "tasks", taskDetailId: taskId3 } } }, message: "prepare remote checkout link" }));
22348
+ index += 1;
22349
+ }
22350
+ if (offerRepairLink) {
22351
+ actionRows2.push(line(" dispatch blocked until the remote project link is repaired", { fg: RIG_UI.ink4 }));
22352
+ } else {
22353
+ actionRows2.push(selectableDeckRow({ label: "dispatch", detail: "submit a Pi run for this task", index, active: selected === index }, { id: `task-detail-run:${taskId3}`, label: `dispatch ${taskId3}`, intent: { scene: "tasks", argv: ["run", taskId3], action: { kind: "task-run-id", payload: { task: taskId3 }, label: `Dispatching ${taskId3}` } }, message: `dispatch task ${taskId3}` }));
22354
+ index += 1;
22355
+ }
21940
22356
  if (activeRunId) {
21941
22357
  actionRows2.push(selectableDeckRow({ label: "attach", detail: `attach the Pi console to run ${activeRunId.slice(0, 8)}`, index, active: selected === index }, { id: `task-detail-attach:${taskId3}`, label: `attach ${activeRunId.slice(0, 8)}`, intent: { scene: "handoff", argv: ["attach", activeRunId], action: { kind: "run-attach", payload: { runId: activeRunId }, label: `Attach Pi ${activeRunId.slice(0, 8)}` } }, message: `attach Pi to ${activeRunId.slice(0, 8)}` }));
21942
22358
  index += 1;
@@ -21995,7 +22411,7 @@ function renderTasksScene(state, layout) {
21995
22411
  const tasks = sortTasks2(filterTasksForSearch(allTasks, query), spec);
21996
22412
  const selected = typeof state.data.selectedTaskId === "string" && tasks.some((task) => task.id === state.data.selectedTaskId) ? state.data.selectedTaskId : tasks[0]?.id;
21997
22413
  const selectedIndex2 = Math.max(0, tasks.findIndex((task) => task.id === selected));
21998
- const recoveryIndex = Math.max(0, Math.min(4, state.selection.index));
22414
+ const recoveryIndex = Math.max(0, Math.min(taskRecoveryItems(state).length - 1, state.selection.index));
21999
22415
  const dispatching = dispatchingFor(state);
22000
22416
  const dispatchLine = dispatching ? ` \xB7 ${state.data.dispatchingRun?.runId ?? "new"} ${dispatching.status}` : "";
22001
22417
  const updatedAgo = updatedAgoLabel2(state);
@@ -22015,7 +22431,7 @@ function renderTasksScene(state, layout) {
22015
22431
  ...tasks.map((task, index) => taskRow(contentWidth, task, index === selectedIndex2, dispatching && dispatching.taskId === task.id ? dispatching.status : undefined))
22016
22432
  ] : state.error ? degradedRows2(state, recoveryIndex) : loading ? loadingRows("tasks", state.tick) : [
22017
22433
  ...query.trim() ? [line(searchSummary("tasks", query, tasks.length, allTasks.length), { fg: RIG_UI.ink4 }), line("", { fg: RIG_UI.ink3 })] : [],
22018
- ...emptyRows2(query, allTasks.length, recoveryIndex)
22434
+ ...emptyRows2(state, query, allTasks.length, recoveryIndex)
22019
22435
  ]
22020
22436
  ];
22021
22437
  return makeSceneFrame({
@@ -22372,6 +22788,29 @@ function createDomainAdapter() {
22372
22788
  else
22373
22789
  await loadInitFacts(ctx);
22374
22790
  return true;
22791
+ case "server-use-local":
22792
+ await useLocalServerFromApp(ctx);
22793
+ return true;
22794
+ case "server-use-remote":
22795
+ await useRemoteServerFromApp(ctx, payloadString8(intent, "alias") ?? intent.argv[2]);
22796
+ return true;
22797
+ case "server-add-remote":
22798
+ await addRemoteServerFromApp(ctx, intent.argv);
22799
+ return true;
22800
+ case "github-auth-import":
22801
+ await importGitHubAuthFromGhApp(ctx);
22802
+ return true;
22803
+ case "remote-link-repair": {
22804
+ await repairRemoteProjectRootLinkFromApp(ctx);
22805
+ const returnScene = payloadString8(intent, "returnScene");
22806
+ if (returnScene && returnScene !== intent.scene) {
22807
+ ctx.emit({ type: "scene.change", scene: returnScene, intent: { scene: returnScene, argv: intent.argv, action: { kind: "refresh", label: `Refresh ${returnScene}`, payload: intent.action.payload } } });
22808
+ await refreshScene(ctx, returnScene, { scene: returnScene, argv: intent.argv, action: { kind: "refresh", label: `Refresh ${returnScene}`, payload: intent.action.payload } });
22809
+ } else {
22810
+ await refreshServerStatus(ctx);
22811
+ }
22812
+ return true;
22813
+ }
22375
22814
  case "doctor-run":
22376
22815
  await runDoctorChecksForApp(ctx);
22377
22816
  return true;