@goodfoot/agent-hooks 1.0.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 (68) hide show
  1. package/README.md +137 -0
  2. package/dist/agents/antigravity/events.js +42 -0
  3. package/dist/agents/antigravity/hooks.js +59 -0
  4. package/dist/agents/antigravity/index.js +28 -0
  5. package/dist/agents/antigravity/outputs.js +81 -0
  6. package/dist/agents/antigravity/transport.js +84 -0
  7. package/dist/agents/antigravity/types.js +11 -0
  8. package/dist/agents/claude-code/env.js +27 -0
  9. package/dist/agents/claude-code/events.js +87 -0
  10. package/dist/agents/claude-code/hooks.js +1000 -0
  11. package/dist/agents/claude-code/index.js +57 -0
  12. package/dist/agents/claude-code/outputs.js +579 -0
  13. package/dist/agents/claude-code/tool-helpers.js +840 -0
  14. package/dist/agents/claude-code/transport.js +254 -0
  15. package/dist/agents/claude-code/types.js +11 -0
  16. package/dist/agents/codex/cli-support.js +318 -0
  17. package/dist/agents/codex/constants.js +29 -0
  18. package/dist/agents/codex/events.js +68 -0
  19. package/dist/agents/codex/hooks.js +93 -0
  20. package/dist/agents/codex/index.js +21 -0
  21. package/dist/agents/codex/outputs.js +189 -0
  22. package/dist/agents/codex/scaffold.js +273 -0
  23. package/dist/agents/codex/transport.js +139 -0
  24. package/dist/agents/codex/types.js +1 -0
  25. package/dist/cli.js +1357 -0
  26. package/dist/constants.js +39 -0
  27. package/dist/core/define-hook.js +61 -0
  28. package/dist/core/env.js +186 -0
  29. package/dist/core/logger.js +508 -0
  30. package/dist/core/stdin.js +38 -0
  31. package/dist/core/transport.js +231 -0
  32. package/dist/core/types.js +10 -0
  33. package/dist/index.js +17 -0
  34. package/dist/scaffold.js +512 -0
  35. package/package.json +61 -0
  36. package/types/agents/antigravity/events.d.ts +50 -0
  37. package/types/agents/antigravity/hooks.d.ts +76 -0
  38. package/types/agents/antigravity/index.d.ts +27 -0
  39. package/types/agents/antigravity/outputs.d.ts +107 -0
  40. package/types/agents/antigravity/transport.d.ts +47 -0
  41. package/types/agents/antigravity/types.d.ts +58 -0
  42. package/types/agents/claude-code/env.d.ts +27 -0
  43. package/types/agents/claude-code/events.d.ts +69 -0
  44. package/types/agents/claude-code/hooks.d.ts +1182 -0
  45. package/types/agents/claude-code/index.d.ts +23 -0
  46. package/types/agents/claude-code/outputs.d.ts +1160 -0
  47. package/types/agents/claude-code/tool-helpers.d.ts +807 -0
  48. package/types/agents/claude-code/transport.d.ts +119 -0
  49. package/types/agents/claude-code/types.d.ts +557 -0
  50. package/types/agents/codex/cli-support.d.ts +105 -0
  51. package/types/agents/codex/constants.d.ts +10 -0
  52. package/types/agents/codex/events.d.ts +63 -0
  53. package/types/agents/codex/hooks.d.ts +135 -0
  54. package/types/agents/codex/index.d.ts +19 -0
  55. package/types/agents/codex/outputs.d.ts +190 -0
  56. package/types/agents/codex/scaffold.d.ts +14 -0
  57. package/types/agents/codex/transport.d.ts +74 -0
  58. package/types/agents/codex/types.d.ts +131 -0
  59. package/types/cli.d.ts +315 -0
  60. package/types/constants.d.ts +9 -0
  61. package/types/core/define-hook.d.ts +39 -0
  62. package/types/core/env.d.ts +150 -0
  63. package/types/core/logger.d.ts +476 -0
  64. package/types/core/stdin.d.ts +21 -0
  65. package/types/core/transport.d.ts +131 -0
  66. package/types/core/types.d.ts +137 -0
  67. package/types/index.d.ts +18 -0
  68. package/types/scaffold.d.ts +46 -0
@@ -0,0 +1,476 @@
1
+ /**
2
+ * Logger system for agent hooks.
3
+ *
4
+ * Provides structured logging with event subscription and optional file output.
5
+ * The logger is **silent by default** to avoid interfering with hook protocol
6
+ * (stdout is reserved for JSON responses, stderr may conflict with the host
7
+ * CLI).
8
+ * @module
9
+ * @example
10
+ * ```typescript
11
+ * import { logger } from '@goodfoot/agent-hooks';
12
+ *
13
+ * // Subscribe to log events
14
+ * const unsubscribe = logger.on('error', (event) => {
15
+ * console.error(`Error in ${event.hookType}: ${event.message}`);
16
+ * });
17
+ *
18
+ * // Later, clean up
19
+ * unsubscribe();
20
+ * ```
21
+ */
22
+ /**
23
+ * Available log levels.
24
+ *
25
+ * | Level | Severity | Use Case |
26
+ * |-------|----------|----------|
27
+ * | `debug` | Lowest | Detailed debugging information |
28
+ * | `info` | Low | General operational events |
29
+ * | `warn` | Medium | Warning conditions that may indicate issues |
30
+ * | `error` | High | Error conditions requiring attention |
31
+ */
32
+ export type LogLevel = "debug" | "info" | "warn" | "error";
33
+ /**
34
+ * All log levels in order of severity (lowest to highest).
35
+ */
36
+ export declare const LOG_LEVELS: readonly ["debug", "info", "warn", "error"];
37
+ /**
38
+ * A structured log event emitted by the logger.
39
+ *
40
+ * Log events contain contextual information about what happened during hook
41
+ * execution, making them suitable for debugging, monitoring, and analytics.
42
+ * @example
43
+ * ```typescript
44
+ * // Example log event
45
+ * const event: LogEvent = {
46
+ * timestamp: '2024-01-15T10:30:00.000Z',
47
+ * level: 'warn',
48
+ * hookType: 'PreToolUse',
49
+ * message: 'Blocking dangerous command',
50
+ * input: { tool_name: 'Bash', tool_input: { command: 'rm -rf /' } }
51
+ * };
52
+ * ```
53
+ */
54
+ export interface LogEvent {
55
+ /**
56
+ * ISO 8601 timestamp of when the event occurred.
57
+ * @example '2024-01-15T10:30:00.000Z'
58
+ */
59
+ timestamp: string;
60
+ /**
61
+ * Severity level of the log event.
62
+ */
63
+ level: LogLevel;
64
+ /**
65
+ * Agent event name of the hook that generated this event.
66
+ * May be undefined for events outside hook context.
67
+ */
68
+ hookType?: string;
69
+ /**
70
+ * Human-readable description of what happened.
71
+ */
72
+ message: string;
73
+ /**
74
+ * Hook input data at the time of logging.
75
+ * Useful for debugging and reproducing issues.
76
+ */
77
+ input?: Record<string, unknown>;
78
+ /**
79
+ * Error information if this event represents an error.
80
+ * Contains structured error details for analysis.
81
+ */
82
+ error?: LogEventError;
83
+ /**
84
+ * Additional context data provided by the caller.
85
+ * Can contain arbitrary metadata relevant to the event.
86
+ */
87
+ context?: Record<string, unknown>;
88
+ }
89
+ /**
90
+ * Structured error information within a log event.
91
+ */
92
+ export interface LogEventError {
93
+ /**
94
+ * Error name (e.g., 'TypeError', 'ValidationError').
95
+ */
96
+ name: string;
97
+ /**
98
+ * Error message describing what went wrong.
99
+ */
100
+ message: string;
101
+ /**
102
+ * Stack trace if available.
103
+ */
104
+ stack?: string;
105
+ /**
106
+ * Error cause chain if the error was wrapped.
107
+ */
108
+ cause?: LogEventError;
109
+ }
110
+ /**
111
+ * Handler function invoked when a log event is emitted.
112
+ *
113
+ * Handlers receive log events and can process them in any way - forwarding
114
+ * to external services, writing to custom formats, or aggregating metrics.
115
+ * @param event - The log event to handle
116
+ * @example
117
+ * ```typescript
118
+ * // Forward to external logging service
119
+ * const handler: LogEventHandler = (event) => {
120
+ * externalLogger.log({
121
+ * level: event.level,
122
+ * message: event.message,
123
+ * metadata: { hookType: event.hookType }
124
+ * });
125
+ * };
126
+ * ```
127
+ */
128
+ export type LogEventHandler = (event: LogEvent) => void;
129
+ /**
130
+ * Function to unsubscribe a log event handler.
131
+ *
132
+ * Call this function to stop receiving log events. Always call unsubscribe
133
+ * when the handler is no longer needed to prevent memory leaks.
134
+ * @example
135
+ * ```typescript
136
+ * const unsubscribe = logger.on('error', handleError);
137
+ * // ... later
138
+ * unsubscribe(); // Stop receiving events
139
+ * ```
140
+ */
141
+ export type Unsubscribe = () => void;
142
+ /**
143
+ * Configuration options for the Logger.
144
+ */
145
+ export interface LoggerConfig {
146
+ /**
147
+ * Path to the log file for file output.
148
+ * If not set, file logging is disabled.
149
+ */
150
+ logFilePath?: string;
151
+ /**
152
+ * Name of the environment variable to read for the log file path.
153
+ * When set, the Logger reads `process.env[logEnvVar]` at construction time.
154
+ * Ignored if `logFilePath` is also provided.
155
+ * Defaults to `'AGENT_HOOKS_LOG_FILE'` for the exported singleton.
156
+ */
157
+ logEnvVar?: string;
158
+ }
159
+ /**
160
+ * Logger for agent hooks with event subscription and file output.
161
+ *
162
+ * ## Key Behaviors
163
+ *
164
+ * | Configuration | Behavior |
165
+ * |--------------|----------|
166
+ * | No config (default) | **Silent** - no output anywhere |
167
+ * | `AGENT_HOOKS_LOG_FILE` env var | Append JSON lines to file |
168
+ * | `.on(level, handler)` registered | Events delivered to handlers only |
169
+ * | Multiple destinations | All destinations receive events |
170
+ *
171
+ * ## Important Notes
172
+ *
173
+ * - **Never outputs to stdout** (reserved for JSON hook response)
174
+ * - **Never outputs to stderr** (may interfere with host CLI error handling)
175
+ * - File output uses JSON Lines format for easy parsing
176
+ * - `.on(level, handler)` returns an unsubscribe function
177
+ * @example
178
+ * ```typescript
179
+ * import { logger } from '@goodfoot/agent-hooks';
180
+ *
181
+ * // Subscribe to events at specific level
182
+ * logger.on('warn', (event) => {
183
+ * sendAlert(event.message);
184
+ * });
185
+ *
186
+ * // Log within a hook handler
187
+ * export default preToolUseHook({ matcher: 'Bash' }, async (input, { logger }) => {
188
+ * logger.warn('About to validate Bash command');
189
+ * return preToolUseOutput({ allow: true });
190
+ * });
191
+ * ```
192
+ */
193
+ export declare class Logger {
194
+ /**
195
+ * Registered event handlers by log level.
196
+ */
197
+ private handlers;
198
+ /**
199
+ * File descriptor for log file output.
200
+ * Lazily initialized on first write.
201
+ */
202
+ private logFileFd;
203
+ /**
204
+ * Path to the log file, if configured.
205
+ */
206
+ private logFilePath;
207
+ /**
208
+ * Whether file initialization has been attempted.
209
+ */
210
+ private fileInitialized;
211
+ /**
212
+ * Current hook context for enriching log events.
213
+ */
214
+ private currentHookType;
215
+ /**
216
+ * Current hook input for enriching log events.
217
+ */
218
+ private currentInput;
219
+ /**
220
+ * Creates a new Logger instance.
221
+ *
222
+ * Typically you should use the exported `logger` singleton rather than
223
+ * creating new instances.
224
+ * @param config - Optional configuration
225
+ * @example
226
+ * ```typescript
227
+ * // Use singleton (recommended)
228
+ * import { logger } from '@goodfoot/agent-hooks';
229
+ *
230
+ * // Or create custom instance
231
+ * const customLogger = new Logger({ logFilePath: '/var/log/hooks.log' });
232
+ * ```
233
+ */
234
+ constructor(config?: LoggerConfig);
235
+ /**
236
+ * Logs a debug message.
237
+ *
238
+ * Use for detailed debugging information that is typically only useful
239
+ * during development or troubleshooting.
240
+ * @param message - The debug message
241
+ * @param context - Optional additional context
242
+ * @example
243
+ * ```typescript
244
+ * logger.debug('Processing tool input', { toolName: 'Bash', inputSize: 256 });
245
+ * ```
246
+ */
247
+ debug(message: string, context?: Record<string, unknown>): void;
248
+ /**
249
+ * Logs an info message.
250
+ *
251
+ * Use for general operational events like hook invocations, successful
252
+ * completions, or state changes.
253
+ * @param message - The info message
254
+ * @param context - Optional additional context
255
+ * @example
256
+ * ```typescript
257
+ * logger.info('Session started', { source: 'startup', sessionId: 'abc123' });
258
+ * ```
259
+ */
260
+ info(message: string, context?: Record<string, unknown>): void;
261
+ /**
262
+ * Logs a warning message.
263
+ *
264
+ * Use for conditions that may indicate issues but don't prevent
265
+ * operation, such as deprecated patterns or performance concerns.
266
+ * @param message - The warning message
267
+ * @param context - Optional additional context
268
+ * @example
269
+ * ```typescript
270
+ * logger.warn('Deprecated hook pattern detected', { pattern: 'legacyMatcher' });
271
+ * ```
272
+ */
273
+ warn(message: string, context?: Record<string, unknown>): void;
274
+ /**
275
+ * Logs an error message.
276
+ *
277
+ * Use for error conditions that require attention but were handled
278
+ * gracefully. For exceptions, prefer {@link logError}.
279
+ * @param message - The error message
280
+ * @param context - Optional additional context
281
+ * @example
282
+ * ```typescript
283
+ * logger.error('Failed to validate tool input', { toolName: 'Bash', reason: 'empty command' });
284
+ * ```
285
+ */
286
+ error(message: string, context?: Record<string, unknown>): void;
287
+ /**
288
+ * Logs a structured error with full error details.
289
+ *
290
+ * Use this method when logging caught exceptions to capture the full
291
+ * error context including name, message, stack trace, and cause chain.
292
+ * @param error - The error to log
293
+ * @param message - Human-readable description of what failed
294
+ * @param context - Optional additional context
295
+ * @example
296
+ * ```typescript
297
+ * try {
298
+ * await dangerousOperation();
299
+ * } catch (err) {
300
+ * logger.logError(err, 'Failed to execute dangerous operation', {
301
+ * operation: 'delete',
302
+ * target: '/important/file.txt'
303
+ * });
304
+ * }
305
+ * ```
306
+ */
307
+ logError(error: unknown, message: string, context?: Record<string, unknown>): void;
308
+ /**
309
+ * Subscribes a handler to log events at the specified level.
310
+ *
311
+ * The handler will be called for every log event at the specified level.
312
+ * Returns an unsubscribe function that should be called when the handler
313
+ * is no longer needed.
314
+ * @param level - The log level to subscribe to
315
+ * @param handler - The handler function to call for each event
316
+ * @returns A function to unsubscribe the handler
317
+ * @example
318
+ * ```typescript
319
+ * // Subscribe to error events
320
+ * const unsubscribe = logger.on('error', (event) => {
321
+ * console.error(`[${event.hookType}] ${event.message}`);
322
+ * if (event.error) {
323
+ * console.error(event.error.stack);
324
+ * }
325
+ * });
326
+ *
327
+ * // Later, clean up
328
+ * unsubscribe();
329
+ * ```
330
+ * @example
331
+ * ```typescript
332
+ * // Forward to external logging library
333
+ * import pino from 'pino';
334
+ * const pinoLogger = pino();
335
+ *
336
+ * logger.on('info', (event) => pinoLogger.info(event, event.message));
337
+ * logger.on('warn', (event) => pinoLogger.warn(event, event.message));
338
+ * logger.on('error', (event) => pinoLogger.error(event, event.message));
339
+ * ```
340
+ */
341
+ on(level: LogLevel, handler: LogEventHandler): Unsubscribe;
342
+ /**
343
+ * Sets the current hook context for enriching log events.
344
+ *
345
+ * This is called internally by the runtime before invoking hook handlers.
346
+ * You typically don't need to call this directly.
347
+ * @param hookType - The agent event name being executed
348
+ * @param input - The hook input data
349
+ * @internal
350
+ */
351
+ setContext(hookType: string | undefined, input: Record<string, unknown> | undefined): void;
352
+ /**
353
+ * Clears the current hook context.
354
+ *
355
+ * Called internally by the runtime after hook execution completes.
356
+ * @internal
357
+ */
358
+ clearContext(): void;
359
+ /**
360
+ * Configures the log file path at runtime.
361
+ *
362
+ * Call this to enable or change file logging. Setting to `null` disables
363
+ * file logging (but doesn't close existing file handle immediately).
364
+ * @param filePath - Path to the log file, or null to disable
365
+ * @example
366
+ * ```typescript
367
+ * // Enable file logging at runtime
368
+ * logger.setLogFile('/var/log/agent-hooks.log');
369
+ *
370
+ * // Disable file logging
371
+ * logger.setLogFile(null);
372
+ * ```
373
+ */
374
+ setLogFile(filePath: string | null): void;
375
+ /**
376
+ * Closes all resources held by the logger.
377
+ *
378
+ * Call this during graceful shutdown to ensure all log data is flushed.
379
+ * @example
380
+ * ```typescript
381
+ * process.on('exit', () => {
382
+ * logger.close();
383
+ * });
384
+ * ```
385
+ */
386
+ close(): void;
387
+ /**
388
+ * Checks if there are any active handlers or destinations.
389
+ *
390
+ * Returns true if any handlers are registered or file logging is enabled.
391
+ * @returns Whether the logger has any active output destinations
392
+ */
393
+ hasDestinations(): boolean;
394
+ /**
395
+ * Emits a log event.
396
+ * @param level - The severity level of the event
397
+ * @param message - The log message
398
+ * @param context - Optional additional context data
399
+ */
400
+ private emit;
401
+ /**
402
+ * Delivers an event to all registered destinations.
403
+ * @param event - The log event to deliver
404
+ */
405
+ private deliverEvent;
406
+ /**
407
+ * Writes an event to the log file.
408
+ * @param event - The log event to write
409
+ */
410
+ private writeToFile;
411
+ /**
412
+ * Initializes the log file for writing.
413
+ */
414
+ private initializeFile;
415
+ /**
416
+ * Extracts structured error information from an unknown error.
417
+ * @param error - The error to extract information from
418
+ * @returns Structured error information
419
+ */
420
+ private extractErrorInfo;
421
+ }
422
+ /**
423
+ * Global logger instance for agent hooks.
424
+ *
425
+ * Use this singleton for all logging within hooks. The logger is configured
426
+ * via environment variables and supports event subscription for custom
427
+ * destinations.
428
+ *
429
+ * ## Configuration
430
+ *
431
+ * | Environment Variable | Description |
432
+ * |---------------------|-------------|
433
+ * | `AGENT_HOOKS_LOG_FILE` | Path to log file (JSON Lines format) |
434
+ *
435
+ * ## Usage in Hooks
436
+ *
437
+ * The logger is passed to hook handlers via context for convenience:
438
+ *
439
+ * ```typescript
440
+ * export default preToolUseHook({ matcher: 'Bash' }, async (input, { logger }) => {
441
+ * logger.warn('Validating Bash command');
442
+ * return preToolUseOutput({ allow: true });
443
+ * });
444
+ * ```
445
+ *
446
+ * ## External Integration
447
+ *
448
+ * Subscribe to events to forward logs to external systems:
449
+ *
450
+ * ```typescript
451
+ * import { logger } from '@goodfoot/agent-hooks';
452
+ * import pino from 'pino';
453
+ *
454
+ * const pinoLogger = pino({ level: 'debug' });
455
+ *
456
+ * logger.on('debug', (event) => pinoLogger.debug(event, event.message));
457
+ * logger.on('info', (event) => pinoLogger.info(event, event.message));
458
+ * logger.on('warn', (event) => pinoLogger.warn(event, event.message));
459
+ * logger.on('error', (event) => pinoLogger.error(event, event.message));
460
+ * ```
461
+ * @example
462
+ * ```typescript
463
+ * // Direct usage
464
+ * import { logger } from '@goodfoot/agent-hooks';
465
+ *
466
+ * logger.info('Starting operation');
467
+ * logger.warn('Resource limit approaching', { usage: 0.9 });
468
+ *
469
+ * try {
470
+ * await riskyOperation();
471
+ * } catch (err) {
472
+ * logger.logError(err, 'Risky operation failed');
473
+ * }
474
+ * ```
475
+ */
476
+ export declare const logger: Logger;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Stdin acquisition and JSON parsing for agent hooks.
3
+ *
4
+ * Both source runtimes (`claude-code-hooks`, `codex-hooks`) read stdin and
5
+ * parse JSON identically; this module is that shared logic. Wire rules for
6
+ * what a parse failure *means* belong to each agent's transport — core only
7
+ * classifies the outcome.
8
+ * @module
9
+ */
10
+ /**
11
+ * Reads all data from stdin.
12
+ * @returns Promise resolving to the complete stdin content
13
+ */
14
+ export declare function readStdin(): Promise<string>;
15
+ /**
16
+ * Parses stdin JSON input.
17
+ * @param stdinContent - Raw stdin content
18
+ * @returns Parsed input in the host CLI's wire format
19
+ * @throws SyntaxError if JSON is malformed
20
+ */
21
+ export declare function parseStdinJson(stdinContent: string): unknown;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Transport abstraction and the shared driver.
3
+ *
4
+ * Core owns the try/catch skeleton — stdin read, JSON parse, handler
5
+ * invocation, outcome classification, buffered single-write emission, process
6
+ * exit — and has **no knowledge of exit codes or stdout policy**: every wire
7
+ * decision is delegated to the agent's {@link Transport.finalize}, a pure
8
+ * mapping from {@link HookOutcome} to `{ stdout?, stderr?, exitCode }`. Agent
9
+ * transports are data-heavy adapters; they must not re-implement driver logic.
10
+ * @module
11
+ * @example
12
+ * ```typescript
13
+ * const transport: Transport<MyOutput> = {
14
+ * finalize: (outcome) => {
15
+ * if (outcome.kind === "response") {
16
+ * return { stdout: JSON.stringify(outcome.output ?? {}), exitCode: 0 };
17
+ * }
18
+ * return { stderr: "blocked", exitCode: 2 };
19
+ * },
20
+ * };
21
+ *
22
+ * await drive(transport, myHook);
23
+ * ```
24
+ */
25
+ import type { HookContext, HookErrorPhase, HookFunction } from "./types.js";
26
+ /**
27
+ * Error thrown by a hook handler to signal an intentional block.
28
+ *
29
+ * `drive()` classifies a caught {@link HookBlockError} **before** consulting
30
+ * the `unexpectedError` policy: it always routes to the `block` outcome,
31
+ * under every policy value, so an advisory event's in-flight block decision
32
+ * survives a crash mid-computation. Any other throw is subject to policy as
33
+ * usual. Agents may subclass or re-export this class; each agent's transport
34
+ * owns the wire translation of the `block` outcome.
35
+ */
36
+ export declare class HookBlockError extends Error {
37
+ /**
38
+ * Optional structured fields carried alongside the block reason (e.g.
39
+ * extra wire fields the agent's translation may forward).
40
+ */
41
+ readonly fields?: Record<string, unknown>;
42
+ /**
43
+ * @param message - The block reason; becomes the error `message`.
44
+ * @param fields - Optional additional structured fields.
45
+ */
46
+ constructor(message: string, fields?: Record<string, unknown>);
47
+ }
48
+ /**
49
+ * The classified result of one hook invocation, prior to wire mapping.
50
+ *
51
+ * Discriminated by `kind`; core produces exactly one outcome per invocation
52
+ * and hands it to {@link Transport.finalize} exactly once.
53
+ * @template TOutput - The agent's output type for the event.
54
+ */
55
+ export type HookOutcome<TOutput> =
56
+ /** The handler returned normally (`output: undefined` = empty response). */
57
+ {
58
+ kind: "response";
59
+ output: TOutput | undefined;
60
+ }
61
+ /** The handler threw a {@link HookBlockError}. */
62
+ | {
63
+ kind: "block";
64
+ error: HookBlockError;
65
+ }
66
+ /** The handler (or a pre-handler phase) failed unexpectedly. */
67
+ | {
68
+ kind: "handlerError";
69
+ error: unknown;
70
+ phase: HookErrorPhase;
71
+ }
72
+ /** The handler returned a plain-text-protocol payload, per the transport's predicate. */
73
+ | {
74
+ kind: "rawStdout";
75
+ stdout: string;
76
+ };
77
+ /**
78
+ * The wire-level result produced by a transport's finalize mapping.
79
+ */
80
+ export interface FinalizedResult {
81
+ /** Text to write to stdout, or undefined to write nothing. */
82
+ stdout?: string;
83
+ /** Text to write to stderr, or undefined to write nothing. */
84
+ stderr?: string;
85
+ /** Process exit code. */
86
+ exitCode: number;
87
+ }
88
+ /**
89
+ * Per-agent wire adapter.
90
+ *
91
+ * Implementations own all exit-code and stdout/stderr policy decisions:
92
+ * `finalize` is a pure mapping from the classified outcome to the streams and
93
+ * exit code the host CLI should observe. It must not throw and must not
94
+ * perform I/O; `drive()` owns writing and exiting.
95
+ *
96
+ * The optional `rawStdout` predicate lets the transport declare which
97
+ * returned outputs carry a plain-text protocol payload (e.g. worktree events
98
+ * whose stdout is read verbatim by the host): when it returns a string for a
99
+ * normally-returned output, `drive()` classifies the invocation as
100
+ * `rawStdout` instead of `response`. The returned output is never modified.
101
+ *
102
+ * @template TOutput - The agent's output type for the event.
103
+ */
104
+ export interface Transport<TOutput> {
105
+ /**
106
+ * Purely maps a classified outcome to wire text and an exit code.
107
+ * Called exactly once per invocation.
108
+ */
109
+ finalize(outcome: HookOutcome<TOutput>): FinalizedResult;
110
+ /**
111
+ * Optional plain-text-protocol detector. See {@link Transport}.
112
+ */
113
+ rawStdout?(output: TOutput): string | undefined;
114
+ }
115
+ /**
116
+ * Runs one hook invocation end to end: reads stdin, parses JSON, invokes the
117
+ * handler, classifies the outcome, maps it once through the transport, writes
118
+ * the resulting streams buffered (at most one write per stream), and exits
119
+ * with the transport's exit code.
120
+ *
121
+ * Malformed/unreadable stdin is classified at its phase (`read`/`parse`) like
122
+ * any other unexpected failure and logged identically to both source
123
+ * runtimes; each agent's transport decides the wire shape for that
124
+ * classification.
125
+ *
126
+ * @param transport - The agent's wire adapter.
127
+ * @param hookFn - The hook function to execute (from a factory built on
128
+ * {@link defineHook}).
129
+ * @returns Never resolves normally: the process exits inside this function.
130
+ */
131
+ export declare function drive<TInput, TOutput>(transport: Transport<TOutput>, hookFn: HookFunction<TInput, TOutput, HookContext>): Promise<void>;