@moda-ai/cli 1.48.0 → 1.50.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.
@@ -7,7 +7,7 @@ import {
7
7
  saveProfileConfig,
8
8
  secretFilePathFromRef,
9
9
  writeSecretJsonFile
10
- } from "./cli-233xm35b.js";
10
+ } from "./cli-xrs02c0t.js";
11
11
  import {
12
12
  __require
13
13
  } from "./cli-0v6na3yp.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  authFetch,
3
3
  getCachedLoginContext
4
- } from "./cli-fpt3jkyh.js";
4
+ } from "./cli-7kbzqact.js";
5
5
  import {
6
6
  CliAccessError,
7
7
  CliInputError,
@@ -15,7 +15,7 @@ import {
15
15
  resolveTenantIdFromActiveProfile,
16
16
  saveCliConfig,
17
17
  writeSecretJsonFile
18
- } from "./cli-233xm35b.js";
18
+ } from "./cli-xrs02c0t.js";
19
19
 
20
20
  // src/init/tenant.ts
21
21
  import { randomUUID } from "node:crypto";
@@ -13,7 +13,7 @@ import {
13
13
  resolvePromptSource,
14
14
  safePromptEvidencePath,
15
15
  scanHarness
16
- } from "./cli-tznhpmf2.js";
16
+ } from "./cli-x0x0n35w.js";
17
17
  import {
18
18
  SKILL_SOURCE_HARNESS,
19
19
  SKILL_SOURCE_INTEGRATION_BUNDLED,
@@ -26,7 +26,7 @@ import {
26
26
  import {
27
27
  isAuthSessionValid,
28
28
  loadAuthSession
29
- } from "./cli-fpt3jkyh.js";
29
+ } from "./cli-7kbzqact.js";
30
30
  import {
31
31
  CliInputError,
32
32
  EXIT_ERROR,
@@ -45,7 +45,7 @@ import {
45
45
  resolveTenantId,
46
46
  secretFilePathFromRef,
47
47
  validateConfig
48
- } from "./cli-233xm35b.js";
48
+ } from "./cli-xrs02c0t.js";
49
49
  import {
50
50
  __commonJS,
51
51
  __require,
@@ -6711,7 +6711,7 @@ async function runPromptSyncExclusive(flags, profileOptions, options) {
6711
6711
  const analyzedDefinitions = new Map;
6712
6712
  if (flags["no-analyze"] !== "true") {
6713
6713
  options.onProgress?.("Analyzing prompt sources before sync.");
6714
- const { analyzeHarnessPrompts } = await import("./harness-vf1e1t2h.js");
6714
+ const { analyzeHarnessPrompts } = await import("./harness-7gr0xbtc.js");
6715
6715
  const result = await analyzeHarnessPrompts({
6716
6716
  rootDir: process.cwd(),
6717
6717
  existing: established.flatMap((prompt) => prompt.sourceDefinition ? [prompt.sourceDefinition.source] : []),
@@ -25,7 +25,7 @@ import {
25
25
  resolveTenantId,
26
26
  shouldInventoryFile,
27
27
  stringOption
28
- } from "./cli-233xm35b.js";
28
+ } from "./cli-xrs02c0t.js";
29
29
  import {
30
30
  __commonJS,
31
31
  __require,
@@ -177909,13 +177909,59 @@ var apiRequestCount = 0;
177909
177909
  class ApiError extends Error {
177910
177910
  statusCode;
177911
177911
  responseBody;
177912
+ code;
177913
+ suggestions;
177914
+ readable;
177912
177915
  constructor(message, statusCode, responseBody) {
177913
- super(message);
177916
+ const typed = parseTypedErrorBody(responseBody);
177917
+ const platform = platformFailure(responseBody);
177918
+ const readable = typed?.code === "audit_too_large" || platform !== null;
177919
+ super(readable ? `API error (${statusCode}): ${typed?.message ?? platform?.message}` : message);
177914
177920
  this.statusCode = statusCode;
177915
177921
  this.responseBody = responseBody;
177916
177922
  this.name = "ApiError";
177923
+ this.code = typed?.code ?? platform?.code;
177924
+ this.suggestions = typed?.suggestions;
177925
+ this.readable = readable;
177926
+ }
177927
+ }
177928
+ function parseTypedErrorBody(body) {
177929
+ if (!body.trimStart().startsWith("{"))
177930
+ return null;
177931
+ try {
177932
+ const parsed = JSON.parse(body);
177933
+ if (typeof parsed.code !== "string")
177934
+ return null;
177935
+ const suggestions = Array.isArray(parsed.suggestions) ? parsed.suggestions.filter((s) => typeof s === "string") : undefined;
177936
+ return {
177937
+ code: parsed.code,
177938
+ message: typeof parsed.message === "string" ? parsed.message : undefined,
177939
+ suggestions
177940
+ };
177941
+ } catch {
177942
+ return null;
177917
177943
  }
177918
177944
  }
177945
+ var DETERMINISTIC_PLATFORM_FAILURES = [
177946
+ "FUNCTION_INVOCATION_FAILED",
177947
+ "FUNCTION_RESPONSE_PAYLOAD_TOO_LARGE"
177948
+ ];
177949
+ function platformFailure(body) {
177950
+ const code = DETERMINISTIC_PLATFORM_FAILURES.find((c) => body.includes(c));
177951
+ if (!code)
177952
+ return null;
177953
+ const requestId = /\b([a-z]{3}\d::[A-Za-z0-9:-]+)/.exec(body)?.[1];
177954
+ const cause = code === "FUNCTION_RESPONSE_PAYLOAD_TOO_LARGE" ? "the response was larger than the hosting platform allows" : "the server function crashed (typically out of memory or an oversized response)";
177955
+ return {
177956
+ code,
177957
+ message: `The Moda API could not complete this request: ${cause} [${code}` + `${requestId ? `, request ${requestId}` : ""}]. Retrying the same request will fail the same way; ` + "narrow it (a smaller --limit, a single trace, or fewer results) and report it with `moda feedback` if it persists."
177958
+ };
177959
+ }
177960
+ function isRetryableResponse(status, body) {
177961
+ if (!isRetryable(status))
177962
+ return false;
177963
+ return platformFailure(body) === null;
177964
+ }
177919
177965
 
177920
177966
  class NetworkError extends Error {
177921
177967
  constructor(message) {
@@ -177970,7 +178016,7 @@ async function callModaAPI(url, options, profileOptions = {}, requestOptions = {
177970
178016
  if (!response.ok) {
177971
178017
  const errorText = await response.text();
177972
178018
  const error = new ApiError(`API error (${response.status}): ${errorText}`, response.status, errorText);
177973
- if (isRetryable(response.status) && attempt < maxRetries) {
178019
+ if (isRetryableResponse(response.status, errorText) && attempt < maxRetries) {
177974
178020
  continue;
177975
178021
  }
177976
178022
  throw error;
@@ -180164,7 +180210,7 @@ function citationExcerpt(rootDir, path, line) {
180164
180210
  const selected = line ? lines[line - 1] : lines.find((candidate) => candidate.trim().length > 0);
180165
180211
  if (!selected)
180166
180212
  return {};
180167
- return { excerpt: selected.trim().slice(0, 240) };
180213
+ return { excerpt: selected.trim() };
180168
180214
  } catch {
180169
180215
  return {};
180170
180216
  }
@@ -181019,7 +181065,6 @@ function enrichScanCaptureMetadata(graph) {
181019
181065
  // src/harness-candidates.ts
181020
181066
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
181021
181067
  import { basename as basename3, dirname as dirname3, extname, resolve as resolve4 } from "node:path";
181022
- var EXCERPT_MAX_CHARS = 240;
181023
181068
  var MAX_CANDIDATES_PER_FILE_PER_SIGNAL = 20;
181024
181069
  var MIN_PROMPT_LITERAL_CHARS = 160;
181025
181070
  var CONFIDENCE_WEIGHT = {
@@ -181265,8 +181310,7 @@ function instructionScore(text) {
181265
181310
  return score;
181266
181311
  }
181267
181312
  function normalizeExcerpt(text) {
181268
- const collapsed = text.replace(/\s+/g, " ").trim();
181269
- return collapsed.length > EXCERPT_MAX_CHARS ? `${collapsed.slice(0, EXCERPT_MAX_CHARS - 1)}…` : collapsed;
181313
+ return text.replace(/\s+/g, " ").trim();
181270
181314
  }
181271
181315
  function buildLineIndex(text) {
181272
181316
  const offsets = [0];
@@ -181377,7 +181421,7 @@ function detectPromptModule(file, out) {
181377
181421
  `).length),
181378
181422
  confidence: "medium",
181379
181423
  signal: "prompt_module.name_hint",
181380
- excerpt: normalizeExcerpt(file.text.slice(0, EXCERPT_MAX_CHARS * 2))
181424
+ excerpt: normalizeExcerpt(file.text)
181381
181425
  });
181382
181426
  }
181383
181427
  function detectPromptLiterals(file, out) {
@@ -181460,7 +181504,7 @@ function detectPromptFile(file, out) {
181460
181504
  `).length),
181461
181505
  confidence,
181462
181506
  signal,
181463
- excerpt: normalizeExcerpt(file.text.slice(0, EXCERPT_MAX_CHARS * 2))
181507
+ excerpt: normalizeExcerpt(file.text)
181464
181508
  });
181465
181509
  }
181466
181510
  function detectPromptLoaderReferences(file, filesByPath, out) {
@@ -184813,7 +184857,7 @@ async function runHarnessCommand(context) {
184813
184857
  throw new CliInputError("Focused remote prompt analysis is not supported yet.", "Use local --analyst=claude|codex|cursor|local-scan, or drop --concern for a full remote report.");
184814
184858
  }
184815
184859
  if (context.flags["github-actions"] === "true") {
184816
- const { runGithubActionsAnalyze } = await import("./harness-github-actions-379faaqe.js");
184860
+ const { runGithubActionsAnalyze } = await import("./harness-github-actions-q9kewzb6.js");
184817
184861
  const result = await runGithubActionsAnalyze(rootDir, {
184818
184862
  writeReport: (report2) => {
184819
184863
  const normalized = normalizeHarnessReport(report2);
@@ -186559,6 +186603,7 @@ var CONCERN_CANDIDATE_KINDS = {
186559
186603
  evals: ["model_config"]
186560
186604
  };
186561
186605
  var MAX_SEED_CANDIDATES_PER_KIND = 40;
186606
+ var MAX_INLINE_CANDIDATE_EXCERPT_CHARS = 500;
186562
186607
  var MAX_SEED_READ_PLAN_FILES = 25;
186563
186608
  var SEED_CANDIDATE_KIND_LABELS = {
186564
186609
  llm_call_site: "LLM call sites",
@@ -186598,7 +186643,7 @@ function renderAnalystCandidateLines(report, concern) {
186598
186643
  const shown = bucket.slice(0, MAX_SEED_CANDIDATES_PER_KIND);
186599
186644
  lines.push(`${label} (${bucket.length}${bucket.length > shown.length ? `, showing ${shown.length}` : ""}):`);
186600
186645
  for (const candidate of shown) {
186601
- lines.push(`- ${candidate.path}:${candidate.startLine} [${candidate.signal}] ${candidate.excerpt}`);
186646
+ lines.push(candidate.endLine > candidate.startLine ? `- ${candidate.path}:${candidate.startLine}-${candidate.endLine} [${candidate.signal}] (multi-line span; read lines ${candidate.startLine}-${candidate.endLine} of the file)` : candidate.excerpt.length > MAX_INLINE_CANDIDATE_EXCERPT_CHARS ? `- ${candidate.path}:${candidate.startLine} [${candidate.signal}] (${candidate.excerpt.length}-character match; read line ${candidate.startLine} of the file)` : `- ${candidate.path}:${candidate.startLine} [${candidate.signal}] ${candidate.excerpt}`);
186602
186647
  }
186603
186648
  }
186604
186649
  const readPlan = report.files.slice(0, MAX_SEED_READ_PLAN_FILES);
@@ -986,6 +986,9 @@ function packSnapshot(inputs, opts) {
986
986
  }
987
987
  // src/output.ts
988
988
  import { randomUUID } from "node:crypto";
989
+ import { lstatSync, mkdirSync as mkdirSync2, readdirSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
990
+ import { tmpdir } from "node:os";
991
+ import { join as join2 } from "node:path";
989
992
  var AGENT_SCHEMA_VERSION = "moda.agent.v1";
990
993
  var AGENT_EVENT_SCHEMA_VERSION = "moda.agent_event.v1";
991
994
  var FEEDBACK_TIP = 'Help us improve! If a result in this response looks wrong or is missing data, flag it: moda feedback "<describe what looked wrong>" [--category=...] [--conversation-id=... | --cluster-id=... | --tool-name=...]. Only submit genuine observations.';
@@ -1005,7 +1008,8 @@ var AGENT_ERROR_CODES = [
1005
1008
  "command_error",
1006
1009
  "access_denied",
1007
1010
  "unknown_error",
1008
- "cloud_unavailable"
1011
+ "cloud_unavailable",
1012
+ "output_error"
1009
1013
  ];
1010
1014
  var FALLBACK_RUN_ID = randomUUID();
1011
1015
  var fallbackSeq = 0;
@@ -1116,6 +1120,7 @@ function createCliOutput(options) {
1116
1120
  seq: 0,
1117
1121
  started: false,
1118
1122
  terminal: false,
1123
+ outputError: false,
1119
1124
  startedAt: Date.now(),
1120
1125
  lastEventAt: Date.now()
1121
1126
  };
@@ -1177,6 +1182,8 @@ function createCliOutput(options) {
1177
1182
  if (state.terminal)
1178
1183
  return;
1179
1184
  const envelope = writeEnvelope(data, writeOptions);
1185
+ if (envelope.errors.some((error) => error.code === "output_error"))
1186
+ state.outputError = true;
1180
1187
  if (options.mode === "agent-stream") {
1181
1188
  ensureStarted();
1182
1189
  emitEvent({
@@ -1272,6 +1279,9 @@ function createCliOutput(options) {
1272
1279
  hasTerminalOutput() {
1273
1280
  return state.terminal;
1274
1281
  },
1282
+ hasOutputError() {
1283
+ return state.outputError;
1284
+ },
1275
1285
  close() {
1276
1286
  clearHeartbeat(state);
1277
1287
  }
@@ -1281,41 +1291,198 @@ function createCliOutput(options) {
1281
1291
  function createAgentEnvelope(options) {
1282
1292
  const safeData = redactDeep(options.data);
1283
1293
  const maxBytes = agentMaxOutputBytes();
1294
+ let fullOutput;
1295
+ let saveFailed = false;
1296
+ let envelopeTextSaveFailed = false;
1297
+ const writeFullOutput = options.persistFullOutput === false ? undefined : () => {
1298
+ fullOutput ??= { path: writeAgentFullOutput(options.command, safeData) };
1299
+ if (!fullOutput.path)
1300
+ saveFailed = true;
1301
+ return fullOutput.path;
1302
+ };
1303
+ let text = redactDeep({
1304
+ summary: normalizeSummary(options.summary, options.command, options.status, safeData),
1305
+ findings: options.findings ?? [],
1306
+ evidence_refs: options.evidenceRefs ?? [],
1307
+ recommended_actions: options.recommendedActions ?? [],
1308
+ next_commands: normalizeNextCommands(options.nextCommands ?? []),
1309
+ warnings: options.warnings ?? inferWarnings(safeData)
1310
+ });
1311
+ let moved;
1284
1312
  const build = (bounded) => ({
1285
1313
  schema_version: AGENT_SCHEMA_VERSION,
1286
1314
  command: options.command,
1287
1315
  status: options.status,
1288
1316
  generated_at: new Date().toISOString(),
1289
1317
  ...options.profile ? { profile: options.profile } : {},
1290
- summary: normalizeSummary(options.summary, options.command, options.status, safeData),
1291
- findings: options.findings ?? [],
1292
- evidence_refs: options.evidenceRefs ?? [],
1293
- recommended_actions: options.recommendedActions ?? [],
1294
- next_commands: normalizeNextCommands(options.nextCommands ?? []),
1295
- artifacts: options.artifacts ?? [],
1318
+ summary: text.summary,
1319
+ findings: text.findings,
1320
+ evidence_refs: text.evidence_refs,
1321
+ recommended_actions: text.recommended_actions,
1322
+ next_commands: text.next_commands,
1323
+ artifacts: [
1324
+ ...options.artifacts ?? [],
1325
+ ...bounded.fullOutputPath ? [{ kind: "full_output", path: bounded.fullOutputPath, description: "Complete command output (the same JSON --json prints); data in this envelope was bounded." }] : [],
1326
+ ...moved?.path ? [{ kind: "full_envelope_text", path: moved.path, description: "Whole envelope text (summary, findings, evidence_refs, actions, next_commands, warnings) before oversized fields were moved out of this envelope." }] : []
1327
+ ],
1296
1328
  warnings: [
1297
- ...options.warnings ?? inferWarnings(safeData),
1298
- ...bounded.warning ? [bounded.warning] : []
1329
+ ...text.warnings,
1330
+ ...bounded.warning ? [bounded.warning] : [],
1331
+ ...moved ? [movedTextWarning(moved)] : []
1299
1332
  ],
1300
1333
  errors: options.errors ?? [],
1301
1334
  data: bounded.data,
1302
1335
  ...options.meta ? { meta: options.meta } : {},
1303
- ...bounded.truncation ? { truncation: bounded.truncation } : {}
1336
+ ...bounded.truncation || moved ? { truncation: {
1337
+ ...bounded.truncation ?? {},
1338
+ ...moved ? { envelope_text: {
1339
+ reason: "agent_output_byte_limit",
1340
+ moved_fields: moved.fields,
1341
+ complete: false,
1342
+ ...moved.whole ? { whole_text_moved: true } : {},
1343
+ ...moved.path ? { full_envelope_path: moved.path } : {}
1344
+ } } : {}
1345
+ } } : {}
1304
1346
  });
1305
- let envelope = build(boundForAgent(safeData, maxBytes));
1306
- const envelopeBytes = Buffer.byteLength(JSON.stringify(envelope), "utf8");
1307
- if (envelopeBytes > maxBytes) {
1308
- const overhead = envelopeBytes - Buffer.byteLength(JSON.stringify(envelope.data) ?? "", "utf8");
1309
- envelope = build(boundForAgent(safeData, Math.max(1024, maxBytes - overhead - 1024)));
1347
+ const sizeOf = (value) => Buffer.byteLength(JSON.stringify(value) ?? "", "utf8");
1348
+ const rebound = (current) => {
1349
+ const overhead = sizeOf(current) - sizeOf(current.data);
1350
+ return build(boundForAgent(safeData, Math.max(1024, maxBytes - overhead - 1024), { writeFullOutput }));
1351
+ };
1352
+ let envelope = build(boundForAgent(safeData, maxBytes, { writeFullOutput }));
1353
+ if (sizeOf(envelope) > maxBytes)
1354
+ envelope = rebound(envelope);
1355
+ if (sizeOf(envelope) > maxBytes && !saveFailed) {
1356
+ const original = text;
1357
+ const savedPath = options.persistFullOutput === false ? undefined : writeAgentFullOutput(`${options.command}-envelope`, original);
1358
+ if (options.persistFullOutput !== false && !savedPath) {
1359
+ saveFailed = true;
1360
+ envelopeTextSaveFailed = true;
1361
+ } else {
1362
+ const slots = envelopeTextSlots(original).sort((left, right) => right.bytes - left.bytes);
1363
+ text = structuredClone(original);
1364
+ moved = { path: savedPath, fields: [] };
1365
+ for (const slot of slots) {
1366
+ if (sizeOf(envelope) <= maxBytes)
1367
+ break;
1368
+ const pointer = movedTextPointer(slot.field, slot.text, savedPath);
1369
+ const added = Buffer.byteLength(JSON.stringify(pointer), "utf8") + 2 * movedFieldNameBytes(slot.field);
1370
+ if (added >= slot.bytes)
1371
+ continue;
1372
+ slot.set(text, pointer);
1373
+ moved.fields.push(slot.field);
1374
+ envelope = rebound(envelope);
1375
+ }
1376
+ if (moved.fields.length === 0)
1377
+ moved = undefined;
1378
+ envelope = rebound(envelope);
1379
+ if (sizeOf(envelope) > maxBytes) {
1380
+ text = {
1381
+ summary: { text: movedWholeTextPointer(savedPath), confidence: original.summary.confidence },
1382
+ findings: [],
1383
+ evidence_refs: [],
1384
+ recommended_actions: [],
1385
+ next_commands: [],
1386
+ warnings: []
1387
+ };
1388
+ moved = { path: savedPath, fields: ["*"], whole: true };
1389
+ envelope = rebound(envelope);
1390
+ }
1391
+ }
1392
+ }
1393
+ if (saveFailed) {
1394
+ envelope = {
1395
+ ...envelope,
1396
+ status: "error",
1397
+ errors: [...envelope.errors, saveFailedError(maxBytes, envelopeTextSaveFailed, fullOutput?.path)]
1398
+ };
1310
1399
  }
1311
1400
  return redactDeep(envelope);
1312
1401
  }
1402
+ function saveFailedError(maxBytes, envelopeTextSaveFailed, dataPath) {
1403
+ const where = "(MODA_AGENT_OUTPUT_DIR is unwritable or the disk is full)";
1404
+ const remedy = "Point MODA_AGENT_OUTPUT_DIR at a writable directory, rerun with --json (no byte limit), or narrow the query.";
1405
+ if (!envelopeTextSaveFailed) {
1406
+ return {
1407
+ code: "output_error",
1408
+ cause: "full_output_save_failed",
1409
+ message: `Output is over the ${maxBytes}-byte agent limit and the complete output could not be saved to a file ${where}, ` + `so nothing was moved out: this envelope holds the whole result, over the limit. ${remedy}`,
1410
+ blocking: false
1411
+ };
1412
+ }
1413
+ const dataState = dataPath ? `data is NOT the whole result: it was bounded to fit, and the complete data was saved to ${dataPath} ` + "(truncation.data.full_output_path); read it in line ranges or with jq. " : "data is the whole result. ";
1414
+ return {
1415
+ code: "output_error",
1416
+ cause: "envelope_text_save_failed",
1417
+ message: `Output is over the ${maxBytes}-byte agent limit and the oversized envelope text (summary, findings, evidence_refs, ` + `recommended_actions, next_commands, warnings) could not be saved to a file ${where}, so that text was kept whole ` + `in this envelope, over the limit. ${dataState}${remedy}`,
1418
+ blocking: false
1419
+ };
1420
+ }
1421
+ function envelopeTextSlots(text) {
1422
+ const slots = [
1423
+ { field: "summary.text", text: text.summary.text, set: (target, value) => {
1424
+ target.summary.text = value;
1425
+ } }
1426
+ ];
1427
+ text.findings.forEach((finding, index) => {
1428
+ slots.push({ field: `findings[${index}].title`, text: finding.title, set: (target, value) => {
1429
+ target.findings[index].title = value;
1430
+ } });
1431
+ if (finding.description) {
1432
+ slots.push({ field: `findings[${index}].description`, text: finding.description, set: (target, value) => {
1433
+ target.findings[index].description = value;
1434
+ } });
1435
+ }
1436
+ });
1437
+ text.evidence_refs.forEach((ref, index) => {
1438
+ slots.push({ field: `evidence_refs[${index}].label`, text: ref.label, set: (target, value) => {
1439
+ target.evidence_refs[index].label = value;
1440
+ } });
1441
+ });
1442
+ text.recommended_actions.forEach((action, index) => {
1443
+ slots.push({ field: `recommended_actions[${index}].title`, text: action.title, set: (target, value) => {
1444
+ target.recommended_actions[index].title = value;
1445
+ } });
1446
+ if (action.rationale) {
1447
+ slots.push({ field: `recommended_actions[${index}].rationale`, text: action.rationale, set: (target, value) => {
1448
+ target.recommended_actions[index].rationale = value;
1449
+ } });
1450
+ }
1451
+ });
1452
+ text.next_commands.forEach((command, index) => {
1453
+ slots.push({ field: `next_commands[${index}].purpose`, text: command.purpose, set: (target, value) => {
1454
+ target.next_commands[index].purpose = value;
1455
+ } });
1456
+ });
1457
+ text.warnings.forEach((warning, index) => {
1458
+ slots.push({ field: `warnings[${index}]`, text: warning, set: (target, value) => {
1459
+ target.warnings[index] = value;
1460
+ } });
1461
+ });
1462
+ return slots.map((slot) => ({ ...slot, bytes: Buffer.byteLength(JSON.stringify(slot.text), "utf8") }));
1463
+ }
1464
+ function movedTextPointer(field, value, path) {
1465
+ const chars = Array.from(value).length;
1466
+ return path ? `[${chars} characters moved whole to ${path} at ${field}]` : `[${chars} characters over the agent byte limit; not saved. Rerun with --json or raise MODA_AGENT_MAX_OUTPUT_BYTES]`;
1467
+ }
1468
+ function movedFieldNameBytes(field) {
1469
+ return Buffer.byteLength(JSON.stringify(field), "utf8") + 2;
1470
+ }
1471
+ function movedWholeTextPointer(path) {
1472
+ return path ? `[Envelope text (summary, findings, evidence_refs, recommended_actions, next_commands, warnings) was over the agent byte limit and moved whole to ${path}]` : "[Envelope text was over the agent byte limit; not saved. Rerun with --json or raise MODA_AGENT_MAX_OUTPUT_BYTES]";
1473
+ }
1474
+ function movedTextWarning(moved) {
1475
+ if (moved.whole) {
1476
+ return "Envelope text outside data was over the agent byte limit even after moving its largest fields: all of it was moved whole " + (moved.path ? `to ${moved.path} (read it with jq)` : "(the file could not be saved; rerun with --json or raise MODA_AGENT_MAX_OUTPUT_BYTES)") + ".";
1477
+ }
1478
+ return `Envelope text outside data was over the agent byte limit: ${moved.fields.length} field(s) were moved whole ` + (moved.path ? `to ${moved.path} (same field paths; read it with jq)` : "(the file could not be saved; rerun with --json or raise MODA_AGENT_MAX_OUTPUT_BYTES)") + ` and replaced by pointers: ${moved.fields.join(", ")}.`;
1479
+ }
1313
1480
  var AGENT_ENVELOPE_RESERVE_BYTES = 2048;
1314
1481
  function agentEnvelopeFits(command, data, options = {}) {
1315
1482
  const maxBytes = agentMaxOutputBytes() - AGENT_ENVELOPE_RESERVE_BYTES;
1316
1483
  if (Buffer.byteLength(JSON.stringify(data) ?? "", "utf8") > maxBytes)
1317
1484
  return false;
1318
- const envelope = createAgentEnvelope({ command, status: "ok", data, ...options });
1485
+ const envelope = createAgentEnvelope({ command, status: "ok", data, ...options, persistFullOutput: false });
1319
1486
  return !envelope.truncation && Buffer.byteLength(JSON.stringify(envelope), "utf8") <= maxBytes;
1320
1487
  }
1321
1488
  function errorWarnings(error) {
@@ -1418,10 +1585,14 @@ function errorToAgentError(error) {
1418
1585
  }
1419
1586
  if (isApiLikeError(error)) {
1420
1587
  const statusCode = "statusCode" in error ? error.statusCode : undefined;
1588
+ const typedCode = "code" in error && typeof error.code === "string" ? error.code : undefined;
1589
+ const suggestions = "suggestions" in error && Array.isArray(error.suggestions) ? error.suggestions.filter((s) => typeof s === "string") : [];
1590
+ const http = typeof statusCode === "number" ? `HTTP ${statusCode}` : undefined;
1421
1591
  return {
1422
1592
  code: "api_error",
1423
1593
  message: error.message,
1424
- cause: typeof statusCode === "number" ? `HTTP ${statusCode}` : undefined,
1594
+ cause: typedCode ? http ? `${typedCode} (${http})` : typedCode : http,
1595
+ ...suggestions.length > 0 ? { suggested_commands: suggestions } : {},
1425
1596
  blocking: true
1426
1597
  };
1427
1598
  }
@@ -1555,41 +1726,61 @@ function inferWarnings(data) {
1555
1726
  }
1556
1727
  return [];
1557
1728
  }
1558
- var AGENT_MAX_STRING_CHARS = 2000;
1559
- var PROTECTED_STRING_KEY = /(cursor|token|_id$|^id$|url$|href$|^key$|hash$)/i;
1560
1729
  function agentMaxOutputBytes() {
1561
1730
  return Number(process.env.MODA_AGENT_MAX_OUTPUT_BYTES ?? 200000);
1562
1731
  }
1563
- function boundForAgent(data, maxBytes = agentMaxOutputBytes()) {
1732
+ var AGENT_FULL_OUTPUT_FILE = /-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z-[0-9a-f]{8}\.json$/;
1733
+ function agentOutputRetentionMs() {
1734
+ const hours = Number(process.env.MODA_AGENT_OUTPUT_RETENTION_HOURS ?? 24);
1735
+ return (Number.isFinite(hours) && hours > 0 ? hours : 24) * 3600000;
1736
+ }
1737
+ function pruneAgentFullOutputs(dir, keep, now = Date.now()) {
1738
+ const cutoff = now - agentOutputRetentionMs();
1739
+ let names;
1740
+ try {
1741
+ names = readdirSync(dir);
1742
+ } catch {
1743
+ return;
1744
+ }
1745
+ for (const name of names) {
1746
+ if (!AGENT_FULL_OUTPUT_FILE.test(name))
1747
+ continue;
1748
+ const path = join2(dir, name);
1749
+ if (path === keep)
1750
+ continue;
1751
+ try {
1752
+ const stat = lstatSync(path);
1753
+ if (stat.isFile() && stat.mtimeMs < cutoff)
1754
+ unlinkSync(path);
1755
+ } catch {}
1756
+ }
1757
+ }
1758
+ function writeAgentFullOutput(command, data) {
1759
+ try {
1760
+ const dir = process.env.MODA_AGENT_OUTPUT_DIR || join2(tmpdir(), "moda-agent-output");
1761
+ mkdirSync2(dir, { recursive: true, mode: 448 });
1762
+ const slug = command.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "output";
1763
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
1764
+ const path = join2(dir, `${slug}-${stamp}-${randomUUID().slice(0, 8)}.json`);
1765
+ writeFileSync2(path, `${JSON.stringify(data, null, 2)}
1766
+ `, { encoding: "utf8", mode: 384 });
1767
+ pruneAgentFullOutputs(dir, path);
1768
+ return path;
1769
+ } catch {
1770
+ return;
1771
+ }
1772
+ }
1773
+ function boundForAgent(data, maxBytes = agentMaxOutputBytes(), options = {}) {
1564
1774
  const sizeOf = (value) => Buffer.byteLength(JSON.stringify(value) ?? "", "utf8");
1565
1775
  const serialized = JSON.stringify(data) ?? "";
1566
1776
  const originalBytes = Buffer.byteLength(serialized, "utf8");
1567
1777
  if (originalBytes <= maxBytes)
1568
1778
  return { data };
1569
- let shortenedStrings = 0;
1570
- const shorten = (value, limit, key) => {
1571
- if (typeof value === "string") {
1572
- if (value.length <= limit || key !== undefined && PROTECTED_STRING_KEY.test(key))
1573
- return value;
1574
- shortenedStrings += 1;
1575
- return `${value.slice(0, limit)}… [truncated ${value.length - limit} chars]`;
1576
- }
1577
- if (Array.isArray(value))
1578
- return value.map((item) => shorten(item, limit));
1579
- if (value && typeof value === "object") {
1580
- return Object.fromEntries(Object.entries(value).map(([entryKey, item]) => [entryKey, shorten(item, limit, entryKey)]));
1581
- }
1582
- return value;
1583
- };
1584
- let bounded = data;
1779
+ const fullOutputPath = options.writeFullOutput?.();
1780
+ if (options.writeFullOutput && !fullOutputPath)
1781
+ return { data, saveFailed: true };
1782
+ const bounded = JSON.parse(serialized);
1585
1783
  let boundedBytes = originalBytes;
1586
- for (const limit of [AGENT_MAX_STRING_CHARS, 500, 200]) {
1587
- shortenedStrings = 0;
1588
- bounded = shorten(data, limit);
1589
- boundedBytes = sizeOf(bounded);
1590
- if (boundedBytes <= maxBytes)
1591
- break;
1592
- }
1593
1784
  const droppedItems = {};
1594
1785
  const collectArrays = (value, path, out) => {
1595
1786
  if (Array.isArray(value)) {
@@ -1626,39 +1817,35 @@ function boundForAgent(data, maxBytes = agentMaxOutputBytes()) {
1626
1817
  boundedBytes = sizeOf(bounded);
1627
1818
  }
1628
1819
  const dropped = Object.entries(droppedItems);
1629
- const parts = [
1630
- shortenedStrings > 0 ? `shortened ${shortenedStrings} long text field(s)` : "",
1631
- ...dropped.map(([path, count]) => `dropped the last ${count} item(s) of data.${path}`)
1632
- ].filter(Boolean);
1633
- const howToGetMore = "For complete results narrow the query (a smaller --limit, with --offset or --cursor paging where the command supports it), " + "rerun with --json (no byte limit), or raise MODA_AGENT_MAX_OUTPUT_BYTES.";
1820
+ const whereIsTheRest = fullOutputPath ? `The complete output (every item, no text shortened) is in ${fullOutputPath}; read it in line ranges or query it with jq. ` : "The complete output could not be saved to a file. ";
1821
+ const howToGetMore = whereIsTheRest + "Alternatively narrow the query (a smaller --limit, with --offset or --cursor paging where the command supports it), " + "rerun with --json (no byte limit), or raise MODA_AGENT_MAX_OUTPUT_BYTES.";
1822
+ const pathField = fullOutputPath ? { full_output_path: fullOutputPath } : {};
1634
1823
  if (boundedBytes > maxBytes) {
1635
- const stub = { truncated: true, original_bytes: originalBytes, max_bytes: maxBytes, preview: serialized.slice(0, Math.max(0, maxBytes - 256)) };
1636
- for (let stubBytes = sizeOf(stub);stubBytes > maxBytes && stub.preview.length > 0; stubBytes = sizeOf(stub)) {
1637
- stub.preview = stub.preview.slice(0, Math.max(0, stub.preview.length - (stubBytes - maxBytes) - 16));
1638
- }
1639
1824
  return {
1640
- data: stub,
1641
- warning: `Output was ${originalBytes} bytes, over the ${maxBytes}-byte agent limit, and could not be reduced while keeping its structure; data is a string preview. ${howToGetMore}`,
1825
+ data: { truncated: true, original_bytes: originalBytes, max_bytes: maxBytes, ...pathField },
1826
+ warning: `Output was ${originalBytes} bytes, over the ${maxBytes}-byte agent limit, and could not be reduced while keeping its structure; data is a pointer stub. ${howToGetMore}`,
1642
1827
  truncation: {
1643
- data: { original_bytes: originalBytes, max_bytes: maxBytes, reason: "agent_output_byte_limit", structure_preserved: false, complete: false }
1644
- }
1828
+ data: { original_bytes: originalBytes, max_bytes: maxBytes, reason: "agent_output_byte_limit", structure_preserved: false, complete: false, ...pathField }
1829
+ },
1830
+ fullOutputPath
1645
1831
  };
1646
1832
  }
1647
1833
  return {
1648
1834
  data: bounded,
1649
- warning: `Output was ${originalBytes} bytes, over the ${maxBytes}-byte agent limit: ${parts.join("; ")}. ${howToGetMore}`,
1835
+ warning: `Output was ${originalBytes} bytes, over the ${maxBytes}-byte agent limit: ${dropped.map(([path, count]) => `dropped the last ${count} item(s) of data.${path}`).join("; ")}. ${howToGetMore}`,
1650
1836
  truncation: {
1651
1837
  data: {
1652
1838
  original_bytes: originalBytes,
1653
1839
  bounded_bytes: boundedBytes,
1654
1840
  max_bytes: maxBytes,
1655
1841
  reason: "agent_output_byte_limit",
1656
- shortened_strings: shortenedStrings,
1657
1842
  dropped_items: droppedItems,
1658
1843
  structure_preserved: true,
1659
- complete: false
1844
+ complete: false,
1845
+ ...pathField
1660
1846
  }
1661
- }
1847
+ },
1848
+ fullOutputPath
1662
1849
  };
1663
1850
  }
1664
1851
  function asRecord(value) {
@@ -1717,4 +1904,4 @@ function createCommandContext(input, options = {}) {
1717
1904
  };
1718
1905
  }
1719
1906
 
1720
- export { PROD_BASE_URL, DEFAULT_INGEST_URL, getModaPaths, getModaDir, isLoopbackBaseUrl, assertValidProfileName, describeBaseUrlSource, loadProfileConfig, saveProfileConfig, getActiveProfile, setActiveProfile, upsertProfile, clearStoredApiKeyCredentials, secretFilePathFromRef, resolveSecretReference, resolveApiKeyFromActiveProfile, resolveTenantIdFromActiveProfile, ensureSecretFilePermissions, writeSecretJsonFile, CliInputError, CliAccessError, CliInputRequiredError, CliAuthError, CliCancellationError, isNetworkLikeError, isApiLikeError, VERSION, MODA_BASE_URL, loadCliConfig, saveCliConfig, resolveApiKey, embeddedTenantIdFromApiKey, resolveIngestUrl2 as resolveIngestUrl, resolveTenantId, resolveApiTenantId, resolveModaBaseUrl, validateConfig, MAX_SCANNED_FILE_BYTES, DEFAULT_MAX_INVENTORY_FILES, DEFAULT_MAX_INVENTORY_TEXT_BYTES, MAX_RECORDED_SKIPS, SKIPPED_DIRS, isSkippedDir, isSkippedPathPrefix, isEvalLikePath, shouldInventoryFile, isTextFile, isSecretishPath, SECRET_KEYS, redactDeep, redactString, packSnapshot, EXIT_OK, EXIT_ERROR, EXIT_DEGRADED, EXIT_AUTH_REQUIRED, EXIT_INPUT_REQUIRED, EXIT_CANCELLED, AGENT_ERROR_CODES, wasOutputCancelled, safeWriteStdout, safeWriteStderr, explicitOutputMode, resolveOutputMode, createCliOutput, agentEnvelopeFits, stringOption, createCommandContext };
1907
+ export { PROD_BASE_URL, DEFAULT_INGEST_URL, getModaPaths, getModaDir, isLoopbackBaseUrl, assertValidProfileName, describeBaseUrlSource, loadProfileConfig, saveProfileConfig, getActiveProfile, setActiveProfile, upsertProfile, clearStoredApiKeyCredentials, secretFilePathFromRef, resolveSecretReference, resolveApiKeyFromActiveProfile, resolveTenantIdFromActiveProfile, ensureSecretFilePermissions, writeSecretJsonFile, CliInputError, CliAccessError, CliInputRequiredError, CliAuthError, CliCancellationError, isNetworkLikeError, isApiLikeError, VERSION, MODA_BASE_URL, loadCliConfig, saveCliConfig, resolveApiKey, embeddedTenantIdFromApiKey, resolveIngestUrl2 as resolveIngestUrl, resolveTenantId, resolveApiTenantId, resolveModaBaseUrl, validateConfig, MAX_SCANNED_FILE_BYTES, DEFAULT_MAX_INVENTORY_FILES, DEFAULT_MAX_INVENTORY_TEXT_BYTES, MAX_RECORDED_SKIPS, SKIPPED_DIRS, isSkippedDir, isSkippedPathPrefix, isEvalLikePath, shouldInventoryFile, isTextFile, isSecretishPath, SECRET_KEYS, redactDeep, redactString, packSnapshot, EXIT_OK, EXIT_ERROR, EXIT_DEGRADED, EXIT_AUTH_REQUIRED, EXIT_INPUT_REQUIRED, EXIT_CANCELLED, AGENT_ERROR_CODES, wasOutputCancelled, safeWriteStdout, safeWriteStderr, explicitOutputMode, resolveOutputMode, createCliOutput, agentEnvelopeFits, agentMaxOutputBytes, stringOption, createCommandContext };
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  runPromptsCommand,
9
9
  runSkillsCommand,
10
10
  runStatusCommand
11
- } from "./cli-eaaegm7a.js";
11
+ } from "./cli-jqfm8dp4.js";
12
12
  import {
13
13
  ApiError,
14
14
  HARNESS_REPORT_APPROVAL_PATH,
@@ -37,7 +37,7 @@ import {
37
37
  terminalStyles,
38
38
  unknownSignalsSubcommandError,
39
39
  validateHarnessReport
40
- } from "./cli-tznhpmf2.js";
40
+ } from "./cli-x0x0n35w.js";
41
41
  import"./cli-ssfq84k5.js";
42
42
  import {
43
43
  authFetch,
@@ -46,7 +46,7 @@ import {
46
46
  loadAuthSession,
47
47
  login,
48
48
  revokeCliSessionToken
49
- } from "./cli-fpt3jkyh.js";
49
+ } from "./cli-7kbzqact.js";
50
50
  import {
51
51
  AGENT_ERROR_CODES,
52
52
  CliAccessError,
@@ -65,6 +65,7 @@ import {
65
65
  PROD_BASE_URL,
66
66
  VERSION,
67
67
  agentEnvelopeFits,
68
+ agentMaxOutputBytes,
68
69
  assertValidProfileName,
69
70
  clearStoredApiKeyCredentials,
70
71
  createCliOutput,
@@ -96,7 +97,7 @@ import {
96
97
  validateConfig,
97
98
  wasOutputCancelled,
98
99
  writeSecretJsonFile
99
- } from "./cli-233xm35b.js";
100
+ } from "./cli-xrs02c0t.js";
100
101
  import {
101
102
  __require
102
103
  } from "./cli-0v6na3yp.js";
@@ -389,7 +390,9 @@ var AuditSchema = z.object({
389
390
  id: z.string().min(1),
390
391
  kind: z.enum(["auto", "trace", "conversation"]).default("auto").optional(),
391
392
  include_raw: boolFlag(),
392
- limit: z.number().min(1).max(1000).default(200).optional()
393
+ include_bodies: boolFlag(),
394
+ limit: z.number().min(1).max(1000).default(200).optional(),
395
+ cursor: z.string().min(1).max(512).optional()
393
396
  });
394
397
  var TIME_RANGES = ["all", "1h", "3d", "7d", "24h", "30d", "90d"];
395
398
  var SEARCH_MODES = ["keyword", "semantic", "hybrid"];
@@ -544,8 +547,16 @@ function buildFrustrationAnchor(row) {
544
547
  if (firstQuote) {
545
548
  signal = asString(firstQuote.signal);
546
549
  const quote = asString(firstQuote.quote);
547
- if (quote !== null)
548
- quote_preview = quote.slice(0, 200);
550
+ if (quote !== null) {
551
+ let end = 0;
552
+ let points = 0;
553
+ for (const ch of quote) {
554
+ if (points++ === 200)
555
+ break;
556
+ end += ch.length;
557
+ }
558
+ quote_preview = quote.slice(0, end);
559
+ }
549
560
  }
550
561
  return {
551
562
  kind: "frustration",
@@ -621,7 +632,6 @@ function withFrustrationAnchors(response) {
621
632
  });
622
633
  return { ...response, frustrations };
623
634
  }
624
- var TOOL_FAILURE_TEXT_LIMIT = 300;
625
635
  var TOOL_FAILURE_CONTEXT_RADIUS = 2;
626
636
  function shellWord(value) {
627
637
  return /^[\w@%+=:,./-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
@@ -671,26 +681,6 @@ function trimToolFailureConversations(response, opts) {
671
681
  });
672
682
  return { ...response, examples };
673
683
  }
674
- function compactToolFailureExamples(response, maxChars = TOOL_FAILURE_TEXT_LIMIT) {
675
- if (!Array.isArray(response.examples))
676
- return { data: response, truncated: 0 };
677
- let truncated = 0;
678
- const cap = (value) => {
679
- if (typeof value === "string") {
680
- if (value.length <= maxChars)
681
- return value;
682
- truncated += 1;
683
- return `${value.slice(0, maxChars)}… [truncated ${value.length - maxChars} chars]`;
684
- }
685
- if (Array.isArray(value))
686
- return value.map(cap);
687
- if (value && typeof value === "object") {
688
- return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, cap(child)]));
689
- }
690
- return value;
691
- };
692
- return { data: { ...response, examples: response.examples.map(cap) }, truncated };
693
- }
694
684
  function withToolFailureAnchors(response) {
695
685
  if (!Array.isArray(response.examples))
696
686
  return response;
@@ -5382,7 +5372,6 @@ var MODA_DIR = ".moda";
5382
5372
  var HARNESS_PATH = join4(MODA_DIR, "harness.json");
5383
5373
  var HARNESS_REPORT_MARKDOWN_PATH = join4(MODA_DIR, "HARNESS_REPORT.md");
5384
5374
  var HARNESS_SUMMARY_PATH = join4(MODA_DIR, "HARNESS.md");
5385
- var MAX_AGENT_EVIDENCE_BYTES = 24000;
5386
5375
  async function runAgentCommand(context) {
5387
5376
  const subcommand2 = context.positionals[0] ?? "context";
5388
5377
  switch (subcommand2) {
@@ -5735,7 +5724,7 @@ function expandEvidenceRef(ref, loaded, cwd) {
5735
5724
  label: artifact.description ?? artifact.path,
5736
5725
  path: artifact.path
5737
5726
  },
5738
- value: readBoundedModaArtifact(cwd, artifact.path),
5727
+ value: readModaArtifact(cwd, artifact.path),
5739
5728
  artifact
5740
5729
  };
5741
5730
  }
@@ -5766,11 +5755,11 @@ function readCitationExcerpt(cwd, citation) {
5766
5755
  return citation.excerpt;
5767
5756
  const text = readFileSync5(absolute, "utf8");
5768
5757
  if (citation.line && citation.line > 0) {
5769
- return text.split(/\r?\n/)[citation.line - 1]?.slice(0, 1000) ?? citation.excerpt;
5758
+ return text.split(/\r?\n/)[citation.line - 1] ?? citation.excerpt;
5770
5759
  }
5771
- return text.slice(0, 1000);
5760
+ return text;
5772
5761
  }
5773
- function readBoundedModaArtifact(cwd, artifactPath) {
5762
+ function readModaArtifact(cwd, artifactPath) {
5774
5763
  if (!artifactPath.startsWith(".moda/")) {
5775
5764
  throw new CliInputError(`Artifact '${artifactPath}' is outside .moda and cannot be expanded directly.`, "Use a citation evidence ref for source-file evidence.");
5776
5765
  }
@@ -5778,12 +5767,10 @@ function readBoundedModaArtifact(cwd, artifactPath) {
5778
5767
  if (!absolute || !existsSync4(absolute))
5779
5768
  throw missingEvidence(`artifact:${artifactPath}`);
5780
5769
  const size = statSync2(absolute).size;
5781
- const text = readFileSync5(absolute, "utf8");
5782
5770
  return {
5783
5771
  path: artifactPath,
5784
5772
  bytes: size,
5785
- truncated: size > MAX_AGENT_EVIDENCE_BYTES,
5786
- text: text.slice(0, MAX_AGENT_EVIDENCE_BYTES)
5773
+ text: readFileSync5(absolute, "utf8")
5787
5774
  };
5788
5775
  }
5789
5776
  function safeResolve(cwd, path) {
@@ -7279,7 +7266,7 @@ function toolRows(data, scopedTool) {
7279
7266
  function firstQuote(row) {
7280
7267
  const quotes = Array.isArray(row?.user_quotes) ? row.user_quotes : [];
7281
7268
  const first = asRecord(quotes[0]);
7282
- return asString(first?.quote)?.slice(0, 160) ?? undefined;
7269
+ return asString(first?.quote) ?? undefined;
7283
7270
  }
7284
7271
  function likelyToolLocation(toolName, hints) {
7285
7272
  const normalized = normalizeKey(toolName);
@@ -7592,7 +7579,10 @@ async function finishRun(context, outcome) {
7592
7579
  context.output.close();
7593
7580
  if (wasOutputCancelled())
7594
7581
  return EXIT_CANCELLED;
7595
- return outcome.exitCode ?? EXIT_OK;
7582
+ const exitCode = outcome.exitCode ?? EXIT_OK;
7583
+ if (context.output.hasOutputError() && (exitCode === EXIT_OK || exitCode === EXIT_DEGRADED))
7584
+ return EXIT_ERROR;
7585
+ return exitCode;
7596
7586
  }
7597
7587
  if (outcome.kind === "cancelled") {
7598
7588
  context.output.writeCancelled(outcome.reason ?? "Command cancelled.");
@@ -7656,6 +7646,8 @@ function buildManifest(commands) {
7656
7646
  "MODA_AUTH_TIMEOUT_MS",
7657
7647
  "MODA_HEARTBEAT_MS",
7658
7648
  "MODA_AGENT_MAX_OUTPUT_BYTES",
7649
+ "MODA_AGENT_OUTPUT_DIR",
7650
+ "MODA_AGENT_OUTPUT_RETENTION_HOURS",
7659
7651
  "MODA_CLI_TELEMETRY",
7660
7652
  "MODA_CLI_UPDATE_CHECK",
7661
7653
  "MODA_CLI_TIPS"
@@ -7919,8 +7911,7 @@ Commands:
7919
7911
  hallucinations Grounding detections: contradicted/verified outputs with rule breakdown
7920
7912
  laziness Laziness detections per trace (repeated calls, premature handoffs, ...)
7921
7913
  tool-failures Get tool failure overview
7922
- tool-failure-detail <tool_name> Get per-tool failure detail (--limit=N examples, 1-20, default 5;
7923
- long example text is cut to 300 chars)
7914
+ tool-failure-detail <tool_name> Get per-tool failure detail (--limit=N examples, 1-20, default 5)
7924
7915
  problems Rank cross-signal Problems by root cause (what to fix first)
7925
7916
  problems remainder Signals no Problem explains yet (--family, --cursor)
7926
7917
  problem-impact Live problem/use-case impact (--node-id, --problem-id, --mode, --days-back)
@@ -8367,7 +8358,62 @@ function auditCapStatus(data, limit) {
8367
8358
  const rowsCapped = hasMore || !serverSaid && rows >= limit;
8368
8359
  if (!rowsCapped && !spansCapped)
8369
8360
  return null;
8370
- return { rowsCapped, spansCapped, hasMoreKnown: hasMore, rows, spans };
8361
+ const reason = typeof summary.truncated_reason === "string" ? summary.truncated_reason : null;
8362
+ const cursorValue = summary.next_cursor ?? record.next_cursor;
8363
+ const nextCursor = typeof cursorValue === "string" && cursorValue ? cursorValue : null;
8364
+ return { rowsCapped, spansCapped, hasMoreKnown: hasMore, rows, spans, reason, nextCursor };
8365
+ }
8366
+ var AUDIT_MIN_PAGE_BYTES = 32 * 1024;
8367
+ var AUDIT_AGENT_RESERVE_BYTES = 16 * 1024;
8368
+ async function fetchAudit(id, query2, maxBytes) {
8369
+ const pageQuery = new URLSearchParams(query2);
8370
+ if (maxBytes !== undefined)
8371
+ pageQuery.set("max_bytes", maxBytes.toString());
8372
+ const queryString = pageQuery.toString() ? `?${pageQuery.toString()}` : "";
8373
+ return callDataAPI(`/audit/${encodeURIComponent(id)}${queryString}`);
8374
+ }
8375
+ function auditOutput(command, params, data) {
8376
+ const capped = auditCapStatus(data, params.limit ?? AUDIT_DEFAULT_LIMIT);
8377
+ const auditRecord = asRecord(data);
8378
+ if (!capped || !auditRecord) {
8379
+ return { data: auditRecord ? { ...auditRecord, truncated: false, has_more: false, next_cursor: null } : data, options: {} };
8380
+ }
8381
+ const limit = params.limit ?? AUDIT_DEFAULT_LIMIT;
8382
+ const perTrace = "audit one OTLP trace at a time with `moda audit <trace_id> --kind=trace` (ids in summary.trace_ids)";
8383
+ const warnings = [];
8384
+ const nextCommands = [];
8385
+ const auditFlags = (extra) => [
8386
+ `moda ${command} ${shellArg(params.id)}`,
8387
+ params.kind && params.kind !== "auto" ? `--kind=${params.kind}` : "",
8388
+ params.include_raw === true ? "--include-raw" : "",
8389
+ params.include_bodies === true ? "--include-bodies" : "",
8390
+ ...extra
8391
+ ].filter(Boolean).join(" ");
8392
+ if (capped.nextCursor) {
8393
+ nextCommands.push({
8394
+ command: auditFlags([params.limit ? `--limit=${params.limit}` : "", `--cursor=${capped.nextCursor}`]),
8395
+ purpose: "Read the next page of ingest rows of this trace.",
8396
+ mutability: "read",
8397
+ requires_approval: false
8398
+ });
8399
+ }
8400
+ if (capped.reason === "byte_limit") {
8401
+ warnings.push(`Spans were capped: the response reached the server's size budget after ${capped.rows} ingest row(s), so this trace has more spans than the ${capped.spans} shown. ` + `Continue with the next_cursor page${params.include_bodies === true ? ", or drop --include-bodies to fit more rows per page" : ""}. Orphan/duplicate analysis covers this page only.`);
8402
+ } else if (capped.reason === "span_limit" && capped.nextCursor) {
8403
+ warnings.push(`Spans were capped: the page reached the server's per-page span cap after ${capped.rows} ingest row(s), so this trace has more spans than the ${capped.spans} shown. ` + "Continue with the next_cursor page. Orphan/duplicate analysis covers this page only.");
8404
+ } else if (capped.rowsCapped) {
8405
+ const bigger = Math.min(AUDIT_MAX_LIMIT, limit * 5);
8406
+ warnings.push(`Spans were capped: read ${capped.rows} ingest row(s), the --limit=${limit} maximum, so this trace ${capped.hasMoreKnown ? "has" : "may have"} more spans than the ${capped.spans} shown. ` + (capped.nextCursor ? "Page on with the next_cursor command, or " : "") + (limit < AUDIT_MAX_LIMIT ? `${capped.nextCursor ? "rerun" : "Rerun"} with --limit=${bigger} (max ${AUDIT_MAX_LIMIT}), or ${perTrace}.` : capped.nextCursor ? `${perTrace}.` : `${perTrace[0].toUpperCase()}${perTrace.slice(1)}.`));
8407
+ if (limit < AUDIT_MAX_LIMIT && !params.cursor) {
8408
+ nextCommands.push({ command: auditFlags([`--limit=${bigger}`]), purpose: "Read more ingest rows of this trace.", mutability: "read", requires_approval: false });
8409
+ }
8410
+ } else {
8411
+ warnings.push(`Spans were capped: the server returned ${capped.spans} span(s) and marked the response truncated. ${perTrace[0].toUpperCase()}${perTrace.slice(1)}.`);
8412
+ }
8413
+ return {
8414
+ data: { ...auditRecord, truncated: true, has_more: capped.rowsCapped, next_cursor: capped.nextCursor },
8415
+ options: { warnings, ...nextCommands.length > 0 ? { nextCommands } : {} }
8416
+ };
8371
8417
  }
8372
8418
  var TAIL_SEEN_MAX = 5000;
8373
8419
  var EMOTIONS_TAIL_PAGE_SIZE = 20;
@@ -9055,7 +9101,7 @@ async function readFullTranscript(conversationId, opts = {}) {
9055
9101
  out.push(`Message order may not be chronological: ${tied} of ${total} messages share a timestamp with another message ` + `(ordered by id, not time)${synthetic > 0 ? ` and ${synthetic} carry a synthetic pre-2022 timestamp` : ""}.`);
9056
9102
  }
9057
9103
  if (returned.some(isTruncated)) {
9058
- out.push(`Message text, tool-call inputs and tool results longer than ${CONTEXT_MESSAGE_TRUNCATE_CHARS} characters are truncated by the context endpoint. ` + "For full raw content (prompts, tool arguments/results) use `moda audit <conversation_id> --include-raw`.");
9104
+ out.push(`Message text, tool-call inputs and tool results longer than ${CONTEXT_MESSAGE_TRUNCATE_CHARS} characters are truncated by the context endpoint. ` + "For full content (prompts, tool arguments/results) use `moda audit <conversation_id> --include-bodies` (or `--include-raw` for the verbatim events).");
9059
9105
  }
9060
9106
  return {
9061
9107
  data: stripLoneSurrogates({
@@ -9366,7 +9412,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
9366
9412
  case "audit":
9367
9413
  case "trace": {
9368
9414
  if (!positional) {
9369
- throw new CliInputError("<conversation_id|trace_id> is required", "Usage: moda audit <conversation_id|trace_id> [--kind=auto|trace|conversation] [--include-raw] [--limit=N]");
9415
+ throw new CliInputError("<conversation_id|trace_id> is required", "Usage: moda audit <conversation_id|trace_id> [--kind=auto|trace|conversation] [--include-raw] [--include-bodies] [--limit=N] [--cursor=<next_cursor>]");
9370
9416
  }
9371
9417
  const args = flagsToArgs(flags, "id", positional);
9372
9418
  const params = parseCommandFlags(AuditSchema, args, command, { positionalKey: "id" });
@@ -9375,36 +9421,26 @@ async function runCommand(command, positional, flags, positionals = positional ?
9375
9421
  query2.set("kind", params.kind);
9376
9422
  if (params.include_raw === true)
9377
9423
  query2.set("include_raw", "1");
9424
+ if (params.include_bodies === true)
9425
+ query2.set("include_bodies", "1");
9378
9426
  if (params.limit)
9379
9427
  query2.set("limit", params.limit.toString());
9380
- const queryString = query2.toString() ? `?${query2.toString()}` : "";
9381
- const data = await callDataAPI(`/audit/${encodeURIComponent(params.id)}${queryString}`);
9382
- const capped = auditCapStatus(data, params.limit ?? AUDIT_DEFAULT_LIMIT);
9383
- const auditRecord = asRecord(data);
9384
- if (!capped || !auditRecord) {
9385
- context.output.writeData(auditRecord ? { ...auditRecord, truncated: false, has_more: false } : data);
9386
- break;
9387
- }
9388
- const limit = params.limit ?? AUDIT_DEFAULT_LIMIT;
9389
- const perTrace = "audit one OTLP trace at a time with `moda audit <trace_id> --kind=trace` (ids in summary.trace_ids)";
9390
- const warnings = [];
9391
- const nextCommands = [];
9392
- if (capped.rowsCapped) {
9393
- const bigger = Math.min(AUDIT_MAX_LIMIT, limit * 5);
9394
- warnings.push(`Spans were capped: read ${capped.rows} ingest row(s), the --limit=${limit} maximum, so this trace ${capped.hasMoreKnown ? "has" : "may have"} more spans than the ${capped.spans} shown. ` + (limit < AUDIT_MAX_LIMIT ? `Rerun with --limit=${bigger} (max ${AUDIT_MAX_LIMIT}), or ${perTrace}.` : `${perTrace[0].toUpperCase()}${perTrace.slice(1)}.`));
9395
- if (limit < AUDIT_MAX_LIMIT) {
9396
- const rerun = [
9397
- `moda ${command} ${shellArg(params.id)}`,
9398
- params.kind && params.kind !== "auto" ? `--kind=${params.kind}` : "",
9399
- params.include_raw === true ? "--include-raw" : "",
9400
- `--limit=${bigger}`
9401
- ].filter(Boolean).join(" ");
9402
- nextCommands.push({ command: rerun, purpose: "Read more ingest rows of this trace.", mutability: "read", requires_approval: false });
9403
- }
9404
- } else {
9405
- warnings.push(`Spans were capped: the server returned ${capped.spans} span(s) and marked the response truncated. ${perTrace[0].toUpperCase()}${perTrace.slice(1)}.`);
9428
+ if (params.cursor)
9429
+ query2.set("cursor", params.cursor);
9430
+ const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
9431
+ let maxBytes = agentMode ? Math.max(AUDIT_MIN_PAGE_BYTES, agentMaxOutputBytes() - AUDIT_AGENT_RESERVE_BYTES) : undefined;
9432
+ let page = auditOutput(command, params, await fetchAudit(params.id, query2, maxBytes));
9433
+ while (maxBytes !== undefined && maxBytes > AUDIT_MIN_PAGE_BYTES && !agentEnvelopeFits(command, page.data, page.options)) {
9434
+ maxBytes = Math.max(AUDIT_MIN_PAGE_BYTES, Math.floor(maxBytes / 2));
9435
+ page = auditOutput(command, params, await fetchAudit(params.id, query2, maxBytes));
9436
+ }
9437
+ if (maxBytes !== undefined && !agentEnvelopeFits(command, page.data, page.options)) {
9438
+ page.options.warnings = [
9439
+ ...page.options.warnings ?? [],
9440
+ "This page is a single ingest row larger than the agent output budget, so some of its spans are cut from this output. " + "Rerun this command with --json (no byte limit) or a higher MODA_AGENT_MAX_OUTPUT_BYTES to see the whole row before following next_cursor."
9441
+ ];
9406
9442
  }
9407
- context.output.writeData({ ...auditRecord, truncated: true, has_more: capped.rowsCapped }, { warnings, ...nextCommands.length > 0 ? { nextCommands } : {} });
9443
+ context.output.writeData(page.data, page.options);
9408
9444
  break;
9409
9445
  }
9410
9446
  case "frustrations": {
@@ -9483,11 +9519,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
9483
9519
  trimmed = { ...trimmed, subtypes: ranked.slice(0, TOOL_FAILURE_AGENT_SUBTYPES), subtypes_total: subtypes.length };
9484
9520
  out.push(`Showing the ${TOOL_FAILURE_AGENT_SUBTYPES} most frequent of ${subtypes.length} failure subtypes; --json lists all of them.`);
9485
9521
  }
9486
- const { data: compacted, truncated } = compactToolFailureExamples(trimmed);
9487
- if (truncated > 0) {
9488
- out.push(`${truncated} long text field(s) in examples were cut to ${TOOL_FAILURE_TEXT_LIMIT} characters to keep this output small. For the full text, run each example's \`context_command\` (\`moda context <conversation_id> --msg-index=<msg_index>\`).`);
9489
- }
9490
- return compacted;
9522
+ return trimmed;
9491
9523
  });
9492
9524
  break;
9493
9525
  }
@@ -10115,7 +10147,7 @@ var commandRegistry = createCommandRegistry([
10115
10147
  resetApiRequestCountBeforeRun: true,
10116
10148
  telemetry: "result",
10117
10149
  handler: async (context) => {
10118
- const { runInit } = await import("./index-p8vab4p4.js");
10150
+ const { runInit } = await import("./index-z0jpjcsm.js");
10119
10151
  if (context.outputMode === "agent-stream") {
10120
10152
  context.output.writeEvent({
10121
10153
  event: "started",
@@ -10156,7 +10188,7 @@ var commandRegistry = createCommandRegistry([
10156
10188
  resetApiRequestCountBeforeRun: true,
10157
10189
  telemetry: "result-and-error",
10158
10190
  handler: async (context) => {
10159
- const { runProvision } = await import("./provision-e4cnxpdh.js");
10191
+ const { runProvision } = await import("./provision-qkazpt0c.js");
10160
10192
  return runProvision(context, {
10161
10193
  resumeCommand: buildResumeCommand("provision", context.rawFlags)
10162
10194
  });
@@ -10633,7 +10665,9 @@ var commandRegistry = createCommandRegistry([
10633
10665
  "moda audit <conversation_id|trace_id>",
10634
10666
  "moda audit <trace_id> --kind=trace --json",
10635
10667
  "moda audit <conversation_id> --include-raw",
10636
- "moda audit <conversation_id> --limit=1000 # ingest rows to read (default 200, max 1000); warns when capped"
10668
+ "moda audit <conversation_id> --include-bodies # keep prompt/response bodies (omitted by default; long strings truncated)",
10669
+ "moda audit <conversation_id> --limit=1000 # ingest rows to read (default 200, max 1000); warns when capped",
10670
+ "moda audit <conversation_id> --cursor=<next_cursor> # next page of a capped audit"
10637
10671
  ],
10638
10672
  ...dataApiDefaults
10639
10673
  }),
@@ -10651,7 +10685,7 @@ var commandRegistry = createCommandRegistry([
10651
10685
  }),
10652
10686
  legacyCommand({
10653
10687
  name: "tool-failure-detail",
10654
- description: "Get per-tool failure detail (each example embeds its anchor ±2 messages, long text cut to 300 chars)",
10688
+ description: "Get per-tool failure detail (each example embeds its anchor ±2 messages)",
10655
10689
  examples: [
10656
10690
  "moda tool-failure-detail search",
10657
10691
  "moda tool-failure-detail search --compact # no embedded conversation (same as --include-window=false)",
@@ -11061,6 +11095,15 @@ function printCliError(error) {
11061
11095
  ${issues.join(`
11062
11096
  `)}
11063
11097
  `);
11098
+ } else if (error instanceof ApiError && error.readable) {
11099
+ safeWriteStderr(`${error.message}
11100
+ `);
11101
+ if (error.suggestions?.length) {
11102
+ safeWriteStderr(`Try:
11103
+ ${error.suggestions.map((s) => ` ${s}`).join(`
11104
+ `)}
11105
+ `);
11106
+ }
11064
11107
  } else if (error instanceof ApiError) {
11065
11108
  safeWriteStderr(`API error (HTTP ${error.statusCode}): ${error.responseBody || error.message}
11066
11109
  `);
@@ -42,8 +42,8 @@ import {
42
42
  writeHarnessAnalyzeResult,
43
43
  writeHarnessArtifacts,
44
44
  writeHumanProgress
45
- } from "./cli-tznhpmf2.js";
46
- import"./cli-233xm35b.js";
45
+ } from "./cli-x0x0n35w.js";
46
+ import"./cli-xrs02c0t.js";
47
47
  import"./cli-0v6na3yp.js";
48
48
  export {
49
49
  writeHumanProgress,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  buildSourceSnapshot
3
- } from "./cli-tznhpmf2.js";
4
- import"./cli-233xm35b.js";
3
+ } from "./cli-x0x0n35w.js";
4
+ import"./cli-xrs02c0t.js";
5
5
  import"./cli-0v6na3yp.js";
6
6
 
7
7
  // src/harness-github-actions.ts
@@ -3,7 +3,7 @@ import {
3
3
  initPrompts,
4
4
  runPromptSync,
5
5
  runSkillSync
6
- } from "./cli-eaaegm7a.js";
6
+ } from "./cli-jqfm8dp4.js";
7
7
  import {
8
8
  codingAgentDisplayName,
9
9
  describeCodingAgentEvent,
@@ -22,7 +22,7 @@ import {
22
22
  runHarnessCommand,
23
23
  startRemoteAnalyze,
24
24
  stripAnsi
25
- } from "./cli-tznhpmf2.js";
25
+ } from "./cli-x0x0n35w.js";
26
26
  import {
27
27
  bundledSkillsDir,
28
28
  installIntegrationSkill,
@@ -34,14 +34,14 @@ import {
34
34
  } from "./cli-ssfq84k5.js";
35
35
  import {
36
36
  selectTenantAndCreateKey
37
- } from "./cli-ss2nere4.js";
37
+ } from "./cli-h4k23fb0.js";
38
38
  import {
39
39
  ConnectCodeError,
40
40
  connectWithCode,
41
41
  loadAuthSession,
42
42
  loadToken,
43
43
  login
44
- } from "./cli-fpt3jkyh.js";
44
+ } from "./cli-7kbzqact.js";
45
45
  import {
46
46
  CliAuthError,
47
47
  CliInputError,
@@ -49,7 +49,7 @@ import {
49
49
  createCommandContext,
50
50
  resolveIngestUrl,
51
51
  resolveModaBaseUrl
52
- } from "./cli-233xm35b.js";
52
+ } from "./cli-xrs02c0t.js";
53
53
  import"./cli-0v6na3yp.js";
54
54
 
55
55
  // src/init/index.ts
@@ -604,6 +604,11 @@ function hasUnexpectedAuditDuplicates(value) {
604
604
  return new Set(calls).size !== calls.length;
605
605
  });
606
606
  }
607
+ function promptHasSystemMessage(prompt) {
608
+ if (Array.isArray(prompt))
609
+ return prompt.some((message) => isRecord2(message) && message.role === "system");
610
+ return isRecord2(prompt) && prompt.reason === "row_payload_cap" && Array.isArray(prompt.roles) && prompt.roles.includes("system");
611
+ }
607
612
  function completeAI7Projection(value) {
608
613
  if (!isAI7Audit(value))
609
614
  return true;
@@ -612,7 +617,7 @@ function completeAI7Projection(value) {
612
617
  const spans = value.spans.filter(isRecord2);
613
618
  for (const span of spans.filter((span2) => span2.type === "llm")) {
614
619
  if (isRecord2(span.attributes) && span.attributes["gen_ai.system_instructions"]) {
615
- if (!Array.isArray(span.prompt) || !span.prompt.some((message) => isRecord2(message) && message.role === "system"))
620
+ if (!promptHasSystemMessage(span.prompt))
616
621
  return false;
617
622
  }
618
623
  }
@@ -632,7 +637,7 @@ function completeAI7Projection(value) {
632
637
  return expected.every((id) => typeof id === "string") && actual.every((id) => typeof id === "string") && new Set(actual).size === actual.length && expected.length === actual.length && expected.every((id) => actual.includes(id));
633
638
  }
634
639
  async function fetchAuditWithApiKey(id, apiKey, baseUrl) {
635
- const url = `${baseUrl}/api/v1/data/audit/${encodeURIComponent(id)}?kind=conversation`;
640
+ const url = `${baseUrl}/api/v1/data/audit/${encodeURIComponent(id)}?kind=conversation&include_bodies=1`;
636
641
  const response = await fetch(url, {
637
642
  method: "GET",
638
643
  headers: { "x-api-key": apiKey },
@@ -5,8 +5,8 @@ import {
5
5
  inspectCodeSelectors,
6
6
  inspectPythonSelectors,
7
7
  safePromptEvidencePath
8
- } from "./cli-tznhpmf2.js";
9
- import"./cli-233xm35b.js";
8
+ } from "./cli-x0x0n35w.js";
9
+ import"./cli-xrs02c0t.js";
10
10
  import {
11
11
  __commonJS,
12
12
  __toESM
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  selectTenantAndCreateKey
3
- } from "./cli-ss2nere4.js";
3
+ } from "./cli-h4k23fb0.js";
4
4
  import {
5
5
  loadAuthSession
6
- } from "./cli-fpt3jkyh.js";
6
+ } from "./cli-7kbzqact.js";
7
7
  import {
8
8
  CliAuthError,
9
9
  resolveIngestUrl,
10
10
  resolveModaBaseUrl,
11
11
  stringOption
12
- } from "./cli-233xm35b.js";
12
+ } from "./cli-xrs02c0t.js";
13
13
  import"./cli-0v6na3yp.js";
14
14
 
15
15
  // src/provision.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moda-ai/cli",
3
- "version": "1.48.0",
3
+ "version": "1.50.0",
4
4
  "description": "CLI for Moda - AI agent analytics and observability",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema_version": "moda.skill_index.v1",
3
- "bundled_at": "2026-10-10T06:36:17.459Z",
4
- "cli_version": "1.48.0",
3
+ "bundled_at": "2026-10-10T14:07:17.742Z",
4
+ "cli_version": "1.50.0",
5
5
  "skills": [
6
6
  {
7
7
  "id": "integration-cloudflare-think",
@@ -364,8 +364,8 @@ Fields an agent should read:
364
364
 
365
365
  `errors[].code` is one of `input_error`, `api_error`, `network_error`,
366
366
  `auth_required`, `input_required`, `cancelled`, `command_error`,
367
- `access_denied`, `unknown_error`, or a documented domain code such as
368
- `cloud_unavailable`. `access_denied` (exit 1) is an access problem — no
367
+ `access_denied`, `output_error`, `unknown_error`, or a documented domain
368
+ code such as `cloud_unavailable`. `access_denied` (exit 1) is an access problem — no
369
369
  organizations or an insufficient (viewer) role — distinct from `auth_required`
370
370
  (exit 4, missing/expired credentials).
371
371
 
@@ -408,14 +408,26 @@ Treat an answer as degraded if **any** of these hold:
408
408
 
409
409
  ### Large outputs and unknown flags
410
410
 
411
- - **Byte limit.** In `--agent` mode, envelopes over 200 KB are bounded
412
- *without* breaking their shape: long text fields (usually embedded
413
- transcripts) are shortened with `… [truncated N chars]`, then trailing list
414
- items are dropped. `warnings[]` and `truncation.data` say exactly what was
415
- cut (`dropped_items`, `complete: false`). `data` is still normal JSON, so
416
- `data.frustrations[0].conversation_id` keeps working. For complete results,
417
- page with a smaller `--limit` and `--offset`, or rerun with `--json` (no byte
418
- limit).
411
+ - **Byte limit.** In `--agent` mode, envelopes over 200 KB
412
+ (`MODA_AGENT_MAX_OUTPUT_BYTES`) are bounded *without* cutting any text or
413
+ breaking their shape: the complete output (the same JSON `--json` prints) is
414
+ written to a local file, and whole trailing list items are dropped from
415
+ `data`. `truncation.data.full_output_path` (also an `artifacts[]` entry of
416
+ kind `full_output`) is that file; read it in line ranges or with `jq`.
417
+ These files are pruned after 24 hours (`MODA_AGENT_OUTPUT_RETENTION_HOURS`).
418
+ `truncation.data` also says what was dropped (`dropped_items`,
419
+ `complete: false`). `data` is still normal JSON, so
420
+ `data.frustrations[0].conversation_id` keeps working. If even one item is
421
+ over budget, `data` is a stub `{ truncated, original_bytes, max_bytes,
422
+ full_output_path }`. If that file cannot be written (unwritable
423
+ `MODA_AGENT_OUTPUT_DIR`, full disk), nothing is dropped: the envelope keeps
424
+ the whole result over the limit with `status: "error"`, an `output_error`
425
+ (`cause: "full_output_save_failed"`), and exit `1`. If only the oversized
426
+ envelope text could not be saved (`cause: "envelope_text_save_failed"`),
427
+ that text stays whole, and the error message says whether `data` is whole
428
+ or bounded with its complete copy at `truncation.data.full_output_path`.
429
+ You can also page with a smaller `--limit` and
430
+ `--offset`, or rerun with `--json` (no byte limit).
419
431
  - **Unknown flags fail.** A flag the command doesn't take (a typo, or a
420
432
  filter it lacks) exits `1` with the valid flag list and a "did you mean",
421
433
  instead of being silently ignored. Stray arguments on commands that take
@@ -652,13 +664,23 @@ tool calls, prompt/response bodies, and `gen_ai.*` attributes — use `moda audi
652
664
  moda audit <conversation_id|trace_id> # auto-detects OTLP trace_id vs trace ID (conversation_id)
653
665
  moda audit <trace_id> --kind=trace # force OTLP trace_id lookup
654
666
  moda audit <conversation_id> --include-raw # attach verbatim raw_event bodies
667
+ moda audit <conversation_id> --include-bodies # keep prompt/response/attribute bodies
655
668
  moda audit <conversation_id> --limit=1000 # ingest rows to read (default 200, max 1000)
669
+ moda audit <conversation_id> --cursor=<next_cursor> # next page of a capped audit
656
670
  ```
657
671
 
658
672
  `truncated` / `has_more` at the top level say whether the audit is complete.
659
- A full `--limit` page of ingest rows (or a server span cap) sets both, and a
660
- warning plus `next_commands` give the rerun; for very large traces audit one
661
- OTLP trace at a time (`summary.trace_ids`, `--kind=trace`).
673
+ A full `--limit` page of ingest rows, the server's ~3 MB response budget
674
+ (`summary.truncated_reason: "byte_limit"`) or a span cap sets both, and a
675
+ warning plus `next_commands` give the follow-up — usually the `--cursor=` page
676
+ from `next_cursor`; for very large traces audit one OTLP trace at a time
677
+ (`summary.trace_ids`, `--kind=trace`). Prompt/response bodies are omitted by
678
+ default (`{"omitted": true, "original_bytes": N}`); `--include-bodies` keeps
679
+ them, cutting strings over 8 KB to `{"truncated": true, "original_bytes": N,
680
+ "preview": …}`. Ingest rows whose stored payload is too large carry
681
+ `payload_capped: true`: their spans keep ids, hierarchy, timing and types but
682
+ no bodies. A trace too large to audit at all fails fast with an
683
+ `audit_too_large` error whose `suggested_commands` list per-trace audits.
662
684
 
663
685
  Returns the raw, NON-deduped span set from the Layer-0 store (`events_raw`), so
664
686
  nothing is collapsed. Use it to verify capture completeness: the `summary` gives
@@ -736,9 +758,9 @@ Pass `--include-window` to attach a `window` field per row (instead of
736
758
  (1–5, default `1`) is the half-width; `--window=1` ⇒ 3 messages total
737
759
  (anchor ± 1).
738
760
 
739
- Examples default to `--limit=5` (max 20). Strings inside `examples[]` longer
740
- than 300 characters are cut, and a warning says so; read the full text with
741
- the row's `context_command`. Agent output lists the 20 most frequent
761
+ Examples default to `--limit=5` (max 20). Example text is returned whole; if
762
+ the agent envelope goes over its byte limit, whole trailing examples are
763
+ dropped and `truncation.data.full_output_path` holds every example. Agent output lists the 20 most frequent
742
764
  `subtypes` (`subtypes_total` has the full count); `--json` lists all.
743
765
 
744
766
  ### 7. Prompt management