@moda-ai/cli 1.9.6 → 1.10.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/agent-schemas.d.ts +3 -0
- package/dist/agent-schemas.js +298 -0
- package/dist/api-client.d.ts +12 -1
- package/dist/api-client.js +10 -8
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +68 -9
- package/dist/command-context.d.ts +6 -0
- package/dist/command-context.js +1 -1
- package/dist/intelligence-schema.d.ts +349 -0
- package/dist/intelligence-schema.js +174 -0
- package/dist/output.d.ts +25 -2
- package/dist/output.js +71 -2
- package/dist/production-intelligence.d.ts +19 -0
- package/dist/production-intelligence.js +358 -17
- package/dist/schemas.d.ts +22 -2
- package/dist/schemas.js +10 -0
- package/package.json +1 -1
- package/skills/moda-cli/SKILL.md +148 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CommandContext } from './command-context.js';
|
|
2
2
|
import type { AgentConfidence } from './output.js';
|
|
3
|
+
import { type ModaIntelligenceV1 } from './intelligence-schema.js';
|
|
3
4
|
declare const INVESTIGATION_SCHEMA_VERSION = "production_investigation.v0.1";
|
|
4
5
|
declare const OVERVIEW_SCHEMA_VERSION = "production_overview.v0.1";
|
|
5
6
|
declare const ASK_SCHEMA_VERSION = "production_ask.v0.1";
|
|
@@ -99,6 +100,13 @@ export interface ProductionAskResult {
|
|
|
99
100
|
schemaVersion: typeof ASK_SCHEMA_VERSION;
|
|
100
101
|
generatedAt: string;
|
|
101
102
|
source: 'cloud' | 'local_fallback';
|
|
103
|
+
/**
|
|
104
|
+
* True when the answer did NOT come from Moda Cloud intelligence and was
|
|
105
|
+
* instead synthesized locally (cloud unreachable / unavailable / empty). This
|
|
106
|
+
* is the canonical in-payload signal of a degraded answer, mirrored by exit
|
|
107
|
+
* code 3 and envelope status `degraded`. Always `false` on the cloud path.
|
|
108
|
+
*/
|
|
109
|
+
degraded: boolean;
|
|
102
110
|
question: string;
|
|
103
111
|
answer: string;
|
|
104
112
|
confidence: AgentConfidence;
|
|
@@ -106,6 +114,17 @@ export interface ProductionAskResult {
|
|
|
106
114
|
findings: ProductionFinding[];
|
|
107
115
|
nextCommands: string[];
|
|
108
116
|
warnings: string[];
|
|
117
|
+
/** Why the cloud path was not used (set only when `degraded` is true). */
|
|
118
|
+
fallbackReason?: string;
|
|
119
|
+
/**
|
|
120
|
+
* The canonical `moda.intelligence.v1` projection of this answer (MODA-388).
|
|
121
|
+
* This is what `moda ask --json`/`--agent` surface as the agent envelope's
|
|
122
|
+
* `data`, so every surface reads one versioned, snake_case payload (carrying
|
|
123
|
+
* per-evidence `grounding`/`anchor` and per-claim grounding verdicts). On the
|
|
124
|
+
* cloud path it is taken from `response.intelligence` when present, else
|
|
125
|
+
* synthesized from the legacy snake_case fields for back-compat.
|
|
126
|
+
*/
|
|
127
|
+
intelligence: ModaIntelligenceV1;
|
|
109
128
|
}
|
|
110
129
|
export declare function runProductionCommand(context: CommandContext): Promise<{
|
|
111
130
|
exitCode: number;
|
|
@@ -2,7 +2,9 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { callControlAPI, callDataAPI } from './api-client.js';
|
|
4
4
|
import { CliInputError } from './errors.js';
|
|
5
|
+
import { EXIT_DEGRADED, EXIT_OK } from './output.js';
|
|
5
6
|
import { asNumber, asRecord, asString } from './transforms.js';
|
|
7
|
+
import { INTELLIGENCE_SCHEMA_VERSION, } from './intelligence-schema.js';
|
|
6
8
|
const INVESTIGATION_SCHEMA_VERSION = 'production_investigation.v0.1';
|
|
7
9
|
const OVERVIEW_SCHEMA_VERSION = 'production_overview.v0.1';
|
|
8
10
|
const ASK_SCHEMA_VERSION = 'production_ask.v0.1';
|
|
@@ -36,9 +38,17 @@ export async function runProductionCommand(context) {
|
|
|
36
38
|
console.log(renderAskHuman(result));
|
|
37
39
|
}
|
|
38
40
|
else {
|
|
39
|
-
|
|
41
|
+
// The agent/JSON `data` payload is the canonical moda.intelligence.v1
|
|
42
|
+
// value (carrying evidence grounding + per-claim verdicts). The
|
|
43
|
+
// moda.agent.v1 envelope still wraps it; only the inner `data` is v1.
|
|
44
|
+
context.output.writeData(result.intelligence, {
|
|
45
|
+
...askAgentOptions(result),
|
|
46
|
+
status: result.degraded ? 'degraded' : 'success',
|
|
47
|
+
});
|
|
40
48
|
}
|
|
41
|
-
|
|
49
|
+
// Exit 3 on a degraded/local-fallback answer so a scripted agent can detect
|
|
50
|
+
// it from the exit code alone (in addition to `data.degraded` / status).
|
|
51
|
+
return { exitCode: result.degraded ? EXIT_DEGRADED : EXIT_OK };
|
|
42
52
|
}
|
|
43
53
|
if (context.command === 'why-failed') {
|
|
44
54
|
const runId = context.positional;
|
|
@@ -184,6 +194,10 @@ export async function investigateProduction(options) {
|
|
|
184
194
|
};
|
|
185
195
|
}
|
|
186
196
|
export async function askModa(options) {
|
|
197
|
+
// Capture *why* the cloud path was not used so the caller (human or agent)
|
|
198
|
+
// can see it, instead of silently swallowing the error and masquerading the
|
|
199
|
+
// local fallback as a normal answer.
|
|
200
|
+
let fallbackReason = 'Moda Cloud ask endpoint returned no answer.';
|
|
187
201
|
try {
|
|
188
202
|
const response = await callControlAPI('/cli/ask', {
|
|
189
203
|
method: 'POST',
|
|
@@ -193,14 +207,14 @@ export async function askModa(options) {
|
|
|
193
207
|
profile: options.profile,
|
|
194
208
|
harness: localHarnessSummary(options.cwd),
|
|
195
209
|
}),
|
|
196
|
-
});
|
|
210
|
+
}, options.profile ? { profile: options.profile } : {}, { timeoutMs: askTimeoutMs(), retries: 0 });
|
|
197
211
|
const cloud = normalizeCloudAsk(response, options.question);
|
|
198
212
|
if (cloud)
|
|
199
213
|
return cloud;
|
|
214
|
+
fallbackReason = 'Moda Cloud ask endpoint returned an empty answer.';
|
|
200
215
|
}
|
|
201
|
-
catch {
|
|
202
|
-
|
|
203
|
-
// deployed everywhere, the CLI keeps answering from bounded Data API evidence.
|
|
216
|
+
catch (error) {
|
|
217
|
+
fallbackReason = `Moda Cloud ask endpoint unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
204
218
|
}
|
|
205
219
|
const investigation = await investigateProduction({
|
|
206
220
|
daysBack: options.daysBack,
|
|
@@ -209,24 +223,56 @@ export async function askModa(options) {
|
|
|
209
223
|
});
|
|
210
224
|
const top = investigation.findings[0];
|
|
211
225
|
const answer = top
|
|
212
|
-
? `Moda Cloud intelligence is not available
|
|
213
|
-
: 'Moda Cloud intelligence is not available
|
|
226
|
+
? `Moda Cloud intelligence is not available, so I synthesized this from production evidence. The strongest signal is: ${top.title}. ${top.summary}`
|
|
227
|
+
: 'Moda Cloud intelligence is not available, and the local Data API evidence did not contain a strong production signal.';
|
|
228
|
+
const confidence = capFallbackConfidence(investigation.summary.confidence);
|
|
229
|
+
const evidenceRefs = investigation.evidenceRefs.slice(0, 12);
|
|
230
|
+
const findings = investigation.findings.slice(0, 5);
|
|
231
|
+
const nextCommands = investigation.nextCommands;
|
|
232
|
+
const warnings = [
|
|
233
|
+
'Cloud ask endpoint unavailable; synthesized answer from local Data API evidence.',
|
|
234
|
+
fallbackReason,
|
|
235
|
+
...investigation.warnings,
|
|
236
|
+
];
|
|
214
237
|
return {
|
|
215
238
|
schemaVersion: ASK_SCHEMA_VERSION,
|
|
216
239
|
generatedAt: new Date().toISOString(),
|
|
217
240
|
source: 'local_fallback',
|
|
241
|
+
degraded: true,
|
|
242
|
+
fallbackReason,
|
|
218
243
|
question: options.question,
|
|
219
244
|
answer,
|
|
220
|
-
confidence
|
|
221
|
-
evidenceRefs
|
|
222
|
-
findings
|
|
223
|
-
nextCommands
|
|
224
|
-
warnings
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
245
|
+
confidence,
|
|
246
|
+
evidenceRefs,
|
|
247
|
+
findings,
|
|
248
|
+
nextCommands,
|
|
249
|
+
warnings,
|
|
250
|
+
// Degraded answers still carry a v1-shaped payload so every surface reads
|
|
251
|
+
// one contract; `source`/`degraded` flag the lower trust (preserving exit
|
|
252
|
+
// code 3 + status 'degraded').
|
|
253
|
+
intelligence: synthesizeIntelligenceV1({
|
|
254
|
+
question: options.question,
|
|
255
|
+
answer,
|
|
256
|
+
confidence,
|
|
257
|
+
evidenceRefs,
|
|
258
|
+
nextCommands,
|
|
259
|
+
warnings,
|
|
260
|
+
source: 'local_fallback',
|
|
261
|
+
degraded: true,
|
|
262
|
+
findings,
|
|
263
|
+
}),
|
|
228
264
|
};
|
|
229
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* Timeout (ms) for the cloud `ask` POST. The backend enforces its own ~240s run
|
|
268
|
+
* deadline under a 300s function limit, so the client waits long enough to
|
|
269
|
+
* receive a real answer rather than aborting a healthy run at 30s and silently
|
|
270
|
+
* falling back. Override with `MODA_ASK_TIMEOUT_MS`.
|
|
271
|
+
*/
|
|
272
|
+
function askTimeoutMs() {
|
|
273
|
+
const raw = Number(process.env.MODA_ASK_TIMEOUT_MS);
|
|
274
|
+
return Number.isFinite(raw) && raw > 0 ? raw : 250_000;
|
|
275
|
+
}
|
|
230
276
|
function writeInvestigation(context, investigation) {
|
|
231
277
|
if (context.outputMode === 'human') {
|
|
232
278
|
if (!context.quiet)
|
|
@@ -547,10 +593,27 @@ function normalizeCloudAsk(response, question) {
|
|
|
547
593
|
? record.next_commands.filter((item) => typeof item === 'string')
|
|
548
594
|
: ['moda investigate'];
|
|
549
595
|
const confidence = normalizeConfidence(record?.confidence);
|
|
596
|
+
// Prefer the backend's canonical v1 payload when it ships one; otherwise
|
|
597
|
+
// synthesize a v1 projection from the legacy snake_case fields so older
|
|
598
|
+
// backends still produce a valid moda.intelligence.v1 `data`.
|
|
599
|
+
const intelligenceRecord = asRecord(record?.intelligence);
|
|
600
|
+
const intelligence = looksLikeIntelligenceV1(intelligenceRecord)
|
|
601
|
+
? coerceIntelligenceV1(intelligenceRecord, { question, answer, confidence })
|
|
602
|
+
: synthesizeIntelligenceV1({
|
|
603
|
+
question,
|
|
604
|
+
answer,
|
|
605
|
+
confidence,
|
|
606
|
+
evidenceRefs,
|
|
607
|
+
nextCommands,
|
|
608
|
+
warnings: [],
|
|
609
|
+
source: 'cloud',
|
|
610
|
+
degraded: false,
|
|
611
|
+
});
|
|
550
612
|
return {
|
|
551
613
|
schemaVersion: ASK_SCHEMA_VERSION,
|
|
552
614
|
generatedAt: new Date().toISOString(),
|
|
553
615
|
source: 'cloud',
|
|
616
|
+
degraded: false,
|
|
554
617
|
question,
|
|
555
618
|
answer,
|
|
556
619
|
confidence,
|
|
@@ -558,8 +621,260 @@ function normalizeCloudAsk(response, question) {
|
|
|
558
621
|
findings: [],
|
|
559
622
|
nextCommands,
|
|
560
623
|
warnings: [],
|
|
624
|
+
intelligence,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Heuristic "is this the canonical v1 payload?" guard. We accept an object that
|
|
629
|
+
* either declares the matching `schema_version` or carries the core v1 fields
|
|
630
|
+
* (`answer_markdown` / `summary`). A present-but-wrong `schema_version` is
|
|
631
|
+
* rejected so a future breaking version falls back to synthesis rather than
|
|
632
|
+
* being mis-projected.
|
|
633
|
+
*/
|
|
634
|
+
function looksLikeIntelligenceV1(record) {
|
|
635
|
+
if (!record)
|
|
636
|
+
return false;
|
|
637
|
+
const version = asString(record.schema_version);
|
|
638
|
+
if (version && version !== INTELLIGENCE_SCHEMA_VERSION)
|
|
639
|
+
return false;
|
|
640
|
+
return (version === INTELLIGENCE_SCHEMA_VERSION ||
|
|
641
|
+
typeof record.answer_markdown === 'string' ||
|
|
642
|
+
asRecord(record.summary) !== null);
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Defensively project a backend `intelligence` object into a schema-valid
|
|
646
|
+
* {@link ModaIntelligenceV1}. Every field is coerced and defaulted so the CLI
|
|
647
|
+
* never emits a malformed payload even if the backend is partially shaped.
|
|
648
|
+
*/
|
|
649
|
+
function coerceIntelligenceV1(record, context) {
|
|
650
|
+
const summaryRecord = asRecord(record.summary);
|
|
651
|
+
const summary = {
|
|
652
|
+
text: asString(summaryRecord?.text) ?? context.answer,
|
|
653
|
+
confidence: confidenceValue(summaryRecord?.confidence) ?? context.confidence,
|
|
654
|
+
};
|
|
655
|
+
const evidenceRefs = Array.isArray(record.evidence_refs)
|
|
656
|
+
? record.evidence_refs.flatMap(coerceV1EvidenceRef)
|
|
657
|
+
: [];
|
|
658
|
+
const answerClaims = Array.isArray(record.answer_claims)
|
|
659
|
+
? record.answer_claims.flatMap(coerceV1Claim)
|
|
660
|
+
: [];
|
|
661
|
+
const findings = Array.isArray(record.findings)
|
|
662
|
+
? record.findings.flatMap(coerceV1Finding)
|
|
663
|
+
: [];
|
|
664
|
+
const nextCommands = Array.isArray(record.next_commands)
|
|
665
|
+
? record.next_commands.flatMap(coerceV1NextCommand)
|
|
666
|
+
: [];
|
|
667
|
+
const warnings = stringArray(record.warnings);
|
|
668
|
+
const meta = asRecord(record.meta);
|
|
669
|
+
return {
|
|
670
|
+
schema_version: INTELLIGENCE_SCHEMA_VERSION,
|
|
671
|
+
generated_at: asString(record.generated_at) ?? new Date().toISOString(),
|
|
672
|
+
command: asString(record.command) ?? 'ask',
|
|
673
|
+
source: record.source === 'local_fallback' ? 'local_fallback' : 'cloud',
|
|
674
|
+
degraded: record.degraded === true,
|
|
675
|
+
question: asString(record.question) ?? context.question,
|
|
676
|
+
summary,
|
|
677
|
+
answer_markdown: asString(record.answer_markdown) ?? context.answer,
|
|
678
|
+
answer_claims: answerClaims,
|
|
679
|
+
findings,
|
|
680
|
+
evidence_refs: evidenceRefs,
|
|
681
|
+
next_commands: nextCommands,
|
|
682
|
+
warnings,
|
|
683
|
+
...(meta ? { meta } : {}),
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Build a v1 payload from legacy snake_case fields (or local-fallback evidence).
|
|
688
|
+
* `evidence_refs` default to `grounding: 'measured'` and `answer_claims` is
|
|
689
|
+
* empty because the legacy contract has no per-claim grounding verdicts.
|
|
690
|
+
*/
|
|
691
|
+
function synthesizeIntelligenceV1(options) {
|
|
692
|
+
return {
|
|
693
|
+
schema_version: INTELLIGENCE_SCHEMA_VERSION,
|
|
694
|
+
generated_at: new Date().toISOString(),
|
|
695
|
+
command: 'ask',
|
|
696
|
+
source: options.source,
|
|
697
|
+
degraded: options.degraded,
|
|
698
|
+
question: options.question,
|
|
699
|
+
summary: { text: options.answer, confidence: options.confidence },
|
|
700
|
+
answer_markdown: options.answer,
|
|
701
|
+
answer_claims: [],
|
|
702
|
+
findings: (options.findings ?? []).map(productionFindingToV1),
|
|
703
|
+
evidence_refs: options.evidenceRefs.map(productionEvidenceToV1),
|
|
704
|
+
next_commands: options.nextCommands.map(commandStringToV1),
|
|
705
|
+
warnings: options.warnings,
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
function coerceV1EvidenceRef(item) {
|
|
709
|
+
const ref = asRecord(item);
|
|
710
|
+
if (!ref)
|
|
711
|
+
return [];
|
|
712
|
+
const label = asString(ref.label);
|
|
713
|
+
const anchor = coerceV1Anchor(asRecord(ref.anchor));
|
|
714
|
+
const snippet = asString(ref.snippet);
|
|
715
|
+
return [{
|
|
716
|
+
id: asString(ref.id) ?? `ask:evidence:${slug(label ?? 'ref')}`,
|
|
717
|
+
kind: asString(ref.kind) ?? 'ask',
|
|
718
|
+
label: label ?? asString(ref.id) ?? 'Cloud evidence',
|
|
719
|
+
...(anchor ? { anchor } : {}),
|
|
720
|
+
...(snippet ? { snippet } : {}),
|
|
721
|
+
grounding: provenanceValue(ref.grounding) ?? 'measured',
|
|
722
|
+
}];
|
|
723
|
+
}
|
|
724
|
+
function coerceV1Anchor(record) {
|
|
725
|
+
if (!record)
|
|
726
|
+
return undefined;
|
|
727
|
+
const anchor = {
|
|
728
|
+
...(asString(record.conversation_id) ? { conversation_id: asString(record.conversation_id) } : {}),
|
|
729
|
+
...(asNumber(record.message_index) !== null ? { message_index: asNumber(record.message_index) } : {}),
|
|
730
|
+
...(asString(record.run_id) ? { run_id: asString(record.run_id) } : {}),
|
|
731
|
+
...(asString(record.tool_name) ? { tool_name: asString(record.tool_name) } : {}),
|
|
732
|
+
};
|
|
733
|
+
return Object.keys(anchor).length > 0 ? anchor : undefined;
|
|
734
|
+
}
|
|
735
|
+
function coerceV1Claim(item) {
|
|
736
|
+
const claim = asRecord(item);
|
|
737
|
+
if (!claim)
|
|
738
|
+
return [];
|
|
739
|
+
const text = asString(claim.text);
|
|
740
|
+
if (!text)
|
|
741
|
+
return [];
|
|
742
|
+
const value = asNumber(claim.value);
|
|
743
|
+
const matchedPath = asString(claim.matched_path);
|
|
744
|
+
return [{
|
|
745
|
+
text,
|
|
746
|
+
...(value !== null ? { value } : {}),
|
|
747
|
+
kind: claim.kind === 'percentage' || claim.kind === 'count' ? claim.kind : 'qualitative',
|
|
748
|
+
status: claim.status === 'grounded' || claim.status === 'ungrounded'
|
|
749
|
+
? claim.status
|
|
750
|
+
: 'unverifiable',
|
|
751
|
+
evidence_ref_ids: stringArray(claim.evidence_ref_ids),
|
|
752
|
+
...(matchedPath ? { matched_path: matchedPath } : {}),
|
|
753
|
+
}];
|
|
754
|
+
}
|
|
755
|
+
function coerceV1Finding(item) {
|
|
756
|
+
const finding = asRecord(item);
|
|
757
|
+
if (!finding)
|
|
758
|
+
return [];
|
|
759
|
+
const id = asString(finding.id);
|
|
760
|
+
const title = asString(finding.title);
|
|
761
|
+
if (!id || !title)
|
|
762
|
+
return [];
|
|
763
|
+
const severity = finding.severity;
|
|
764
|
+
const description = asString(finding.description);
|
|
765
|
+
return [{
|
|
766
|
+
id,
|
|
767
|
+
title,
|
|
768
|
+
...(severity === 'critical' || severity === 'high' || severity === 'medium' ||
|
|
769
|
+
severity === 'low' || severity === 'info'
|
|
770
|
+
? { severity }
|
|
771
|
+
: {}),
|
|
772
|
+
...(description ? { description } : {}),
|
|
773
|
+
evidence_ref_ids: stringArray(finding.evidence_ref_ids),
|
|
774
|
+
recommended_actions: Array.isArray(finding.recommended_actions)
|
|
775
|
+
? finding.recommended_actions.flatMap(coerceV1RecommendedAction)
|
|
776
|
+
: [],
|
|
777
|
+
}];
|
|
778
|
+
}
|
|
779
|
+
function coerceV1RecommendedAction(item) {
|
|
780
|
+
const action = asRecord(item);
|
|
781
|
+
if (!action)
|
|
782
|
+
return [];
|
|
783
|
+
const id = asString(action.id);
|
|
784
|
+
const title = asString(action.title);
|
|
785
|
+
if (!id || !title)
|
|
786
|
+
return [];
|
|
787
|
+
const rationale = asString(action.rationale);
|
|
788
|
+
const command = asString(action.command);
|
|
789
|
+
return [{
|
|
790
|
+
id,
|
|
791
|
+
title,
|
|
792
|
+
...(rationale ? { rationale } : {}),
|
|
793
|
+
...(command ? { command } : {}),
|
|
794
|
+
mutability: mutabilityValue(action.mutability) ?? 'unknown',
|
|
795
|
+
requires_approval: action.requires_approval === true,
|
|
796
|
+
}];
|
|
797
|
+
}
|
|
798
|
+
function coerceV1NextCommand(item) {
|
|
799
|
+
if (typeof item === 'string')
|
|
800
|
+
return [commandStringToV1(item)];
|
|
801
|
+
const record = asRecord(item);
|
|
802
|
+
if (!record)
|
|
803
|
+
return [];
|
|
804
|
+
const command = asString(record.command);
|
|
805
|
+
if (!command)
|
|
806
|
+
return [];
|
|
807
|
+
const purpose = asString(record.purpose);
|
|
808
|
+
return [{
|
|
809
|
+
command,
|
|
810
|
+
...(purpose ? { purpose } : {}),
|
|
811
|
+
mutability: mutabilityValue(record.mutability) ?? mutabilityForCommand(command),
|
|
812
|
+
requires_approval: typeof record.requires_approval === 'boolean'
|
|
813
|
+
? record.requires_approval
|
|
814
|
+
: requiresApprovalForCommand(command),
|
|
815
|
+
}];
|
|
816
|
+
}
|
|
817
|
+
function productionEvidenceToV1(ref) {
|
|
818
|
+
const anchor = {
|
|
819
|
+
...(ref.conversationId ? { conversation_id: ref.conversationId } : {}),
|
|
820
|
+
...(ref.runId ? { run_id: ref.runId } : {}),
|
|
821
|
+
...(ref.toolName ? { tool_name: ref.toolName } : {}),
|
|
822
|
+
};
|
|
823
|
+
return {
|
|
824
|
+
id: ref.id,
|
|
825
|
+
kind: ref.kind,
|
|
826
|
+
label: ref.label,
|
|
827
|
+
...(Object.keys(anchor).length > 0 ? { anchor } : {}),
|
|
828
|
+
grounding: 'measured',
|
|
561
829
|
};
|
|
562
830
|
}
|
|
831
|
+
function productionFindingToV1(finding) {
|
|
832
|
+
return {
|
|
833
|
+
id: finding.id,
|
|
834
|
+
title: finding.title,
|
|
835
|
+
severity: finding.severity,
|
|
836
|
+
description: `${finding.summary} ${finding.rankReason}`.trim(),
|
|
837
|
+
evidence_ref_ids: finding.evidenceRefIds,
|
|
838
|
+
recommended_actions: finding.recommendedActions.map((action) => ({
|
|
839
|
+
id: action.id,
|
|
840
|
+
title: action.title,
|
|
841
|
+
rationale: action.rationale,
|
|
842
|
+
command: action.command,
|
|
843
|
+
mutability: action.mutability,
|
|
844
|
+
requires_approval: action.requiresApproval,
|
|
845
|
+
})),
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
function commandStringToV1(command) {
|
|
849
|
+
return {
|
|
850
|
+
command,
|
|
851
|
+
purpose: purposeForCommand(command),
|
|
852
|
+
mutability: mutabilityForCommand(command),
|
|
853
|
+
requires_approval: requiresApprovalForCommand(command),
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
function mutabilityForCommand(command) {
|
|
857
|
+
return command.includes(' sync') ? 'sync' : 'read';
|
|
858
|
+
}
|
|
859
|
+
function requiresApprovalForCommand(command) {
|
|
860
|
+
return command.includes(' sync') || command.includes(' --yes');
|
|
861
|
+
}
|
|
862
|
+
function stringArray(value) {
|
|
863
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [];
|
|
864
|
+
}
|
|
865
|
+
function confidenceValue(value) {
|
|
866
|
+
return value === 'high' || value === 'medium' || value === 'low' || value === 'unknown'
|
|
867
|
+
? value
|
|
868
|
+
: null;
|
|
869
|
+
}
|
|
870
|
+
function provenanceValue(value) {
|
|
871
|
+
return value === 'measured' || value === 'inferred' ? value : null;
|
|
872
|
+
}
|
|
873
|
+
function mutabilityValue(value) {
|
|
874
|
+
return value === 'read' || value === 'write' || value === 'sync' || value === 'unknown'
|
|
875
|
+
? value
|
|
876
|
+
: null;
|
|
877
|
+
}
|
|
563
878
|
function investigationAgentOptions(investigation) {
|
|
564
879
|
return {
|
|
565
880
|
summary: investigation.summary,
|
|
@@ -585,6 +900,17 @@ function overviewAgentOptions(briefing) {
|
|
|
585
900
|
};
|
|
586
901
|
}
|
|
587
902
|
function askAgentOptions(result) {
|
|
903
|
+
const errors = result.degraded
|
|
904
|
+
? [{
|
|
905
|
+
code: 'cloud_unavailable',
|
|
906
|
+
message: result.fallbackReason ??
|
|
907
|
+
'Moda Cloud intelligence was unavailable; answer synthesized from local Data API evidence.',
|
|
908
|
+
// Non-blocking: an answer WAS produced, just from a degraded source. The
|
|
909
|
+
// agent should still treat it as lower-trust (see exit code 3 / status).
|
|
910
|
+
blocking: false,
|
|
911
|
+
suggested_commands: ['moda doctor --online'],
|
|
912
|
+
}]
|
|
913
|
+
: [];
|
|
588
914
|
return {
|
|
589
915
|
summary: { text: result.answer, confidence: result.confidence },
|
|
590
916
|
findings: result.findings.map(agentFinding),
|
|
@@ -592,6 +918,7 @@ function askAgentOptions(result) {
|
|
|
592
918
|
recommendedActions: result.findings.flatMap((finding) => finding.recommendedActions.map(agentAction)),
|
|
593
919
|
nextCommands: result.nextCommands.map(nextCommand),
|
|
594
920
|
warnings: result.warnings,
|
|
921
|
+
errors,
|
|
595
922
|
};
|
|
596
923
|
}
|
|
597
924
|
function agentFinding(finding) {
|
|
@@ -707,17 +1034,31 @@ function renderAskHuman(result) {
|
|
|
707
1034
|
const lines = [];
|
|
708
1035
|
lines.push('Moda ask');
|
|
709
1036
|
lines.push(`Question: ${result.question}`);
|
|
1037
|
+
if (result.degraded) {
|
|
1038
|
+
lines.push('');
|
|
1039
|
+
lines.push('[degraded] Moda Cloud was unavailable — this answer is synthesized from local Data API evidence (lower trust).');
|
|
1040
|
+
}
|
|
710
1041
|
lines.push('');
|
|
711
1042
|
lines.push(result.answer);
|
|
712
1043
|
lines.push('');
|
|
713
1044
|
lines.push(`Confidence: ${result.confidence}`);
|
|
1045
|
+
lines.push(`Source: ${result.source}${result.degraded ? ' (degraded)' : ''}`);
|
|
714
1046
|
if (result.evidenceRefs.length > 0) {
|
|
715
1047
|
lines.push('');
|
|
716
1048
|
lines.push('Evidence');
|
|
1049
|
+
// Subtle grounding marker: flag model-inferred evidence so a human reader
|
|
1050
|
+
// can weight it. Ids/anchors stay hidden in the human projection.
|
|
1051
|
+
const grounding = new Map(result.intelligence.evidence_refs.map((ref) => [ref.id, ref.grounding]));
|
|
717
1052
|
for (const ref of result.evidenceRefs.slice(0, 8)) {
|
|
718
|
-
|
|
1053
|
+
const marker = grounding.get(ref.id) === 'inferred' ? ' (inferred)' : '';
|
|
1054
|
+
lines.push(` - ${ref.label}${ref.path ? ` (${ref.path})` : ''}${marker}`);
|
|
719
1055
|
}
|
|
720
1056
|
}
|
|
1057
|
+
const unverifiedClaims = result.intelligence.answer_claims.filter((claim) => claim.status !== 'grounded').length;
|
|
1058
|
+
if (unverifiedClaims > 0) {
|
|
1059
|
+
lines.push('');
|
|
1060
|
+
lines.push(`Note: ${unverifiedClaims} claim(s) could not be grounded against tool data.`);
|
|
1061
|
+
}
|
|
721
1062
|
if (result.nextCommands.length > 0) {
|
|
722
1063
|
lines.push('');
|
|
723
1064
|
lines.push('Next commands');
|
package/dist/schemas.d.ts
CHANGED
|
@@ -141,6 +141,26 @@ export declare const ToolFailureDetailSchema: z.ZodObject<{
|
|
|
141
141
|
include_window?: unknown;
|
|
142
142
|
subtype?: string | undefined;
|
|
143
143
|
}>;
|
|
144
|
+
/**
|
|
145
|
+
* moda audit <conversation_id|trace_id> — raw, non-deduped span/trace read.
|
|
146
|
+
* `kind=auto` (default) lets the backend try trace first, then conversation.
|
|
147
|
+
*/
|
|
148
|
+
export declare const AuditSchema: z.ZodObject<{
|
|
149
|
+
id: z.ZodString;
|
|
150
|
+
kind: z.ZodOptional<z.ZodDefault<z.ZodEnum<["auto", "trace", "conversation"]>>>;
|
|
151
|
+
include_raw: z.ZodOptional<z.ZodEffects<z.ZodBoolean, boolean, unknown>>;
|
|
152
|
+
limit: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
|
|
153
|
+
}, "strip", z.ZodTypeAny, {
|
|
154
|
+
id: string;
|
|
155
|
+
kind?: "auto" | "trace" | "conversation" | undefined;
|
|
156
|
+
limit?: number | undefined;
|
|
157
|
+
include_raw?: boolean | undefined;
|
|
158
|
+
}, {
|
|
159
|
+
id: string;
|
|
160
|
+
kind?: "auto" | "trace" | "conversation" | undefined;
|
|
161
|
+
limit?: number | undefined;
|
|
162
|
+
include_raw?: unknown;
|
|
163
|
+
}>;
|
|
144
164
|
/**
|
|
145
165
|
* moda search "<query>" — keyword/semantic/hybrid search across conversation
|
|
146
166
|
* messages. `q` arrives as a positional arg; the runCommand handler rejects an
|
|
@@ -190,14 +210,14 @@ export declare const RunsSchema: z.ZodObject<{
|
|
|
190
210
|
limit?: number | undefined;
|
|
191
211
|
offset?: number | undefined;
|
|
192
212
|
user_id?: string | undefined;
|
|
193
|
-
outcome?: "unknown" | "
|
|
213
|
+
outcome?: "unknown" | "loop" | "running" | "failed" | "succeeded" | "partial" | "abandoned" | undefined;
|
|
194
214
|
class_id?: string | undefined;
|
|
195
215
|
}, {
|
|
196
216
|
time_range?: "all" | "1h" | "3d" | "7d" | "24h" | "30d" | "90d" | undefined;
|
|
197
217
|
limit?: number | undefined;
|
|
198
218
|
offset?: number | undefined;
|
|
199
219
|
user_id?: string | undefined;
|
|
200
|
-
outcome?: "unknown" | "
|
|
220
|
+
outcome?: "unknown" | "loop" | "running" | "failed" | "succeeded" | "partial" | "abandoned" | undefined;
|
|
201
221
|
class_id?: string | undefined;
|
|
202
222
|
}>;
|
|
203
223
|
/** moda run <runId> — single execution detail with side-effect receipts. */
|
package/dist/schemas.js
CHANGED
|
@@ -68,6 +68,16 @@ export const ToolFailureDetailSchema = z.object({
|
|
|
68
68
|
include_window: boolFlag(),
|
|
69
69
|
window: z.number().min(1).max(5).default(1).optional(),
|
|
70
70
|
});
|
|
71
|
+
/**
|
|
72
|
+
* moda audit <conversation_id|trace_id> — raw, non-deduped span/trace read.
|
|
73
|
+
* `kind=auto` (default) lets the backend try trace first, then conversation.
|
|
74
|
+
*/
|
|
75
|
+
export const AuditSchema = z.object({
|
|
76
|
+
id: z.string().min(1),
|
|
77
|
+
kind: z.enum(['auto', 'trace', 'conversation']).default('auto').optional(),
|
|
78
|
+
include_raw: boolFlag(),
|
|
79
|
+
limit: z.number().min(1).max(1000).default(200).optional(),
|
|
80
|
+
});
|
|
71
81
|
// ─── Workflows + Runs (agent runs runtime) ──────────────────
|
|
72
82
|
//
|
|
73
83
|
// Naming model:
|