@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
@@ -0,0 +1,80 @@
1
+ /**
2
+ * THE ONE QUESTION THE CLI ASKS MID-JOURNEY, AND WHAT THE SCREEN OWES THE
3
+ * PERSON THE MOMENT THEY ANSWER IT.
4
+ *
5
+ * WHY THIS IS A MODULE AND NOT FOUR LINES IN `run.ts`. Measured 2026-09-22
6
+ * (`prds/cuna-cli-latency-before-20260922.md` § 3, finding 1): after this
7
+ * answer was accepted, the terminal went BYTE-silent — no output at all, not a
8
+ * repeating spinner — for 2 317 ms, 3 864 ms and 18 314 ms in three of five
9
+ * runs. The screen froze on the person's own keystroke, which is the worst
10
+ * shape a stall can take, because the last thing they did is the thing that
11
+ * appears to have broken it.
12
+ *
13
+ * The cause was structural: the question gave the progress row up in order to
14
+ * be readable, and nothing took the row back until the next remote step
15
+ * happened to report. Whatever that step cost was silence. So the repair is not
16
+ * "make the next step faster" — it is that the answer itself is an event worth
17
+ * rendering, and it is rendered from the branch that was actually taken rather
18
+ * than from the question that was asked.
19
+ *
20
+ * Keeping it here rather than inline is what makes that assertable: `run.ts`
21
+ * reaches this code only through a real TTY, a real `process.stdin` and a
22
+ * 90 ms paint timer, none of which can express the defect, which was entirely
23
+ * about WHEN a line appears relative to an answer.
24
+ */
25
+ /**
26
+ * Asked verbatim, and quoted verbatim in the measurement, so the two can be
27
+ * compared without guessing which build produced a transcript.
28
+ */
29
+ export const RECORDED_LAUNCH_QUESTION = "A previous launch is recorded. Create another session? [y/N; No resumes the recorded launch] ";
30
+ /**
31
+ * How long the screen may stay unchanged after the answer is accepted.
32
+ *
33
+ * DERIVATION. Not a tuning knob: it is the bound this module exists to hold,
34
+ * and it is deliberately far below the 2 000 ms threshold the measurement used
35
+ * to call a stall a stall, because a stall that begins on the person's own
36
+ * keypress reads as a crash rather than as waiting. The measured worst case was
37
+ * 18 314 ms, which is 36 times this.
38
+ */
39
+ export const RECORDED_LAUNCH_ACKNOWLEDGEMENT_BUDGET_MS = 500;
40
+ /**
41
+ * What answers the recorded-launch question for a folder journey.
42
+ * `--new-session` is that answer already given on the command line, so it is
43
+ * not asked again: asking let a No, or a slow answer, override the flag (qa6
44
+ * witness 2026-09-22). Without the flag the person is asked.
45
+ */
46
+ export function recordedLaunchConfirmation(newSessionFlag, ask) {
47
+ return newSessionFlag ? async () => true : ask;
48
+ }
49
+ /** Only an explicit yes creates; anything else, including empty, resumes. */
50
+ export function recordedLaunchWantsNewSession(answer) {
51
+ return /^y(?:es)?$/iu.test(answer.trim());
52
+ }
53
+ /**
54
+ * What the screen says next, named for the branch that was taken rather than
55
+ * for the question that was asked.
56
+ *
57
+ * `No` resumes a launch that already exists, and calling that "Creating" would
58
+ * be the CLI announcing work it is not about to do — which is the same defect
59
+ * as the silence, one layer up: a screen that does not describe what is
60
+ * happening.
61
+ */
62
+ export function recordedLaunchAcknowledgement(wantsNewSession, createLabel) {
63
+ return wantsNewSession ? createLabel : "Resuming the recorded launch";
64
+ }
65
+ /**
66
+ * Ask the recorded-launch question and answer the screen at once.
67
+ *
68
+ * THE BOUND THIS GIVES, STATED EXACTLY. `acknowledge` is called synchronously
69
+ * between the answer arriving and this function resolving, so no remote work
70
+ * can be dispatched in between — not because a timer says so, but because
71
+ * there is nowhere for it to go. The caller therefore cannot reintroduce the
72
+ * measured silence by being slow; it can only reintroduce it by writing
73
+ * nothing, which is what `acknowledge` exists to prevent.
74
+ */
75
+ export async function askRecordedLaunch(input) {
76
+ const wantsNewSession = recordedLaunchWantsNewSession(await input.ask(RECORDED_LAUNCH_QUESTION));
77
+ input.acknowledge(recordedLaunchAcknowledgement(wantsNewSession, input.createLabel));
78
+ return wantsNewSession;
79
+ }
80
+ //# sourceMappingURL=recorded-launch-prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recorded-launch-prompt.js","sourceRoot":"","sources":["../../src/cli/recorded-launch-prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GACnC,+FAA+F,CAAC;AAElG;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,yCAAyC,GAAG,GAAG,CAAC;AAE7D;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CACxC,cAAuB,EACvB,GAAoC;IAEpC,OAAO,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;AACjD,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,6BAA6B,CAAC,MAAc;IAC1D,OAAO,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,6BAA6B,CAAC,eAAwB,EAAE,WAAmB;IACzF,OAAO,eAAe,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,8BAA8B,CAAC;AACxE,CAAC;AAgBD;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,KAAgC;IACtE,MAAM,eAAe,GAAG,6BAA6B,CAAC,MAAM,KAAK,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,CAAC;IACjG,KAAK,CAAC,WAAW,CAAC,6BAA6B,CAAC,eAAe,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;IACrF,OAAO,eAAe,CAAC;AACzB,CAAC"}
package/dist/cli/run.d.ts CHANGED
@@ -10,6 +10,7 @@ import { type ExitCode } from "../core/errors.js";
10
10
  import type { SecureCredentialBackend } from "../credentials/contracts.js";
11
11
  import { CredentialVault } from "../credentials/vault.js";
12
12
  import { type AgentJourneyEffects, type ReconciledAgentJourneyIntent } from "../journey/index.js";
13
+ import { type FirstLine } from "./first-line.js";
13
14
  import { type RootJourneyRunner } from "../journey/root-entry.js";
14
15
  import { type PlatformAdapter } from "../platform/adapter.js";
15
16
  import { type RuntimeFeatureGate } from "../runtime/contracts.js";
@@ -50,6 +51,24 @@ export interface RunCliDependencies {
50
51
  readonly managedWorkspaceMachineId?: string;
51
52
  /** Test seam for the folder a command resolves its workspace binding from. */
52
53
  readonly workspaceRoot?: string;
54
+ /**
55
+ * Test seam for the stream a mid-journey question reads its answer from.
56
+ * Production always leaves this absent and uses the real `process.stdin`.
57
+ *
58
+ * It exists because the recorded-launch question is the one place the CLI
59
+ * stops and waits for a person mid-journey, and the measured defect there was
60
+ * entirely about WHEN the next line appears relative to the answer
61
+ * (`prds/cuna-cli-latency-before-20260922.md` § 3, finding 1). Without a seam
62
+ * that ordering is only assertable through a real TTY, and the live evidence
63
+ * for the repair is n=0: none of the nine runs of § 8.2 met the question.
64
+ */
65
+ readonly promptInput?: NodeJS.ReadableStream;
66
+ /**
67
+ * The row `bin/cuna.ts` painted before this module loaded, if it painted one.
68
+ * The paint site that would have drawn the same row claims it instead of
69
+ * drawing it again; see `cli/first-line.ts`.
70
+ */
71
+ readonly firstLine?: FirstLine;
53
72
  readonly automaticJourneyEffectsFactory?: (input: {
54
73
  readonly client: CunaApiClient;
55
74
  readonly intent: ReconciledAgentJourneyIntent;
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"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"}
package/dist/cli/run.js CHANGED
@@ -5,8 +5,9 @@ import { runOwnerGrantsScreen } from "../runtime/owner-grants-screen.js";
5
5
  import { ownerGrantOperationStore } from "../runtime/owner-grant-operations.js";
6
6
  import { runProviderScreen } from "../machines/provider-screen.js";
7
7
  import { Writable } from "node:stream";
8
- import { terminalCellWidth, truncateTerminalLine } from "../terminal/cell-width.js";
8
+ import { terminalCellWidth } from "../terminal/cell-width.js";
9
9
  import { createInterface } from "node:readline/promises";
10
+ import { setTimeout as delay } from "node:timers/promises";
10
11
  import { mkdir } from "node:fs/promises";
11
12
  import { createHash } from "node:crypto";
12
13
  import { launchRemoteWorkspaceSession } from "../journey/remote-workspace.js";
@@ -26,8 +27,12 @@ import { integerArgument } from "../core/validation.js";
26
27
  import { CredentialBoundaryError } from "../credentials/errors.js";
27
28
  import { CredentialVault } from "../credentials/vault.js";
28
29
  import { LocalEncryptedSessionBackend, localEncryptedSessionPaths } from "../credentials/local-session.js";
29
- import { conservativeFilesystemCapabilities, createApiAgentJourneyEffects, createWorkspaceJourneyEffects, orchestrateAgentJourney, preflightAgentJourneyInvocation, } from "../journey/index.js";
30
- import { rootJourneyArgv, runNodeRootJourney, } from "../journey/root-entry.js";
30
+ import { conservativeFilesystemCapabilities, createApiAgentJourneyEffects, createWorkspaceJourneyEffects, journeyWaitLine, orchestrateAgentJourney, preflightAgentJourneyInvocation, readAccountIdentityWithin, } from "../journey/index.js";
31
+ import { agentSessionDispositionLine, composeInlineProgressLine, inlineProgressColumns, renderInlineProgressFrame, } from "./progress-line.js";
32
+ import { agentDisplayName, journeyPreparationLabel, ROOT_FIRST_LINE_LABEL, } from "./first-line.js";
33
+ import { askRecordedLaunch, recordedLaunchConfirmation } from "./recorded-launch-prompt.js";
34
+ import { settledAgentSessionDisposition } from "../journey/session-disposition.js";
35
+ import { agentJourneyCommand, rootJourneyArgv, runNodeRootJourney, } from "../journey/root-entry.js";
31
36
  import { createPlatformAdapter } from "../platform/adapter.js";
32
37
  import { CLI_VERSION, OUTPUT_SCHEMA_VERSION } from "../version.js";
33
38
  import { runtimeFeatureGates } from "../runtime/contracts.js";
@@ -421,12 +426,6 @@ function managedWorkspaceScope(intent, machineId) {
421
426
  }
422
427
  return `${intent.agent}-${intent.machine.kind}`;
423
428
  }
424
- function agentDisplayName(agent) {
425
- return agent === "claude-code" ? "Claude Code"
426
- : agent === "codex" ? "Codex"
427
- : agent === "opencode" ? "OpenCode"
428
- : "OpenClaw";
429
- }
430
429
  /**
431
430
  * `doctor` must distinguish two independent facts: whether this host can
432
431
  * protect a durable login-code envelope, and whether the selected deployment
@@ -620,51 +619,58 @@ function writeTerminalSupervisorReadiness(stream, color, state, sessionIds) {
620
619
  : "After the stopped-machine supervisor update, open this same AgentSession again.\n");
621
620
  }
622
621
  }
623
- function inlineProgressColumns(stream) {
624
- const tty = stream;
625
- // Node 24 on Windows can retain stale public columns after ConPTY resize.
626
- // Read this stream's native TTY observation without changing its prototype
627
- // or cached fields. This guarded private API depends on the supported Node
628
- // engine; absent/failed/malformed observations retain the ordinary fallback.
629
- if (process.platform === "win32" && typeof tty._handle?.getWindowSize === "function") {
630
- try {
631
- const size = [];
632
- if (tty._handle.getWindowSize(size) === 0 && size.length === 2 &&
633
- Number.isSafeInteger(size[0]) && size[0] >= 2 && size[0] <= 4096 &&
634
- Number.isSafeInteger(size[1]) && size[1] >= 1 && size[1] <= 4096)
635
- return size[0];
636
- }
637
- catch { /* An unavailable native observation does not break progress. */ }
638
- }
639
- return Number.isSafeInteger(tty.columns) && tty.columns >= 2 && tty.columns <= 4096 ? tty.columns : 80;
640
- }
641
- function startInlineProgress(stream, color, initialLabel = "Loading machines") {
642
- const frames = ["◐", "◓", "◑", "◒"];
643
- const bars = ["━╺━━━━", "━━╺━━━", "━━━╺━━", "━━━━╺━", "━━━━━╺", "━━━━╸━", "━━━╸━━", "━━╸━━━"];
644
- const startedAt = Date.now();
645
- let frame = 0;
622
+ /**
623
+ * Start the one inline progress row, or continue a row `cli/first-line.ts`
624
+ * already painted before this module loaded (`adopted`). An adopted row is not
625
+ * painted again: its frame 0 is on screen, so the loop starts at frame 1 and its
626
+ * clocks start when that row appeared, not when this code got to run.
627
+ */
628
+ function startInlineProgress(stream, color, initialLabel = "Loading machines", adopted) {
629
+ const startedAt = adopted?.paintedAt ?? Date.now();
630
+ let frame = adopted === undefined ? 0 : 1;
646
631
  let label = initialLabel;
632
+ // When THIS label started, not when the spinner did. The measured defect was
633
+ // label dwell — 61 259 ms on one unchanging sentence
634
+ // (`prds/cuna-cli-latency-before-20260922.md` § 3) — so the number a person
635
+ // needs is how long the current step has been running, not the command.
636
+ let labelStartedAt = startedAt;
637
+ let waiting;
647
638
  let stopped = false;
648
- let lastColumns = 0;
649
- let lastCells = 0;
639
+ let lastColumns = adopted?.columns ?? 0;
640
+ let lastCells = adopted?.cells ?? 0;
650
641
  const paint = () => {
651
642
  const columns = inlineProgressColumns(stream);
652
- const elapsed = Date.now() - startedAt;
643
+ const now = Date.now();
653
644
  // A spinner alone is too easy to mistake for a frozen cursor on slower
654
- // Windows terminals. Keep the phase honest, then add a small, actionable
655
- // acknowledgement while an authenticated read is still in flight.
656
- const slowHint = elapsed >= 12_000
657
- ? " · still working Ctrl-C cancels"
658
- : elapsed >= 4_000
659
- ? " · still working"
660
- : "";
661
- const text = `◆ CUNA ${frames[frame % frames.length]} ${label}${slowHint} ${bars[frame % bars.length]}`;
662
- const fitted = truncateTerminalLine(text, columns - 1);
663
- const styled = color && fitted === text
664
- ? `\u001b[38;5;202m\u001b[1m◆ CUNA\u001b[0m \u001b[38;5;202m${frames[frame % frames.length]}\u001b[0m \u001b[38;5;255m\u001b[1m${label}\u001b[0m\u001b[38;5;245m${slowHint}\u001b[0m \u001b[38;5;208m${bars[frame % bars.length]}\u001b[0m`
665
- : color ? `\u001b[38;5;255m${fitted}\u001b[0m` : fitted;
645
+ // Windows terminals, and still working` the whole escalation this
646
+ // replaces said nothing a second repaint had not already said. The
647
+ // sentence and its thresholds live in `progress-line.ts`, where they can
648
+ // be asserted without a clock or a TTY.
649
+ const { headline, trailer } = composeInlineProgressLine({
650
+ label,
651
+ ...(waiting === undefined ? {} : {
652
+ waiting: {
653
+ waitingFor: waiting.notice.waitingFor,
654
+ // Re-derived per repaint: the reporter speaks once per poll, up to
655
+ // 1 600 ms apart, and a number that only moves when the poll does
656
+ // reproduces the dwell it is meant to cure.
657
+ elapsedMs: waiting.notice.elapsedMs + (now - waiting.observedAt),
658
+ deadlineMs: waiting.notice.deadlineMs,
659
+ },
660
+ }),
661
+ labelElapsedMs: now - labelStartedAt,
662
+ totalElapsedMs: now - startedAt,
663
+ });
664
+ const { styled, fitted } = renderInlineProgressFrame({ headline, trailer, frame, columns, color });
666
665
  if (lastColumns > columns && lastCells >= columns) {
667
666
  // A terminal resize can reflow our previously single row before repaint.
667
+ // HAZARD, unrepaired: this assumes the terminal reflowed. One that
668
+ // truncated instead still has a single row here, so the walk clears
669
+ // whatever is above it — on this path, the durable `note()` line naming
670
+ // the AgentSession. @xterm/headless truncates, which is why
671
+ // `test/cli.test.mjs` asserts the progress row is never BELOW the durable
672
+ // rows rather than exactly at row 0. Distinguishing the two needs
673
+ // terminal feedback the CLI does not have.
668
674
  for (let row = 0; row < Math.floor(lastCells / columns); row += 1)
669
675
  stream.write("\r\u001b[2K\u001b[1A");
670
676
  }
@@ -673,14 +679,33 @@ function startInlineProgress(stream, color, initialLabel = "Loading machines") {
673
679
  lastCells = terminalCellWidth(fitted);
674
680
  frame += 1;
675
681
  };
676
- paint();
682
+ if (adopted === undefined)
683
+ paint();
677
684
  const timer = setInterval(paint, 90);
678
685
  timer.unref();
679
686
  return Object.freeze({
680
687
  update(nextLabel) {
681
- if (stopped || nextLabel === label)
688
+ if (stopped)
682
689
  return;
683
- label = nextLabel;
690
+ // A repeat of the same label must not restart the dwell clock, but it
691
+ // MUST still end a wait: the phase resuming is exactly the state change
692
+ // the wait line was standing in for, and an early return on the label
693
+ // alone would leave the stale wait on screen forever.
694
+ if (nextLabel === label && waiting === undefined)
695
+ return;
696
+ waiting = undefined;
697
+ if (nextLabel !== label) {
698
+ label = nextLabel;
699
+ labelStartedAt = Date.now();
700
+ }
701
+ paint();
702
+ },
703
+ wait(notice) {
704
+ if (stopped)
705
+ return;
706
+ waiting = notice === undefined ? undefined : Object.freeze({ notice, observedAt: Date.now() });
707
+ if (notice === undefined)
708
+ labelStartedAt = Date.now();
684
709
  paint();
685
710
  },
686
711
  note(line) {
@@ -812,11 +837,6 @@ function journeyPhaseLabel(phase, agent) {
812
837
  : `Opening ${display}`;
813
838
  }
814
839
  }
815
- function journeyPreparationLabel(agent) {
816
- return agent === "opencode"
817
- ? "Preparing OpenCode — use /connect in its terminal"
818
- : `Preparing ${agentDisplayName(agent)}`;
819
- }
820
840
  function foregroundAttachLabel(agent) {
821
841
  return agent === "opencode"
822
842
  ? "Opening OpenCode terminal — use /connect there"
@@ -841,9 +861,21 @@ export async function runCli(argv, dependencies = {}) {
841
861
  let interactiveCloseUi = false;
842
862
  let interactiveCloseColor = false;
843
863
  let terminalSessionIds = [];
864
+ // Known once configuration is read; every attach happens after that.
865
+ let terminalClients;
844
866
  const runForeground = async (input) => {
845
867
  terminalSessionIds = [...input.agentSessionIds];
846
- await (dependencies.foregroundTerminalRunner ?? runNodeForegroundSessions)(input);
868
+ await (dependencies.foregroundTerminalRunner ?? runNodeForegroundSessions)({
869
+ ...(terminalClients === undefined ? {} : { terminalClients }),
870
+ onNotice: (line) => {
871
+ const row = inlineJourneyProgress ?? inlineRootProgress;
872
+ if (row !== undefined)
873
+ row.note(line);
874
+ else
875
+ streams.stderr.write(`${line}\n`);
876
+ },
877
+ ...input,
878
+ });
847
879
  };
848
880
  try {
849
881
  const parsed = parseArgv(argv);
@@ -950,7 +982,7 @@ export async function runCli(argv, dependencies = {}) {
950
982
  interactiveCloseUi = true;
951
983
  interactiveCloseColor = color;
952
984
  if (streams.stderrIsTTY === true) {
953
- inlineRootProgress = startInlineProgress(streams.stderr, color, "Starting Cuna");
985
+ inlineRootProgress = startInlineProgress(streams.stderr, color, ROOT_FIRST_LINE_LABEL, dependencies.firstLine?.claim({ kind: "root", label: ROOT_FIRST_LINE_LABEL, color, stream: streams.stderr }));
954
986
  }
955
987
  else {
956
988
  streams.stderr.write("Cuna: starting...\n");
@@ -1001,8 +1033,9 @@ export async function runCli(argv, dependencies = {}) {
1001
1033
  // tells the truth immediately without claiming that attach has begun.
1002
1034
  const preparation = journeyPreparationLabel(journeyIntent.agent);
1003
1035
  const color = !booleanOption(parsed, "no-color") && !Object.hasOwn(effectiveEnvironment, "NO_COLOR");
1004
- if (streams.stderrIsTTY === true)
1005
- inlineJourneyProgress = startInlineProgress(streams.stderr, color, preparation);
1036
+ if (streams.stderrIsTTY === true) {
1037
+ inlineJourneyProgress = startInlineProgress(streams.stderr, color, preparation, dependencies.firstLine?.claim({ kind: "journey", label: preparation, color, stream: streams.stderr }));
1038
+ }
1006
1039
  else
1007
1040
  streams.stderr.write(`Cuna: ${preparation.charAt(0).toLowerCase()}${preparation.slice(1)}...\n`);
1008
1041
  }
@@ -1046,6 +1079,7 @@ export async function runCli(argv, dependencies = {}) {
1046
1079
  },
1047
1080
  ...(creatingProfile ? { allowMissingProfile: true } : {}),
1048
1081
  });
1082
+ terminalClients = Object.freeze({ platform, profile: config.profile });
1049
1083
  if (interactiveRoot) {
1050
1084
  inlineRootProgress?.update(config.apiKey === undefined ? "Checking your Cuna sign-in" : "Checking Cuna access");
1051
1085
  }
@@ -1488,6 +1522,80 @@ export async function runCli(argv, dependencies = {}) {
1488
1522
  throw unsupportedError("openclaw", "provider_route_unavailable");
1489
1523
  }
1490
1524
  const journeyAgent = journeyIntent.agent;
1525
+ const journeyColor = () => !booleanOption(parsed, "no-color") && !Object.hasOwn(effectiveEnvironment, "NO_COLOR");
1526
+ const resumeJourneyProgress = () => {
1527
+ if (streams.stderrIsTTY === true) {
1528
+ return startInlineProgress(streams.stderr, journeyColor(), journeyPhaseLabel("create-agent-session", journeyAgent));
1529
+ }
1530
+ return undefined;
1531
+ };
1532
+ // Both launch paths render these two the same way, so they are written
1533
+ // once: a declared wait takes the spinner row and gives it back, while a
1534
+ // settled AgentSession is a durable note that stays above it. Without the
1535
+ // row, each becomes a plain line on stderr.
1536
+ const renderJourneyWait = (notice) => {
1537
+ if (inlineJourneyProgress !== undefined)
1538
+ inlineJourneyProgress.wait(notice);
1539
+ else
1540
+ streams.stderr.write(`${journeyWaitLine(notice)}\n`);
1541
+ };
1542
+ const renderAgentSession = (event) => {
1543
+ const line = agentSessionDispositionLine(event);
1544
+ if (inlineJourneyProgress !== undefined)
1545
+ inlineJourneyProgress.note(line);
1546
+ else
1547
+ streams.stderr.write(`${line}\n`);
1548
+ };
1549
+ /**
1550
+ * True once the recorded-launch question has been answered No, on either
1551
+ * launch path. It is what separates a new launch from a resumed one; see
1552
+ * `journey/session-disposition.ts` for why nothing else on the path can.
1553
+ */
1554
+ let recordedLaunchResumed = false;
1555
+ /**
1556
+ * Ask the one recorded-launch question, and answer the screen at once.
1557
+ *
1558
+ * ONE HELPER BECAUSE THERE WAS ONE QUESTION AND TWO BEHAVIOURS. The
1559
+ * journey copy stopped the spinner to ask and never took the row back;
1560
+ * the remote-menu copy left the spinner running, so its 90 ms repaint
1561
+ * erased the question before it could be read. Measured 2026-09-22
1562
+ * (`prds/cuna-cli-latency-before-20260922.md` § 3, finding 1): the
1563
+ * terminal went BYTE-silent for up to 18 314 ms in 3 of 5 runs, always
1564
+ * immediately after this answer was accepted — the screen froze on the
1565
+ * person's own keystroke. What is said, and that it is said before this
1566
+ * returns, live in `recorded-launch-prompt.ts`, which can be asserted
1567
+ * without a TTY; this closure owns only the readline and the row.
1568
+ */
1569
+ const askCreateAnotherSession = async (signal) => {
1570
+ inlineJourneyProgress?.stop();
1571
+ inlineJourneyProgress = undefined;
1572
+ const another = await askRecordedLaunch({
1573
+ // The readline is opened and closed inside `ask`, so it has released
1574
+ // the cursor before the acknowledgement is painted onto the same row.
1575
+ ask: async (question) => {
1576
+ const prompt = createInterface({ input: dependencies.promptInput ?? process.stdin, output: streams.stderr });
1577
+ try {
1578
+ return signal === undefined
1579
+ ? await prompt.question(question)
1580
+ : await prompt.question(question, { signal });
1581
+ }
1582
+ finally {
1583
+ prompt.close();
1584
+ }
1585
+ },
1586
+ acknowledge: (line) => {
1587
+ if (streams.stderrIsTTY === true) {
1588
+ inlineJourneyProgress = startInlineProgress(streams.stderr, journeyColor(), line);
1589
+ }
1590
+ else {
1591
+ streams.stderr.write(`${line}\n`);
1592
+ }
1593
+ },
1594
+ createLabel: journeyPhaseLabel("create-agent-session", journeyAgent),
1595
+ });
1596
+ recordedLaunchResumed = recordedLaunchResumed || !another;
1597
+ return another;
1598
+ };
1491
1599
  if (credentialMode === undefined) {
1492
1600
  throw new CunaError({
1493
1601
  code: "cuna.auth.required",
@@ -1501,7 +1609,30 @@ export async function runCli(argv, dependencies = {}) {
1501
1609
  // Read before the effects branch, not inside it: the principal and the
1502
1610
  // workspace are half of the machine-create request identity, so every
1503
1611
  // journey needs them, including the one built from injected effects.
1504
- const identity = await client.getIdentity(dependencies.signal);
1612
+ //
1613
+ // Under the journey's own wait policy rather than under the per-request
1614
+ // budget alone. Being first is exactly why this read needed it: it sits
1615
+ // outside `journey/api-effects.ts` and `journey/remote-workspace.ts`, so
1616
+ // the re-issue repair reached neither of the two places it runs, and one
1617
+ // elapsed 15 000 ms budget still ended the whole command — measured
1618
+ // 2026-09-22 (`prds/cuna-cli-latency-before-20260922.md` § 8.3, runs `a5`
1619
+ // and the `--timeout-ms 800` control `slow1`, both exit 5 on `GET /v1/me`).
1620
+ // `journey/account-identity.ts` holds the deadline and its derivation.
1621
+ let accountWaitShown = false;
1622
+ const identity = await readAccountIdentityWithin({
1623
+ read: () => client.getIdentity(dependencies.signal),
1624
+ signal: dependencies.signal ?? new AbortController().signal,
1625
+ sleep: async (milliseconds, signal) => { await delay(milliseconds, undefined, { signal }); },
1626
+ now: dependencies.now ?? Date.now,
1627
+ onWait: (notice) => {
1628
+ accountWaitShown = true;
1629
+ renderJourneyWait(notice);
1630
+ },
1631
+ });
1632
+ // The account answered, so the row stops counting a wait that is over.
1633
+ // Nothing else ends it: the next phase label can be several steps away.
1634
+ if (accountWaitShown)
1635
+ inlineJourneyProgress?.wait(undefined);
1505
1636
  const workspaceId = identity.workspaceId;
1506
1637
  if (workspaceId === undefined) {
1507
1638
  throw new CunaError({
@@ -1518,22 +1649,25 @@ export async function runCli(argv, dependencies = {}) {
1518
1649
  const preset = await (dependencies.providerScreenRunner ?? runProviderScreen)(client, { kind: "preset", agent: journeyAgent }, undefined, dependencies.signal);
1519
1650
  if (preset === undefined)
1520
1651
  return EXIT_CODES.success;
1652
+ inlineJourneyProgress = resumeJourneyProgress();
1521
1653
  const agentSessionId = await launchRemoteWorkspaceSession({
1522
1654
  preset,
1523
1655
  providerLaunchState: { stateDirectory: platform.paths.stateDirectory, ownerId: identity.id },
1524
- confirmNew: async () => { const prompt = createInterface({ input: process.stdin, output: streams.stderr }); try {
1525
- return /^y(?:es)?$/iu.test((await prompt.question("A previous launch is recorded. Create another session? [y/N; No resumes the recorded launch] ", { signal: dependencies.signal })).trim());
1526
- }
1527
- finally {
1528
- prompt.close();
1529
- } },
1656
+ confirmNew: () => askCreateAnotherSession(dependencies.signal),
1530
1657
  client, machineId: dependencies.managedWorkspaceMachineId, workspaceId, agent: journeyAgent,
1531
1658
  onProgress: (label) => inlineJourneyProgress?.update(label),
1659
+ onWait: renderJourneyWait,
1660
+ onAgentSession: renderAgentSession,
1532
1661
  ...(dependencies.signal === undefined ? {} : { signal: dependencies.signal }),
1533
1662
  ...(dependencies.now === undefined ? {} : { now: dependencies.now }),
1534
1663
  });
1664
+ inlineJourneyProgress?.stop();
1665
+ inlineJourneyProgress = undefined;
1535
1666
  return await runCli([
1536
- journeyAgent,
1667
+ // The COMMAND, not the agent identifier. `claude-code` is not a
1668
+ // command, and passing it here ended this path at `Unknown command
1669
+ // claude-code` right after the AgentSession was created.
1670
+ agentJourneyCommand(journeyAgent),
1537
1671
  "--agent-session", agentSessionId,
1538
1672
  ...(booleanOption(parsed, "no-color") ? ["--no-color"] : []),
1539
1673
  ], { ...dependencies, ...(humanAuth === undefined ? {} : { humanAuth }) });
@@ -1584,15 +1718,17 @@ export async function runCli(argv, dependencies = {}) {
1584
1718
  effects = createApiAgentJourneyEffects({
1585
1719
  client,
1586
1720
  requestedAgent: journeyAgent,
1587
- confirmNewProviderLaunch: async (signal) => { inlineJourneyProgress?.stop(); inlineJourneyProgress = undefined; const prompt = createInterface({ input: process.stdin, output: streams.stderr }); try {
1588
- return /^y(?:es)?$/iu.test((await prompt.question("A previous launch is recorded. Create another session? [y/N; No resumes the recorded launch] ", { signal })).trim());
1589
- }
1590
- finally {
1591
- prompt.close();
1592
- } },
1721
+ confirmNewProviderLaunch: recordedLaunchConfirmation(journeyIntent.newSession, askCreateAnotherSession),
1593
1722
  providerLaunchState: { stateDirectory: platform.paths.stateDirectory, ownerId: identity.id, workspaceId },
1594
- selectProviderPreset: async (signal) => { inlineJourneyProgress?.stop(); inlineJourneyProgress = undefined; const preset = await (dependencies.providerScreenRunner ?? runProviderScreen)(client, { kind: "preset", agent: journeyAgent }, undefined, signal); if (!preset)
1595
- throw new CunaError({ code: "cuna.provider.selection_cancelled", message: "Provider selection cancelled. The synchronized Workspace is preserved.", exitCode: EXIT_CODES.usage }); return preset; },
1723
+ selectProviderPreset: async (signal) => {
1724
+ inlineJourneyProgress?.stop();
1725
+ inlineJourneyProgress = undefined;
1726
+ const preset = await (dependencies.providerScreenRunner ?? runProviderScreen)(client, { kind: "preset", agent: journeyAgent }, undefined, signal);
1727
+ if (!preset)
1728
+ throw new CunaError({ code: "cuna.provider.selection_cancelled", message: "Provider selection cancelled. The synchronized Workspace is preserved.", exitCode: EXIT_CODES.usage });
1729
+ inlineJourneyProgress = resumeJourneyProgress();
1730
+ return preset;
1731
+ },
1596
1732
  inspectWorkspace: workspace.inspectWorkspace,
1597
1733
  synchronizeWorkspace: workspace.synchronizeWorkspace,
1598
1734
  // The spinner and the prompt write to the same row of the same
@@ -1638,6 +1774,7 @@ export async function runCli(argv, dependencies = {}) {
1638
1774
  signal,
1639
1775
  });
1640
1776
  },
1777
+ onWait: renderJourneyWait,
1641
1778
  ...(dependencies.now === undefined ? {} : { now: dependencies.now }),
1642
1779
  });
1643
1780
  }
@@ -1648,6 +1785,23 @@ export async function runCli(argv, dependencies = {}) {
1648
1785
  baseEffects.onPhase?.(phase);
1649
1786
  inlineJourneyProgress?.update(journeyPhaseLabel(phase, journeyAgent));
1650
1787
  },
1788
+ onAgentSession(event) {
1789
+ baseEffects.onAgentSession?.(event);
1790
+ // The orchestrator reads its disposition from the selection plan,
1791
+ // which cannot see the answer to the recorded-launch question asked
1792
+ // inside the create. On No, the create carries the recorded operation
1793
+ // id and returns the row that launch already produced, so the plan
1794
+ // still says `create-required` while the screen has just said
1795
+ // `Resuming the recorded launch`. This closure owns that question, so
1796
+ // it is the only place that can reconcile the two.
1797
+ renderAgentSession({
1798
+ ...event,
1799
+ disposition: settledAgentSessionDisposition({
1800
+ planned: event.disposition,
1801
+ resumedRecordedLaunch: recordedLaunchResumed,
1802
+ }),
1803
+ });
1804
+ },
1651
1805
  async attach(input) {
1652
1806
  inlineJourneyProgress?.update(`Attaching to ${agentDisplayName(journeyAgent)}`);
1653
1807
  try {
@@ -1913,6 +2067,10 @@ export async function runCli(argv, dependencies = {}) {
1913
2067
  return EXIT_CODES.success;
1914
2068
  }
1915
2069
  catch (unknownError) {
2070
+ // A first line painted before this module loaded, and refused before any
2071
+ // progress row took it over, is cleared here, before the error is written
2072
+ // after it on the same row.
2073
+ dependencies.firstLine?.release();
1916
2074
  inlineMachinesProgress?.stop();
1917
2075
  inlineJourneyProgress?.stop();
1918
2076
  inlineRootProgress?.stop();