@wastedcode/claudemux 0.2.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 (249) hide show
  1. package/CHANGELOG.md +257 -0
  2. package/LICENSE +21 -0
  3. package/README.md +493 -0
  4. package/bin/claudemux +6 -0
  5. package/dist/agents/claude.d.ts +3 -0
  6. package/dist/agents/claude.d.ts.map +1 -0
  7. package/dist/agents/claude.js +585 -0
  8. package/dist/agents/claude.js.map +1 -0
  9. package/dist/agents/index.d.ts +2 -0
  10. package/dist/agents/index.d.ts.map +1 -0
  11. package/dist/agents/index.js +2 -0
  12. package/dist/agents/index.js.map +1 -0
  13. package/dist/agents/types.d.ts +252 -0
  14. package/dist/agents/types.d.ts.map +1 -0
  15. package/dist/agents/types.js +2 -0
  16. package/dist/agents/types.js.map +1 -0
  17. package/dist/backends/tmux/capture.d.ts +25 -0
  18. package/dist/backends/tmux/capture.d.ts.map +1 -0
  19. package/dist/backends/tmux/capture.js +35 -0
  20. package/dist/backends/tmux/capture.js.map +1 -0
  21. package/dist/backends/tmux/exec.d.ts +105 -0
  22. package/dist/backends/tmux/exec.d.ts.map +1 -0
  23. package/dist/backends/tmux/exec.js +226 -0
  24. package/dist/backends/tmux/exec.js.map +1 -0
  25. package/dist/backends/tmux/index.d.ts +22 -0
  26. package/dist/backends/tmux/index.d.ts.map +1 -0
  27. package/dist/backends/tmux/index.js +108 -0
  28. package/dist/backends/tmux/index.js.map +1 -0
  29. package/dist/backends/tmux/keys.d.ts +38 -0
  30. package/dist/backends/tmux/keys.d.ts.map +1 -0
  31. package/dist/backends/tmux/keys.js +63 -0
  32. package/dist/backends/tmux/keys.js.map +1 -0
  33. package/dist/backends/tmux/options.d.ts +24 -0
  34. package/dist/backends/tmux/options.d.ts.map +1 -0
  35. package/dist/backends/tmux/options.js +84 -0
  36. package/dist/backends/tmux/options.js.map +1 -0
  37. package/dist/backends/tmux/sessions.d.ts +70 -0
  38. package/dist/backends/tmux/sessions.d.ts.map +1 -0
  39. package/dist/backends/tmux/sessions.js +156 -0
  40. package/dist/backends/tmux/sessions.js.map +1 -0
  41. package/dist/backends/tmux/socket.d.ts +26 -0
  42. package/dist/backends/tmux/socket.d.ts.map +1 -0
  43. package/dist/backends/tmux/socket.js +31 -0
  44. package/dist/backends/tmux/socket.js.map +1 -0
  45. package/dist/backends/types.d.ts +110 -0
  46. package/dist/backends/types.d.ts.map +1 -0
  47. package/dist/backends/types.js +24 -0
  48. package/dist/backends/types.js.map +1 -0
  49. package/dist/cli/ask.d.ts +11 -0
  50. package/dist/cli/ask.d.ts.map +1 -0
  51. package/dist/cli/ask.js +17 -0
  52. package/dist/cli/ask.js.map +1 -0
  53. package/dist/cli/capture.d.ts +8 -0
  54. package/dist/cli/capture.d.ts.map +1 -0
  55. package/dist/cli/capture.js +15 -0
  56. package/dist/cli/capture.js.map +1 -0
  57. package/dist/cli/context.d.ts +71 -0
  58. package/dist/cli/context.d.ts.map +1 -0
  59. package/dist/cli/context.js +82 -0
  60. package/dist/cli/context.js.map +1 -0
  61. package/dist/cli/exists.d.ts +7 -0
  62. package/dist/cli/exists.d.ts.map +1 -0
  63. package/dist/cli/exists.js +16 -0
  64. package/dist/cli/exists.js.map +1 -0
  65. package/dist/cli/interrupt.d.ts +10 -0
  66. package/dist/cli/interrupt.d.ts.map +1 -0
  67. package/dist/cli/interrupt.js +13 -0
  68. package/dist/cli/interrupt.js.map +1 -0
  69. package/dist/cli/kill.d.ts +7 -0
  70. package/dist/cli/kill.d.ts.map +1 -0
  71. package/dist/cli/kill.js +14 -0
  72. package/dist/cli/kill.js.map +1 -0
  73. package/dist/cli/list.d.ts +10 -0
  74. package/dist/cli/list.d.ts.map +1 -0
  75. package/dist/cli/list.js +19 -0
  76. package/dist/cli/list.js.map +1 -0
  77. package/dist/cli/main.d.ts +13 -0
  78. package/dist/cli/main.d.ts.map +1 -0
  79. package/dist/cli/main.js +143 -0
  80. package/dist/cli/main.js.map +1 -0
  81. package/dist/cli/messages.d.ts +9 -0
  82. package/dist/cli/messages.d.ts.map +1 -0
  83. package/dist/cli/messages.js +13 -0
  84. package/dist/cli/messages.js.map +1 -0
  85. package/dist/cli/respond.d.ts +10 -0
  86. package/dist/cli/respond.d.ts.map +1 -0
  87. package/dist/cli/respond.js +23 -0
  88. package/dist/cli/respond.js.map +1 -0
  89. package/dist/cli/resume.d.ts +12 -0
  90. package/dist/cli/resume.d.ts.map +1 -0
  91. package/dist/cli/resume.js +21 -0
  92. package/dist/cli/resume.js.map +1 -0
  93. package/dist/cli/send.d.ts +9 -0
  94. package/dist/cli/send.d.ts.map +1 -0
  95. package/dist/cli/send.js +16 -0
  96. package/dist/cli/send.js.map +1 -0
  97. package/dist/cli/spawn.d.ts +14 -0
  98. package/dist/cli/spawn.d.ts.map +1 -0
  99. package/dist/cli/spawn.js +21 -0
  100. package/dist/cli/spawn.js.map +1 -0
  101. package/dist/cli/state.d.ts +7 -0
  102. package/dist/cli/state.d.ts.map +1 -0
  103. package/dist/cli/state.js +11 -0
  104. package/dist/cli/state.js.map +1 -0
  105. package/dist/cli/turn-complete.d.ts +8 -0
  106. package/dist/cli/turn-complete.d.ts.map +1 -0
  107. package/dist/cli/turn-complete.js +14 -0
  108. package/dist/cli/turn-complete.js.map +1 -0
  109. package/dist/cli/wait.d.ts +13 -0
  110. package/dist/cli/wait.d.ts.map +1 -0
  111. package/dist/cli/wait.js +17 -0
  112. package/dist/cli/wait.js.map +1 -0
  113. package/dist/compose.d.ts +81 -0
  114. package/dist/compose.d.ts.map +1 -0
  115. package/dist/compose.js +64 -0
  116. package/dist/compose.js.map +1 -0
  117. package/dist/errors.d.ts +250 -0
  118. package/dist/errors.d.ts.map +1 -0
  119. package/dist/errors.js +300 -0
  120. package/dist/errors.js.map +1 -0
  121. package/dist/index.d.ts +22 -0
  122. package/dist/index.d.ts.map +1 -0
  123. package/dist/index.js +17 -0
  124. package/dist/index.js.map +1 -0
  125. package/dist/io/baseline.d.ts +53 -0
  126. package/dist/io/baseline.d.ts.map +1 -0
  127. package/dist/io/baseline.js +97 -0
  128. package/dist/io/baseline.js.map +1 -0
  129. package/dist/io/capture.d.ts +15 -0
  130. package/dist/io/capture.d.ts.map +1 -0
  131. package/dist/io/capture.js +13 -0
  132. package/dist/io/capture.js.map +1 -0
  133. package/dist/io/interrupt.d.ts +46 -0
  134. package/dist/io/interrupt.d.ts.map +1 -0
  135. package/dist/io/interrupt.js +60 -0
  136. package/dist/io/interrupt.js.map +1 -0
  137. package/dist/io/respond.d.ts +28 -0
  138. package/dist/io/respond.d.ts.map +1 -0
  139. package/dist/io/respond.js +33 -0
  140. package/dist/io/respond.js.map +1 -0
  141. package/dist/io/send.d.ts +44 -0
  142. package/dist/io/send.d.ts.map +1 -0
  143. package/dist/io/send.js +66 -0
  144. package/dist/io/send.js.map +1 -0
  145. package/dist/io/stabilize.d.ts +28 -0
  146. package/dist/io/stabilize.d.ts.map +1 -0
  147. package/dist/io/stabilize.js +20 -0
  148. package/dist/io/stabilize.js.map +1 -0
  149. package/dist/io/wait.d.ts +47 -0
  150. package/dist/io/wait.d.ts.map +1 -0
  151. package/dist/io/wait.js +117 -0
  152. package/dist/io/wait.js.map +1 -0
  153. package/dist/observe/incremental.d.ts +28 -0
  154. package/dist/observe/incremental.d.ts.map +1 -0
  155. package/dist/observe/incremental.js +57 -0
  156. package/dist/observe/incremental.js.map +1 -0
  157. package/dist/observe/observer.d.ts +86 -0
  158. package/dist/observe/observer.d.ts.map +1 -0
  159. package/dist/observe/observer.js +167 -0
  160. package/dist/observe/observer.js.map +1 -0
  161. package/dist/observe/session-observer.d.ts +49 -0
  162. package/dist/observe/session-observer.d.ts.map +1 -0
  163. package/dist/observe/session-observer.js +123 -0
  164. package/dist/observe/session-observer.js.map +1 -0
  165. package/dist/session/adopt.d.ts +52 -0
  166. package/dist/session/adopt.d.ts.map +1 -0
  167. package/dist/session/adopt.js +57 -0
  168. package/dist/session/adopt.js.map +1 -0
  169. package/dist/session/boot.d.ts +66 -0
  170. package/dist/session/boot.d.ts.map +1 -0
  171. package/dist/session/boot.js +216 -0
  172. package/dist/session/boot.js.map +1 -0
  173. package/dist/session/constants.d.ts +57 -0
  174. package/dist/session/constants.d.ts.map +1 -0
  175. package/dist/session/constants.js +54 -0
  176. package/dist/session/constants.js.map +1 -0
  177. package/dist/session/create.d.ts +88 -0
  178. package/dist/session/create.d.ts.map +1 -0
  179. package/dist/session/create.js +66 -0
  180. package/dist/session/create.js.map +1 -0
  181. package/dist/session/default-backend.d.ts +27 -0
  182. package/dist/session/default-backend.d.ts.map +1 -0
  183. package/dist/session/default-backend.js +58 -0
  184. package/dist/session/default-backend.js.map +1 -0
  185. package/dist/session/handle.d.ts +63 -0
  186. package/dist/session/handle.d.ts.map +1 -0
  187. package/dist/session/handle.js +284 -0
  188. package/dist/session/handle.js.map +1 -0
  189. package/dist/session/hooks.d.ts +37 -0
  190. package/dist/session/hooks.d.ts.map +1 -0
  191. package/dist/session/hooks.js +42 -0
  192. package/dist/session/hooks.js.map +1 -0
  193. package/dist/session/mutex.d.ts +15 -0
  194. package/dist/session/mutex.d.ts.map +1 -0
  195. package/dist/session/mutex.js +29 -0
  196. package/dist/session/mutex.js.map +1 -0
  197. package/dist/session/recover.d.ts +43 -0
  198. package/dist/session/recover.d.ts.map +1 -0
  199. package/dist/session/recover.js +45 -0
  200. package/dist/session/recover.js.map +1 -0
  201. package/dist/session/ref.d.ts +2 -0
  202. package/dist/session/ref.d.ts.map +1 -0
  203. package/dist/session/ref.js +5 -0
  204. package/dist/session/ref.js.map +1 -0
  205. package/dist/session/registry.d.ts +31 -0
  206. package/dist/session/registry.d.ts.map +1 -0
  207. package/dist/session/registry.js +32 -0
  208. package/dist/session/registry.js.map +1 -0
  209. package/dist/session/resolve.d.ts +30 -0
  210. package/dist/session/resolve.d.ts.map +1 -0
  211. package/dist/session/resolve.js +24 -0
  212. package/dist/session/resolve.js.map +1 -0
  213. package/dist/session/resume.d.ts +68 -0
  214. package/dist/session/resume.d.ts.map +1 -0
  215. package/dist/session/resume.js +54 -0
  216. package/dist/session/resume.js.map +1 -0
  217. package/dist/session/spawn-boot.d.ts +44 -0
  218. package/dist/session/spawn-boot.d.ts.map +1 -0
  219. package/dist/session/spawn-boot.js +87 -0
  220. package/dist/session/spawn-boot.js.map +1 -0
  221. package/dist/session/validate.d.ts +10 -0
  222. package/dist/session/validate.d.ts.map +1 -0
  223. package/dist/session/validate.js +0 -0
  224. package/dist/session/validate.js.map +1 -0
  225. package/dist/state/classifier.d.ts +29 -0
  226. package/dist/state/classifier.d.ts.map +1 -0
  227. package/dist/state/classifier.js +37 -0
  228. package/dist/state/classifier.js.map +1 -0
  229. package/dist/state/types.d.ts +32 -0
  230. package/dist/state/types.d.ts.map +1 -0
  231. package/dist/state/types.js +2 -0
  232. package/dist/state/types.js.map +1 -0
  233. package/dist/types.d.ts +401 -0
  234. package/dist/types.d.ts.map +1 -0
  235. package/dist/types.js +9 -0
  236. package/dist/types.js.map +1 -0
  237. package/dist/util/ansi.d.ts +14 -0
  238. package/dist/util/ansi.d.ts.map +1 -0
  239. package/dist/util/ansi.js +18 -0
  240. package/dist/util/ansi.js.map +1 -0
  241. package/dist/util/emitter.d.ts +17 -0
  242. package/dist/util/emitter.d.ts.map +1 -0
  243. package/dist/util/emitter.js +33 -0
  244. package/dist/util/emitter.js.map +1 -0
  245. package/dist/util/sleep.d.ts +8 -0
  246. package/dist/util/sleep.d.ts.map +1 -0
  247. package/dist/util/sleep.js +10 -0
  248. package/dist/util/sleep.js.map +1 -0
  249. package/package.json +50 -0
@@ -0,0 +1,216 @@
1
+ import { AgentExitedDuringBoot, BackendUnreachable, DialogStuck, LoginRequired, ReplTimeout, WorkspaceUntrusted, } from "../errors.js";
2
+ import { stabilize } from "../io/stabilize.js";
3
+ import { readHookEdges } from "../observe/observer.js";
4
+ import { sleep } from "../util/sleep.js";
5
+ import { CLASSIFIER_BOTTOM_N, CLASSIFIER_CAPTURE } from "./constants.js";
6
+ import { formatSessionLabel } from "./ref.js";
7
+ /**
8
+ * Default total budget for boot: 60s. The dialog loop must reach a *stable*
9
+ * `agent.boot.isReady` within this window or `ReplTimeout` fires.
10
+ */
11
+ const DEFAULT_BOOT_TIMEOUT_MS = 60_000;
12
+ /** How long we wait between pane captures while watching for dialog advancement. */
13
+ const POLL_INTERVAL_MS = 150;
14
+ /**
15
+ * After we respond to a dialog, the pane must show *something different*
16
+ * within this window. If the same dialog text persists past it, we throw
17
+ * `DialogStuck` — the response key didn't advance the pane, which means
18
+ * either the matcher fired on stale scrollback or claude's input layer
19
+ * isn't accepting keys (the latter is a setup error worth surfacing loudly).
20
+ */
21
+ const DIALOG_ADVANCE_BUDGET_MS = 5_000;
22
+ /**
23
+ * Once `isReady` first matches, the pane must hold steady for this window
24
+ * before we declare boot complete. The empty `❯` input box can flash during
25
+ * the welcome / MCP-init render *before input is actually interactive* — a
26
+ * consumer that `send`s into a not-yet-interactive prompt loses the turn
27
+ * silently (the paste lands, never submits). Requiring stability lets the
28
+ * welcome/MCP render settle. Longer than `wait`'s idle window (250ms) because
29
+ * boot has more going on (box draw, remote-control line, MCP connections).
30
+ * See `engineer/wiki/wait-needs-a-transition-not-a-snapshot`.
31
+ */
32
+ const READY_STABLE_WINDOW_MS = 1_200;
33
+ /**
34
+ * Boot the session: dismiss any matching dialogs in order, then wait for ready.
35
+ *
36
+ * **Ready signal:** a hook *gate* plus a pane *settle*.
37
+ * 1. **`session-start` hook edge — the authoritative "started" gate.** With
38
+ * hooks on (the default, `opts.rendezvousPath` set), boot will not declare
39
+ * ready until this edge fires — a ready-*looking* pane is NOT trusted on
40
+ * its own (the founder's "hooks, not screen-scraping" north star). The edge
41
+ * lands only once input is interactive and post-dialog. With hooks off
42
+ * there is no edge, so the pane is the only signal.
43
+ * 2. **Stable ready box — the delivery-safety settle.** Even after "started,"
44
+ * a fresh REPL is still painting its welcome/MCP render, and the *first*
45
+ * send pasted into that render storm is silently lost (verified). So boot
46
+ * returns only once the ready box has held *stable*, guaranteeing the input
47
+ * is paintable. Dialogs are handled before either check each iteration.
48
+ *
49
+ * Throws on the documented failures.
50
+ *
51
+ * @throws `WorkspaceUntrusted` if the workspace-trust dialog fires and
52
+ * `trustWorkspace` was not set — thrown *before* any keystroke, so no
53
+ * persistent trust flag is written.
54
+ * @throws `LoginRequired` if the login-method dialog fires.
55
+ * @throws `DialogStuck` if a recognized dialog persists after its response.
56
+ * @throws `AgentExitedDuringBoot` if the agent process exits (its session is
57
+ * reaped) before becoming ready — most often an `agentSessionId` collision.
58
+ * @throws `ReplTimeout` if the total budget elapses before a stable ready.
59
+ */
60
+ export async function bootSession(backend, agent, ref, opts = {}) {
61
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_BOOT_TIMEOUT_MS;
62
+ const trustWorkspace = opts.trustWorkspace ?? false;
63
+ const cwd = opts.cwd ?? ref.name;
64
+ const start = Date.now();
65
+ // The rendezvous is reused across resume, so it may ALREADY hold the prior
66
+ // life's `session-start`. Baseline the count now and wait for a NEW one — else
67
+ // a resume boots "ready" on a stale edge (clock-independent: count, not time).
68
+ const priorStarts = countSessionStarts(agent, opts.rendezvousPath);
69
+ while (true) {
70
+ if (Date.now() - start > timeoutMs) {
71
+ throw new ReplTimeout(formatSessionLabel(ref), timeoutMs);
72
+ }
73
+ const text = await captureDuringBoot(backend, ref, opts.agentSessionId);
74
+ // Try every dialog matcher in order — the first that fires wins. Dialogs
75
+ // are always handled before any ready check (hook or pane), so neither can
76
+ // declare ready while a dialog is still on screen.
77
+ const matched = agent.boot.dialogs.find((d) => d.matches(text));
78
+ if (matched) {
79
+ await respondToDialog(backend, ref, matched, trustWorkspace, cwd);
80
+ // Wait for the pane to advance past the matched dialog. If it stays,
81
+ // either the response didn't land or the matcher misfired on history.
82
+ await waitForAdvancement(backend, ref, matched);
83
+ continue; // re-evaluate from the top
84
+ }
85
+ // Authoritative "session has started" gate: the agent's SessionStart hook
86
+ // edge (when hooks are on). The edge only lands once input is interactive
87
+ // and post-dialog, so we DON'T trust a ready-looking pane until it fires —
88
+ // that is the founder's "hooks, not screen-scraping" north star. With hooks
89
+ // off, there is no edge, so the pane is the only signal.
90
+ const hooksOn = opts.rendezvousPath !== undefined;
91
+ const started = !hooksOn || countSessionStarts(agent, opts.rendezvousPath) > priorStarts;
92
+ if (!started) {
93
+ await sleep(POLL_INTERVAL_MS); // session not started yet — wait for the edge
94
+ continue;
95
+ }
96
+ // Started — but a fresh REPL is still painting its welcome/MCP render, and a
97
+ // paste into that render storm is silently lost (verified: the first send
98
+ // races a repaint). So gate the RETURN on a *stable* ready box: the hook
99
+ // says "started", the pane settle says "the input is paintable now." The
100
+ // empty box can also flash mid-render, so requiring it to hold is necessary
101
+ // regardless of the hook.
102
+ if (agent.boot.isReady(text)) {
103
+ const remaining = Math.max(0, timeoutMs - (Date.now() - start));
104
+ const r = await stabilize(backend, ref, {
105
+ lines: CLASSIFIER_BOTTOM_N,
106
+ windowMs: READY_STABLE_WINDOW_MS,
107
+ pollMs: POLL_INTERVAL_MS,
108
+ timeoutMs: Math.min(remaining, READY_STABLE_WINDOW_MS * 6),
109
+ ansi: true,
110
+ });
111
+ // Re-confirm ready on the settled pane (the render may have moved to a
112
+ // dialog or back to working; only a stable ready counts).
113
+ if (r.stable && agent.boot.isReady(r.text) && !anyDialog(agent, r.text)) {
114
+ return;
115
+ }
116
+ continue; // not stable yet (still rendering) — loop and re-evaluate
117
+ }
118
+ // Started but the box isn't ready yet (still rendering) — keep polling.
119
+ await sleep(POLL_INTERVAL_MS);
120
+ }
121
+ }
122
+ function anyDialog(agent, text) {
123
+ return agent.boot.dialogs.some((d) => d.matches(text));
124
+ }
125
+ /** How many `session-start` edges the rendezvous holds (0 when hooks off/absent). */
126
+ function countSessionStarts(agent, rendezvousPath) {
127
+ if (rendezvousPath === undefined)
128
+ return 0;
129
+ return readHookEdges({ agent, rendezvousPath }).filter((e) => e.event === "session-start").length;
130
+ }
131
+ /**
132
+ * Capture the boot pane, distinguishing **the agent exiting before ready**
133
+ * from a **backend-level fault**. The agent runs with `remain-on-exit off`, so
134
+ * a fast exit (the dominant case: an `agentSessionId` collision — see
135
+ * {@link AgentExitedDuringBoot}) reaps the session; the next capture then fails
136
+ * because the session is gone, not because anything is wrong with us.
137
+ *
138
+ * On a capture failure we branch:
139
+ * - {@link BackendUnreachable} → a server-level fault (no server / wedged /
140
+ * spawn-failed) — surface it unchanged; it is not "the agent exited."
141
+ * - otherwise, probe liveness: if the session is genuinely gone, the agent
142
+ * exited before ready → {@link AgentExitedDuringBoot} (fast, no waiting out
143
+ * the 60s `ReplTimeout`). If it is still alive, the capture hiccuped for
144
+ * some other reason — surface the original error honestly.
145
+ *
146
+ * This must not mask the *alive-pane* boot failures (`LoginRequired`,
147
+ * `WorkspaceUntrusted`, `DialogStuck`, `ReplTimeout`): those fire on captured
148
+ * pane text or the timeout, never on a capture failure, so they are untouched.
149
+ */
150
+ async function captureDuringBoot(backend, ref, agentSessionId) {
151
+ try {
152
+ return await backend.capture(ref, CLASSIFIER_CAPTURE);
153
+ }
154
+ catch (err) {
155
+ if (err instanceof BackendUnreachable)
156
+ throw err;
157
+ const alive = await backend.exists(ref).catch(() => false);
158
+ if (!alive) {
159
+ throw new AgentExitedDuringBoot(formatSessionLabel(ref), agentSessionId);
160
+ }
161
+ throw err;
162
+ }
163
+ }
164
+ /**
165
+ * Respond to a matched dialog.
166
+ *
167
+ * - `respond.kind === "throw"` → raise the typed error.
168
+ * - A **gated** dialog (an authority grant, e.g. workspace-trust) → throw the
169
+ * gate's error *before sending any key* unless the consumer opted in. The
170
+ * throw-before-keystroke order is load-bearing: answering the trust dialog
171
+ * writes a persistent trust flag, so we must not send the key on the
172
+ * fail-closed path.
173
+ * - `respond.kind === "key"` → send the key; non-Enter keys get an Enter
174
+ * follow-up to submit (Enter is its own submit).
175
+ */
176
+ async function respondToDialog(backend, ref, dialog, trustWorkspace, cwd) {
177
+ if (dialog.respond.kind === "throw") {
178
+ switch (dialog.respond.errorClass) {
179
+ case "LoginRequired":
180
+ throw new LoginRequired(formatSessionLabel(ref));
181
+ }
182
+ }
183
+ // Authority gate: fail closed unless opted in — BEFORE any keystroke.
184
+ if (dialog.gate) {
185
+ const optedIn = dialog.gate.option === "trustWorkspace" && trustWorkspace;
186
+ if (!optedIn) {
187
+ switch (dialog.gate.errorClass) {
188
+ case "WorkspaceUntrusted":
189
+ throw new WorkspaceUntrusted(formatSessionLabel(ref), cwd);
190
+ }
191
+ }
192
+ }
193
+ const key = dialog.respond.key;
194
+ await backend.send(ref, { kind: "key", key });
195
+ // Numeric/letter dialog responses (1, 2, y, n) typically need an Enter to
196
+ // submit; Enter is its own submit, so no follow-up needed there.
197
+ if (key !== "Enter") {
198
+ await backend.send(ref, { kind: "key", key: "Enter" });
199
+ }
200
+ }
201
+ /**
202
+ * Wait until the pane no longer matches the just-responded dialog. If the
203
+ * matcher keeps firing past {@link DIALOG_ADVANCE_BUDGET_MS}, the response
204
+ * didn't land — throw `DialogStuck` with the dialog's id.
205
+ */
206
+ async function waitForAdvancement(backend, ref, matched) {
207
+ const start = Date.now();
208
+ while (Date.now() - start < DIALOG_ADVANCE_BUDGET_MS) {
209
+ await sleep(POLL_INTERVAL_MS);
210
+ const now = await backend.capture(ref, CLASSIFIER_CAPTURE);
211
+ if (!matched.matches(now))
212
+ return;
213
+ }
214
+ throw new DialogStuck(formatSessionLabel(ref), matched.id);
215
+ }
216
+ //# sourceMappingURL=boot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boot.js","sourceRoot":"","sources":["../../src/session/boot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The default namespace prefix used by `create`, `exists`, `kill`, `list`
3
+ * when the caller doesn't pass one. Two consumers on the same machine
4
+ * pick distinct namespaces to coexist on one shared backend server.
5
+ */
6
+ export declare const DEFAULT_NAMESPACE = "claudemux";
7
+ /**
8
+ * Pane height the substrate creates sessions at (`new-session -y`). The
9
+ * classifier never sees more than this many rows, because `capture-pane -p`
10
+ * returns only the visible region (never scrollback) — so a stray match in
11
+ * history is impossible by construction, regardless of the scan window.
12
+ *
13
+ * If this changes, {@link CLASSIFIER_BOTTOM_N} should track it.
14
+ */
15
+ export declare const PANE_HEIGHT = 40;
16
+ /**
17
+ * How many lines from the bottom of the visible region the classifier scans.
18
+ * Set to the full pane height ({@link PANE_HEIGHT}): the visible region is
19
+ * already only {@link PANE_HEIGHT} rows, so `slice(-CLASSIFIER_BOTTOM_N)` is
20
+ * the whole capture — there is no larger buffer to trim. (An earlier value of
21
+ * 50 implied a 50-line scan that a 40-row pane can never produce; this ties
22
+ * the constant to the real cap.)
23
+ *
24
+ * Shared by `session/boot.ts`, `session/handle.ts`, and `io/wait.ts` so a
25
+ * dialog/idle pattern is detected consistently across the boot and wait paths.
26
+ */
27
+ export declare const CLASSIFIER_BOTTOM_N = 40;
28
+ /**
29
+ * Capture options for any **readiness / classifier** read — bottom-N rows with
30
+ * ANSI styling **on** (`capture -e`). The styling is load-bearing: the agent's
31
+ * idle check separates the dim ghost-placeholder hint shown in an empty input
32
+ * box from a real (normal-intensity) draft, which is impossible on plain text
33
+ * (see claude `isReady`). Substring predicates (dialog headers, working) strip
34
+ * SGR first, so ANSI-on is safe for them too.
35
+ *
36
+ * Every readiness read uses this one shape so the `send→wait` pane fingerprints
37
+ * stay self-consistent (all ANSI). The *public* `capture()` is unaffected — it
38
+ * defaults to plain, user-facing text.
39
+ */
40
+ export declare const CLASSIFIER_CAPTURE: {
41
+ readonly lines: 40;
42
+ readonly ansi: true;
43
+ };
44
+ /**
45
+ * Backend-neutral session-meta key under which {@link create} caches the
46
+ * agent's conversation id, and {@link adopt} reads it back. The id is a
47
+ * *locator* (it names a conversation and its transcript), not a secret; the
48
+ * real boundary is who can reach the backend's per-session store — today a
49
+ * per-process private tmux socket. If claudemux ever multi-tenants onto a
50
+ * *shared* tmux server, a co-tenant could enumerate every such key and locate
51
+ * others' transcripts; revisit the store's trust model then.
52
+ *
53
+ * One authoritative spelling shared by the writer and the reader so they can
54
+ * never drift apart.
55
+ */
56
+ export declare const AGENT_SESSION_ID_META_KEY = "agent-session-id";
57
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/session/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAE7C;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,KAAK,CAAC;AAE9B;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,KAAc,CAAC;AAE/C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB;;;CAAsD,CAAC;AAEtF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,qBAAqB,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The default namespace prefix used by `create`, `exists`, `kill`, `list`
3
+ * when the caller doesn't pass one. Two consumers on the same machine
4
+ * pick distinct namespaces to coexist on one shared backend server.
5
+ */
6
+ export const DEFAULT_NAMESPACE = "claudemux";
7
+ /**
8
+ * Pane height the substrate creates sessions at (`new-session -y`). The
9
+ * classifier never sees more than this many rows, because `capture-pane -p`
10
+ * returns only the visible region (never scrollback) — so a stray match in
11
+ * history is impossible by construction, regardless of the scan window.
12
+ *
13
+ * If this changes, {@link CLASSIFIER_BOTTOM_N} should track it.
14
+ */
15
+ export const PANE_HEIGHT = 40;
16
+ /**
17
+ * How many lines from the bottom of the visible region the classifier scans.
18
+ * Set to the full pane height ({@link PANE_HEIGHT}): the visible region is
19
+ * already only {@link PANE_HEIGHT} rows, so `slice(-CLASSIFIER_BOTTOM_N)` is
20
+ * the whole capture — there is no larger buffer to trim. (An earlier value of
21
+ * 50 implied a 50-line scan that a 40-row pane can never produce; this ties
22
+ * the constant to the real cap.)
23
+ *
24
+ * Shared by `session/boot.ts`, `session/handle.ts`, and `io/wait.ts` so a
25
+ * dialog/idle pattern is detected consistently across the boot and wait paths.
26
+ */
27
+ export const CLASSIFIER_BOTTOM_N = PANE_HEIGHT;
28
+ /**
29
+ * Capture options for any **readiness / classifier** read — bottom-N rows with
30
+ * ANSI styling **on** (`capture -e`). The styling is load-bearing: the agent's
31
+ * idle check separates the dim ghost-placeholder hint shown in an empty input
32
+ * box from a real (normal-intensity) draft, which is impossible on plain text
33
+ * (see claude `isReady`). Substring predicates (dialog headers, working) strip
34
+ * SGR first, so ANSI-on is safe for them too.
35
+ *
36
+ * Every readiness read uses this one shape so the `send→wait` pane fingerprints
37
+ * stay self-consistent (all ANSI). The *public* `capture()` is unaffected — it
38
+ * defaults to plain, user-facing text.
39
+ */
40
+ export const CLASSIFIER_CAPTURE = { lines: CLASSIFIER_BOTTOM_N, ansi: true };
41
+ /**
42
+ * Backend-neutral session-meta key under which {@link create} caches the
43
+ * agent's conversation id, and {@link adopt} reads it back. The id is a
44
+ * *locator* (it names a conversation and its transcript), not a secret; the
45
+ * real boundary is who can reach the backend's per-session store — today a
46
+ * per-process private tmux socket. If claudemux ever multi-tenants onto a
47
+ * *shared* tmux server, a co-tenant could enumerate every such key and locate
48
+ * others' transcripts; revisit the store's trust model then.
49
+ *
50
+ * One authoritative spelling shared by the writer and the reader so they can
51
+ * never drift apart.
52
+ */
53
+ export const AGENT_SESSION_ID_META_KEY = "agent-session-id";
54
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/session/constants.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAAC;AAE7C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAE9B;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,WAAW,CAAC;AAE/C;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,KAAK,EAAE,mBAAmB,EAAE,IAAI,EAAE,IAAI,EAAW,CAAC;AAEtF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,kBAAkB,CAAC"}
@@ -0,0 +1,88 @@
1
+ import type { AgentDef } from "../agents/types.js";
2
+ import type { Backend } from "../backends/types.js";
3
+ import type { SessionHandle } from "../types.js";
4
+ /**
5
+ * Options for {@link create}. The substrate provides sensible defaults so
6
+ * the canonical call is `create({ name, cwd })`.
7
+ */
8
+ export interface CreateOptions {
9
+ /** Session name within the namespace. */
10
+ name: string;
11
+ /** Working directory the agent runs in. */
12
+ cwd: string;
13
+ /** Namespace prefix (default: `"claudemux"`). Lets two consumers coexist. */
14
+ namespace?: string;
15
+ /** Agent definition (default: claude). */
16
+ agent?: AgentDef;
17
+ /** Backend instance (default: tmux on a fresh shared socket per process). */
18
+ backend?: Backend;
19
+ /** Extra args passed to the agent's argv. */
20
+ extraArgs?: string[];
21
+ /**
22
+ * Choose the conversation id for this **fresh** session, instead of letting
23
+ * the substrate mint one. Must be a v4 UUID (validated before spawn —
24
+ * {@link InvalidAgentSessionId} otherwise). The chosen id is what
25
+ * {@link SessionHandle.agentSessionId} reports. Leave unset for the common
26
+ * case: the substrate mints a v4 UUID for you.
27
+ *
28
+ * Resume is **not** this option — use the first-class {@link resume} (or, for
29
+ * an advanced case, an `extraArgs` `--resume <id>`). Passing both this and an
30
+ * `extraArgs` identity flag (`--session-id` / `--resume` / `--fork-session`)
31
+ * is a conflict → {@link AgentSessionIdConflict}.
32
+ */
33
+ agentSessionId?: string;
34
+ /** Override env passed to the agent process. */
35
+ env?: Record<string, string>;
36
+ /** Boot timeout in ms (default 60_000). */
37
+ bootTimeoutMs?: number;
38
+ /**
39
+ * Opt in to auto-dismissing the agent's workspace-trust dialog. Default
40
+ * **false** (fail closed). When the agent asks to trust `cwd` and this is
41
+ * not set, `create` throws `WorkspaceUntrusted` before any keystroke —
42
+ * trusting a folder is an authority grant the substrate won't make
43
+ * silently. See {@link WorkspaceUntrusted} for the persistent/global-trust
44
+ * caveats before enabling it for untrusted-fork (PR-bot / CI) workloads.
45
+ */
46
+ trustWorkspace?: boolean;
47
+ /**
48
+ * Inject the agent's observe hooks at spawn (default **true**). Hooks give
49
+ * claudemux deterministic, reliable turn-lifecycle insight without scraping
50
+ * the TUI. Set `false` to opt out (e.g. you manage your own hooks); observe
51
+ * then degrades to the best-effort pane+transcript fallback. The exact
52
+ * injected settings are inspectable via the agent's hook spec.
53
+ */
54
+ hooks?: boolean;
55
+ }
56
+ /**
57
+ * Create a new session: spawn the agent, dismiss boot dialogs, wait for
58
+ * ready, return a handle.
59
+ *
60
+ * @throws `SessionExists` if a session with the same `{ namespace, name }`
61
+ * already exists — the substrate never silently adopts.
62
+ * @throws `InvalidAgentSessionId` if `agentSessionId` is supplied but is not a
63
+ * v4 UUID (thrown before spawn).
64
+ * @throws `AgentSessionIdConflict` if `agentSessionId` is supplied alongside an
65
+ * `extraArgs` identity flag (`--session-id` / `--resume` / `--fork-session`)
66
+ * — the id was chosen two ways (thrown before spawn).
67
+ * @throws `LoginRequired` if claude's login-method dialog fires (the
68
+ * consumer must `claude auth` first).
69
+ * @throws `WorkspaceUntrusted` if the agent asks to trust `cwd` and
70
+ * `trustWorkspace` was not set (thrown before any keystroke).
71
+ * @throws `DialogStuck` if a recognized boot dialog persists after its
72
+ * response.
73
+ * @throws `AgentExitedDuringBoot` if the agent exits before becoming ready —
74
+ * most often an `agentSessionId` collision (the agent refuses to silently
75
+ * resume an in-use id and exits). The id is carried on the error.
76
+ * @throws `ReplTimeout` if the boot budget elapses before ready.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * import { create, claude } from "claudemux";
81
+ * const session = await create({ name: "job", cwd: process.cwd() });
82
+ * await session.send("Add a CHANGELOG entry");
83
+ * await session.wait();
84
+ * const text = await session.capture();
85
+ * ```
86
+ */
87
+ export declare function create(opts: CreateOptions): Promise<SessionHandle>;
88
+ //# sourceMappingURL=create.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/session/create.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAMjD;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,2CAA2C;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CA+BxE"}
@@ -0,0 +1,66 @@
1
+ import { SessionExists } from "../errors.js";
2
+ import { formatSessionLabel } from "./ref.js";
3
+ import { resolveSessionContext } from "./resolve.js";
4
+ import { spawnBootHandle } from "./spawn-boot.js";
5
+ import { validateAgentSessionId } from "./validate.js";
6
+ /**
7
+ * Create a new session: spawn the agent, dismiss boot dialogs, wait for
8
+ * ready, return a handle.
9
+ *
10
+ * @throws `SessionExists` if a session with the same `{ namespace, name }`
11
+ * already exists — the substrate never silently adopts.
12
+ * @throws `InvalidAgentSessionId` if `agentSessionId` is supplied but is not a
13
+ * v4 UUID (thrown before spawn).
14
+ * @throws `AgentSessionIdConflict` if `agentSessionId` is supplied alongside an
15
+ * `extraArgs` identity flag (`--session-id` / `--resume` / `--fork-session`)
16
+ * — the id was chosen two ways (thrown before spawn).
17
+ * @throws `LoginRequired` if claude's login-method dialog fires (the
18
+ * consumer must `claude auth` first).
19
+ * @throws `WorkspaceUntrusted` if the agent asks to trust `cwd` and
20
+ * `trustWorkspace` was not set (thrown before any keystroke).
21
+ * @throws `DialogStuck` if a recognized boot dialog persists after its
22
+ * response.
23
+ * @throws `AgentExitedDuringBoot` if the agent exits before becoming ready —
24
+ * most often an `agentSessionId` collision (the agent refuses to silently
25
+ * resume an in-use id and exits). The id is carried on the error.
26
+ * @throws `ReplTimeout` if the boot budget elapses before ready.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * import { create, claude } from "claudemux";
31
+ * const session = await create({ name: "job", cwd: process.cwd() });
32
+ * await session.send("Add a CHANGELOG entry");
33
+ * await session.wait();
34
+ * const text = await session.capture();
35
+ * ```
36
+ */
37
+ export async function create(opts) {
38
+ const { ref, agent, backend } = resolveSessionContext(opts);
39
+ // Exists-check first. Never silently adopt an existing session — that is
40
+ // the lifecycle-policy footgun claudemux explicitly avoids.
41
+ if (await backend.exists(ref)) {
42
+ throw new SessionExists(formatSessionLabel(ref));
43
+ }
44
+ // Resolve the FRESH conversation id. The mint is neutral (crypto.randomUUID —
45
+ // no agent vocabulary); a caller-supplied id is validated as a v4 UUID. The
46
+ // agent's buildArgv decides how it becomes a flag and returns the id that will
47
+ // actually run, which `spawnBootHandle` surfaces (single source of truth).
48
+ const explicit = opts.agentSessionId !== undefined;
49
+ if (explicit) {
50
+ validateAgentSessionId(opts.agentSessionId);
51
+ }
52
+ const sessionId = explicit ? opts.agentSessionId : crypto.randomUUID();
53
+ return spawnBootHandle({
54
+ agent,
55
+ backend,
56
+ ref,
57
+ cwd: opts.cwd,
58
+ identity: { mode: "fresh", sessionId, explicit },
59
+ hooks: opts.hooks !== false,
60
+ ...(opts.extraArgs === undefined ? {} : { extraArgs: opts.extraArgs }),
61
+ ...(opts.env === undefined ? {} : { env: opts.env }),
62
+ ...(opts.bootTimeoutMs === undefined ? {} : { bootTimeoutMs: opts.bootTimeoutMs }),
63
+ ...(opts.trustWorkspace === undefined ? {} : { trustWorkspace: opts.trustWorkspace }),
64
+ });
65
+ }
66
+ //# sourceMappingURL=create.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create.js","sourceRoot":"","sources":["../../src/session/create.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAuDvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAmB;IAC9C,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAE5D,yEAAyE;IACzE,4DAA4D;IAC5D,IAAI,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,aAAa,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC;IACnD,CAAC;IAED,8EAA8E;IAC9E,4EAA4E;IAC5E,+EAA+E;IAC/E,2EAA2E;IAC3E,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,KAAK,SAAS,CAAC;IACnD,IAAI,QAAQ,EAAE,CAAC;QACb,sBAAsB,CAAC,IAAI,CAAC,cAAwB,CAAC,CAAC;IACxD,CAAC;IACD,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAE,IAAI,CAAC,cAAyB,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;IAEnF,OAAO,eAAe,CAAC;QACrB,KAAK;QACL,OAAO;QACP,GAAG;QACH,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,QAAQ,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE;QAChD,KAAK,EAAE,IAAI,CAAC,KAAK,KAAK,KAAK;QAC3B,GAAG,CAAC,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;QACtE,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC;QACpD,GAAG,CAAC,IAAI,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC;QAClF,GAAG,CAAC,IAAI,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC;KACtF,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { Backend } from "../backends/types.js";
2
+ /**
3
+ * Single-source socket-name resolution. Precedence (highest first):
4
+ *
5
+ * `explicit` (`--socket` flag) > `CLAUDEMUX_SOCKET` env > `defaultSocketName()`
6
+ *
7
+ * Every candidate is **trimmed**, and the trimmed value is what's both gated
8
+ * AND returned — so `" claudemux "` resolves to the same socket as the bare
9
+ * default, instead of a silently-divergent `-L ' claudemux '` server that
10
+ * re-opens the cross-process rendezvous bug the P0 fix closed (QA P2,
11
+ * [[decisions/0006-default-backend-rendezvous-identity]]). A whitespace-only
12
+ * value is treated as "not set" and falls through to the next candidate.
13
+ *
14
+ * Lives in this bootstrap module so the backend leaf
15
+ * (`backends/tmux/socket.ts`) stays a pure function — env/flag composition
16
+ * is a bootstrap concern, not a backend concern.
17
+ */
18
+ export declare function resolveSocket(explicit?: string): string;
19
+ export declare function sharedDefaultBackend(): Backend;
20
+ /**
21
+ * Build a backend with an explicit socket name. Used by the CLI when the
22
+ * caller passes `--socket <name>` to opt out of the process-wide shared
23
+ * default. Each call returns a fresh instance (no caching) — the caller
24
+ * owns the lifetime.
25
+ */
26
+ export declare function backendWithSocket(socket: string): Backend;
27
+ //# sourceMappingURL=default-backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-backend.d.ts","sourceRoot":"","sources":["../../src/session/default-backend.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAMvD;AAkBD,wBAAgB,oBAAoB,IAAI,OAAO,CAK9C;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEzD"}
@@ -0,0 +1,58 @@
1
+ import { tmuxBackend } from "../backends/tmux/index.js";
2
+ import { defaultSocketName } from "../backends/tmux/socket.js";
3
+ /**
4
+ * Single-source socket-name resolution. Precedence (highest first):
5
+ *
6
+ * `explicit` (`--socket` flag) > `CLAUDEMUX_SOCKET` env > `defaultSocketName()`
7
+ *
8
+ * Every candidate is **trimmed**, and the trimmed value is what's both gated
9
+ * AND returned — so `" claudemux "` resolves to the same socket as the bare
10
+ * default, instead of a silently-divergent `-L ' claudemux '` server that
11
+ * re-opens the cross-process rendezvous bug the P0 fix closed (QA P2,
12
+ * [[decisions/0006-default-backend-rendezvous-identity]]). A whitespace-only
13
+ * value is treated as "not set" and falls through to the next candidate.
14
+ *
15
+ * Lives in this bootstrap module so the backend leaf
16
+ * (`backends/tmux/socket.ts`) stays a pure function — env/flag composition
17
+ * is a bootstrap concern, not a backend concern.
18
+ */
19
+ export function resolveSocket(explicit) {
20
+ const fromFlag = explicit?.trim();
21
+ if (fromFlag)
22
+ return fromFlag;
23
+ const fromEnv = process.env.CLAUDEMUX_SOCKET?.trim();
24
+ if (fromEnv)
25
+ return fromEnv;
26
+ return defaultSocketName();
27
+ }
28
+ /**
29
+ * Process-wide default backend. Lazily constructed on first use so an
30
+ * `import { exists } from "claudemux"` doesn't spawn a tmux server just by
31
+ * loading the module.
32
+ *
33
+ * The socket name is **stable** (default `"claudemux"`, overridable via
34
+ * `CLAUDEMUX_SOCKET`). Stability is load-bearing for the CLI: each
35
+ * `claudemux <verb>` invocation is a cold Node process, and they must
36
+ * discover each other's sessions. A per-process random socket would break
37
+ * the entire CLI user journey — see `brain/initiatives/.../qa.md` §P0-1
38
+ * and `brain/decisions/0006-default-backend-rendezvous-identity.md`.
39
+ *
40
+ * Internal — never exported from `src/index.ts`.
41
+ */
42
+ let cached = null;
43
+ export function sharedDefaultBackend() {
44
+ if (cached === null) {
45
+ cached = tmuxBackend({ socket: resolveSocket() });
46
+ }
47
+ return cached;
48
+ }
49
+ /**
50
+ * Build a backend with an explicit socket name. Used by the CLI when the
51
+ * caller passes `--socket <name>` to opt out of the process-wide shared
52
+ * default. Each call returns a fresh instance (no caching) — the caller
53
+ * owns the lifetime.
54
+ */
55
+ export function backendWithSocket(socket) {
56
+ return tmuxBackend({ socket });
57
+ }
58
+ //# sourceMappingURL=default-backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-backend.js","sourceRoot":"","sources":["../../src/session/default-backend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAG/D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,aAAa,CAAC,QAAiB;IAC7C,MAAM,QAAQ,GAAG,QAAQ,EAAE,IAAI,EAAE,CAAC;IAClC,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,EAAE,CAAC;IACrD,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAC5B,OAAO,iBAAiB,EAAE,CAAC;AAC7B,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,IAAI,MAAM,GAAmB,IAAI,CAAC;AAElC,MAAM,UAAU,oBAAoB;IAClC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,GAAG,WAAW,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,OAAO,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;AACjC,CAAC"}