@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,252 @@
1
+ import type { ClassifierRules } from "../state/types.js";
2
+ import type { Message, PromptChoice } from "../types.js";
3
+ /**
4
+ * A single boot dialog: how to recognize it and how to respond.
5
+ *
6
+ * @remarks
7
+ * Used by `src/session/boot.ts`'s dialog loop. Dialogs are tried in order
8
+ * for each pane snapshot — the first matcher wins. `respond.kind === "throw"`
9
+ * is the escape hatch for dialogs that mean "this is a setup error, not an
10
+ * auto-answerable prompt" (e.g. `login-method` under claudemux, which means
11
+ * the user is not authenticated — see `LoginRequired`).
12
+ */
13
+ export interface BootDialog {
14
+ /** Stable identifier; used in `DialogStuck.dialogId`. */
15
+ id: string;
16
+ /** Returns `true` when the pane text indicates this dialog. */
17
+ matches(paneText: string): boolean;
18
+ /**
19
+ * What to send to dismiss the dialog. `key` sends a single keystroke and
20
+ * advances; `throw` raises a typed error keyed by `errorClass`.
21
+ */
22
+ respond: {
23
+ kind: "key";
24
+ key: "Enter" | "1" | "2" | "y" | "n";
25
+ } | {
26
+ kind: "throw";
27
+ errorClass: "LoginRequired";
28
+ };
29
+ /**
30
+ * Optional authority gate. A gated dialog represents an authority grant
31
+ * (e.g. trusting a folder) that the substrate must NOT auto-answer by
32
+ * default — it fails closed. `boot.ts` throws the gate's error *before*
33
+ * sending the response key unless the consumer explicitly opted in via the
34
+ * matching `CreateOptions` flag. Dismissing it otherwise would make a
35
+ * silent policy decision for the caller (north-star: "report state, the
36
+ * consumer decides policy").
37
+ */
38
+ gate?: {
39
+ /** The `CreateOptions` boolean that must be `true` to auto-answer. */
40
+ option: "trustWorkspace";
41
+ /** The error thrown when the gate is closed (not opted in). */
42
+ errorClass: "WorkspaceUntrusted";
43
+ };
44
+ }
45
+ /**
46
+ * Everything the substrate needs to know about a specific agent. Adding
47
+ * `codex` later = add `src/agents/codex.ts` and export an `AgentDef`.
48
+ *
49
+ * The substrate never imports from `src/backends/**` here. Layering is
50
+ * grep-enforced.
51
+ */
52
+ export interface AgentDef {
53
+ /** Stable identifier (e.g. `"claude"`). */
54
+ readonly name: string;
55
+ /**
56
+ * Resolve the spawn argv for this agent.
57
+ *
58
+ * @param o.cwd — the working directory (plumbed by the session/backend layer).
59
+ * @param o.extraArgs — caller-supplied flags, passed through verbatim.
60
+ * @param o.sessionId — the substrate-minted (or caller-supplied) conversation
61
+ * id the agent should run under. The agent decides whether and how to inject
62
+ * it (claude maps it to `--session-id`); a caller's own identity flag in
63
+ * `extraArgs` wins over it. Neutral here — the flag *string* is the agent's.
64
+ * @param o.sessionIdExplicit — `true` only when the consumer explicitly chose
65
+ * the id (`create({ agentSessionId })`), `false`/absent when it was minted.
66
+ * An explicit id that conflicts with an `extraArgs` identity flag is a
67
+ * caller error → the agent throws a typed `ClaudemuxError`; a minted id is
68
+ * silently suppressed by such a flag instead.
69
+ * @param o.sessionName — the session's human label, for typed-error context
70
+ * on the conflict path (the session has not been spawned yet).
71
+ * @returns `agentSessionId` — the id the argv will **actually** run under (the
72
+ * injected id, a caller's `extraArgs` id, or `undefined` when the agent
73
+ * cannot know it, e.g. a bare `--resume`). `create()` surfaces *this* value,
74
+ * never the mint variable — single source of truth.
75
+ */
76
+ buildArgv(o: {
77
+ cwd: string;
78
+ extraArgs?: string[];
79
+ sessionId?: string;
80
+ sessionIdExplicit?: boolean;
81
+ /**
82
+ * Neutral "resume this existing conversation" request from the session
83
+ * layer's `resume()`. The agent maps it to its own resume flag (claude:
84
+ * `--resume <id>`) and surfaces that id as `agentSessionId`. Mutually
85
+ * exclusive with `sessionId`/an `extraArgs` identity flag (choosing the id
86
+ * twice → typed conflict). Keeps the vendor flag inside the agent seam.
87
+ */
88
+ resumeFrom?: string;
89
+ sessionName?: string;
90
+ }): {
91
+ cmd: string;
92
+ argv: string[];
93
+ env?: Record<string, string>;
94
+ agentSessionId?: string;
95
+ };
96
+ /** Boot orchestration knobs. */
97
+ readonly boot: {
98
+ /** Ordered list of dialogs to try on each snapshot during boot. */
99
+ readonly dialogs: ReadonlyArray<BootDialog>;
100
+ /**
101
+ * Returns `true` when the bottom-N pane text indicates the REPL is
102
+ * ready for input. Must qualify by line context — see
103
+ * `engineer/wiki/tmux-capture-pane-the-bottom-n-trap` ("same glyph,
104
+ * different roles").
105
+ */
106
+ isReady(paneTextBottomN: string): boolean;
107
+ };
108
+ /** Classifier predicates for this agent. */
109
+ readonly rules: ClassifierRules;
110
+ /**
111
+ * Permission-prompt handling — how to *answer* the tool-approval menu the
112
+ * classifier detects via `rules.permissionPrompt`. The SOLE owner of this
113
+ * agent's menu option-order (claude: `1` approve-once / `2` approve-for-session
114
+ * / `3` deny), so the neutral {@link PromptChoice} → keystroke mapping stays
115
+ * out of the agent-agnostic layers (grep-enforced, like the dialog keys). The
116
+ * handle's `respond(choice)` calls {@link respondKey} and fires the returned
117
+ * key; one keystroke selects-and-confirms (no Enter — verified against
118
+ * authenticated claude 2.1.162).
119
+ *
120
+ * Optional: an agent that declares none has `respond()` throw
121
+ * `PromptResponseUnsupported` rather than the substrate guessing a digit (a
122
+ * wrong guess could pick "allow all"). Pairs with a non-empty
123
+ * `rules.permissionPrompt` — detection without an answer mapping would surface
124
+ * `awaiting{permission-prompt}` with no way to act on it.
125
+ */
126
+ readonly permissionPrompt?: {
127
+ /** The keystroke that selects {@link PromptChoice} in this agent's menu. */
128
+ respondKey(choice: PromptChoice): "1" | "2" | "3";
129
+ };
130
+ /**
131
+ * Reading the agent's session transcript. The **sole owner** of this agent's
132
+ * transcript knowledge — both *where* the file lives and *how* its records
133
+ * parse — so every claude-version-fragile bit stays in one file
134
+ * (grep-enforced). The agent-agnostic Observer reads the file and feeds its
135
+ * lines here; it never knows the schema or the path rule.
136
+ *
137
+ * Optional by **graceful degrade**, not by incompleteness: the Observer has
138
+ * landed and uses this whenever it's present (it backs `messagesSince` /
139
+ * `turnComplete` and fuses into the belief). An agent that omits it simply has
140
+ * no transcript channel — reads return empty and observation leans on hooks +
141
+ * pane. (claude implements it; a future agent may not.)
142
+ */
143
+ readonly transcript?: {
144
+ /**
145
+ * Absolute path of the on-disk transcript for a session id, or `null` if
146
+ * not found. Located by id across the agent's session store — never by
147
+ * recomputing the fragile cwd→path rule.
148
+ */
149
+ locate(o: {
150
+ agentSessionId: string;
151
+ home?: string;
152
+ }): string | null;
153
+ /**
154
+ * Parse one transcript line into a neutral {@link Message}, or `null` for a
155
+ * metadata record, a blank line, or a partial/half-flushed line.
156
+ */
157
+ parseLine(line: string): Message | null;
158
+ /**
159
+ * The raw ancestry link (`id` + optional `parentId`) of **any** transcript
160
+ * record that carries an identity — including non-message records (e.g.
161
+ * `attachment`) that an agent may thread *between* a prompt and its reply.
162
+ * Returns `null` for blank/partial lines and records with no id.
163
+ *
164
+ * Lets the consumer reconstruct the *full* causal graph and so detect
165
+ * message descendants even when the chain passes through records
166
+ * {@link parseLine} drops. Optional: when absent, descendant detection falls
167
+ * back to links between surfaced messages only.
168
+ */
169
+ parseEdge?(line: string): {
170
+ id: string;
171
+ parentId?: string;
172
+ } | null;
173
+ /**
174
+ * True when `message` starts a new user turn (a typed prompt), false for
175
+ * tool-result feedback that is also recorded turn-side.
176
+ */
177
+ isTurnStart(message: Message): boolean;
178
+ };
179
+ /**
180
+ * Hook-based turn observation — the SOLE owner of this agent's hook
181
+ * vocabulary (which events to wire, the settings shape, the marker format).
182
+ * The agent emits turn markers to a claudemux-owned local rendezvous; the
183
+ * Observer reads them as deterministic phase edges (no pane-scraping). The
184
+ * spawn layer injects {@link spec}'s flag at launch; the Observer parses each
185
+ * rendezvous line with {@link parseMarker}. Both live here so the
186
+ * hook-event strings stay out of the agent-agnostic layers (grep-enforced).
187
+ *
188
+ * Optional by **graceful degrade**, not by incompleteness: injection (the
189
+ * spawn layer) and the Observer have landed and use this whenever it's present
190
+ * (the reliable observe channel). An agent that omits it — or `create({ hooks:
191
+ * false })` — degrades honestly to the best-effort pane fallback
192
+ * (`hookChannelHealthy: false`), never silently.
193
+ */
194
+ readonly hooks?: {
195
+ /**
196
+ * The launch flag that wires this agent's turn hooks to append markers to
197
+ * `rendezvousPath`. Inspectable (transparency) and the one place that knows
198
+ * the settings shape. e.g. `{ flag: "--settings", value: "<json>" }`.
199
+ */
200
+ spec(o: {
201
+ rendezvousPath: string;
202
+ }): {
203
+ flag: string;
204
+ value: string;
205
+ };
206
+ /** Parse one rendezvous marker line into a neutral {@link HookEdge}, or null. */
207
+ parseMarker(line: string): HookEdge | null;
208
+ };
209
+ }
210
+ /**
211
+ * A neutral, deterministic turn-lifecycle edge derived from an agent hook —
212
+ * the reliable observe signal (vs pane-scraping). `event` is backend-neutral;
213
+ * the Observer composes a sequence of these into phase / toolInFlight / done.
214
+ *
215
+ * Beyond the bare edge, a hook payload also carries data the Observer fuses with
216
+ * the transcript and pane. Those fields are surfaced here as **neutral** concepts
217
+ * (`transcriptPath`, `source`, `finalMessage`); the agent's `parseMarker` owns
218
+ * translating its vendor payload (e.g. claude's `transcript_path` /
219
+ * `last_assistant_message`) into them, so vendor field names never leak past the
220
+ * agent seam (grep-enforced). All are optional — present only on the edges that
221
+ * carry them, and only for agents whose hooks expose them.
222
+ */
223
+ export interface HookEdge {
224
+ readonly event: "session-start" | "prompt-submit" | "tool-start" | "tool-end" | "stop" | "notification" | "pre-compact" | "other";
225
+ /** Epoch milliseconds the hook fired. */
226
+ readonly at: number;
227
+ /** The agent session id the hook reported, when present. */
228
+ readonly sessionId?: string;
229
+ /** Tool name for `tool-start`/`tool-end` edges, when present. */
230
+ readonly tool?: string;
231
+ /**
232
+ * The **authoritative** absolute path of the session's durable transcript, as
233
+ * the hook reported it. The Observer prefers this over recomputing/globbing
234
+ * the on-disk location (the path rule is fragile). Present on most edges.
235
+ */
236
+ readonly transcriptPath?: string;
237
+ /**
238
+ * Why the session started — only on a `session-start` edge. Lets the Observer
239
+ * distinguish a fresh boot from a resume (expect a history re-print) or a
240
+ * post-compaction continuation, without inspecting the screen.
241
+ */
242
+ readonly source?: "start" | "resume" | "clear" | "compact";
243
+ /**
244
+ * A preview of the turn's terminal assistant text, as the agent reported it on
245
+ * the `stop` edge — available *before* the transcript flushes that record.
246
+ * The Observer uses it to close the hook→transcript flush skew: on `stop` it
247
+ * polls the transcript until the durable reply is present, rather than reading
248
+ * stale content the instant the edge fires.
249
+ */
250
+ readonly finalMessage?: string;
251
+ }
252
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/agents/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEzD;;;;;;;;;GASG;AACH,MAAM,WAAW,UAAU;IACzB,yDAAyD;IACzD,EAAE,EAAE,MAAM,CAAC;IACX,+DAA+D;IAC/D,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IACnC;;;OAGG;IACH,OAAO,EACH;QAAE,IAAI,EAAE,KAAK,CAAC;QAAC,GAAG,EAAE,OAAO,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;KAAE,GACrD;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,eAAe,CAAA;KAAE,CAAC;IACnD;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE;QACL,sEAAsE;QACtE,MAAM,EAAE,gBAAgB,CAAC;QACzB,+DAA+D;QAC/D,UAAU,EAAE,oBAAoB,CAAC;KAClC,CAAC;CACH;AAED;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACvB,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,SAAS,CAAC,CAAC,EAAE;QACX,GAAG,EAAE,MAAM,CAAC;QACZ,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAC5B;;;;;;WAMG;QACH,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,GAAG;QACF,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,EAAE,CAAC;QACf,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IAEF,gCAAgC;IAChC,QAAQ,CAAC,IAAI,EAAE;QACb,mEAAmE;QACnE,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;QAC5C;;;;;WAKG;QACH,OAAO,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC;KAC3C,CAAC;IAEF,4CAA4C;IAC5C,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;IAEhC;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE;QAC1B,4EAA4E;QAC5E,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;KACnD,CAAC;IAEF;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE;QACpB;;;;WAIG;QACH,MAAM,CAAC,CAAC,EAAE;YAAE,cAAc,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,GAAG,MAAM,GAAG,IAAI,CAAC;QACpE;;;WAGG;QACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;QACxC;;;;;;;;;;WAUG;QACH,SAAS,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC;QACnE;;;WAGG;QACH,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC;KACxC,CAAC;IAEF;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE;QACf;;;;WAIG;QACH,IAAI,CAAC,CAAC,EAAE;YAAE,cAAc,EAAE,MAAM,CAAA;SAAE,GAAG;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QACrE,iFAAiF;QACjF,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAAC;KAC5C,CAAC;CACH;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EACV,eAAe,GACf,eAAe,GACf,YAAY,GACZ,UAAU,GACV,MAAM,GACN,cAAc,GACd,aAAa,GACb,OAAO,CAAC;IACZ,yCAAyC;IACzC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;IAC3D;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/agents/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,25 @@
1
+ import { type TmuxExec } from "./exec.js";
2
+ /**
3
+ * Capture the named session's pane text.
4
+ *
5
+ * Always uses `capture-pane -p` (the live visible region — proven unaffected
6
+ * by copy-mode and attached clients; see
7
+ * `docs/decisions/0003-capture-pane-invocation.md`). Bottom-N
8
+ * slicing happens **in code** via `output.split('\n').slice(-N)`, NEVER
9
+ * `capture-pane -S -N` — the latter returns `N + pane_height` lines, not
10
+ * bottom-N (see `engineer/wiki/tmux-capture-pane-the-bottom-n-trap`).
11
+ *
12
+ * `-e` flag is added only when the caller passes `ansi: true`; the default
13
+ * is plain text (which is what the classifier matches against anyway).
14
+ *
15
+ * A gone session surfaces as `SessionGone` via {@link runForSession} (the
16
+ * canonical per-session mapping). The substrate runs `remain-on-exit off`, so a
17
+ * dead pane is reaped rather than left annotated — there is no separate
18
+ * pane-dead path to handle.
19
+ */
20
+ export declare function capturePane(exec: TmuxExec, target: string, opts?: {
21
+ ansi?: boolean;
22
+ lines?: number;
23
+ label?: string;
24
+ }): Promise<string>;
25
+ //# sourceMappingURL=capture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../../src/backends/tmux/capture.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAiB,MAAM,WAAW,CAAC;AAEzD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,MAAM,EACd,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5D,OAAO,CAAC,MAAM,CAAC,CAejB"}
@@ -0,0 +1,35 @@
1
+ import { runForSession } from "./exec.js";
2
+ /**
3
+ * Capture the named session's pane text.
4
+ *
5
+ * Always uses `capture-pane -p` (the live visible region — proven unaffected
6
+ * by copy-mode and attached clients; see
7
+ * `docs/decisions/0003-capture-pane-invocation.md`). Bottom-N
8
+ * slicing happens **in code** via `output.split('\n').slice(-N)`, NEVER
9
+ * `capture-pane -S -N` — the latter returns `N + pane_height` lines, not
10
+ * bottom-N (see `engineer/wiki/tmux-capture-pane-the-bottom-n-trap`).
11
+ *
12
+ * `-e` flag is added only when the caller passes `ansi: true`; the default
13
+ * is plain text (which is what the classifier matches against anyway).
14
+ *
15
+ * A gone session surfaces as `SessionGone` via {@link runForSession} (the
16
+ * canonical per-session mapping). The substrate runs `remain-on-exit off`, so a
17
+ * dead pane is reaped rather than left annotated — there is no separate
18
+ * pane-dead path to handle.
19
+ */
20
+ export async function capturePane(exec, target, opts = {}) {
21
+ const label = opts.label ?? target;
22
+ const args = ["capture-pane", "-p"];
23
+ if (opts.ansi === true)
24
+ args.push("-e");
25
+ args.push("-t", target);
26
+ // Per-session read: a dead server means THIS session is gone (canonical
27
+ // SessionGone), matching the write path — not a divergent BackendUnreachable.
28
+ const r = await runForSession(exec, args, label);
29
+ if (opts.lines !== undefined && opts.lines > 0) {
30
+ const lines = r.stdout.split("\n");
31
+ return lines.slice(-opts.lines).join("\n");
32
+ }
33
+ return r.stdout;
34
+ }
35
+ //# sourceMappingURL=capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.js","sourceRoot":"","sources":["../../../src/backends/tmux/capture.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,aAAa,EAAE,MAAM,WAAW,CAAC;AAEzD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAc,EACd,MAAc,EACd,OAA2D,EAAE;IAE7D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC;IACnC,MAAM,IAAI,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;IACpC,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI;QAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAExB,wEAAwE;IACxE,8EAA8E;IAC9E,MAAM,CAAC,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAEjD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;QAC/C,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,CAAC,CAAC,MAAM,CAAC;AAClB,CAAC"}
@@ -0,0 +1,105 @@
1
+ import type { BackendEvent } from "../types.js";
2
+ /**
3
+ * tmux stderr patterns that indicate "the backend's server is not reachable
4
+ * on this socket." Older tmux says `no server running on /tmp/.../sock`;
5
+ * tmux ≥3.3 says `error connecting to /tmp/.../sock (No such file or
6
+ * directory)`. We treat both as the same condition (server's gone).
7
+ *
8
+ * Single-source — callers (`sessions.ts`, `classifyTmuxFailure`) import
9
+ * from here so the regex can't drift.
10
+ */
11
+ export declare function isNoServerStderr(text: string): boolean;
12
+ export declare function isSessionGoneStderr(text: string): boolean;
13
+ /**
14
+ * tmux stderr shape when `new-session -s <name>` races another process
15
+ * creating the same target. With the shared default socket (ADR 0006),
16
+ * concurrent `spawn`s of the same name are routine, and the TOCTOU window
17
+ * between `create()`'s exists-check and `backend.spawn()` means tmux —
18
+ * not the substrate's check — sometimes discovers the collision. The
19
+ * semantically-correct typed error is `SessionExists`, same as a check-time
20
+ * collision: the substrate refuses to silently adopt either way.
21
+ */
22
+ export declare function isDuplicateSessionStderr(text: string): boolean;
23
+ /**
24
+ * Raw result of one tmux invocation. Callers (sessions.ts, keys.ts,
25
+ * capture.ts) interpret this — typed errors are thrown at the caller, not
26
+ * here, because only the caller knows the session-name context for the error.
27
+ */
28
+ export interface TmuxResult {
29
+ readonly exit: number;
30
+ readonly stdout: string;
31
+ readonly stderr: string;
32
+ readonly durationMs: number;
33
+ }
34
+ /**
35
+ * One executor instance per backend. Holds the private socket name and the
36
+ * observability emitter. Every tmux invocation goes through {@link run}; that
37
+ * is what enforces the `-f /dev/null` + `-L <socket>` discipline tree-wide.
38
+ *
39
+ * See `engineer/wiki/tmux-private-server-bootstrap` — bare `-L` does not
40
+ * prevent `~/.tmux.conf` reads; both flags are required.
41
+ */
42
+ export declare class TmuxExec {
43
+ #private;
44
+ readonly socket: string;
45
+ constructor(socket: string);
46
+ /** Subscribe to every tmux invocation + result. Returns unsubscribe. */
47
+ onCommand(handler: (e: BackendEvent) => void): () => void;
48
+ /**
49
+ * Spawn `tmux -L <socket> -f /dev/null <args...>` and capture both streams —
50
+ * stdout for the op's payload (e.g. `capture-pane`), stderr for failure
51
+ * classification (`can't find …` → `SessionGone`, etc.).
52
+ *
53
+ * @throws `BackendUnreachable` — `spawn-failed` on spawn error (ENOENT,
54
+ * EPIPE), `no-server` when the server isn't running on a connect-only
55
+ * operation, `timeout` when the invocation doesn't return within
56
+ * `opts.timeoutMs` (default {@link DEFAULT_EXEC_TIMEOUT_MS}). The
57
+ * session-name field on the typed error is the *requested* session.
58
+ */
59
+ run(args: string[], opts?: {
60
+ sessionName?: string;
61
+ input?: string;
62
+ timeoutMs?: number;
63
+ }): Promise<TmuxResult>;
64
+ }
65
+ /**
66
+ * Classify a non-zero `TmuxResult` into a typed error. Callers use this when
67
+ * the operation was expected to succeed; for idempotent ops (kill), callers
68
+ * inspect `result.exit` + stderr directly and choose to swallow SessionGone.
69
+ *
70
+ * Returns `null` if the result looks successful (exit 0 with no error
71
+ * annotation on stdout), otherwise the right typed error to throw.
72
+ *
73
+ * `sessionName` is the requested target (so the error carries the right
74
+ * context even when tmux's stderr names something else).
75
+ *
76
+ * Order matters: `BackendUnreachable` (no server) > `SessionExists`
77
+ * (duplicate-session race) > `SessionGone` (target doesn't exist) >
78
+ * `BackendError` (unrecognized failure). The routine failure modes are
79
+ * promoted above `BackendError` so the substrate surfaces a clean typed
80
+ * error rather than the catch-all. As a structural backstop,
81
+ * `BackendError.message` itself does not embed the tmux argv (see
82
+ * `errors.ts`), so even an unclassified shape cannot leak the backend's
83
+ * vocabulary into user-facing text.
84
+ */
85
+ export declare function classifyTmuxFailure(sessionName: string, argv: readonly string[], result: TmuxResult): Error | null;
86
+ /** Is this the `no-server` {@link BackendUnreachable} (empty/down server)? */
87
+ export declare function isNoServer(err: unknown): boolean;
88
+ /**
89
+ * Run a tmux op that targets ONE named session, applying the **canonical
90
+ * per-session failure mapping** — the single place reads and writes share so the
91
+ * same crash can't surface as two different errors again (the read/write drift).
92
+ *
93
+ * From a single session's vantage, a `no-server` failure means *this session is
94
+ * gone* → {@link SessionGone}, NOT a distinct backend fault. (Registry ops —
95
+ * `list`/`exists` — keep treating no-server as plain absence; `BackendUnreachable`
96
+ * is left to mean a *real* backend fault: `spawn-failed` / `timeout`.) The
97
+ * canonical answer holds whether no-server arrives as a rejection (the common
98
+ * path) or as a returned non-zero result.
99
+ *
100
+ * Returns the raw {@link TmuxResult} on success; throws the typed error otherwise.
101
+ */
102
+ export declare function runForSession(exec: TmuxExec, args: readonly string[], label: string, opts?: {
103
+ input?: string;
104
+ }): Promise<TmuxResult>;
105
+ //# sourceMappingURL=exec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../../src/backends/tmux/exec.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD;AAaD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGzD;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAaD;;;;;;;GAOG;AACH,qBAAa,QAAQ;;IACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAGZ,MAAM,EAAE,MAAM;IAI1B,wEAAwE;IACxE,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,GAAG,MAAM,IAAI;IAIzD;;;;;;;;;;OAUG;IACG,GAAG,CACP,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GACtE,OAAO,CAAC,UAAU,CAAC;CAiFvB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,MAAM,EAAE,UAAU,GACjB,KAAK,GAAG,IAAI,CAqBd;AAED,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAEhD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,KAAK,EAAE,MAAM,EACb,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5B,OAAO,CAAC,UAAU,CAAC,CAcrB"}