@moda-ai/cli 1.1.1 → 1.3.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/README.md +36 -0
- package/dist/agent.d.ts +5 -0
- package/dist/agent.js +477 -0
- package/dist/analyst-tui.d.ts +81 -0
- package/dist/analyst-tui.js +314 -0
- package/dist/api-client.d.ts +4 -0
- package/dist/api-client.js +20 -3
- package/dist/auth.d.ts +22 -3
- package/dist/auth.js +230 -66
- package/dist/cli.d.ts +20 -3
- package/dist/cli.js +745 -93
- package/dist/command-context.d.ts +34 -0
- package/dist/command-context.js +38 -0
- package/dist/command-registry.d.ts +22 -0
- package/dist/command-registry.js +15 -0
- package/dist/config.d.ts +40 -1
- package/dist/config.js +70 -3
- package/dist/doctor.d.ts +79 -0
- package/dist/doctor.js +1111 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +9 -0
- package/dist/harness-report.d.ts +138 -0
- package/dist/harness-report.js +1464 -0
- package/dist/harness.d.ts +233 -0
- package/dist/harness.js +3264 -0
- package/dist/init/index.d.ts +30 -1
- package/dist/init/index.js +389 -23
- package/dist/init/rules.d.ts +5 -1
- package/dist/init/rules.js +22 -16
- package/dist/init/sdk-guide.d.ts +3 -0
- package/dist/init/sdk-guide.js +397 -0
- package/dist/init/tenant.js +12 -3
- package/dist/keys.d.ts +47 -0
- package/dist/keys.js +136 -0
- package/dist/output.d.ts +118 -0
- package/dist/output.js +228 -0
- package/dist/production-intelligence.d.ts +134 -0
- package/dist/production-intelligence.js +1113 -0
- package/dist/profile-commands.d.ts +10 -0
- package/dist/profile-commands.js +252 -0
- package/dist/profiles.d.ts +90 -0
- package/dist/profiles.js +432 -0
- package/dist/prompts.d.ts +6 -0
- package/dist/prompts.js +368 -0
- package/dist/schemas.d.ts +4 -4
- package/dist/skills.d.ts +25 -0
- package/dist/skills.js +468 -0
- package/dist/sync.d.ts +5 -0
- package/dist/sync.js +27 -0
- package/dist/telemetry.d.ts +18 -0
- package/dist/telemetry.js +63 -0
- package/dist/tui.d.ts +40 -0
- package/dist/tui.js +387 -0
- package/dist/workspace.d.ts +12 -0
- package/dist/workspace.js +1294 -0
- package/hooks/moda-snapshot.sh +41 -0
- package/package.json +10 -7
- package/skills/moda-cli/SKILL.md +48 -4
- package/skills/integrate-moda/SKILL.md +0 -63
|
@@ -0,0 +1,1113 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { callControlAPI, callDataAPI } from './api-client.js';
|
|
4
|
+
import { CliInputError } from './errors.js';
|
|
5
|
+
import { asNumber, asRecord, asString } from './transforms.js';
|
|
6
|
+
const INVESTIGATION_SCHEMA_VERSION = 'production_investigation.v0.1';
|
|
7
|
+
const OVERVIEW_SCHEMA_VERSION = 'production_overview.v0.1';
|
|
8
|
+
const ASK_SCHEMA_VERSION = 'production_ask.v0.1';
|
|
9
|
+
export async function runProductionCommand(context) {
|
|
10
|
+
const daysBack = numberFlag(context.flags, 'days-back', 7);
|
|
11
|
+
if (context.command === 'overview') {
|
|
12
|
+
const briefing = await loadOverviewBriefing({ daysBack, cwd: context.cwd });
|
|
13
|
+
if (context.flags.raw === 'true') {
|
|
14
|
+
context.output.writeData(briefing.rawOverview, {
|
|
15
|
+
summary: 'Loaded raw Moda overview.',
|
|
16
|
+
nextCommands: ['moda overview', `moda investigate --days-back=${daysBack}`],
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
else if (context.outputMode === 'human') {
|
|
20
|
+
if (!context.quiet)
|
|
21
|
+
console.log(renderOverviewBriefingHuman(briefing));
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
context.output.writeData(briefing, overviewAgentOptions(briefing));
|
|
25
|
+
}
|
|
26
|
+
return { exitCode: 0 };
|
|
27
|
+
}
|
|
28
|
+
if (context.command === 'ask') {
|
|
29
|
+
const question = context.positionals.join(' ').trim();
|
|
30
|
+
if (!question) {
|
|
31
|
+
throw new CliInputError('<question> is required', 'Usage: moda ask "what should I fix first?" [--days-back=N]');
|
|
32
|
+
}
|
|
33
|
+
const result = await askModa({ question, daysBack, cwd: context.cwd, profile: context.profile });
|
|
34
|
+
if (context.outputMode === 'human') {
|
|
35
|
+
if (!context.quiet)
|
|
36
|
+
console.log(renderAskHuman(result));
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
context.output.writeData(result, askAgentOptions(result));
|
|
40
|
+
}
|
|
41
|
+
return { exitCode: 0 };
|
|
42
|
+
}
|
|
43
|
+
if (context.command === 'why-failed') {
|
|
44
|
+
const runId = context.positional;
|
|
45
|
+
if (!runId) {
|
|
46
|
+
throw new CliInputError('<run_id> is required', 'Usage: moda why-failed <run_id>');
|
|
47
|
+
}
|
|
48
|
+
const investigation = await investigateProduction({ daysBack, cwd: context.cwd, runId });
|
|
49
|
+
writeInvestigation(context, investigation);
|
|
50
|
+
return { exitCode: 0 };
|
|
51
|
+
}
|
|
52
|
+
if (context.command === 'failures') {
|
|
53
|
+
const investigation = await investigateProduction({
|
|
54
|
+
daysBack,
|
|
55
|
+
cwd: context.cwd,
|
|
56
|
+
failureOnly: true,
|
|
57
|
+
toolName: context.flags.tool,
|
|
58
|
+
});
|
|
59
|
+
writeInvestigation(context, investigation);
|
|
60
|
+
return { exitCode: 0 };
|
|
61
|
+
}
|
|
62
|
+
if (context.command === 'investigate') {
|
|
63
|
+
const investigation = await investigateProduction({
|
|
64
|
+
daysBack,
|
|
65
|
+
cwd: context.cwd,
|
|
66
|
+
runId: context.flags.run,
|
|
67
|
+
conversationId: context.flags.conversation,
|
|
68
|
+
toolName: context.flags.tool,
|
|
69
|
+
loopId: context.flags.loop,
|
|
70
|
+
});
|
|
71
|
+
writeInvestigation(context, investigation);
|
|
72
|
+
return { exitCode: 0 };
|
|
73
|
+
}
|
|
74
|
+
throw new CliInputError(`Unknown production command '${context.command}'`);
|
|
75
|
+
}
|
|
76
|
+
export async function loadOverviewBriefing(options) {
|
|
77
|
+
const endpoint = `/overview?days_back=${options.daysBack}`;
|
|
78
|
+
const overview = await callDataAPI(endpoint);
|
|
79
|
+
const hints = loadHarnessHints(options.cwd);
|
|
80
|
+
const evidenceRefs = [
|
|
81
|
+
{
|
|
82
|
+
id: 'overview:data',
|
|
83
|
+
kind: 'data_api',
|
|
84
|
+
label: `Overview for last ${options.daysBack} day(s)`,
|
|
85
|
+
endpoint,
|
|
86
|
+
value: overviewMetrics(overview),
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
const findings = findingsFromOverview(overview, options.daysBack, hints, evidenceRefs);
|
|
90
|
+
const topFinding = findings[0] ?? null;
|
|
91
|
+
const conversations = numberAt(overview, ['conversations', 'total']);
|
|
92
|
+
const recent = recentActivity(overview)[0];
|
|
93
|
+
const dataFlowStatus = conversations > 0 || recent ? 'flowing' : 'quiet';
|
|
94
|
+
const nextCommand = topFinding
|
|
95
|
+
? `moda investigate --days-back=${options.daysBack}`
|
|
96
|
+
: 'moda doctor --online';
|
|
97
|
+
return {
|
|
98
|
+
schemaVersion: OVERVIEW_SCHEMA_VERSION,
|
|
99
|
+
generatedAt: new Date().toISOString(),
|
|
100
|
+
daysBack: options.daysBack,
|
|
101
|
+
status: topFinding
|
|
102
|
+
? 'attention'
|
|
103
|
+
: dataFlowStatus === 'flowing'
|
|
104
|
+
? 'healthy'
|
|
105
|
+
: 'quiet',
|
|
106
|
+
dataFlow: {
|
|
107
|
+
status: dataFlowStatus,
|
|
108
|
+
conversations,
|
|
109
|
+
...(recent ? { lastEvent: recent } : {}),
|
|
110
|
+
},
|
|
111
|
+
harnessSync: localSyncBrief(options.cwd, '.moda/sync-state.json', 'Harness graph'),
|
|
112
|
+
promptSync: localSyncBrief(options.cwd, '.moda/prompts.lock.json', 'Prompt versions'),
|
|
113
|
+
skillSync: localSkillBrief(options.cwd),
|
|
114
|
+
metrics: overviewMetrics(overview),
|
|
115
|
+
topFinding,
|
|
116
|
+
evidenceRefs,
|
|
117
|
+
nextCommand,
|
|
118
|
+
rawOverview: overview,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
export async function investigateProduction(options) {
|
|
122
|
+
const warnings = [];
|
|
123
|
+
const sourceStatus = {};
|
|
124
|
+
const hints = loadHarnessHints(options.cwd);
|
|
125
|
+
const overviewEndpoint = `/overview?days_back=${options.daysBack}`;
|
|
126
|
+
const toolEndpoint = options.toolName
|
|
127
|
+
? `/tool-failures/${encodeURIComponent(options.toolName)}?days_back=${options.daysBack}&limit=5`
|
|
128
|
+
: `/tool-failures?days_back=${options.daysBack}`;
|
|
129
|
+
const frustrationsEndpoint = `/frustrations?days_back=${options.daysBack}&limit=5`;
|
|
130
|
+
const runsEndpoint = options.runId
|
|
131
|
+
? `/runs/${encodeURIComponent(options.runId)}`
|
|
132
|
+
: `/runs?time_range=${timeRangeForDays(options.daysBack)}&limit=20`;
|
|
133
|
+
const loopsEndpoint = options.loopId
|
|
134
|
+
? `/loops/${encodeURIComponent(options.loopId)}`
|
|
135
|
+
: '/loops/active?limit=20';
|
|
136
|
+
const [overview, tools, frustrations, runs, loops] = await Promise.all([
|
|
137
|
+
safeData(overviewEndpoint),
|
|
138
|
+
safeData(toolEndpoint),
|
|
139
|
+
safeData(frustrationsEndpoint),
|
|
140
|
+
safeData(runsEndpoint),
|
|
141
|
+
safeData(loopsEndpoint),
|
|
142
|
+
]);
|
|
143
|
+
for (const [key, result] of Object.entries({ overview, tools, frustrations, runs, loops })) {
|
|
144
|
+
sourceStatus[key] = result.status;
|
|
145
|
+
if (result.warning)
|
|
146
|
+
warnings.push(result.warning);
|
|
147
|
+
}
|
|
148
|
+
const evidenceRefs = [];
|
|
149
|
+
const findings = [];
|
|
150
|
+
addOverviewEvidence(overview.data, overviewEndpoint, evidenceRefs);
|
|
151
|
+
findings.push(...toolFailureFindings(tools.data, options.daysBack, hints, evidenceRefs, options.toolName));
|
|
152
|
+
if (!options.failureOnly) {
|
|
153
|
+
findings.push(...frustrationFindings(frustrations.data, options.daysBack, evidenceRefs));
|
|
154
|
+
findings.push(...runFindings(runs.data, evidenceRefs, options.runId));
|
|
155
|
+
findings.push(...loopFindings(loops.data, evidenceRefs, options.loopId));
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
findings.push(...runFindings(runs.data, evidenceRefs, options.runId).filter((finding) => finding.kind === 'partial_run'));
|
|
159
|
+
findings.push(...loopFindings(loops.data, evidenceRefs, options.loopId));
|
|
160
|
+
}
|
|
161
|
+
if (findings.length === 0) {
|
|
162
|
+
findings.push(insufficientDataFinding(overview.data, evidenceRefs));
|
|
163
|
+
}
|
|
164
|
+
findings.sort(compareFindings);
|
|
165
|
+
const boundedFindings = findings.slice(0, 8);
|
|
166
|
+
const nextCommands = nextCommandsForFindings(boundedFindings, options.daysBack);
|
|
167
|
+
return {
|
|
168
|
+
schemaVersion: INVESTIGATION_SCHEMA_VERSION,
|
|
169
|
+
generatedAt: new Date().toISOString(),
|
|
170
|
+
daysBack: options.daysBack,
|
|
171
|
+
scope: {
|
|
172
|
+
...(options.runId ? { runId: options.runId } : {}),
|
|
173
|
+
...(options.conversationId ? { conversationId: options.conversationId } : {}),
|
|
174
|
+
...(options.toolName ? { toolName: options.toolName } : {}),
|
|
175
|
+
...(options.loopId ? { loopId: options.loopId } : {}),
|
|
176
|
+
...(options.question ? { question: options.question } : {}),
|
|
177
|
+
},
|
|
178
|
+
summary: summarizeInvestigation(boundedFindings),
|
|
179
|
+
findings: boundedFindings,
|
|
180
|
+
evidenceRefs: uniqueEvidenceRefs(evidenceRefs),
|
|
181
|
+
nextCommands,
|
|
182
|
+
warnings,
|
|
183
|
+
sourceStatus,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
export async function askModa(options) {
|
|
187
|
+
try {
|
|
188
|
+
const response = await callControlAPI('/cli/ask', {
|
|
189
|
+
method: 'POST',
|
|
190
|
+
body: JSON.stringify({
|
|
191
|
+
question: options.question,
|
|
192
|
+
daysBack: options.daysBack,
|
|
193
|
+
profile: options.profile,
|
|
194
|
+
harness: localHarnessSummary(options.cwd),
|
|
195
|
+
}),
|
|
196
|
+
});
|
|
197
|
+
const cloud = normalizeCloudAsk(response, options.question);
|
|
198
|
+
if (cloud)
|
|
199
|
+
return cloud;
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// The endpoint is intentionally future-facing. Until cloud intelligence is
|
|
203
|
+
// deployed everywhere, the CLI keeps answering from bounded Data API evidence.
|
|
204
|
+
}
|
|
205
|
+
const investigation = await investigateProduction({
|
|
206
|
+
daysBack: options.daysBack,
|
|
207
|
+
cwd: options.cwd,
|
|
208
|
+
question: options.question,
|
|
209
|
+
});
|
|
210
|
+
const top = investigation.findings[0];
|
|
211
|
+
const answer = top
|
|
212
|
+
? `Moda Cloud intelligence is not available yet, so I synthesized this from production evidence. The strongest signal is: ${top.title}. ${top.summary}`
|
|
213
|
+
: 'Moda Cloud intelligence is not available yet, and the local Data API evidence did not contain a strong production signal.';
|
|
214
|
+
return {
|
|
215
|
+
schemaVersion: ASK_SCHEMA_VERSION,
|
|
216
|
+
generatedAt: new Date().toISOString(),
|
|
217
|
+
source: 'local_fallback',
|
|
218
|
+
question: options.question,
|
|
219
|
+
answer,
|
|
220
|
+
confidence: capFallbackConfidence(investigation.summary.confidence),
|
|
221
|
+
evidenceRefs: investigation.evidenceRefs.slice(0, 12),
|
|
222
|
+
findings: investigation.findings.slice(0, 5),
|
|
223
|
+
nextCommands: investigation.nextCommands,
|
|
224
|
+
warnings: [
|
|
225
|
+
'Cloud ask endpoint unavailable; synthesized answer from local Data API evidence.',
|
|
226
|
+
...investigation.warnings,
|
|
227
|
+
],
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function writeInvestigation(context, investigation) {
|
|
231
|
+
if (context.outputMode === 'human') {
|
|
232
|
+
if (!context.quiet)
|
|
233
|
+
console.log(renderInvestigationHuman(investigation));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
context.output.writeData(investigation, investigationAgentOptions(investigation));
|
|
237
|
+
}
|
|
238
|
+
async function safeData(endpoint) {
|
|
239
|
+
try {
|
|
240
|
+
return { data: await callDataAPI(endpoint), status: 'ok' };
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
return {
|
|
244
|
+
data: null,
|
|
245
|
+
status: 'unavailable',
|
|
246
|
+
warning: `Could not load ${endpoint}: ${error instanceof Error ? error.message : String(error)}`,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function findingsFromOverview(overview, daysBack, hints, evidenceRefs) {
|
|
251
|
+
const findings = [];
|
|
252
|
+
const toolFailureTotal = numberAt(overview, ['tool_failures', 'total']);
|
|
253
|
+
if (toolFailureTotal > 0) {
|
|
254
|
+
const conversations = numberAt(overview, ['tool_failures', 'conversations']);
|
|
255
|
+
const evidenceId = 'overview:tool_failures';
|
|
256
|
+
evidenceRefs.push({
|
|
257
|
+
id: evidenceId,
|
|
258
|
+
kind: 'data_api',
|
|
259
|
+
label: `${toolFailureTotal} tool failure(s) across ${conversations} conversation(s)`,
|
|
260
|
+
endpoint: `/overview?days_back=${daysBack}`,
|
|
261
|
+
value: valueAt(overview, ['tool_failures']),
|
|
262
|
+
});
|
|
263
|
+
findings.push({
|
|
264
|
+
id: 'finding_overview_tool_failures',
|
|
265
|
+
kind: 'tool_failure',
|
|
266
|
+
title: `${toolFailureTotal} tool failure(s) need investigation`,
|
|
267
|
+
severity: severityForCount(toolFailureTotal, 10, 3),
|
|
268
|
+
confidence: 'medium',
|
|
269
|
+
impactScore: 80 + Math.min(toolFailureTotal, 20),
|
|
270
|
+
rankReason: 'Tool failures directly block agent tasks and have explicit production evidence.',
|
|
271
|
+
summary: `Moda saw ${toolFailureTotal} failed tool call(s) in the last ${daysBack} day(s).`,
|
|
272
|
+
evidenceRefIds: [evidenceId],
|
|
273
|
+
likelyLocations: defaultLikelyLocations(hints, 'tool'),
|
|
274
|
+
recommendedActions: [{
|
|
275
|
+
id: 'action_investigate_tools',
|
|
276
|
+
title: 'Open the ranked production investigation.',
|
|
277
|
+
rationale: 'The overview only has aggregate counts; investigation pulls examples and likely file paths.',
|
|
278
|
+
command: `moda investigate --days-back=${daysBack}`,
|
|
279
|
+
mutability: 'read',
|
|
280
|
+
requiresApproval: false,
|
|
281
|
+
}],
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
const frustrationRate = numberAt(overview, ['frustrations', 'rate_pct']);
|
|
285
|
+
const frustrated = numberAt(overview, ['frustrations', 'frustrated']);
|
|
286
|
+
if (frustrated > 0 || frustrationRate >= 10) {
|
|
287
|
+
const evidenceId = 'overview:frustrations';
|
|
288
|
+
evidenceRefs.push({
|
|
289
|
+
id: evidenceId,
|
|
290
|
+
kind: 'data_api',
|
|
291
|
+
label: `${frustrationRate}% frustration rate`,
|
|
292
|
+
endpoint: `/overview?days_back=${daysBack}`,
|
|
293
|
+
value: valueAt(overview, ['frustrations']),
|
|
294
|
+
});
|
|
295
|
+
findings.push({
|
|
296
|
+
id: 'finding_overview_frustration',
|
|
297
|
+
kind: 'frustration',
|
|
298
|
+
title: `${frustrationRate}% user frustration rate`,
|
|
299
|
+
severity: frustrationRate >= 25 ? 'high' : 'medium',
|
|
300
|
+
confidence: 'medium',
|
|
301
|
+
impactScore: 65 + Math.min(frustrationRate, 30),
|
|
302
|
+
rankReason: 'Frustration indicates users are getting stuck even when runs may not hard-fail.',
|
|
303
|
+
summary: `Moda classified ${frustrated} frustrated conversation(s) in the last ${daysBack} day(s).`,
|
|
304
|
+
evidenceRefIds: [evidenceId],
|
|
305
|
+
likelyLocations: defaultLikelyLocations(hints, 'prompt'),
|
|
306
|
+
recommendedActions: [{
|
|
307
|
+
id: 'action_investigate_frustration',
|
|
308
|
+
title: 'Inspect frustration anchors.',
|
|
309
|
+
rationale: 'Frustration anchors show the user turn where the experience degraded.',
|
|
310
|
+
command: `moda frustrations --days-back=${daysBack} --include-window`,
|
|
311
|
+
mutability: 'read',
|
|
312
|
+
requiresApproval: false,
|
|
313
|
+
}],
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
return findings.sort(compareFindings);
|
|
317
|
+
}
|
|
318
|
+
function toolFailureFindings(data, daysBack, hints, evidenceRefs, scopedTool) {
|
|
319
|
+
const tools = toolRows(data, scopedTool);
|
|
320
|
+
return tools.flatMap((tool) => {
|
|
321
|
+
const name = tool.name;
|
|
322
|
+
if (!name)
|
|
323
|
+
return [];
|
|
324
|
+
const count = tool.count;
|
|
325
|
+
if (count <= 0)
|
|
326
|
+
return [];
|
|
327
|
+
const evidenceId = `tool:${name}`;
|
|
328
|
+
const subtype = tool.subtype ? ` (${tool.subtype})` : '';
|
|
329
|
+
evidenceRefs.push({
|
|
330
|
+
id: evidenceId,
|
|
331
|
+
kind: 'tool_failure',
|
|
332
|
+
label: `${name}: ${count} failure(s)${subtype}`,
|
|
333
|
+
endpoint: scopedTool
|
|
334
|
+
? `/tool-failures/${encodeURIComponent(name)}?days_back=${daysBack}`
|
|
335
|
+
: `/tool-failures?days_back=${daysBack}`,
|
|
336
|
+
toolName: name,
|
|
337
|
+
value: tool.raw,
|
|
338
|
+
});
|
|
339
|
+
const location = likelyToolLocation(name, hints);
|
|
340
|
+
if (location?.path) {
|
|
341
|
+
evidenceRefs.push({
|
|
342
|
+
id: `harness:tool:${name}`,
|
|
343
|
+
kind: 'harness',
|
|
344
|
+
label: `Likely tool definition for ${name}`,
|
|
345
|
+
path: location.path,
|
|
346
|
+
toolName: name,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
const conversations = tool.conversations > 0 ? tool.conversations : count;
|
|
350
|
+
return [{
|
|
351
|
+
id: `finding_tool_failure_${slug(name)}`,
|
|
352
|
+
kind: 'tool_failure',
|
|
353
|
+
title: `${name} is failing in production`,
|
|
354
|
+
severity: severityForCount(count, 10, 3),
|
|
355
|
+
confidence: 'high',
|
|
356
|
+
impactScore: 90 + Math.min(count, 25),
|
|
357
|
+
rankReason: `${count} failed call(s) across ${conversations} conversation(s), with direct tool failure evidence.`,
|
|
358
|
+
summary: `${name} produced ${count} failed call(s) in the last ${daysBack} day(s).${tool.subtype ? ` The dominant subtype is ${tool.subtype}.` : ''}`,
|
|
359
|
+
evidenceRefIds: location?.path ? [evidenceId, `harness:tool:${name}`] : [evidenceId],
|
|
360
|
+
likelyLocations: [location ?? unknownLocation('tool', `No local definition matched ${name}.`)],
|
|
361
|
+
recommendedActions: [{
|
|
362
|
+
id: `action_tool_detail_${slug(name)}`,
|
|
363
|
+
title: `Inspect ${name} failure examples.`,
|
|
364
|
+
rationale: 'Examples include conversation anchors and error subtypes.',
|
|
365
|
+
command: `moda tool-failure-detail ${name} --include-window`,
|
|
366
|
+
mutability: 'read',
|
|
367
|
+
requiresApproval: false,
|
|
368
|
+
}],
|
|
369
|
+
}];
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
function frustrationFindings(data, daysBack, evidenceRefs) {
|
|
373
|
+
const summary = asRecord(valueAt(data, ['summary']));
|
|
374
|
+
const frustrations = Array.isArray(valueAt(data, ['frustrations']))
|
|
375
|
+
? valueAt(data, ['frustrations'])
|
|
376
|
+
: [];
|
|
377
|
+
const count = numberValue(summary?.frustrated_count) || frustrations.length;
|
|
378
|
+
const atRisk = numberValue(summary?.at_risk_count);
|
|
379
|
+
if (count <= 0 && atRisk <= 0 && frustrations.length === 0)
|
|
380
|
+
return [];
|
|
381
|
+
const first = asRecord(frustrations[0]);
|
|
382
|
+
const conversationId = asString(first?.conversation_id);
|
|
383
|
+
const quote = firstQuote(first);
|
|
384
|
+
const evidenceId = conversationId ? `frustration:${conversationId}` : 'frustration:summary';
|
|
385
|
+
evidenceRefs.push({
|
|
386
|
+
id: evidenceId,
|
|
387
|
+
kind: 'frustration',
|
|
388
|
+
label: quote ? `Frustration anchor: "${quote}"` : `${count} frustrated conversation(s)`,
|
|
389
|
+
endpoint: `/frustrations?days_back=${daysBack}&limit=5`,
|
|
390
|
+
...(conversationId ? { conversationId } : {}),
|
|
391
|
+
value: first ?? summary ?? data,
|
|
392
|
+
});
|
|
393
|
+
return [{
|
|
394
|
+
id: 'finding_frustration_rate',
|
|
395
|
+
kind: 'frustration',
|
|
396
|
+
title: count > 0 ? `${count} frustrated conversation(s)` : `${atRisk} at-risk conversation(s)`,
|
|
397
|
+
severity: count >= 10 ? 'high' : 'medium',
|
|
398
|
+
confidence: first ? 'high' : 'medium',
|
|
399
|
+
impactScore: 70 + Math.min(count * 3 + atRisk, 25),
|
|
400
|
+
rankReason: 'User frustration is a product-quality signal even when the agent technically completes.',
|
|
401
|
+
summary: first?.primary_cause
|
|
402
|
+
? `Primary cause: ${String(first.primary_cause)}.`
|
|
403
|
+
: `Moda found frustration or risk in the last ${daysBack} day(s).`,
|
|
404
|
+
evidenceRefIds: [evidenceId],
|
|
405
|
+
likelyLocations: [unknownLocation('prompt', 'Prompt or policy issue likely; inspect the conversation window.')],
|
|
406
|
+
recommendedActions: [{
|
|
407
|
+
id: 'action_frustration_window',
|
|
408
|
+
title: 'Read the frustration window.',
|
|
409
|
+
rationale: 'The user quote and nearby turns reveal what confused or blocked the user.',
|
|
410
|
+
command: conversationId
|
|
411
|
+
? `moda context ${conversationId} --window=3`
|
|
412
|
+
: `moda frustrations --days-back=${daysBack} --include-window`,
|
|
413
|
+
mutability: 'read',
|
|
414
|
+
requiresApproval: false,
|
|
415
|
+
}],
|
|
416
|
+
}];
|
|
417
|
+
}
|
|
418
|
+
function runFindings(data, evidenceRefs, scopedRunId) {
|
|
419
|
+
const rows = runRows(data);
|
|
420
|
+
return rows.flatMap((run) => {
|
|
421
|
+
const runId = run.id || scopedRunId;
|
|
422
|
+
const outcome = run.outcome;
|
|
423
|
+
if (!runId || !['failed', 'partial', 'abandoned', 'loop'].includes(outcome))
|
|
424
|
+
return [];
|
|
425
|
+
const evidenceId = `run:${runId}`;
|
|
426
|
+
evidenceRefs.push({
|
|
427
|
+
id: evidenceId,
|
|
428
|
+
kind: 'run',
|
|
429
|
+
label: `${run.agentName || 'Agent run'} ${outcome}`,
|
|
430
|
+
endpoint: `/runs/${runId}`,
|
|
431
|
+
runId,
|
|
432
|
+
conversationId: run.conversationId,
|
|
433
|
+
value: run.raw,
|
|
434
|
+
});
|
|
435
|
+
return [{
|
|
436
|
+
id: `finding_run_${slug(runId)}`,
|
|
437
|
+
kind: outcome === 'partial' ? 'partial_run' : 'tool_failure',
|
|
438
|
+
title: `${run.agentName || 'Agent run'} ended as ${outcome}`,
|
|
439
|
+
severity: outcome === 'failed' ? 'high' : 'medium',
|
|
440
|
+
confidence: 'high',
|
|
441
|
+
impactScore: outcome === 'failed' ? 88 : 76,
|
|
442
|
+
rankReason: `Run outcome is ${outcome}${run.reason ? `: ${run.reason}` : ''}.`,
|
|
443
|
+
summary: run.reason || 'Moda classified this run as incomplete or unhealthy.',
|
|
444
|
+
evidenceRefIds: [evidenceId],
|
|
445
|
+
likelyLocations: [unknownLocation('entrypoint', 'Map this run to a harness entrypoint after harness sync.')],
|
|
446
|
+
recommendedActions: [{
|
|
447
|
+
id: `action_run_${slug(runId)}`,
|
|
448
|
+
title: 'Open the run detail.',
|
|
449
|
+
rationale: 'Run detail includes receipts and missing effects.',
|
|
450
|
+
command: `moda run ${runId}`,
|
|
451
|
+
mutability: 'read',
|
|
452
|
+
requiresApproval: false,
|
|
453
|
+
}],
|
|
454
|
+
}];
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
function loopFindings(data, evidenceRefs, scopedLoopId) {
|
|
458
|
+
const rows = loopRows(data);
|
|
459
|
+
return rows.flatMap((loop) => {
|
|
460
|
+
const id = loop.id || scopedLoopId;
|
|
461
|
+
if (!id)
|
|
462
|
+
return [];
|
|
463
|
+
const evidenceId = `loop:${id}`;
|
|
464
|
+
evidenceRefs.push({
|
|
465
|
+
id: evidenceId,
|
|
466
|
+
kind: 'loop',
|
|
467
|
+
label: loop.signature || `${loop.type || 'Loop'} detected`,
|
|
468
|
+
endpoint: `/loops/${id}`,
|
|
469
|
+
detectionId: id,
|
|
470
|
+
conversationId: loop.conversationId,
|
|
471
|
+
value: loop.raw,
|
|
472
|
+
});
|
|
473
|
+
return [{
|
|
474
|
+
id: `finding_loop_${slug(id)}`,
|
|
475
|
+
kind: 'loop',
|
|
476
|
+
title: loop.signature || `${loop.type || 'Loop'} detected`,
|
|
477
|
+
severity: loop.blastRadius >= 5 ? 'high' : 'medium',
|
|
478
|
+
confidence: 'high',
|
|
479
|
+
impactScore: 84 + Math.min(loop.blastRadius, 10),
|
|
480
|
+
rankReason: `Active loop detection${loop.blastRadius ? ` with blast radius ${loop.blastRadius}` : ''}.`,
|
|
481
|
+
summary: loop.rootCause || 'The agent appears to repeat work or retry a failing path.',
|
|
482
|
+
evidenceRefIds: [evidenceId],
|
|
483
|
+
likelyLocations: [unknownLocation('prompt', 'Loops often come from retry policy, stop conditions, or tool handling.')],
|
|
484
|
+
recommendedActions: [{
|
|
485
|
+
id: `action_loop_${slug(id)}`,
|
|
486
|
+
title: 'Inspect loop detail.',
|
|
487
|
+
rationale: 'Loop detail includes signature, cycle length, and root-cause hypothesis.',
|
|
488
|
+
command: `moda loop ${id}`,
|
|
489
|
+
mutability: 'read',
|
|
490
|
+
requiresApproval: false,
|
|
491
|
+
}],
|
|
492
|
+
}];
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
function insufficientDataFinding(overview, evidenceRefs) {
|
|
496
|
+
const evidenceId = 'overview:data';
|
|
497
|
+
if (!evidenceRefs.some((ref) => ref.id === evidenceId)) {
|
|
498
|
+
evidenceRefs.push({
|
|
499
|
+
id: evidenceId,
|
|
500
|
+
kind: 'data_api',
|
|
501
|
+
label: 'Overview did not contain actionable production signals',
|
|
502
|
+
endpoint: '/overview',
|
|
503
|
+
value: overview,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
return {
|
|
507
|
+
id: 'finding_insufficient_data',
|
|
508
|
+
kind: 'insufficient_data',
|
|
509
|
+
title: 'No strong production issue found yet',
|
|
510
|
+
severity: 'info',
|
|
511
|
+
confidence: 'medium',
|
|
512
|
+
impactScore: 10,
|
|
513
|
+
rankReason: 'Moda needs more recent production traces before ranking failures.',
|
|
514
|
+
summary: 'Data flow may be quiet or current signals are below the investigation threshold.',
|
|
515
|
+
evidenceRefIds: [evidenceId],
|
|
516
|
+
likelyLocations: [],
|
|
517
|
+
recommendedActions: [{
|
|
518
|
+
id: 'action_doctor_online',
|
|
519
|
+
title: 'Validate data flow.',
|
|
520
|
+
rationale: 'A quiet overview can mean no traffic or broken ingestion.',
|
|
521
|
+
command: 'moda doctor --online',
|
|
522
|
+
mutability: 'read',
|
|
523
|
+
requiresApproval: false,
|
|
524
|
+
}],
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function normalizeCloudAsk(response, question) {
|
|
528
|
+
const record = asRecord(response);
|
|
529
|
+
const answer = asString(record?.answer);
|
|
530
|
+
if (!answer)
|
|
531
|
+
return null;
|
|
532
|
+
const evidenceRefs = Array.isArray(record?.evidence_refs)
|
|
533
|
+
? record.evidence_refs.flatMap((item) => {
|
|
534
|
+
const ref = asRecord(item);
|
|
535
|
+
if (!ref)
|
|
536
|
+
return [];
|
|
537
|
+
return [{
|
|
538
|
+
id: asString(ref.id) ?? `ask:evidence:${slug(asString(ref.label) ?? 'ref')}`,
|
|
539
|
+
kind: evidenceKind(asString(ref.kind)),
|
|
540
|
+
label: asString(ref.label) ?? asString(ref.id) ?? 'Cloud evidence',
|
|
541
|
+
path: asString(ref.path) ?? undefined,
|
|
542
|
+
value: ref.value,
|
|
543
|
+
}];
|
|
544
|
+
})
|
|
545
|
+
: [];
|
|
546
|
+
const nextCommands = Array.isArray(record?.next_commands)
|
|
547
|
+
? record.next_commands.filter((item) => typeof item === 'string')
|
|
548
|
+
: ['moda investigate'];
|
|
549
|
+
const confidence = normalizeConfidence(record?.confidence);
|
|
550
|
+
return {
|
|
551
|
+
schemaVersion: ASK_SCHEMA_VERSION,
|
|
552
|
+
generatedAt: new Date().toISOString(),
|
|
553
|
+
source: 'cloud',
|
|
554
|
+
question,
|
|
555
|
+
answer,
|
|
556
|
+
confidence,
|
|
557
|
+
evidenceRefs,
|
|
558
|
+
findings: [],
|
|
559
|
+
nextCommands,
|
|
560
|
+
warnings: [],
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function investigationAgentOptions(investigation) {
|
|
564
|
+
return {
|
|
565
|
+
summary: investigation.summary,
|
|
566
|
+
findings: investigation.findings.map(agentFinding),
|
|
567
|
+
evidenceRefs: investigation.evidenceRefs.map(agentEvidence),
|
|
568
|
+
recommendedActions: investigation.findings.flatMap((finding) => finding.recommendedActions.map(agentAction)),
|
|
569
|
+
nextCommands: investigation.nextCommands.map(nextCommand),
|
|
570
|
+
warnings: investigation.warnings,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
function overviewAgentOptions(briefing) {
|
|
574
|
+
return {
|
|
575
|
+
summary: {
|
|
576
|
+
text: `Moda health briefing: ${briefing.status}. Next: ${briefing.nextCommand}`,
|
|
577
|
+
confidence: briefing.topFinding ? briefing.topFinding.confidence : 'medium',
|
|
578
|
+
},
|
|
579
|
+
findings: briefing.topFinding ? [agentFinding(briefing.topFinding)] : [],
|
|
580
|
+
evidenceRefs: briefing.evidenceRefs.map(agentEvidence),
|
|
581
|
+
recommendedActions: briefing.topFinding
|
|
582
|
+
? briefing.topFinding.recommendedActions.map(agentAction)
|
|
583
|
+
: [],
|
|
584
|
+
nextCommands: [nextCommand(briefing.nextCommand)],
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
function askAgentOptions(result) {
|
|
588
|
+
return {
|
|
589
|
+
summary: { text: result.answer, confidence: result.confidence },
|
|
590
|
+
findings: result.findings.map(agentFinding),
|
|
591
|
+
evidenceRefs: result.evidenceRefs.map(agentEvidence),
|
|
592
|
+
recommendedActions: result.findings.flatMap((finding) => finding.recommendedActions.map(agentAction)),
|
|
593
|
+
nextCommands: result.nextCommands.map(nextCommand),
|
|
594
|
+
warnings: result.warnings,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
function agentFinding(finding) {
|
|
598
|
+
return {
|
|
599
|
+
id: finding.id,
|
|
600
|
+
title: finding.title,
|
|
601
|
+
severity: finding.severity,
|
|
602
|
+
description: `${finding.summary} ${finding.rankReason}`,
|
|
603
|
+
evidence_ref_ids: finding.evidenceRefIds,
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
function agentEvidence(ref) {
|
|
607
|
+
return {
|
|
608
|
+
id: ref.id,
|
|
609
|
+
kind: ref.kind,
|
|
610
|
+
label: ref.label,
|
|
611
|
+
path: ref.path,
|
|
612
|
+
line: ref.line,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
function agentAction(action) {
|
|
616
|
+
return {
|
|
617
|
+
id: action.id,
|
|
618
|
+
title: action.title,
|
|
619
|
+
rationale: action.rationale,
|
|
620
|
+
command: action.command,
|
|
621
|
+
mutability: action.mutability,
|
|
622
|
+
requires_approval: action.requiresApproval,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function nextCommand(command) {
|
|
626
|
+
return {
|
|
627
|
+
command,
|
|
628
|
+
purpose: purposeForCommand(command),
|
|
629
|
+
mutability: command.includes(' sync') ? 'sync' : 'read',
|
|
630
|
+
requires_approval: command.includes(' sync') || command.includes(' --yes'),
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
function renderOverviewBriefingHuman(briefing) {
|
|
634
|
+
const lines = [];
|
|
635
|
+
lines.push('Moda overview');
|
|
636
|
+
lines.push('Production health briefing');
|
|
637
|
+
lines.push('');
|
|
638
|
+
lines.push(`Status ${briefing.status}`);
|
|
639
|
+
lines.push(`Data flow ${briefing.dataFlow.status} (${briefing.dataFlow.conversations} conversation(s))`);
|
|
640
|
+
if (briefing.dataFlow.lastEvent) {
|
|
641
|
+
lines.push(`Last event ${briefing.dataFlow.lastEvent.summary}${briefing.dataFlow.lastEvent.timestamp ? ` - ${briefing.dataFlow.lastEvent.timestamp}` : ''}`);
|
|
642
|
+
}
|
|
643
|
+
lines.push('');
|
|
644
|
+
lines.push('Sync');
|
|
645
|
+
lines.push(` Harness ${briefing.harnessSync.status} - ${briefing.harnessSync.summary}`);
|
|
646
|
+
lines.push(` Prompts ${briefing.promptSync.status} - ${briefing.promptSync.summary}`);
|
|
647
|
+
lines.push(` Skills ${briefing.skillSync.status} - ${briefing.skillSync.summary}`);
|
|
648
|
+
lines.push('');
|
|
649
|
+
lines.push('Signals');
|
|
650
|
+
for (const [label, value] of overviewSignalPairs(briefing.metrics)) {
|
|
651
|
+
lines.push(` ${label.padEnd(18)} ${String(value)}`);
|
|
652
|
+
}
|
|
653
|
+
lines.push('');
|
|
654
|
+
lines.push('Top signal');
|
|
655
|
+
if (briefing.topFinding) {
|
|
656
|
+
lines.push(` ${briefing.topFinding.severity.toUpperCase()} ${briefing.topFinding.title}`);
|
|
657
|
+
lines.push(` ${briefing.topFinding.summary}`);
|
|
658
|
+
lines.push(` Why ranked: ${briefing.topFinding.rankReason}`);
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
lines.push(' No urgent production issue found in the selected window.');
|
|
662
|
+
}
|
|
663
|
+
lines.push('');
|
|
664
|
+
lines.push('Next best action');
|
|
665
|
+
lines.push(` ${briefing.nextCommand}`);
|
|
666
|
+
return lines.join('\n');
|
|
667
|
+
}
|
|
668
|
+
function renderInvestigationHuman(investigation) {
|
|
669
|
+
const lines = [];
|
|
670
|
+
lines.push('Moda investigation');
|
|
671
|
+
lines.push(`Window: last ${investigation.daysBack} day(s)`);
|
|
672
|
+
lines.push('');
|
|
673
|
+
lines.push(investigation.summary.text);
|
|
674
|
+
for (const finding of investigation.findings) {
|
|
675
|
+
lines.push('');
|
|
676
|
+
lines.push(`${finding.severity.toUpperCase()} ${finding.title}`);
|
|
677
|
+
lines.push(` Confidence: ${finding.confidence}`);
|
|
678
|
+
lines.push(` Impact: ${finding.impactScore}`);
|
|
679
|
+
lines.push(` ${finding.summary}`);
|
|
680
|
+
lines.push(` Why ranked: ${finding.rankReason}`);
|
|
681
|
+
if (finding.likelyLocations.length > 0) {
|
|
682
|
+
lines.push(' Likely location:');
|
|
683
|
+
for (const location of finding.likelyLocations.slice(0, 3)) {
|
|
684
|
+
lines.push(` - ${location.path ?? location.label} (${location.confidence})`);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (finding.recommendedActions.length > 0) {
|
|
688
|
+
lines.push(' Next:');
|
|
689
|
+
for (const action of finding.recommendedActions.slice(0, 2)) {
|
|
690
|
+
lines.push(` ${action.command}`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
lines.push('');
|
|
695
|
+
lines.push('Next commands');
|
|
696
|
+
for (const command of investigation.nextCommands.slice(0, 6))
|
|
697
|
+
lines.push(` ${command}`);
|
|
698
|
+
if (investigation.warnings.length > 0) {
|
|
699
|
+
lines.push('');
|
|
700
|
+
lines.push('Warnings');
|
|
701
|
+
for (const warning of investigation.warnings.slice(0, 5))
|
|
702
|
+
lines.push(` - ${warning}`);
|
|
703
|
+
}
|
|
704
|
+
return lines.join('\n');
|
|
705
|
+
}
|
|
706
|
+
function renderAskHuman(result) {
|
|
707
|
+
const lines = [];
|
|
708
|
+
lines.push('Moda ask');
|
|
709
|
+
lines.push(`Question: ${result.question}`);
|
|
710
|
+
lines.push('');
|
|
711
|
+
lines.push(result.answer);
|
|
712
|
+
lines.push('');
|
|
713
|
+
lines.push(`Confidence: ${result.confidence}`);
|
|
714
|
+
if (result.evidenceRefs.length > 0) {
|
|
715
|
+
lines.push('');
|
|
716
|
+
lines.push('Evidence');
|
|
717
|
+
for (const ref of result.evidenceRefs.slice(0, 8)) {
|
|
718
|
+
lines.push(` - ${ref.label}${ref.path ? ` (${ref.path})` : ''}`);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
if (result.nextCommands.length > 0) {
|
|
722
|
+
lines.push('');
|
|
723
|
+
lines.push('Next commands');
|
|
724
|
+
for (const command of result.nextCommands.slice(0, 5))
|
|
725
|
+
lines.push(` ${command}`);
|
|
726
|
+
}
|
|
727
|
+
if (result.warnings.length > 0) {
|
|
728
|
+
lines.push('');
|
|
729
|
+
lines.push('Warnings');
|
|
730
|
+
for (const warning of result.warnings.slice(0, 3))
|
|
731
|
+
lines.push(` - ${warning}`);
|
|
732
|
+
}
|
|
733
|
+
return lines.join('\n');
|
|
734
|
+
}
|
|
735
|
+
function addOverviewEvidence(data, endpoint, refs) {
|
|
736
|
+
refs.push({
|
|
737
|
+
id: 'overview:data',
|
|
738
|
+
kind: 'data_api',
|
|
739
|
+
label: 'Production overview aggregate',
|
|
740
|
+
endpoint,
|
|
741
|
+
value: overviewMetrics(data),
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
function toolRows(data, scopedTool) {
|
|
745
|
+
const record = asRecord(data);
|
|
746
|
+
const directToolName = asString(record?.tool_name) ?? scopedTool;
|
|
747
|
+
const examples = Array.isArray(record?.examples) ? record.examples : [];
|
|
748
|
+
if (directToolName) {
|
|
749
|
+
return [{
|
|
750
|
+
name: directToolName,
|
|
751
|
+
count: numberValue(record?.failure_count) || numberValue(record?.total_failures) || examples.length,
|
|
752
|
+
conversations: numberValue(record?.failed_conversations) || numberValue(record?.conversation_count) || examples.length,
|
|
753
|
+
subtype: asString(record?.top_error_subtype) ?? asString(record?.error_subtype) ?? undefined,
|
|
754
|
+
raw: data,
|
|
755
|
+
}];
|
|
756
|
+
}
|
|
757
|
+
const tools = Array.isArray(record?.tools)
|
|
758
|
+
? record.tools
|
|
759
|
+
: Array.isArray(record?.tool_failures)
|
|
760
|
+
? record.tool_failures
|
|
761
|
+
: Array.isArray(record?.failures)
|
|
762
|
+
? record.failures
|
|
763
|
+
: [];
|
|
764
|
+
return tools.flatMap((item) => {
|
|
765
|
+
const row = asRecord(item);
|
|
766
|
+
const name = asString(row?.tool_name) ?? asString(row?.name);
|
|
767
|
+
if (!name)
|
|
768
|
+
return [];
|
|
769
|
+
return [{
|
|
770
|
+
name,
|
|
771
|
+
count: numberValue(row?.failure_count) || numberValue(row?.total_failures) || numberValue(row?.count),
|
|
772
|
+
conversations: numberValue(row?.failed_conversations) || numberValue(row?.conversation_count),
|
|
773
|
+
subtype: asString(row?.top_error_subtype) ?? asString(row?.error_subtype) ?? undefined,
|
|
774
|
+
raw: row,
|
|
775
|
+
}];
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
function runRows(data) {
|
|
779
|
+
const record = asRecord(data);
|
|
780
|
+
const run = asRecord(record?.run);
|
|
781
|
+
const rows = Array.isArray(record?.runs) ? record.runs : run ? [run] : [];
|
|
782
|
+
return rows.flatMap((item) => {
|
|
783
|
+
const row = asRecord(item);
|
|
784
|
+
if (!row)
|
|
785
|
+
return [];
|
|
786
|
+
return [{
|
|
787
|
+
id: asString(row.job_id) ?? asString(row.run_id) ?? '',
|
|
788
|
+
outcome: asString(row.outcome) ?? 'unknown',
|
|
789
|
+
agentName: asString(row.agent_name) ?? undefined,
|
|
790
|
+
reason: asString(row.outcome_reason) ?? undefined,
|
|
791
|
+
conversationId: asString(row.conversation_id) ?? undefined,
|
|
792
|
+
raw: row,
|
|
793
|
+
}];
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
function loopRows(data) {
|
|
797
|
+
const record = asRecord(data);
|
|
798
|
+
const rows = Array.isArray(record?.loops)
|
|
799
|
+
? record.loops
|
|
800
|
+
: asRecord(record)
|
|
801
|
+
? [record]
|
|
802
|
+
: [];
|
|
803
|
+
return rows.flatMap((item) => {
|
|
804
|
+
const row = asRecord(item);
|
|
805
|
+
if (!row)
|
|
806
|
+
return [];
|
|
807
|
+
return [{
|
|
808
|
+
id: asString(row.detection_id) ?? '',
|
|
809
|
+
type: asString(row.loop_type) ?? undefined,
|
|
810
|
+
signature: asString(row.loop_signature_human) ?? undefined,
|
|
811
|
+
blastRadius: numberValue(row.blast_radius),
|
|
812
|
+
conversationId: asString(row.conversation_id) ?? undefined,
|
|
813
|
+
rootCause: asString(row.root_cause_hypothesis) ?? undefined,
|
|
814
|
+
raw: row,
|
|
815
|
+
}];
|
|
816
|
+
}).filter((loop) => loop.id);
|
|
817
|
+
}
|
|
818
|
+
function firstQuote(row) {
|
|
819
|
+
const quotes = Array.isArray(row?.user_quotes) ? row.user_quotes : [];
|
|
820
|
+
const first = asRecord(quotes[0]);
|
|
821
|
+
return asString(first?.quote)?.slice(0, 160) ?? undefined;
|
|
822
|
+
}
|
|
823
|
+
function likelyToolLocation(toolName, hints) {
|
|
824
|
+
const normalized = normalizeKey(toolName);
|
|
825
|
+
for (const [key, path] of hints.toolPaths.entries()) {
|
|
826
|
+
if (key.includes(normalized) || normalized.includes(key)) {
|
|
827
|
+
return {
|
|
828
|
+
kind: 'tool',
|
|
829
|
+
path,
|
|
830
|
+
label: `Tool definition for ${toolName}`,
|
|
831
|
+
confidence: 'medium',
|
|
832
|
+
reason: 'Matched tool name against the local harness graph.',
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
function defaultLikelyLocations(hints, kind) {
|
|
839
|
+
const source = kind === 'tool' ? hints.toolPaths : kind === 'prompt' ? hints.promptPaths : hints.skillPaths;
|
|
840
|
+
const first = [...source.values()][0];
|
|
841
|
+
if (first) {
|
|
842
|
+
return [{
|
|
843
|
+
kind,
|
|
844
|
+
path: first,
|
|
845
|
+
label: `${kind} artifact`,
|
|
846
|
+
confidence: 'low',
|
|
847
|
+
reason: 'The overview is aggregate-only; investigation will map specific production evidence.',
|
|
848
|
+
}];
|
|
849
|
+
}
|
|
850
|
+
if (hints.reportPath) {
|
|
851
|
+
return [{
|
|
852
|
+
kind: 'harness',
|
|
853
|
+
path: hints.reportPath,
|
|
854
|
+
label: 'Local harness manifest',
|
|
855
|
+
confidence: 'low',
|
|
856
|
+
reason: 'Use the local harness graph to map production signals to code.',
|
|
857
|
+
}];
|
|
858
|
+
}
|
|
859
|
+
return [];
|
|
860
|
+
}
|
|
861
|
+
function unknownLocation(kind, reason) {
|
|
862
|
+
return {
|
|
863
|
+
kind,
|
|
864
|
+
label: 'Unknown local path',
|
|
865
|
+
confidence: 'unknown',
|
|
866
|
+
reason,
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
function loadHarnessHints(cwd) {
|
|
870
|
+
const hints = {
|
|
871
|
+
toolPaths: new Map(),
|
|
872
|
+
promptPaths: new Map(),
|
|
873
|
+
skillPaths: new Map(),
|
|
874
|
+
entrypointPaths: [],
|
|
875
|
+
};
|
|
876
|
+
const harnessPath = join(cwd, '.moda', 'harness.json');
|
|
877
|
+
if (!existsSync(harnessPath))
|
|
878
|
+
return hints;
|
|
879
|
+
hints.reportPath = '.moda/harness.json';
|
|
880
|
+
try {
|
|
881
|
+
const parsed = JSON.parse(readFileSync(harnessPath, 'utf8'));
|
|
882
|
+
const graph = asRecord(parsed);
|
|
883
|
+
const artifacts = Array.isArray(graph?.artifacts) ? graph.artifacts : [];
|
|
884
|
+
for (const item of artifacts) {
|
|
885
|
+
const artifact = asRecord(item);
|
|
886
|
+
if (!artifact)
|
|
887
|
+
continue;
|
|
888
|
+
const name = asString(artifact.name) ?? asString(artifact.id) ?? '';
|
|
889
|
+
const path = asString(artifact.sourcePath) ?? asString(artifact.path);
|
|
890
|
+
const type = asString(artifact.type);
|
|
891
|
+
if (!path || !name)
|
|
892
|
+
continue;
|
|
893
|
+
const key = normalizeKey(name);
|
|
894
|
+
if (type === 'tool')
|
|
895
|
+
hints.toolPaths.set(key, path);
|
|
896
|
+
if (type === 'prompt')
|
|
897
|
+
hints.promptPaths.set(key, path);
|
|
898
|
+
if (type === 'skill')
|
|
899
|
+
hints.skillPaths.set(key, path);
|
|
900
|
+
if (type === 'entrypoint')
|
|
901
|
+
hints.entrypointPaths.push(path);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
catch {
|
|
905
|
+
return hints;
|
|
906
|
+
}
|
|
907
|
+
return hints;
|
|
908
|
+
}
|
|
909
|
+
function localHarnessSummary(cwd) {
|
|
910
|
+
const path = join(cwd, '.moda', 'harness.json');
|
|
911
|
+
if (!existsSync(path))
|
|
912
|
+
return null;
|
|
913
|
+
try {
|
|
914
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
915
|
+
return {
|
|
916
|
+
harnessId: parsed.harnessId,
|
|
917
|
+
graphHash: parsed.graphHash,
|
|
918
|
+
runtimeAgentCount: Array.isArray(parsed.runtimeAgents) ? parsed.runtimeAgents.length : undefined,
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
catch {
|
|
922
|
+
return null;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
function localSyncBrief(cwd, relativePath, label) {
|
|
926
|
+
const path = join(cwd, relativePath);
|
|
927
|
+
if (!existsSync(path)) {
|
|
928
|
+
return { status: 'missing', summary: `${label} state not found`, path: relativePath };
|
|
929
|
+
}
|
|
930
|
+
return { status: 'present', summary: `${label} state exists locally`, path: relativePath };
|
|
931
|
+
}
|
|
932
|
+
function localSkillBrief(cwd) {
|
|
933
|
+
const claudeSkill = join(cwd, '.claude', 'skills');
|
|
934
|
+
const cursorRules = join(cwd, '.cursor', 'rules');
|
|
935
|
+
if (existsSync(claudeSkill) || existsSync(cursorRules)) {
|
|
936
|
+
return { status: 'present', summary: 'Local agent skill/rule directory exists' };
|
|
937
|
+
}
|
|
938
|
+
return { status: 'missing', summary: 'No local agent skill/rule directory found' };
|
|
939
|
+
}
|
|
940
|
+
function summarizeInvestigation(findings) {
|
|
941
|
+
const top = findings[0];
|
|
942
|
+
if (!top) {
|
|
943
|
+
return { text: 'No production findings were generated.', confidence: 'unknown' };
|
|
944
|
+
}
|
|
945
|
+
if (top.kind === 'insufficient_data') {
|
|
946
|
+
return { text: top.title, confidence: top.confidence };
|
|
947
|
+
}
|
|
948
|
+
return {
|
|
949
|
+
text: `Top production finding: ${top.title}. ${top.summary}`,
|
|
950
|
+
confidence: top.confidence,
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
function nextCommandsForFindings(findings, daysBack) {
|
|
954
|
+
const commands = findings
|
|
955
|
+
.flatMap((finding) => finding.recommendedActions.map((action) => action.command))
|
|
956
|
+
.filter(Boolean);
|
|
957
|
+
commands.push(`moda overview --days-back=${daysBack}`);
|
|
958
|
+
commands.push('moda doctor --online');
|
|
959
|
+
return [...new Set(commands)].slice(0, 8);
|
|
960
|
+
}
|
|
961
|
+
function compareFindings(a, b) {
|
|
962
|
+
const severityDelta = severityWeight(b.severity) - severityWeight(a.severity);
|
|
963
|
+
if (severityDelta !== 0)
|
|
964
|
+
return severityDelta;
|
|
965
|
+
return b.impactScore - a.impactScore;
|
|
966
|
+
}
|
|
967
|
+
function severityWeight(severity) {
|
|
968
|
+
return { critical: 5, high: 4, medium: 3, low: 2, info: 1 }[severity];
|
|
969
|
+
}
|
|
970
|
+
function severityForCount(count, high, medium) {
|
|
971
|
+
if (count >= high)
|
|
972
|
+
return 'critical';
|
|
973
|
+
if (count >= medium)
|
|
974
|
+
return 'high';
|
|
975
|
+
return 'medium';
|
|
976
|
+
}
|
|
977
|
+
function uniqueEvidenceRefs(refs) {
|
|
978
|
+
const seen = new Set();
|
|
979
|
+
const out = [];
|
|
980
|
+
for (const ref of refs) {
|
|
981
|
+
if (seen.has(ref.id))
|
|
982
|
+
continue;
|
|
983
|
+
seen.add(ref.id);
|
|
984
|
+
out.push(ref);
|
|
985
|
+
}
|
|
986
|
+
return out;
|
|
987
|
+
}
|
|
988
|
+
function overviewMetrics(data) {
|
|
989
|
+
return {
|
|
990
|
+
conversations: valueAt(data, ['conversations', 'total']) ?? 0,
|
|
991
|
+
conversationTrendPct: valueAt(data, ['conversations', 'trend_pct']) ?? 0,
|
|
992
|
+
toolFailures: valueAt(data, ['tool_failures', 'total']) ?? 0,
|
|
993
|
+
failedToolConversations: valueAt(data, ['tool_failures', 'conversations']) ?? 0,
|
|
994
|
+
impactedTools: valueAt(data, ['tool_failures', 'tools']) ?? 0,
|
|
995
|
+
frustrated: valueAt(data, ['frustrations', 'frustrated']) ?? 0,
|
|
996
|
+
atRisk: valueAt(data, ['frustrations', 'at_risk']) ?? 0,
|
|
997
|
+
frustrationRatePct: valueAt(data, ['frustrations', 'rate_pct']) ?? 0,
|
|
998
|
+
recentActivity: recentActivity(data).length,
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
function overviewSignalPairs(metrics) {
|
|
1002
|
+
return [
|
|
1003
|
+
['Conversations', metrics.conversations],
|
|
1004
|
+
['Tool failures', metrics.toolFailures],
|
|
1005
|
+
['Failed convos', metrics.failedToolConversations],
|
|
1006
|
+
['Impacted tools', metrics.impactedTools],
|
|
1007
|
+
['Frustrated', metrics.frustrated],
|
|
1008
|
+
['At risk', metrics.atRisk],
|
|
1009
|
+
['Frustration %', metrics.frustrationRatePct],
|
|
1010
|
+
['Recent events', metrics.recentActivity],
|
|
1011
|
+
];
|
|
1012
|
+
}
|
|
1013
|
+
function recentActivity(data) {
|
|
1014
|
+
const activity = valueAt(data, ['recent_activity']);
|
|
1015
|
+
if (!Array.isArray(activity))
|
|
1016
|
+
return [];
|
|
1017
|
+
return activity.flatMap((item) => {
|
|
1018
|
+
const row = asRecord(item);
|
|
1019
|
+
const summary = asString(row?.summary) ?? asString(row?.title) ?? asString(row?.event);
|
|
1020
|
+
if (!summary)
|
|
1021
|
+
return [];
|
|
1022
|
+
return [{
|
|
1023
|
+
summary,
|
|
1024
|
+
timestamp: asString(row?.timestamp) ?? asString(row?.created_at) ?? undefined,
|
|
1025
|
+
conversationId: asString(row?.conversation_id) ?? undefined,
|
|
1026
|
+
}];
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
function valueAt(value, path) {
|
|
1030
|
+
let cursor = value;
|
|
1031
|
+
for (const key of path) {
|
|
1032
|
+
const record = asRecord(cursor);
|
|
1033
|
+
if (!record)
|
|
1034
|
+
return undefined;
|
|
1035
|
+
cursor = record[key];
|
|
1036
|
+
}
|
|
1037
|
+
return cursor;
|
|
1038
|
+
}
|
|
1039
|
+
function numberAt(value, path) {
|
|
1040
|
+
return numberValue(valueAt(value, path));
|
|
1041
|
+
}
|
|
1042
|
+
function numberValue(value) {
|
|
1043
|
+
const direct = asNumber(value);
|
|
1044
|
+
if (direct !== null)
|
|
1045
|
+
return direct;
|
|
1046
|
+
if (typeof value === 'string') {
|
|
1047
|
+
const parsed = Number(value);
|
|
1048
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
1049
|
+
}
|
|
1050
|
+
return 0;
|
|
1051
|
+
}
|
|
1052
|
+
function numberFlag(flags, key, fallback) {
|
|
1053
|
+
const raw = flags[key];
|
|
1054
|
+
const parsed = Number(raw);
|
|
1055
|
+
if (!Number.isFinite(parsed) || parsed < 1)
|
|
1056
|
+
return fallback;
|
|
1057
|
+
return Math.min(Math.floor(parsed), 90);
|
|
1058
|
+
}
|
|
1059
|
+
function timeRangeForDays(days) {
|
|
1060
|
+
if (days <= 1)
|
|
1061
|
+
return '24h';
|
|
1062
|
+
if (days <= 3)
|
|
1063
|
+
return '3d';
|
|
1064
|
+
if (days <= 7)
|
|
1065
|
+
return '7d';
|
|
1066
|
+
if (days <= 30)
|
|
1067
|
+
return '30d';
|
|
1068
|
+
return '90d';
|
|
1069
|
+
}
|
|
1070
|
+
function normalizeConfidence(value) {
|
|
1071
|
+
if (value === 'high' || value === 'medium' || value === 'low' || value === 'unknown')
|
|
1072
|
+
return value;
|
|
1073
|
+
return 'medium';
|
|
1074
|
+
}
|
|
1075
|
+
function capFallbackConfidence(confidence) {
|
|
1076
|
+
return confidence === 'high' ? 'medium' : confidence;
|
|
1077
|
+
}
|
|
1078
|
+
function evidenceKind(value) {
|
|
1079
|
+
if (value === 'data_api' ||
|
|
1080
|
+
value === 'tool_failure' ||
|
|
1081
|
+
value === 'frustration' ||
|
|
1082
|
+
value === 'loop' ||
|
|
1083
|
+
value === 'run' ||
|
|
1084
|
+
value === 'harness' ||
|
|
1085
|
+
value === 'ask')
|
|
1086
|
+
return value;
|
|
1087
|
+
return 'ask';
|
|
1088
|
+
}
|
|
1089
|
+
function normalizeKey(value) {
|
|
1090
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, '');
|
|
1091
|
+
}
|
|
1092
|
+
function slug(value) {
|
|
1093
|
+
const clean = value.replace(/[^a-zA-Z0-9_-]+/g, '_').replace(/^_+|_+$/g, '');
|
|
1094
|
+
return clean || 'unknown';
|
|
1095
|
+
}
|
|
1096
|
+
function purposeForCommand(command) {
|
|
1097
|
+
if (command.startsWith('moda investigate'))
|
|
1098
|
+
return 'Open the ranked production investigation.';
|
|
1099
|
+
if (command.startsWith('moda tool-failure-detail'))
|
|
1100
|
+
return 'Inspect tool failure examples and conversation anchors.';
|
|
1101
|
+
if (command.startsWith('moda context'))
|
|
1102
|
+
return 'Read the relevant conversation window.';
|
|
1103
|
+
if (command.startsWith('moda loop'))
|
|
1104
|
+
return 'Inspect loop detection details.';
|
|
1105
|
+
if (command.startsWith('moda run'))
|
|
1106
|
+
return 'Inspect one agent run and side-effect receipts.';
|
|
1107
|
+
if (command.startsWith('moda doctor'))
|
|
1108
|
+
return 'Validate Moda setup and data flow.';
|
|
1109
|
+
if (command.startsWith('moda overview'))
|
|
1110
|
+
return 'Return to the harness health briefing.';
|
|
1111
|
+
return 'Suggested next Moda command.';
|
|
1112
|
+
}
|
|
1113
|
+
//# sourceMappingURL=production-intelligence.js.map
|