@moda-ai/cli 1.22.0 → 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.
- package/dist/cli-g8qs2tj9.js +9777 -0
- package/dist/cli-pwbvtes8.js +2312 -0
- package/dist/cli-qszcpda3.js +284 -0
- package/dist/cli.js +4775 -1159
- package/dist/index-6h0nc1r5.js +2765 -0
- package/dist/provision-j5gq8cvx.js +72 -0
- package/package.json +3 -2
- package/skills/integration/index.json +2 -2
- package/dist/agent-schemas.d.ts +0 -3
- package/dist/agent-schemas.js +0 -358
- package/dist/agent.d.ts +0 -5
- package/dist/agent.js +0 -477
- package/dist/analyst-tui.d.ts +0 -81
- package/dist/analyst-tui.js +0 -314
- package/dist/api-client.d.ts +0 -46
- package/dist/api-client.js +0 -120
- package/dist/ask-stream.d.ts +0 -146
- package/dist/ask-stream.js +0 -473
- package/dist/auth-storage.d.ts +0 -35
- package/dist/auth-storage.js +0 -159
- package/dist/auth.d.ts +0 -50
- package/dist/auth.js +0 -694
- package/dist/cli-session.d.ts +0 -26
- package/dist/cli-session.js +0 -169
- package/dist/cli.d.ts +0 -44
- package/dist/command-context.d.ts +0 -62
- package/dist/command-context.js +0 -57
- package/dist/command-registry.d.ts +0 -22
- package/dist/command-registry.js +0 -15
- package/dist/config.d.ts +0 -60
- package/dist/config.js +0 -110
- package/dist/doctor.d.ts +0 -79
- package/dist/doctor.js +0 -1182
- package/dist/errors.d.ts +0 -50
- package/dist/errors.js +0 -64
- package/dist/exit-funnel.d.ts +0 -14
- package/dist/exit-funnel.js +0 -53
- package/dist/harness-artifact-bodies.d.ts +0 -28
- package/dist/harness-artifact-bodies.js +0 -276
- package/dist/harness-candidates.d.ts +0 -77
- package/dist/harness-candidates.js +0 -482
- package/dist/harness-remote.d.ts +0 -135
- package/dist/harness-remote.js +0 -540
- package/dist/harness-report.d.ts +0 -145
- package/dist/harness-report.js +0 -1579
- package/dist/harness-sync-limits.d.ts +0 -8
- package/dist/harness-sync-limits.js +0 -21
- package/dist/harness.d.ts +0 -463
- package/dist/harness.js +0 -4009
- package/dist/init/index.d.ts +0 -78
- package/dist/init/index.js +0 -1442
- package/dist/init/mcp.d.ts +0 -2
- package/dist/init/mcp.js +0 -27
- package/dist/init/progress-board.d.ts +0 -80
- package/dist/init/progress-board.js +0 -225
- package/dist/init/rules.d.ts +0 -6
- package/dist/init/rules.js +0 -171
- package/dist/init/sdk-catalog.d.ts +0 -10
- package/dist/init/sdk-catalog.js +0 -80
- package/dist/init/sdk-guide.d.ts +0 -19
- package/dist/init/sdk-guide.js +0 -185
- package/dist/init/sdk-integration.d.ts +0 -53
- package/dist/init/sdk-integration.js +0 -835
- package/dist/init/sdk-protocol.d.ts +0 -135
- package/dist/init/sdk-protocol.js +0 -152
- package/dist/init/sdk-verification.d.ts +0 -27
- package/dist/init/sdk-verification.js +0 -123
- package/dist/init/tenant.d.ts +0 -67
- package/dist/init/tenant.js +0 -421
- package/dist/intelligence-schema.d.ts +0 -349
- package/dist/intelligence-schema.js +0 -174
- package/dist/keys.d.ts +0 -47
- package/dist/keys.js +0 -136
- package/dist/manifest.d.ts +0 -5
- package/dist/manifest.js +0 -114
- package/dist/markdown-terminal.d.ts +0 -49
- package/dist/markdown-terminal.js +0 -212
- package/dist/output.d.ts +0 -191
- package/dist/output.js +0 -624
- package/dist/production-intelligence.d.ts +0 -175
- package/dist/production-intelligence.js +0 -1430
- package/dist/profile-commands.d.ts +0 -10
- package/dist/profile-commands.js +0 -342
- package/dist/profiles.d.ts +0 -112
- package/dist/profiles.js +0 -573
- package/dist/prompts-ab.d.ts +0 -71
- package/dist/prompts-ab.js +0 -313
- package/dist/prompts-propose.d.ts +0 -15
- package/dist/prompts-propose.js +0 -212
- package/dist/prompts.d.ts +0 -58
- package/dist/prompts.js +0 -418
- package/dist/provision.d.ts +0 -28
- package/dist/provision.js +0 -104
- package/dist/redact.d.ts +0 -2
- package/dist/redact.js +0 -90
- package/dist/schemas.d.ts +0 -225
- package/dist/schemas.js +0 -126
- package/dist/skills-bundled.d.ts +0 -35
- package/dist/skills-bundled.js +0 -157
- package/dist/skills-manifest.d.ts +0 -33
- package/dist/skills-manifest.js +0 -244
- package/dist/skills.d.ts +0 -57
- package/dist/skills.js +0 -777
- package/dist/sync.d.ts +0 -5
- package/dist/sync.js +0 -36
- package/dist/telemetry.d.ts +0 -56
- package/dist/telemetry.js +0 -167
- package/dist/transforms.d.ts +0 -25
- package/dist/transforms.js +0 -102
- package/dist/tui.d.ts +0 -48
- package/dist/tui.js +0 -393
- package/dist/update-check.d.ts +0 -31
- package/dist/update-check.js +0 -141
- package/dist/workspace.d.ts +0 -12
- package/dist/workspace.js +0 -1294
package/dist/analyst-tui.js
DELETED
|
@@ -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
|
package/dist/api-client.d.ts
DELETED
|
@@ -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
|
package/dist/api-client.js
DELETED
|
@@ -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
|
package/dist/ask-stream.d.ts
DELETED
|
@@ -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
|