@moda-ai/cli 1.45.3 → 1.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/{cli-nnwegmzh.js → cli-6xndg2h4.js} +4 -4
- package/dist/{cli-bvnw7585.js → cli-bqhs6sk7.js} +7 -2
- package/dist/{cli-dy2aavbe.js → cli-c64d2jrw.js} +1 -1
- package/dist/{cli-3xh2w7hb.js → cli-m3bg9yyh.js} +50 -4
- package/dist/{cli-phgzghwq.js → cli-taf854xx.js} +2 -2
- package/dist/cli.js +234 -86
- package/dist/{harness-9qpbj2qj.js → harness-4ypeg9dh.js} +2 -2
- package/dist/{harness-github-actions-ky4ym5n3.js → harness-github-actions-dvq7spqh.js} +2 -2
- package/dist/{index-bcn5chmf.js → index-pdg9t77n.js} +12 -7
- package/dist/prompt-source-mcp.js +2 -2
- package/dist/{provision-mqw8s0jb.js → provision-7fhbgk2d.js} +3 -3
- package/package.json +2 -1
- package/skills/integration/index.json +2 -2
- package/skills/moda-cli/SKILL.md +32 -7
package/README.md
CHANGED
|
@@ -116,6 +116,7 @@ Feedback:
|
|
|
116
116
|
|
|
117
117
|
```bash
|
|
118
118
|
moda feedback "cluster label looks wrong" --category=bad_cluster_label
|
|
119
|
+
moda feedback "trace belongs to another problem" --category=false_positive_attribution --problem-id=<id>
|
|
119
120
|
```
|
|
120
121
|
|
|
121
122
|
Output adapts to where it runs: piped/CI/agent environments get schema-versioned
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
resolvePromptSource,
|
|
14
14
|
safePromptEvidencePath,
|
|
15
15
|
scanHarness
|
|
16
|
-
} from "./cli-
|
|
16
|
+
} from "./cli-m3bg9yyh.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-c64d2jrw.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-bqhs6sk7.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-4ypeg9dh.js");
|
|
6715
6715
|
const result = await analyzeHarnessPrompts({
|
|
6716
6716
|
rootDir: process.cwd(),
|
|
6717
6717
|
existing: established.flatMap((prompt) => prompt.sourceDefinition ? [prompt.sourceDefinition.source] : []),
|
|
@@ -1418,10 +1418,14 @@ function errorToAgentError(error) {
|
|
|
1418
1418
|
}
|
|
1419
1419
|
if (isApiLikeError(error)) {
|
|
1420
1420
|
const statusCode = "statusCode" in error ? error.statusCode : undefined;
|
|
1421
|
+
const typedCode = "code" in error && typeof error.code === "string" ? error.code : undefined;
|
|
1422
|
+
const suggestions = "suggestions" in error && Array.isArray(error.suggestions) ? error.suggestions.filter((s) => typeof s === "string") : [];
|
|
1423
|
+
const http = typeof statusCode === "number" ? `HTTP ${statusCode}` : undefined;
|
|
1421
1424
|
return {
|
|
1422
1425
|
code: "api_error",
|
|
1423
1426
|
message: error.message,
|
|
1424
|
-
cause:
|
|
1427
|
+
cause: typedCode ? http ? `${typedCode} (${http})` : typedCode : http,
|
|
1428
|
+
...suggestions.length > 0 ? { suggested_commands: suggestions } : {},
|
|
1425
1429
|
blocking: true
|
|
1426
1430
|
};
|
|
1427
1431
|
}
|
|
@@ -1692,6 +1696,7 @@ function createCommandContext(input, options = {}) {
|
|
|
1692
1696
|
positionals: input.positionals,
|
|
1693
1697
|
flags,
|
|
1694
1698
|
rawFlags,
|
|
1699
|
+
...input.bareFlags ? { bareFlags: input.bareFlags } : {},
|
|
1695
1700
|
startedAt: options.startedAt ?? Date.now(),
|
|
1696
1701
|
cwd: options.cwd ?? process.cwd(),
|
|
1697
1702
|
env: options.env ?? process.env,
|
|
@@ -1716,4 +1721,4 @@ function createCommandContext(input, options = {}) {
|
|
|
1716
1721
|
};
|
|
1717
1722
|
}
|
|
1718
1723
|
|
|
1719
|
-
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 };
|
|
1724
|
+
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 };
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
resolveTenantId,
|
|
26
26
|
shouldInventoryFile,
|
|
27
27
|
stringOption
|
|
28
|
-
} from "./cli-
|
|
28
|
+
} from "./cli-bqhs6sk7.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;
|
|
177917
177926
|
}
|
|
177918
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;
|
|
177943
|
+
}
|
|
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;
|
|
@@ -184813,7 +184859,7 @@ async function runHarnessCommand(context) {
|
|
|
184813
184859
|
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
184860
|
}
|
|
184815
184861
|
if (context.flags["github-actions"] === "true") {
|
|
184816
|
-
const { runGithubActionsAnalyze } = await import("./harness-github-actions-
|
|
184862
|
+
const { runGithubActionsAnalyze } = await import("./harness-github-actions-dvq7spqh.js");
|
|
184817
184863
|
const result = await runGithubActionsAnalyze(rootDir, {
|
|
184818
184864
|
writeReport: (report2) => {
|
|
184819
184865
|
const normalized = normalizeHarnessReport(report2);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
authFetch,
|
|
3
3
|
getCachedLoginContext
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-c64d2jrw.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-bqhs6sk7.js";
|
|
19
19
|
|
|
20
20
|
// src/init/tenant.ts
|
|
21
21
|
import { randomUUID } from "node:crypto";
|
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-6xndg2h4.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-m3bg9yyh.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-c64d2jrw.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-bqhs6sk7.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"];
|
|
@@ -402,6 +405,8 @@ var SearchSchema = z.object({
|
|
|
402
405
|
include_tool_io: boolFlag(),
|
|
403
406
|
offset: z.number().min(0).max(1e4).optional()
|
|
404
407
|
});
|
|
408
|
+
|
|
409
|
+
// ../../packages/database/src/agent-feedback.ts
|
|
405
410
|
var FEEDBACK_CATEGORIES = [
|
|
406
411
|
"bad_cluster_label",
|
|
407
412
|
"mismatched_frustration",
|
|
@@ -410,18 +415,98 @@ var FEEDBACK_CATEGORIES = [
|
|
|
410
415
|
"wrong_tool_failure",
|
|
411
416
|
"incorrect_loop",
|
|
412
417
|
"api_quirk",
|
|
418
|
+
"false_positive_attribution",
|
|
419
|
+
"misrouted_fix",
|
|
420
|
+
"harness_bug",
|
|
413
421
|
"other"
|
|
414
422
|
];
|
|
415
423
|
var FEEDBACK_SEVERITIES = ["info", "low", "medium", "high"];
|
|
416
|
-
var
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
424
|
+
var DEFAULT_FEEDBACK_CATEGORY = "other";
|
|
425
|
+
var RAW_FEEDBACK_CATEGORY_MAX = 64;
|
|
426
|
+
function isFeedbackCategory(value) {
|
|
427
|
+
return typeof value === "string" && FEEDBACK_CATEGORIES.includes(value);
|
|
428
|
+
}
|
|
429
|
+
function normalizeFeedbackCategory(value) {
|
|
430
|
+
if (value === undefined || value === null || value === "")
|
|
431
|
+
return { category: DEFAULT_FEEDBACK_CATEGORY };
|
|
432
|
+
if (isFeedbackCategory(value))
|
|
433
|
+
return { category: value };
|
|
434
|
+
const raw = String(value).trim().slice(0, RAW_FEEDBACK_CATEGORY_MAX);
|
|
435
|
+
return raw ? { category: DEFAULT_FEEDBACK_CATEGORY, raw_category: raw } : { category: DEFAULT_FEEDBACK_CATEGORY };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// src/feedback.ts
|
|
439
|
+
var FEEDBACK_FLAGS = ["category", "severity", "note", "cluster-id", "conversation-id", "run-id", "tool-name"];
|
|
440
|
+
var FEEDBACK_USAGE = `Usage: moda feedback "<note>" [--category=${FEEDBACK_CATEGORIES.join("|")}] ` + `[--severity=${FEEDBACK_SEVERITIES.join("|")}] [--conversation-id=ID] [--cluster-id=ID] ` + "[--tool-name=NAME] [--run-id=ID] [--<name>-id=ID]";
|
|
441
|
+
var NOTE_MAX = 4000;
|
|
442
|
+
var REF_VALUE_MAX = 256;
|
|
443
|
+
var REF_FLAG_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*-(?:id|ids|index|token)$/;
|
|
444
|
+
var REF_FLAG_WORDS = new Set(["turn", "tool", "family", "kind"]);
|
|
445
|
+
function isFeedbackRefFlag(flag) {
|
|
446
|
+
return REF_FLAG_PATTERN.test(flag) || REF_FLAG_WORDS.has(flag);
|
|
447
|
+
}
|
|
448
|
+
function feedbackCategoryHelp(indent = 35, width = 100) {
|
|
449
|
+
const lines = [];
|
|
450
|
+
let line = "";
|
|
451
|
+
FEEDBACK_CATEGORIES.forEach((category, index) => {
|
|
452
|
+
const piece = index < FEEDBACK_CATEGORIES.length - 1 ? `${category}|` : `${category} (default other)`;
|
|
453
|
+
if (line && indent + line.length + piece.length > width) {
|
|
454
|
+
lines.push(line);
|
|
455
|
+
line = "";
|
|
456
|
+
}
|
|
457
|
+
line += piece;
|
|
458
|
+
});
|
|
459
|
+
lines.push(line);
|
|
460
|
+
return lines.join(`
|
|
461
|
+
${" ".repeat(indent)}`);
|
|
462
|
+
}
|
|
463
|
+
function buildFeedbackRequest(positionals, flags, bareFlags) {
|
|
464
|
+
const isBare = (flag) => bareFlags ? bareFlags.includes(flag) : flags[flag] === "true";
|
|
465
|
+
const flagNote = flags.note !== undefined && !isBare("note") ? flags.note.trim() : "";
|
|
466
|
+
const note = [positionals.join(" ").trim(), flagNote].filter(Boolean).join(`
|
|
467
|
+
`);
|
|
468
|
+
if (note === "")
|
|
469
|
+
throw new CliInputError("<note> is required", FEEDBACK_USAGE);
|
|
470
|
+
if (note.length > NOTE_MAX) {
|
|
471
|
+
throw new CliInputError(`<note> is ${note.length} characters; the limit is ${NOTE_MAX}.`, FEEDBACK_USAGE);
|
|
472
|
+
}
|
|
473
|
+
const severity = flags.severity ?? "low";
|
|
474
|
+
if (!FEEDBACK_SEVERITIES.includes(severity)) {
|
|
475
|
+
throw new CliInputError(`Invalid --severity '${severity}'. Valid severities: ${FEEDBACK_SEVERITIES.join(", ")}.`, FEEDBACK_USAGE);
|
|
476
|
+
}
|
|
477
|
+
const refs = {};
|
|
478
|
+
for (const [flag, value] of Object.entries(flags)) {
|
|
479
|
+
if (flag === "category" || flag === "severity" || flag === "note")
|
|
480
|
+
continue;
|
|
481
|
+
if (!FEEDBACK_FLAGS.includes(flag) && !isFeedbackRefFlag(flag))
|
|
482
|
+
continue;
|
|
483
|
+
const trimmed = value.trim();
|
|
484
|
+
if (value === "true" || trimmed === "") {
|
|
485
|
+
throw new CliInputError(`--${flag} needs a value, e.g. --${flag}=<id>.`, FEEDBACK_USAGE);
|
|
486
|
+
}
|
|
487
|
+
if (trimmed.length > REF_VALUE_MAX) {
|
|
488
|
+
throw new CliInputError(`--${flag} is ${trimmed.length} characters; the limit is ${REF_VALUE_MAX}.`, FEEDBACK_USAGE);
|
|
489
|
+
}
|
|
490
|
+
refs[flag.replace(/-/g, "_")] = trimmed;
|
|
491
|
+
}
|
|
492
|
+
const warnings = [];
|
|
493
|
+
const { category, raw_category: rawCategory } = normalizeFeedbackCategory(flags.category);
|
|
494
|
+
if (rawCategory !== undefined) {
|
|
495
|
+
refs.raw_category = rawCategory;
|
|
496
|
+
warnings.push(`Unknown category '${rawCategory}': sent as 'other' with refs.raw_category. ` + `Valid categories: ${FEEDBACK_CATEGORIES.join(", ")}.`);
|
|
497
|
+
}
|
|
498
|
+
const hint = dedicatedCommandHint(category, rawCategory);
|
|
499
|
+
if (hint)
|
|
500
|
+
warnings.push(hint);
|
|
501
|
+
return { category, severity, note, refs, warnings };
|
|
502
|
+
}
|
|
503
|
+
var MISROUTE_CATEGORIES = new Set(["false_positive_attribution", "misrouted_fix"]);
|
|
504
|
+
var MISROUTE_WORDS = /attribut|misrout|false[\s_-]?positive|wrong[\s_-]?(?:problem|fix)/i;
|
|
505
|
+
function dedicatedCommandHint(category, rawCategory) {
|
|
506
|
+
if (!MISROUTE_CATEGORIES.has(category) && !(rawCategory && MISROUTE_WORDS.test(rawCategory)))
|
|
507
|
+
return;
|
|
508
|
+
return "Feedback is read by the Moda team but does not change the problem. To act on it directly, use " + "`moda problem-feedback <problem_id> --action=flag_attribution --attribution-id=<uuid> --reason=TEXT` (a trace wrongly " + "attributed to a problem), `moda false-positive <kind> <target> --reason=TEXT` (a detection that should not " + "have fired), or `moda fix dismiss <fix_id> --reason=TEXT` (a fix aimed at the wrong problem).";
|
|
509
|
+
}
|
|
425
510
|
|
|
426
511
|
// src/transforms.ts
|
|
427
512
|
function asRecord(value) {
|
|
@@ -7667,6 +7752,7 @@ function parseArgs(argv) {
|
|
|
7667
7752
|
const flags = {};
|
|
7668
7753
|
const positionals = [];
|
|
7669
7754
|
const shortFlags = [];
|
|
7755
|
+
const bareFlags = [];
|
|
7670
7756
|
let positional;
|
|
7671
7757
|
let i = 0;
|
|
7672
7758
|
let endOfOptions = false;
|
|
@@ -7675,7 +7761,7 @@ function parseArgs(argv) {
|
|
|
7675
7761
|
if (!endOfOptions && arg === "--" && command !== "") {
|
|
7676
7762
|
endOfOptions = true;
|
|
7677
7763
|
} else if (!endOfOptions && arg.startsWith("--")) {
|
|
7678
|
-
i = parseFlag(args, i, flags, command === "");
|
|
7764
|
+
i = parseFlag(args, i, flags, command === "", bareFlags);
|
|
7679
7765
|
} else if (!endOfOptions && command !== "" && /^-[A-Za-z]/.test(arg)) {
|
|
7680
7766
|
shortFlags.push(arg);
|
|
7681
7767
|
} else if (command === "") {
|
|
@@ -7688,7 +7774,14 @@ function parseArgs(argv) {
|
|
|
7688
7774
|
}
|
|
7689
7775
|
i++;
|
|
7690
7776
|
}
|
|
7691
|
-
return {
|
|
7777
|
+
return {
|
|
7778
|
+
command,
|
|
7779
|
+
positional,
|
|
7780
|
+
positionals,
|
|
7781
|
+
flags,
|
|
7782
|
+
...shortFlags.length > 0 ? { shortFlags } : {},
|
|
7783
|
+
...bareFlags.length > 0 ? { bareFlags } : {}
|
|
7784
|
+
};
|
|
7692
7785
|
}
|
|
7693
7786
|
var DRY_RUN_TRUE = new Set(["true", "yes", "1", "on"]);
|
|
7694
7787
|
var DRY_RUN_FALSE = new Set(["false", "no", "0", "off"]);
|
|
@@ -7708,24 +7801,32 @@ var GLOBAL_BOOLEAN_FLAGS = new Set([
|
|
|
7708
7801
|
"tui",
|
|
7709
7802
|
"no-tui"
|
|
7710
7803
|
]);
|
|
7711
|
-
function parseFlag(args, index, flags, beforeCommand) {
|
|
7804
|
+
function parseFlag(args, index, flags, beforeCommand, bareFlags) {
|
|
7805
|
+
const set = (key2, value, bare) => {
|
|
7806
|
+
flags[key2] = value;
|
|
7807
|
+
const at = bareFlags.indexOf(key2);
|
|
7808
|
+
if (at !== -1)
|
|
7809
|
+
bareFlags.splice(at, 1);
|
|
7810
|
+
if (bare)
|
|
7811
|
+
bareFlags.push(key2);
|
|
7812
|
+
};
|
|
7712
7813
|
const arg = args[index];
|
|
7713
7814
|
const eqIndex = arg.indexOf("=");
|
|
7714
7815
|
if (eqIndex !== -1) {
|
|
7715
|
-
|
|
7816
|
+
set(arg.slice(2, eqIndex), arg.slice(eqIndex + 1), false);
|
|
7716
7817
|
return index;
|
|
7717
7818
|
}
|
|
7718
7819
|
const key = arg.slice(2);
|
|
7719
7820
|
const next = args[index + 1];
|
|
7720
7821
|
if (beforeCommand && GLOBAL_BOOLEAN_FLAGS.has(key)) {
|
|
7721
|
-
|
|
7822
|
+
set(key, "true", true);
|
|
7722
7823
|
return index;
|
|
7723
7824
|
}
|
|
7724
7825
|
if (next !== undefined && !next.startsWith("--")) {
|
|
7725
|
-
|
|
7826
|
+
set(key, next, false);
|
|
7726
7827
|
return index + 1;
|
|
7727
7828
|
}
|
|
7728
|
-
|
|
7829
|
+
set(key, "true", true);
|
|
7729
7830
|
return index;
|
|
7730
7831
|
}
|
|
7731
7832
|
function initFlagsForContext(flags, outputMode, stdinIsTTY) {
|
|
@@ -8016,13 +8117,16 @@ Clusters search flags:
|
|
|
8016
8117
|
--mode=fuzzy|semantic|hybrid Cluster search mode (default hybrid)
|
|
8017
8118
|
|
|
8018
8119
|
Feedback flags:
|
|
8019
|
-
--category=CAT
|
|
8020
|
-
|
|
8120
|
+
--category=CAT ${feedbackCategoryHelp()}
|
|
8121
|
+
An unknown category is sent as other (with refs.raw_category)
|
|
8021
8122
|
--severity=info|low|medium|high How bad it is (default low)
|
|
8123
|
+
--note=TEXT The note, if not given as the positional argument
|
|
8022
8124
|
--conversation-id=ID Attach the trace ID you were looking at
|
|
8023
8125
|
--cluster-id=ID Attach a cluster node id
|
|
8024
8126
|
--tool-name=NAME Attach a tool name
|
|
8025
8127
|
--run-id=ID Attach a run id
|
|
8128
|
+
--<name>-id=ID Any other id (--problem-id, --fix-id, --attribution-id,
|
|
8129
|
+
--trace-id, --span-id, --message-index, ...) goes into refs
|
|
8026
8130
|
(set MODA_CLI_TIPS=0 to hide the feedback tip line)
|
|
8027
8131
|
|
|
8028
8132
|
Init flags:
|
|
@@ -8266,7 +8370,62 @@ function auditCapStatus(data, limit) {
|
|
|
8266
8370
|
const rowsCapped = hasMore || !serverSaid && rows >= limit;
|
|
8267
8371
|
if (!rowsCapped && !spansCapped)
|
|
8268
8372
|
return null;
|
|
8269
|
-
|
|
8373
|
+
const reason = typeof summary.truncated_reason === "string" ? summary.truncated_reason : null;
|
|
8374
|
+
const cursorValue = summary.next_cursor ?? record.next_cursor;
|
|
8375
|
+
const nextCursor = typeof cursorValue === "string" && cursorValue ? cursorValue : null;
|
|
8376
|
+
return { rowsCapped, spansCapped, hasMoreKnown: hasMore, rows, spans, reason, nextCursor };
|
|
8377
|
+
}
|
|
8378
|
+
var AUDIT_MIN_PAGE_BYTES = 32 * 1024;
|
|
8379
|
+
var AUDIT_AGENT_RESERVE_BYTES = 16 * 1024;
|
|
8380
|
+
async function fetchAudit(id, query2, maxBytes) {
|
|
8381
|
+
const pageQuery = new URLSearchParams(query2);
|
|
8382
|
+
if (maxBytes !== undefined)
|
|
8383
|
+
pageQuery.set("max_bytes", maxBytes.toString());
|
|
8384
|
+
const queryString = pageQuery.toString() ? `?${pageQuery.toString()}` : "";
|
|
8385
|
+
return callDataAPI(`/audit/${encodeURIComponent(id)}${queryString}`);
|
|
8386
|
+
}
|
|
8387
|
+
function auditOutput(command, params, data) {
|
|
8388
|
+
const capped = auditCapStatus(data, params.limit ?? AUDIT_DEFAULT_LIMIT);
|
|
8389
|
+
const auditRecord = asRecord(data);
|
|
8390
|
+
if (!capped || !auditRecord) {
|
|
8391
|
+
return { data: auditRecord ? { ...auditRecord, truncated: false, has_more: false, next_cursor: null } : data, options: {} };
|
|
8392
|
+
}
|
|
8393
|
+
const limit = params.limit ?? AUDIT_DEFAULT_LIMIT;
|
|
8394
|
+
const perTrace = "audit one OTLP trace at a time with `moda audit <trace_id> --kind=trace` (ids in summary.trace_ids)";
|
|
8395
|
+
const warnings = [];
|
|
8396
|
+
const nextCommands = [];
|
|
8397
|
+
const auditFlags = (extra) => [
|
|
8398
|
+
`moda ${command} ${shellArg(params.id)}`,
|
|
8399
|
+
params.kind && params.kind !== "auto" ? `--kind=${params.kind}` : "",
|
|
8400
|
+
params.include_raw === true ? "--include-raw" : "",
|
|
8401
|
+
params.include_bodies === true ? "--include-bodies" : "",
|
|
8402
|
+
...extra
|
|
8403
|
+
].filter(Boolean).join(" ");
|
|
8404
|
+
if (capped.nextCursor) {
|
|
8405
|
+
nextCommands.push({
|
|
8406
|
+
command: auditFlags([params.limit ? `--limit=${params.limit}` : "", `--cursor=${capped.nextCursor}`]),
|
|
8407
|
+
purpose: "Read the next page of ingest rows of this trace.",
|
|
8408
|
+
mutability: "read",
|
|
8409
|
+
requires_approval: false
|
|
8410
|
+
});
|
|
8411
|
+
}
|
|
8412
|
+
if (capped.reason === "byte_limit") {
|
|
8413
|
+
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.`);
|
|
8414
|
+
} else if (capped.reason === "span_limit" && capped.nextCursor) {
|
|
8415
|
+
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.");
|
|
8416
|
+
} else if (capped.rowsCapped) {
|
|
8417
|
+
const bigger = Math.min(AUDIT_MAX_LIMIT, limit * 5);
|
|
8418
|
+
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)}.`));
|
|
8419
|
+
if (limit < AUDIT_MAX_LIMIT && !params.cursor) {
|
|
8420
|
+
nextCommands.push({ command: auditFlags([`--limit=${bigger}`]), purpose: "Read more ingest rows of this trace.", mutability: "read", requires_approval: false });
|
|
8421
|
+
}
|
|
8422
|
+
} else {
|
|
8423
|
+
warnings.push(`Spans were capped: the server returned ${capped.spans} span(s) and marked the response truncated. ${perTrace[0].toUpperCase()}${perTrace.slice(1)}.`);
|
|
8424
|
+
}
|
|
8425
|
+
return {
|
|
8426
|
+
data: { ...auditRecord, truncated: true, has_more: capped.rowsCapped, next_cursor: capped.nextCursor },
|
|
8427
|
+
options: { warnings, ...nextCommands.length > 0 ? { nextCommands } : {} }
|
|
8428
|
+
};
|
|
8270
8429
|
}
|
|
8271
8430
|
var TAIL_SEEN_MAX = 5000;
|
|
8272
8431
|
var EMOTIONS_TAIL_PAGE_SIZE = 20;
|
|
@@ -8755,7 +8914,7 @@ var HAND_PARSED_COMMAND_FLAGS = {
|
|
|
8755
8914
|
"tool-use-id"
|
|
8756
8915
|
],
|
|
8757
8916
|
"false-positives": ["all", "kind", "limit"],
|
|
8758
|
-
feedback:
|
|
8917
|
+
feedback: FEEDBACK_FLAGS,
|
|
8759
8918
|
"problem-impact": ["mode", "node-id", "problem-id", "run-id", "to", "cursor", "conversation-ids", "days-back"]
|
|
8760
8919
|
};
|
|
8761
8920
|
var NO_POSITIONAL_COMMANDS = new Set([
|
|
@@ -8786,9 +8945,16 @@ function assertHandParsedFlags(command, flags, positionals) {
|
|
|
8786
8945
|
const allowed = HAND_PARSED_COMMAND_FLAGS[command];
|
|
8787
8946
|
if (!allowed)
|
|
8788
8947
|
return;
|
|
8948
|
+
const checked = command === "feedback" ? Object.keys(flags).filter((flag2) => !isFeedbackRefFlag(flag2)) : Object.keys(flags);
|
|
8789
8949
|
const shape = z2.object(Object.fromEntries(allowed.map((flag2) => [flag2.replace(/-/g, "_"), z2.unknown()])));
|
|
8790
|
-
const args = Object.fromEntries(
|
|
8791
|
-
|
|
8950
|
+
const args = Object.fromEntries(checked.map((flag2) => [flag2.replace(/-/g, "_"), true]));
|
|
8951
|
+
try {
|
|
8952
|
+
parseCommandFlags(shape.passthrough(), args, command);
|
|
8953
|
+
} catch (error) {
|
|
8954
|
+
if (command !== "feedback" || !(error instanceof CliInputError))
|
|
8955
|
+
throw error;
|
|
8956
|
+
throw new CliInputError(/nothing was sent/i.test(error.message) ? error.message : `${error.message} Nothing was sent.`, `${error.usage ?? ""} Id flags (--<name>-id=ID) are attached as refs; put other detail in the note. ${FEEDBACK_USAGE}`.trim());
|
|
8957
|
+
}
|
|
8792
8958
|
}
|
|
8793
8959
|
var PROFILE_MANAGING_COMMANDS = new Set(["profiles", "auth", "init", "provision", "status", "config", "keys", "manifest", "agent", "doctor"]);
|
|
8794
8960
|
async function runProblemsRemainder(flags, context) {
|
|
@@ -8947,7 +9113,7 @@ async function readFullTranscript(conversationId, opts = {}) {
|
|
|
8947
9113
|
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` : ""}.`);
|
|
8948
9114
|
}
|
|
8949
9115
|
if (returned.some(isTruncated)) {
|
|
8950
|
-
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
|
|
9116
|
+
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).");
|
|
8951
9117
|
}
|
|
8952
9118
|
return {
|
|
8953
9119
|
data: stripLoneSurrogates({
|
|
@@ -9258,7 +9424,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
9258
9424
|
case "audit":
|
|
9259
9425
|
case "trace": {
|
|
9260
9426
|
if (!positional) {
|
|
9261
|
-
throw new CliInputError("<conversation_id|trace_id> is required", "Usage: moda audit <conversation_id|trace_id> [--kind=auto|trace|conversation] [--include-raw] [--limit=N]");
|
|
9427
|
+
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>]");
|
|
9262
9428
|
}
|
|
9263
9429
|
const args = flagsToArgs(flags, "id", positional);
|
|
9264
9430
|
const params = parseCommandFlags(AuditSchema, args, command, { positionalKey: "id" });
|
|
@@ -9267,36 +9433,26 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
9267
9433
|
query2.set("kind", params.kind);
|
|
9268
9434
|
if (params.include_raw === true)
|
|
9269
9435
|
query2.set("include_raw", "1");
|
|
9436
|
+
if (params.include_bodies === true)
|
|
9437
|
+
query2.set("include_bodies", "1");
|
|
9270
9438
|
if (params.limit)
|
|
9271
9439
|
query2.set("limit", params.limit.toString());
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
const
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
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)}.`));
|
|
9287
|
-
if (limit < AUDIT_MAX_LIMIT) {
|
|
9288
|
-
const rerun = [
|
|
9289
|
-
`moda ${command} ${shellArg(params.id)}`,
|
|
9290
|
-
params.kind && params.kind !== "auto" ? `--kind=${params.kind}` : "",
|
|
9291
|
-
params.include_raw === true ? "--include-raw" : "",
|
|
9292
|
-
`--limit=${bigger}`
|
|
9293
|
-
].filter(Boolean).join(" ");
|
|
9294
|
-
nextCommands.push({ command: rerun, purpose: "Read more ingest rows of this trace.", mutability: "read", requires_approval: false });
|
|
9295
|
-
}
|
|
9296
|
-
} else {
|
|
9297
|
-
warnings.push(`Spans were capped: the server returned ${capped.spans} span(s) and marked the response truncated. ${perTrace[0].toUpperCase()}${perTrace.slice(1)}.`);
|
|
9440
|
+
if (params.cursor)
|
|
9441
|
+
query2.set("cursor", params.cursor);
|
|
9442
|
+
const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
|
|
9443
|
+
let maxBytes = agentMode ? Math.max(AUDIT_MIN_PAGE_BYTES, agentMaxOutputBytes() - AUDIT_AGENT_RESERVE_BYTES) : undefined;
|
|
9444
|
+
let page = auditOutput(command, params, await fetchAudit(params.id, query2, maxBytes));
|
|
9445
|
+
while (maxBytes !== undefined && maxBytes > AUDIT_MIN_PAGE_BYTES && !agentEnvelopeFits(command, page.data, page.options)) {
|
|
9446
|
+
maxBytes = Math.max(AUDIT_MIN_PAGE_BYTES, Math.floor(maxBytes / 2));
|
|
9447
|
+
page = auditOutput(command, params, await fetchAudit(params.id, query2, maxBytes));
|
|
9448
|
+
}
|
|
9449
|
+
if (maxBytes !== undefined && !agentEnvelopeFits(command, page.data, page.options)) {
|
|
9450
|
+
page.options.warnings = [
|
|
9451
|
+
...page.options.warnings ?? [],
|
|
9452
|
+
"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."
|
|
9453
|
+
];
|
|
9298
9454
|
}
|
|
9299
|
-
context.output.writeData(
|
|
9455
|
+
context.output.writeData(page.data, page.options);
|
|
9300
9456
|
break;
|
|
9301
9457
|
}
|
|
9302
9458
|
case "frustrations": {
|
|
@@ -9886,30 +10042,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
9886
10042
|
break;
|
|
9887
10043
|
}
|
|
9888
10044
|
case "feedback": {
|
|
9889
|
-
const note = positionals
|
|
9890
|
-
if (note === "") {
|
|
9891
|
-
throw new CliInputError("<note> is required", `Usage: moda feedback "<note>" [--category=${FEEDBACK_CATEGORIES.join("|")}] [--severity=${FEEDBACK_SEVERITIES.join("|")}] [--conversation-id=ID] [--cluster-id=ID] [--tool-name=NAME] [--run-id=ID]`);
|
|
9892
|
-
}
|
|
9893
|
-
const params = FeedbackSchema.parse({
|
|
9894
|
-
note,
|
|
9895
|
-
...flags.category !== undefined ? { category: flags.category } : {},
|
|
9896
|
-
...flags.severity !== undefined ? { severity: flags.severity } : {},
|
|
9897
|
-
...flags["conversation-id"] !== undefined ? { conversation_id: flags["conversation-id"] } : {},
|
|
9898
|
-
...flags["cluster-id"] !== undefined ? { cluster_id: flags["cluster-id"] } : {},
|
|
9899
|
-
...flags["tool-name"] !== undefined ? { tool_name: flags["tool-name"] } : {},
|
|
9900
|
-
...flags["run-id"] !== undefined ? { run_id: flags["run-id"] } : {}
|
|
9901
|
-
});
|
|
9902
|
-
const refs = {};
|
|
9903
|
-
if (params.conversation_id)
|
|
9904
|
-
refs.conversation_id = params.conversation_id;
|
|
9905
|
-
if (params.cluster_id)
|
|
9906
|
-
refs.cluster_id = params.cluster_id;
|
|
9907
|
-
if (params.tool_name)
|
|
9908
|
-
refs.tool_name = params.tool_name;
|
|
9909
|
-
if (params.run_id)
|
|
9910
|
-
refs.run_id = params.run_id;
|
|
9911
|
-
const category = params.category ?? "other";
|
|
9912
|
-
const severity = params.severity ?? "low";
|
|
10045
|
+
const { category, severity, note, refs, warnings } = buildFeedbackRequest(positionals, flags, context.bareFlags);
|
|
9913
10046
|
const data = await callDataAPI("/feedback", {
|
|
9914
10047
|
method: "POST",
|
|
9915
10048
|
body: JSON.stringify({
|
|
@@ -9918,11 +10051,14 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
9918
10051
|
source: "cli",
|
|
9919
10052
|
agent: detectCallerAgent(process.env),
|
|
9920
10053
|
cli_version: VERSION,
|
|
9921
|
-
note
|
|
10054
|
+
note,
|
|
9922
10055
|
...Object.keys(refs).length > 0 ? { refs } : {}
|
|
9923
10056
|
})
|
|
9924
10057
|
}, { retries: 0 });
|
|
9925
|
-
context.output.writeData({ submitted: true, category, severity, ...asRecord(data) ?? {} }, {
|
|
10058
|
+
context.output.writeData({ submitted: true, category, severity, ...asRecord(data) ?? {} }, {
|
|
10059
|
+
summary: "Feedback submitted. Thank you for helping us improve!",
|
|
10060
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
10061
|
+
});
|
|
9926
10062
|
break;
|
|
9927
10063
|
}
|
|
9928
10064
|
default:
|
|
@@ -10027,7 +10163,7 @@ var commandRegistry = createCommandRegistry([
|
|
|
10027
10163
|
resetApiRequestCountBeforeRun: true,
|
|
10028
10164
|
telemetry: "result",
|
|
10029
10165
|
handler: async (context) => {
|
|
10030
|
-
const { runInit } = await import("./index-
|
|
10166
|
+
const { runInit } = await import("./index-pdg9t77n.js");
|
|
10031
10167
|
if (context.outputMode === "agent-stream") {
|
|
10032
10168
|
context.output.writeEvent({
|
|
10033
10169
|
event: "started",
|
|
@@ -10068,7 +10204,7 @@ var commandRegistry = createCommandRegistry([
|
|
|
10068
10204
|
resetApiRequestCountBeforeRun: true,
|
|
10069
10205
|
telemetry: "result-and-error",
|
|
10070
10206
|
handler: async (context) => {
|
|
10071
|
-
const { runProvision } = await import("./provision-
|
|
10207
|
+
const { runProvision } = await import("./provision-7fhbgk2d.js");
|
|
10072
10208
|
return runProvision(context, {
|
|
10073
10209
|
resumeCommand: buildResumeCommand("provision", context.rawFlags)
|
|
10074
10210
|
});
|
|
@@ -10545,7 +10681,9 @@ var commandRegistry = createCommandRegistry([
|
|
|
10545
10681
|
"moda audit <conversation_id|trace_id>",
|
|
10546
10682
|
"moda audit <trace_id> --kind=trace --json",
|
|
10547
10683
|
"moda audit <conversation_id> --include-raw",
|
|
10548
|
-
"moda audit <conversation_id> --
|
|
10684
|
+
"moda audit <conversation_id> --include-bodies # keep prompt/response bodies (omitted by default; long strings truncated)",
|
|
10685
|
+
"moda audit <conversation_id> --limit=1000 # ingest rows to read (default 200, max 1000); warns when capped",
|
|
10686
|
+
"moda audit <conversation_id> --cursor=<next_cursor> # next page of a capped audit"
|
|
10549
10687
|
],
|
|
10550
10688
|
...dataApiDefaults
|
|
10551
10689
|
}),
|
|
@@ -10769,7 +10907,8 @@ var commandRegistry = createCommandRegistry([
|
|
|
10769
10907
|
description: "Flag wrong/missing data or CLI quirks to the Moda team",
|
|
10770
10908
|
examples: [
|
|
10771
10909
|
'moda feedback "cluster label looks wrong" --category=bad_cluster_label --cluster-id=<node_id>',
|
|
10772
|
-
'moda feedback "search finds nothing for a trace I can open" --category=missing_data --conversation-id=<id>'
|
|
10910
|
+
'moda feedback "search finds nothing for a trace I can open" --category=missing_data --conversation-id=<id>',
|
|
10911
|
+
'moda feedback "trace is about refunds, not this problem" --category=false_positive_attribution --problem-id=<id> --conversation-id=<id>'
|
|
10773
10912
|
],
|
|
10774
10913
|
...dataApiDefaults,
|
|
10775
10914
|
mutability: "write"
|
|
@@ -10972,6 +11111,15 @@ function printCliError(error) {
|
|
|
10972
11111
|
${issues.join(`
|
|
10973
11112
|
`)}
|
|
10974
11113
|
`);
|
|
11114
|
+
} else if (error instanceof ApiError && error.readable) {
|
|
11115
|
+
safeWriteStderr(`${error.message}
|
|
11116
|
+
`);
|
|
11117
|
+
if (error.suggestions?.length) {
|
|
11118
|
+
safeWriteStderr(`Try:
|
|
11119
|
+
${error.suggestions.map((s) => ` ${s}`).join(`
|
|
11120
|
+
`)}
|
|
11121
|
+
`);
|
|
11122
|
+
}
|
|
10975
11123
|
} else if (error instanceof ApiError) {
|
|
10976
11124
|
safeWriteStderr(`API error (HTTP ${error.statusCode}): ${error.responseBody || error.message}
|
|
10977
11125
|
`);
|
|
@@ -42,8 +42,8 @@ import {
|
|
|
42
42
|
writeHarnessAnalyzeResult,
|
|
43
43
|
writeHarnessArtifacts,
|
|
44
44
|
writeHumanProgress
|
|
45
|
-
} from "./cli-
|
|
46
|
-
import"./cli-
|
|
45
|
+
} from "./cli-m3bg9yyh.js";
|
|
46
|
+
import"./cli-bqhs6sk7.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-6xndg2h4.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-m3bg9yyh.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-taf854xx.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-c64d2jrw.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-bqhs6sk7.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-taf854xx.js";
|
|
4
4
|
import {
|
|
5
5
|
loadAuthSession
|
|
6
|
-
} from "./cli-
|
|
6
|
+
} from "./cli-c64d2jrw.js";
|
|
7
7
|
import {
|
|
8
8
|
CliAuthError,
|
|
9
9
|
resolveIngestUrl,
|
|
10
10
|
resolveModaBaseUrl,
|
|
11
11
|
stringOption
|
|
12
|
-
} from "./cli-
|
|
12
|
+
} from "./cli-bqhs6sk7.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.
|
|
3
|
+
"version": "1.49.0",
|
|
4
4
|
"description": "CLI for Moda - AI agent analytics and observability",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"@modelcontextprotocol/sdk": "^1.30.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
+
"@moda/database": "*",
|
|
59
60
|
"@moda/eslint-config": "*",
|
|
60
61
|
"@moda/harness-snapshot": "*",
|
|
61
62
|
"@moda/integration-skills": "*",
|
package/skills/moda-cli/SKILL.md
CHANGED
|
@@ -652,13 +652,23 @@ tool calls, prompt/response bodies, and `gen_ai.*` attributes — use `moda audi
|
|
|
652
652
|
moda audit <conversation_id|trace_id> # auto-detects OTLP trace_id vs trace ID (conversation_id)
|
|
653
653
|
moda audit <trace_id> --kind=trace # force OTLP trace_id lookup
|
|
654
654
|
moda audit <conversation_id> --include-raw # attach verbatim raw_event bodies
|
|
655
|
+
moda audit <conversation_id> --include-bodies # keep prompt/response/attribute bodies
|
|
655
656
|
moda audit <conversation_id> --limit=1000 # ingest rows to read (default 200, max 1000)
|
|
657
|
+
moda audit <conversation_id> --cursor=<next_cursor> # next page of a capped audit
|
|
656
658
|
```
|
|
657
659
|
|
|
658
660
|
`truncated` / `has_more` at the top level say whether the audit is complete.
|
|
659
|
-
A full `--limit` page of ingest rows
|
|
660
|
-
|
|
661
|
-
|
|
661
|
+
A full `--limit` page of ingest rows, the server's ~3 MB response budget
|
|
662
|
+
(`summary.truncated_reason: "byte_limit"`) or a span cap sets both, and a
|
|
663
|
+
warning plus `next_commands` give the follow-up — usually the `--cursor=` page
|
|
664
|
+
from `next_cursor`; for very large traces audit one OTLP trace at a time
|
|
665
|
+
(`summary.trace_ids`, `--kind=trace`). Prompt/response bodies are omitted by
|
|
666
|
+
default (`{"omitted": true, "original_bytes": N}`); `--include-bodies` keeps
|
|
667
|
+
them, cutting strings over 8 KB to `{"truncated": true, "original_bytes": N,
|
|
668
|
+
"preview": …}`. Ingest rows whose stored payload is too large carry
|
|
669
|
+
`payload_capped: true`: their spans keep ids, hierarchy, timing and types but
|
|
670
|
+
no bodies. A trace too large to audit at all fails fast with an
|
|
671
|
+
`audit_too_large` error whose `suggested_commands` list per-trace audits.
|
|
662
672
|
|
|
663
673
|
Returns the raw, NON-deduped span set from the Layer-0 store (`events_raw`), so
|
|
664
674
|
nothing is collapsed. Use it to verify capture completeness: the `summary` gives
|
|
@@ -1186,15 +1196,30 @@ moda feedback "search finds nothing for a trace I can open" \
|
|
|
1186
1196
|
--category=missing_data --conversation-id=<id>
|
|
1187
1197
|
moda feedback "a cancelled call is counted as a tool failure" \
|
|
1188
1198
|
--category=wrong_tool_failure --tool-name=<tool>
|
|
1199
|
+
moda feedback "trace is a refund request, not this problem" \
|
|
1200
|
+
--category=false_positive_attribution --problem-id=<id> --conversation-id=<id>
|
|
1189
1201
|
```
|
|
1190
1202
|
|
|
1191
|
-
- `<note>` is required (max 4000 chars)
|
|
1203
|
+
- `<note>` is required (max 4000 chars), as the argument or `--note=TEXT`.
|
|
1204
|
+
Say what looked wrong and why.
|
|
1192
1205
|
- `--category`: `bad_cluster_label | mismatched_frustration | missing_data |
|
|
1193
|
-
noisy_data | wrong_tool_failure | incorrect_loop | api_quirk |
|
|
1194
|
-
|
|
1206
|
+
noisy_data | wrong_tool_failure | incorrect_loop | api_quirk |
|
|
1207
|
+
false_positive_attribution | misrouted_fix | harness_bug | other`
|
|
1208
|
+
(default `other`). Use `false_positive_attribution` when a trace is
|
|
1209
|
+
attributed to the wrong problem, `misrouted_fix` when a fix targets the
|
|
1210
|
+
wrong problem or file, and `harness_bug` for a bug in the agent harness
|
|
1211
|
+
Moda analysed. An unknown category is still sent, as `other` with
|
|
1212
|
+
`refs.raw_category`, and a warning lists the valid ones.
|
|
1195
1213
|
- `--severity`: `info | low | medium | high` (default `low`).
|
|
1196
1214
|
- Attach ids when you have them: `--conversation-id`, `--cluster-id`,
|
|
1197
|
-
`--tool-name`, `--run-id
|
|
1215
|
+
`--tool-name`, `--run-id`, or any other `--<name>-id` (`--problem-id`,
|
|
1216
|
+
`--fix-id`, `--attribution-id`, `--trace-id`, `--span-id`,
|
|
1217
|
+
`--message-index`); they go into `refs` and let the team reproduce what
|
|
1218
|
+
you saw.
|
|
1219
|
+
- Feedback does not change the problem itself. To act on a misroute directly,
|
|
1220
|
+
use `moda problem-feedback <problem_id> --action=flag_attribution
|
|
1221
|
+
--attribution-id=<uuid> --reason=TEXT`, `moda false-positive <kind> <target>
|
|
1222
|
+
--reason=TEXT`, or `moda fix dismiss <fix_id> --reason=TEXT`.
|
|
1198
1223
|
- Never include secrets or full transcripts in the note.
|
|
1199
1224
|
|
|
1200
1225
|
## Validation — how to know it worked
|