@defai.digital/cli 13.3.0 → 13.4.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.
- package/dist/bootstrap.d.ts +12 -0
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +23 -0
- package/dist/bootstrap.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/agent.d.ts +1 -0
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +211 -27
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/call.d.ts +2 -1
- package/dist/commands/call.d.ts.map +1 -1
- package/dist/commands/call.js +199 -10
- package/dist/commands/call.js.map +1 -1
- package/dist/commands/cleanup.d.ts +3 -1
- package/dist/commands/cleanup.d.ts.map +1 -1
- package/dist/commands/cleanup.js +123 -11
- package/dist/commands/cleanup.js.map +1 -1
- package/dist/commands/discuss.d.ts +2 -1
- package/dist/commands/discuss.d.ts.map +1 -1
- package/dist/commands/discuss.js +604 -105
- package/dist/commands/discuss.js.map +1 -1
- package/dist/commands/doctor.d.ts +11 -2
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +272 -38
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +2 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +136 -122
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/monitor.d.ts +22 -0
- package/dist/commands/monitor.d.ts.map +1 -0
- package/dist/commands/monitor.js +255 -0
- package/dist/commands/monitor.js.map +1 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +107 -2
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/status.d.ts +6 -5
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +260 -75
- package/dist/commands/status.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +30 -0
- package/dist/parser.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/database.d.ts.map +1 -1
- package/dist/utils/database.js +5 -0
- package/dist/utils/database.js.map +1 -1
- package/dist/web/api.d.ts +12 -0
- package/dist/web/api.d.ts.map +1 -0
- package/dist/web/api.js +1189 -0
- package/dist/web/api.js.map +1 -0
- package/dist/web/dashboard.d.ts +13 -0
- package/dist/web/dashboard.d.ts.map +1 -0
- package/dist/web/dashboard.js +5290 -0
- package/dist/web/dashboard.js.map +1 -0
- package/package.json +21 -21
package/dist/commands/discuss.js
CHANGED
|
@@ -7,13 +7,15 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Enables multiple AI models to discuss a topic from their unique perspectives,
|
|
9
9
|
* building consensus through various patterns and mechanisms.
|
|
10
|
+
* Traces are emitted to SQLite for dashboard visibility.
|
|
10
11
|
*/
|
|
12
|
+
import { randomUUID } from 'node:crypto';
|
|
11
13
|
// Bootstrap imports - composition root provides adapter access
|
|
12
|
-
import { createProvider, PROVIDER_CONFIGS, } from '../bootstrap.js';
|
|
14
|
+
import { bootstrap, createProvider, getTraceStore, PROVIDER_CONFIGS, } from '../bootstrap.js';
|
|
13
15
|
// Discussion domain imports
|
|
14
16
|
import { DiscussionExecutor, RecursiveDiscussionExecutor, parseParticipantList, } from '@defai.digital/discussion-domain';
|
|
15
17
|
// Contract types
|
|
16
|
-
import {
|
|
18
|
+
import { DEFAULT_PROVIDER_TIMEOUT, DEFAULT_TOTAL_BUDGET_MS, DEFAULT_ROUNDS, DEFAULT_DISCUSSION_DEPTH, MAX_DISCUSSION_DEPTH, DEFAULT_MAX_TOTAL_CALLS, DEFAULT_CONFIDENCE_THRESHOLD, DEFAULT_AGENT_WEIGHT_MULTIPLIER, getErrorMessage, } from '@defai.digital/contracts';
|
|
17
19
|
import { COLORS, ICONS } from '../utils/terminal.js';
|
|
18
20
|
// Pattern display names
|
|
19
21
|
const PATTERN_NAMES = {
|
|
@@ -32,6 +34,248 @@ const CONSENSUS_NAMES = {
|
|
|
32
34
|
majority: 'Majority',
|
|
33
35
|
};
|
|
34
36
|
// ============================================================================
|
|
37
|
+
// Model Selection
|
|
38
|
+
// ============================================================================
|
|
39
|
+
/**
|
|
40
|
+
* Safely gets the default model for a provider
|
|
41
|
+
* Returns the provider's default model, or first available, or 'default' placeholder
|
|
42
|
+
*/
|
|
43
|
+
function getModelForProviderConfig(config) {
|
|
44
|
+
if (!config?.models || config.models.length === 0) {
|
|
45
|
+
return 'default';
|
|
46
|
+
}
|
|
47
|
+
const defaultModel = config.models.find(m => m.isDefault);
|
|
48
|
+
if (defaultModel !== undefined) {
|
|
49
|
+
return defaultModel.modelId;
|
|
50
|
+
}
|
|
51
|
+
return config.models[0]?.modelId ?? 'default';
|
|
52
|
+
}
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// Smart Provider Selection
|
|
55
|
+
// ============================================================================
|
|
56
|
+
/**
|
|
57
|
+
* Randomly selects N items from an array (Fisher-Yates shuffle)
|
|
58
|
+
*/
|
|
59
|
+
function randomSelect(items, count) {
|
|
60
|
+
if (items.length <= count)
|
|
61
|
+
return [...items];
|
|
62
|
+
// Fisher-Yates shuffle
|
|
63
|
+
const shuffled = [...items];
|
|
64
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
65
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
66
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
67
|
+
}
|
|
68
|
+
return shuffled.slice(0, count);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Smart provider selection:
|
|
72
|
+
* - If providers specified: use them (checking availability)
|
|
73
|
+
* - If not specified and ≥3 available: randomly pick 3
|
|
74
|
+
* - If not specified and exactly 2 available: use both
|
|
75
|
+
* - If not specified and exactly 1 available: skip discussion, use single provider
|
|
76
|
+
* - If not specified and 0 available: error
|
|
77
|
+
*/
|
|
78
|
+
async function selectProvidersForCLI(specifiedProviders, providersExplicitlySpecified, providerBridge, verbose) {
|
|
79
|
+
// If providers were explicitly specified, check their availability
|
|
80
|
+
if (providersExplicitlySpecified && specifiedProviders.length > 0) {
|
|
81
|
+
const availableProviders = [];
|
|
82
|
+
for (const providerId of specifiedProviders) {
|
|
83
|
+
const isAvailable = await providerBridge.isAvailable(providerId);
|
|
84
|
+
if (isAvailable) {
|
|
85
|
+
availableProviders.push(providerId);
|
|
86
|
+
if (verbose) {
|
|
87
|
+
console.log(` ${ICONS.check} ${providerId}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else if (verbose) {
|
|
91
|
+
console.log(` ${ICONS.cross} ${providerId} ${COLORS.dim}(not available)${COLORS.reset}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (availableProviders.length === 0) {
|
|
95
|
+
return {
|
|
96
|
+
providers: [],
|
|
97
|
+
skipDiscussion: false,
|
|
98
|
+
error: 'No specified providers are available. Run "ax doctor" to check provider status.',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (availableProviders.length === 1) {
|
|
102
|
+
const singleProvider = availableProviders[0];
|
|
103
|
+
if (singleProvider === undefined) {
|
|
104
|
+
return {
|
|
105
|
+
providers: [],
|
|
106
|
+
skipDiscussion: false,
|
|
107
|
+
error: 'No specified providers are available.',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
providers: [],
|
|
112
|
+
skipDiscussion: true,
|
|
113
|
+
singleProvider,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
providers: availableProviders,
|
|
118
|
+
skipDiscussion: false,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// No providers specified - get all available and use smart selection
|
|
122
|
+
const allAvailable = await providerBridge.getAvailableProviders();
|
|
123
|
+
if (allAvailable.length === 0) {
|
|
124
|
+
return {
|
|
125
|
+
providers: [],
|
|
126
|
+
skipDiscussion: false,
|
|
127
|
+
error: 'No providers available. Run "ax doctor" to check provider status.',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (allAvailable.length === 1) {
|
|
131
|
+
const singleProvider = allAvailable[0];
|
|
132
|
+
if (singleProvider === undefined) {
|
|
133
|
+
return {
|
|
134
|
+
providers: [],
|
|
135
|
+
skipDiscussion: false,
|
|
136
|
+
error: 'No providers available.',
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (verbose) {
|
|
140
|
+
console.log(` ${ICONS.bullet} Only ${singleProvider} is available - using direct mode`);
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
providers: [],
|
|
144
|
+
skipDiscussion: true,
|
|
145
|
+
singleProvider,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (allAvailable.length === 2) {
|
|
149
|
+
if (verbose) {
|
|
150
|
+
console.log(` ${ICONS.bullet} Using both available providers: ${allAvailable.join(', ')}`);
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
providers: allAvailable,
|
|
154
|
+
skipDiscussion: false,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// 3 or more available - randomly pick 3
|
|
158
|
+
const selected = randomSelect(allAvailable, 3);
|
|
159
|
+
if (verbose) {
|
|
160
|
+
console.log(` ${ICONS.bullet} Randomly selected from ${allAvailable.length} available: ${selected.join(', ')}`);
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
providers: selected,
|
|
164
|
+
skipDiscussion: false,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Call a single provider directly (when only 1 provider available)
|
|
169
|
+
*/
|
|
170
|
+
async function callSingleProviderCLI(topic, providerId, providerBridge, timeout, options, traceStore, traceId, _startTime // Kept for API consistency, used in caller for trace start event
|
|
171
|
+
) {
|
|
172
|
+
const callStartTime = Date.now();
|
|
173
|
+
if (!options.verbose) {
|
|
174
|
+
process.stdout.write(`${ICONS.discuss} Asking ${providerId} directly... `);
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
const result = await providerBridge.execute({
|
|
178
|
+
providerId,
|
|
179
|
+
prompt: topic,
|
|
180
|
+
timeoutMs: timeout,
|
|
181
|
+
});
|
|
182
|
+
if (!options.verbose) {
|
|
183
|
+
console.log(result.success ? ICONS.check : ICONS.cross);
|
|
184
|
+
}
|
|
185
|
+
const durationMs = Date.now() - callStartTime;
|
|
186
|
+
// Emit trace event
|
|
187
|
+
await traceStore.write({
|
|
188
|
+
eventId: randomUUID(),
|
|
189
|
+
traceId,
|
|
190
|
+
type: 'discussion.end',
|
|
191
|
+
timestamp: new Date().toISOString(),
|
|
192
|
+
durationMs,
|
|
193
|
+
status: result.success ? 'success' : 'failure',
|
|
194
|
+
context: { providerId },
|
|
195
|
+
payload: {
|
|
196
|
+
success: result.success,
|
|
197
|
+
command: 'ax discuss',
|
|
198
|
+
pattern: 'direct',
|
|
199
|
+
providers: [providerId],
|
|
200
|
+
singleProviderMode: true,
|
|
201
|
+
durationMs,
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
if (!result.success) {
|
|
205
|
+
return {
|
|
206
|
+
success: false,
|
|
207
|
+
message: `Provider ${providerId} failed: ${result.error}`,
|
|
208
|
+
data: undefined,
|
|
209
|
+
exitCode: 1,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const lines = [];
|
|
213
|
+
lines.push('');
|
|
214
|
+
lines.push(`${COLORS.bold}${ICONS.discuss} Direct Response${COLORS.reset}`);
|
|
215
|
+
lines.push('─'.repeat(50));
|
|
216
|
+
lines.push(`Provider: ${COLORS.cyan}${providerId}${COLORS.reset}`);
|
|
217
|
+
lines.push(`Duration: ${(durationMs / 1000).toFixed(1)}s`);
|
|
218
|
+
lines.push(`${COLORS.dim}(Only 1 provider available - discussion skipped)${COLORS.reset}`);
|
|
219
|
+
lines.push('');
|
|
220
|
+
lines.push(`${COLORS.bold}Response${COLORS.reset}`);
|
|
221
|
+
lines.push('─'.repeat(50));
|
|
222
|
+
lines.push(result.content ?? '');
|
|
223
|
+
if (options.format === 'json') {
|
|
224
|
+
const response = {
|
|
225
|
+
success: true,
|
|
226
|
+
pattern: 'direct',
|
|
227
|
+
topic,
|
|
228
|
+
participatingProviders: [providerId],
|
|
229
|
+
failedProviders: [],
|
|
230
|
+
synthesis: result.content ?? '',
|
|
231
|
+
totalDurationMs: durationMs,
|
|
232
|
+
consensus: { method: 'direct', synthesizer: providerId, agreementScore: 1.0 },
|
|
233
|
+
metadata: { singleProviderMode: true },
|
|
234
|
+
};
|
|
235
|
+
return {
|
|
236
|
+
success: true,
|
|
237
|
+
message: undefined,
|
|
238
|
+
data: response,
|
|
239
|
+
exitCode: 0,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
success: true,
|
|
244
|
+
message: lines.join('\n'),
|
|
245
|
+
data: { success: true, synthesis: result.content, provider: providerId, durationMs },
|
|
246
|
+
exitCode: 0,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
const errorMessage = getErrorMessage(error);
|
|
251
|
+
if (!options.verbose) {
|
|
252
|
+
console.log(ICONS.cross);
|
|
253
|
+
}
|
|
254
|
+
await traceStore.write({
|
|
255
|
+
eventId: randomUUID(),
|
|
256
|
+
traceId,
|
|
257
|
+
type: 'discussion.end',
|
|
258
|
+
timestamp: new Date().toISOString(),
|
|
259
|
+
durationMs: Date.now() - callStartTime,
|
|
260
|
+
status: 'failure',
|
|
261
|
+
context: { providerId },
|
|
262
|
+
payload: {
|
|
263
|
+
success: false,
|
|
264
|
+
command: 'ax discuss',
|
|
265
|
+
providers: [providerId],
|
|
266
|
+
singleProviderMode: true,
|
|
267
|
+
error: errorMessage,
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
return {
|
|
271
|
+
success: false,
|
|
272
|
+
message: `Error calling ${providerId}: ${errorMessage}`,
|
|
273
|
+
data: undefined,
|
|
274
|
+
exitCode: 1,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// ============================================================================
|
|
35
279
|
// Provider Bridge
|
|
36
280
|
// ============================================================================
|
|
37
281
|
/**
|
|
@@ -67,9 +311,9 @@ function createProviderBridge(providerConfigs) {
|
|
|
67
311
|
try {
|
|
68
312
|
const config = providerConfigs[request.providerId];
|
|
69
313
|
const completionRequest = {
|
|
70
|
-
requestId:
|
|
314
|
+
requestId: randomUUID(),
|
|
71
315
|
messages: [{ role: 'user', content: request.prompt }],
|
|
72
|
-
model: config
|
|
316
|
+
model: getModelForProviderConfig(config),
|
|
73
317
|
systemPrompt: request.systemPrompt,
|
|
74
318
|
};
|
|
75
319
|
const response = await adapter.complete(completionRequest);
|
|
@@ -132,12 +376,39 @@ function createProviderBridge(providerConfigs) {
|
|
|
132
376
|
// ============================================================================
|
|
133
377
|
// Argument Parsing
|
|
134
378
|
// ============================================================================
|
|
379
|
+
/**
|
|
380
|
+
* Helper to parse integer with warning on invalid input
|
|
381
|
+
*/
|
|
382
|
+
function parseIntWithWarning(value, defaultValue, argName) {
|
|
383
|
+
if (value === undefined)
|
|
384
|
+
return defaultValue;
|
|
385
|
+
const parsed = parseInt(value, 10);
|
|
386
|
+
if (isNaN(parsed)) {
|
|
387
|
+
console.warn(`Warning: Invalid value "${value}" for ${argName}, using default ${defaultValue}`);
|
|
388
|
+
return defaultValue;
|
|
389
|
+
}
|
|
390
|
+
return parsed;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Helper to parse float with warning on invalid input
|
|
394
|
+
*/
|
|
395
|
+
function parseFloatWithWarning(value, defaultValue, argName) {
|
|
396
|
+
if (value === undefined)
|
|
397
|
+
return defaultValue;
|
|
398
|
+
const parsed = parseFloat(value);
|
|
399
|
+
if (isNaN(parsed)) {
|
|
400
|
+
console.warn(`Warning: Invalid value "${value}" for ${argName}, using default ${defaultValue}`);
|
|
401
|
+
return defaultValue;
|
|
402
|
+
}
|
|
403
|
+
return parsed;
|
|
404
|
+
}
|
|
135
405
|
/**
|
|
136
406
|
* Parses discuss command arguments
|
|
137
407
|
*/
|
|
138
408
|
function parseDiscussArgs(args, _options) {
|
|
139
409
|
let topic;
|
|
140
410
|
let providers = [];
|
|
411
|
+
let providersExplicitlySpecified = false;
|
|
141
412
|
let pattern = 'synthesis';
|
|
142
413
|
let rounds = DEFAULT_ROUNDS;
|
|
143
414
|
let consensus = 'synthesis';
|
|
@@ -147,6 +418,10 @@ function parseDiscussArgs(args, _options) {
|
|
|
147
418
|
// Participant options
|
|
148
419
|
let participants;
|
|
149
420
|
let agentWeight = DEFAULT_AGENT_WEIGHT_MULTIPLIER; // (INV-DISC-642)
|
|
421
|
+
// Performance optimization options
|
|
422
|
+
let fastMode = false;
|
|
423
|
+
let roundEarlyExit = true; // Enabled by default
|
|
424
|
+
let roundAgreementThreshold = 0.85;
|
|
150
425
|
// Recursive options
|
|
151
426
|
let recursive = false;
|
|
152
427
|
let maxDepth = DEFAULT_DISCUSSION_DEPTH;
|
|
@@ -160,14 +435,14 @@ function parseDiscussArgs(args, _options) {
|
|
|
160
435
|
if (arg === '--providers' && i + 1 < args.length) {
|
|
161
436
|
const providerStr = args[++i];
|
|
162
437
|
providers = providerStr?.split(',').map(p => p.trim()) ?? [];
|
|
438
|
+
providersExplicitlySpecified = true;
|
|
163
439
|
}
|
|
164
440
|
else if (arg === '--pattern' && i + 1 < args.length) {
|
|
165
441
|
pattern = args[++i];
|
|
166
442
|
}
|
|
167
443
|
else if (arg === '--rounds' && i + 1 < args.length) {
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
rounds = parsed;
|
|
444
|
+
const rawValue = args[++i];
|
|
445
|
+
rounds = parseIntWithWarning(rawValue, DEFAULT_ROUNDS, '--rounds');
|
|
171
446
|
}
|
|
172
447
|
else if (arg === '--consensus' && i + 1 < args.length) {
|
|
173
448
|
consensus = args[++i];
|
|
@@ -179,9 +454,8 @@ function parseDiscussArgs(args, _options) {
|
|
|
179
454
|
context = args[++i];
|
|
180
455
|
}
|
|
181
456
|
else if (arg === '--timeout' && i + 1 < args.length) {
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
timeout = parsed;
|
|
457
|
+
const rawValue = args[++i];
|
|
458
|
+
timeout = parseIntWithWarning(rawValue, DEFAULT_PROVIDER_TIMEOUT, '--timeout');
|
|
185
459
|
}
|
|
186
460
|
// Participant options (agents and providers)
|
|
187
461
|
else if (arg === '--participants' && i + 1 < args.length) {
|
|
@@ -191,43 +465,75 @@ function parseDiscussArgs(args, _options) {
|
|
|
191
465
|
}
|
|
192
466
|
else if (arg === '--agent-weight' && i + 1 < args.length) {
|
|
193
467
|
// Agent weight multiplier (0.5 - 3.0)
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
468
|
+
const rawValue = args[++i];
|
|
469
|
+
const parsed = parseFloatWithWarning(rawValue, DEFAULT_AGENT_WEIGHT_MULTIPLIER, '--agent-weight');
|
|
470
|
+
agentWeight = Math.max(0.5, Math.min(3.0, parsed));
|
|
471
|
+
}
|
|
472
|
+
// Performance optimization options
|
|
473
|
+
else if (arg === '--fast' || arg === '-f') {
|
|
474
|
+
// Fast mode: single round, skip cross-discussion
|
|
475
|
+
fastMode = true;
|
|
476
|
+
rounds = 1; // Override to single round
|
|
477
|
+
}
|
|
478
|
+
else if (arg === '--no-round-early-exit') {
|
|
479
|
+
// Disable round-level early exit
|
|
480
|
+
roundEarlyExit = false;
|
|
481
|
+
}
|
|
482
|
+
else if (arg === '--round-threshold' && i + 1 < args.length) {
|
|
483
|
+
// Agreement threshold for round early exit (0.5-1.0)
|
|
484
|
+
const rawValue = args[++i];
|
|
485
|
+
const parsed = parseFloatWithWarning(rawValue, 0.85, '--round-threshold');
|
|
486
|
+
roundAgreementThreshold = Math.max(0.5, Math.min(1.0, parsed));
|
|
197
487
|
}
|
|
198
488
|
// Recursive discussion options
|
|
199
489
|
else if (arg === '--recursive' || arg === '-r') {
|
|
200
490
|
recursive = true;
|
|
201
491
|
}
|
|
202
492
|
else if (arg === '--max-depth' && i + 1 < args.length) {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
493
|
+
const rawValue = args[++i];
|
|
494
|
+
maxDepth = parseIntWithWarning(rawValue, DEFAULT_DISCUSSION_DEPTH, '--max-depth');
|
|
495
|
+
// Validate max-depth is within bounds (1-4)
|
|
496
|
+
if (maxDepth < 1 || maxDepth > MAX_DISCUSSION_DEPTH) {
|
|
497
|
+
console.warn(`Warning: --max-depth must be between 1 and ${MAX_DISCUSSION_DEPTH}. Clamping to valid range.`);
|
|
498
|
+
maxDepth = Math.max(1, Math.min(maxDepth, MAX_DISCUSSION_DEPTH));
|
|
499
|
+
}
|
|
206
500
|
recursive = true; // Implies recursive
|
|
207
501
|
}
|
|
208
502
|
else if (arg === '--timeout-strategy' && i + 1 < args.length) {
|
|
209
503
|
timeoutStrategy = args[++i];
|
|
210
504
|
}
|
|
211
505
|
else if (arg === '--budget' && i + 1 < args.length) {
|
|
212
|
-
// Parse budget like "180s" or "180000"
|
|
213
|
-
const budgetStr = args[++i] ?? '
|
|
506
|
+
// Parse budget like "180s", "3m", or "180000"
|
|
507
|
+
const budgetStr = args[++i] ?? '';
|
|
214
508
|
let parsed;
|
|
215
509
|
if (budgetStr.endsWith('s')) {
|
|
216
|
-
|
|
510
|
+
const numPart = budgetStr.slice(0, -1);
|
|
511
|
+
parsed = parseInt(numPart, 10) * 1000;
|
|
512
|
+
if (isNaN(parsed)) {
|
|
513
|
+
console.warn(`Warning: Invalid budget format "${budgetStr}", expected format like "180s", "3m", or "180000". Using default.`);
|
|
514
|
+
parsed = DEFAULT_TOTAL_BUDGET_MS;
|
|
515
|
+
}
|
|
217
516
|
}
|
|
218
517
|
else if (budgetStr.endsWith('m')) {
|
|
219
|
-
|
|
518
|
+
const numPart = budgetStr.slice(0, -1);
|
|
519
|
+
parsed = parseInt(numPart, 10) * 60000;
|
|
520
|
+
if (isNaN(parsed)) {
|
|
521
|
+
console.warn(`Warning: Invalid budget format "${budgetStr}", expected format like "180s", "3m", or "180000". Using default.`);
|
|
522
|
+
parsed = DEFAULT_TOTAL_BUDGET_MS;
|
|
523
|
+
}
|
|
220
524
|
}
|
|
221
525
|
else {
|
|
222
526
|
parsed = parseInt(budgetStr, 10);
|
|
527
|
+
if (isNaN(parsed)) {
|
|
528
|
+
console.warn(`Warning: Invalid budget format "${budgetStr}", expected format like "180s", "3m", or "180000". Using default.`);
|
|
529
|
+
parsed = DEFAULT_TOTAL_BUDGET_MS;
|
|
530
|
+
}
|
|
223
531
|
}
|
|
224
|
-
|
|
225
|
-
totalBudget = parsed;
|
|
532
|
+
totalBudget = parsed;
|
|
226
533
|
}
|
|
227
534
|
else if (arg === '--max-calls' && i + 1 < args.length) {
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
maxCalls = parsed;
|
|
535
|
+
const rawValue = args[++i];
|
|
536
|
+
maxCalls = parseIntWithWarning(rawValue, DEFAULT_MAX_TOTAL_CALLS, '--max-calls');
|
|
231
537
|
}
|
|
232
538
|
else if (arg === '--early-exit') {
|
|
233
539
|
earlyExit = true;
|
|
@@ -236,33 +542,32 @@ function parseDiscussArgs(args, _options) {
|
|
|
236
542
|
earlyExit = false;
|
|
237
543
|
}
|
|
238
544
|
else if (arg === '--confidence-threshold' && i + 1 < args.length) {
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
confidenceThreshold = parsed;
|
|
545
|
+
const rawValue = args[++i];
|
|
546
|
+
confidenceThreshold = parseFloatWithWarning(rawValue, DEFAULT_CONFIDENCE_THRESHOLD, '--confidence-threshold');
|
|
242
547
|
}
|
|
243
548
|
else if (arg !== undefined && !arg.startsWith('-')) {
|
|
244
549
|
// Positional argument is the topic
|
|
245
550
|
if (topic === undefined) {
|
|
246
|
-
// Collect
|
|
551
|
+
// Collect consecutive non-flag args as topic, then continue parsing remaining flags
|
|
247
552
|
const topicParts = [];
|
|
248
|
-
|
|
553
|
+
let j = i;
|
|
554
|
+
for (; j < args.length; j++) {
|
|
249
555
|
const part = args[j];
|
|
250
556
|
if (part?.startsWith('-'))
|
|
251
557
|
break;
|
|
252
558
|
topicParts.push(part ?? '');
|
|
253
559
|
}
|
|
254
560
|
topic = topicParts.join(' ');
|
|
255
|
-
|
|
561
|
+
// Skip over the topic parts we just consumed (minus 1 because the for loop will increment i)
|
|
562
|
+
i = j - 1;
|
|
256
563
|
}
|
|
257
564
|
}
|
|
258
565
|
}
|
|
259
|
-
//
|
|
260
|
-
if (providers.length === 0) {
|
|
261
|
-
providers = [...DEFAULT_PROVIDERS];
|
|
262
|
-
}
|
|
566
|
+
// Note: providers are NOT defaulted here - smart selection happens in the command handler
|
|
263
567
|
return {
|
|
264
568
|
topic,
|
|
265
569
|
providers,
|
|
570
|
+
providersExplicitlySpecified,
|
|
266
571
|
pattern,
|
|
267
572
|
rounds,
|
|
268
573
|
consensus,
|
|
@@ -271,6 +576,9 @@ function parseDiscussArgs(args, _options) {
|
|
|
271
576
|
timeout,
|
|
272
577
|
participants,
|
|
273
578
|
agentWeight,
|
|
579
|
+
fastMode,
|
|
580
|
+
roundEarlyExit,
|
|
581
|
+
roundAgreementThreshold,
|
|
274
582
|
recursive,
|
|
275
583
|
maxDepth,
|
|
276
584
|
timeoutStrategy,
|
|
@@ -280,36 +588,112 @@ function parseDiscussArgs(args, _options) {
|
|
|
280
588
|
confidenceThreshold,
|
|
281
589
|
};
|
|
282
590
|
}
|
|
283
|
-
// ============================================================================
|
|
284
|
-
// Progress Display
|
|
285
|
-
// ============================================================================
|
|
286
591
|
/**
|
|
287
|
-
* Creates a progress handler for verbose output
|
|
592
|
+
* Creates a progress handler for verbose output and trace event emission (Phase 2)
|
|
593
|
+
* Emits granular trace events: discussion.provider, discussion.round, discussion.consensus
|
|
288
594
|
*/
|
|
289
|
-
function createProgressHandler(verbose) {
|
|
290
|
-
if (!verbose) {
|
|
291
|
-
return () => { }; // No-op
|
|
292
|
-
}
|
|
595
|
+
function createProgressHandler(verbose, traceContext) {
|
|
293
596
|
return (event) => {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
597
|
+
// Console output for verbose mode
|
|
598
|
+
if (verbose) {
|
|
599
|
+
switch (event.type) {
|
|
600
|
+
case 'round_start':
|
|
601
|
+
console.log(`\n${COLORS.bold}Round ${event.round}${COLORS.reset}`);
|
|
602
|
+
break;
|
|
603
|
+
case 'provider_start':
|
|
604
|
+
process.stdout.write(` ${ICONS.bullet} ${event.provider}: `);
|
|
605
|
+
break;
|
|
606
|
+
case 'provider_complete':
|
|
607
|
+
console.log(`${ICONS.check} ${COLORS.dim}(${event.message ?? 'done'})${COLORS.reset}`);
|
|
608
|
+
break;
|
|
609
|
+
case 'round_complete':
|
|
610
|
+
console.log(` ${ICONS.arrow} Round ${event.round} complete`);
|
|
611
|
+
break;
|
|
612
|
+
case 'synthesis_start':
|
|
613
|
+
console.log(`\n${COLORS.bold}Synthesizing...${COLORS.reset}`);
|
|
614
|
+
break;
|
|
615
|
+
case 'synthesis_complete':
|
|
616
|
+
console.log(`${ICONS.check} Synthesis complete`);
|
|
617
|
+
break;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
// Emit trace events for dashboard visibility (Phase 2: Granular Events)
|
|
621
|
+
if (traceContext) {
|
|
622
|
+
const { traceStore, traceId } = traceContext;
|
|
623
|
+
switch (event.type) {
|
|
624
|
+
case 'provider_complete':
|
|
625
|
+
// Emit discussion.provider trace event
|
|
626
|
+
if (event.provider && event.round !== undefined) {
|
|
627
|
+
traceStore.write({
|
|
628
|
+
eventId: randomUUID(),
|
|
629
|
+
traceId,
|
|
630
|
+
type: 'discussion.provider',
|
|
631
|
+
timestamp: event.timestamp,
|
|
632
|
+
durationMs: event.durationMs,
|
|
633
|
+
status: event.success ? 'success' : 'failure',
|
|
634
|
+
context: {
|
|
635
|
+
providerId: event.provider,
|
|
636
|
+
},
|
|
637
|
+
payload: {
|
|
638
|
+
providerId: event.provider,
|
|
639
|
+
roundNumber: event.round,
|
|
640
|
+
success: event.success ?? false,
|
|
641
|
+
durationMs: event.durationMs ?? 0,
|
|
642
|
+
tokenCount: event.tokenCount,
|
|
643
|
+
role: event.role,
|
|
644
|
+
error: event.error,
|
|
645
|
+
},
|
|
646
|
+
}).catch((err) => {
|
|
647
|
+
// Fire-and-forget with error logging
|
|
648
|
+
console.error('[discuss] Failed to write provider trace event:', err);
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
break;
|
|
652
|
+
case 'round_complete':
|
|
653
|
+
// Emit discussion.round trace event
|
|
654
|
+
if (event.round !== undefined) {
|
|
655
|
+
traceStore.write({
|
|
656
|
+
eventId: randomUUID(),
|
|
657
|
+
traceId,
|
|
658
|
+
type: 'discussion.round',
|
|
659
|
+
timestamp: event.timestamp,
|
|
660
|
+
durationMs: event.durationMs,
|
|
661
|
+
context: {},
|
|
662
|
+
payload: {
|
|
663
|
+
roundNumber: event.round,
|
|
664
|
+
participatingProviders: event.participatingProviders ?? [],
|
|
665
|
+
failedProviders: event.failedProviders,
|
|
666
|
+
responseCount: event.responseCount ?? 0,
|
|
667
|
+
durationMs: event.durationMs ?? 0,
|
|
668
|
+
},
|
|
669
|
+
}).catch((err) => {
|
|
670
|
+
console.error('[discuss] Failed to write round trace event:', err);
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
break;
|
|
674
|
+
case 'consensus_complete':
|
|
675
|
+
// Emit discussion.consensus trace event
|
|
676
|
+
traceStore.write({
|
|
677
|
+
eventId: randomUUID(),
|
|
678
|
+
traceId,
|
|
679
|
+
type: 'discussion.consensus',
|
|
680
|
+
timestamp: event.timestamp,
|
|
681
|
+
durationMs: event.durationMs,
|
|
682
|
+
status: event.success ? 'success' : 'failure',
|
|
683
|
+
context: {},
|
|
684
|
+
payload: {
|
|
685
|
+
method: event.consensusMethod ?? 'synthesis',
|
|
686
|
+
success: event.success ?? false,
|
|
687
|
+
winner: event.winner,
|
|
688
|
+
confidence: event.confidence,
|
|
689
|
+
votes: event.votes,
|
|
690
|
+
durationMs: event.durationMs ?? 0,
|
|
691
|
+
},
|
|
692
|
+
}).catch((err) => {
|
|
693
|
+
console.error('[discuss] Failed to write consensus trace event:', err);
|
|
694
|
+
});
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
313
697
|
}
|
|
314
698
|
};
|
|
315
699
|
}
|
|
@@ -426,10 +810,15 @@ ${COLORS.bold}Basic Options:${COLORS.reset}
|
|
|
426
810
|
--consensus Consensus method: ${consensusMethods}
|
|
427
811
|
--synthesizer Provider for synthesis (default: claude)
|
|
428
812
|
--context Additional context for the discussion
|
|
429
|
-
--timeout Per-provider timeout in ms (default:
|
|
813
|
+
--timeout Per-provider timeout in ms (default: 600000/10min, max: 30 min)
|
|
430
814
|
--verbose, -v Show detailed progress
|
|
431
815
|
--format Output format: text (default) or json
|
|
432
816
|
|
|
817
|
+
${COLORS.bold}Performance Options:${COLORS.reset}
|
|
818
|
+
--fast, -f Fast mode: single round, skip cross-discussion (~50% faster)
|
|
819
|
+
--no-round-early-exit Disable round-level early exit on high agreement
|
|
820
|
+
--round-threshold Agreement threshold for round early exit (default: 0.85)
|
|
821
|
+
|
|
433
822
|
${COLORS.bold}Recursive Discussion Options:${COLORS.reset}
|
|
434
823
|
--recursive, -r Enable recursive sub-discussions
|
|
435
824
|
--max-depth Maximum discussion depth (default: 2, max: 4)
|
|
@@ -462,6 +851,10 @@ ${COLORS.bold}Examples:${COLORS.reset}
|
|
|
462
851
|
ax discuss --pattern voting "Which framework: React, Vue, or Angular?"
|
|
463
852
|
ax discuss --verbose --rounds 3 "Optimize database queries"
|
|
464
853
|
|
|
854
|
+
${COLORS.cyan}# Fast mode (single round, ~50% faster)${COLORS.reset}
|
|
855
|
+
ax discuss --fast "What is TypeScript?"
|
|
856
|
+
ax discuss -f "Explain microservices"
|
|
857
|
+
|
|
465
858
|
${COLORS.cyan}# Recursive discussions${COLORS.reset}
|
|
466
859
|
ax discuss --recursive "Complex architectural decision"
|
|
467
860
|
ax discuss --recursive --max-depth 3 "Design a distributed system"
|
|
@@ -478,13 +871,15 @@ ${COLORS.bold}Examples:${COLORS.reset}
|
|
|
478
871
|
// Main Command Handler
|
|
479
872
|
// ============================================================================
|
|
480
873
|
/**
|
|
481
|
-
* Discuss command handler
|
|
874
|
+
* Discuss command handler with trace integration
|
|
482
875
|
*/
|
|
483
876
|
export async function discussCommand(args, options) {
|
|
484
877
|
// Show help if requested
|
|
485
878
|
if (args.length === 0 || args[0] === 'help' || options.help) {
|
|
486
879
|
return showDiscussHelp();
|
|
487
880
|
}
|
|
881
|
+
// Initialize bootstrap to get SQLite trace store
|
|
882
|
+
await bootstrap();
|
|
488
883
|
// Handle 'quick' subcommand - use quick synthesis with 2-3 providers
|
|
489
884
|
if (args[0] === 'quick') {
|
|
490
885
|
const quickArgs = args.slice(1);
|
|
@@ -515,55 +910,59 @@ export async function discussCommand(args, options) {
|
|
|
515
910
|
exitCode: 1,
|
|
516
911
|
};
|
|
517
912
|
}
|
|
518
|
-
// Validate providers
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
if (parsed.providers.length < 2) {
|
|
531
|
-
return {
|
|
532
|
-
success: false,
|
|
533
|
-
message: 'Error: At least 2 providers are required for discussion.',
|
|
534
|
-
data: undefined,
|
|
535
|
-
exitCode: 1,
|
|
536
|
-
};
|
|
913
|
+
// Validate explicitly specified providers
|
|
914
|
+
if (parsed.providersExplicitlySpecified) {
|
|
915
|
+
const invalidProviders = parsed.providers.filter(p => PROVIDER_CONFIGS[p] === undefined);
|
|
916
|
+
if (invalidProviders.length > 0) {
|
|
917
|
+
const available = Object.keys(PROVIDER_CONFIGS).join(', ');
|
|
918
|
+
return {
|
|
919
|
+
success: false,
|
|
920
|
+
message: `Error: Unknown provider(s): ${invalidProviders.join(', ')}\nAvailable: ${available}`,
|
|
921
|
+
data: undefined,
|
|
922
|
+
exitCode: 1,
|
|
923
|
+
};
|
|
924
|
+
}
|
|
537
925
|
}
|
|
538
926
|
// Create provider bridge
|
|
539
927
|
const providerBridge = createProviderBridge(PROVIDER_CONFIGS);
|
|
540
|
-
//
|
|
928
|
+
// Create trace for dashboard visibility
|
|
929
|
+
const traceStore = getTraceStore();
|
|
930
|
+
const traceId = randomUUID();
|
|
931
|
+
const startTime = new Date().toISOString();
|
|
932
|
+
// Smart provider selection
|
|
541
933
|
if (options.verbose) {
|
|
542
|
-
console.log(`${COLORS.bold}
|
|
543
|
-
}
|
|
544
|
-
const availableProviders = [];
|
|
545
|
-
for (const providerId of parsed.providers) {
|
|
546
|
-
const isAvailable = await providerBridge.isAvailable(providerId);
|
|
547
|
-
if (isAvailable) {
|
|
548
|
-
availableProviders.push(providerId);
|
|
549
|
-
if (options.verbose) {
|
|
550
|
-
console.log(` ${ICONS.check} ${providerId}`);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
else {
|
|
554
|
-
if (options.verbose) {
|
|
555
|
-
console.log(` ${ICONS.cross} ${providerId} ${COLORS.dim}(not available)${COLORS.reset}`);
|
|
556
|
-
}
|
|
557
|
-
}
|
|
934
|
+
console.log(`${COLORS.bold}Selecting providers...${COLORS.reset}`);
|
|
558
935
|
}
|
|
559
|
-
|
|
936
|
+
const selection = await selectProvidersForCLI(parsed.providers, parsed.providersExplicitlySpecified, providerBridge, options.verbose);
|
|
937
|
+
// Handle selection errors
|
|
938
|
+
if (selection.error) {
|
|
560
939
|
return {
|
|
561
940
|
success: false,
|
|
562
|
-
message: `Error:
|
|
941
|
+
message: `Error: ${selection.error}`,
|
|
563
942
|
data: undefined,
|
|
564
943
|
exitCode: 1,
|
|
565
944
|
};
|
|
566
945
|
}
|
|
946
|
+
// If only 1 provider available, skip discussion and call directly
|
|
947
|
+
if (selection.skipDiscussion && selection.singleProvider) {
|
|
948
|
+
// Emit run.start trace event
|
|
949
|
+
await traceStore.write({
|
|
950
|
+
eventId: randomUUID(),
|
|
951
|
+
traceId,
|
|
952
|
+
type: 'discussion.start',
|
|
953
|
+
timestamp: startTime,
|
|
954
|
+
context: { providerId: selection.singleProvider },
|
|
955
|
+
payload: {
|
|
956
|
+
command: 'ax discuss',
|
|
957
|
+
topic: parsed.topic,
|
|
958
|
+
pattern: 'direct',
|
|
959
|
+
providers: [selection.singleProvider],
|
|
960
|
+
singleProviderMode: true,
|
|
961
|
+
},
|
|
962
|
+
});
|
|
963
|
+
return callSingleProviderCLI(parsed.topic, selection.singleProvider, providerBridge, parsed.timeout, options, traceStore, traceId, startTime);
|
|
964
|
+
}
|
|
965
|
+
const availableProviders = selection.providers;
|
|
567
966
|
// Build discussion config
|
|
568
967
|
const config = {
|
|
569
968
|
pattern: parsed.pattern,
|
|
@@ -583,6 +982,13 @@ export async function discussCommand(args, options) {
|
|
|
583
982
|
minProviders: 2,
|
|
584
983
|
temperature: 0.7,
|
|
585
984
|
agentWeightMultiplier: parsed.agentWeight,
|
|
985
|
+
// Performance optimization options
|
|
986
|
+
fastMode: parsed.fastMode,
|
|
987
|
+
roundEarlyExit: {
|
|
988
|
+
enabled: parsed.roundEarlyExit,
|
|
989
|
+
agreementThreshold: parsed.roundAgreementThreshold,
|
|
990
|
+
minRounds: 1,
|
|
991
|
+
},
|
|
586
992
|
// Include participants if specified via --participants option
|
|
587
993
|
...(parsed.participants !== undefined && { participants: parsed.participants }),
|
|
588
994
|
};
|
|
@@ -594,6 +1000,9 @@ export async function discussCommand(args, options) {
|
|
|
594
1000
|
console.log(` Pattern: ${PATTERN_NAMES[parsed.pattern] ?? parsed.pattern}`);
|
|
595
1001
|
console.log(` Providers: ${availableProviders.join(', ')}`);
|
|
596
1002
|
console.log(` Rounds: ${parsed.rounds}`);
|
|
1003
|
+
if (parsed.fastMode) {
|
|
1004
|
+
console.log(` ${COLORS.cyan}Fast Mode: enabled (single round)${COLORS.reset}`);
|
|
1005
|
+
}
|
|
597
1006
|
if (parsed.participants !== undefined && parsed.participants.length > 0) {
|
|
598
1007
|
const agentCount = parsed.participants.filter(p => p.type === 'agent').length;
|
|
599
1008
|
const providerCount = parsed.participants.filter(p => p.type === 'provider').length;
|
|
@@ -610,9 +1019,37 @@ export async function discussCommand(args, options) {
|
|
|
610
1019
|
}
|
|
611
1020
|
else {
|
|
612
1021
|
// Simple progress indicator for non-verbose mode
|
|
1022
|
+
const fastLabel = parsed.fastMode ? ' (fast)' : '';
|
|
613
1023
|
const recursiveLabel = parsed.recursive ? ' (recursive)' : '';
|
|
614
|
-
process.stdout.write(`${ICONS.discuss} Discussing with ${availableProviders.length} providers${recursiveLabel}... `);
|
|
1024
|
+
process.stdout.write(`${ICONS.discuss} Discussing with ${availableProviders.length} providers${fastLabel}${recursiveLabel}... `);
|
|
615
1025
|
}
|
|
1026
|
+
// Emit run.start trace event with full topic
|
|
1027
|
+
// INV-TR-010: Include participating providers for drill-down
|
|
1028
|
+
const startEvent = {
|
|
1029
|
+
eventId: randomUUID(),
|
|
1030
|
+
traceId,
|
|
1031
|
+
type: 'discussion.start',
|
|
1032
|
+
timestamp: startTime,
|
|
1033
|
+
context: {
|
|
1034
|
+
// For discussions with multiple providers, store first provider as primary
|
|
1035
|
+
// All providers are listed in payload.providers
|
|
1036
|
+
providerId: availableProviders[0], // Primary provider for filtering
|
|
1037
|
+
},
|
|
1038
|
+
payload: {
|
|
1039
|
+
command: 'ax discuss',
|
|
1040
|
+
topic: parsed.topic, // Full topic for dashboard
|
|
1041
|
+
topicLength: parsed.topic.length,
|
|
1042
|
+
pattern: parsed.pattern,
|
|
1043
|
+
consensus: parsed.consensus,
|
|
1044
|
+
providers: availableProviders,
|
|
1045
|
+
rounds: parsed.rounds,
|
|
1046
|
+
recursive: parsed.recursive,
|
|
1047
|
+
maxDepth: parsed.recursive ? parsed.maxDepth : undefined,
|
|
1048
|
+
context: parsed.context,
|
|
1049
|
+
},
|
|
1050
|
+
};
|
|
1051
|
+
await traceStore.write(startEvent);
|
|
1052
|
+
const discussStartTime = Date.now();
|
|
616
1053
|
try {
|
|
617
1054
|
// Choose executor based on recursive flag
|
|
618
1055
|
let result;
|
|
@@ -642,7 +1079,7 @@ export async function discussCommand(args, options) {
|
|
|
642
1079
|
},
|
|
643
1080
|
});
|
|
644
1081
|
result = await recursiveExecutor.execute(config, {
|
|
645
|
-
onProgress: createProgressHandler(options.verbose),
|
|
1082
|
+
onProgress: createProgressHandler(options.verbose, { traceStore, traceId }),
|
|
646
1083
|
});
|
|
647
1084
|
}
|
|
648
1085
|
else {
|
|
@@ -653,13 +1090,55 @@ export async function discussCommand(args, options) {
|
|
|
653
1090
|
checkProviderHealth: false, // Already checked above
|
|
654
1091
|
});
|
|
655
1092
|
result = await discussionExecutor.execute(config, {
|
|
656
|
-
onProgress: createProgressHandler(options.verbose),
|
|
1093
|
+
onProgress: createProgressHandler(options.verbose, { traceStore, traceId }),
|
|
657
1094
|
});
|
|
658
1095
|
}
|
|
659
1096
|
// Clear simple progress indicator if not verbose
|
|
660
1097
|
if (!options.verbose) {
|
|
661
1098
|
console.log(result.success ? ICONS.check : ICONS.cross);
|
|
662
1099
|
}
|
|
1100
|
+
// Emit discussion.end trace event with full details
|
|
1101
|
+
const durationMs = Date.now() - discussStartTime;
|
|
1102
|
+
// Extract provider responses from rounds for dashboard
|
|
1103
|
+
const providerResponses = {};
|
|
1104
|
+
if (result.rounds) {
|
|
1105
|
+
for (const round of result.rounds) {
|
|
1106
|
+
for (const response of round.responses ?? []) {
|
|
1107
|
+
const providerId = response.provider ?? 'unknown';
|
|
1108
|
+
if (!providerResponses[providerId]) {
|
|
1109
|
+
providerResponses[providerId] = [];
|
|
1110
|
+
}
|
|
1111
|
+
providerResponses[providerId].push(response.content ?? '');
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
// INV-TR-010: Include participating providers for drill-down
|
|
1116
|
+
const endEvent = {
|
|
1117
|
+
eventId: randomUUID(),
|
|
1118
|
+
traceId,
|
|
1119
|
+
type: 'discussion.end',
|
|
1120
|
+
timestamp: new Date().toISOString(),
|
|
1121
|
+
durationMs,
|
|
1122
|
+
status: result.success ? 'success' : 'failure',
|
|
1123
|
+
context: {
|
|
1124
|
+
providerId: availableProviders[0], // Primary provider for filtering
|
|
1125
|
+
},
|
|
1126
|
+
payload: {
|
|
1127
|
+
success: result.success,
|
|
1128
|
+
command: 'ax discuss',
|
|
1129
|
+
pattern: parsed.pattern,
|
|
1130
|
+
providers: availableProviders,
|
|
1131
|
+
roundCount: result.rounds?.length ?? parsed.rounds,
|
|
1132
|
+
durationMs,
|
|
1133
|
+
consensusReached: result.consensus !== undefined,
|
|
1134
|
+
consensus: result.consensus, // Consensus metadata (method, agreementScore, etc.)
|
|
1135
|
+
synthesis: result.synthesis, // Final synthesized text for dashboard
|
|
1136
|
+
responses: providerResponses, // Provider responses by provider ID
|
|
1137
|
+
// Summary for quick view
|
|
1138
|
+
totalResponses: Object.values(providerResponses).flat().length,
|
|
1139
|
+
},
|
|
1140
|
+
};
|
|
1141
|
+
await traceStore.write(endEvent);
|
|
663
1142
|
// Handle JSON output
|
|
664
1143
|
if (options.format === 'json') {
|
|
665
1144
|
return {
|
|
@@ -683,7 +1162,27 @@ export async function discussCommand(args, options) {
|
|
|
683
1162
|
if (!options.verbose) {
|
|
684
1163
|
console.log(ICONS.cross);
|
|
685
1164
|
}
|
|
1165
|
+
// Emit error trace event
|
|
1166
|
+
// INV-TR-010: Include participating providers for drill-down
|
|
1167
|
+
const durationMs = Date.now() - discussStartTime;
|
|
686
1168
|
const errorMessage = getErrorMessage(error);
|
|
1169
|
+
await traceStore.write({
|
|
1170
|
+
eventId: randomUUID(),
|
|
1171
|
+
traceId,
|
|
1172
|
+
type: 'discussion.end',
|
|
1173
|
+
timestamp: new Date().toISOString(),
|
|
1174
|
+
durationMs,
|
|
1175
|
+
status: 'failure',
|
|
1176
|
+
context: {
|
|
1177
|
+
providerId: availableProviders[0], // Primary provider for filtering
|
|
1178
|
+
},
|
|
1179
|
+
payload: {
|
|
1180
|
+
success: false,
|
|
1181
|
+
command: 'ax discuss',
|
|
1182
|
+
providers: availableProviders,
|
|
1183
|
+
error: errorMessage,
|
|
1184
|
+
},
|
|
1185
|
+
});
|
|
687
1186
|
return {
|
|
688
1187
|
success: false,
|
|
689
1188
|
message: `Error during discussion: ${errorMessage}`,
|