@moda-ai/cli 1.22.1 → 1.22.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.
Files changed (115) hide show
  1. package/dist/cli-g8qs2tj9.js +9777 -0
  2. package/dist/cli-pwbvtes8.js +2312 -0
  3. package/dist/cli-qszcpda3.js +284 -0
  4. package/dist/cli.js +4775 -1159
  5. package/dist/index-6h0nc1r5.js +2765 -0
  6. package/dist/provision-j5gq8cvx.js +72 -0
  7. package/package.json +3 -2
  8. package/skills/integration/index.json +2 -2
  9. package/dist/agent-schemas.d.ts +0 -3
  10. package/dist/agent-schemas.js +0 -358
  11. package/dist/agent.d.ts +0 -5
  12. package/dist/agent.js +0 -477
  13. package/dist/analyst-tui.d.ts +0 -81
  14. package/dist/analyst-tui.js +0 -314
  15. package/dist/api-client.d.ts +0 -46
  16. package/dist/api-client.js +0 -120
  17. package/dist/ask-stream.d.ts +0 -146
  18. package/dist/ask-stream.js +0 -473
  19. package/dist/auth-storage.d.ts +0 -35
  20. package/dist/auth-storage.js +0 -159
  21. package/dist/auth.d.ts +0 -50
  22. package/dist/auth.js +0 -694
  23. package/dist/cli-session.d.ts +0 -26
  24. package/dist/cli-session.js +0 -169
  25. package/dist/cli.d.ts +0 -44
  26. package/dist/command-context.d.ts +0 -62
  27. package/dist/command-context.js +0 -57
  28. package/dist/command-registry.d.ts +0 -22
  29. package/dist/command-registry.js +0 -15
  30. package/dist/config.d.ts +0 -60
  31. package/dist/config.js +0 -110
  32. package/dist/doctor.d.ts +0 -79
  33. package/dist/doctor.js +0 -1182
  34. package/dist/errors.d.ts +0 -50
  35. package/dist/errors.js +0 -64
  36. package/dist/exit-funnel.d.ts +0 -14
  37. package/dist/exit-funnel.js +0 -53
  38. package/dist/harness-artifact-bodies.d.ts +0 -28
  39. package/dist/harness-artifact-bodies.js +0 -276
  40. package/dist/harness-candidates.d.ts +0 -77
  41. package/dist/harness-candidates.js +0 -482
  42. package/dist/harness-remote.d.ts +0 -135
  43. package/dist/harness-remote.js +0 -540
  44. package/dist/harness-report.d.ts +0 -145
  45. package/dist/harness-report.js +0 -1579
  46. package/dist/harness-sync-limits.d.ts +0 -8
  47. package/dist/harness-sync-limits.js +0 -21
  48. package/dist/harness.d.ts +0 -463
  49. package/dist/harness.js +0 -4009
  50. package/dist/init/index.d.ts +0 -78
  51. package/dist/init/index.js +0 -1442
  52. package/dist/init/mcp.d.ts +0 -2
  53. package/dist/init/mcp.js +0 -27
  54. package/dist/init/progress-board.d.ts +0 -80
  55. package/dist/init/progress-board.js +0 -225
  56. package/dist/init/rules.d.ts +0 -6
  57. package/dist/init/rules.js +0 -171
  58. package/dist/init/sdk-catalog.d.ts +0 -10
  59. package/dist/init/sdk-catalog.js +0 -80
  60. package/dist/init/sdk-guide.d.ts +0 -19
  61. package/dist/init/sdk-guide.js +0 -185
  62. package/dist/init/sdk-integration.d.ts +0 -53
  63. package/dist/init/sdk-integration.js +0 -835
  64. package/dist/init/sdk-protocol.d.ts +0 -135
  65. package/dist/init/sdk-protocol.js +0 -152
  66. package/dist/init/sdk-verification.d.ts +0 -27
  67. package/dist/init/sdk-verification.js +0 -123
  68. package/dist/init/tenant.d.ts +0 -67
  69. package/dist/init/tenant.js +0 -421
  70. package/dist/intelligence-schema.d.ts +0 -349
  71. package/dist/intelligence-schema.js +0 -174
  72. package/dist/keys.d.ts +0 -47
  73. package/dist/keys.js +0 -136
  74. package/dist/manifest.d.ts +0 -5
  75. package/dist/manifest.js +0 -114
  76. package/dist/markdown-terminal.d.ts +0 -49
  77. package/dist/markdown-terminal.js +0 -212
  78. package/dist/output.d.ts +0 -191
  79. package/dist/output.js +0 -624
  80. package/dist/production-intelligence.d.ts +0 -175
  81. package/dist/production-intelligence.js +0 -1430
  82. package/dist/profile-commands.d.ts +0 -10
  83. package/dist/profile-commands.js +0 -342
  84. package/dist/profiles.d.ts +0 -112
  85. package/dist/profiles.js +0 -573
  86. package/dist/prompts-ab.d.ts +0 -71
  87. package/dist/prompts-ab.js +0 -313
  88. package/dist/prompts-propose.d.ts +0 -15
  89. package/dist/prompts-propose.js +0 -212
  90. package/dist/prompts.d.ts +0 -58
  91. package/dist/prompts.js +0 -418
  92. package/dist/provision.d.ts +0 -28
  93. package/dist/provision.js +0 -104
  94. package/dist/redact.d.ts +0 -2
  95. package/dist/redact.js +0 -90
  96. package/dist/schemas.d.ts +0 -225
  97. package/dist/schemas.js +0 -126
  98. package/dist/skills-bundled.d.ts +0 -35
  99. package/dist/skills-bundled.js +0 -157
  100. package/dist/skills-manifest.d.ts +0 -33
  101. package/dist/skills-manifest.js +0 -244
  102. package/dist/skills.d.ts +0 -57
  103. package/dist/skills.js +0 -783
  104. package/dist/sync.d.ts +0 -5
  105. package/dist/sync.js +0 -36
  106. package/dist/telemetry.d.ts +0 -56
  107. package/dist/telemetry.js +0 -167
  108. package/dist/transforms.d.ts +0 -25
  109. package/dist/transforms.js +0 -102
  110. package/dist/tui.d.ts +0 -48
  111. package/dist/tui.js +0 -393
  112. package/dist/update-check.d.ts +0 -31
  113. package/dist/update-check.js +0 -141
  114. package/dist/workspace.d.ts +0 -12
  115. package/dist/workspace.js +0 -1294
@@ -1,314 +0,0 @@
1
- export class AnalystTui {
2
- state;
3
- active = false;
4
- closed = false;
5
- lastRenderAt = 0;
6
- spinnerIndex = 0;
7
- constructor(options) {
8
- const startedAt = options.startedAt ?? Date.now();
9
- this.state = {
10
- adapterName: options.adapterName,
11
- command: options.command,
12
- root: options.root,
13
- reportPath: options.reportPath,
14
- startedAt,
15
- phase: 'launching analyst',
16
- sawOutput: false,
17
- lastOutputAt: startedAt,
18
- eventCount: 0,
19
- rateLimitCount: 0,
20
- validation: 'pending',
21
- artifacts: [],
22
- summary: {},
23
- timeline: [],
24
- toolCounts: new Map(),
25
- completed: false,
26
- };
27
- }
28
- start() {
29
- if (this.active || this.closed)
30
- return;
31
- this.active = true;
32
- process.stderr.write('\u001b[?1049h\u001b[?25l');
33
- this.pushTimeline('Moda', 'Launching read-only analyst.');
34
- this.render(true);
35
- }
36
- recordEvent(event) {
37
- if (this.closed)
38
- return;
39
- this.state.sawOutput = true;
40
- this.state.lastOutputAt = Date.now();
41
- this.state.eventCount += 1;
42
- if (event.kind === 'rate_limit') {
43
- this.state.rateLimitCount += 1;
44
- this.state.phase = 'rate limit wait';
45
- }
46
- else if (event.kind === 'tool') {
47
- this.state.phase = `using ${event.toolName ?? 'tool'}`;
48
- if (event.toolName) {
49
- this.state.toolCounts.set(event.toolName, (this.state.toolCounts.get(event.toolName) ?? 0) + 1);
50
- }
51
- }
52
- else if (event.kind === 'command') {
53
- this.state.phase = 'running command';
54
- }
55
- else if (event.kind === 'agent_text') {
56
- this.state.phase = inferPhase(event.message);
57
- }
58
- const label = event.kind === 'rate_limit'
59
- ? 'Rate limit'
60
- : event.kind === 'tool'
61
- ? event.toolName ?? 'Tool'
62
- : event.kind === 'command'
63
- ? 'Command'
64
- : this.state.adapterName;
65
- this.pushTimeline(label, event.message);
66
- this.render();
67
- }
68
- heartbeat(options) {
69
- if (this.closed)
70
- return;
71
- this.state.sawOutput = options.sawOutput;
72
- this.render();
73
- }
74
- setValidation(status) {
75
- if (this.closed)
76
- return;
77
- this.state.validation = status;
78
- this.state.phase = status === 'running'
79
- ? 'validating cited report'
80
- : status === 'pass'
81
- ? 'validation passed'
82
- : status === 'fail'
83
- ? 'validation failed'
84
- : this.state.phase;
85
- this.pushTimeline('Moda', `Validation ${status}.`);
86
- this.render(true);
87
- }
88
- setSummary(summary) {
89
- if (this.closed)
90
- return;
91
- this.state.summary = { ...this.state.summary, ...summary };
92
- this.render(true);
93
- }
94
- setArtifacts(artifacts) {
95
- if (this.closed)
96
- return;
97
- this.state.artifacts = artifacts;
98
- if (artifacts.length > 0)
99
- this.pushTimeline('Moda', `Wrote ${artifacts.length} local artifact${artifacts.length === 1 ? '' : 's'}.`);
100
- this.render(true);
101
- }
102
- complete(status) {
103
- if (this.closed)
104
- return;
105
- this.state.completed = true;
106
- this.setValidation(status);
107
- }
108
- stop() {
109
- if (this.closed)
110
- return;
111
- this.closed = true;
112
- if (this.active) {
113
- this.render(true);
114
- process.stderr.write('\u001b[?25h\u001b[?1049l');
115
- }
116
- this.active = false;
117
- }
118
- pushTimeline(label, message) {
119
- const compact = compactWhitespace(message);
120
- if (!compact)
121
- return;
122
- const last = this.state.timeline.at(-1);
123
- if (last?.label === label && last.message === compact)
124
- return;
125
- this.state.timeline.push({
126
- time: timeOfDay(new Date()),
127
- label,
128
- message: compact,
129
- });
130
- if (this.state.timeline.length > 80)
131
- this.state.timeline.splice(0, this.state.timeline.length - 80);
132
- }
133
- render(force = false) {
134
- if (!this.active)
135
- return;
136
- const now = Date.now();
137
- if (!force && now - this.lastRenderAt < 120)
138
- return;
139
- this.lastRenderAt = now;
140
- const frame = renderAnalystTuiFrame(this.state, {
141
- color: process.env.NO_COLOR !== '1',
142
- columns: process.stderr.columns,
143
- rows: process.stderr.rows,
144
- now,
145
- spinnerFrame: spinner(this.spinnerIndex++),
146
- });
147
- process.stderr.write(`\u001b[H\u001b[2J${frame}`);
148
- }
149
- }
150
- export function renderAnalystTuiFrame(state, options = {}) {
151
- const color = options.color ?? false;
152
- const width = Math.max(72, Math.min(options.columns ?? 96, 128));
153
- const rows = Math.max(22, Math.min(options.rows ?? 32, 48));
154
- const now = options.now ?? Date.now();
155
- const paint = (code, text) => color ? `\u001b[${code}m${text}\u001b[0m` : text;
156
- const dim = (text) => paint(90, text);
157
- const cyan = (text) => paint(36, text);
158
- const green = (text) => paint(32, text);
159
- const yellow = (text) => paint(33, text);
160
- const red = (text) => paint(31, text);
161
- const bold = (text) => paint(1, text);
162
- const elapsedSeconds = Math.max(0, Math.round((now - state.startedAt) / 1000));
163
- const quietForSeconds = Math.max(0, Math.round((now - state.lastOutputAt) / 1000));
164
- const validation = state.validation === 'pass'
165
- ? green('pass')
166
- : state.validation === 'fail'
167
- ? red('fail')
168
- : state.validation === 'running'
169
- ? yellow('running')
170
- : dim('pending');
171
- const spinnerText = state.completed ? (state.validation === 'pass' ? green('ok') : red('fail')) : cyan(options.spinnerFrame ?? '|');
172
- const timelineRows = Math.max(6, rows - 22);
173
- const tools = [...state.toolCounts.entries()]
174
- .sort((a, b) => b[1] - a[1])
175
- .slice(0, 4)
176
- .map(([name, count]) => `${name} ${count}`)
177
- .join(', ') || 'none yet';
178
- const lines = [];
179
- lines.push(`${bold('Moda Analyst')} ${dim('Step 4/5 Set up')}`);
180
- lines.push(rule(width));
181
- lines.push(`${spinnerText} ${state.adapterName} ${dim('read-only repository access')} ${dim(`elapsed ${formatDuration(elapsedSeconds)}`)} ${dim(state.sawOutput ? `last output ${formatDuration(quietForSeconds)} ago` : 'waiting for first output')}`);
182
- lines.push('');
183
- lines.push(cyan('Session'));
184
- lines.push(keyValue('Phase', state.phase, width));
185
- lines.push(keyValue('Command', state.command, width));
186
- lines.push(keyValue('Root', state.root, width));
187
- lines.push(keyValue('Output', state.reportPath, width));
188
- lines.push(keyValue('Boundary', 'Agent can inspect files and print a report. Moda validates, then writes artifacts.', width));
189
- lines.push('');
190
- lines.push(cyan('Status'));
191
- lines.push(` validation ${validation}`);
192
- lines.push(` stream events ${state.eventCount}`);
193
- lines.push(` rate limits ${state.rateLimitCount}`);
194
- lines.push(` tools ${tools}`);
195
- lines.push('');
196
- lines.push(cyan('Evidence'));
197
- lines.push(` runtime agents ${metric(state.summary.runtimeAgentCount)}`);
198
- lines.push(` agent families ${metric(state.summary.agentFamilyCount)}`);
199
- lines.push(` artifacts ${metric(state.summary.artifactCount)}`);
200
- lines.push(` providers ${metric(state.summary.modelProviderCount)}`);
201
- lines.push(` claims/citations ${metricPair(state.summary.claimCount, state.summary.citationCount)}`);
202
- lines.push('');
203
- lines.push(cyan('Timeline'));
204
- const timeline = state.timeline.slice(-timelineRows);
205
- if (timeline.length === 0) {
206
- lines.push(` ${dim('Waiting for analyst output...')}`);
207
- }
208
- else {
209
- for (const item of timeline) {
210
- const prefix = ` ${dim(item.time)} ${item.label.padEnd(11).slice(0, 11)} `;
211
- lines.push(...wrapWithPrefix(prefix, item.message, width));
212
- }
213
- }
214
- if (state.artifacts.length > 0) {
215
- lines.push('');
216
- lines.push(cyan('Artifacts'));
217
- for (const artifact of state.artifacts.slice(0, 5))
218
- lines.push(` ${artifact}`);
219
- }
220
- lines.push('');
221
- lines.push(dim('Ctrl+C aborts | --no-tui shows raw stream | --agent --stream emits NDJSON for agents'));
222
- return fitToScreen(lines, rows, width).join('\n');
223
- }
224
- export function shouldUseAnalystTui(flags, outputMode) {
225
- if (outputMode !== 'human')
226
- return false;
227
- if (flags['no-tui'] === 'true' || process.env.MODA_NO_TUI === '1')
228
- return false;
229
- if (process.env.TERM === 'dumb')
230
- return false;
231
- if (flags.tui === 'true')
232
- return process.stderr.isTTY === true;
233
- return process.stderr.isTTY === true && process.env.CI !== 'true';
234
- }
235
- function inferPhase(message) {
236
- const lower = message.toLowerCase();
237
- if (lower.includes('report') && (lower.includes('json') || lower.includes('final')))
238
- return 'producing cited report';
239
- if (lower.includes('citation') || lower.includes('line number') || lower.includes('evidence'))
240
- return 'verifying citations';
241
- if (lower.includes('read') || lower.includes('file'))
242
- return 'reading source evidence';
243
- if (lower.includes('repository') || lower.includes('structure') || lower.includes('map'))
244
- return 'mapping repository';
245
- if (lower.includes('agent') || lower.includes('harness'))
246
- return 'classifying harness';
247
- return 'investigating repo';
248
- }
249
- function keyValue(label, value, width) {
250
- const prefix = ` ${label.padEnd(10)} `;
251
- return wrapWithPrefix(prefix, value, width).join('\n');
252
- }
253
- function wrapWithPrefix(prefix, text, width) {
254
- const available = Math.max(16, width - visibleLength(prefix));
255
- const words = compactWhitespace(text).split(' ');
256
- const lines = [];
257
- let current = '';
258
- for (const word of words) {
259
- const next = current ? `${current} ${word}` : word;
260
- if (visibleLength(next) > available && current) {
261
- lines.push(`${prefix}${current}`);
262
- current = word;
263
- }
264
- else {
265
- current = next;
266
- }
267
- }
268
- if (current)
269
- lines.push(`${prefix}${current}`);
270
- return lines.length > 0 ? lines : [prefix.trimEnd()];
271
- }
272
- function fitToScreen(lines, rows, width) {
273
- return lines
274
- .slice(0, rows)
275
- .map((line) => trimVisible(line, width));
276
- }
277
- function trimVisible(value, width) {
278
- if (visibleLength(value) <= width)
279
- return value;
280
- const stripped = stripAnsi(value);
281
- return stripped.length > width ? `${stripped.slice(0, Math.max(0, width - 3))}...` : stripped;
282
- }
283
- function rule(width) {
284
- return '-'.repeat(width);
285
- }
286
- function metric(value) {
287
- return typeof value === 'number' && Number.isFinite(value) ? String(value) : 'pending';
288
- }
289
- function metricPair(left, right) {
290
- return `${metric(left)} / ${metric(right)}`;
291
- }
292
- function formatDuration(seconds) {
293
- if (seconds < 60)
294
- return `${seconds}s`;
295
- const minutes = Math.floor(seconds / 60);
296
- const remaining = seconds % 60;
297
- return `${minutes}m ${remaining}s`;
298
- }
299
- function timeOfDay(date) {
300
- return date.toTimeString().slice(0, 8);
301
- }
302
- function spinner(index) {
303
- return ['|', '/', '-', '\\'][index % 4] ?? '|';
304
- }
305
- function compactWhitespace(value) {
306
- return value.replace(/\s+/g, ' ').trim();
307
- }
308
- function visibleLength(value) {
309
- return stripAnsi(value).length;
310
- }
311
- function stripAnsi(value) {
312
- return value.replace(/\u001b\[[0-9;?]*[A-Za-z]/g, '');
313
- }
314
- //# sourceMappingURL=analyst-tui.js.map
@@ -1,46 +0,0 @@
1
- import type { ProfileIOOptions } from './profiles.js';
2
- export declare class ApiError extends Error {
3
- readonly statusCode: number;
4
- readonly responseBody: string;
5
- constructor(message: string, statusCode: number, responseBody: string);
6
- }
7
- /**
8
- * Transport-level failure: the socket died before any HTTP response existed
9
- * (`fetch failed`, ECONNRESET, …). Distinct from `ApiError` (an HTTP status)
10
- * and from the per-attempt timeout so callers can retry ONLY genuine network
11
- * faults — e.g. undici handing out a pooled connection poisoned by a
12
- * just-dropped SSE stream on the same origin.
13
- */
14
- export declare class NetworkError extends Error {
15
- constructor(message: string);
16
- }
17
- export declare function callDataAPI(endpoint: string, init?: RequestInit, requestOptions?: ModaRequestOptions): Promise<unknown>;
18
- /**
19
- * Per-call transport overrides. Used by expensive, long-running, non-idempotent
20
- * endpoints (e.g. the agent `ask` POST) that need a longer timeout than the
21
- * default and must NOT be retried on timeout.
22
- */
23
- export interface ModaRequestOptions {
24
- /** Abort timeout in ms for each attempt (default REQUEST_TIMEOUT_MS). */
25
- timeoutMs?: number;
26
- /** Max retries (default MAX_RETRIES). Set 0 to disable retries entirely. */
27
- retries?: number;
28
- /**
29
- * Retry exactly once more on a NETWORK-level failure, even with `retries: 0`.
30
- * After a streamed SSE connection dies, undici's per-origin pool can hand
31
- * the very next fetch the same dead socket — the buffered fallback then
32
- * fails instantly ("fetch failed") against a healthy backend. The retry
33
- * waits ~1s and sends `Connection: close` so it cannot reuse a pooled
34
- * connection. HTTP errors (`ApiError`) and the per-attempt timeout NEVER
35
- * consume this retry.
36
- */
37
- retryOnceOnNetworkError?: boolean;
38
- /** Delay before the network retry (default 1s; test seam). */
39
- networkRetryDelayMs?: number;
40
- /** Invoked when the network retry is consumed (callers may log a note). */
41
- onNetworkRetry?: () => void;
42
- }
43
- export declare function callControlAPI(endpoint: string, options?: RequestInit, profileOptions?: ProfileIOOptions, requestOptions?: ModaRequestOptions): Promise<unknown>;
44
- export declare function getApiRequestCount(): number;
45
- export declare function resetApiRequestCount(): void;
46
- //# sourceMappingURL=api-client.d.ts.map
@@ -1,120 +0,0 @@
1
- // src/api-client.ts
2
- import { resolveApiKey, resolveModaBaseUrl } from './config.js';
3
- const REQUEST_TIMEOUT_MS = 30_000;
4
- const MAX_RETRIES = 3;
5
- const INITIAL_BACKOFF_MS = 500;
6
- const NETWORK_RETRY_DELAY_MS = 1_000;
7
- let apiRequestCount = 0;
8
- export class ApiError extends Error {
9
- statusCode;
10
- responseBody;
11
- constructor(message, statusCode, responseBody) {
12
- super(message);
13
- this.statusCode = statusCode;
14
- this.responseBody = responseBody;
15
- this.name = 'ApiError';
16
- }
17
- }
18
- /**
19
- * Transport-level failure: the socket died before any HTTP response existed
20
- * (`fetch failed`, ECONNRESET, …). Distinct from `ApiError` (an HTTP status)
21
- * and from the per-attempt timeout so callers can retry ONLY genuine network
22
- * faults — e.g. undici handing out a pooled connection poisoned by a
23
- * just-dropped SSE stream on the same origin.
24
- */
25
- export class NetworkError extends Error {
26
- constructor(message) {
27
- super(message);
28
- this.name = 'NetworkError';
29
- }
30
- }
31
- function isRetryable(status) {
32
- return status === 429 || status >= 500;
33
- }
34
- export async function callDataAPI(endpoint, init = { method: 'GET' }, requestOptions = {}) {
35
- apiRequestCount++;
36
- const url = `${resolveModaBaseUrl()}/api/v1/data${endpoint}`;
37
- return callModaAPI(url, init, {}, requestOptions);
38
- }
39
- export async function callControlAPI(endpoint, options = {}, profileOptions = {}, requestOptions = {}) {
40
- const url = `${resolveModaBaseUrl(profileOptions)}/api${endpoint}`;
41
- return callModaAPI(url, options, profileOptions, requestOptions);
42
- }
43
- async function callModaAPI(url, options, profileOptions = {}, requestOptions = {}) {
44
- const timeoutMs = requestOptions.timeoutMs ?? REQUEST_TIMEOUT_MS;
45
- const maxRetries = requestOptions.retries ?? MAX_RETRIES;
46
- const initialNetworkRetryBudget = requestOptions.retryOnceOnNetworkError
47
- ? 1
48
- : 0;
49
- let networkRetryBudget = initialNetworkRetryBudget;
50
- // Set once the network-retry budget is consumed: the retry attempt (and any
51
- // later ones) must not reuse a possibly-poisoned pooled connection.
52
- let connectionClose = false;
53
- // Structural termination bound: (maxRetries + 1) normal attempts plus at most
54
- // one extra network-retry attempt. Every path inside returns, throws, or
55
- // `continue`s, so the trailing throw is unreachable — but the bound makes the
56
- // contract hold by construction rather than by manual path analysis.
57
- const maxAttempts = maxRetries + initialNetworkRetryBudget + 1;
58
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
59
- if (attempt > 0) {
60
- const backoff = connectionClose
61
- ? requestOptions.networkRetryDelayMs ?? NETWORK_RETRY_DELAY_MS
62
- : INITIAL_BACKOFF_MS * Math.pow(2, attempt - 1);
63
- await new Promise((resolve) => setTimeout(resolve, backoff));
64
- }
65
- const controller = new AbortController();
66
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
67
- try {
68
- const response = await fetch(url, {
69
- ...options,
70
- headers: {
71
- 'x-api-key': resolveApiKey(profileOptions) ?? '',
72
- 'Content-Type': 'application/json',
73
- ...(options.headers ?? {}),
74
- ...(connectionClose ? { Connection: 'close' } : {}),
75
- },
76
- signal: controller.signal,
77
- });
78
- if (!response.ok) {
79
- const errorText = await response.text();
80
- const error = new ApiError(`API error (${response.status}): ${errorText}`, response.status, errorText);
81
- if (isRetryable(response.status) && attempt < maxRetries) {
82
- continue;
83
- }
84
- throw error;
85
- }
86
- return response.json();
87
- }
88
- catch (error) {
89
- if (error instanceof ApiError)
90
- throw error;
91
- const isAbort = error instanceof DOMException && error.name === 'AbortError';
92
- const wrapped = isAbort
93
- ? new Error(`Request timed out after ${timeoutMs}ms: ${url}`)
94
- : new NetworkError(`Network error: ${error instanceof Error ? error.message : String(error)}`);
95
- if (attempt < maxRetries) {
96
- continue;
97
- }
98
- if (!isAbort && networkRetryBudget > 0) {
99
- networkRetryBudget -= 1;
100
- connectionClose = true;
101
- requestOptions.onNetworkRetry?.();
102
- continue;
103
- }
104
- throw wrapped;
105
- }
106
- finally {
107
- clearTimeout(timeout);
108
- }
109
- }
110
- // Unreachable: the loop above always returns or throws within maxAttempts.
111
- // Present so the bound is enforced by construction, not just documented.
112
- throw new NetworkError(`Request retries exhausted: ${url}`);
113
- }
114
- export function getApiRequestCount() {
115
- return apiRequestCount;
116
- }
117
- export function resetApiRequestCount() {
118
- apiRequestCount = 0;
119
- }
120
- //# sourceMappingURL=api-client.js.map
@@ -1,146 +0,0 @@
1
- import type { AgentConfidence } from './output.js';
2
- /** Mirrors backend `AgentPhase`. */
3
- export type AskStreamPhase = 'idle' | 'planning' | 'searching' | 'analyzing' | 'writing' | 'done' | 'error' | 'stopped';
4
- /** Mirrors backend `StreamCitation`. */
5
- export interface AskStreamCitation {
6
- id: string;
7
- n: number;
8
- label: string;
9
- source: string;
10
- snippet?: string;
11
- href?: string;
12
- }
13
- /** Everything the CLI needs from a completed stream to render the final answer. */
14
- export interface AskStreamOutcome {
15
- answer: string;
16
- confidence: AgentConfidence;
17
- citations: AskStreamCitation[];
18
- nextCommands: string[];
19
- elapsedMs?: number;
20
- /**
21
- * Set when the stream terminated with a terminal `error` event (the agent
22
- * failed server-side). Transport failures throw `AskStreamError` instead.
23
- */
24
- errorMessage?: string;
25
- }
26
- /** Signals "fall back to the buffered ask endpoint" — never surfaced raw. */
27
- export declare class AskStreamError extends Error {
28
- /** Wall-clock ms the stream ran before failing (annotated by `streamAsk`). */
29
- elapsedMs?: number;
30
- /** tool_start/tool_end events seen before failing (annotated by `streamAsk`). */
31
- toolEvents?: number;
32
- constructor(message: string);
33
- }
34
- /**
35
- * Thresholds for "substantial progress": a stream that ran at least this long,
36
- * or produced more tool events than this, did real work before dropping —
37
- * the buffered fallback then re-runs the WHOLE question, roughly doubling
38
- * total latency, so the user deserves a notice instead of silence. The elapsed
39
- * check is inclusive (`>=`) so a drop landing exactly on the boundary — where
40
- * the doubled wait is felt most acutely — still gets the notice.
41
- */
42
- export declare const SUBSTANTIAL_STREAM_PROGRESS_MS = 60000;
43
- export declare const SUBSTANTIAL_STREAM_TOOL_EVENTS = 5;
44
- /**
45
- * One-line stderr notice for a stream that died after substantial progress.
46
- * Returns null for early failures (they keep today's silent fallback) and for
47
- * non-stream errors. The caller still falls back either way — the user must
48
- * still get an answer, just an informed wait instead of an apparent hang.
49
- */
50
- export declare function streamFallbackNotice(error: unknown): string | null;
51
- /**
52
- * Incremental SSE frame parser. Feed decoded text chunks (arbitrarily split —
53
- * even mid `data:` line or mid multi-byte frame); complete `data:` payloads
54
- * are returned in order. Handles LF and CRLF framing, multi-line `data:`
55
- * fields, and ignores comments (`: ...`) and non-data fields (`event:`, `id:`).
56
- */
57
- export declare class SseParser {
58
- private buffer;
59
- private pendingCr;
60
- push(chunk: string): string[];
61
- /** Drain a final frame that was not terminated by a blank line. */
62
- flush(): string[];
63
- }
64
- /** Quiet period before the first liveness line is printed. */
65
- export declare const IDLE_TICKER_THRESHOLD_MS = 10000;
66
- /** Minimum spacing between liveness lines. */
67
- export declare const IDLE_TICKER_MIN_UPDATE_MS = 15000;
68
- /**
69
- * Client-side liveness for quiet stream periods: long tool-less LLM turns
70
- * (coverage gate, synthesis) emit no events for 30–90s, which reads as a
71
- * hang. Once no event has arrived for IDLE_TICKER_THRESHOLD_MS this prints an
72
- * unobtrusive "… still working (Ns)" line (at most every
73
- * IDLE_TICKER_MIN_UPDATE_MS) through the same progress channel as tool cards —
74
- * stderr only, never interleaved into the final answer.
75
- */
76
- export declare class IdleTicker {
77
- private readonly write;
78
- private readonly now;
79
- private startedAt;
80
- private lastEventAt;
81
- private lastPrintAt;
82
- private timer;
83
- constructor(write: (line: string) => void, now?: () => number);
84
- start(): void;
85
- /** Reset the idle clock — call on every real stream event. */
86
- touch(): void;
87
- stop(): void;
88
- /** One liveness check; driven by the interval (public for tests). */
89
- poll(): void;
90
- }
91
- /**
92
- * Consumes parsed stream events: renders live progress lines through
93
- * `onProgress` and accumulates the final outcome. `finish()` throws
94
- * `AskStreamError` unless a terminal event (`done` or `error`) arrived, so a
95
- * connection dropped mid-run triggers the buffered fallback instead of
96
- * rendering a truncated answer.
97
- */
98
- export declare class AskStreamCollector {
99
- private readonly onProgress;
100
- private answer;
101
- private confidence;
102
- private citations;
103
- private nextCommands;
104
- private elapsedMs;
105
- private errorMessage;
106
- private terminal;
107
- private lastPhase;
108
- private toolNames;
109
- private toolEvents;
110
- constructor(onProgress?: (line: string) => void);
111
- handle(raw: unknown): void;
112
- isTerminal(): boolean;
113
- /** tool_start/tool_end events seen so far (fallback-heuristic input). */
114
- get toolEventCount(): number;
115
- finish(): AskStreamOutcome;
116
- }
117
- /**
118
- * Iterate any chunk source (a fetch body, or test fixtures) through the SSE
119
- * parser into the collector. Exported separately from `streamAsk` so tests can
120
- * exercise chunk-splitting behavior without a network layer.
121
- */
122
- export declare function consumeAskSse(chunks: AsyncIterable<Uint8Array | string>, collector: AskStreamCollector,
123
- /**
124
- * Fires once per parsed `data:` event (keepalive comment frames never reach
125
- * it). Used to reset the idle liveness ticker on real progress.
126
- */
127
- onEvent?: () => void): Promise<AskStreamOutcome>;
128
- export interface StreamAskOptions {
129
- question: string;
130
- daysBack: number;
131
- profile?: string;
132
- harness?: Record<string, unknown> | null;
133
- /** Overall wall-clock budget for the run (default 250s, like buffered ask). */
134
- timeoutMs?: number;
135
- onProgress?: (line: string) => void;
136
- /** Test seam. */
137
- fetchImpl?: typeof fetch;
138
- }
139
- /**
140
- * POST the question to `/api/cli/ask/stream` and consume the SSE response.
141
- * Throws `AskStreamError` on any transport/framing problem (non-2xx, wrong
142
- * content type, network drop, truncated stream) so callers can fall back to
143
- * the buffered endpoint.
144
- */
145
- export declare function streamAsk(options: StreamAskOptions): Promise<AskStreamOutcome>;
146
- //# sourceMappingURL=ask-stream.d.ts.map