@simonren/quorum 0.7.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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +144 -0
  3. package/commands/multi-consult.md +109 -0
  4. package/commands/multi-review.md +139 -0
  5. package/dist/adapters/base.d.ts +120 -0
  6. package/dist/adapters/base.js +98 -0
  7. package/dist/adapters/claude.d.ts +25 -0
  8. package/dist/adapters/claude.js +217 -0
  9. package/dist/adapters/codex.d.ts +20 -0
  10. package/dist/adapters/codex.js +227 -0
  11. package/dist/adapters/gemini.d.ts +20 -0
  12. package/dist/adapters/gemini.js +197 -0
  13. package/dist/adapters/index.d.ts +12 -0
  14. package/dist/adapters/index.js +15 -0
  15. package/dist/cli/check.d.ts +20 -0
  16. package/dist/cli/check.js +78 -0
  17. package/dist/cli/codex.d.ts +11 -0
  18. package/dist/cli/codex.js +255 -0
  19. package/dist/cli/gemini.d.ts +12 -0
  20. package/dist/cli/gemini.js +253 -0
  21. package/dist/commands.d.ts +28 -0
  22. package/dist/commands.js +105 -0
  23. package/dist/config.d.ts +244 -0
  24. package/dist/config.js +179 -0
  25. package/dist/consult-prompt.d.ts +10 -0
  26. package/dist/consult-prompt.js +72 -0
  27. package/dist/context.d.ts +1538 -0
  28. package/dist/context.js +383 -0
  29. package/dist/decoders/claude.d.ts +53 -0
  30. package/dist/decoders/claude.js +106 -0
  31. package/dist/decoders/codex.d.ts +71 -0
  32. package/dist/decoders/codex.js +145 -0
  33. package/dist/decoders/gemini.d.ts +33 -0
  34. package/dist/decoders/gemini.js +58 -0
  35. package/dist/decoders/index.d.ts +6 -0
  36. package/dist/decoders/index.js +3 -0
  37. package/dist/errors.d.ts +46 -0
  38. package/dist/errors.js +192 -0
  39. package/dist/executor.d.ts +103 -0
  40. package/dist/executor.js +244 -0
  41. package/dist/handoff.d.ts +270 -0
  42. package/dist/handoff.js +599 -0
  43. package/dist/index.d.ts +18 -0
  44. package/dist/index.js +134 -0
  45. package/dist/pipeline.d.ts +135 -0
  46. package/dist/pipeline.js +462 -0
  47. package/dist/prompt-v2.d.ts +38 -0
  48. package/dist/prompt-v2.js +391 -0
  49. package/dist/prompt.d.ts +71 -0
  50. package/dist/prompt.js +309 -0
  51. package/dist/schema.d.ts +660 -0
  52. package/dist/schema.js +536 -0
  53. package/dist/tools/consult.d.ts +104 -0
  54. package/dist/tools/consult.js +220 -0
  55. package/dist/tools/feedback.d.ts +91 -0
  56. package/dist/tools/feedback.js +117 -0
  57. package/dist/types.d.ts +105 -0
  58. package/dist/types.js +31 -0
  59. package/package.json +54 -0
@@ -0,0 +1,145 @@
1
+ /**
2
+ * CodexEventDecoder
3
+ *
4
+ * Parses JSONL streaming events emitted by `codex exec --json` on stdout.
5
+ * Extracts the final agent_message text and usage statistics.
6
+ *
7
+ * Event stream format:
8
+ * {"type":"thread.started","thread_id":"..."}
9
+ * {"type":"turn.started"}
10
+ * {"type":"item.started","item":{...}}
11
+ * {"type":"item.completed","item":{...}}
12
+ * {"type":"turn.completed","usage":{...}}
13
+ */
14
+ // =============================================================================
15
+ // DECODER
16
+ // =============================================================================
17
+ export class CodexEventDecoder {
18
+ /**
19
+ * Optional callback invoked for every successfully parsed event.
20
+ * @param eventType - The `type` field of the event (e.g. "item.completed").
21
+ * @param detail - A human-readable detail string for logging (may be undefined).
22
+ */
23
+ onProgress;
24
+ // The text from the most recently seen item.completed with item.type === "agent_message"
25
+ _finalResponse = null;
26
+ // Token usage from the most recently seen turn.completed
27
+ _usage = null;
28
+ // Error message from error/turn.failed events
29
+ _error = null;
30
+ // Count of events received (0 = possible rate limit / instant rejection)
31
+ _eventCount = 0;
32
+ // =============================================================================
33
+ // PUBLIC API
34
+ // =============================================================================
35
+ /**
36
+ * Parse a single JSONL line. Silently skips malformed or empty input.
37
+ */
38
+ processLine(line) {
39
+ const trimmed = line.trim();
40
+ if (trimmed.length === 0)
41
+ return;
42
+ let event;
43
+ try {
44
+ const parsed = JSON.parse(trimmed);
45
+ // Must be a plain object, not an array or primitive
46
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
47
+ return;
48
+ event = parsed;
49
+ }
50
+ catch {
51
+ // Malformed JSON — silently skip
52
+ return;
53
+ }
54
+ this._handleEvent(event);
55
+ }
56
+ /**
57
+ * Returns the text from the LAST `item.completed` event whose item type is
58
+ * `"agent_message"`, or `null` if no such event has been seen.
59
+ */
60
+ getFinalResponse() {
61
+ return this._finalResponse;
62
+ }
63
+ /**
64
+ * Returns the usage stats from the most recent `turn.completed` event, or
65
+ * `null` if no such event has been seen.
66
+ */
67
+ getUsage() {
68
+ return this._usage;
69
+ }
70
+ /**
71
+ * Returns the error message from `error` or `turn.failed` events, or `null`.
72
+ */
73
+ getError() {
74
+ return this._error;
75
+ }
76
+ /**
77
+ * Returns true if events were received but no agent_message was produced.
78
+ * Combined with a fast exit, this indicates rate limiting or instant rejection.
79
+ */
80
+ hasNoOutput() {
81
+ return this._eventCount > 0 && this._finalResponse === null;
82
+ }
83
+ // =============================================================================
84
+ // PRIVATE HELPERS
85
+ // =============================================================================
86
+ _handleEvent(event) {
87
+ this._eventCount++;
88
+ // Track the last agent_message text
89
+ if (event.type === 'item.completed' &&
90
+ event.item?.type === 'agent_message' &&
91
+ typeof event.item.text === 'string') {
92
+ this._finalResponse = event.item.text;
93
+ }
94
+ // Track usage from turn completion
95
+ if (event.type === 'turn.completed' && event.usage != null) {
96
+ this._usage = event.usage;
97
+ }
98
+ // Capture errors from error/turn.failed events
99
+ if (event.type === 'error') {
100
+ this._error = event.message || 'Unknown error from Codex';
101
+ }
102
+ if (event.type === 'turn.failed') {
103
+ this._error = event.error?.message || 'Turn failed';
104
+ }
105
+ // Capture error items (e.g. model errors reported as item.completed with type=error)
106
+ if (event.type === 'item.completed' && event.item?.type === 'error') {
107
+ this._error = event.item.message || event.item.text || 'Model error';
108
+ }
109
+ // Notify caller
110
+ this.onProgress?.(event.type, describeEvent(event));
111
+ }
112
+ }
113
+ // =============================================================================
114
+ // INTERNAL HELPERS
115
+ // =============================================================================
116
+ /**
117
+ * Returns a short human-readable description of an event for progress logging.
118
+ * Returns `undefined` for event types that carry no meaningful extra detail.
119
+ */
120
+ function describeEvent(event) {
121
+ if (event.type === 'thread.started' && event.thread_id) {
122
+ return `thread: ${event.thread_id}`;
123
+ }
124
+ if ((event.type === 'item.started' || event.type === 'item.completed') &&
125
+ event.item != null) {
126
+ const { type: itemType, command, status } = event.item;
127
+ if (itemType === 'command_execution') {
128
+ const parts = [];
129
+ if (command)
130
+ parts.push(`command: ${command}`);
131
+ if (status)
132
+ parts.push(`status: ${status}`);
133
+ return parts.length > 0 ? parts.join(', ') : undefined;
134
+ }
135
+ if (itemType === 'agent_message') {
136
+ return 'agent message';
137
+ }
138
+ return itemType;
139
+ }
140
+ if (event.type === 'turn.completed' && event.usage) {
141
+ const { input_tokens, output_tokens } = event.usage;
142
+ return `tokens: ${input_tokens} in / ${output_tokens} out`;
143
+ }
144
+ return undefined;
145
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * GeminiEventDecoder — Parses Gemini CLI stream-json JSONL events.
3
+ *
4
+ * Concatenates assistant message deltas into a final response,
5
+ * tracks stats from the result event, and emits progress callbacks.
6
+ */
7
+ export interface GeminiEvent {
8
+ type: string;
9
+ session_id?: string;
10
+ model?: string;
11
+ role?: string;
12
+ content?: string;
13
+ delta?: boolean;
14
+ tool_name?: string;
15
+ tool_id?: string;
16
+ status?: string;
17
+ stats?: {
18
+ total_tokens: number;
19
+ input_tokens: number;
20
+ output_tokens: number;
21
+ duration_ms: number;
22
+ [key: string]: unknown;
23
+ };
24
+ }
25
+ export declare class GeminiEventDecoder {
26
+ private assistantChunks;
27
+ private _stats;
28
+ onProgress?: (eventType: string, detail?: string) => void;
29
+ processLine(line: string): void;
30
+ getFinalResponse(): string;
31
+ getStats(): GeminiEvent['stats'] | null;
32
+ private describeEvent;
33
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * GeminiEventDecoder — Parses Gemini CLI stream-json JSONL events.
3
+ *
4
+ * Concatenates assistant message deltas into a final response,
5
+ * tracks stats from the result event, and emits progress callbacks.
6
+ */
7
+ export class GeminiEventDecoder {
8
+ assistantChunks = [];
9
+ _stats = null;
10
+ onProgress;
11
+ processLine(line) {
12
+ let event;
13
+ try {
14
+ const parsed = JSON.parse(line);
15
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
16
+ return;
17
+ event = parsed;
18
+ }
19
+ catch {
20
+ return;
21
+ }
22
+ if (!event.type)
23
+ return;
24
+ this.onProgress?.(event.type, this.describeEvent(event));
25
+ switch (event.type) {
26
+ case 'message':
27
+ if (event.role === 'assistant' && event.delta && event.content) {
28
+ this.assistantChunks.push(event.content);
29
+ }
30
+ break;
31
+ case 'result':
32
+ if (event.stats) {
33
+ this._stats = event.stats;
34
+ }
35
+ break;
36
+ }
37
+ }
38
+ getFinalResponse() {
39
+ return this.assistantChunks.join('');
40
+ }
41
+ getStats() {
42
+ return this._stats;
43
+ }
44
+ describeEvent(event) {
45
+ switch (event.type) {
46
+ case 'init':
47
+ return `model: ${event.model || 'unknown'}`;
48
+ case 'tool_use':
49
+ return `tool: ${event.tool_name || 'unknown'}`;
50
+ case 'tool_result':
51
+ return `status: ${event.status || 'unknown'}`;
52
+ case 'result':
53
+ return `status: ${event.status || 'unknown'}`;
54
+ default:
55
+ return '';
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,6 @@
1
+ export { CodexEventDecoder } from './codex.js';
2
+ export type { CodexEvent } from './codex.js';
3
+ export { GeminiEventDecoder } from './gemini.js';
4
+ export type { GeminiEvent } from './gemini.js';
5
+ export { ClaudeEventDecoder } from './claude.js';
6
+ export type { ClaudeEvent } from './claude.js';
@@ -0,0 +1,3 @@
1
+ export { CodexEventDecoder } from './codex.js';
2
+ export { GeminiEventDecoder } from './gemini.js';
3
+ export { ClaudeEventDecoder } from './claude.js';
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Error Handling for AI Reviewer MCP Server
3
+ */
4
+ import { FeedbackError, CliType } from './types.js';
5
+ /**
6
+ * Create a CLI not found error
7
+ */
8
+ export declare function createCliNotFoundError(cli: CliType): FeedbackError;
9
+ /**
10
+ * Create a timeout error
11
+ */
12
+ export declare function createTimeoutError(cli: CliType, durationMs: number): FeedbackError;
13
+ /**
14
+ * Create a rate limit error
15
+ */
16
+ export declare function createRateLimitError(cli: CliType, retryAfterMs?: number): FeedbackError;
17
+ /**
18
+ * Create an auth error
19
+ */
20
+ export declare function createAuthError(cli: CliType, message: string): FeedbackError;
21
+ /**
22
+ * Create an invalid response error
23
+ */
24
+ export declare function createInvalidResponseError(cli: CliType, rawOutput: string): FeedbackError;
25
+ /**
26
+ * Create a CLI crash error
27
+ */
28
+ export declare function createCliError(cli: CliType, exitCode: number, stderr: string): FeedbackError;
29
+ /**
30
+ * Format an error for user display
31
+ */
32
+ export declare function formatErrorForUser(error: FeedbackError): string;
33
+ /**
34
+ * Detect error type from CLI output and error messages
35
+ */
36
+ export declare function detectErrorType(cli: CliType, error: Error & {
37
+ code?: string;
38
+ }, stdout: string, stderr: string, exitCode: number | null): FeedbackError;
39
+ /**
40
+ * Parse retry-after from error response
41
+ */
42
+ export declare function parseRetryAfter(errorMessage: string): number | undefined;
43
+ /**
44
+ * Generate suggestion based on error type
45
+ */
46
+ export declare function getSuggestion(error: FeedbackError): string | undefined;
package/dist/errors.js ADDED
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Error Handling for AI Reviewer MCP Server
3
+ */
4
+ // CLI installation commands
5
+ // Codex: https://developers.openai.com/codex/cli/
6
+ // Gemini: https://github.com/google-gemini/gemini-cli
7
+ const INSTALL_COMMANDS = {
8
+ codex: 'npm install -g @openai/codex-cli',
9
+ gemini: 'npm install -g @google/gemini-cli',
10
+ claude: 'https://docs.anthropic.com/en/docs/claude-code'
11
+ };
12
+ // Environment variables for API keys
13
+ const ENV_VARS = {
14
+ codex: 'OPENAI_API_KEY',
15
+ gemini: 'GEMINI_API_KEY',
16
+ claude: 'ANTHROPIC_API_KEY'
17
+ };
18
+ // Authentication commands
19
+ const AUTH_COMMANDS = {
20
+ codex: 'codex login',
21
+ gemini: 'gemini (follow prompts)',
22
+ claude: 'claude auth'
23
+ };
24
+ /**
25
+ * Create a CLI not found error
26
+ */
27
+ export function createCliNotFoundError(cli) {
28
+ return {
29
+ type: 'cli_not_found',
30
+ cli,
31
+ installCmd: INSTALL_COMMANDS[cli]
32
+ };
33
+ }
34
+ /**
35
+ * Create a timeout error
36
+ */
37
+ export function createTimeoutError(cli, durationMs) {
38
+ return {
39
+ type: 'timeout',
40
+ cli,
41
+ durationMs
42
+ };
43
+ }
44
+ /**
45
+ * Create a rate limit error
46
+ */
47
+ export function createRateLimitError(cli, retryAfterMs) {
48
+ return {
49
+ type: 'rate_limit',
50
+ cli,
51
+ retryAfterMs
52
+ };
53
+ }
54
+ /**
55
+ * Create an auth error
56
+ */
57
+ export function createAuthError(cli, message) {
58
+ return {
59
+ type: 'auth_error',
60
+ cli,
61
+ message
62
+ };
63
+ }
64
+ /**
65
+ * Create an invalid response error
66
+ */
67
+ export function createInvalidResponseError(cli, rawOutput) {
68
+ return {
69
+ type: 'invalid_response',
70
+ cli,
71
+ rawOutput
72
+ };
73
+ }
74
+ /**
75
+ * Create a CLI crash error
76
+ */
77
+ export function createCliError(cli, exitCode, stderr) {
78
+ return {
79
+ type: 'cli_error',
80
+ cli,
81
+ exitCode,
82
+ stderr
83
+ };
84
+ }
85
+ /**
86
+ * Format an error for user display
87
+ */
88
+ export function formatErrorForUser(error) {
89
+ const others = ['codex', 'gemini', 'claude'].filter(c => c !== error.cli);
90
+ const otherCli = others[0];
91
+ switch (error.type) {
92
+ case 'cli_not_found':
93
+ return `❌ ${error.cli} CLI not found.
94
+
95
+ Install with: ${error.installCmd}
96
+
97
+ Alternative: Use /${otherCli}-review instead`;
98
+ case 'timeout':
99
+ return `⏱️ ${error.cli} timed out after ${Math.round(error.durationMs / 1000)}s.
100
+
101
+ This might happen with complex reviews. Try:
102
+ • Reviewing a smaller scope
103
+ • Using --focus to narrow the review`;
104
+ case 'rate_limit':
105
+ const retryMsg = error.retryAfterMs
106
+ ? `Try again in ${Math.ceil(error.retryAfterMs / 1000)}s`
107
+ : 'Wait a moment and try again';
108
+ return `🚫 ${error.cli} rate limit hit.
109
+
110
+ ${retryMsg}
111
+
112
+ Alternative: Use /${otherCli}-review instead`;
113
+ case 'auth_error':
114
+ return `🔐 ${error.cli} authentication failed.
115
+
116
+ ${error.message}
117
+
118
+ Check your API key: ${ENV_VARS[error.cli]}
119
+ Run: ${AUTH_COMMANDS[error.cli]}`;
120
+ case 'invalid_response':
121
+ return `⚠️ ${error.cli} returned an unusable response.
122
+
123
+ The output couldn't be parsed as valid feedback.
124
+ Raw output (truncated): ${error.rawOutput.slice(0, 200)}...`;
125
+ case 'cli_error':
126
+ return `❌ ${error.cli} crashed (exit code ${error.exitCode}).
127
+
128
+ ${error.stderr}`;
129
+ }
130
+ }
131
+ /**
132
+ * Detect error type from CLI output and error messages
133
+ */
134
+ export function detectErrorType(cli, error, stdout, stderr, exitCode) {
135
+ // CLI not found
136
+ if (error.code === 'ENOENT') {
137
+ return createCliNotFoundError(cli);
138
+ }
139
+ // Rate limit
140
+ if (stderr.toLowerCase().includes('rate limit') ||
141
+ stdout.toLowerCase().includes('rate limit')) {
142
+ const retryAfterMatch = stderr.match(/retry after (\d+)/i);
143
+ const retryAfterMs = retryAfterMatch ? parseInt(retryAfterMatch[1]) * 1000 : undefined;
144
+ return createRateLimitError(cli, retryAfterMs);
145
+ }
146
+ // Auth error
147
+ if (stderr.toLowerCase().includes('unauthorized') ||
148
+ stderr.toLowerCase().includes('authentication') ||
149
+ stderr.toLowerCase().includes('401') ||
150
+ stderr.toLowerCase().includes('403')) {
151
+ return createAuthError(cli, stderr);
152
+ }
153
+ // Generic CLI error
154
+ if (exitCode !== null && exitCode !== 0) {
155
+ return createCliError(cli, exitCode, stderr);
156
+ }
157
+ // Invalid response (fallback)
158
+ return createInvalidResponseError(cli, stdout);
159
+ }
160
+ /**
161
+ * Parse retry-after from error response
162
+ */
163
+ export function parseRetryAfter(errorMessage) {
164
+ const match = errorMessage.match(/retry[- ]?after[:\s]+(\d+)/i);
165
+ if (match) {
166
+ return parseInt(match[1]) * 1000; // Convert to ms
167
+ }
168
+ return undefined;
169
+ }
170
+ /**
171
+ * Generate suggestion based on error type
172
+ */
173
+ export function getSuggestion(error) {
174
+ switch (error.type) {
175
+ case 'cli_not_found':
176
+ return `Install ${error.cli} CLI or use the other model`;
177
+ case 'timeout':
178
+ return 'Try reviewing a smaller scope or using --focus';
179
+ case 'rate_limit':
180
+ return error.retryAfterMs
181
+ ? `Wait ${Math.ceil(error.retryAfterMs / 1000)}s and retry`
182
+ : 'Wait a moment and retry';
183
+ case 'auth_error':
184
+ return `Check your ${ENV_VARS[error.cli]} environment variable`;
185
+ case 'invalid_response':
186
+ return 'Retry with a more specific focus area';
187
+ case 'cli_error':
188
+ return 'Check the CLI documentation for troubleshooting';
189
+ default:
190
+ return undefined;
191
+ }
192
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * CliExecutor — Shared process management for external AI CLI tools.
3
+ *
4
+ * Extracted from the duplicated spawn logic in codex.ts and gemini.ts to
5
+ * provide a single, well-tested implementation of:
6
+ * - Child-process spawning with stdin delivery
7
+ * - Line-buffered stdout parsing (JSONL-friendly)
8
+ * - Inactivity and absolute-max timeouts
9
+ * - Max buffer size enforcement with truncation flag
10
+ * - Settled guard to prevent double resolve/reject
11
+ * - Dynamic inactivity timeout adjustment via setInactivityTimeout()
12
+ */
13
+ export interface CliExecutorOptions {
14
+ /** Executable to spawn (e.g. 'codex', 'gemini', 'bash'). */
15
+ command: string;
16
+ /** Arguments passed to the command. */
17
+ args: string[];
18
+ /** Working directory for the spawned process. */
19
+ cwd: string;
20
+ /** Optional content to write to the process's stdin then close it. */
21
+ stdin?: string;
22
+ /** Additional environment variables merged over process.env. */
23
+ env?: Record<string, string>;
24
+ /**
25
+ * Called for each complete newline-delimited line on stdout.
26
+ * Errors thrown inside are swallowed so they cannot break the executor.
27
+ */
28
+ onLine?: (line: string) => void;
29
+ /**
30
+ * Called whenever a chunk of data arrives on stderr.
31
+ * Errors thrown inside are swallowed so they cannot break the executor.
32
+ */
33
+ onStderr?: (data: string) => void;
34
+ /**
35
+ * Milliseconds of stdout/stderr inactivity before the process is killed
36
+ * and the promise rejects with Error('TIMEOUT').
37
+ * Default: 120_000 (2 minutes).
38
+ */
39
+ inactivityTimeoutMs?: number;
40
+ /**
41
+ * Absolute maximum runtime in milliseconds regardless of activity.
42
+ * Process is killed and the promise rejects with Error('MAX_TIMEOUT').
43
+ * Default: 3_600_000 (60 minutes).
44
+ */
45
+ maxTimeoutMs?: number;
46
+ /**
47
+ * Maximum number of bytes accumulated in rawStdout before truncation.
48
+ * Once exceeded, rawStdout is capped and truncated is set to true.
49
+ * Default: 1_048_576 (1 MB).
50
+ */
51
+ maxBufferSize?: number;
52
+ }
53
+ export interface CliResult {
54
+ /** Lines split on '\n', excluding the trailing empty string from a final newline. */
55
+ stdoutLines: string[];
56
+ /** Raw accumulated stdout string (may be truncated). */
57
+ rawStdout: string;
58
+ /** Accumulated stderr string. */
59
+ stderr: string;
60
+ /** Process exit code; -1 if the process was killed without an exit code. */
61
+ exitCode: number;
62
+ /** True when rawStdout was capped at maxBufferSize. */
63
+ truncated: boolean;
64
+ }
65
+ export declare class CliExecutor {
66
+ private readonly opts;
67
+ /**
68
+ * Mutable inactivity timeout — callers can tighten it after first streaming
69
+ * event via setInactivityTimeout(). Changing it clears and restarts the
70
+ * currently running inactivity timer immediately.
71
+ */
72
+ private currentInactivityMs;
73
+ /**
74
+ * Handle to the live inactivity timer (kept here so setInactivityTimeout
75
+ * can reset it from outside the Promise closure via the shared ref below).
76
+ */
77
+ private inactivityTimer;
78
+ /**
79
+ * Injected by run() so setInactivityTimeout can restart the timer using
80
+ * the correct reject handle while the process is still running.
81
+ */
82
+ private resetInactivityFn;
83
+ constructor(options: CliExecutorOptions);
84
+ /**
85
+ * Dynamically adjust the inactivity timeout.
86
+ *
87
+ * If the process is currently running, the active inactivity timer is
88
+ * cancelled and restarted with the new duration immediately. Subsequent
89
+ * activity resets will also use this new value.
90
+ *
91
+ * Typical use: tighten the timeout once the first streaming event arrives,
92
+ * so a stalled process is detected sooner after it starts responding.
93
+ */
94
+ setInactivityTimeout(ms: number): void;
95
+ /**
96
+ * Spawn the process and return a promise that resolves with CliResult on
97
+ * normal completion (any exit code) or rejects with:
98
+ * - Error('TIMEOUT') — inactivity timeout exceeded
99
+ * - Error('MAX_TIMEOUT') — absolute max timeout exceeded
100
+ * - ENOENT / other spawn errors propagated from child_process
101
+ */
102
+ run(): Promise<CliResult>;
103
+ }