@sentropic/h2a-runtime 0.92.0 → 0.93.0

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 (63) hide show
  1. package/dist/conv-guard.d.ts +6 -1
  2. package/dist/conv-guard.d.ts.map +1 -1
  3. package/dist/conv-guard.js +33 -8
  4. package/dist/conv-guard.js.map +1 -1
  5. package/dist/enroll.d.ts.map +1 -1
  6. package/dist/enroll.js +12 -2
  7. package/dist/enroll.js.map +1 -1
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +743 -97
  11. package/dist/index.js.map +1 -1
  12. package/dist/launch-layout.d.ts +74 -0
  13. package/dist/launch-layout.d.ts.map +1 -0
  14. package/dist/launch-layout.js +324 -0
  15. package/dist/launch-layout.js.map +1 -0
  16. package/dist/native-host.d.ts +146 -0
  17. package/dist/native-host.d.ts.map +1 -0
  18. package/dist/native-host.js +456 -0
  19. package/dist/native-host.js.map +1 -0
  20. package/dist/native-terminal/host.d.ts +317 -1
  21. package/dist/native-terminal/host.d.ts.map +1 -1
  22. package/dist/native-terminal/host.js +696 -3
  23. package/dist/native-terminal/host.js.map +1 -1
  24. package/dist/native-terminal/op.d.ts +5 -0
  25. package/dist/native-terminal/op.d.ts.map +1 -0
  26. package/dist/native-terminal/op.js +408 -0
  27. package/dist/native-terminal/op.js.map +1 -0
  28. package/dist/native-terminal/process.d.ts +2 -0
  29. package/dist/native-terminal/process.d.ts.map +1 -1
  30. package/dist/native-terminal/process.js +16 -2
  31. package/dist/native-terminal/process.js.map +1 -1
  32. package/dist/native-terminal/server.d.ts.map +1 -1
  33. package/dist/native-terminal/server.js +6 -2
  34. package/dist/native-terminal/server.js.map +1 -1
  35. package/dist/native-terminal/socket-path.d.ts +8 -0
  36. package/dist/native-terminal/socket-path.d.ts.map +1 -1
  37. package/dist/native-terminal/socket-path.js +31 -1
  38. package/dist/native-terminal/socket-path.js.map +1 -1
  39. package/dist/native-terminal/supervisor.d.ts +13 -0
  40. package/dist/native-terminal/supervisor.d.ts.map +1 -1
  41. package/dist/native-terminal/supervisor.js +45 -0
  42. package/dist/native-terminal/supervisor.js.map +1 -1
  43. package/dist/pty.d.ts +23 -0
  44. package/dist/pty.d.ts.map +1 -1
  45. package/dist/pty.js +35 -0
  46. package/dist/pty.js.map +1 -1
  47. package/dist/readiness.d.ts +7 -0
  48. package/dist/readiness.d.ts.map +1 -1
  49. package/dist/readiness.js +12 -9
  50. package/dist/readiness.js.map +1 -1
  51. package/dist/registry.d.ts +317 -2
  52. package/dist/registry.d.ts.map +1 -1
  53. package/dist/registry.js +622 -19
  54. package/dist/registry.js.map +1 -1
  55. package/dist/restore.d.ts +91 -32
  56. package/dist/restore.d.ts.map +1 -1
  57. package/dist/restore.js +226 -186
  58. package/dist/restore.js.map +1 -1
  59. package/dist/tmux.d.ts +2 -1
  60. package/dist/tmux.d.ts.map +1 -1
  61. package/dist/tmux.js +1 -1
  62. package/dist/tmux.js.map +1 -1
  63. package/package.json +1 -1
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Terminal-layout rendering for `remote restore` — split out of restore.ts so
3
+ * the F3 invariant is a MODULE-BOUNDARY property, not a review rule:
4
+ *
5
+ * launchLayout decides every view/launch from the ONE `HostViewSnapshot`
6
+ * it receives AS A PARAMETER. This module must NOT import any live host
7
+ * reader (listNativeSessions, listLocalSessions*, nativeSession*, probe*):
8
+ * you can't call what you don't have, so a divergence between plan time
9
+ * and launch time can never be re-read here — and an error or divergence
10
+ * NEVER becomes permission to launch (`live -> empty` yields attach or
11
+ * refuse, never `h2a run` fabricating a second writer on a live
12
+ * conversation). Only PURE name helpers may be imported from tmux.js.
13
+ */
14
+ import { type LocalSession } from "./tmux.js";
15
+ import type { LayoutTab, LayoutWindow } from "./restore.js";
16
+ /**
17
+ * The ONE host inventory snapshot a restore plan runs on: BOTH inventories
18
+ * read once by the caller (restore.ts owns the readers), so the plan gate and
19
+ * the layout render see the SAME state. A failed inventory read is carried as
20
+ * `known: false` — an UNKNOWN view, never an empty one.
21
+ */
22
+ export type HostViewSnapshot = {
23
+ readonly tmux: {
24
+ readonly known: true;
25
+ readonly sessions: ReadonlyArray<LocalSession>;
26
+ } | {
27
+ readonly known: false;
28
+ readonly reason?: string;
29
+ };
30
+ readonly native: {
31
+ readonly known: true;
32
+ /** RUNNING native sessions only, with the controller-visibility bit. */
33
+ readonly sessions: ReadonlyArray<{
34
+ readonly name: string;
35
+ readonly controlled: boolean | undefined;
36
+ }>;
37
+ } | {
38
+ readonly known: false;
39
+ };
40
+ };
41
+ /**
42
+ * Per-tab command for an ABSENT local session: create it via `remote run …
43
+ * --resume …` (which attaches by default). Live sessions are deliberately
44
+ * handled by `launchLayout`, which knows their exact tmux names and whether they
45
+ * have a client attached; it emits `tmux attach -t <exact-name>` and never
46
+ * relaunches them.
47
+ */
48
+ export declare function tabCommand(tab: LayoutTab, liveSlugs?: ReadonlySet<string>, opts?: {
49
+ forceGateway?: "gateway" | "direct";
50
+ /** Exact existing tmux name — attach directly, never replace/relaunch it. */
51
+ attachSession?: string;
52
+ }): string;
53
+ /** Group collision candidates before restore emits bare-slug attach/resume commands. */
54
+ export declare function ambiguousLiveSessionNames(sessions: readonly Pick<LocalSession, "name" | "slug">[]): Map<string, string[]>;
55
+ /**
56
+ * Launch the layout in gnome-terminal: one window per group, one tab per session.
57
+ *
58
+ * Default behaviour: a live session with a tmux client is already visible and is
59
+ * skipped; a live session with zero clients is orphaned, so a terminal tab is
60
+ * opened directly onto that exact tmux session. An absent session is launched as
61
+ * before. `--reattach` explicitly opens another tab even for an already-visible
62
+ * session. This is deliberately a view decision, not a liveness decision.
63
+ *
64
+ * Every decision reads the `hostView` PARAMETER — the plan's one snapshot —
65
+ * never a live host (F3: this module has no reader to call).
66
+ */
67
+ export declare function launchLayout(windows: LayoutWindow[], hostView: HostViewSnapshot, stderr?: NodeJS.WriteStream, opts?: {
68
+ reattach?: boolean;
69
+ forceGateway?: "gateway" | "direct";
70
+ }): {
71
+ opened: number;
72
+ skippedLive: string[];
73
+ };
74
+ //# sourceMappingURL=launch-layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch-layout.d.ts","sourceRoot":"","sources":["../src/launch-layout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAOH,OAAO,EAA6B,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AACzE,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5D;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,IAAI,EACT;QAAE,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,YAAY,CAAC,CAAA;KAAE,GACxE;QAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxD,QAAQ,CAAC,MAAM,EACX;QACE,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;QACrB,wEAAwE;QACxE,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;YAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YACtB,QAAQ,CAAC,UAAU,EAAE,OAAO,GAAG,SAAS,CAAC;SAC1C,CAAC,CAAC;KACJ,GACD;QAAE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC;CAC/B,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,SAAS,EACd,SAAS,GAAE,WAAW,CAAC,MAAM,CAAa,EAC1C,IAAI,GAAE;IACJ,YAAY,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IACpC,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAC;CACnB,GACL,MAAM,CAgDR;AAED,wFAAwF;AACxF,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,SAAS,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,GACvD,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAUvB;AA4BD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,YAAY,EAAE,EACvB,QAAQ,EAAE,gBAAgB,EAC1B,MAAM,GAAE,MAAM,CAAC,WAA4B,EAC3C,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;CAAO,GACrE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAA;CAAE,CAgQ3C"}
@@ -0,0 +1,324 @@
1
+ /**
2
+ * Terminal-layout rendering for `remote restore` — split out of restore.ts so
3
+ * the F3 invariant is a MODULE-BOUNDARY property, not a review rule:
4
+ *
5
+ * launchLayout decides every view/launch from the ONE `HostViewSnapshot`
6
+ * it receives AS A PARAMETER. This module must NOT import any live host
7
+ * reader (listNativeSessions, listLocalSessions*, nativeSession*, probe*):
8
+ * you can't call what you don't have, so a divergence between plan time
9
+ * and launch time can never be re-read here — and an error or divergence
10
+ * NEVER becomes permission to launch (`live -> empty` yields attach or
11
+ * refuse, never `h2a run` fabricating a second writer on a live
12
+ * conversation). Only PURE name helpers may be imported from tmux.js.
13
+ */
14
+ import { spawn } from "node:child_process";
15
+ import { mkdirSync, writeFileSync } from "node:fs";
16
+ import { homedir } from "node:os";
17
+ import { join } from "node:path";
18
+ import { localSessionName, slugify } from "./tmux.js";
19
+ /**
20
+ * Per-tab command for an ABSENT local session: create it via `remote run …
21
+ * --resume …` (which attaches by default). Live sessions are deliberately
22
+ * handled by `launchLayout`, which knows their exact tmux names and whether they
23
+ * have a client attached; it emits `tmux attach -t <exact-name>` and never
24
+ * relaunches them.
25
+ */
26
+ export function tabCommand(tab, liveSlugs = new Set(), opts = {}) {
27
+ const q = (s) => `'${s.replace(/'/g, "'\\''")}'`;
28
+ if (tab.remoteId) {
29
+ // SCW: attach straight into the Pod's tmux (live, copy-friendly).
30
+ return `h2a attach ${q(tab.remoteId)} --exec`;
31
+ }
32
+ const slug = slugify(tab.label);
33
+ if (tab.attachLive) {
34
+ // §4.1 attach-live: a live managed row whose conversation id is
35
+ // unresolved re-enters by EXACT managed name only. No resume id exists,
36
+ // so no run/replace command may ever be rendered for it.
37
+ return `h2a attach ${q(tab.managedName ?? localSessionName(slug))}`;
38
+ }
39
+ // Effective gateway posture: an explicit `restore --gw/--no-gw` OVERRIDES the
40
+ // per-instance pin; otherwise honour the pinned gatewayMode (absent = default).
41
+ const posture = opts.forceGateway ?? tab.gatewayMode;
42
+ const gwFlag = posture === "gateway" ? " --gw" : posture === "direct" ? " --no-gw" : "";
43
+ // Persisted-host pin — the gatewayMode pattern applied to the terminal
44
+ // host: a NON-LIVE session is re-created on the host its registry record
45
+ // names, never on the current default. A recorded native session survives
46
+ // the fleet-wide H2A_SESSION_HOST=tmux valve via the env pin (any other
47
+ // value selects native — resolveSessionHostKind), where it fails closed if
48
+ // the native host is unavailable rather than silently re-routing; a
49
+ // recorded tmux session survives the native default via --tmux. Tabs
50
+ // without a recorded host (scan fallback) keep following the default.
51
+ const nativePin = tab.hostKind === "local-native" ? "H2A_SESSION_HOST=native " : "";
52
+ const tmuxPin = tab.hostKind === "local-tmux" ? " --tmux" : "";
53
+ const runCmd = (extra) => `${nativePin}h2a run ${q(tab.tool ?? "shell")} ${q(tab.cwd)} ` +
54
+ (tab.sid ? `--resume ${q(tab.sid)} ` : "") +
55
+ `--name ${q(tab.label)}${gwFlag}${tmuxPin}${extra}`;
56
+ if (opts.attachSession) {
57
+ // `h2a attach` resolves the exact managed name and honors the session's
58
+ // RECORDED host (tmux attach for tmux sessions, the native bridge for
59
+ // native-PTY sessions) — a raw `tmux attach -t` here would strand every
60
+ // native session behind a "no such session" error.
61
+ return `h2a attach ${q(opts.attachSession)}`;
62
+ }
63
+ if (liveSlugs.has(slug)) {
64
+ // Compatibility for direct callers that only know a unique slug. Restore
65
+ // itself always supplies the exact name above, avoiding prefix collisions.
66
+ // Crucially, a gateway override never turns a live session into --replace:
67
+ // restore must preserve its process and conversation.
68
+ return `h2a attach ${q(slug)}`;
69
+ }
70
+ return runCmd("");
71
+ }
72
+ /** Group collision candidates before restore emits bare-slug attach/resume commands. */
73
+ export function ambiguousLiveSessionNames(sessions) {
74
+ const namesBySlug = new Map();
75
+ for (const session of sessions) {
76
+ const names = namesBySlug.get(session.slug) ?? [];
77
+ names.push(session.name);
78
+ namesBySlug.set(session.slug, names);
79
+ }
80
+ return new Map([...namesBySlug].filter(([, names]) => names.length > 1));
81
+ }
82
+ // gnome-terminal applies ONE trailing `-- command` to EVERY tab of an
83
+ // invocation (you cannot give each tab its own `--`). So all tabs run the same
84
+ // dispatcher; each claims (under flock) the first map line matching its $PWD
85
+ // and runs that tab's command — exactly how ~/bin/resume-dev-sessions worked.
86
+ const DISPATCHER = `map="$1"
87
+ lock="$map.lock"
88
+ exec 9>"$lock"; flock 9
89
+ line=$(awk -F'\\t' -v c="$PWD" '$1==c{print;exit}' "$map")
90
+ if [ -n "$line" ]; then
91
+ awk -F'\\t' -v c="$PWD" 'BEGIN{d=0} d==0 && $1==c {d=1; next} {print}' "$map" > "$map.tmp" && mv "$map.tmp" "$map"
92
+ fi
93
+ flock -u 9
94
+ cmd=$(printf '%s' "$line" | cut -f2-)
95
+ if [ -n "$cmd" ]; then eval "$cmd"; else echo "[h2a] rien a reprendre pour $PWD" >&2; fi
96
+ exec bash -l`;
97
+ let mapCounter = 0;
98
+ function runDir() {
99
+ const base = process.env.XDG_RUNTIME_DIR
100
+ ? join(process.env.XDG_RUNTIME_DIR, "sentropic-remote")
101
+ : join(homedir(), ".config", "sentropic", "remote-cli", "run");
102
+ mkdirSync(base, { recursive: true });
103
+ return base;
104
+ }
105
+ /**
106
+ * Launch the layout in gnome-terminal: one window per group, one tab per session.
107
+ *
108
+ * Default behaviour: a live session with a tmux client is already visible and is
109
+ * skipped; a live session with zero clients is orphaned, so a terminal tab is
110
+ * opened directly onto that exact tmux session. An absent session is launched as
111
+ * before. `--reattach` explicitly opens another tab even for an already-visible
112
+ * session. This is deliberately a view decision, not a liveness decision.
113
+ *
114
+ * Every decision reads the `hostView` PARAMETER — the plan's one snapshot —
115
+ * never a live host (F3: this module has no reader to call).
116
+ */
117
+ export function launchLayout(windows, hostView, stderr = process.stderr, opts = {}) {
118
+ const tmuxKnown = hostView.tmux.known;
119
+ const tmuxReason = hostView.tmux.known ? undefined : hostView.tmux.reason;
120
+ const tmuxSessions = hostView.tmux.known
121
+ ? hostView.tmux.sessions
122
+ : [];
123
+ // Native view by EXACT session name, carrying the controller-visibility
124
+ // bit. A failed inventory read is an UNKNOWN view (undefined map) — never
125
+ // "no sessions": launching a replacement onto an unprovable host state
126
+ // could create a second writer, so unknown fails closed below.
127
+ const nativeSessionsByName = hostView.native.known
128
+ ? new Map(hostView.native.sessions.map((session) => [
129
+ session.name,
130
+ { controlled: session.controlled },
131
+ ]))
132
+ : undefined;
133
+ // Slug-union view for LEGACY tabs without a recorded host (scan fallback)
134
+ // ONLY — a tab that carries its persisted hostKind + exact managed name is
135
+ // rendered from that identity and never chooses a host from a merged slug
136
+ // list. A CONTROLLED native session counts as attached (already visible).
137
+ const nativeLive = nativeSessionsByName !== undefined
138
+ ? [...nativeSessionsByName.entries()]
139
+ .filter(([name]) => name.startsWith("h2a-") && !name.endsWith(".h2a"))
140
+ .map(([name, view]) => ({
141
+ name,
142
+ slug: name.slice("h2a-".length),
143
+ attached: view.controlled === true,
144
+ }))
145
+ : [];
146
+ const liveSessions = [
147
+ ...tmuxSessions,
148
+ ...nativeLive.filter((native) => !tmuxSessions.some((s) => s.name === native.name)),
149
+ ];
150
+ const ambiguousLiveNames = ambiguousLiveSessionNames(liveSessions);
151
+ const skippedLive = [];
152
+ let opened = 0;
153
+ // A gateway override can configure a NEW process but must never replace a live
154
+ // one. `--reattach` is the sole opt-in that duplicates an already-visible tab.
155
+ const includeAttached = opts.reattach === true;
156
+ const tabOpts = opts.forceGateway ? { forceGateway: opts.forceGateway } : {};
157
+ if (!tmuxKnown) {
158
+ // We cannot reliably tell "attached" from "orphaned". Prefer a harmless
159
+ // duplicate/failing attach over `h2a run` possibly racing a live tmux
160
+ // session; a restore command must never reclaim a view by killing its work.
161
+ stderr.write(`[h2a] tmux view state unavailable${tmuxReason ? ` (${tmuxReason})` : ""}; ` +
162
+ "opening attach tabs rather than relaunching local sessions\n");
163
+ }
164
+ for (const win of windows) {
165
+ // Remote (k8s) tabs are always included — we can't probe pod health here.
166
+ // Local tabs carry their finalized command because a live orphan must attach
167
+ // to its exact tmux name, while an absent session must be launched.
168
+ const activeTabs = [];
169
+ for (const t of win.tabs) {
170
+ if (t.remoteId) {
171
+ activeTabs.push({ tab: t, command: tabCommand(t) });
172
+ continue;
173
+ }
174
+ const slug = slugify(t.label);
175
+ // --- Tabs WITH a recorded host: exact-identity, single-host render. ---
176
+ // The host decision was made by the plan (persisted kind + exact
177
+ // managed name); this block only renders the view for THAT host.
178
+ if (t.hostKind === "local-native") {
179
+ const exact = t.managedName ?? `h2a-${slug}`;
180
+ if (nativeSessionsByName === undefined) {
181
+ stderr.write(`[h2a] restore: native host state UNKNOWN — "${t.label}" neither relaunched nor attached (fail closed)\n`);
182
+ continue;
183
+ }
184
+ const view = nativeSessionsByName.get(exact);
185
+ if (view !== undefined) {
186
+ if (view.controlled === true) {
187
+ // running && controlled → already visible. Even --reattach must
188
+ // not open a competing controller (single-controller model).
189
+ skippedLive.push(t.label);
190
+ if (includeAttached) {
191
+ stderr.write(`[h2a] restore: "${t.label}" is already controlled in a terminal; not opening a second controller\n`);
192
+ }
193
+ continue;
194
+ }
195
+ if (view.controlled === undefined) {
196
+ // running && view unknown (host predates the visibility bit):
197
+ // preserve the process, report, never relaunch.
198
+ stderr.write(`[h2a] restore: controller visibility UNKNOWN for running native session "${t.label}"; attach manually: h2a attach ${exact}\n`);
199
+ continue;
200
+ }
201
+ // running && !controlled → exactly one exact-name attach.
202
+ activeTabs.push({
203
+ tab: t,
204
+ command: tabCommand(t, new Set([slug]), { attachSession: exact }),
205
+ });
206
+ continue;
207
+ }
208
+ // Dead/absent on a KNOWN native view.
209
+ if (t.attachLive) {
210
+ stderr.write(`[h2a] restore: "${t.label}" was live at plan time but is gone; no resolved conversation exists to relaunch it\n`);
211
+ continue;
212
+ }
213
+ activeTabs.push({ tab: t, command: tabCommand(t, new Set(), tabOpts) });
214
+ continue;
215
+ }
216
+ if (t.hostKind === "local-tmux") {
217
+ const exact = t.managedName ?? `h2a-${slug}`;
218
+ if (!tmuxKnown) {
219
+ // Conservative: a harmless exact attach, never a relaunch race.
220
+ activeTabs.push({
221
+ tab: t,
222
+ command: tabCommand(t, new Set([slug]), { attachSession: exact }),
223
+ });
224
+ continue;
225
+ }
226
+ const liveTmux = tmuxSessions.find((session) => session.name === exact);
227
+ if (liveTmux) {
228
+ // DRAIN view: the live legacy process is attach-only — dispatched
229
+ // to its persisted host via `h2a attach`; never replaced, and no
230
+ // tmux session is ever created here.
231
+ if (!includeAttached && liveTmux.attached) {
232
+ skippedLive.push(t.label);
233
+ continue;
234
+ }
235
+ activeTabs.push({
236
+ tab: t,
237
+ command: tabCommand(t, new Set([slug]), { attachSession: exact }),
238
+ });
239
+ continue;
240
+ }
241
+ if (t.attachLive) {
242
+ stderr.write(`[h2a] restore: "${t.label}" was live at plan time but is gone; no resolved conversation exists to relaunch it\n`);
243
+ continue;
244
+ }
245
+ // Dead recorded tmux row: re-created on its recorded host via the
246
+ // --tmux pin (tabCommand). Which host a dead tmux session should be
247
+ // relaunched on is a REOPENED policy question — this keeps the #199
248
+ // behavior as-is.
249
+ activeTabs.push({ tab: t, command: tabCommand(t, new Set(), tabOpts) });
250
+ continue;
251
+ }
252
+ // --- Legacy tabs without a recorded host: slug-union view (scan). ---
253
+ if (!tmuxKnown) {
254
+ activeTabs.push({
255
+ tab: t,
256
+ command: tabCommand(t, new Set([slug]), { attachSession: `h2a-${slug}` }),
257
+ });
258
+ continue;
259
+ }
260
+ const names = ambiguousLiveNames.get(slug);
261
+ if (names) {
262
+ skippedLive.push(t.label);
263
+ stderr.write(`[h2a] restore skipped "${t.label}": local tmux slug is ambiguous (${names.sort().join(", ")}); ` +
264
+ `attach explicitly with h2a attach ${names.sort()[0]} or h2a attach ${names.sort()[1]}\n`);
265
+ continue;
266
+ }
267
+ const liveSession = liveSessions.find((session) => session.slug === slug);
268
+ if (liveSession && !includeAttached && liveSession.attached) {
269
+ skippedLive.push(t.label);
270
+ continue;
271
+ }
272
+ if (!liveSession && nativeSessionsByName === undefined) {
273
+ // The tab would LAUNCH — but the native host state is unknown, so a
274
+ // homonymous live native session cannot be ruled out. Fail closed.
275
+ stderr.write(`[h2a] restore: native host state UNKNOWN — "${t.label}" not launched (fail closed)\n`);
276
+ continue;
277
+ }
278
+ activeTabs.push({
279
+ tab: t,
280
+ command: liveSession
281
+ ? tabCommand(t, new Set([slug]), { attachSession: liveSession.name })
282
+ : tabCommand(t, new Set(), tabOpts),
283
+ });
284
+ }
285
+ if (activeTabs.length === 0) {
286
+ // All sessions in this window are already active — no tab needed.
287
+ continue;
288
+ }
289
+ // Map keyed by per-tab working directory -> the tab's command. Tabs sharing
290
+ // a cwd (several sessions of one project) each claim a distinct line FIFO.
291
+ const slug = win.title.replace(/[^a-zA-Z0-9]+/g, "-");
292
+ const mapPath = join(runDir(), `restore-${process.pid}-${slug}-${mapCounter++}.map`);
293
+ const body = activeTabs
294
+ .map(({ tab, command }) => `${tab.cwd}\t${command}`)
295
+ .join("\n") + "\n";
296
+ writeFileSync(mapPath, body, "utf8");
297
+ const args = [];
298
+ activeTabs.forEach(({ tab }, i) => {
299
+ args.push(i === 0 ? "--window" : "--tab", `--working-directory=${tab.cwd}`, `--title=${tab.label}`);
300
+ });
301
+ // ONE shared dispatcher command for all tabs of this window.
302
+ args.push("--", "bash", "-lc", DISPATCHER, "remote-restore", mapPath);
303
+ stderr.write(`[h2a] fenêtre "${win.title}" (${activeTabs.length} onglet(s))\n`);
304
+ // Surface gnome-terminal errors (e.g. "Failed to get screen…") instead of
305
+ // silently claiming the window opened.
306
+ const { GNOME_TERMINAL_SCREEN: _drop, ...childEnv } = process.env;
307
+ const child = spawn("gnome-terminal", args, {
308
+ stdio: ["ignore", "ignore", "pipe"],
309
+ detached: true,
310
+ env: childEnv,
311
+ });
312
+ child.stderr?.on("data", (chunk) => {
313
+ stderr.write(`[h2a] gnome-terminal: ${chunk.toString().trim()}\n`);
314
+ });
315
+ child.unref();
316
+ opened += activeTabs.length;
317
+ }
318
+ if (skippedLive.length > 0) {
319
+ stderr.write(`[h2a] ${skippedLive.length} session(s) déjà visibles ignorées` +
320
+ ` (--reattach pour les rouvrir quand même): ${skippedLive.join(", ")}\n`);
321
+ }
322
+ return { opened, skippedLive };
323
+ }
324
+ //# sourceMappingURL=launch-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch-layout.js","sourceRoot":"","sources":["../src/launch-layout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,146 @@
1
+ import { type PromptDeliveryDeps } from "./prompt-delivery.js";
2
+ import { type SessionClass } from "./session-class.js";
3
+ export type SessionHostKind = "native" | "local-tmux";
4
+ /**
5
+ * Which host runs a NEW session's terminal. The native PTY host is the
6
+ * default; `--tmux` (per command) or H2A_SESSION_HOST=tmux (fleet-wide safety
7
+ * valve) selects tmux. Existing sessions are NEVER re-routed here — verbs that
8
+ * act on a recorded session honor its registry kind instead.
9
+ */
10
+ export declare function resolveSessionHostKind(opts: {
11
+ readonly tmux?: boolean;
12
+ }, env?: Readonly<Record<string, string | undefined>>): SessionHostKind;
13
+ /** Companion native session that hosts the h2a MCP sidecar for `name`. */
14
+ export declare function nativeSidecarName(name: string): string;
15
+ export type NativeSessionState = {
16
+ readonly id: string;
17
+ readonly pid: number;
18
+ readonly status: "running" | "stopping" | "exited";
19
+ readonly exit: {
20
+ readonly exitCode: number;
21
+ readonly signal?: number;
22
+ } | null;
23
+ /**
24
+ * Controller-visibility bit (never the controller's identity): true when an
25
+ * exclusive input controller is attached. OPTIONAL because an older running
26
+ * host does not report it — consumers must treat absence as an UNKNOWN view
27
+ * and fail closed (report, don't relaunch), never as "uncontrolled".
28
+ */
29
+ readonly controlled?: boolean;
30
+ };
31
+ export type NativeStartResult = {
32
+ readonly name: string;
33
+ readonly slug: string;
34
+ readonly pid: number;
35
+ };
36
+ /** node-pty loadable + containment binaries present. Pure preflight. */
37
+ export declare function nativeHostAvailable(): {
38
+ readonly ok: true;
39
+ } | {
40
+ readonly ok: false;
41
+ readonly reason: string;
42
+ };
43
+ /** Spawn-or-adopt the per-user host; returns its identity. */
44
+ export declare function ensureNativeHost(): {
45
+ hostPid: number;
46
+ socketPath: string;
47
+ };
48
+ export declare function listNativeSessions(): ReadonlyArray<NativeSessionState>;
49
+ /**
50
+ * 3-state session probe (F2). The lie this type removes: a failed `state` op
51
+ * used to flatten into `undefined`/`alive=false` — indistinguishable from a
52
+ * PROVEN dead/absent session, which let destructive acts read an op failure
53
+ * as a death certificate. The states:
54
+ * - "found": a reachable host answered with the session's state;
55
+ * - "absent": POSITIVE proof of absence — a reachable host does not know
56
+ * the session, or no host is listening at all (a PTY cannot outlive its
57
+ * host process, the same provable-death rule as a missing tmux server);
58
+ * - "unknown": the op failed (spawn error, timeout, protocol failure) —
59
+ * NEVER proof of death; destructive callers must fail closed on it.
60
+ * The classification happens IN-BAND in the `probe` op (op.ts), where the
61
+ * error codes live — never by parsing a generic failure on this side.
62
+ */
63
+ export type NativeStateProbe = {
64
+ readonly state: "found";
65
+ readonly session: NativeSessionState;
66
+ } | {
67
+ readonly state: "absent";
68
+ } | {
69
+ readonly state: "unknown";
70
+ readonly reason: string;
71
+ };
72
+ export declare function nativeSessionState(name: string): NativeStateProbe;
73
+ /**
74
+ * 3-state liveness (F2): `true` / `false` are POSITIVE verdicts; "unknown"
75
+ * is a probe failure that destructive paths must refuse on. Never throws.
76
+ */
77
+ export declare function nativeSessionLiveness(name: string): true | false | "unknown";
78
+ export declare function nativeSessionPid(name: string): number | undefined;
79
+ export type NativeLaunchMetadata = {
80
+ readonly label?: string;
81
+ readonly resumeId?: string;
82
+ readonly sessionClass?: SessionClass;
83
+ readonly terminateOnAgentExit?: boolean;
84
+ readonly refuseExisting?: boolean;
85
+ };
86
+ /**
87
+ * startLocalSession twin. Runs the SAME bash wrapper contract as the tmux
88
+ * path (LOCAL_WRAPPER drop-to-shell / STRUCTURED_LOCAL_WRAPPER exec-only), so
89
+ * agent exit semantics are identical across hosts.
90
+ */
91
+ export declare function startNativeSession(profile: string, command: string, cwd: string, args?: ReadonlyArray<string>, label?: string, metadata?: NativeLaunchMetadata): NativeStartResult;
92
+ /** sendKeysLiteral twin: raw keystrokes, no interpretation. */
93
+ export declare function nativeSendKeysLiteral(name: string, text: string): boolean;
94
+ /** paste-buffer -p twin: the text lands as ONE bracketed block. */
95
+ export declare function nativePasteBlock(name: string, text: string): boolean;
96
+ /** The Enter that submits a composed block. */
97
+ export declare function nativeSendEnter(name: string): boolean;
98
+ /**
99
+ * capturePane twin. IMPORTANT semantic difference: tmux returns the RENDERED
100
+ * screen; this returns the ANSI-stripped tail of the output stream. For probe
101
+ * matching (prompt echo, modal text) the stream tail carries the same visible
102
+ * text — full-screen-repaint TUIs re-emit their frame into the tail.
103
+ */
104
+ export declare function nativeCapture(name: string, bytes?: number): string | undefined;
105
+ /** kill-session twin: SIGTERM with SIGKILL escalation inside the op. */
106
+ export declare function killNativeSession(name: string): boolean;
107
+ /**
108
+ * Kill a session AND its h2a sidecar companion (the tmux twin gets this for
109
+ * free because the sidecar is a window inside the killed session).
110
+ */
111
+ export declare function killNativeSessionTree(name: string): boolean;
112
+ /**
113
+ * startHeadlessSession twin: run-once agent under HEADLESS_WRAPPER, writing
114
+ * result.json + output.log, session ends when the wrapper finishes. The
115
+ * prompt rides a 0600 file consumed and unlinked by the wrapper — identical
116
+ * contract to the tmux path.
117
+ */
118
+ export declare function startNativeHeadlessSession(profile: string, command: string, cwd: string, args: ReadonlyArray<string>, resultJson: string, outputLog: string, label: string, promptInput?: string, refuseExisting?: boolean, sessionClass?: SessionClass): NativeStartResult & {
119
+ promptFile?: string;
120
+ };
121
+ /**
122
+ * startH2aWindow twin: the h2a MCP sidecar runs in a COMPANION native session
123
+ * (`<name>.h2a`) instead of a tmux window. Lifecycle: killNativeSessionTree
124
+ * stops it with its main session.
125
+ */
126
+ export declare function startNativeH2aSidecar(name: string, cwd: string, h2aCommand: string, options?: {
127
+ verified?: boolean;
128
+ }): boolean;
129
+ /**
130
+ * Foreground interactive attach on the CURRENT terminal (tmux attach twin).
131
+ * Blocks until the session exits or the user detaches with Ctrl-\.
132
+ */
133
+ export declare function attachNativeSession(name: string): number;
134
+ /** clearPaneComposer twin: C-u wipes the composer line (best-effort). */
135
+ export declare function nativeClearComposer(name: string): boolean;
136
+ /** paneTreeCpuMs twin, keyed by session name instead of tmux pane. */
137
+ export declare function nativeTreeCpuMs(name: string): number | undefined;
138
+ /** paneWorkerPid twin: the pid actually doing the work under the wrapper. */
139
+ export declare function nativeWorkerPid(name: string): number | undefined;
140
+ /**
141
+ * PromptDeliveryDeps for a native session — deliverInitialPrompt() runs the
142
+ * SAME measured protocol as on tmux; only the transport differs. The "pane"
143
+ * key given to deliverInitialPrompt must be the session NAME.
144
+ */
145
+ export declare function nativePromptDeliveryDeps(sleep: (ms: number) => void): PromptDeliveryDeps;
146
+ //# sourceMappingURL=native-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-host.d.ts","sourceRoot":"","sources":["../src/native-host.ts"],"names":[],"mappings":"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"}