@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.
- package/dist/{cli-fpt3jkyh.js → cli-7kbzqact.js} +1 -1
- package/dist/{cli-ss2nere4.js → cli-h4k23fb0.js} +2 -2
- package/dist/{cli-eaaegm7a.js → cli-jqfm8dp4.js} +4 -4
- package/dist/{cli-tznhpmf2.js → cli-x0x0n35w.js} +56 -11
- package/dist/{cli-233xm35b.js → cli-xrs02c0t.js} +248 -61
- package/dist/cli.js +122 -79
- package/dist/{harness-vf1e1t2h.js → harness-7gr0xbtc.js} +2 -2
- package/dist/{harness-github-actions-379faaqe.js → harness-github-actions-q9kewzb6.js} +2 -2
- package/dist/{index-p8vab4p4.js → index-z0jpjcsm.js} +12 -7
- package/dist/prompt-source-mcp.js +2 -2
- package/dist/{provision-e4cnxpdh.js → provision-qkazpt0c.js} +3 -3
- package/package.json +1 -1
- package/skills/integration/index.json +2 -2
- package/skills/moda-cli/SKILL.md +38 -16
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
authFetch,
|
|
3
3
|
getCachedLoginContext
|
|
4
|
-
} from "./cli-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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 (
|
|
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()
|
|
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
|
-
|
|
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
|
|
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
|
|
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-
|
|
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:
|
|
1291
|
-
findings:
|
|
1292
|
-
evidence_refs:
|
|
1293
|
-
recommended_actions:
|
|
1294
|
-
next_commands:
|
|
1295
|
-
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
|
-
...
|
|
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:
|
|
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
|
-
|
|
1306
|
-
const
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
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
|
|
1630
|
-
|
|
1631
|
-
|
|
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:
|
|
1641
|
-
warning: `Output was ${originalBytes} bytes, over the ${maxBytes}-byte agent limit, and could not be reduced while keeping its structure; data is a
|
|
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: ${
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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]
|
|
5758
|
+
return text.split(/\r?\n/)[citation.line - 1] ?? citation.excerpt;
|
|
5770
5759
|
}
|
|
5771
|
-
return text
|
|
5760
|
+
return text;
|
|
5772
5761
|
}
|
|
5773
|
-
function
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
9381
|
-
|
|
9382
|
-
const
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
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(
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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> --
|
|
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
|
|
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-
|
|
46
|
-
import"./cli-
|
|
45
|
+
} from "./cli-x0x0n35w.js";
|
|
46
|
+
import"./cli-xrs02c0t.js";
|
|
47
47
|
import"./cli-0v6na3yp.js";
|
|
48
48
|
export {
|
|
49
49
|
writeHumanProgress,
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
initPrompts,
|
|
4
4
|
runPromptSync,
|
|
5
5
|
runSkillSync
|
|
6
|
-
} from "./cli-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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 (!
|
|
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 },
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
selectTenantAndCreateKey
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-h4k23fb0.js";
|
|
4
4
|
import {
|
|
5
5
|
loadAuthSession
|
|
6
|
-
} from "./cli-
|
|
6
|
+
} from "./cli-7kbzqact.js";
|
|
7
7
|
import {
|
|
8
8
|
CliAuthError,
|
|
9
9
|
resolveIngestUrl,
|
|
10
10
|
resolveModaBaseUrl,
|
|
11
11
|
stringOption
|
|
12
|
-
} from "./cli-
|
|
12
|
+
} from "./cli-xrs02c0t.js";
|
|
13
13
|
import"./cli-0v6na3yp.js";
|
|
14
14
|
|
|
15
15
|
// src/provision.ts
|
package/package.json
CHANGED
package/skills/moda-cli/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
|
412
|
-
*without*
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
`
|
|
417
|
-
|
|
418
|
-
|
|
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
|
|
660
|
-
|
|
661
|
-
|
|
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).
|
|
740
|
-
|
|
741
|
-
|
|
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
|