@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,63 @@
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
+ * The {@link Cursor} {@link SessionHandle.send} returns when it could **not
6
+ * confirm delivery** — no user record for the sent text appeared (a lost Enter,
7
+ * a boot-race drop). It is a detectable sentinel, NOT a positional count: a count
8
+ * cursor persisted and reused later would silently slice the WHOLE transcript
9
+ * ("everything since index 0"). `messagesSince`/`turnComplete` on this sentinel
10
+ * read empty/false, so an unconfirmed turn surfaces as "re-send me", never a
11
+ * flood. Consumers can compare `cursor === DELIVERY_UNCONFIRMED`.
12
+ */
13
+ export declare const DELIVERY_UNCONFIRMED = "delivery-unconfirmed";
14
+ /**
15
+ * The {@link Cursor} {@link SessionHandle.send} returns when the message was sent
16
+ * into a **busy** session and the agent **queued** it — accepted, and it will run
17
+ * after the in-flight turn finishes (claude shows "Press up to edit queued
18
+ * messages"). Distinct from {@link DELIVERY_UNCONFIRMED} on purpose: the message
19
+ * is NOT lost, so a consumer must **not** re-send (that would double-run). Its
20
+ * user record does not exist yet (the queued turn hasn't started), so like
21
+ * `DELIVERY_UNCONFIRMED` it resolves empty in `messagesSince`/`turnComplete` —
22
+ * the consumer `wait()`s for the current turn, lets the queued turn run, then
23
+ * reads with a fresh cursor. Compare `cursor === DELIVERED_QUEUED`.
24
+ */
25
+ export declare const DELIVERED_QUEUED = "delivered-queued";
26
+ interface HandleDeps {
27
+ backend: Backend;
28
+ agent: AgentDef;
29
+ namespace: string;
30
+ name: string;
31
+ /**
32
+ * The agent's conversation id, when known — surfaced as
33
+ * {@link SessionHandle.agentSessionId}. `undefined` for sessions with no
34
+ * recoverable id (older/non-claudemux, a cache-miss at adopt, or a bare
35
+ * `--resume`); never fabricated.
36
+ */
37
+ agentSessionId?: string;
38
+ /**
39
+ * The hook rendezvous file injected at spawn (for the observe channel). When
40
+ * absent (e.g. {@link attachHandle}/adopt), the handle re-derives it from
41
+ * {@link agentSessionId} — correct for the common (non-resume) case.
42
+ */
43
+ rendezvousPath?: string;
44
+ }
45
+ /**
46
+ * Build a {@link SessionHandle} backed by a per-handle mutex. Every public
47
+ * method that mutates or reads pane state goes through the mutex so
48
+ * concurrent consumer calls cannot interleave bytes.
49
+ */
50
+ export declare function makeHandle(deps: HandleDeps): SessionHandle;
51
+ /**
52
+ * Build a {@link SessionHandle} that points at an existing session, without
53
+ * spawning or booting — the "attach to a live session" seam.
54
+ *
55
+ * Two public consumers: the stateless CLI reattaches through this on every
56
+ * invocation, and the public `adopt()` primitive is built directly on it —
57
+ * `adopt()` asserts the session EXISTS, then calls `attachHandle` (the
58
+ * exists-asserting mirror of `create()`'s SessionExists guard). `create()`
59
+ * remains the only path that *boots*; `attachHandle` is pure attach.
60
+ */
61
+ export declare function attachHandle(deps: HandleDeps): SessionHandle;
62
+ export {};
63
+ //# sourceMappingURL=handle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handle.d.ts","sourceRoot":"","sources":["../../src/session/handle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,OAAO,EAAc,MAAM,sBAAsB,CAAC;AAUhE,OAAO,KAAK,EAMV,aAAa,EAEd,MAAM,aAAa,CAAC;AAOrB;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,yBAAyB,CAAC;AAE3D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAEnD,UAAU,UAAU;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,QAAQ,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,aAAa,CAoJ1D;AA8GD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,GAAG,aAAa,CAE5D"}
@@ -0,0 +1,284 @@
1
+ import { PromptResponseUnsupported, TranscriptUnlocatable } from "../errors.js";
2
+ import { interruptOnce } from "../io/interrupt.js";
3
+ import { respondOnce } from "../io/respond.js";
4
+ import { sendOnce, submitOnce } from "../io/send.js";
5
+ import { stabilize } from "../io/stabilize.js";
6
+ import { waitForOutcome } from "../io/wait.js";
7
+ import { SessionObserver } from "../observe/session-observer.js";
8
+ import { classify } from "../state/classifier.js";
9
+ import { stripSgr } from "../util/ansi.js";
10
+ import { sleep } from "../util/sleep.js";
11
+ import { CLASSIFIER_CAPTURE } from "./constants.js";
12
+ import { rendezvousPathFor } from "./hooks.js";
13
+ import { Mutex } from "./mutex.js";
14
+ /**
15
+ * The {@link Cursor} {@link SessionHandle.send} returns when it could **not
16
+ * confirm delivery** — no user record for the sent text appeared (a lost Enter,
17
+ * a boot-race drop). It is a detectable sentinel, NOT a positional count: a count
18
+ * cursor persisted and reused later would silently slice the WHOLE transcript
19
+ * ("everything since index 0"). `messagesSince`/`turnComplete` on this sentinel
20
+ * read empty/false, so an unconfirmed turn surfaces as "re-send me", never a
21
+ * flood. Consumers can compare `cursor === DELIVERY_UNCONFIRMED`.
22
+ */
23
+ export const DELIVERY_UNCONFIRMED = "delivery-unconfirmed";
24
+ /**
25
+ * The {@link Cursor} {@link SessionHandle.send} returns when the message was sent
26
+ * into a **busy** session and the agent **queued** it — accepted, and it will run
27
+ * after the in-flight turn finishes (claude shows "Press up to edit queued
28
+ * messages"). Distinct from {@link DELIVERY_UNCONFIRMED} on purpose: the message
29
+ * is NOT lost, so a consumer must **not** re-send (that would double-run). Its
30
+ * user record does not exist yet (the queued turn hasn't started), so like
31
+ * `DELIVERY_UNCONFIRMED` it resolves empty in `messagesSince`/`turnComplete` —
32
+ * the consumer `wait()`s for the current turn, lets the queued turn run, then
33
+ * reads with a fresh cursor. Compare `cursor === DELIVERED_QUEUED`.
34
+ */
35
+ export const DELIVERED_QUEUED = "delivered-queued";
36
+ /**
37
+ * Build a {@link SessionHandle} backed by a per-handle mutex. Every public
38
+ * method that mutates or reads pane state goes through the mutex so
39
+ * concurrent consumer calls cannot interleave bytes.
40
+ */
41
+ export function makeHandle(deps) {
42
+ const ref = { namespace: deps.namespace, name: deps.name };
43
+ const mutex = new Mutex();
44
+ // Authoritative session-interaction state: this handle issued an `interrupt()`
45
+ // not yet superseded by a `send()`. An interrupt fires no `stop` edge and
46
+ // leaves the spinner's "esc to interrupt" frozen in scrollback, so neither
47
+ // the hook nor the pane can tell aborted from working — but the handle knows.
48
+ let interruptPending = false;
49
+ // The per-handle stateful read core: incremental (bounded) reads of the hook
50
+ // rendezvous + transcript. state()/progress()/wait()/messagesSince all defer to
51
+ // it — one owner of "what's true", parsing only newly-appended bytes per poll.
52
+ const rv = rendezvousPath(deps);
53
+ const observer = new SessionObserver({
54
+ agent: deps.agent,
55
+ ...(rv === undefined ? {} : { rendezvousPath: rv }),
56
+ ...(deps.agentSessionId === undefined ? {} : { agentSessionId: deps.agentSessionId }),
57
+ });
58
+ /**
59
+ * Capture + classify the pane, fuse with the observer's belief. The capture
60
+ * failure is NOT swallowed: in tmux a failed capture means the session/server
61
+ * is gone (terminal), so `SessionGone`/`BackendUnreachable` propagates.
62
+ */
63
+ const readBelief = async (weInterrupted) => {
64
+ const paneText = await deps.backend.capture(ref, CLASSIFIER_CAPTURE);
65
+ const pane = {
66
+ state: classify(paneText, deps.agent.rules),
67
+ interrupted: deps.agent.rules.interrupted?.(paneText) ?? false,
68
+ // Real, non-whitespace content (SGR stripped) — gates the drift canary so a
69
+ // blank/gone pane is never judged as "all channels blind."
70
+ nonEmpty: stripSgr(paneText).trim().length > 0,
71
+ };
72
+ return { belief: observer.belief(pane, weInterrupted), paneText };
73
+ };
74
+ return {
75
+ name: deps.name,
76
+ namespace: deps.namespace,
77
+ ...(deps.agentSessionId === undefined ? {} : { agentSessionId: deps.agentSessionId }),
78
+ send: (text) => mutex.run(async () => {
79
+ interruptPending = false; // a new turn supersedes any pending interrupt
80
+ const beforeIds = new Set(observer.thread().messages.map((m) => m.id));
81
+ await sendOnce(deps.backend, deps.agent, ref, text);
82
+ // Anchor the cursor on OUR OWN user record, not a positional count.
83
+ // A count read here is wrong: the PRIOR turn's reply may not have flushed
84
+ // yet (the transcript trails the done signal by ~100ms), and a human may
85
+ // type an interleaved turn. Anchoring on the record this send produced is
86
+ // immune to both. Fall back to the sentinel only if it never appears.
87
+ const ownId = await anchorOwnTurn(observer, beforeIds, text);
88
+ if (ownId !== undefined)
89
+ return ownId;
90
+ // No user record appeared. A turn sent into a BUSY session is QUEUED by
91
+ // the agent (accepted, runs next — do NOT re-send); report that distinctly
92
+ // so the consumer doesn't double-run. The agent owns the "queued" pane
93
+ // vocabulary; we just ask it.
94
+ const pane = await deps.backend.capture(ref, CLASSIFIER_CAPTURE);
95
+ if (deps.agent.rules.queued?.(pane))
96
+ return DELIVERED_QUEUED;
97
+ // Lost-submit recovery — but ONLY when the pane looks like an un-submitted
98
+ // DRAFT: it classifies `unknown` (a `❯ <text>` composer that is neither the
99
+ // empty idle box nor the working spinner). That is the lost-Enter signature
100
+ // — the paste reached the composer but the Enter didn't register. In that
101
+ // one case re-fire Enter ONCE (submitOnce never re-pastes, so it cannot
102
+ // duplicate the body) and re-anchor; the needle-match keeps it honest
103
+ // (confirms a NEW record matching OUR text, never a stray draft).
104
+ //
105
+ // If the pane is `working`/`idle` instead, the submit already TOOK (or
106
+ // there is nothing in the composer) and we merely couldn't anchor the
107
+ // record — e.g. an adopted session whose transcript isn't locatable. Firing
108
+ // a stray Enter there would inject a spurious empty turn, so we don't: the
109
+ // honest answer is DELIVERY_UNCONFIRMED.
110
+ if (classify(pane, deps.agent.rules) === "unknown") {
111
+ await submitOnce(deps.backend, ref);
112
+ const recovered = await anchorOwnTurn(observer, beforeIds, text, RETRY_ANCHOR_POLLS);
113
+ if (recovered !== undefined)
114
+ return recovered;
115
+ }
116
+ return DELIVERY_UNCONFIRMED;
117
+ }),
118
+ messagesSince: (cursor) => mutex.run(async () => {
119
+ if (!observer.transcriptLocatable())
120
+ throw new TranscriptUnlocatable(deps.name);
121
+ return messagesSince(observer, cursor);
122
+ }),
123
+ turnComplete: (cursor) => mutex.run(async () => {
124
+ if (!observer.transcriptLocatable())
125
+ throw new TranscriptUnlocatable(deps.name);
126
+ return messagesSince(observer, cursor).some((m) => m.role === "assistant");
127
+ }),
128
+ progress: () => mutex.run(async () => {
129
+ const { belief } = await readBelief(interruptPending);
130
+ // Project to the public Progress; the extra belief fields (interrupted /
131
+ // edge timings) are wait()'s concern, not progress()'s.
132
+ const { phase, toolInFlight, transcriptCount, hookChannelHealthy, agentChannelHealthy, state, } = belief;
133
+ return {
134
+ phase,
135
+ toolInFlight,
136
+ transcriptCount,
137
+ hookChannelHealthy,
138
+ agentChannelHealthy,
139
+ state,
140
+ };
141
+ }),
142
+ respond: (choice) => mutex.run(async () => {
143
+ // Compose two sub-owners; own neither's internals. The AGENT owns the
144
+ // menu option-order (choice→key) — no mapping ⇒ refuse rather than guess
145
+ // a key (a wrong guess could pick "allow all"). The OBSERVER owns "is a
146
+ // prompt still showing" — injected as the confirm predicate. The io
147
+ // primitive owns delivery + settle.
148
+ const key = deps.agent.permissionPrompt?.respondKey(choice);
149
+ if (key === undefined)
150
+ throw new PromptResponseUnsupported(deps.name, deps.agent.name);
151
+ await respondOnce(deps.backend, ref, key, async () => (await readBelief(false)).belief.state === "permission-prompt");
152
+ }),
153
+ interrupt: () => mutex.run(async () => {
154
+ interruptPending = true; // authoritative: we aborted; no `stop` will come
155
+ await interruptOnce(deps.backend, ref);
156
+ }),
157
+ wait: (opts) => mutex.run(() => {
158
+ // We issued an interrupt and haven't sent since — there is no turn to
159
+ // wait for; report the abort rather than poll a frozen spinner to budget.
160
+ if (interruptPending)
161
+ return Promise.resolve({ kind: "aborted" });
162
+ return waitForOutcome(deps.backend, ref, opts ?? {}, { stabilize }, () => readBelief(false));
163
+ }),
164
+ state: () => mutex.run(async () => (await readBelief(interruptPending)).belief.state),
165
+ capture: (opts) => mutex.run(() => deps.backend.capture(ref, opts)),
166
+ kill: () => mutex.run(() => deps.backend.kill(ref)),
167
+ onBackendCommand: (handler) => deps.backend.onCommand(handler),
168
+ };
169
+ }
170
+ /** The hook rendezvous file: the one injected at spawn, else re-derived from the id. */
171
+ function rendezvousPath(deps) {
172
+ if (deps.rendezvousPath !== undefined)
173
+ return deps.rendezvousPath;
174
+ return deps.agentSessionId === undefined ? undefined : rendezvousPathFor(deps.agentSessionId);
175
+ }
176
+ /**
177
+ * The messages produced since `cursor` — the shared body of `messagesSince` and
178
+ * `turnComplete`, read from the observer's incremental transcript cache. Causal
179
+ * chain when the cursor is a real message id; clean positional slice for a legacy
180
+ * numeric cursor; EMPTY for an unresolvable cursor (sentinel/garbage) — never the
181
+ * whole transcript (F40).
182
+ */
183
+ function messagesSince(observer, cursor) {
184
+ const { messages: all, parentOf } = observer.thread();
185
+ if (all.some((m) => m.id === cursor))
186
+ return descendantsOf(all, parentOf, cursor);
187
+ const n = Number.parseInt(cursor, 10);
188
+ return Number.isFinite(n) && n >= 0 && String(n) === cursor.trim() ? all.slice(n) : [];
189
+ }
190
+ /**
191
+ * Messages causally after `ancestorId` — those whose parent chain passes through
192
+ * it — in file order. Uses the thread links, not file position, so the prior
193
+ * turn's late-flushing reply (which descends from an EARLIER user record) is
194
+ * excluded even if it lands after our record in the append-only file. Records
195
+ * with no parent chain (e.g. an agent that omits `parentId`) fall back to a
196
+ * positional slice so a thread-less transcript still works.
197
+ *
198
+ * **Compaction-safe.** Verified empirically on claude 2.1.162: a compaction
199
+ * (`/compact` or auto) summarizes the *context window* but leaves the on-disk
200
+ * transcript append-only with an UNBROKEN linear `parentUuid` chain, so a
201
+ * post-compaction turn still descends from a pre-compaction cursor — no special
202
+ * handling needed for the observed case. As defense-in-depth against a future
203
+ * record-format change (or a partial flush) that *did* drop an intermediate
204
+ * record, a message whose chain hits a MISSING parent (orphaned, not a clean
205
+ * root) and that sits positionally after the cursor is still included — we can't
206
+ * prove causality through a hole, so we fall back to position. This cannot
207
+ * re-include the late-flush prior reply: its parent record IS present (it roots
208
+ * cleanly at an earlier turn), so it's never an orphan. (F43/F25.)
209
+ */
210
+ function descendantsOf(all, fullParentOf, ancestorId) {
211
+ // Prefer the FULL ancestry graph (every record, incl. the non-message ones an
212
+ // agent threads between a prompt and its reply). Without it — an agent with no
213
+ // `parseEdge` — fall back to links between surfaced messages only.
214
+ const parentOf = fullParentOf.size > 0 ? fullParentOf : new Map(all.map((m) => [m.id, m.parentId]));
215
+ const hasLinks = [...parentOf.values()].some((p) => p !== undefined);
216
+ const anchorIdx = all.findIndex((m) => m.id === ancestorId);
217
+ if (!hasLinks)
218
+ return anchorIdx >= 0 ? all.slice(anchorIdx + 1) : all.slice();
219
+ // Classify a message's lineage relative to the cursor by walking its parent
220
+ // chain: reaches the cursor → `descends`; hits a referenced-but-absent parent
221
+ // → `orphan` (a hole, e.g. a dropped record); reaches a clean root (no parent)
222
+ // → `rooted` (a different lineage — an earlier turn).
223
+ const lineage = (id) => {
224
+ const seen = new Set();
225
+ let cur = parentOf.get(id);
226
+ while (cur !== undefined) {
227
+ if (cur === ancestorId)
228
+ return "descends";
229
+ if (seen.has(cur))
230
+ return "rooted"; // cycle guard
231
+ seen.add(cur);
232
+ if (!parentOf.has(cur))
233
+ return "orphan"; // parent referenced but its record is gone
234
+ cur = parentOf.get(cur);
235
+ }
236
+ return "rooted";
237
+ };
238
+ return all.filter((m, i) => {
239
+ const l = lineage(m.id);
240
+ return l === "descends" || (l === "orphan" && anchorIdx >= 0 && i > anchorIdx);
241
+ });
242
+ }
243
+ const ANCHOR_POLLS = 12;
244
+ // The post-recovery re-anchor is shorter: a lost-Enter record appears within a
245
+ // poll or two of the retry Enter, so a true non-delivery still reports promptly
246
+ // rather than paying a second full anchor window.
247
+ const RETRY_ANCHOR_POLLS = 8;
248
+ const ANCHOR_POLL_MS = 250;
249
+ /** Collapse whitespace so a reflowed/echoed prompt still matches. */
250
+ const squash = (s) => s.replace(/\s+/g, " ").trim();
251
+ /**
252
+ * The id of the user record THIS send produced — a NEW (not pre-existing) user
253
+ * message whose text matches what we sent. Polled because the record flushes
254
+ * shortly after delivery; `undefined` if it never appears (a delivery problem).
255
+ */
256
+ async function anchorOwnTurn(observer, beforeIds, text, polls = ANCHOR_POLLS) {
257
+ const needle = squash(text).slice(0, 80); // prefix tolerates echo reflow / truncation
258
+ for (let attempt = 0; attempt < polls; attempt++) {
259
+ const msgs = observer.thread().messages;
260
+ for (let i = msgs.length - 1; i >= 0; i--) {
261
+ const m = msgs[i];
262
+ if (m === undefined || m.role !== "user" || beforeIds.has(m.id))
263
+ continue;
264
+ if (m.parts.some((p) => p.kind === "text" && squash(p.text).includes(needle)))
265
+ return m.id;
266
+ }
267
+ await sleep(ANCHOR_POLL_MS);
268
+ }
269
+ return undefined;
270
+ }
271
+ /**
272
+ * Build a {@link SessionHandle} that points at an existing session, without
273
+ * spawning or booting — the "attach to a live session" seam.
274
+ *
275
+ * Two public consumers: the stateless CLI reattaches through this on every
276
+ * invocation, and the public `adopt()` primitive is built directly on it —
277
+ * `adopt()` asserts the session EXISTS, then calls `attachHandle` (the
278
+ * exists-asserting mirror of `create()`'s SessionExists guard). `create()`
279
+ * remains the only path that *boots*; `attachHandle` is pure attach.
280
+ */
281
+ export function attachHandle(deps) {
282
+ return makeHandle(deps);
283
+ }
284
+ //# sourceMappingURL=handle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handle.js","sourceRoot":"","sources":["../../src/session/handle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { AgentDef } from "../agents/types.js";
2
+ /**
3
+ * Hook injection for the observe side. claudemux observes a session reliably
4
+ * via agent **hooks** (deterministic, harness-fired) + the transcript — never
5
+ * by scraping the TUI. On spawn we inject the agent's hook settings (default
6
+ * on) so the agent appends turn-lifecycle markers to a claudemux-owned local
7
+ * rendezvous file; the Observer reads them as phase edges.
8
+ *
9
+ * This module owns the claudemux-side mechanism (where the rendezvous lives,
10
+ * whether to inject); the agent owns the hook *vocabulary* (which events, the
11
+ * settings shape) behind {@link AgentDef.hooks}. No agent flag strings here.
12
+ */
13
+ /**
14
+ * The claudemux-owned local rendezvous file for a session's hook turn-markers.
15
+ * Local, absolute, no network (honors the trust posture). `.ndjson` (not the
16
+ * agent's `.jsonl` transcript) keeps the two stores distinct.
17
+ */
18
+ export declare function rendezvousPathFor(sessionId: string): string;
19
+ /**
20
+ * Compute the hook injection for a spawn.
21
+ *
22
+ * @returns `args` — extra argv to prepend so the agent emits turn markers to
23
+ * `rendezvousPath` (the Observer reads it). Both empty/absent when hooks are
24
+ * disabled, the agent has none, or the consumer already supplied the agent's
25
+ * hook flag (we don't double-inject — deep-merging the consumer's settings
26
+ * with ours is a hardening follow-up).
27
+ */
28
+ export declare function buildHookInjection(o: {
29
+ agent: AgentDef;
30
+ sessionId: string;
31
+ enabled: boolean;
32
+ userExtraArgs: readonly string[];
33
+ }): {
34
+ args: string[];
35
+ rendezvousPath?: string;
36
+ };
37
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/session/hooks.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD;;;;;;;;;;GAUG;AAEH;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAG3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE;IACpC,KAAK,EAAE,QAAQ,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC,GAAG;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,CAO9C"}
@@ -0,0 +1,42 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ /**
4
+ * Hook injection for the observe side. claudemux observes a session reliably
5
+ * via agent **hooks** (deterministic, harness-fired) + the transcript — never
6
+ * by scraping the TUI. On spawn we inject the agent's hook settings (default
7
+ * on) so the agent appends turn-lifecycle markers to a claudemux-owned local
8
+ * rendezvous file; the Observer reads them as phase edges.
9
+ *
10
+ * This module owns the claudemux-side mechanism (where the rendezvous lives,
11
+ * whether to inject); the agent owns the hook *vocabulary* (which events, the
12
+ * settings shape) behind {@link AgentDef.hooks}. No agent flag strings here.
13
+ */
14
+ /**
15
+ * The claudemux-owned local rendezvous file for a session's hook turn-markers.
16
+ * Local, absolute, no network (honors the trust posture). `.ndjson` (not the
17
+ * agent's `.jsonl` transcript) keeps the two stores distinct.
18
+ */
19
+ export function rendezvousPathFor(sessionId) {
20
+ const stateDir = process.env.XDG_STATE_HOME ?? join(homedir(), ".local", "state");
21
+ return join(stateDir, "claudemux", "turns", `${sessionId}.ndjson`);
22
+ }
23
+ /**
24
+ * Compute the hook injection for a spawn.
25
+ *
26
+ * @returns `args` — extra argv to prepend so the agent emits turn markers to
27
+ * `rendezvousPath` (the Observer reads it). Both empty/absent when hooks are
28
+ * disabled, the agent has none, or the consumer already supplied the agent's
29
+ * hook flag (we don't double-inject — deep-merging the consumer's settings
30
+ * with ours is a hardening follow-up).
31
+ */
32
+ export function buildHookInjection(o) {
33
+ if (!o.enabled || o.agent.hooks === undefined)
34
+ return { args: [] };
35
+ const rendezvousPath = rendezvousPathFor(o.sessionId);
36
+ const spec = o.agent.hooks.spec({ rendezvousPath });
37
+ const alreadySet = o.userExtraArgs.some((a) => a === spec.flag || a.startsWith(`${spec.flag}=`));
38
+ if (alreadySet)
39
+ return { args: [] };
40
+ return { args: [spec.flag, spec.value], rendezvousPath };
41
+ }
42
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/session/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC;;;;;;;;;;GAUG;AAEH;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAClF,OAAO,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,SAAS,SAAS,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,CAKlC;IACC,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IACnE,MAAM,cAAc,GAAG,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC;IACpD,MAAM,UAAU,GAAG,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IACjG,IAAI,UAAU;QAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IACpC,OAAO,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Tiny async mutex. Wraps an async task so calls serialize per-instance.
3
+ *
4
+ * The substrate uses one mutex per `SessionHandle` so concurrent consumer
5
+ * calls (e.g. `send` racing with `capture`) cannot interleave bytes.
6
+ */
7
+ export declare class Mutex {
8
+ #private;
9
+ /**
10
+ * Run `task` with exclusive access. The mutex is released whether the
11
+ * task resolves or rejects.
12
+ */
13
+ run<T>(task: () => Promise<T>): Promise<T>;
14
+ }
15
+ //# sourceMappingURL=mutex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutex.d.ts","sourceRoot":"","sources":["../../src/session/mutex.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,qBAAa,KAAK;;IAGhB;;;OAGG;IACG,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAcjD"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Tiny async mutex. Wraps an async task so calls serialize per-instance.
3
+ *
4
+ * The substrate uses one mutex per `SessionHandle` so concurrent consumer
5
+ * calls (e.g. `send` racing with `capture`) cannot interleave bytes.
6
+ */
7
+ export class Mutex {
8
+ #tail = Promise.resolve();
9
+ /**
10
+ * Run `task` with exclusive access. The mutex is released whether the
11
+ * task resolves or rejects.
12
+ */
13
+ async run(task) {
14
+ const prior = this.#tail;
15
+ let resolveNext;
16
+ const next = new Promise((res) => {
17
+ resolveNext = res;
18
+ });
19
+ this.#tail = next;
20
+ try {
21
+ await prior;
22
+ return await task();
23
+ }
24
+ finally {
25
+ resolveNext();
26
+ }
27
+ }
28
+ }
29
+ //# sourceMappingURL=mutex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutex.js","sourceRoot":"","sources":["../../src/session/mutex.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,OAAO,KAAK;IAChB,KAAK,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAC;IAE5C;;;OAGG;IACH,KAAK,CAAC,GAAG,CAAI,IAAsB;QACjC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,IAAI,WAAwB,CAAC;QAC7B,MAAM,IAAI,GAAG,IAAI,OAAO,CAAO,CAAC,GAAG,EAAE,EAAE;YACrC,WAAW,GAAG,GAAG,CAAC;QACpB,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC;YACH,MAAM,KAAK,CAAC;YACZ,OAAO,MAAM,IAAI,EAAE,CAAC;QACtB,CAAC;gBAAS,CAAC;YACT,WAAW,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,43 @@
1
+ import type { SessionHandle } from "../types.js";
2
+ import { type ResumeOptions } from "./resume.js";
3
+ /**
4
+ * How a session was reconnected on daemon boot — the answer to "did it survive?"
5
+ * - `"attached"` — the pane was **still alive** (your process restarted, the
6
+ * session didn't). Reconnected to the running session; nothing was lost.
7
+ * - `"resumed"` — the pane was **gone** (it crashed, or the box lost its tmux
8
+ * server). The conversation was continued in a **fresh** pane from the
9
+ * persisted id. A turn may have been in flight when it died — check
10
+ * `turnComplete(yourLastCursor)` and re-send if it reads `false`.
11
+ */
12
+ export type RecoverStatus = "attached" | "resumed";
13
+ /** The result of {@link recover}: the live handle + how it was recovered. */
14
+ export interface RecoverResult {
15
+ readonly session: SessionHandle;
16
+ readonly status: RecoverStatus;
17
+ }
18
+ /**
19
+ * Reconnect to a session on boot **without the consumer hand-rolling the
20
+ * adopt-vs-resume decision.** This is the one verb a daemon calls for each
21
+ * `{ name, agentSessionId }` it persisted: it tries to {@link adopt} the running
22
+ * pane, and if that pane is gone (`SessionGone` — a crash, or the box lost tmux)
23
+ * it {@link resume}s the same conversation in a fresh pane. The returned
24
+ * {@link RecoverStatus} tells you which happened, so "was it crashed?" is a field,
25
+ * not a `try`/`catch` you write yourself.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * // On daemon boot, for each persisted { name, agentSessionId, cwd, lastCursor }:
30
+ * const { session, status } = await recover({ name, agentSessionId, cwd });
31
+ * if (status === "resumed" && !(await session.turnComplete(lastCursor))) {
32
+ * await session.send(lastInFlightPrompt); // it crashed mid-turn — re-send the lost turn
33
+ * }
34
+ * // status === "attached" ⇒ the session never went down; just keep going.
35
+ * ```
36
+ *
37
+ * Takes the same options as {@link resume} (the crash path needs them — `cwd`,
38
+ * `agentSessionId`, boot knobs). Only `SessionGone` from `adopt` triggers the
39
+ * resume fallback; any other adopt error (e.g. `InvalidSessionName`) propagates —
40
+ * those are caller mistakes, not crashes.
41
+ */
42
+ export declare function recover(opts: ResumeOptions): Promise<RecoverResult>;
43
+ //# sourceMappingURL=recover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recover.d.ts","sourceRoot":"","sources":["../../src/session/recover.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,EAAU,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEzD;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,SAAS,CAAC;AAEnD,6EAA6E;AAC7E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAczE"}
@@ -0,0 +1,45 @@
1
+ import { SessionGone } from "../errors.js";
2
+ import { adopt } from "./adopt.js";
3
+ import { resume } from "./resume.js";
4
+ /**
5
+ * Reconnect to a session on boot **without the consumer hand-rolling the
6
+ * adopt-vs-resume decision.** This is the one verb a daemon calls for each
7
+ * `{ name, agentSessionId }` it persisted: it tries to {@link adopt} the running
8
+ * pane, and if that pane is gone (`SessionGone` — a crash, or the box lost tmux)
9
+ * it {@link resume}s the same conversation in a fresh pane. The returned
10
+ * {@link RecoverStatus} tells you which happened, so "was it crashed?" is a field,
11
+ * not a `try`/`catch` you write yourself.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // On daemon boot, for each persisted { name, agentSessionId, cwd, lastCursor }:
16
+ * const { session, status } = await recover({ name, agentSessionId, cwd });
17
+ * if (status === "resumed" && !(await session.turnComplete(lastCursor))) {
18
+ * await session.send(lastInFlightPrompt); // it crashed mid-turn — re-send the lost turn
19
+ * }
20
+ * // status === "attached" ⇒ the session never went down; just keep going.
21
+ * ```
22
+ *
23
+ * Takes the same options as {@link resume} (the crash path needs them — `cwd`,
24
+ * `agentSessionId`, boot knobs). Only `SessionGone` from `adopt` triggers the
25
+ * resume fallback; any other adopt error (e.g. `InvalidSessionName`) propagates —
26
+ * those are caller mistakes, not crashes.
27
+ */
28
+ export async function recover(opts) {
29
+ try {
30
+ const session = await adopt({
31
+ name: opts.name,
32
+ ...(opts.namespace === undefined ? {} : { namespace: opts.namespace }),
33
+ ...(opts.agent === undefined ? {} : { agent: opts.agent }),
34
+ ...(opts.backend === undefined ? {} : { backend: opts.backend }),
35
+ });
36
+ return { session, status: "attached" };
37
+ }
38
+ catch (e) {
39
+ if (!(e instanceof SessionGone))
40
+ throw e; // a real adopt error, not a crash
41
+ const session = await resume(opts);
42
+ return { session, status: "resumed" };
43
+ }
44
+ }
45
+ //# sourceMappingURL=recover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recover.js","sourceRoot":"","sources":["../../src/session/recover.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAsB,MAAM,aAAa,CAAC;AAmBzD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAmB;IAC/C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC;YAC1B,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,GAAG,CAAC,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;YACtE,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;YAC1D,GAAG,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;SACjE,CAAC,CAAC;QACH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IACzC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,CAAC,CAAC,YAAY,WAAW,CAAC;YAAE,MAAM,CAAC,CAAC,CAAC,kCAAkC;QAC5E,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IACxC,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { formatSessionLabel } from "../backends/types.js";
2
+ //# sourceMappingURL=ref.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ref.d.ts","sourceRoot":"","sources":["../../src/session/ref.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,5 @@
1
+ // formatSessionLabel moved to `src/backends/types.ts` so the backend layer
2
+ // can use it without importing from `src/session/`. This file re-exports
3
+ // for callers that already use the `src/session/ref` path.
4
+ export { formatSessionLabel } from "../backends/types.js";
5
+ //# sourceMappingURL=ref.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ref.js","sourceRoot":"","sources":["../../src/session/ref.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,yEAAyE;AACzE,2DAA2D;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}