@termwright/mcp 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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +257 -0
  3. package/dist/bin.d.ts +1 -0
  4. package/dist/bin.js +12 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/chunk-2J5WHI6X.js +2000 -0
  7. package/dist/chunk-2J5WHI6X.js.map +1 -0
  8. package/dist/chunk-36C7A7DW.js +2685 -0
  9. package/dist/chunk-36C7A7DW.js.map +1 -0
  10. package/dist/chunk-3PLOAM2C.js +2427 -0
  11. package/dist/chunk-3PLOAM2C.js.map +1 -0
  12. package/dist/chunk-57GYK2EF.js +2991 -0
  13. package/dist/chunk-57GYK2EF.js.map +1 -0
  14. package/dist/chunk-ABLJBL5P.js +2687 -0
  15. package/dist/chunk-ABLJBL5P.js.map +1 -0
  16. package/dist/chunk-BOOUADRN.js +1938 -0
  17. package/dist/chunk-BOOUADRN.js.map +1 -0
  18. package/dist/chunk-BPWIETN5.js +2983 -0
  19. package/dist/chunk-BPWIETN5.js.map +1 -0
  20. package/dist/chunk-CMQB5G7R.js +2968 -0
  21. package/dist/chunk-CMQB5G7R.js.map +1 -0
  22. package/dist/chunk-I4B53KZ7.js +2955 -0
  23. package/dist/chunk-I4B53KZ7.js.map +1 -0
  24. package/dist/chunk-IPNUAUAN.js +2991 -0
  25. package/dist/chunk-IPNUAUAN.js.map +1 -0
  26. package/dist/chunk-KZWL2S6E.js +2869 -0
  27. package/dist/chunk-KZWL2S6E.js.map +1 -0
  28. package/dist/chunk-LB2QBYW4.js +2686 -0
  29. package/dist/chunk-LB2QBYW4.js.map +1 -0
  30. package/dist/chunk-MR3AXSXL.js +1977 -0
  31. package/dist/chunk-MR3AXSXL.js.map +1 -0
  32. package/dist/chunk-NVSZXEZU.js +2688 -0
  33. package/dist/chunk-NVSZXEZU.js.map +1 -0
  34. package/dist/chunk-PD2WKAFE.js +2531 -0
  35. package/dist/chunk-PD2WKAFE.js.map +1 -0
  36. package/dist/chunk-PGY4ZDLD.js +1843 -0
  37. package/dist/chunk-PGY4ZDLD.js.map +1 -0
  38. package/dist/chunk-QDIAASH7.js +2982 -0
  39. package/dist/chunk-QDIAASH7.js.map +1 -0
  40. package/dist/chunk-UZWFLJGG.js +2873 -0
  41. package/dist/chunk-UZWFLJGG.js.map +1 -0
  42. package/dist/chunk-VFYTROYG.js +2825 -0
  43. package/dist/chunk-VFYTROYG.js.map +1 -0
  44. package/dist/chunk-ZTHKAJKT.js +2981 -0
  45. package/dist/chunk-ZTHKAJKT.js.map +1 -0
  46. package/dist/chunk-ZZULGRRE.js +2991 -0
  47. package/dist/chunk-ZZULGRRE.js.map +1 -0
  48. package/dist/index.d.ts +826 -0
  49. package/dist/index.js +105 -0
  50. package/dist/index.js.map +1 -0
  51. package/package.json +40 -0
@@ -0,0 +1,826 @@
1
+ import { TermwrightErrorCode, TerminalHarness, AppLogEvent, ExitStatus, EnvMode, LaunchOptions, AppLogSource, Locator } from '@termwright/driver';
2
+ import { z } from 'zod';
3
+ import { SemanticSnapshot, Rect, SemanticState, SemanticNode, SemanticRole } from '@termwright/protocol';
4
+ export { Rect, SEMANTIC_ROLES, SemanticNode, SemanticRole, SemanticSnapshot, SemanticState } from '@termwright/protocol';
5
+ import { Server } from 'node:http';
6
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
8
+ import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
9
+ import { TraceReader } from '@termwright/trace';
10
+ import { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';
11
+ import { ScreenFrame } from '@termwright/screenshot';
12
+
13
+ /**
14
+ * Crash reports, projected for agents.
15
+ *
16
+ * When a program dies on its own — a signal, or a non-zero exit nobody asked
17
+ * for — the driver keeps what the session knew at that moment: the exit status,
18
+ * the tail of the screen (where a stack trace lands), the last semantic
19
+ * revision, the inputs just before the end, and the diagnostics log. This module
20
+ * bounds that for a tool result and renders it the way an agent reads it.
21
+ *
22
+ * **The screen tail is unredacted.** It is whatever the terminal displayed,
23
+ * secrets included, so it is treated like a screenshot: bounded, never logged,
24
+ * and flagged as sensitive wherever an agent is told about it.
25
+ */
26
+
27
+ /** The crash projection carried in `structuredContent`. */
28
+ declare const crashSchema: z.ZodObject<{
29
+ exit: z.ZodObject<{
30
+ code: z.ZodNullable<z.ZodNumber>;
31
+ signal: z.ZodNullable<z.ZodString>;
32
+ }, z.core.$strip>;
33
+ timeMs: z.ZodNumber;
34
+ screenTail: z.ZodArray<z.ZodString>;
35
+ screenTailTruncated: z.ZodBoolean;
36
+ lastSemanticRevision: z.ZodNullable<z.ZodNumber>;
37
+ recentInputs: z.ZodArray<z.ZodObject<{
38
+ timeMs: z.ZodNumber;
39
+ kind: z.ZodEnum<{
40
+ key: "key";
41
+ mouse: "mouse";
42
+ paste: "paste";
43
+ raw: "raw";
44
+ }>;
45
+ bytes: z.ZodNumber;
46
+ preview: z.ZodOptional<z.ZodString>;
47
+ }, z.core.$strip>>;
48
+ diagnostics: z.ZodArray<z.ZodObject<{
49
+ code: z.ZodString;
50
+ detail: z.ZodString;
51
+ timeMs: z.ZodNumber;
52
+ revision: z.ZodOptional<z.ZodNumber>;
53
+ mode: z.ZodOptional<z.ZodEnum<{
54
+ mouse: "mouse";
55
+ focus: "focus";
56
+ }>>;
57
+ }, z.core.$strip>>;
58
+ }, z.core.$strip>;
59
+ /** The structured shape of {@link crashSchema}. */
60
+ type CrashProjection = z.output<typeof crashSchema>;
61
+
62
+ /**
63
+ * Kinds an agent can branch on: every driver code, plus the three failures that
64
+ * never reach the driver.
65
+ *
66
+ * Derived from `TermwrightErrorCode` rather than restated, so a code added
67
+ * upstream is publishable here the moment it lands instead of failing the
68
+ * assignment in {@link toErrorPayload}.
69
+ */
70
+ type ErrorKind = TermwrightErrorCode | 'usage' | 'no-session' | 'internal';
71
+ /** CLI exit codes (CONTRACTS.md §MCP). */
72
+ declare const EXIT_CODES: Readonly<{
73
+ ok: 0;
74
+ assertion: 1;
75
+ usage: 2;
76
+ noSession: 3;
77
+ ipc: 4;
78
+ internal: 5;
79
+ }>;
80
+ /** Exit code for a failure of the given kind. */
81
+ declare function exitCodeFor(kind: ErrorKind): number;
82
+ /**
83
+ * A failure raised by the MCP layer itself (argument validation, unknown
84
+ * terminal handle, capacity). Structurally identical to `TermwrightError`
85
+ * (`kind` + `suggestion`) but with a wider kind domain; it never crosses a
86
+ * package boundary, so it does not need to be a `TermwrightError` subclass.
87
+ */
88
+ declare class McpError extends Error {
89
+ readonly kind: ErrorKind;
90
+ readonly suggestion: string | undefined;
91
+ constructor(kind: ErrorKind, message: string, suggestion?: string);
92
+ }
93
+ /** Bad or contradictory tool arguments — exit code 2. */
94
+ declare function usageError(message: string, suggestion?: string): McpError;
95
+ /** Unknown or already-closed terminal handle — exit code 3. */
96
+ declare function noSessionError(message: string, suggestion?: string): McpError;
97
+ /** The agent-facing projection of a failure. */
98
+ interface ErrorPayload {
99
+ readonly kind: ErrorKind;
100
+ readonly message: string;
101
+ readonly suggestion?: string;
102
+ readonly semanticTree?: boolean;
103
+ readonly candidates?: readonly string[];
104
+ readonly screenExcerpt?: string;
105
+ /**
106
+ * What the session knew when the program died, when the failure happened
107
+ * around a crash. Its `screenTail` is unredacted — see `crash.ts`.
108
+ */
109
+ readonly crash?: CrashProjection;
110
+ }
111
+ /**
112
+ * Projects any thrown value into an {@link ErrorPayload}. Driver diagnostics
113
+ * (candidates, screen excerpt, suggestion) are carried over — bounded — because
114
+ * they are what lets an agent fix its own next call. Stack traces are dropped.
115
+ */
116
+ declare function toErrorPayload(error: unknown): ErrorPayload;
117
+ /** Renders an {@link ErrorPayload} the way a tool result's text content shows it. */
118
+ declare function renderErrorPayload(payload: ErrorPayload): string;
119
+
120
+ /** JSON Schema for one tool's input or output. */
121
+ type JsonSchema = Record<string, unknown>;
122
+ /** One tool, as `agent-context` describes it. */
123
+ interface AgentContextTool {
124
+ readonly name: string;
125
+ readonly title: string;
126
+ readonly description: string;
127
+ readonly inputSchema: JsonSchema;
128
+ readonly outputSchema: JsonSchema;
129
+ readonly annotations: Readonly<Record<string, unknown>>;
130
+ }
131
+ /** The whole document. */
132
+ interface AgentContext {
133
+ readonly v: number;
134
+ readonly server: {
135
+ readonly name: string;
136
+ readonly version: string;
137
+ };
138
+ readonly tools: readonly AgentContextTool[];
139
+ readonly enums: {
140
+ readonly roles: readonly string[];
141
+ readonly states: readonly string[];
142
+ readonly signals: readonly string[];
143
+ readonly errorKinds: readonly ErrorKind[];
144
+ };
145
+ readonly exitCodes: Readonly<Record<string, number>>;
146
+ readonly limits: Readonly<Record<string, number>>;
147
+ readonly conventions: readonly string[];
148
+ }
149
+ /** Builds the versioned document from the live tool definitions. */
150
+ declare function buildAgentContext(): AgentContext;
151
+ /** The one-screen cheat sheet printed by `termwright-mcp usage`. */
152
+ declare function buildUsage(): string;
153
+
154
+ /** One file of the emitted package, keyed by its path inside the directory. */
155
+ interface SkillFile {
156
+ readonly path: string;
157
+ readonly contents: string;
158
+ }
159
+ /** Builds the agent-skill package in memory. */
160
+ declare function buildAgentSkill(): readonly SkillFile[];
161
+ /** Writes the package into `directory`, creating it if needed. Returns the paths written. */
162
+ declare function writeAgentSkill(directory: string): Promise<readonly string[]>;
163
+
164
+ /** Where the CLI writes. Injectable so tests never touch the real streams. */
165
+ interface CliIo {
166
+ readonly out: (text: string) => void;
167
+ readonly err: (text: string) => void;
168
+ }
169
+ /**
170
+ * Runs the CLI and resolves with the process exit code. Serving blocks until
171
+ * the transport closes, so `serve` only resolves on shutdown.
172
+ */
173
+ declare function runCli(argv: readonly string[], io?: CliIo): Promise<number>;
174
+ /** Entry point for the `termwright-mcp` bin. */
175
+ declare function main(): Promise<void>;
176
+
177
+ /**
178
+ * The protocol- and driver-shaped types this package projects into MCP results.
179
+ *
180
+ * Roles, states and snapshot types come straight from `@termwright/protocol`
181
+ * (CONTRACTS.md §Dependency rules allows `mcp` to import its constants and
182
+ * types), so there is exactly one source of truth for the closed sets an agent
183
+ * sees in the tool schemas. Screen- and session-shaped types come from the
184
+ * driver, which owns them.
185
+ */
186
+
187
+ /** The driver's view of the visible grid. */
188
+ type ScreenSnapshot = ReturnType<TerminalHarness['screen']>;
189
+ /** Session capabilities as reported after the semantic handshake window. */
190
+ type SessionCapabilities = ReturnType<TerminalHarness['capabilities']>;
191
+ /** State flags an agent may filter on; the value type follows {@link SemanticState}. */
192
+ declare const FILTERABLE_STATES: readonly ["disabled", "focused", "selected", "checked", "expanded", "modal", "busy", "hidden", "readonly"];
193
+ /** Signals `terminal.signal` accepts, mirroring `TerminalHarness['signal']`. */
194
+ declare const SIGNALS: readonly ["INT", "TERM", "KILL", "HUP"];
195
+
196
+ /** A screen row whose text differs from the baseline. */
197
+ interface RowChange {
198
+ readonly row: number;
199
+ readonly text: string;
200
+ }
201
+ /** A changed semantic subtree, identified by the ref of its root. */
202
+ interface SubtreeChange {
203
+ readonly change: 'added' | 'removed' | 'updated';
204
+ readonly ref: string;
205
+ readonly role: string;
206
+ readonly name: string;
207
+ /** The subtree in compact ref format, one node per line, indented. */
208
+ readonly compact: string;
209
+ }
210
+ /** Rows present in `after` but different (or absent) in `before`. */
211
+ declare function diffRows(before: readonly string[], after: readonly string[]): readonly RowChange[];
212
+ /**
213
+ * Changed subtrees between two semantic snapshots, matched by node id.
214
+ *
215
+ * Only *minimal roots* are reported: when a node and its parent both changed,
216
+ * the parent's subtree already contains the child, so the child is folded in.
217
+ * Removals are reported with the ref they had in the baseline.
218
+ */
219
+ declare function diffSemantic(before: SemanticSnapshot | null, after: SemanticSnapshot | null): readonly SubtreeChange[];
220
+
221
+ /**
222
+ * The compact snapshot format from CONTRACTS.md §MCP. It is normative, so this
223
+ * module is deliberately small and covered by a golden test:
224
+ *
225
+ * ```
226
+ * Terminal t1 100x30 revision 42
227
+ * semanticTree: available
228
+ * dialog "Permission" ref=n7@42 bounds=(8,20,40,9) modal
229
+ * button "Approve" ref=n8@42 bounds=(14,23,11,1) focused
230
+ * visible text:
231
+ * <grid text>
232
+ * ```
233
+ *
234
+ * `bounds` is `(row,column,width,height)` — the field order of the protocol's
235
+ * `Rect`. Refs are `<nodeId>@<semanticRevision>`, byte-identical to the refs the
236
+ * driver puts on `ResolvedTarget`, so a ref can be quoted back to any tool.
237
+ */
238
+
239
+ /** One line of the ref list, plus the structured fields behind it. */
240
+ interface RefEntry {
241
+ readonly ref: string;
242
+ readonly role: string;
243
+ readonly name: string;
244
+ readonly depth: number;
245
+ readonly bounds?: Rect;
246
+ readonly flags: readonly string[];
247
+ readonly testId?: string;
248
+ readonly value?: string;
249
+ }
250
+ /** Formats `n8@42` for a node observed at `revision`. */
251
+ declare function formatRef(nodeId: string, revision: number): string;
252
+ /** Splits `n8@42` back into its parts; returns `null` for anything else. */
253
+ declare function parseRef(ref: string): {
254
+ readonly nodeId: string;
255
+ readonly revision: number;
256
+ } | null;
257
+ /** Renders a rect as `(row,column,width,height)`. */
258
+ declare function formatBounds(bounds: Rect): string;
259
+ /**
260
+ * The trailing flag list of a node line: every asserted state, in the closed
261
+ * order of the protocol's state set. Booleans render as bare names (`modal`),
262
+ * everything else as `name=value` (`checked=mixed`, `level=2`).
263
+ */
264
+ declare function stateFlags(state: SemanticState | undefined): readonly string[];
265
+ /** Renders one node line (without indentation). */
266
+ declare function formatNodeLine(entry: RefEntry): string;
267
+ /** Depth-first walk of a snapshot in document order, roots first. */
268
+ declare function walkSnapshot(snapshot: SemanticSnapshot): readonly {
269
+ node: SemanticNode;
270
+ depth: number;
271
+ }[];
272
+ /** Turns a snapshot into ref entries in document order. */
273
+ declare function refEntries(snapshot: SemanticSnapshot): readonly RefEntry[];
274
+ /** Projects a single node into a {@link RefEntry}. */
275
+ declare function toRefEntry(node: SemanticNode, revision: number, depth?: number): RefEntry;
276
+ /** Options for {@link formatCompactSnapshot}. */
277
+ interface CompactSnapshotOptions {
278
+ /** Terminal handle, e.g. `t1`. */
279
+ readonly terminal: string;
280
+ readonly columns: number;
281
+ readonly rows: number;
282
+ /** Screen revision — the cursor `terminal.capture_since` takes. */
283
+ readonly revision: number;
284
+ readonly semantic: SemanticSnapshot | null;
285
+ /** Visible grid text, one entry per row. */
286
+ readonly text: readonly string[];
287
+ /** Cap on listed nodes; the remainder is summarised on one line. */
288
+ readonly maxNodes?: number;
289
+ /** Cap on rendered rows; the remainder is summarised on one line. */
290
+ readonly maxRows?: number;
291
+ /** Omit the `visible text:` block (the `full` variant writes it to disk). */
292
+ readonly includeText?: boolean;
293
+ }
294
+ /** Renders the normative compact snapshot. */
295
+ declare function formatCompactSnapshot(options: CompactSnapshotOptions): string;
296
+
297
+ /**
298
+ * The application's own log, on the session timeline.
299
+ *
300
+ * A terminal shows what a program *drew*; its log says what the program
301
+ * *decided*. That gap is where "the screen looks right but nothing happened"
302
+ * lives, so an agent that can read both stops guessing.
303
+ *
304
+ * The driver publishes `app-log` events — a followed file yields a raw line, an
305
+ * instrumented adapter yields a structured record — and this module buffers
306
+ * them per terminal so `terminal.capture_since` can hand back everything since
307
+ * a cursor.
308
+ */
309
+
310
+ /** One buffered log entry, as an agent reads it. */
311
+ declare const logEntrySchema: z.ZodObject<{
312
+ seq: z.ZodNumber;
313
+ timeMs: z.ZodNumber;
314
+ source: z.ZodEnum<{
315
+ file: "file";
316
+ adapter: "adapter";
317
+ }>;
318
+ label: z.ZodOptional<z.ZodString>;
319
+ level: z.ZodOptional<z.ZodEnum<{
320
+ error: "error";
321
+ trace: "trace";
322
+ debug: "debug";
323
+ info: "info";
324
+ warn: "warn";
325
+ fatal: "fatal";
326
+ }>>;
327
+ message: z.ZodString;
328
+ logger: z.ZodOptional<z.ZodString>;
329
+ attrs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
330
+ revision: z.ZodOptional<z.ZodNumber>;
331
+ }, z.core.$strip>;
332
+ /** The structured shape of {@link logEntrySchema}. */
333
+ type LogEntry = z.output<typeof logEntrySchema>;
334
+ /** What a read since a cursor found. */
335
+ interface LogWindow {
336
+ readonly entries: readonly LogEntry[];
337
+ /**
338
+ * Entries that existed between the cursor and the oldest one still buffered,
339
+ * plus any trimmed to fit the response ceiling.
340
+ *
341
+ * Computed **at read time** from sequence numbers rather than accumulated in
342
+ * a counter as entries are dropped. A counter that is only published with the
343
+ * next event silently loses the final window — exactly when a program has
344
+ * gone quiet because it died, which is when the number matters most.
345
+ */
346
+ readonly omitted: number;
347
+ /** Pass as the next cursor; equals the newest sequence number seen. */
348
+ readonly cursor: number;
349
+ }
350
+ /** A bounded, per-terminal ring of log entries. */
351
+ declare class LogBuffer {
352
+ #private;
353
+ constructor(capacity?: number);
354
+ /** Sequence number of the newest entry; 0 when nothing has arrived. */
355
+ get sequence(): number;
356
+ /** Entries currently retained. */
357
+ get size(): number;
358
+ /** Records one driver event. */
359
+ append(event: AppLogEvent): void;
360
+ /**
361
+ * Everything after `cursor`, newest-biased and bounded.
362
+ *
363
+ * A cursor older than the buffer is not an error: the entries in between are
364
+ * counted in {@link LogWindow.omitted} so an agent knows its view has a hole,
365
+ * rather than quietly seeing a shorter list.
366
+ */
367
+ since(cursor: number, limit?: number): LogWindow;
368
+ }
369
+
370
+ /**
371
+ * The same properties, but optional ones may also be explicitly `undefined` —
372
+ * the shape zod produces for `.optional()` fields.
373
+ */
374
+ type Loose<T> = {
375
+ [K in keyof T]?: T[K] | undefined;
376
+ };
377
+
378
+ /** Ceilings for the replay side of the server. */
379
+ declare const TRACE_LIMITS: Readonly<{
380
+ /** Archives kept open per MCP session; the least recently used is evicted. */
381
+ maxOpen: 8;
382
+ /** Refusal threshold for an archive, in bytes. */
383
+ maxArchiveBytes: number;
384
+ /** Rows of reconstructed screen text a single frame may return. */
385
+ maxFrameRows: 200;
386
+ }>;
387
+ /** One archive this session has open. */
388
+ interface OpenTrace {
389
+ /** Agent-facing handle, `tr1`, `tr2`, … */
390
+ readonly id: string;
391
+ readonly path: string;
392
+ readonly reader: TraceReader;
393
+ /** Bumped on every use, so eviction drops the coldest archive. */
394
+ lastUsedAt: number;
395
+ }
396
+ /** Options for {@link TraceStore}. */
397
+ interface TraceStoreOptions {
398
+ readonly maxOpen?: number;
399
+ readonly maxArchiveBytes?: number;
400
+ /** Injectable clock, for tests. */
401
+ readonly now?: () => number;
402
+ }
403
+ /** The `.twtrace` archives one MCP session has open. */
404
+ declare class TraceStore {
405
+ #private;
406
+ constructor(options?: TraceStoreOptions);
407
+ /** Handles of every archive still open. */
408
+ list(): readonly OpenTrace[];
409
+ /**
410
+ * Opens an archive and registers it under a fresh `tr<n>` handle.
411
+ *
412
+ * At the ceiling the least recently used archive is closed rather than the
413
+ * call being refused: an agent can always re-open a path, but it cannot
414
+ * recover from a server that has wedged itself on old readers. The evicted
415
+ * handle is reported so the caller knows why it stopped working.
416
+ */
417
+ open(path: string): Promise<{
418
+ readonly trace: OpenTrace;
419
+ readonly evicted: string | null;
420
+ }>;
421
+ /** Looks up a handle and marks it as used. */
422
+ get(id: string): OpenTrace;
423
+ /** Closes every open archive. Best-effort, so shutdown always completes. */
424
+ closeAll(): Promise<void>;
425
+ }
426
+
427
+ /**
428
+ * Ceilings for the MCP layer. The session counts come from
429
+ * `DEFAULT_LIMITS` in `@termwright/protocol`; the rest are this package's own
430
+ * (how much capture history a terminal keeps, how long a command line may be).
431
+ */
432
+ declare const MCP_LIMITS: Readonly<{
433
+ /** Concurrent MCP sessions. */
434
+ maxSessions: number;
435
+ /** Concurrent terminals inside one MCP session. */
436
+ maxTerminals: number;
437
+ /** Snapshots retained per terminal for `capture_since` cursors. */
438
+ maxHistory: 16;
439
+ /** Argument ceiling for a launch command line. */
440
+ maxCommandParts: 64;
441
+ }>;
442
+ /** A snapshot the server handed out, kept so a later cursor can diff against it. */
443
+ interface RevisionRecord {
444
+ /** Screen revision; this is the `cursor` value agents pass back. */
445
+ readonly revision: number;
446
+ readonly semanticRevision: number | null;
447
+ readonly rows: readonly string[];
448
+ readonly semantic: SemanticSnapshot | null;
449
+ /** Log sequence at capture time, so a later diff knows where to resume. */
450
+ readonly logSeq: number;
451
+ readonly capturedAt: number;
452
+ }
453
+ /** One terminal owned by one MCP session. */
454
+ interface TerminalEntry {
455
+ /** Agent-facing handle, `t1`, `t2`, … */
456
+ readonly id: string;
457
+ readonly harness: TerminalHarness;
458
+ /** Directory for `variant: "full"` dumps; created lazily. */
459
+ readonly directory: string;
460
+ readonly command: readonly string[];
461
+ exit: ExitStatus | null;
462
+ closed: boolean;
463
+ history: RevisionRecord[];
464
+ /** The application's own log, buffered for `terminal.capture_since`. */
465
+ readonly logs: LogBuffer;
466
+ }
467
+ /** Options for {@link TerminalStore}. */
468
+ interface TerminalStoreOptions {
469
+ /** Stable key of the owning MCP session (`stdio`, or an `Mcp-Session-Id`). */
470
+ readonly sessionKey: string;
471
+ /** Root for on-disk snapshot dumps. Defaults to `<tmp>/termwright-mcp`. */
472
+ readonly storageDir?: string;
473
+ readonly maxTerminals?: number;
474
+ /** Injectable clock, for tests. */
475
+ readonly now?: () => number;
476
+ }
477
+ /**
478
+ * Arguments accepted by {@link TerminalStore.launch}. Optional fields admit an
479
+ * explicit `undefined` because they arrive from zod-parsed tool arguments.
480
+ */
481
+ interface LaunchRequest {
482
+ readonly command: readonly string[];
483
+ readonly cwd?: string | undefined;
484
+ readonly env?: Readonly<Record<string, string>> | undefined;
485
+ readonly envMode?: EnvMode | undefined;
486
+ readonly columns?: number | undefined;
487
+ readonly rows?: number | undefined;
488
+ readonly scrollbackLines?: number | undefined;
489
+ readonly semanticNegotiationMs?: number | undefined;
490
+ readonly timeouts?: Loose<NonNullable<LaunchOptions['timeouts']>> | undefined;
491
+ /** Log files to follow for the lifetime of the session. */
492
+ readonly logs?: readonly Loose<AppLogSource>[] | undefined;
493
+ }
494
+ /** The terminals of a single MCP session, plus their capture history. */
495
+ declare class TerminalStore {
496
+ #private;
497
+ readonly sessionKey: string;
498
+ constructor(options: TerminalStoreOptions);
499
+ /** Handles of every terminal still open in this session. */
500
+ list(): readonly TerminalEntry[];
501
+ /** Launches a child and registers it under a fresh `t<n>` handle. */
502
+ launch(request: LaunchRequest): Promise<TerminalEntry>;
503
+ /** Looks up a handle without throwing; for callers that tolerate absence. */
504
+ find(id: string): TerminalEntry | undefined;
505
+ /** Looks up a handle; unknown or closed handles are a `no-session` failure. */
506
+ get(id: string): TerminalEntry;
507
+ /**
508
+ * Captures the current screen and semantic tree, and remembers it so a later
509
+ * `capture_since` can diff against this revision.
510
+ */
511
+ record(entry: TerminalEntry): RevisionRecord;
512
+ /** The recorded baseline for a cursor, or a `history-truncated` failure. */
513
+ baseline(entry: TerminalEntry, cursor: number): RevisionRecord;
514
+ /** Writes a full dump next to the session and returns its path. */
515
+ writeDump(entry: TerminalEntry, name: string, contents: string): Promise<string>;
516
+ /** Closes one terminal and forgets it. Idempotent. */
517
+ close(id: string): Promise<TerminalEntry>;
518
+ /** Closes every terminal; failures are swallowed so shutdown always completes. */
519
+ closeAll(): Promise<void>;
520
+ }
521
+ /**
522
+ * Everything one MCP session owns: the terminals it launched and the trace
523
+ * archives it opened. Both are disposed together when the session goes away.
524
+ */
525
+ interface SessionStores {
526
+ readonly terminals: TerminalStore;
527
+ readonly traces: TraceStore;
528
+ }
529
+ /** Builds the stores for a session key. */
530
+ declare function createSessionStores(options: {
531
+ readonly sessionKey: string;
532
+ readonly storageDir?: string | undefined;
533
+ }): SessionStores;
534
+ /** Closes both stores of a session. */
535
+ declare function closeSessionStores(stores: SessionStores): Promise<void>;
536
+ /** A registered MCP session: its key, its stores, and its disposer. */
537
+ interface RegisteredSession<T> {
538
+ readonly key: string;
539
+ readonly stores: SessionStores;
540
+ readonly attachment: T;
541
+ /** Clock reading of the last request that named this session. */
542
+ lastSeenAt: number;
543
+ }
544
+ /** Options for {@link SessionRegistry}. */
545
+ interface SessionRegistryOptions<T> {
546
+ readonly maxSessions?: number;
547
+ readonly storageDir?: string;
548
+ /**
549
+ * Milliseconds a session may sit idle before it is torn down. `0` disables
550
+ * expiry, which is what stdio wants: there, EOF on the pipe is the signal.
551
+ */
552
+ readonly idleTtlMs?: number;
553
+ /** Injectable clock, for tests. */
554
+ readonly now?: () => number;
555
+ /** Released alongside the stores — the transport, for a socket-backed session. */
556
+ readonly disposeAttachment?: (attachment: T) => Promise<void> | void;
557
+ /** Called after an idle session was torn down, for the server log. */
558
+ readonly onExpired?: (key: string) => void;
559
+ }
560
+ /**
561
+ * Sessions keyed by `Mcp-Session-Id` (or `stdio` for the stdio transport).
562
+ * The registry — not the transport — owns the lifetime and the ceiling.
563
+ */
564
+ declare class SessionRegistry<T> {
565
+ #private;
566
+ constructor(options?: SessionRegistryOptions<T>);
567
+ /** The configured idle ceiling; `0` when expiry is disabled. */
568
+ get idleTtlMs(): number;
569
+ /**
570
+ * Marks a session as used. Called for **every** request that names one, so a
571
+ * session stays alive exactly as long as someone is talking to it.
572
+ */
573
+ touch(key: string): void;
574
+ /**
575
+ * Tears down every session idle past the TTL and returns their keys.
576
+ *
577
+ * Streamable HTTP has no disconnect signal: a client that crashes or walks
578
+ * away leaves its session, its terminals and their children running, and its
579
+ * slot taken. Repeated agent failures would then add up to an accidental
580
+ * denial of service against the operator's own machine, so idleness is the
581
+ * only honest liveness signal available here.
582
+ */
583
+ sweepIdle(): Promise<readonly string[]>;
584
+ /**
585
+ * Runs {@link sweepIdle} on a timer until the returned function is called.
586
+ * The timer is unref'd, so it never keeps a process alive on its own.
587
+ */
588
+ startIdleSweeper(intervalMs?: number): () => void;
589
+ /** Stops the sweeper started by {@link startIdleSweeper}. Idempotent. */
590
+ stopIdleSweeper(): void;
591
+ get size(): number;
592
+ /** True when another session would exceed the ceiling. */
593
+ get atCapacity(): boolean;
594
+ /** Creates a session and its stores; throws `capacity` at the ceiling. */
595
+ create(key: string, attach: (stores: SessionStores) => T): RegisteredSession<T>;
596
+ get(key: string): RegisteredSession<T> | undefined;
597
+ /** Removes a session and closes everything it owned. */
598
+ delete(key: string): Promise<void>;
599
+ /** Closes every session and stops the sweeper. */
600
+ closeAll(): Promise<void>;
601
+ }
602
+
603
+ /** Registers every tool from {@link TOOLS} on a fresh `McpServer`. */
604
+ declare function createTermwrightMcpServer(stores: SessionStores): McpServer;
605
+ /** A running server plus the handle needed to shut it down. */
606
+ interface RunningServer {
607
+ readonly server: McpServer;
608
+ readonly stores: SessionStores;
609
+ close(): Promise<void>;
610
+ }
611
+ /** Options shared by the transports. */
612
+ interface ServeOptions {
613
+ /** Root for `variant: "full"` snapshot dumps. */
614
+ readonly storageDir?: string;
615
+ readonly maxSessions?: number;
616
+ }
617
+ /** Serves the tools over stdio — the transport an MCP host spawns. */
618
+ declare function serveStdio(options?: ServeOptions): Promise<RunningServer>;
619
+ /**
620
+ * An in-process client/server pair over `InMemoryTransport`. Used by this
621
+ * package's tests, and by anything embedding the tools without a socket.
622
+ */
623
+ declare function serveInMemory(options?: ServeOptions & {
624
+ readonly sessionKey?: string;
625
+ }): Promise<RunningServer & {
626
+ readonly clientTransport: Transport;
627
+ }>;
628
+ /** A listening Streamable HTTP server. */
629
+ interface HttpServerHandle {
630
+ readonly http: Server;
631
+ readonly registry: SessionRegistry<{
632
+ transport: StreamableHTTPServerTransport;
633
+ server: McpServer;
634
+ }>;
635
+ readonly port: number;
636
+ close(): Promise<void>;
637
+ }
638
+ /** Options for {@link serveHttp}. */
639
+ interface HttpServeOptions extends ServeOptions {
640
+ readonly port?: number;
641
+ readonly host?: string;
642
+ /** Path the MCP endpoint listens on. Defaults to `/mcp`. */
643
+ readonly path?: string;
644
+ /**
645
+ * Milliseconds a session may sit idle before it is torn down. Defaults to
646
+ * {@link DEFAULT_IDLE_TTL_MS}; `0` disables expiry.
647
+ */
648
+ readonly idleTtlMs?: number;
649
+ /** Injectable clock, for tests. */
650
+ readonly now?: () => number;
651
+ /** Where an expiry is reported. Defaults to stderr. */
652
+ readonly log?: (message: string) => void;
653
+ }
654
+ /**
655
+ * Serves the tools over Streamable HTTP with multi-session support.
656
+ *
657
+ * Sessions live in {@link SessionRegistry}: `initialize` mints a session id and
658
+ * registers a store together with its transport, every later request is routed
659
+ * by its `Mcp-Session-Id` header, and `DELETE` (or transport close) disposes the
660
+ * session's terminals. The ceiling is enforced here, before a transport exists.
661
+ */
662
+ declare function serveHttp(options?: HttpServeOptions): Promise<HttpServerHandle>;
663
+
664
+ /** Ceilings for images leaving the server. */
665
+ declare const SCREENSHOT_LIMITS: Readonly<{
666
+ /**
667
+ * Refusal threshold for one PNG, in bytes.
668
+ *
669
+ * An MCP result travels inside a JSON-RPC message, and base64 inflates it by
670
+ * a third: a screenshot larger than this is more likely to blow a context
671
+ * window than to answer a question, so it fails with a suggestion instead.
672
+ */
673
+ maxPngBytes: number;
674
+ /** Pixel density multiplier ceiling. */
675
+ maxScale: 3;
676
+ }>;
677
+ /** An image ready to be attached to a tool result. */
678
+ interface ScreenshotImage {
679
+ /** Base64 PNG, as `ImageContent.data` requires. */
680
+ readonly data: string;
681
+ readonly mimeType: 'image/png';
682
+ readonly width: number;
683
+ readonly height: number;
684
+ /** False when a character had no embedded outline; see `fallbackCharacters`. */
685
+ readonly selfContained: boolean;
686
+ readonly fallbackCharacters: readonly string[];
687
+ }
688
+ /** How a caller asks for an image. */
689
+ interface ScreenshotRequest {
690
+ readonly scale?: number | undefined;
691
+ /** Light background instead of the default dark one. */
692
+ readonly theme?: 'dark' | 'light' | undefined;
693
+ }
694
+ /**
695
+ * Renders `frame` to a PNG.
696
+ *
697
+ * Failures are typed rather than thrown as raw errors: a scale out of range is
698
+ * `usage`, an image over the ceiling is `capacity`, and a renderer that cannot
699
+ * run at all (no font, no rasteriser) is `unsupported-action` — each with the
700
+ * next thing to try.
701
+ */
702
+ declare function renderScreenshot(frame: ScreenFrame, request?: ScreenshotRequest): ScreenshotImage;
703
+
704
+ /**
705
+ * The small kit every tool definition is built from: what a handler is given,
706
+ * what it returns, and the `defineTool` helper that keeps input schema, output
707
+ * schema and handler types in step.
708
+ *
709
+ * It lives apart from the tool files so that `tools.ts` (live terminals) and
710
+ * `trace-tools.ts` (recorded sessions) can share it without importing each
711
+ * other.
712
+ */
713
+
714
+ /** What a tool handler is given besides its arguments: the session's stores. */
715
+ interface ToolContext {
716
+ readonly terminals: TerminalStore;
717
+ readonly traces: TraceStore;
718
+ }
719
+ /** A handler's result: the text block an agent reads, plus the structured data. */
720
+ interface ToolOutcome<T> {
721
+ readonly text: string;
722
+ readonly data: T;
723
+ /**
724
+ * Images attached to the result as `ImageContent`, when the caller asked for
725
+ * one. Text and structured data are never replaced by an image — an agent
726
+ * that cannot see pictures loses nothing.
727
+ */
728
+ readonly images?: readonly ScreenshotImage[];
729
+ }
730
+ /** A registered tool, in the form both the server and `agent-context` consume. */
731
+ interface ToolDefinition {
732
+ readonly name: string;
733
+ readonly title: string;
734
+ readonly description: string;
735
+ readonly inputSchema: Record<string, z.ZodType>;
736
+ readonly outputSchema: Record<string, z.ZodType>;
737
+ readonly annotations: ToolAnnotations;
738
+ /**
739
+ * Args are typed `never` in the erased form so that any concrete handler is
740
+ * assignable; the server passes the values zod already validated.
741
+ */
742
+ readonly handler: (context: ToolContext, args: never) => Promise<ToolOutcome<Record<string, unknown>>>;
743
+ }
744
+ declare function defineTool<I extends Record<string, z.ZodType>, O extends Record<string, z.ZodType>>(definition: {
745
+ readonly name: string;
746
+ readonly title: string;
747
+ readonly description: string;
748
+ readonly inputSchema: I;
749
+ readonly outputSchema: O;
750
+ readonly annotations?: ToolAnnotations;
751
+ readonly handler: (context: ToolContext, args: z.output<z.ZodObject<I>>) => Promise<ToolOutcome<z.output<z.ZodObject<O>>>>;
752
+ }): ToolDefinition;
753
+
754
+ /**
755
+ * The tool registry: everything the server exposes, in one ordered list.
756
+ *
757
+ * Live-terminal tools come first (CONTRACTS.md §MCP), replay tools after. The
758
+ * server, `agent-context` and the `skill` package all read this list, so a tool
759
+ * is registered, documented and distributed by being added here once.
760
+ */
761
+
762
+ /** Every tool this server exposes. */
763
+ declare const TOOLS: readonly ToolDefinition[];
764
+ /** Convenience lookup used by the server and by tests. */
765
+ declare function toolByName(name: string): ToolDefinition | undefined;
766
+
767
+ /** The live-terminal tools, in the order CONTRACTS.md §MCP lists them. */
768
+ declare const TERMINAL_TOOLS: readonly ToolDefinition[];
769
+
770
+ /** The replay tools, in the order an investigation uses them. */
771
+ declare const TRACE_TOOLS: readonly ToolDefinition[];
772
+
773
+ /**
774
+ * Turning a tool's target arguments into a driver `Locator`.
775
+ *
776
+ * This module owns *no* matching logic: it picks the driver locator factory the
777
+ * arguments describe and hands everything else — strictness, waiting, staleness,
778
+ * candidate diagnostics — to `@termwright/driver`.
779
+ */
780
+
781
+ /**
782
+ * The target arguments every acting tool accepts. Exactly one selector wins.
783
+ *
784
+ * Optional fields admit an explicit `undefined` because they arrive straight
785
+ * from zod-parsed arguments, where an absent key is present-and-undefined.
786
+ */
787
+ interface TargetInput {
788
+ /** A ref from a previous snapshot: `n8@42`, or `grid:1,2,9,1@7`. */
789
+ readonly ref?: string | undefined;
790
+ /** The Textual-style CSS dialect, e.g. `dialog button#approve:focused`. */
791
+ readonly selector?: string | undefined;
792
+ readonly role?: SemanticRole | undefined;
793
+ /** Accessible name; `/…/flags` is read as a regular expression. */
794
+ readonly name?: string | undefined;
795
+ readonly testId?: string | undefined;
796
+ /** Visible text (grid matching when there is no semantic tree). */
797
+ readonly text?: string | undefined;
798
+ /** Label text (`labelledBy`, else name). */
799
+ readonly label?: string | undefined;
800
+ readonly exact?: boolean | undefined;
801
+ readonly state?: Readonly<Loose<SemanticState>> | undefined;
802
+ /** Zero-based pick among multiple matches; strict mode applies when omitted. */
803
+ readonly nth?: number | undefined;
804
+ }
805
+ /**
806
+ * Reads `/pattern/flags` as a RegExp and anything else as a literal string —
807
+ * the same convention the YAML snapshot format uses for names.
808
+ */
809
+ declare function textOrRegExp(value: string): string | RegExp;
810
+ /**
811
+ * Builds the locator described by `input`. Precedence is `ref`, `selector`,
812
+ * `testId`, `role`, `label`, `text` — the order from most to least specific.
813
+ *
814
+ * Every branch hands straight to a driver factory; nothing here matches, waits
815
+ * or decides staleness.
816
+ */
817
+ declare function buildLocator(harness: TerminalHarness, input: TargetInput): Locator;
818
+
819
+ /** Identity reported over MCP and by `--version`. Kept in step with package.json. */
820
+ declare const SERVER_NAME = "termwright";
821
+ /** Package version. */
822
+ declare const SERVER_VERSION = "0.1.0";
823
+ /** Version of the `agent-context` document shape (independent of the package). */
824
+ declare const AGENT_CONTEXT_VERSION = 1;
825
+
826
+ export { AGENT_CONTEXT_VERSION, type AgentContext, type AgentContextTool, type CliIo, type CompactSnapshotOptions, EXIT_CODES, type ErrorKind, type ErrorPayload, FILTERABLE_STATES, type HttpServeOptions, type HttpServerHandle, type JsonSchema, type LaunchRequest, MCP_LIMITS, McpError, type OpenTrace, type RefEntry, type RegisteredSession, type RevisionRecord, type RowChange, type RunningServer, SCREENSHOT_LIMITS, SERVER_NAME, SERVER_VERSION, SIGNALS, type ScreenSnapshot, type ScreenshotImage, type ScreenshotRequest, type ServeOptions, type SessionCapabilities, SessionRegistry, type SessionStores, type SkillFile, type SubtreeChange, TERMINAL_TOOLS, TOOLS, TRACE_LIMITS, TRACE_TOOLS, type TargetInput, type TerminalEntry, TerminalStore, type TerminalStoreOptions, type ToolContext, type ToolDefinition, type ToolOutcome, TraceStore, type TraceStoreOptions, buildAgentContext, buildAgentSkill, buildLocator, buildUsage, closeSessionStores, createSessionStores, createTermwrightMcpServer, defineTool, diffRows, diffSemantic, exitCodeFor, formatBounds, formatCompactSnapshot, formatNodeLine, formatRef, main, noSessionError, parseRef, refEntries, renderErrorPayload, renderScreenshot, runCli, serveHttp, serveInMemory, serveStdio, stateFlags, textOrRegExp, toErrorPayload, toRefEntry, toolByName, usageError, walkSnapshot, writeAgentSkill };