@phnx-labs/agents-cli 1.22.57 → 1.22.58

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 (101) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/bootstrap.js +8 -1
  3. package/dist/commands/accounts.js +7 -3
  4. package/dist/commands/apply.js +10 -2
  5. package/dist/commands/fork.d.ts +23 -10
  6. package/dist/commands/fork.js +115 -58
  7. package/dist/commands/monitors.js +11 -0
  8. package/dist/commands/prune.js +5 -3
  9. package/dist/commands/routines.d.ts +8 -0
  10. package/dist/commands/routines.js +57 -3
  11. package/dist/commands/sessions-picker.d.ts +11 -0
  12. package/dist/commands/sessions-picker.js +16 -0
  13. package/dist/commands/sessions.js +1 -0
  14. package/dist/commands/share.d.ts +14 -0
  15. package/dist/commands/share.js +43 -2
  16. package/dist/commands/status.js +1 -1
  17. package/dist/commands/sync.js +83 -7
  18. package/dist/commands/traces.js +7 -0
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js +6 -1
  21. package/dist/lib/account-registry.d.ts +5 -1
  22. package/dist/lib/account-registry.js +47 -14
  23. package/dist/lib/accounting/capacity.d.ts +18 -7
  24. package/dist/lib/accounting/capacity.js +19 -8
  25. package/dist/lib/accounting/usage-sync.d.ts +29 -1
  26. package/dist/lib/accounting/usage-sync.js +76 -2
  27. package/dist/lib/accounting/usage.js +7 -1
  28. package/dist/lib/auth-mint.d.ts +11 -1
  29. package/dist/lib/auth-mint.js +21 -6
  30. package/dist/lib/browser/ipc.d.ts +8 -0
  31. package/dist/lib/browser/ipc.js +87 -0
  32. package/dist/lib/browser/service.d.ts +19 -0
  33. package/dist/lib/browser/service.js +96 -11
  34. package/dist/lib/browser/sessions-list.js +10 -1
  35. package/dist/lib/daemon/runner.d.ts +3 -0
  36. package/dist/lib/daemon/runner.js +86 -45
  37. package/dist/lib/daemon/usage-sync-service.d.ts +3 -3
  38. package/dist/lib/daemon/usage-sync-service.js +14 -8
  39. package/dist/lib/daemon-services.js +1 -1
  40. package/dist/lib/devices/connect.d.ts +17 -8
  41. package/dist/lib/devices/connect.js +31 -14
  42. package/dist/lib/doctor-diff.js +77 -7
  43. package/dist/lib/fleet/manifest.d.ts +17 -0
  44. package/dist/lib/fleet/manifest.js +26 -0
  45. package/dist/lib/hooks/install.d.ts +27 -11
  46. package/dist/lib/hooks/install.js +42 -17
  47. package/dist/lib/hosts/reconnect.d.ts +52 -203
  48. package/dist/lib/hosts/reconnect.js +64 -284
  49. package/dist/lib/installations/migrate.d.ts +6 -120
  50. package/dist/lib/installations/migrate.js +27 -259
  51. package/dist/lib/installations/shims.d.ts +13 -95
  52. package/dist/lib/installations/shims.js +22 -139
  53. package/dist/lib/installations/store.js +1 -1
  54. package/dist/lib/installations/versions.d.ts +26 -133
  55. package/dist/lib/installations/versions.js +41 -204
  56. package/dist/lib/plugins/skills.d.ts +8 -1
  57. package/dist/lib/plugins/skills.js +18 -2
  58. package/dist/lib/refresh.d.ts +9 -0
  59. package/dist/lib/refresh.js +3 -1
  60. package/dist/lib/routine-readiness.d.ts +15 -1
  61. package/dist/lib/routine-readiness.js +41 -0
  62. package/dist/lib/sandbox.d.ts +4 -1
  63. package/dist/lib/sandbox.js +30 -1
  64. package/dist/lib/secrets/agent.d.ts +80 -225
  65. package/dist/lib/secrets/agent.js +139 -401
  66. package/dist/lib/secrets/bundles.d.ts +73 -222
  67. package/dist/lib/secrets/bundles.js +168 -467
  68. package/dist/lib/secrets/reaper.d.ts +28 -70
  69. package/dist/lib/secrets/reaper.js +30 -85
  70. package/dist/lib/secrets/remote.d.ts +42 -129
  71. package/dist/lib/secrets/remote.js +55 -173
  72. package/dist/lib/self-heal/checks/install-staging.d.ts +4 -0
  73. package/dist/lib/self-heal/checks/install-staging.js +96 -0
  74. package/dist/lib/self-heal/registry.js +2 -0
  75. package/dist/lib/self-heal/types.d.ts +1 -1
  76. package/dist/lib/self-update.d.ts +23 -0
  77. package/dist/lib/self-update.js +50 -0
  78. package/dist/lib/session/active.d.ts +13 -1
  79. package/dist/lib/session/active.js +2 -0
  80. package/dist/lib/session/db.d.ts +20 -1
  81. package/dist/lib/session/db.js +139 -9
  82. package/dist/lib/session/fork.d.ts +45 -26
  83. package/dist/lib/session/fork.js +32 -95
  84. package/dist/lib/session/tool-calls.d.ts +43 -1
  85. package/dist/lib/session/tool-calls.js +74 -44
  86. package/dist/lib/session/tool-store.d.ts +33 -2
  87. package/dist/lib/session/tool-store.js +56 -3
  88. package/dist/lib/staleness/writers/sources.d.ts +5 -0
  89. package/dist/lib/staleness/writers/sources.js +2 -1
  90. package/dist/lib/sync-status.d.ts +22 -0
  91. package/dist/lib/sync-status.js +27 -0
  92. package/dist/lib/sync-umbrella.d.ts +9 -0
  93. package/dist/lib/sync-umbrella.js +21 -2
  94. package/dist/lib/traces/insights.d.ts +47 -14
  95. package/dist/lib/traces/insights.js +92 -21
  96. package/dist/lib/traces/phenotype.d.ts +23 -3
  97. package/dist/lib/traces/phenotype.js +72 -24
  98. package/dist/lib/traces/sync.d.ts +15 -0
  99. package/dist/lib/traces/sync.js +104 -19
  100. package/dist/lib/traces/worker-template.js +154 -1
  101. package/package.json +1 -1
@@ -1,58 +1,37 @@
1
1
  import { type Host } from './types.js';
2
- /** ssh's connection-layer failure code — the signal that the link dropped rather
3
- * than the remote command exiting on its own. Re-exported from ssh-exec.ts, which
4
- * owns the ssh invocation, so the two cannot drift apart. */
2
+ /** ssh's connection-layer failure code — re-exported from ssh-exec.ts. */
5
3
  export declare const SSH_CONN_FAILURE = 255;
6
- /** What a would-be-255 remote-origin exit code is remapped to by
7
- * {@link wrapRemoteExitCode} — see the file header. Never produced by the ssh
8
- * transport itself, so it can never be confused with {@link SSH_CONN_FAILURE}. */
4
+ /**
5
+ * ssh returns 255 for BOTH "couldn't connect" and "connected then dropped", so a
6
+ * remote-origin 255 (the focus command's own exit) is remapped to 254 before the
7
+ * reconnect loop sees it — inside the loop, 255 therefore always means a network
8
+ * drop, never a code the remote command chose.
9
+ */
9
10
  export declare const REMOTE_EXIT_255_REMAPPED = 254;
10
11
  /**
11
- * How long the loop keeps trying across an UNPRODUCTIVE streak before giving up.
12
- *
13
- * A wall-clock window, not an attempt count, because what it has to outlast is
14
- * measured in minutes: a laptop lid close, a Wi-Fi handoff, a VPN or Tailscale
15
- * re-auth, a router reboot. The previous `MAX_ATTEMPTS = 6` over a 2/4/8/16/30/30
16
- * backoff gave up after about **90 seconds** — shorter than any of them
17
- * (RUSH-3125). Worse, timers are suspended across sleep, so on wake the whole
18
- * backoff fired back-to-back before the network was up and the budget was gone in
19
- * seconds.
20
- *
21
- * It bounds the STREAK, not the session: a reattach that reconnects and HOLDS
22
- * resets it to zero ({@link refillsBudget}), so a session that blinks all day
23
- * still reconnects every time. That is the property the file header insists on,
24
- * and a flat total would break it — this changes only how a *streak* is bounded.
12
+ * Wall-clock window bounding unproductive reconnect streaks, not the total
13
+ * session. A reattach that reaches the host and holds resets the budget.
25
14
  */
26
15
  export declare const RECONNECT_WINDOW_MS: number;
27
- /** How long a reattach must hold the remote pane before it counts as a genuine
28
- * reconnection that refills the budget. Measured on the interactive attach ALONE
29
- * — the preflight probe has already returned by then, so this is not the connect
30
- * timing the file header rules out. 10s is comfortably longer than any attach that
31
- * dies during TTY negotiation and far shorter than a session the user is working
32
- * in; a link that drops the user out inside 10s on every attempt is one this loop
33
- * should stop retrying, not one it should keep re-entering. */
16
+ /**
17
+ * A genuine reconnection must hold the remote pane this long before refilling
18
+ * the retry budget. It is the minimum time to clear TTY negotiation and confirm
19
+ * a working session, distinguishing that from an attach that reconnects and
20
+ * immediately re-drops; otherwise a flapping link would retry forever.
21
+ */
34
22
  export declare const MIN_HOLD_MS = 10000;
35
23
  export interface ReconnectState {
36
- /** Consecutive unproductive reattaches since the last genuine reconnection — one
37
- * that reached the host and held for {@link MIN_HOLD_MS}. Drives the backoff
38
- * curve and the human-facing attempt number; no longer the give-up condition. */
24
+ /** Consecutive unproductive reattaches since the last genuine reconnection. */
39
25
  attempt: number;
40
- /** Wall-clock ms burned on the current unproductive streak — the waits plus the
41
- * time each failed attach itself took. Compared against
42
- * {@link RECONNECT_WINDOW_MS}; reset to 0 by a reattach that held. */
26
+ /** Ms burned on the current unproductive streak; compared to the window. */
43
27
  unproductiveMs: number;
44
28
  }
45
29
  export interface ReconnectOutcome {
46
- /** Exit code of the run (initial) or the last re-attach. */
30
+ /** Exit code of the run or last re-attach. */
47
31
  code: number;
48
- /** Whether the ssh handshake for this attempt actually completed. The initial run
49
- * and any reattach whose preflight probe succeeded are `connected`; a reattach
50
- * that couldn't reach the host is not. Half of the budget refill (see file head). */
32
+ /** Whether the ssh handshake completed (preflight probe succeeded). */
51
33
  connected: boolean;
52
- /** Wall-clock ms the interactive attach ran for, timed from after the preflight
53
- * probe returned. `0` when the attempt never reached the host. The other half of
54
- * the refill: it must be at least {@link MIN_HOLD_MS} to count as a genuine
55
- * reconnection rather than a link that drops the user straight back out. */
34
+ /** How long the interactive attach held after the probe returned. */
56
35
  heldMs: number;
57
36
  }
58
37
  export type ReconnectDecision = {
@@ -67,93 +46,33 @@ export type ReconnectDecision = {
67
46
  export declare function initialReconnectState(): ReconnectState;
68
47
  /** Exponential backoff capped at {@link MAX_BACKOFF_MS}: 2s, 4s, 8s, 16s, 30s… */
69
48
  export declare function backoffMs(attempt: number): number;
70
- /**
71
- * Did this attempt genuinely put the user back into the agent? Only such an attempt
72
- * refills the retry budget — it must have reached the host AND held the pane for at
73
- * least {@link MIN_HOLD_MS}. An attach that reached the host and died right back is
74
- * a flapping link, not a reconnection, and counts against the budget like an
75
- * unreachable host (agents-cli#1884; see the file header).
76
- */
49
+ /** True when the attempt both connected and held long enough to refill the budget. */
77
50
  export declare function refillsBudget(outcome: ReconnectOutcome): boolean;
78
51
  /**
79
- * Decide what to do after a run/re-attach returned `outcome`. Pure — the only
80
- * input is the prior state and the outcome, the only output is the next action.
81
- *
82
- * - a non-255 code means the remote command spoke for itself (clean detach = 0,
83
- * agent exit / no live session = non-zero) → stop and surface that code.
84
- * - a 255 means the link dropped → retry, unless the budget is spent.
85
- * - a 255 from an attempt that reconnected AND held ({@link refillsBudget})
86
- * refills the budget first; every other 255 counts against it, so a host that
87
- * stays unreachable — and a link that keeps dropping the attach immediately —
88
- * both give up once the retry window closes.
52
+ * Decide the next action from a run/re-attach outcome. Non-255 exits stop and
53
+ * surface that code; 255 retries until the unproductive window is spent.
89
54
  */
90
55
  export declare function reconnectStep(state: ReconnectState, outcome: ReconnectOutcome): ReconnectDecision;
91
56
  /** "14m51s", "51s" — a duration a waiting human can read at a glance. */
92
57
  export declare function formatDuration(ms: number): string;
93
- /**
94
- * Notice shown before each reconnect wait.
95
- *
96
- * Says how long is left in the window rather than "attempt 2/6": with a
97
- * wall-clock budget the attempt number no longer tells the user when this stops,
98
- * and "how much longer will it keep trying" is the actual question during an
99
- * outage. Ctrl-C is advertised because a user who wants their shell back should
100
- * not have to guess whether interrupting is safe — it is (the agent keeps
101
- * running on the peer, which is the whole point).
102
- */
58
+ /** Notice shown before each reconnect wait. */
103
59
  export declare function reconnectNotice(target: ReconnectTarget, host: string, attempt: number, waitMs: number, remainingMs: number): string;
104
- /** Notice shown once the retry budget is spent on a host that stayed UNREACHABLE.
105
- * Hands back the one verb that re-enters the terminal — attach the live pane if it
106
- * survived, else resume. */
60
+ /** Notice shown once the retry budget is spent on an unreachable host. */
107
61
  export declare function exhaustedNotice(target: ReconnectTarget, host: string): string;
108
- /** Notice shown when the budget is spent the OTHER way: the last reattach reached
109
- * the host and the connection dropped again within {@link MIN_HOLD_MS}. Saying
110
- * "couldn't reconnect" there would be false — it did reconnect and could not stay
111
- * — and the user needs to know the link, not the host, is the problem. It claims
112
- * no count of successful reconnections: the budget can also be spent by a run of
113
- * unreachable attempts followed by one that reconnected and dropped straight out. */
62
+ /** Notice shown when the host reconnects but drops again within {@link MIN_HOLD_MS}. */
114
63
  export declare function unstableNotice(target: ReconnectTarget, host: string): string;
115
- /** Notice shown when a reattach stops on a remapped remote-side exit
116
- * ({@link REMOTE_EXIT_255_REMAPPED} — a would-be-255 the remote command decided
117
- * on for its own reasons, not the ssh transport dropping; see
118
- * {@link wrapRemoteExitCode}). Distinct from {@link exhaustedNotice}, which is
119
- * only for a genuinely spent retry budget. */
64
+ /** Notice shown when a reattach ends with a remapped remote-side exit. */
120
65
  export declare function remoteExitNotice(target: ReconnectTarget, host: string): string;
121
- /** Shown when the user Ctrl-Cs out of the wait. The agent is untouched — it is
122
- * detached on the peer — so this says how to get back rather than implying the
123
- * work was lost. */
66
+ /** Notice shown when the user stops the wait with Ctrl-C. */
124
67
  export declare function interruptedNotice(target: ReconnectTarget, host: string): string;
125
68
  /**
126
- * Wrap `cmd` in `bash -lc` (the login-shell pattern `buildRemoteAgentsInvocation`
127
- * in remote-cmd.ts uses for its own POSIX callers — see its doc comment for why
128
- * a login shell at all; the sibling interactive dispatch in dispatch.ts sends a
129
- * bare `agents …` with no shell wrapper, so this is a NEW login-shell hop on the
130
- * reattach path specifically, not something already universal here) with a
131
- * trailing exit-code remap: whatever `cmd` itself exits with, a 255 becomes
132
- * {@link REMOTE_EXIT_255_REMAPPED} before the wrapper exits — see the file
133
- * header for why. Every other code (0, 1, …) passes through unchanged. This
134
- * carries no PATH bootstrap of its own — `ensureHostReady`/`readyProbe` already
135
- * gates every `--device` dispatch on `bash -lc 'agents --version'` succeeding
136
- * before a run is attempted at all, so the peer's login shell resolving `agents`
137
- * is an established precondition here too. Pure string-building, so it is
138
- * unit-tested without SSH (and, since the constructed script is ordinary POSIX,
139
- * also exercised by actually running it through a real shell in the test — no
140
- * mock needed).
69
+ * Wrap `cmd` in `bash -lc` with an exit-code remap: 255 becomes
70
+ * {@link REMOTE_EXIT_255_REMAPPED} so a remote-origin 255 cannot pass as ssh drop.
141
71
  */
142
72
  export declare function wrapRemoteExitCode(cmd: string): string;
143
73
  /**
144
74
  * How a dropped run is named when we go back for it.
145
- *
146
- * `session` is the direct case: the id was known before the link died — Claude
147
- * is handed one up front, and a resumed run already has one.
148
- *
149
- * `launch` is what makes reconnect work for every OTHER harness (RUSH-3125).
150
- * Their real session id is coined on the peer, and the launcher used to read it
151
- * back over SSH *after* the stream returned — i.e. over the link that had just
152
- * dropped. That read fails exactly when it matters, leaving `reconnectId`
153
- * undefined and the process exiting straight to a shell, which is why a Grok tab
154
- * got no reconnect at all while a Claude tab beside it got a countdown. The
155
- * launch id is minted locally before the connection exists, so it survives the
156
- * drop; the peer maps it to the real session with a purely local lookup.
75
+ * `launch` ids are minted locally before the connection exists, so they survive a drop.
157
76
  */
158
77
  export type ReconnectTarget = {
159
78
  kind: 'session';
@@ -165,43 +84,25 @@ export type ReconnectTarget = {
165
84
  /** The short form shown to a human. Both kinds are uuid-shaped, so 8 chars reads the same. */
166
85
  export declare function targetLabel(target: ReconnectTarget): string;
167
86
  /**
168
- * The command to hand a user whose reconnect gave up.
169
- *
170
- * A `session` target has a real id, so `agents sessions resume <id>` does the
171
- * same attach-else-recover the loop was attempting. The full id is labeled on
172
- * its own `Session` line, not only inside the command (RUSH-3227). (It used to
173
- * say `agents reconnect`, which is deprecated and hidden — `commands/reconnect.ts`
174
- * — so the advice printed at the worst possible moment was itself stale; RUSH-3125.)
175
- *
176
- * A `launch` target has no id a user-facing verb accepts: the mapping lives in
177
- * the peer's hook records, which is exactly why reconnect uses it. So point at
178
- * the peer's own resolver rather than inventing a launch-id selector on every
179
- * local command for a string no human ever types.
87
+ * The command a user can run to re-enter a session after reconnect gives up.
88
+ * The full id is printed on its own line so it remains copyable after a drop.
180
89
  */
181
90
  export declare function recoveryHint(target: ReconnectTarget, host: string): string;
182
91
  /**
183
- * Notice shown when an interactive remote connection has ended and the user is
184
- * back at a local shell — clean detach, agent exit, or a drop that is NOT
185
- * about to auto-reconnect. The OpenSSH close line (`Shared connection to …
186
- * closed.`) names the host and nothing else; this is the handle they need to
187
- * get back in (RUSH-3227).
92
+ * Notice shown when an interactive remote connection ends and no auto-reconnect
93
+ * follows. Prints the session id/handle so the user can get back in.
188
94
  */
189
95
  export declare function connectionEndedNotice(target: ReconnectTarget, host: string, opts?: {
190
96
  dropped?: boolean;
191
97
  }): string;
192
98
  /**
193
- * Banner printed when an interactive `--device` run whose session id is already
194
- * known (Claude's forced id, or a resume) is about to take the TTY. The TUI
195
- * will cover this; it survives in scrollback so the id exists *while* the
196
- * connection exists, not only after it dies (RUSH-3227 plan B). Launch-id
197
- * targets are not a resume handle — do not print them here.
99
+ * Banner printed as an interactive `--device` run takes the TTY, so the id is
100
+ * visible in scrollback while the connection exists.
198
101
  */
199
102
  export declare function connectionStartedNotice(target: ReconnectTarget, host: string): string | undefined;
200
103
  /**
201
- * The id to print as an interactive `--device` stream starts. Resume is a
202
- * real session id (`resolveHostSessionId` returns undefined on `--resume`);
203
- * Claude's forced id is the other. `run auto` is excluded: its forwarded
204
- * `--session-id` is only real if the remote pick is Claude.
104
+ * The id to print as an interactive `--device` stream starts. `run auto` is
105
+ * excluded because its forwarded id is only real when the remote picks Claude.
205
106
  */
206
107
  export declare function startConnectionTarget(opts: {
207
108
  agent: string;
@@ -209,15 +110,8 @@ export declare function startConnectionTarget(opts: {
209
110
  resumeId?: string;
210
111
  }): ReconnectTarget | undefined;
211
112
  /**
212
- * Decide what happens after an interactive `--device` stream returns.
213
- *
214
- * Auto-reconnect fires whenever the link dropped (255) and the run did not opt
215
- * out with `--raw` (`willReconnect`) — wrap or no wrap. A wrapped run's pane
216
- * survived, so the reattach rejoins it; a bare run's agent died with the link,
217
- * so the same verb resumes the session in place from disk (PHNX-3316). `--raw`
218
- * never reconnects — but it still prints the session id: the user is at a
219
- * shell, and EXEC-55 does not exempt raw (RUSH-3227). Bundling the notice
220
- * behind `!isRaw` was the miss.
113
+ * Decide what happens after an interactive `--device` stream returns. Auto-
114
+ * reconnect fires on 255 unless `--raw` opted out; otherwise print recovery info.
221
115
  */
222
116
  export declare function afterInteractiveRemoteExit(opts: {
223
117
  target?: ReconnectTarget;
@@ -229,84 +123,39 @@ export declare function afterInteractiveRemoteExit(opts: {
229
123
  reconnect: boolean;
230
124
  notice: string | undefined;
231
125
  };
232
- /** What the launcher knows about a run once its interactive stream has returned. */
126
+ /** Inputs the launcher has once its interactive stream has returned. */
233
127
  export interface ReconnectTargetInputs {
234
- /** The harness (or {@link RUN_AUTO_KEYWORD}) that was dispatched. */
235
128
  agent: string;
236
- /** An id forced by the launcher up front — Claude's `--session-id`. */
237
129
  sessionId?: string;
238
- /** An id read back off the peer AFTER the stream returned. Absent on a drop. */
239
130
  resolvedId?: string;
240
- /** The id of a run that was resuming an existing session. */
241
131
  resumeId?: string;
242
- /** The launcher-minted AGENT_LAUNCH_ID, known before the connection existed. */
243
132
  launchId?: string;
244
133
  }
245
134
  /**
246
- * Choose how to name the dropped run when going back for it.
247
- *
248
- * Order matters, and the last clause is the fix (RUSH-3125):
249
- *
250
- * 1. `run auto` prefers `resolvedId` — the harness the remote ACTUALLY picked,
251
- * which the launcher's own `--session-id` (adopted only by Claude) may not
252
- * name. Every other agent prefers the id the launcher forced.
253
- * 2. `resumeId` covers a run that was continuing a known session.
254
- * 3. **`launchId` last, and it is what makes this work at all off-Claude.**
255
- * Every id above either came from the launcher or was read back over SSH —
256
- * and that read happens after the stream returned, i.e. over the link that
257
- * just died, so on a real drop it yields nothing. Falling through to the
258
- * launch id means the reconnect no longer depends on reaching the host to
259
- * learn what to reconnect to; the peer resolves it locally instead.
260
- *
261
- * Returns undefined only when the launcher has no handle at all (a hookless
262
- * harness with no forced id), which is the one case reconnect genuinely cannot
263
- * serve. Pure, so the precedence is unit-tested without SSH.
135
+ * Choose how to name the dropped run. `launchId` is last because it is minted
136
+ * locally before the connection exists, so it survives the drop.
264
137
  */
265
138
  export declare function pickReconnectTarget(inputs: ReconnectTargetInputs): ReconnectTarget | undefined;
266
139
  /**
267
- * The remote command a reattach runs — the peer's own recovery verb
268
- * (`agents sessions focus … --local`), wrapped by {@link wrapRemoteExitCode}
269
- * so a stray remote-origin 255 (from this command, whatever produces it — see the
270
- * file header) can never masquerade as a network drop. No `--attach-only`: focus
271
- * joins the live pane when it survived, else RESUMES the session in place, so a
272
- * reattach landing after the pane died recovers the agent instead of dead-ending
273
- * (RUSH-2085). Split out from {@link reattachRemoteSession} so it is unit-tested
274
- * without SSH — mirrors `remoteAgentsJsonCommand` in lib/remote-agents-json.ts.
275
- *
276
- * A `launch` target passes `--launch-id`, which focus resolves against the hook
277
- * records on the peer itself — no network read from this side, which is the
278
- * whole point (see {@link ReconnectTarget}).
140
+ * The peer's recovery verb wrapped so a remote-origin 255 cannot masquerade as
141
+ * a network drop. No `--attach-only`: focus resumes in place when no pane survived.
279
142
  */
280
143
  export declare function reattachRemoteCommand(target: ReconnectTarget): string;
281
144
  /**
282
- * Re-attach the live remote tmux pane for `sessionId` by driving the peer's own
283
- * `agents sessions focus`. A fast, un-multiplexed preflight probe (`ssh … true`)
284
- * first establishes whether the host is actually reachable this attempt — that
285
- * `connected` bit, not the call duration, is what the retry policy keys on. Only on
286
- * a reachable host do we run the interactive attach-or-resume (which carries no
287
- * credentials — the agent already runs on the peer — so it rides the normal
288
- * transport). Returns the ssh exit code (255 = dropped again / unreachable; 0 =
289
- * clean detach; other = session ended), whether this attempt connected, and how
290
- * long the attach held — the two inputs {@link refillsBudget} decides on.
145
+ * Re-attach the live remote pane by driving the peer's `agents sessions focus`.
146
+ * A fast preflight probe decides reachability; only then do we run the
147
+ * interactive attach/resume. Returns the exit code, connected bit, and hold time.
291
148
  */
292
149
  export declare function reattachRemoteSession(host: Host, target: ReconnectTarget): ReconnectOutcome;
293
150
  export interface ReconnectLoopOpts {
294
151
  host: Host;
295
152
  target: ReconnectTarget;
296
- /** The exit code from the initial interactive run (which, having run the agent,
297
- * is treated as a connected attempt). */
153
+ /** Exit code from the initial interactive run (treated as connected). */
298
154
  initialExit: number;
299
- /** Injected for tests: the real re-attach and wait are swapped for deterministic
300
- * fakes so the loop's control flow is exercised without SSH. Production uses the
301
- * real {@link reattachRemoteSession} + {@link waitOrInterrupt}. */
155
+ /** Test seams for the re-attach and wait. */
302
156
  reattach?: (host: Host, target: ReconnectTarget) => ReconnectOutcome;
303
- /** Resolves 'interrupted' when the user Ctrl-Cs out of the backoff. */
304
157
  wait?: (ms: number) => Promise<'elapsed' | 'interrupted'>;
305
158
  write?: (s: string) => void;
306
159
  }
307
- /**
308
- * Drive the reconnect loop from the initial run's outcome to a terminal code.
309
- * Only the real SSH re-attach and the wait are side effects; the decision is
310
- * {@link reconnectStep}. Returns the exit code the process should ultimately use.
311
- */
160
+ /** Drive the reconnect loop from the initial run's outcome to a terminal code. */
312
161
  export declare function reconnectInteractiveSession(opts: ReconnectLoopOpts): Promise<number>;