aicodeman 1.12.2 → 1.13.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 (104) hide show
  1. package/README.md +67 -13
  2. package/dist/config/agent-wait.d.ts +73 -0
  3. package/dist/config/agent-wait.d.ts.map +1 -0
  4. package/dist/config/agent-wait.js +99 -0
  5. package/dist/config/agent-wait.js.map +1 -0
  6. package/dist/hooks-config.d.ts +4 -2
  7. package/dist/hooks-config.d.ts.map +1 -1
  8. package/dist/hooks-config.js +14 -4
  9. package/dist/hooks-config.js.map +1 -1
  10. package/dist/session-cli-builder.d.ts.map +1 -1
  11. package/dist/session-cli-builder.js +6 -2
  12. package/dist/session-cli-builder.js.map +1 -1
  13. package/dist/tmux-manager.d.ts.map +1 -1
  14. package/dist/tmux-manager.js +5 -1
  15. package/dist/tmux-manager.js.map +1 -1
  16. package/dist/web/public/admin-ui.js.gz +0 -0
  17. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  18. package/dist/web/public/{app.2e9ffd31.js → app.b24f795e.js} +5 -7
  19. package/dist/web/public/app.b24f795e.js.br +0 -0
  20. package/dist/web/public/app.b24f795e.js.gz +0 -0
  21. package/dist/web/public/constants.e7175b95.js.gz +0 -0
  22. package/dist/web/public/cron-ui.js.gz +0 -0
  23. package/dist/web/public/entrance-animations.js.gz +0 -0
  24. package/dist/web/public/i18n.da34cfaf.js +1 -0
  25. package/dist/web/public/i18n.da34cfaf.js.br +0 -0
  26. package/dist/web/public/i18n.da34cfaf.js.gz +0 -0
  27. package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
  28. package/dist/web/public/index.html +12 -5
  29. package/dist/web/public/index.html.br +0 -0
  30. package/dist/web/public/index.html.gz +0 -0
  31. package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
  32. package/dist/web/public/keyboard-accessory.9936227d.js.gz +0 -0
  33. package/dist/web/public/mobile-handlers.32fdd57f.js.gz +0 -0
  34. package/dist/web/public/mobile-overview.js.gz +0 -0
  35. package/dist/web/public/mobile.32cacde5.css.gz +0 -0
  36. package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
  37. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  38. package/dist/web/public/panels-ui.1d4203e0.js.gz +0 -0
  39. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  40. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  41. package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
  42. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  43. package/dist/web/public/session-ui.1356a4f4.js.gz +0 -0
  44. package/dist/web/public/settings-ui.ea63d5db.js +67 -0
  45. package/dist/web/public/settings-ui.ea63d5db.js.br +0 -0
  46. package/dist/web/public/settings-ui.ea63d5db.js.gz +0 -0
  47. package/dist/web/public/styles.b10e5edd.css +1 -0
  48. package/dist/web/public/styles.b10e5edd.css.br +0 -0
  49. package/dist/web/public/styles.b10e5edd.css.gz +0 -0
  50. package/dist/web/public/subagent-windows.4cc8b005.js.gz +0 -0
  51. package/dist/web/public/sw.js.gz +0 -0
  52. package/dist/web/public/terminal-ui.78c71f3a.js.gz +0 -0
  53. package/dist/web/public/ultracode-panel.js.gz +0 -0
  54. package/dist/web/public/ultracode-windows.js.gz +0 -0
  55. package/dist/web/public/upload.html.gz +0 -0
  56. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  57. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  58. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  59. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  60. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  61. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  62. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  63. package/dist/web/public/vendor/xterm.css.gz +0 -0
  64. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  65. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  66. package/dist/web/public/webview-tabs.js +1 -2
  67. package/dist/web/public/webview-tabs.js.br +0 -0
  68. package/dist/web/public/webview-tabs.js.gz +0 -0
  69. package/dist/web/routes/hook-event-routes.d.ts.map +1 -1
  70. package/dist/web/routes/hook-event-routes.js +22 -0
  71. package/dist/web/routes/hook-event-routes.js.map +1 -1
  72. package/dist/web/routes/session-routes.d.ts +4 -0
  73. package/dist/web/routes/session-routes.d.ts.map +1 -1
  74. package/dist/web/routes/session-routes.js +499 -17
  75. package/dist/web/routes/session-routes.js.map +1 -1
  76. package/dist/web/schemas.d.ts +53 -0
  77. package/dist/web/schemas.d.ts.map +1 -1
  78. package/dist/web/schemas.js +59 -0
  79. package/dist/web/schemas.js.map +1 -1
  80. package/dist/web/server.d.ts.map +1 -1
  81. package/dist/web/server.js +14 -0
  82. package/dist/web/server.js.map +1 -1
  83. package/dist/web/session-listener-wiring.d.ts.map +1 -1
  84. package/dist/web/session-listener-wiring.js +28 -0
  85. package/dist/web/session-listener-wiring.js.map +1 -1
  86. package/dist/web/session-wait-registry.d.ts +407 -0
  87. package/dist/web/session-wait-registry.d.ts.map +1 -0
  88. package/dist/web/session-wait-registry.js +895 -0
  89. package/dist/web/session-wait-registry.js.map +1 -0
  90. package/package.json +2 -1
  91. package/skills/codeman/SKILL.md +274 -0
  92. package/skills/codeman/reference/endpoints.md +214 -0
  93. package/skills/codeman/reference/recipes.md +249 -0
  94. package/dist/web/public/app.2e9ffd31.js.br +0 -0
  95. package/dist/web/public/app.2e9ffd31.js.gz +0 -0
  96. package/dist/web/public/i18n.0fbea500.js +0 -1
  97. package/dist/web/public/i18n.0fbea500.js.br +0 -0
  98. package/dist/web/public/i18n.0fbea500.js.gz +0 -0
  99. package/dist/web/public/settings-ui.01a1ae7d.js +0 -67
  100. package/dist/web/public/settings-ui.01a1ae7d.js.br +0 -0
  101. package/dist/web/public/settings-ui.01a1ae7d.js.gz +0 -0
  102. package/dist/web/public/styles.50cd52bd.css +0 -1
  103. package/dist/web/public/styles.50cd52bd.css.br +0 -0
  104. package/dist/web/public/styles.50cd52bd.css.gz +0 -0
@@ -0,0 +1,407 @@
1
+ /**
2
+ * @fileoverview Blocking-wait registry backing the agent wait primitives.
3
+ *
4
+ * Codeman's only "tell me when" channel today is SSE, which an agent driving the
5
+ * API from a shell tool cannot practically consume (it would have to hold a
6
+ * streaming connection and parse events inline). This registry is the piece that
7
+ * lets a request *block* until something happens instead, so an orchestrating
8
+ * agent can `curl` and wait. Plan: `docs/agent-control-plan.md`.
9
+ *
10
+ * Two waiter kinds:
11
+ * - **signal waiters** resolve on the first of a requested set of lifecycle
12
+ * signals (`idle`, `working`, `stop`, `blocked`, `exit`).
13
+ * - **output waiters** resolve when a literal string appears in a session's
14
+ * ANSI-stripped output.
15
+ *
16
+ * ## What "ANSI-stripped output" means here
17
+ *
18
+ * `normalizeForMatch()` defines it, and it is a superset of `stripAnsi()`: that helper
19
+ * knows three escape families and leaves the rest, which is enough for `ESC ( B` from a
20
+ * stock bash prompt to break `match=tnode:` on a prompt that reads `tnode:`. Everything
21
+ * downstream derives from one call to it, so the carry, the haystack and the snippet
22
+ * window are all the same text. The returned `snippet` is then RENDERED from that window
23
+ * (control bytes removed, blank runs collapsed) and so is not a byte-for-byte quotation
24
+ * of what was matched — see `findMatch`.
25
+ *
26
+ * ## Signal provenance (why `stop` is the good one)
27
+ *
28
+ * `idle` / `working` / `exit` come from `Session`'s own events (heuristic: output
29
+ * stabilization plus prompt detection), so `idle` can flap mid-turn when a spinner
30
+ * pauses. `stop` and `blocked` come from Claude Code hooks (`POST /api/hook-event`)
31
+ * and are definitive. Only `claude` mode installs those hooks, so `stop` and
32
+ * `blocked` never fire for anything else: callers must be rejected up front rather
33
+ * than left to hit the timeout. `hooksAvailableForMode()` keeps that rule in one
34
+ * place, and it is keyed on the MODE rather than on `isExternalCliMode()` because
35
+ * `shell` is not an external CLI yet installs no hooks either.
36
+ *
37
+ * ## Ordering contract for the wiring
38
+ *
39
+ * A session that exits must `notifySignal(id, 'exit')` BEFORE `cancelAll(id)`, so
40
+ * a caller waiting on `exit` gets `signal: 'exit'` rather than `ended: true`.
41
+ *
42
+ * ## Lifetime discipline (24h sessions)
43
+ *
44
+ * Every waiter owns exactly one timer, cleared on resolve, and waiter sets are
45
+ * deleted when they empty. Session teardown must call `cancelAll()` and shutdown
46
+ * `stop()`: timers are deliberately NOT unref'd, because an unref'd timer can let
47
+ * the process exit mid-wait and strand the HTTP response.
48
+ *
49
+ * Two things keep a slot from outliving its caller:
50
+ * - `abortSignal`, so a client that hangs up frees its waiter immediately instead of
51
+ * holding one for the rest of its timeout. Routes wire it to `req.raw.on('close')`.
52
+ * - the latched stopped flag, so a request that lands in the window between shutdown
53
+ * starting and the HTTP server actually closing cannot register a fresh waiter that
54
+ * nothing will ever cancel.
55
+ *
56
+ * This module holds no IO and no `Session` reference, which is what keeps it
57
+ * unit-testable in isolation.
58
+ */
59
+ import type { SessionMode, SessionStatus } from '../types.js';
60
+ /** A lifecycle signal a caller can block on. */
61
+ export type WaitSignal = 'idle' | 'working' | 'stop' | 'blocked' | 'exit';
62
+ /** Every valid signal, in documentation order. */
63
+ export declare const WAIT_SIGNALS: readonly WaitSignal[];
64
+ /**
65
+ * Applied when a caller omits `until`. `stop` first because it is the definitive
66
+ * end-of-turn signal, `idle` as the fallback for sessions that emit no hooks, and
67
+ * `exit` so a worker that CRASHES resolves the wait promptly instead of burning
68
+ * the caller's whole timeout on something that can no longer happen.
69
+ */
70
+ export declare const DEFAULT_WAIT_SIGNALS: readonly WaitSignal[];
71
+ /** Result of parsing a caller-supplied `until` value. */
72
+ export interface ParsedWaitSignals {
73
+ /** Valid signals, deduped, in the order given. */
74
+ signals: WaitSignal[];
75
+ /** Tokens that are not signals. Non-empty means the caller should get a 400. */
76
+ invalid: string[];
77
+ }
78
+ /**
79
+ * Parse an `until` query value: a comma-separated string, an array of strings, or
80
+ * absent. Invalid tokens are REPORTED rather than dropped: silently falling back
81
+ * to the default would leave an agent believing it is waiting for `stop` when a
82
+ * typo means it is waiting for something else entirely.
83
+ *
84
+ * @param raw - `"stop,idle"`, `["stop","idle"]`, or undefined
85
+ * @returns valid signals plus any unrecognized tokens
86
+ */
87
+ export declare function parseWaitSignals(raw: unknown): ParsedWaitSignals;
88
+ /**
89
+ * The signal a session is *already* emitting, given its status, so a wait can
90
+ * resolve immediately instead of hanging until something changes.
91
+ *
92
+ * ⚠️ **What this CANNOT tell you: whether the session is alive.** It sees only the
93
+ * four-value `SessionStatus` enum, and that enum does not distinguish a finished
94
+ * worker from a dead one. Both PTY `onExit` handlers in `session.ts` (Claude
95
+ * interactive and shell) park the session at `'idle'`, and a PTY exit does not remove
96
+ * the session from the map, so a crashed worker reports `'idle'` indefinitely and this
97
+ * function will answer `'idle'` for it forever. `'stopped'` is set only on a spawn
98
+ * failure and by `stop(killMux:true)` (which deletes the session moments later), so in
99
+ * practice the `'stopped' → 'exit'` arm almost never fires for a session that merely
100
+ * died.
101
+ *
102
+ * Read the return value as "what state is it in", never as "is it still running". A
103
+ * caller that must tell a completed turn from a corpse has to consult liveness itself
104
+ * (`session.pid`, which is null after a PTY exit) and cannot get it from here. The
105
+ * live `exit` signal is still delivered by `notifySignal(id, 'exit')` at the moment
106
+ * the PTY dies; this function is only about the state a LATER caller finds.
107
+ *
108
+ * `stopped` and `error` both map to `exit` because both mean "not running", so a
109
+ * caller that does catch one of them does not block. `blocked` is deliberately
110
+ * underivable here, it exists only as a hook event until it becomes a real
111
+ * `SessionStatus` (see the deferred Part 3 in the plan).
112
+ */
113
+ export declare function signalForStatus(status: SessionStatus): WaitSignal | null;
114
+ /**
115
+ * Whether a session in this mode ever POSTs Codeman hook events, and therefore
116
+ * whether `stop` / `blocked` can ever fire for it.
117
+ *
118
+ * True for `claude` and nothing else. The tempting predicate is
119
+ * `!isExternalCliMode(mode)`, and it is WRONG: that helper covers only
120
+ * opencode/codex/gemini/antigravity, so `shell` falls through it — and a shell session
121
+ * is a plain bash PTY with no Claude Code and no hooks installed. `until=stop` on one
122
+ * was accepted and then blocked for the caller's whole timeout, which is precisely the
123
+ * infinite-wait-dressed-as-a-timeout this guard exists to prevent.
124
+ */
125
+ export declare function hooksAvailableForMode(mode: SessionMode): boolean;
126
+ /** Outcome of resolving a caller-supplied wait target against a session's mode. */
127
+ export interface ResolvedWaitSignals {
128
+ /** Signals to actually wait on. Empty when `error` is set. */
129
+ until: WaitSignal[];
130
+ /** Caller-facing 400 message, or null when the request is usable. */
131
+ error: string | null;
132
+ }
133
+ /**
134
+ * Turn a raw `until` / `wait` value into the set to wait on, applying both rules
135
+ * every wait endpoint shares:
136
+ *
137
+ * 1. An unknown token is an ERROR, never a silent fallback to the default.
138
+ * 2. Hook-only signals are rejected when asked for EXPLICITLY on a mode that emits
139
+ * no hooks, but merely dropped from the DEFAULT set: omitting the parameter must
140
+ * never 400.
141
+ *
142
+ * Shared by `GET .../wait` and the `wait` field on `POST .../input` so the two can
143
+ * not drift; the second-guessing that produces is worse than the duplication.
144
+ *
145
+ * @param raw - the caller's value (comma string, array, `true` for "the default")
146
+ * @param options - `mode` decides whether the hook-only signals are available, and
147
+ * names the mode in the error message so the caller can see why
148
+ */
149
+ export declare function resolveWaitSignals(raw: unknown, options: {
150
+ mode: SessionMode;
151
+ }): ResolvedWaitSignals;
152
+ /** Fields every wait result carries, whichever kind it is. */
153
+ interface WaitResultBase {
154
+ /** True when the wait hit its timeout. */
155
+ timedOut: boolean;
156
+ /** True when the answer came from state already present at call time. */
157
+ immediate: boolean;
158
+ /** True when the session went away (deleted / torn down) before the wait resolved. */
159
+ ended: boolean;
160
+ /**
161
+ * True when the caller's `abortSignal` fired: the client hung up, so nobody is
162
+ * reading this result. `ended` is set alongside it, since the wait did end without
163
+ * an answer. False on every other path.
164
+ */
165
+ aborted: boolean;
166
+ /** Wall-clock ms spent waiting (0 for an immediate resolve). */
167
+ waitedMs: number;
168
+ /**
169
+ * The timeout actually applied, after clamping. Echoed because a caller that asked
170
+ * for 30 minutes and silently got 600s otherwise reads the timeout as a stalled
171
+ * worker and kills a session that was working fine.
172
+ */
173
+ timeoutMs: number;
174
+ }
175
+ /** Outcome of a signal wait. A timeout is a normal outcome, never an error. */
176
+ export interface SignalWaitResult extends WaitResultBase {
177
+ /** The signal that fired, or null if the wait ended without one. */
178
+ signal: WaitSignal | null;
179
+ }
180
+ /** Outcome of an output wait. */
181
+ export interface OutputWaitResult extends WaitResultBase {
182
+ matched: boolean;
183
+ /** Bounded window of output around the match, or null if nothing matched. */
184
+ snippet: string | null;
185
+ }
186
+ /**
187
+ * Thrown when a waiter would exceed a configured cap.
188
+ *
189
+ * `scope` is what the route needs to answer honestly: `'session'` is the caller's own
190
+ * session being oversubscribed (409 SESSION_BUSY), while `'owner'` and `'total'` are
191
+ * caps the caller may have no part in and cannot fix by switching sessions, so those
192
+ * map to 429 RATE_LIMITED. Reporting a process-wide cap as SESSION_BUSY tells the
193
+ * caller the wrong session is at fault.
194
+ */
195
+ export declare class WaitCapacityError extends Error {
196
+ /** Which cap was hit, so the route can say something useful. */
197
+ readonly scope: 'session' | 'owner' | 'total';
198
+ readonly name = "WaitCapacityError";
199
+ constructor(message: string,
200
+ /** Which cap was hit, so the route can say something useful. */
201
+ scope: 'session' | 'owner' | 'total');
202
+ }
203
+ /** Options shared by both wait kinds. */
204
+ interface WaitOptionsBase {
205
+ /** Timeout in ms. Callers pass the CLAMPED value; it is echoed back in the result. */
206
+ timeoutMs: number;
207
+ /**
208
+ * Owner to charge this waiter to (multi-user mode). Omit in single-user mode: the
209
+ * per-owner cap applies only when this is set, so leaving it undefined behaves
210
+ * exactly as if the cap did not exist.
211
+ */
212
+ owner?: string;
213
+ /**
214
+ * Fires when the caller goes away (routes wire this to `req.raw.on('close')`).
215
+ * The waiter is removed and its timer cleared at once, resolving `ended: true,
216
+ * aborted: true`. Freeing the slot is the entire point: the response can no longer
217
+ * be sent, so holding one for the rest of the timeout only denies the pool to
218
+ * someone else. An already-aborted signal registers no waiter at all.
219
+ */
220
+ abortSignal?: AbortSignal;
221
+ }
222
+ export interface SignalWaitOptions extends WaitOptionsBase {
223
+ /** Signals to wait for; the first to fire wins. An empty set can only time out. */
224
+ until: readonly WaitSignal[];
225
+ /**
226
+ * When true, ignore `currentSignal` and require an actual transition. This is
227
+ * the `fresh=1` behavior: "tell me about the NEXT one", not "is it already so".
228
+ */
229
+ requireTransition?: boolean;
230
+ /** The session's current signal (from `signalForStatus`), if known. */
231
+ currentSignal?: WaitSignal | null;
232
+ }
233
+ export interface OutputWaitOptions extends WaitOptionsBase {
234
+ /** Literal substring to look for. No regex: see the note on `waitForOutput`. */
235
+ match: string;
236
+ /** Case-insensitive compare. */
237
+ nocase?: boolean;
238
+ /**
239
+ * Existing buffered output to scan before waiting (the `from=buffer` mode).
240
+ * Callers should pass a BOUNDED slice: a session's text buffer can be tens of
241
+ * megabytes and this is scanned synchronously. `assertCapacity()` runs BEFORE this
242
+ * is touched, so a request that cannot get a slot never pays for the scan.
243
+ */
244
+ initialText?: string;
245
+ }
246
+ export interface SessionWaitRegistryOptions {
247
+ maxWaitersPerSession?: number;
248
+ maxWaitersPerOwner?: number;
249
+ maxWaitersTotal?: number;
250
+ }
251
+ /**
252
+ * Holds pending waiters keyed by session id.
253
+ *
254
+ * One instance is shared process-wide (`sessionWaits` below); tests construct
255
+ * their own so no state leaks between cases.
256
+ */
257
+ export declare class SessionWaitRegistry {
258
+ private readonly signalWaiters;
259
+ private readonly outputWaiters;
260
+ /**
261
+ * Live waiter count per owner. A derived count would mean walking every waiter in
262
+ * the process on each registration, so it is maintained incrementally instead:
263
+ * incremented once at registration, decremented once in the remove* helpers, which
264
+ * are the only paths that take a waiter out of a set.
265
+ */
266
+ private readonly ownerCounts;
267
+ /**
268
+ * Trailing bytes of a PTY chunk that look like the START of an ANSI sequence whose
269
+ * terminator has not arrived yet, held back until the next chunk completes it. Keyed
270
+ * by session because every output waiter on a session sees the same stream, which
271
+ * keeps `notifyOutput` at ONE `stripAnsi` per chunk rather than one per waiter.
272
+ * Deleted with the session's waiter set.
273
+ */
274
+ private readonly pendingAnsi;
275
+ private readonly maxPerSession;
276
+ private readonly maxPerOwner;
277
+ private readonly maxTotal;
278
+ /** Latched by `stop()` / `cancelEverything()`. Never cleared: shutdown is one-way. */
279
+ private stopped;
280
+ constructor(options?: SessionWaitRegistryOptions);
281
+ /** Pending signal waiters for a session. */
282
+ signalWaiterCount(sessionId: string): number;
283
+ /** Pending output waiters for a session. */
284
+ outputWaiterCount(sessionId: string): number;
285
+ /** Pending waiters of both kinds for a session. */
286
+ waiterCount(sessionId: string): number;
287
+ /** Pending waiters of both kinds across every session. */
288
+ totalWaiterCount(): number;
289
+ /** Pending waiters of both kinds charged to one owner. */
290
+ ownerWaiterCount(owner: string): number;
291
+ /** True once `stop()` / `cancelEverything()` has run: no new waiter can register. */
292
+ get isStopped(): boolean;
293
+ /**
294
+ * Throw if registering one more waiter would exceed a cap.
295
+ *
296
+ * PUBLIC so a route can check BEFORE doing expensive setup work. `/wait-output`'s
297
+ * `from=buffer` mode is the case that matters: reading `session.terminalBuffer`
298
+ * joins the whole 32MB accumulator, and paying that for a request that is about to
299
+ * be refused turns the cap into an amplifier instead of a protection. Both
300
+ * `waitForSignal` and `waitForOutput` call it themselves too, so a caller that skips
301
+ * the pre-check is still bounded.
302
+ *
303
+ * @throws {WaitCapacityError} naming the cap that was hit in `scope`.
304
+ */
305
+ assertCapacity(sessionId: string, owner?: string): void;
306
+ private chargeOwner;
307
+ private releaseOwner;
308
+ /**
309
+ * Wire a caller's abort signal to `onAbort`, returning the detach function.
310
+ * Detaching matters even though request signals are short-lived: without it a
311
+ * caller reusing one controller across several waits accumulates listeners on it.
312
+ */
313
+ private attachAbort;
314
+ /**
315
+ * Block until one of `until` fires, the timeout elapses, or the session ends.
316
+ *
317
+ * Resolves immediately (`immediate: true`) when the session's `currentSignal` is
318
+ * already in `until` and `requireTransition` is not set.
319
+ *
320
+ * @throws {WaitCapacityError} when a waiter cap is already reached.
321
+ */
322
+ waitForSignal(sessionId: string, options: SignalWaitOptions): Promise<SignalWaitResult>;
323
+ /**
324
+ * Deliver a lifecycle signal. Every waiter for this session that asked for it
325
+ * resolves; the rest keep waiting.
326
+ *
327
+ * @returns how many waiters were resolved (0 is the overwhelmingly common case,
328
+ * which is why this is safe to call from the session event hot path).
329
+ */
330
+ notifySignal(sessionId: string, signal: WaitSignal): number;
331
+ private removeSignalWaiter;
332
+ /**
333
+ * Block until `match` appears in the session's output.
334
+ *
335
+ * **Literal matching only, deliberately.** `search-service.ts` avoids regex so
336
+ * there is no ReDoS surface, and this endpoint is more exposed still: the
337
+ * pattern is caller-supplied and the input is a live stream. herdr can offer
338
+ * `--regex` because Rust's regex crate is linear-time with no backtracking;
339
+ * JavaScript's `RegExp` backtracks.
340
+ *
341
+ * ⚠️ "New output" is not the same as "output produced after you asked". tmux
342
+ * REPAINTS the visible screen (on attach, resize, or a TUI redraw), and a repaint
343
+ * arrives as ordinary `terminal` data, so text that was already on screen can
344
+ * match a `from=now` wait. Observed live: a marker echoed a minute earlier matched
345
+ * instantly on a fresh wait. Callers must use a marker unique to the call
346
+ * (`echo DONE_$RANDOM`), not a generic one like `BUILD OK`.
347
+ *
348
+ * @throws {WaitCapacityError} when a waiter cap is already reached.
349
+ */
350
+ waitForOutput(sessionId: string, options: OutputWaitOptions): Promise<OutputWaitResult>;
351
+ /**
352
+ * Feed a raw PTY chunk to this session's output waiters.
353
+ *
354
+ * Called from the always-attached `terminal` listener, so it sits on the PTY hot
355
+ * path: it must stay a single Map lookup when nobody is waiting, which is why the
356
+ * no-waiter check comes before the ANSI strip.
357
+ *
358
+ * ⚠️ A PTY read boundary can fall INSIDE an escape sequence, which is routine under
359
+ * tmux (it emits SGR runs constantly). `stripAnsi` needs a complete sequence, so a
360
+ * split one used to survive the strip, land in the carry, and split the needle: the
361
+ * same text matched or not depending on where the kernel happened to cut the read.
362
+ * The incomplete tail is therefore held back in `pendingAnsi` and stripped together
363
+ * with the next chunk. That is the same class of bug the carry buffer exists to
364
+ * solve, one level down.
365
+ *
366
+ * @returns how many waiters matched.
367
+ */
368
+ notifyOutput(sessionId: string, chunk: string): number;
369
+ private removeOutputWaiter;
370
+ /**
371
+ * Resolve every waiter for a session with `ended: true`.
372
+ *
373
+ * Call on session deletion and PTY teardown. A session that EXITS should
374
+ * `notifySignal(id, 'exit')` first, so an `until=exit` caller sees the signal
375
+ * rather than a bare `ended`.
376
+ *
377
+ * @returns how many waiters were resolved.
378
+ */
379
+ cancelAll(sessionId: string): number;
380
+ /**
381
+ * Resolve every waiter in the process and refuse every later one.
382
+ *
383
+ * Shutdown must call this: waiter timers are not unref'd, so a pending 10-minute
384
+ * wait would otherwise hold the event loop open and strand its HTTP response.
385
+ *
386
+ * ⚠️ The latch is the point, not just the sweep. `server.stop()` cancels waiters
387
+ * well before `app.close()`, with several awaits in between (orchestrator teardown,
388
+ * scheduled-run stops that each await a session stop and a tmux kill), and the
389
+ * listener is still accepting requests throughout. A `GET .../wait?timeout=600000`
390
+ * landing in that window used to register a fresh waiter that nothing would ever
391
+ * cancel, stalling shutdown for up to `MAX_WAIT_MS` — exactly the outcome cancelling
392
+ * was meant to prevent. Once latched, a wait resolves at once with `ended: true`.
393
+ */
394
+ stop(): number;
395
+ /**
396
+ * Resolve every waiter in the process. Latches the stopped flag, so this is
397
+ * shutdown-shaped; `stop()` is the same call under the name that says so.
398
+ */
399
+ cancelEverything(): number;
400
+ }
401
+ /**
402
+ * Process-wide registry used by the routes and the session/hook wiring.
403
+ * Tests should construct their own `SessionWaitRegistry` instead of touching this.
404
+ */
405
+ export declare const sessionWaits: SessionWaitRegistry;
406
+ export {};
407
+ //# sourceMappingURL=session-wait-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-wait-registry.d.ts","sourceRoot":"","sources":["../../src/web/session-wait-registry.ts"],"names":[],"mappings":"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"}