@orkestrel/terminal 0.0.1 → 0.0.2

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.
@@ -1,8 +1,10 @@
1
+ import { DriverInterface } from '@orkestrel/database';
1
2
  import { EmitterErrorHandler } from '@orkestrel/emitter';
2
3
  import { EmitterHooks } from '@orkestrel/emitter';
3
4
  import { EmitterInterface } from '@orkestrel/emitter';
4
5
  import { Guard } from '@orkestrel/contract';
5
6
  import { StylerInterface } from '@orkestrel/console';
7
+ import { TableInterface } from '@orkestrel/database';
6
8
 
7
9
  /** The `Accept` header value that opens the broker's SSE stream. */
8
10
  export declare const ACCEPT_EVENT_STREAM = "text/event-stream";
@@ -10,6 +12,18 @@ export declare const ACCEPT_EVENT_STREAM = "text/event-stream";
10
12
  /** Matches an alphanumeric string (letters and digits only). The `alphanumeric` rule tests against this. */
11
13
  export declare const ALPHANUMERIC_PATTERN: RegExp;
12
14
 
15
+ /** The rejection reason a bare {@link PromptInterface.answer} returns — `'unknown'` (no such parked prompt) or `'rejected'` (failed validation / type-check). */
16
+ export declare type AnswerError = 'unknown' | 'rejected';
17
+
18
+ /** The outcome of a bare {@link PromptInterface.answer} call — the accepted `value` on success, else the {@link AnswerError}. */
19
+ export declare type AnswerResult = {
20
+ readonly success: true;
21
+ readonly value: unknown;
22
+ } | {
23
+ readonly success: false;
24
+ readonly error: AnswerError;
25
+ };
26
+
13
27
  /**
14
28
  * Append a rule-backed {@link Validator} to `validators` when the rule is enabled — a `false` /
15
29
  * `undefined` `check` is skipped, a function `check` is added verbatim (the custom override), and
@@ -143,12 +157,44 @@ export declare function createCheckboxState(options: CheckboxOptions): CheckboxS
143
157
  /** Build the initial {@link ConfirmState} from {@link ConfirmOptions} — defaulting the answer to `false`. */
144
158
  export declare function createConfirmState(options: ConfirmOptions): ConfirmState;
145
159
 
160
+ /**
161
+ * Create a {@link TerminalStoreInterface} backed by one table of the `databases` layer — the
162
+ * driver-pluggable twin of {@link createMemoryTerminalStore}, storing each endpoint's config
163
+ * snapshot as one opaque JSON column.
164
+ *
165
+ * @param driver - The {@link DriverInterface} backing the table (default an in-memory driver)
166
+ * @returns A {@link TerminalStoreInterface}
167
+ *
168
+ * @example
169
+ * ```ts
170
+ * import { createDatabaseTerminalStore } from '@src/core'
171
+ *
172
+ * const store = createDatabaseTerminalStore() // in-memory by default
173
+ * ```
174
+ */
175
+ export declare function createDatabaseTerminalStore(driver?: DriverInterface): TerminalStoreInterface;
176
+
146
177
  /** Build the initial {@link EditorState} from {@link EditorOptions} — resolving the validator + styler, seeding empty lines. */
147
178
  export declare function createEditorState(options: EditorOptions): EditorState;
148
179
 
149
180
  /** Build the initial {@link InputState} from {@link InputOptions} — resolving the validator + styler, seeding an empty value. */
150
181
  export declare function createInputState(options: InputOptions): InputState;
151
182
 
183
+ /**
184
+ * Create the in-memory {@link TerminalStoreInterface} — a process-lifetime `Map` of endpoint
185
+ * config snapshots, the default store backing a {@link TerminalManagerInterface}'s `open` / `save`.
186
+ *
187
+ * @returns A {@link TerminalStoreInterface}
188
+ *
189
+ * @example
190
+ * ```ts
191
+ * import { createMemoryTerminalStore } from '@src/core'
192
+ *
193
+ * const store = createMemoryTerminalStore()
194
+ * ```
195
+ */
196
+ export declare function createMemoryTerminalStore(): TerminalStoreInterface;
197
+
152
198
  /** Build the initial {@link PasswordState} from {@link PasswordOptions} — resolving the validator + styler + mask. */
153
199
  export declare function createPasswordState(options: PasswordOptions): PasswordState;
154
200
 
@@ -210,6 +256,24 @@ export declare function createPromptClient(options: PromptClientOptions): Prompt
210
256
  /** Build the initial {@link SelectState} from {@link SelectOptions} — normalizing choices and pre-focusing the default. */
211
257
  export declare function createSelectState(options: SelectOptions): SelectState;
212
258
 
259
+ /**
260
+ * Create the multi-endpoint {@link TerminalManagerInterface} — a named registry of
261
+ * {@link PromptInterface} brokers so several parties can `ask` prompts of each other by name,
262
+ * with a transitive DEADLOCK check across every in-flight ask.
263
+ *
264
+ * @param options - See {@link TerminalManagerOptions}
265
+ * @returns A {@link TerminalManagerInterface}
266
+ *
267
+ * @example
268
+ * ```ts
269
+ * import { createTerminalManager } from '@src/core'
270
+ *
271
+ * const manager = createTerminalManager()
272
+ * manager.add('agent')
273
+ * ```
274
+ */
275
+ export declare function createTerminalManager(options?: TerminalManagerOptions): TerminalManagerInterface;
276
+
213
277
  /** Ctrl+A (SOH, U+0001) — move to start of line. */
214
278
  export declare const CTRL_A: string;
215
279
 
@@ -225,6 +289,65 @@ export declare const CTRL_E: string;
225
289
  /** Ctrl+U (NAK, U+0015) — clear the current line. */
226
290
  export declare const CTRL_U: string;
227
291
 
292
+ /**
293
+ * A {@link TerminalStoreInterface} backed by one table of the `databases` layer — an endpoint's
294
+ * durable CONFIG state IS a row, so persistence reduces to keyed point-access (`get` / `set` /
295
+ * `delete`) over a {@link TableInterface}, the driver-pluggable twin of the plain-`Map`
296
+ * {@link import('./MemoryTerminalStore.js').MemoryTerminalStore}.
297
+ *
298
+ * @remarks
299
+ * The store is driver-agnostic: it holds a single {@link TableInterface} whose backend (memory,
300
+ * JSON, SQLite, IndexedDB) is chosen by whoever builds it (the factories), so a JSON / SQLite /
301
+ * IndexedDB backend swaps in WITHOUT touching the manager — the same seam as
302
+ * {@link import('./MemoryTerminalStore.js').MemoryTerminalStore}. The driver defaults to memory
303
+ * ({@link import('./factories.js').createDatabaseTerminalStore} passes `createMemoryDriver()`), so
304
+ * it ALSO works in memory out of the box; you opt into the durable plumbing by passing a JSON /
305
+ * SQLite / IndexedDB driver.
306
+ *
307
+ * The {@link TerminalSnapshot} is stored as ONE OPAQUE JSON COLUMN — the table is a row of
308
+ * `{ id; snapshot }` ({@link TerminalSnapshotRow}). The snapshot is already a COMPLETE,
309
+ * self-contained, pure-JSON CONFIG payload (no live broker state), so storing it whole is lossless
310
+ * AND keeps the row type flat (`snapshot` reads back as `unknown`).
311
+ *
312
+ * - **`set(snapshot)` upserts under the snapshot's OWN `id`** (no separate id param) — it writes
313
+ * the row `{ id: snapshot.id, snapshot }`.
314
+ * - **`get(id)` resolves the stored snapshot for an id**, narrowing the opaque JSON column back to
315
+ * a {@link TerminalSnapshot} ({@link import('./helpers.js').isTerminalSnapshot} — the AGENTS §14
316
+ * boundary narrow for an untrusted storage read), or `undefined` if none is stored.
317
+ * - **`delete(id)` drops a snapshot by id**; an absent id is a no-op (no throw).
318
+ *
319
+ * There is NO idle-TTL / eviction — a persisted config lives until an explicit `delete`. The public
320
+ * surface is EXACTLY `get` / `set` / `delete` — no extra members (the §22 method bijection with
321
+ * {@link TerminalStoreInterface}). Hydration stays a caller concern: `open` always restores an EMPTY
322
+ * broker — parked Promises are process-bound and never resurrected.
323
+ *
324
+ * @example
325
+ * ```ts
326
+ * import { createDatabaseTerminalStore, createMemoryDriver } from '@src/core'
327
+ *
328
+ * const store = createDatabaseTerminalStore(createMemoryDriver()) // a durable driver swaps in here
329
+ * await store.set({ id: 'shell', timeout: 5000 }) // persist the config (one JSON column)
330
+ * const snapshot = await store.get('shell')
331
+ * await store.delete('shell') // drop it
332
+ * ```
333
+ */
334
+ export declare class DatabaseTerminalStore implements TerminalStoreInterface {
335
+ #private;
336
+ /**
337
+ * Wrap a table as a terminal store.
338
+ *
339
+ * @param table - The {@link TableInterface} holding the snapshots — its row is the
340
+ * {@link TerminalSnapshotRow} `{ id; snapshot }` shape (the snapshot one opaque JSON column)
341
+ */
342
+ constructor(table: TableInterface<TerminalSnapshotRow>);
343
+ /** Resolve the persisted snapshot for `id`, narrowing the opaque JSON column back to a `TerminalSnapshot`. */
344
+ get(id: string): Promise<TerminalSnapshot | undefined>;
345
+ /** Insert or replace under the snapshot's OWN `id` (no separate id param) — the row is `{ id, snapshot }`. */
346
+ set(snapshot: TerminalSnapshot): Promise<void>;
347
+ /** Drop a snapshot by id; an absent id is a no-op (no throw). */
348
+ delete(id: string): Promise<void>;
349
+ }
350
+
228
351
  /** The default mask glyph a {@link import('./types.js').PasswordState} renders each input character as — `*`. */
229
352
  export declare const DEFAULT_MASK = "*";
230
353
 
@@ -411,6 +534,12 @@ export declare const INTEGER_PATTERN: RegExp;
411
534
  */
412
535
  export declare function isAbortError(error: unknown): boolean;
413
536
 
537
+ /** Narrow an unknown wire payload to an answer POST body — a non-empty `id` string plus a `value` key (of any shape) present. */
538
+ export declare function isAnswerPayload(value: unknown): value is {
539
+ readonly id: string;
540
+ readonly value: unknown;
541
+ };
542
+
414
543
  /** Narrow an unknown value to a {@link CheckboxChoice} — the `recordOf` shape inlined so no non-exported member lingers (§5). */
415
544
  export declare function isCheckboxChoice(value: unknown): value is CheckboxChoice;
416
545
 
@@ -470,6 +599,9 @@ export declare const isPromptType: Guard<PromptType>;
470
599
  */
471
600
  export declare function isTerminalError(value: unknown): value is TerminalError;
472
601
 
602
+ /** Narrow an unknown value to a {@link TerminalSnapshot} — a non-empty `id` plus an optional numeric `timeout`. */
603
+ export declare const isTerminalSnapshot: Guard<TerminalSnapshot>;
604
+
473
605
  /**
474
606
  * The Control Sequence Introducer lead (`ESC[`) for the navigation keys — the prefix of the
475
607
  * arrow / home / end / delete sequences {@link SEQUENCE_NAMES} is keyed by. Named `KEY_CSI`
@@ -508,6 +640,45 @@ export declare interface KeyEvent {
508
640
  readonly shift: boolean;
509
641
  }
510
642
 
643
+ /**
644
+ * The in-memory {@link TerminalStoreInterface} — a process-lifetime `Map` of
645
+ * {@link TerminalSnapshot}s keyed by endpoint id, the DEFAULT store
646
+ * {@link import('./factories.js').createMemoryTerminalStore} builds. The EXACT twin of
647
+ * {@link import('./DatabaseTerminalStore.js').DatabaseTerminalStore}.
648
+ *
649
+ * @remarks
650
+ * A plain `Map<string, TerminalSnapshot>` (AGENTS §21 — the snapshot is already pure, self-contained
651
+ * CONFIG-only JSON, so no encoding is needed for the memory tier). There is NO idle-TTL and NO
652
+ * eviction: a persisted config lives until an explicit `delete`. A durable backend (JSON / SQLite /
653
+ * IndexedDB) swaps in through the SAME interface without touching the manager — its
654
+ * driver-pluggable twin is {@link import('./DatabaseTerminalStore.js').DatabaseTerminalStore} (the
655
+ * snapshot as one opaque JSON column).
656
+ *
657
+ * - **`get` resolves the persisted snapshot for an id**, or `undefined` if none is stored.
658
+ * - **`set` inserts / replaces under the snapshot's OWN `id`** (no separate id param).
659
+ * - **`delete` drops a snapshot by id**; an absent id is a no-op (no throw).
660
+ *
661
+ * The public surface is EXACTLY `get` / `set` / `delete` — no extra members (the §22 method
662
+ * bijection with {@link TerminalStoreInterface}). Hydration is a caller concern: `open` always
663
+ * restores an EMPTY broker — parked Promises are process-bound and never resurrected.
664
+ *
665
+ * @example
666
+ * ```ts
667
+ * import { createMemoryTerminalStore } from '@src/core'
668
+ *
669
+ * const store = createMemoryTerminalStore()
670
+ * await store.set({ id: 'shell', timeout: 5000 }) // persist a config
671
+ * const snapshot = await store.get('shell')
672
+ * await store.delete('shell') // drop it
673
+ * ```
674
+ */
675
+ export declare class MemoryTerminalStore implements TerminalStoreInterface {
676
+ #private;
677
+ get(id: string): Promise<TerminalSnapshot | undefined>;
678
+ set(snapshot: TerminalSnapshot): Promise<void>;
679
+ delete(id: string): Promise<void>;
680
+ }
681
+
511
682
  /** Line feed (`\n`, U+000A) — Enter on some terminals / pasted input. */
512
683
  export declare const NEWLINE: string;
513
684
 
@@ -535,6 +706,23 @@ export declare interface Parked {
535
706
  readonly cancel: TimerCancel;
536
707
  }
537
708
 
709
+ /**
710
+ * The request to {@link PromptInterface.park} a prompt directly — the general form the six
711
+ * `PromptFormInterface` methods each wrap via a private `gateFor(form, options)` dispatch.
712
+ *
713
+ * @remarks
714
+ * - `form` — which {@link PromptType} to park.
715
+ * - `options` — that form's options bag ({@link PromptFormOptions}).
716
+ * - `from` / `to` — set ONLY by a {@link TerminalManagerInterface} (the attribution edge); a
717
+ * direct broker caller leaves both `undefined`.
718
+ */
719
+ export declare interface ParkRequest {
720
+ readonly form: PromptType;
721
+ readonly options: PromptFormOptions;
722
+ readonly from?: string;
723
+ readonly to?: string;
724
+ }
725
+
538
726
  /**
539
727
  * Decode one keypress's bytes into a {@link KeyEvent} — total, never throws. A `Uint8Array` is
540
728
  * read as UTF-8; the resulting string is matched against the known control bytes
@@ -631,6 +819,9 @@ export declare function passwordView(state: PasswordState): string;
631
819
  * from the rules via {@link resolveValidation}.
632
820
  * - `status` — the current {@link PendingPromptStatus}.
633
821
  * - `time` — the creation timestamp (ms since epoch).
822
+ * - `from` / `to` — the OPTIONAL attribution edge a {@link TerminalManagerInterface} stamps on a
823
+ * parked prompt (which endpoint asked, which endpoint must answer); absent for a bare broker
824
+ * {@link PromptInterface} used directly (no manager attribution).
634
825
  */
635
826
  export declare interface PendingPrompt {
636
827
  readonly id: string;
@@ -639,6 +830,8 @@ export declare interface PendingPrompt {
639
830
  readonly options: Readonly<Record<string, unknown>>;
640
831
  readonly status: PendingPromptStatus;
641
832
  readonly time: number;
833
+ readonly from?: string;
834
+ readonly to?: string;
642
835
  }
643
836
 
644
837
  /**
@@ -693,7 +886,8 @@ export declare class Prompt implements PromptInterface {
693
886
  get count(): number;
694
887
  pending(): readonly PendingPrompt[];
695
888
  pending(id: string): PendingPrompt | undefined;
696
- answer(id: string, value: unknown): boolean;
889
+ answer(id: string, value: unknown): AnswerResult;
890
+ park(request: ParkRequest): Ticket;
697
891
  input(options: InputOptions): Promise<string>;
698
892
  password(options: PasswordOptions): Promise<string>;
699
893
  confirm(options: ConfirmOptions): Promise<boolean>;
@@ -749,8 +943,10 @@ export declare interface PromptChoice {
749
943
  * never an `as`), dispatched to `terminal`, and its resolved value POSTed back to `url`.
750
944
  * Dispatch is strictly SERIAL: the read loop drives the terminal for ONE prompt at a time and
751
945
  * only reads/dispatches the next event after the current prompt fully settles (its answer
752
- * POSTed). A prompt id redelivered by the broker AFTER its prior dispatch has settled is
753
- * dispatched again — the client does not dedupe across completion.
946
+ * POSTed). A prompt id redelivered (e.g. after a reconnect) WHILE its prior dispatch is still
947
+ * in flight is skipped (a `#seen` set tracks in-flight ids); once that dispatch settles (or the
948
+ * broker sends `expire` for the id) the id is dropped from `#seen`, so a later redelivery of the
949
+ * SAME id after completion is dispatched again — the client does not dedupe across completion.
754
950
  * - **Server signals.** An `expire` event (the broker dropped a parked prompt) emits `expire`; a
755
951
  * `shutdown` event calls {@link disconnect} (not {@link destroy}) — the client stops streaming
756
952
  * without auto-reconnect but stays reusable; a later {@link connect} recovers it.
@@ -890,6 +1086,12 @@ export declare interface PromptFormInterface {
890
1086
  editor(options: EditorOptions): Promise<string>;
891
1087
  }
892
1088
 
1089
+ /**
1090
+ * The union of every prompt form's options bag — the `options` a {@link ParkRequest} carries,
1091
+ * narrowed at the call site by the paired {@link PromptType}.
1092
+ */
1093
+ export declare type PromptFormOptions = InputOptions | PasswordOptions | ConfirmOptions | SelectOptions | CheckboxOptions | EditorOptions;
1094
+
893
1095
  /** The styled prompt-message header (`? message`) — the leading line every active prompt view shares. */
894
1096
  export declare function promptHeader(styler: StylerInterface, message: string): string;
895
1097
 
@@ -902,10 +1104,11 @@ export declare function promptHeader(styler: StylerInterface, message: string):
902
1104
  *
903
1105
  * @remarks
904
1106
  * - **Park-as-Promise.** Each `input` / `password` / … call mints an id, parks a
905
- * {@link PendingPrompt}, emits `pending`, and returns an unresolved Promise.
1107
+ * {@link PendingPrompt}, emits `pending`, and returns an unresolved Promise. {@link park} is the
1108
+ * general entry point the six form methods wrap.
906
1109
  * - **Answer validates.** {@link answer} validates `value` against the prompt's resolved
907
1110
  * validator AND type-checks it to the prompt form before accepting; a bad answer is rejected
908
- * (returns `false`, the prompt stays `pending`).
1111
+ * (an {@link AnswerResult} failure, the prompt stays `pending`).
909
1112
  * - **Timeout → expire → reject.** An unanswered prompt expires after `timeout` ms — `expire`
910
1113
  * fires and the parked Promise rejects (a {@link import('./errors.js').TerminalError}). The
911
1114
  * timer is injectable for deterministic tests.
@@ -914,9 +1117,10 @@ export declare function promptHeader(styler: StylerInterface, message: string):
914
1117
  export declare interface PromptInterface extends PromptFormInterface {
915
1118
  readonly emitter: EmitterInterface<PromptEventMap>;
916
1119
  readonly count: number;
1120
+ park(request: ParkRequest): Ticket;
917
1121
  pending(): readonly PendingPrompt[];
918
1122
  pending(id: string): PendingPrompt | undefined;
919
- answer(id: string, value: unknown): boolean;
1123
+ answer(id: string, value: unknown): AnswerResult;
920
1124
  destroy(): void;
921
1125
  }
922
1126
 
@@ -930,12 +1134,17 @@ export declare interface PromptInterface extends PromptFormInterface {
930
1134
  * {@link import('./constants.js').DEFAULT_PROMPT_TIMEOUT_MS}).
931
1135
  * - `timer` — the injected {@link TimerHandler} (default the host `setTimeout`); supply a
932
1136
  * deterministic timer to drive expiry in tests without real time.
1137
+ * - `cap` — the maximum number of prompts this broker will hold PARKED at once (default
1138
+ * UNBOUNDED — no cap unless supplied). Once `count` reaches `cap`, a new park is rejected with
1139
+ * a {@link import('./errors.js').TerminalError} (`code: 'LIMIT'`) WITHOUT parking, minting an
1140
+ * id, emitting `pending`, or arming the expiry timer — the runaway-asker memory ceiling.
933
1141
  */
934
1142
  export declare interface PromptOptions {
935
1143
  readonly on?: EmitterHooks<PromptEventMap>;
936
1144
  readonly error?: EmitterErrorHandler;
937
1145
  readonly timeout?: number;
938
1146
  readonly timer?: TimerHandler;
1147
+ readonly cap?: number;
939
1148
  }
940
1149
 
941
1150
  /**
@@ -980,6 +1189,13 @@ export declare interface PromptStep<T, S> {
980
1189
  */
981
1190
  export declare type PromptType = 'input' | 'password' | 'confirm' | 'select' | 'checkbox' | 'editor';
982
1191
 
1192
+ /**
1193
+ * The union of a resolved prompt's value shapes — a `string` (`input` / `password` / `select` /
1194
+ * `editor`), a `boolean` (`confirm`), or a `readonly string[]` (`checkbox`, the checked values in
1195
+ * choice order). The type a {@link Ticket}'s `value` Promise resolves to.
1196
+ */
1197
+ export declare type PromptValue = string | boolean | readonly string[];
1198
+
983
1199
  /**
984
1200
  * Rebuild a wire-decoded `validate` payload into a {@link ValidationRules} bag — the inverse of
985
1201
  * {@link serializeValidationRules}. Keeps only the primitive rule values (`boolean` / `number` /
@@ -1112,6 +1328,12 @@ export declare const SEQUENCE_NAMES: Readonly<Record<string, string>>;
1112
1328
  /** Strip functions from a `choices` option — each choice keeps its plain fields; a bare string passes through. */
1113
1329
  export declare function serializeChoices(choices: unknown): readonly unknown[];
1114
1330
 
1331
+ /** Serialize a parked prompt's expiry into a {@link WireEvent} — event `'expire'`, `data` the JSON-stringified `{ id }` payload. */
1332
+ export declare function serializeExpire(id: string): WireEvent;
1333
+
1334
+ /** Serialize a parked {@link PendingPrompt} into a {@link WireEvent} — event `'pending'`, `data` the JSON-stringified prompt, `id` the prompt's own id (an SSE bridge sets its frame `id:` from this). */
1335
+ export declare function serializePending(prompt: PendingPrompt): WireEvent;
1336
+
1115
1337
  /**
1116
1338
  * Produce the WIRE-SAFE form of a prompt's options — the {@link PendingPrompt.options} a broker
1117
1339
  * serializes over SSE. Drops everything that cannot cross the wire (a `styler`, and any
@@ -1135,6 +1357,9 @@ export declare function serializeChoices(choices: unknown): readonly unknown[];
1135
1357
  */
1136
1358
  export declare function serializePromptOptions(options: object): Readonly<Record<string, unknown>>;
1137
1359
 
1360
+ /** The {@link WireEvent} a broker/manager sends when it is going away — event `'shutdown'`, no payload. */
1361
+ export declare function serializeShutdown(): WireEvent;
1362
+
1138
1363
  /**
1139
1364
  * Flatten a `validate` option to its wire-safe {@link ValidationRules} DATA — a function rule
1140
1365
  * becomes `true` (its built-in check survives; its body cannot cross the wire). A bare-function
@@ -1174,6 +1399,18 @@ export declare function submitHeader(styler: StylerInterface, message: string):
1174
1399
  /** Tab (`\t`, U+0009). */
1175
1400
  export declare const TAB: string;
1176
1401
 
1402
+ /** The rejection reason a {@link TerminalManagerInterface.answer} call returns — an {@link AnswerError}, plus `'terminal'` (no such endpoint). */
1403
+ export declare type TerminalAnswerError = AnswerError | 'terminal';
1404
+
1405
+ /** The outcome of a {@link TerminalManagerInterface.answer} call — the accepted `value` on success, else the {@link TerminalAnswerError}. */
1406
+ export declare type TerminalAnswerResult = {
1407
+ readonly success: true;
1408
+ readonly value: unknown;
1409
+ } | {
1410
+ readonly success: false;
1411
+ readonly error: TerminalAnswerError;
1412
+ };
1413
+
1177
1414
  /**
1178
1415
  * An error a {@link import('./Prompt.js').Prompt} broker rejects a parked prompt's Promise with.
1179
1416
  *
@@ -1201,8 +1438,181 @@ export declare class TerminalError extends Error {
1201
1438
  * `destroy`ed while it was still pending); the prompt's Promise rejects with this.
1202
1439
  * - `CANCEL` — the user aborted an interactive prompt (ctrl-c) at the server `Terminal` (T-c)
1203
1440
  * driver; the awaited prompt call rejects with this so a caller can branch on `error.code`.
1441
+ * - `LIMIT` — a broker's optional `cap` on concurrently-parked prompts was already reached; the
1442
+ * new call is rejected WITHOUT parking (never counted, never emitted, no timer armed).
1443
+ * - `DESTROYED` — a call reached an already-`destroy`ed {@link TerminalManagerInterface} (`add`
1444
+ * at entry, or `open` whose `store` read resolved after a `destroy` happened in the gap).
1445
+ */
1446
+ export declare type TerminalErrorCode = 'EXPIRE' | 'CANCEL' | 'DRIVER' | 'DEADLOCK' | 'TARGET' | 'LIMIT' | 'DESTROYED';
1447
+
1448
+ /**
1449
+ * The multi-endpoint terminal MANAGER — a registry of named {@link PromptInterface} brokers (one
1450
+ * per endpoint), so several parties can `ask` prompts of each other by NAME with a `from` → `to`
1451
+ * attribution edge on every parked prompt, and a transitive DEADLOCK check across all in-flight
1452
+ * asks.
1453
+ *
1454
+ * @remarks
1455
+ * - **Registry.** `add(name, options?)` mints (or, if `name` is already mounted, returns the
1456
+ * EXISTING broker UNCHANGED — idempotent, never clobbers a live/parked endpoint). Every mounted
1457
+ * broker's `pending` / `answer` / `expire` events are re-emitted on the manager, attributed by
1458
+ * `name`.
1459
+ * - **`ask`.** The target must already be mounted via {@link add} — `ask` never auto-adds it;
1460
+ * rejects `TARGET` for an unknown `to` (listing the known names). Rejects `DEADLOCK` when parking
1461
+ * `from → to` would close a cycle over the CURRENT in-flight edge set (walked transitively);
1462
+ * otherwise parks through the target's broker and resolves with the ORIGINAL ticket Promise (edge
1463
+ * cleanup is attached via `.then`, never altering the value/rejection the caller observes).
1464
+ * - **Durable open / save.** `open(name)` restores an EMPTY broker from the `store` (parked
1465
+ * Promises are process-bound and never resurrected); `save(name)` persists the endpoint's
1466
+ * configured `timeout`.
1467
+ * - **Removal.** `remove` drops one endpoint or a batch (§9.2, array overload FIRST), destroying
1468
+ * each broker (which expires every prompt still parked on it). `clear` drops all; `destroy` is
1469
+ * idempotent.
1470
+ *
1471
+ * @example
1472
+ * ```ts
1473
+ * const manager = new TerminalManager()
1474
+ * manager.add('agent')
1475
+ * const name = manager.ask('user', 'agent', 'input', { message: 'Your name' })
1476
+ * manager.answer('agent', manager.pending('agent')[0].id, 'Ada')
1477
+ * await name // 'Ada'
1478
+ * ```
1479
+ */
1480
+ export declare class TerminalManager implements TerminalManagerInterface {
1481
+ #private;
1482
+ constructor(options?: TerminalManagerOptions);
1483
+ get emitter(): EmitterInterface<TerminalManagerEventMap>;
1484
+ get count(): number;
1485
+ terminal(name: string): PromptInterface | undefined;
1486
+ terminals(): readonly string[];
1487
+ add(name: string, options?: TerminalOptions): PromptInterface;
1488
+ ask(from: string, to: string, form: 'input' | 'password' | 'editor', options: InputOptions | PasswordOptions | EditorOptions): Promise<string>;
1489
+ ask(from: string, to: string, form: 'confirm', options: ConfirmOptions): Promise<boolean>;
1490
+ ask(from: string, to: string, form: 'select', options: SelectOptions): Promise<string>;
1491
+ ask(from: string, to: string, form: 'checkbox', options: CheckboxOptions): Promise<readonly string[]>;
1492
+ pending(): readonly PendingPrompt[];
1493
+ pending(to: string): readonly PendingPrompt[];
1494
+ answer(to: string, id: string, value: unknown): TerminalAnswerResult;
1495
+ open(name: string): Promise<PromptInterface | undefined>;
1496
+ save(name: string): Promise<boolean>;
1497
+ remove(names: readonly string[]): boolean;
1498
+ remove(name: string): boolean;
1499
+ clear(): void;
1500
+ destroy(): void;
1501
+ }
1502
+
1503
+ /**
1504
+ * The manager's event map (AGENTS §13) — the name-attributed re-emission of every mounted
1505
+ * broker's events, so a caller subscribes once for ALL endpoints instead of per-broker.
1506
+ *
1507
+ * @remarks
1508
+ * - `pending` — an endpoint parked a prompt (carries the {@link PendingPrompt}, itself carrying
1509
+ * `from` / `to`).
1510
+ * - `answer` — an endpoint's parked prompt was answered (`to` names the endpoint).
1511
+ * - `expire` — an endpoint's parked prompt timed out (`to` names the endpoint).
1512
+ */
1513
+ export declare type TerminalManagerEventMap = {
1514
+ pending: [prompt: PendingPrompt];
1515
+ answer: [to: string, id: string, value: unknown];
1516
+ expire: [to: string, id: string];
1517
+ };
1518
+
1519
+ /**
1520
+ * The multi-endpoint terminal MANAGER (§9.1/§9.2) — a registry of named {@link PromptInterface}
1521
+ * brokers (one per endpoint), so several parties (agents, tools, humans) can `ask` prompts of
1522
+ * each other by NAME, attributed with a `from` → `to` edge on every parked {@link PendingPrompt}.
1523
+ *
1524
+ * @remarks
1525
+ * - **Accessors (§9.1).** `terminal(name)` looks up one endpoint's broker; `terminals()` lists
1526
+ * every mounted endpoint name.
1527
+ * - **`add`** mints (or returns the existing) broker for `name` — idempotent, never clobbers a
1528
+ * live endpoint.
1529
+ * - **`ask`** is the attributed convenience: parks a prompt from `from` to `to` (auto-`add`ing
1530
+ * `to` if absent) and resolves with the typed value, precisely overloaded per {@link PromptType}.
1531
+ * - **`pending()`** lists every endpoint's parked prompts; `pending(to)` scopes to one endpoint.
1532
+ * - **`answer`** routes to the named endpoint's broker.
1533
+ * - **`open`** restores (or returns the live) broker for `name` from the `store`.
1534
+ * - **`save`** persists an endpoint's config snapshot to the `store` (`false` when there is no
1535
+ * store, or `name` is unknown).
1536
+ * - **Batch `remove` (§9.2).** The array overload is declared FIRST — `remove(names)` removes each
1537
+ * listed endpoint (`true` when any of the named terminals was removed); `remove(name)` removes one.
1538
+ * - **`clear`** removes every endpoint without destroying the manager; **`destroy`** tears down
1539
+ * every broker, then the manager's own emitter.
1540
+ */
1541
+ export declare interface TerminalManagerInterface {
1542
+ readonly emitter: EmitterInterface<TerminalManagerEventMap>;
1543
+ readonly count: number;
1544
+ terminal(name: string): PromptInterface | undefined;
1545
+ terminals(): readonly string[];
1546
+ add(name: string, options?: TerminalOptions): PromptInterface;
1547
+ ask(from: string, to: string, form: 'input' | 'password' | 'editor', options: InputOptions | PasswordOptions | EditorOptions): Promise<string>;
1548
+ ask(from: string, to: string, form: 'confirm', options: ConfirmOptions): Promise<boolean>;
1549
+ ask(from: string, to: string, form: 'select', options: SelectOptions): Promise<string>;
1550
+ ask(from: string, to: string, form: 'checkbox', options: CheckboxOptions): Promise<readonly string[]>;
1551
+ pending(): readonly PendingPrompt[];
1552
+ pending(to: string): readonly PendingPrompt[];
1553
+ answer(to: string, id: string, value: unknown): TerminalAnswerResult;
1554
+ open(name: string): Promise<PromptInterface | undefined>;
1555
+ save(name: string): Promise<boolean>;
1556
+ remove(names: readonly string[]): boolean;
1557
+ remove(name: string): boolean;
1558
+ clear(): void;
1559
+ destroy(): void;
1560
+ }
1561
+
1562
+ /**
1563
+ * Options for {@link import('./factories.js').createTerminalManager} / the
1564
+ * {@link TerminalManagerInterface}.
1565
+ *
1566
+ * @remarks
1567
+ * - `store` — the optional {@link TerminalStoreInterface} backing `open` / `save`.
1568
+ * - `timeout` / `timer` / `cap` — the manager-wide default for each endpoint's broker (overridable
1569
+ * per {@link TerminalManagerInterface.add} call via {@link TerminalOptions}, exactly like
1570
+ * `timeout` / `timer`).
1571
+ * - `on` / `error` — the manager's {@link EmitterHooks} + {@link EmitterErrorHandler} (AGENTS §13).
1204
1572
  */
1205
- export declare type TerminalErrorCode = 'EXPIRE' | 'CANCEL';
1573
+ export declare interface TerminalManagerOptions {
1574
+ readonly store?: TerminalStoreInterface;
1575
+ readonly timeout?: number;
1576
+ readonly timer?: TimerHandler;
1577
+ readonly cap?: number;
1578
+ readonly on?: EmitterHooks<TerminalManagerEventMap>;
1579
+ readonly error?: EmitterErrorHandler;
1580
+ }
1581
+
1582
+ /** Options for {@link import('./factories.js').createTerminal} / a manager-owned {@link PromptInterface} broker — the per-endpoint `timeout` + injected `timer` + optional `cap` on concurrently-parked prompts. */
1583
+ export declare interface TerminalOptions {
1584
+ readonly timeout?: number;
1585
+ readonly timer?: TimerHandler;
1586
+ readonly cap?: number;
1587
+ }
1588
+
1589
+ /** One endpoint's persisted CONFIG snapshot — `id` is the endpoint name; `timeout` its configured default. Parked Promises are process-bound and are never resurrected — `open` always restores an EMPTY broker. */
1590
+ export declare interface TerminalSnapshot {
1591
+ readonly id: string;
1592
+ readonly timeout?: number;
1593
+ }
1594
+
1595
+ /** One opaque persisted row — the shape a `TableInterface<TerminalSnapshotRow>`-backed store reads/writes; `snapshot` is narrowed with {@link import('./helpers.js').isTerminalSnapshot} on read. */
1596
+ export declare interface TerminalSnapshotRow {
1597
+ readonly id: string;
1598
+ readonly snapshot: unknown;
1599
+ }
1600
+
1601
+ /**
1602
+ * The point-access persistence seam (AGENTS §5 — Stores) for a {@link TerminalManagerInterface}'s
1603
+ * endpoint configs. Every primitive is async; `delete` of an absent id is a no-op.
1604
+ */
1605
+ export declare interface TerminalStoreInterface {
1606
+ get(id: string): Promise<TerminalSnapshot | undefined>;
1607
+ set(snapshot: TerminalSnapshot): Promise<void>;
1608
+ delete(id: string): Promise<void>;
1609
+ }
1610
+
1611
+ /** The handle {@link PromptInterface.park} returns — the parked prompt's `id` plus the Promise that resolves (or rejects) with its {@link PromptValue}. */
1612
+ export declare interface Ticket {
1613
+ readonly id: string;
1614
+ readonly value: Promise<PromptValue>;
1615
+ }
1206
1616
 
1207
1617
  /** Cancel a pending {@link TimerHandler} deadline — idempotent, safe to call after the timer fired. */
1208
1618
  export declare type TimerCancel = () => void;
@@ -1270,4 +1680,11 @@ export declare interface ValidationRules {
1270
1680
  */
1271
1681
  export declare type Validator = (input: string) => true | string;
1272
1682
 
1683
+ /** One SSE-shaped wire frame — the `event` name, its `data` payload (already JSON-stringified), and an optional `id`. The transport-neutral shape {@link import('./helpers.js').serializePending} / {@link import('./helpers.js').serializeExpire} / {@link import('./helpers.js').serializeShutdown} build, with no `http` dependency. */
1684
+ export declare interface WireEvent {
1685
+ readonly event: string;
1686
+ readonly data: string;
1687
+ readonly id?: string;
1688
+ }
1689
+
1273
1690
  export { }