@cuna_labs/cli 0.1.0 → 0.1.1

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 (120) hide show
  1. package/dist/bin/cuna.js +15 -2
  2. package/dist/bin/cuna.js.map +1 -1
  3. package/dist/cli/command-help.js +2 -2
  4. package/dist/cli/command-help.js.map +1 -1
  5. package/dist/cli/first-line.d.ts +80 -0
  6. package/dist/cli/first-line.d.ts.map +1 -0
  7. package/dist/cli/first-line.js +119 -0
  8. package/dist/cli/first-line.js.map +1 -0
  9. package/dist/cli/help.js +1 -1
  10. package/dist/cli/process-entrypoint.d.ts +3 -0
  11. package/dist/cli/process-entrypoint.d.ts.map +1 -1
  12. package/dist/cli/process-entrypoint.js +3 -1
  13. package/dist/cli/process-entrypoint.js.map +1 -1
  14. package/dist/cli/progress-line.d.ts +78 -0
  15. package/dist/cli/progress-line.d.ts.map +1 -0
  16. package/dist/cli/progress-line.js +94 -0
  17. package/dist/cli/progress-line.js.map +1 -0
  18. package/dist/cli/recorded-launch-prompt.d.ts +83 -0
  19. package/dist/cli/recorded-launch-prompt.d.ts.map +1 -0
  20. package/dist/cli/recorded-launch-prompt.js +80 -0
  21. package/dist/cli/recorded-launch-prompt.js.map +1 -0
  22. package/dist/cli/run.d.ts +19 -0
  23. package/dist/cli/run.d.ts.map +1 -1
  24. package/dist/cli/run.js +233 -75
  25. package/dist/cli/run.js.map +1 -1
  26. package/dist/commands/commands.js +6 -4
  27. package/dist/commands/commands.js.map +1 -1
  28. package/dist/journey/account-identity.d.ts +55 -0
  29. package/dist/journey/account-identity.d.ts.map +1 -0
  30. package/dist/journey/account-identity.js +69 -0
  31. package/dist/journey/account-identity.js.map +1 -0
  32. package/dist/journey/api-effects.d.ts +9 -0
  33. package/dist/journey/api-effects.d.ts.map +1 -1
  34. package/dist/journey/api-effects.js +172 -19
  35. package/dist/journey/api-effects.js.map +1 -1
  36. package/dist/journey/index.d.ts +3 -1
  37. package/dist/journey/index.d.ts.map +1 -1
  38. package/dist/journey/index.js +2 -0
  39. package/dist/journey/index.js.map +1 -1
  40. package/dist/journey/orchestrator.d.ts +24 -0
  41. package/dist/journey/orchestrator.d.ts.map +1 -1
  42. package/dist/journey/orchestrator.js +21 -3
  43. package/dist/journey/orchestrator.js.map +1 -1
  44. package/dist/journey/provider-launch-intent.d.ts +11 -0
  45. package/dist/journey/provider-launch-intent.d.ts.map +1 -1
  46. package/dist/journey/provider-launch-intent.js +50 -15
  47. package/dist/journey/provider-launch-intent.js.map +1 -1
  48. package/dist/journey/remote-workspace.d.ts +11 -0
  49. package/dist/journey/remote-workspace.d.ts.map +1 -1
  50. package/dist/journey/remote-workspace.js +94 -14
  51. package/dist/journey/remote-workspace.js.map +1 -1
  52. package/dist/journey/root-entry.d.ts +15 -0
  53. package/dist/journey/root-entry.d.ts.map +1 -1
  54. package/dist/journey/root-entry.js +18 -6
  55. package/dist/journey/root-entry.js.map +1 -1
  56. package/dist/journey/selection.d.ts +9 -0
  57. package/dist/journey/selection.d.ts.map +1 -1
  58. package/dist/journey/selection.js +14 -2
  59. package/dist/journey/selection.js.map +1 -1
  60. package/dist/journey/session-disposition.d.ts +45 -0
  61. package/dist/journey/session-disposition.d.ts.map +1 -0
  62. package/dist/journey/session-disposition.js +7 -0
  63. package/dist/journey/session-disposition.js.map +1 -0
  64. package/dist/journey/wait-policy.d.ts +218 -0
  65. package/dist/journey/wait-policy.d.ts.map +1 -0
  66. package/dist/journey/wait-policy.js +233 -0
  67. package/dist/journey/wait-policy.js.map +1 -0
  68. package/dist/journey/workspace-effects.d.ts +7 -1
  69. package/dist/journey/workspace-effects.d.ts.map +1 -1
  70. package/dist/journey/workspace-effects.js +131 -14
  71. package/dist/journey/workspace-effects.js.map +1 -1
  72. package/dist/machines/explorer.js +2 -2
  73. package/dist/machines/explorer.js.map +1 -1
  74. package/dist/machines/machine-first.d.ts.map +1 -1
  75. package/dist/machines/machine-first.js +2 -0
  76. package/dist/machines/machine-first.js.map +1 -1
  77. package/dist/machines/session-actionability.js +10 -0
  78. package/dist/machines/session-actionability.js.map +1 -1
  79. package/dist/machines/session-visibility.d.ts +2 -0
  80. package/dist/machines/session-visibility.d.ts.map +1 -1
  81. package/dist/machines/session-visibility.js +5 -0
  82. package/dist/machines/session-visibility.js.map +1 -1
  83. package/dist/platform/process-lock.d.ts +21 -0
  84. package/dist/platform/process-lock.d.ts.map +1 -0
  85. package/dist/platform/process-lock.js +114 -0
  86. package/dist/platform/process-lock.js.map +1 -0
  87. package/dist/runtime/boundary.d.ts +2 -0
  88. package/dist/runtime/boundary.d.ts.map +1 -1
  89. package/dist/runtime/boundary.js +155 -8
  90. package/dist/runtime/boundary.js.map +1 -1
  91. package/dist/runtime/node-foreground-session.d.ts +10 -0
  92. package/dist/runtime/node-foreground-session.d.ts.map +1 -1
  93. package/dist/runtime/node-foreground-session.js +112 -4
  94. package/dist/runtime/node-foreground-session.js.map +1 -1
  95. package/dist/runtime/terminal-client-identity.d.ts +66 -0
  96. package/dist/runtime/terminal-client-identity.d.ts.map +1 -0
  97. package/dist/runtime/terminal-client-identity.js +184 -0
  98. package/dist/runtime/terminal-client-identity.js.map +1 -0
  99. package/dist/runtime/terminal-transport.js +2 -2
  100. package/dist/runtime/terminal-transport.js.map +1 -1
  101. package/dist/sync/continuous-sync-supervisor.d.ts +64 -1
  102. package/dist/sync/continuous-sync-supervisor.d.ts.map +1 -1
  103. package/dist/sync/continuous-sync-supervisor.js +247 -28
  104. package/dist/sync/continuous-sync-supervisor.js.map +1 -1
  105. package/dist/sync/journal.js +9 -39
  106. package/dist/sync/journal.js.map +1 -1
  107. package/dist/sync/workspace-sync-product-service.d.ts +83 -1
  108. package/dist/sync/workspace-sync-product-service.d.ts.map +1 -1
  109. package/dist/sync/workspace-sync-product-service.js +324 -12
  110. package/dist/sync/workspace-sync-product-service.js.map +1 -1
  111. package/dist/terminal/foreground.d.ts.map +1 -1
  112. package/dist/terminal/foreground.js +82 -15
  113. package/dist/terminal/foreground.js.map +1 -1
  114. package/dist/terminal/workbench.d.ts +7 -0
  115. package/dist/terminal/workbench.d.ts.map +1 -1
  116. package/dist/terminal/workbench.js +24 -6
  117. package/dist/terminal/workbench.js.map +1 -1
  118. package/dist/version.d.ts +1 -1
  119. package/dist/version.js +1 -1
  120. package/package.json +1 -1
@@ -4,7 +4,7 @@ import { LOCAL_ACTION_PROTOCOL_VERSION, LocalActionBroker, digestLocalActionArgu
4
4
  import { RuntimeBoundaryError, runtimeFailure, terminalHistoryGap } from "../runtime/errors.js";
5
5
  import { assertCanonicalUuid } from "../core/validation.js";
6
6
  import { buildAppbarModel } from "./appbar.js";
7
- import { renderWorkbenchFrame } from "./workbench.js";
7
+ import { renderWorkbenchFrame, workbenchUpdate } from "./workbench.js";
8
8
  import { ViewportRegistry } from "./viewport.js";
9
9
  import { XtermViewportAdapter } from "./xterm-vte.js";
10
10
  const ESCAPE_PREFIX = 0x1d;
@@ -39,6 +39,10 @@ const ATTACHING_FRAME_MS = 90;
39
39
  // never more than one second under an injected/test cadence.
40
40
  const MAX_DISCONNECT_FRAME_MS = 250;
41
41
  const INPUT_WITHHELD_NOTICE = "Reconnecting · input was not sent. Retry after terminal attached.";
42
+ // Sent keys went unacknowledged past the runtime's input deadline (R12). Said
43
+ // the moment the runtime gives up on the connection, and it already carries the
44
+ // "not resent" half, so it is never prefixed with HISTORICAL_INPUT_NOTICE.
45
+ const INPUT_STALLED_NOTICE = "Connection stalled · reconnecting — input not resent";
42
46
  const FLOW_CONTROL_NOTICE = "Terminal output kept active · Ctrl+] s sends Ctrl+S remotely.";
43
47
  const RECONNECT_FAILED_NOTICE = "Reconnect failed · Ctrl+] r retries · Ctrl+C disconnects.";
44
48
  // Automatic recovery back-off: 100 ms doubling, capped at 5 s per wait, ten
@@ -69,6 +73,7 @@ export class ForegroundTerminalCoordinator {
69
73
  #renderTail = Promise.resolve();
70
74
  /** The last complete frame the host accepted; undefined whenever the host screen is not known to show it. */
71
75
  #lastHostFrame;
76
+ #lastWorkbenchFrame;
72
77
  #inputTail = Promise.resolve();
73
78
  #copyDetectors = new Map();
74
79
  #copyLinks = new Map();
@@ -574,17 +579,33 @@ export class ForegroundTerminalCoordinator {
574
579
  const current = this.#tabs.get(event.tabId);
575
580
  if (event.signal.aborted || current !== tab || !sameSnapshotBinding(tab.snapshot, event.binding))
576
581
  return;
577
- await this.#render();
582
+ // Parsing preserves every ordered byte; painting may skip intermediate
583
+ // screens. A slow host must not hold up ingestion of newer remote output.
584
+ this.#queueStateRender();
578
585
  }
579
586
  #terminalState(snapshot) {
580
587
  const tab = this.#tabs.get(snapshot.tabId);
581
588
  if (tab !== undefined) {
582
589
  this.#forgetSeatNoticeOnSeatChange(tab.snapshot, snapshot);
590
+ // The PTY keeps whatever geometry the previous writer set, and an
591
+ // observer never resizes it. Taking the seat is therefore the first
592
+ // moment this client may state its own size -- without this, the remote
593
+ // keeps painting at the old writer's height and the bottom of a larger
594
+ // local terminal stays empty until some later host resize happens to
595
+ // occur. Reconcile once, on the observer -> writer edge only.
596
+ const becameWriter = tab.snapshot.accessMode === "observer" &&
597
+ snapshot.accessMode === "writer" && snapshot.state === "active";
583
598
  tab.snapshot = snapshot;
599
+ if (becameWriter)
600
+ this.#reconcileSeatGeometry(snapshot);
584
601
  if (snapshot.state === "active" &&
585
- (this.#browserNotice === INPUT_WITHHELD_NOTICE || this.#browserNotice === RECONNECT_FAILED_NOTICE)) {
602
+ (this.#browserNotice === INPUT_WITHHELD_NOTICE || this.#browserNotice === INPUT_STALLED_NOTICE ||
603
+ isReconnectFailedNotice(this.#browserNotice))) {
586
604
  this.#browserNotice = undefined;
587
605
  }
606
+ if (snapshot.state === "interrupted" && snapshot.reason === "input_ack_timeout") {
607
+ this.#browserNotice = INPUT_STALLED_NOTICE;
608
+ }
588
609
  if (this.#localDetachTabIds.has(snapshot.tabId) &&
589
610
  (snapshot.state === "failed" || snapshot.state === "interrupted" || snapshot.state === "closed" || snapshot.state === "detached")) {
590
611
  // A local detach may close the wire before the runtime publishes its
@@ -626,7 +647,8 @@ export class ForegroundTerminalCoordinator {
626
647
  void this.stop().catch(() => { this.#state = "failed"; });
627
648
  return;
628
649
  }
629
- if (snapshot.state === "interrupted" && !this.#reconnectTasks.has(snapshot.tabId)) {
650
+ if (snapshot.state === "interrupted" && !this.#reconnectTasks.has(snapshot.tabId) &&
651
+ !this.#recoverableReconnectFailures.has(snapshot.tabId)) {
630
652
  this.#startRecovery(snapshot.tabId);
631
653
  }
632
654
  this.#queueStateRender();
@@ -645,7 +667,8 @@ export class ForegroundTerminalCoordinator {
645
667
  const snapshot = await this.#requireRuntime().reconnect({ tabId, signal: this.#lifetimeAbort.signal });
646
668
  await this.#reconcileGeometry(snapshot, false);
647
669
  this.#recoverableReconnectFailures.delete(tabId);
648
- if (this.#browserNotice === INPUT_WITHHELD_NOTICE || this.#browserNotice === RECONNECT_FAILED_NOTICE) {
670
+ if (this.#browserNotice === INPUT_WITHHELD_NOTICE || this.#browserNotice === INPUT_STALLED_NOTICE ||
671
+ isReconnectFailedNotice(this.#browserNotice)) {
649
672
  this.#browserNotice = undefined;
650
673
  }
651
674
  return;
@@ -660,7 +683,7 @@ export class ForegroundTerminalCoordinator {
660
683
  }
661
684
  if (this.#state === "active") {
662
685
  this.#recoverableReconnectFailures.set(tabId, lastFailure ?? runtimeFailure("terminal_disconnected", "Automatic terminal reconnection was exhausted.", { retryable: true }));
663
- this.#browserNotice = RECONNECT_FAILED_NOTICE;
686
+ this.#browserNotice = reconnectFailedNotice(this.#recoverableReconnectFailures.get(tabId));
664
687
  await this.#render();
665
688
  }
666
689
  }
@@ -699,7 +722,7 @@ export class ForegroundTerminalCoordinator {
699
722
  this.#pendingBrowserAction = undefined;
700
723
  this.#pendingBrowserActionTabId = undefined;
701
724
  }
702
- this.#browserNotice = INPUT_WITHHELD_NOTICE;
725
+ this.#browserNotice = this.#unavailableInputNotice();
703
726
  void this.#render().catch(() => undefined);
704
727
  return;
705
728
  }
@@ -745,7 +768,7 @@ export class ForegroundTerminalCoordinator {
745
768
  this.#pendingBrowserAction = undefined;
746
769
  this.#pendingBrowserActionTabId = undefined;
747
770
  }
748
- this.#browserNotice = INPUT_WITHHELD_NOTICE;
771
+ this.#browserNotice = this.#unavailableInputNotice();
749
772
  void this.#render().catch(() => undefined);
750
773
  return;
751
774
  }
@@ -1014,15 +1037,15 @@ export class ForegroundTerminalCoordinator {
1014
1037
  if (target === undefined || target.tabId !== this.#activeTabId || tab === undefined || link === undefined ||
1015
1038
  link.agentSessionId !== tab.snapshot.agentSessionId || link.processEpoch !== tab.snapshot.processEpoch ||
1016
1039
  link.fencingGeneration !== tab.snapshot.fencingGeneration) {
1017
- this.#browserNotice = "No hay enlace para copiar. Solicita un enlace de acceso en el agente.";
1040
+ this.#browserNotice = "No sign-in link available. Request a new link in the agent.";
1018
1041
  }
1019
1042
  else {
1020
1043
  try {
1021
1044
  await (this.#options.copyText ?? copyLocalText)(link.url);
1022
- this.#browserNotice = "Enlace copiado al portapapeles local. Si caducó, solicita uno nuevo.";
1045
+ this.#browserNotice = "Full link copied. Paste it into your browser.";
1023
1046
  }
1024
1047
  catch {
1025
- this.#browserNotice = "No se pudo copiar el enlace al portapapeles local. Inténtalo de nuevo.";
1048
+ this.#browserNotice = "Could not copy the link. Try again.";
1026
1049
  }
1027
1050
  }
1028
1051
  await this.#render();
@@ -1504,6 +1527,10 @@ export class ForegroundTerminalCoordinator {
1504
1527
  }
1505
1528
  catch { /* restore still owns terminal cleanup */ }
1506
1529
  }
1530
+ #unavailableInputNotice() {
1531
+ const failure = this.#activeTabId === undefined ? undefined : this.#recoverableReconnectFailures.get(this.#activeTabId);
1532
+ return failure === undefined ? INPUT_WITHHELD_NOTICE : reconnectFailedNotice(failure);
1533
+ }
1507
1534
  #captureInputTarget() {
1508
1535
  const tabId = this.#activeTabId;
1509
1536
  if (tabId === undefined)
@@ -1558,6 +1585,21 @@ export class ForegroundTerminalCoordinator {
1558
1585
  }
1559
1586
  await this.#render();
1560
1587
  }
1588
+ /**
1589
+ * Push this host's geometry to the PTY after the writer seat moves here.
1590
+ * `#terminalState` is synchronous, so the reconciliation runs as its own
1591
+ * guarded task; a refusal is reported like any other terminal failure and
1592
+ * never silently leaves the viewport claiming a size the PTY does not have.
1593
+ */
1594
+ #reconcileSeatGeometry(snapshot) {
1595
+ void this.#reconcileGeometry(snapshot, false).then(() => this.#queueStateRender(), (error) => {
1596
+ if (this.#state !== "active")
1597
+ return;
1598
+ if (error instanceof RuntimeBoundaryError && (error.code === "terminal_observer" || error.code === "terminal_disconnected"))
1599
+ return;
1600
+ this.#recordFailure(error);
1601
+ });
1602
+ }
1561
1603
  async #reconcileGeometry(snapshot, repaintAfterReplay) {
1562
1604
  const tab = this.#tabs.get(snapshot.tabId);
1563
1605
  if (tab === undefined ||
@@ -1640,7 +1682,9 @@ export class ForegroundTerminalCoordinator {
1640
1682
  id: tab.intent.tabId,
1641
1683
  label: tab.intent.label,
1642
1684
  agent: tab.intent.agent,
1643
- viewport: tab.snapshot.accessMode === "observer"
1685
+ // Hidden tabs contribute labels only; projecting their cells would
1686
+ // recapture a full terminal on every visible output frame.
1687
+ viewport: tab.intent.tabId === activeTabId && tab.snapshot.accessMode === "observer"
1644
1688
  ? tab.viewport.snapshotForHost(dimensions.columns, remoteRows(dimensions.rows))
1645
1689
  : tab.viewport.snapshot(),
1646
1690
  }));
@@ -1649,7 +1693,7 @@ export class ForegroundTerminalCoordinator {
1649
1693
  rows: dimensions.rows,
1650
1694
  activeTabId,
1651
1695
  tabs,
1652
- ...(this.#copyLinks.has(activeTabId) ? { action: "Copiar enlace · Ctrl+] y" } : {}),
1696
+ ...(this.#copyLinks.has(activeTabId) ? { action: "Copy link · Ctrl+] y" } : {}),
1653
1697
  appbar: this.#options.appbar?.() ?? runtimeAppbar(this.#clock(), this.#tabs.get(activeTabId)?.snapshot, this.#tabs.get(activeTabId)?.intent),
1654
1698
  color: this.#options.color ?? true,
1655
1699
  ...(this.#disconnectNotice !== undefined
@@ -1658,7 +1702,7 @@ export class ForegroundTerminalCoordinator {
1658
1702
  ? { notice: "Restoring terminal\u2026" }
1659
1703
  : this.#browserNotice !== undefined
1660
1704
  ? { notice: this.#tabs.get(activeTabId)?.snapshot.historicalInputUncertainty === true &&
1661
- (this.#browserNotice === INPUT_WITHHELD_NOTICE || this.#browserNotice === RECONNECT_FAILED_NOTICE)
1705
+ (this.#browserNotice === INPUT_WITHHELD_NOTICE || isReconnectFailedNotice(this.#browserNotice))
1662
1706
  ? `${HISTORICAL_INPUT_NOTICE} · ${this.#browserNotice}` : this.#browserNotice }
1663
1707
  : this.#pendingBrowserAction !== undefined && this.#pendingBrowserActionTabId === activeTabId
1664
1708
  ? {
@@ -1687,9 +1731,11 @@ export class ForegroundTerminalCoordinator {
1687
1731
  // anything else that touches the host clears this memory first.
1688
1732
  if (this.#lastHostFrame !== undefined && sameBytes(this.#lastHostFrame, frame.bytes))
1689
1733
  return;
1734
+ const update = workbenchUpdate(this.#lastHostFrame === undefined ? undefined : this.#lastWorkbenchFrame, frame);
1690
1735
  this.#lastHostFrame = undefined;
1691
- await this.#options.host.write(frame.bytes);
1736
+ await this.#options.host.write(update);
1692
1737
  this.#lastHostFrame = frame.bytes;
1738
+ this.#lastWorkbenchFrame = frame;
1693
1739
  });
1694
1740
  this.#renderTail = operation.then(() => undefined, () => undefined);
1695
1741
  await operation;
@@ -1962,6 +2008,27 @@ export function admitForegroundDimensions(input) {
1962
2008
  }
1963
2009
  return Object.freeze({ columns: input.columns, rows: input.rows });
1964
2010
  }
2011
+ /**
2012
+ * Automatic recovery gave up. Name the typed reason it gave up on: the person
2013
+ * is looking at a frozen frame and "Reconnect failed" alone tells them nothing
2014
+ * about whether retrying can help. Only this client's own closed error codes
2015
+ * are rendered -- never remote text.
2016
+ */
2017
+ function reconnectFailedNotice(failure) {
2018
+ if (!(failure instanceof RuntimeBoundaryError))
2019
+ return RECONNECT_FAILED_NOTICE;
2020
+ // The capability name is this client's own closed enum, not remote text, and
2021
+ // it is the one word that says WHICH contract the replacement grant failed to
2022
+ // prove. Without it "capability unknown" cannot be acted on by anyone.
2023
+ const capability = failure.safeDetails?.capability;
2024
+ const subject = typeof capability === "string" && /^[a-z_]{1,32}$/u.test(capability)
2025
+ ? `${failure.code.replaceAll("_", " ")} (${capability.replaceAll("_", " ")})`
2026
+ : failure.code.replaceAll("_", " ");
2027
+ return `Reconnect failed: ${subject} · Ctrl+] r retries · Ctrl+C disconnects.`;
2028
+ }
2029
+ function isReconnectFailedNotice(value) {
2030
+ return value !== undefined && value.startsWith("Reconnect failed");
2031
+ }
1965
2032
  function sameBytes(left, right) {
1966
2033
  if (left.byteLength !== right.byteLength)
1967
2034
  return false;