@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.
@@ -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
- context.output.writeData(result, askAgentOptions(result));
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
- return { exitCode: 0 };
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
- // The endpoint is intentionally future-facing. Until cloud intelligence is
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 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.';
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: 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
- ],
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
- lines.push(` - ${ref.label}${ref.path ? ` (${ref.path})` : ''}`);
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" | "running" | "failed" | "succeeded" | "partial" | "loop" | "abandoned" | undefined;
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" | "running" | "failed" | "succeeded" | "partial" | "loop" | "abandoned" | undefined;
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:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moda-ai/cli",
3
- "version": "1.9.6",
3
+ "version": "1.10.0",
4
4
  "description": "CLI for Moda - AI agent analytics and observability",
5
5
  "type": "module",
6
6
  "bin": {