@moda-ai/cli 1.55.24 → 1.55.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-b4tdzy9q.js → agent-nqmha8ck.js} +8 -8
- package/dist/{cli-9hc5bg2r.js → cli-1mbb0ct0.js} +1 -1
- package/dist/{cli-vg88mg20.js → cli-5g1tv80h.js} +5 -5
- package/dist/{cli-rt26sbz7.js → cli-8tfk8qeb.js} +10 -6
- package/dist/{cli-y2ty9vtv.js → cli-b4740tnv.js} +210 -23
- package/dist/{cli-qvg26twp.js → cli-cdrvjva5.js} +11 -8
- package/dist/{cli-7xc585hc.js → cli-d577kq6h.js} +5 -5
- package/dist/{cli-dn91fb56.js → cli-jgfm7w1r.js} +89 -47
- package/dist/{cli-9btx12wp.js → cli-jtyvbd8d.js} +1 -1
- package/dist/{cli-qycsmxev.js → cli-k6q8dbpk.js} +3 -3
- package/dist/{cli-0wr3483h.js → cli-q6f6vrrr.js} +1 -1
- package/dist/{cli-dve50x7p.js → cli-sqsjd08w.js} +784 -725
- package/dist/cli.js +713 -234
- package/dist/{doctor-3r19ry92.js → doctor-w913fj4g.js} +8 -8
- package/dist/{fix-exvxzvtz.js → fix-crkwdkt4.js} +6 -6
- package/dist/{harness-github-actions-atc5bbkj.js → harness-github-actions-fjhvhgpj.js} +5 -5
- package/dist/{harness-vzy7n7ea.js → harness-x3e7d68b.js} +5 -5
- package/dist/{index-e1pf1qz2.js → index-2kf3fnnb.js} +11 -11
- package/dist/{production-intelligence-c8e0xw3n.js → production-intelligence-7n83c701.js} +75 -15
- package/dist/{profile-commands-6e2dgv72.js → profile-commands-ct2t0bfr.js} +3 -3
- package/dist/prompt-source-mcp.js +5 -5
- package/dist/{prompts-cvzt994j.js → prompts-92aqqb2k.js} +6 -6
- package/dist/{provision-q86phh4a.js → provision-gn8raqpt.js} +5 -5
- package/dist/{registry-s2vdsfw8.js → registry-ex162j5y.js} +3 -3
- package/dist/{skills-tadh1jss.js → skills-2mbsj9bt.js} +5 -5
- package/dist/{sync-g8wtecz1.js → sync-kghmmmk5.js} +7 -7
- package/package.json +1 -1
- package/skills/integration/index.json +2 -2
- package/skills/moda-cli/SKILL.md +8 -2
- package/skills/moda-cli/references/dashboard-parity.md +1 -1
- package/skills/moda-cli/references/output-protocol.md +35 -10
- package/skills/moda-cli/references/production-intelligence.md +3 -2
- package/skills/moda-cli/references/prompts.md +6 -2
- package/skills/moda-cli/references/search-and-traces.md +20 -2
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
createCommandContext,
|
|
7
7
|
parseBooleanFlagValue
|
|
8
|
-
} from "./cli-
|
|
8
|
+
} from "./cli-cdrvjva5.js";
|
|
9
9
|
import {
|
|
10
10
|
AuditSchema,
|
|
11
11
|
COMMAND_FLAG_SPECS,
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
assertPositionals,
|
|
30
30
|
buildFeedbackRequest,
|
|
31
31
|
closestMatches,
|
|
32
|
+
describeField,
|
|
32
33
|
describeSpecFlags,
|
|
33
34
|
flagSpecFor,
|
|
34
35
|
isFeedbackRefFlag,
|
|
@@ -43,13 +44,15 @@ import {
|
|
|
43
44
|
specFlagKeys,
|
|
44
45
|
specFlagNames,
|
|
45
46
|
subcommandHelpFlags,
|
|
46
|
-
unknownSignalsSubcommandError
|
|
47
|
-
|
|
47
|
+
unknownSignalsSubcommandError,
|
|
48
|
+
usageLine,
|
|
49
|
+
writeDryRun
|
|
50
|
+
} from "./cli-jgfm7w1r.js";
|
|
48
51
|
import {
|
|
49
52
|
ApiError,
|
|
50
53
|
callControlAPI,
|
|
51
54
|
callDataAPI
|
|
52
|
-
} from "./cli-
|
|
55
|
+
} from "./cli-jtyvbd8d.js";
|
|
53
56
|
import {
|
|
54
57
|
AGENT_ERROR_CODES,
|
|
55
58
|
BATCH_CONCURRENCY,
|
|
@@ -110,7 +113,7 @@ import {
|
|
|
110
113
|
withFrustrationAnchors,
|
|
111
114
|
withToolFailureAnchors,
|
|
112
115
|
withoutDetectionWindows
|
|
113
|
-
} from "./cli-
|
|
116
|
+
} from "./cli-sqsjd08w.js";
|
|
114
117
|
import {
|
|
115
118
|
__require
|
|
116
119
|
} from "./cli-0v6na3yp.js";
|
|
@@ -2249,6 +2252,7 @@ function buildManifest(commands) {
|
|
|
2249
2252
|
"--agent",
|
|
2250
2253
|
"--stream",
|
|
2251
2254
|
"--json",
|
|
2255
|
+
"--pretty",
|
|
2252
2256
|
"--human",
|
|
2253
2257
|
"--profile <name>",
|
|
2254
2258
|
"--quiet",
|
|
@@ -2275,7 +2279,7 @@ function buildManifest(commands) {
|
|
|
2275
2279
|
exit_codes: [
|
|
2276
2280
|
{ code: 0, name: "ok", meaning: "Command completed successfully." },
|
|
2277
2281
|
{ code: 1, name: "error", meaning: "Command, input, API, network, or unexpected failure." },
|
|
2278
|
-
{ code: 3, name: "degraded", meaning: "Degraded result: `moda ask` local fallback." },
|
|
2282
|
+
{ code: 3, name: "degraded", meaning: "Degraded result: `moda ask` local fallback, or `investigate`/`failures` with some sources unavailable (`data.failedSources`)." },
|
|
2279
2283
|
{ code: 4, name: "auth_required", meaning: "Run login_command, then resume." },
|
|
2280
2284
|
{ code: 5, name: "input_required", meaning: "Choose from input_request choices, then resume." },
|
|
2281
2285
|
{ code: 130, name: "cancelled", meaning: "Interrupted or output consumer closed." }
|
|
@@ -2390,6 +2394,8 @@ function event(name, when, terminal, deprecated = false) {
|
|
|
2390
2394
|
function exitCodesForCommand(command) {
|
|
2391
2395
|
if (command === "ask")
|
|
2392
2396
|
return [0, 1, 3, 4, 5, 130];
|
|
2397
|
+
if (command === "investigate" || command === "failures")
|
|
2398
|
+
return [0, 1, 3, 4, 130];
|
|
2393
2399
|
if (command === "init" || command === "harness")
|
|
2394
2400
|
return [0, 1, 4, 5, 130];
|
|
2395
2401
|
return [0, 1, 4, 130];
|
|
@@ -2522,46 +2528,46 @@ function leafManifestFields(leaf, commands) {
|
|
|
2522
2528
|
import { fileURLToPath } from "url";
|
|
2523
2529
|
import { realpathSync } from "fs";
|
|
2524
2530
|
async function runPromptsCommand(...args) {
|
|
2525
|
-
return (await import("./prompts-
|
|
2531
|
+
return (await import("./prompts-92aqqb2k.js")).runPromptsCommand(...args);
|
|
2526
2532
|
}
|
|
2527
2533
|
async function runSkillsCommand(...args) {
|
|
2528
|
-
return (await import("./skills-
|
|
2534
|
+
return (await import("./skills-2mbsj9bt.js")).runSkillsCommand(...args);
|
|
2529
2535
|
}
|
|
2530
2536
|
async function runFixCommand(...args) {
|
|
2531
|
-
return (await import("./fix-
|
|
2537
|
+
return (await import("./fix-crkwdkt4.js")).runFixCommand(...args);
|
|
2532
2538
|
}
|
|
2533
2539
|
async function runFixesCommand(...args) {
|
|
2534
|
-
return (await import("./fix-
|
|
2540
|
+
return (await import("./fix-crkwdkt4.js")).runFixesCommand(...args);
|
|
2535
2541
|
}
|
|
2536
2542
|
async function runHarnessCommand(...args) {
|
|
2537
|
-
return (await import("./harness-
|
|
2543
|
+
return (await import("./harness-x3e7d68b.js")).runHarnessCommand(...args);
|
|
2538
2544
|
}
|
|
2539
2545
|
async function runSyncCommand(...args) {
|
|
2540
|
-
return (await import("./sync-
|
|
2546
|
+
return (await import("./sync-kghmmmk5.js")).runSyncCommand(...args);
|
|
2541
2547
|
}
|
|
2542
2548
|
async function runRegistryCommand(...args) {
|
|
2543
|
-
return (await import("./registry-
|
|
2549
|
+
return (await import("./registry-ex162j5y.js")).runRegistryCommand(...args);
|
|
2544
2550
|
}
|
|
2545
2551
|
async function runDoctorCommand(...args) {
|
|
2546
|
-
return (await import("./doctor-
|
|
2552
|
+
return (await import("./doctor-w913fj4g.js")).runDoctorCommand(...args);
|
|
2547
2553
|
}
|
|
2548
2554
|
async function runStatusCommand(...args) {
|
|
2549
|
-
return (await import("./doctor-
|
|
2555
|
+
return (await import("./doctor-w913fj4g.js")).runStatusCommand(...args);
|
|
2550
2556
|
}
|
|
2551
2557
|
async function runAuthCommand(...args) {
|
|
2552
|
-
return (await import("./profile-commands-
|
|
2558
|
+
return (await import("./profile-commands-ct2t0bfr.js")).runAuthCommand(...args);
|
|
2553
2559
|
}
|
|
2554
2560
|
async function runConfigCommand(...args) {
|
|
2555
|
-
return (await import("./profile-commands-
|
|
2561
|
+
return (await import("./profile-commands-ct2t0bfr.js")).runConfigCommand(...args);
|
|
2556
2562
|
}
|
|
2557
2563
|
async function runProfilesCommand(...args) {
|
|
2558
|
-
return (await import("./profile-commands-
|
|
2564
|
+
return (await import("./profile-commands-ct2t0bfr.js")).runProfilesCommand(...args);
|
|
2559
2565
|
}
|
|
2560
2566
|
async function runAgentCommand(...args) {
|
|
2561
|
-
return (await import("./agent-
|
|
2567
|
+
return (await import("./agent-nqmha8ck.js")).runAgentCommand(...args);
|
|
2562
2568
|
}
|
|
2563
2569
|
async function runProductionCommand(...args) {
|
|
2564
|
-
return (await import("./production-intelligence-
|
|
2570
|
+
return (await import("./production-intelligence-7n83c701.js")).runProductionCommand(...args);
|
|
2565
2571
|
}
|
|
2566
2572
|
function parseArgs(argv) {
|
|
2567
2573
|
const args = argv.slice(2);
|
|
@@ -2575,10 +2581,10 @@ function parseArgs(argv) {
|
|
|
2575
2581
|
let endOfOptions = false;
|
|
2576
2582
|
while (i < args.length) {
|
|
2577
2583
|
const arg = args[i];
|
|
2578
|
-
if (!endOfOptions && arg === "--" && command
|
|
2584
|
+
if (!endOfOptions && arg === "--" && command === "") {} else if (!endOfOptions && arg === "--") {
|
|
2579
2585
|
endOfOptions = true;
|
|
2580
2586
|
} else if (!endOfOptions && arg.startsWith("--")) {
|
|
2581
|
-
i = parseFlag(args, i, flags, command === "", bareFlags,
|
|
2587
|
+
i = parseFlag(args, i, flags, command === "", bareFlags, command, positional);
|
|
2582
2588
|
} else if (!endOfOptions && command !== "" && /^-[A-Za-z]/.test(arg)) {
|
|
2583
2589
|
shortFlags.push(arg);
|
|
2584
2590
|
} else if (command === "") {
|
|
@@ -2655,7 +2661,8 @@ var RAW_BOOLEAN_FLAGS = new Set([
|
|
|
2655
2661
|
"tui",
|
|
2656
2662
|
"no-tui",
|
|
2657
2663
|
"no-telemetry",
|
|
2658
|
-
"no-update-check"
|
|
2664
|
+
"no-update-check",
|
|
2665
|
+
"pretty"
|
|
2659
2666
|
]);
|
|
2660
2667
|
var GLOBAL_BOOLEAN_FLAGS = new Set([
|
|
2661
2668
|
"agent",
|
|
@@ -2671,7 +2678,8 @@ var GLOBAL_BOOLEAN_FLAGS = new Set([
|
|
|
2671
2678
|
"dry-run",
|
|
2672
2679
|
"stream",
|
|
2673
2680
|
"tui",
|
|
2674
|
-
"no-tui"
|
|
2681
|
+
"no-tui",
|
|
2682
|
+
"pretty"
|
|
2675
2683
|
]);
|
|
2676
2684
|
var BOOLEAN_WORDS = new Set(["true", "false", "yes", "no", "on", "off"]);
|
|
2677
2685
|
var META_SWITCHES = new Set([
|
|
@@ -2685,14 +2693,64 @@ var META_SWITCHES = new Set([
|
|
|
2685
2693
|
"tui",
|
|
2686
2694
|
"no-tui",
|
|
2687
2695
|
"no-telemetry",
|
|
2688
|
-
"no-update-check"
|
|
2696
|
+
"no-update-check",
|
|
2697
|
+
"pretty"
|
|
2689
2698
|
]);
|
|
2690
2699
|
var AGENT_VALUE_COMMANDS = new Set(["harness", "sync"]);
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
}
|
|
2694
|
-
function
|
|
2695
|
-
const
|
|
2700
|
+
function canonicalFlagKey(key) {
|
|
2701
|
+
return key.replace(/_/g, "-");
|
|
2702
|
+
}
|
|
2703
|
+
function canonicalizeFlagKeys(flags, bareFlags) {
|
|
2704
|
+
const entries = Object.entries(flags);
|
|
2705
|
+
for (const [key] of entries)
|
|
2706
|
+
delete flags[key];
|
|
2707
|
+
for (const [key, value] of entries)
|
|
2708
|
+
flags[canonicalFlagKey(key)] = value;
|
|
2709
|
+
if (bareFlags) {
|
|
2710
|
+
const canonical = [...new Set(bareFlags.map(canonicalFlagKey))];
|
|
2711
|
+
bareFlags.splice(0, bareFlags.length, ...canonical);
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
function parseHandlersFor(command, verb) {
|
|
2715
|
+
if (command === "review")
|
|
2716
|
+
return leafHandlers(leafFor("review", undefined));
|
|
2717
|
+
if (CANONICAL_NOUNS.includes(command) && verb !== undefined) {
|
|
2718
|
+
const leaf = leafFor(command, verb);
|
|
2719
|
+
if (leaf)
|
|
2720
|
+
return leaf.path === "problems unexplained" ? ["problems"] : leafHandlers(leaf);
|
|
2721
|
+
}
|
|
2722
|
+
return [commandRegistry.find(command)?.name ?? command];
|
|
2723
|
+
}
|
|
2724
|
+
function specsOf(name) {
|
|
2725
|
+
const spec = flagSpecFor(name);
|
|
2726
|
+
return spec ? [spec, ...Object.values(spec.subcommands ?? {})] : [];
|
|
2727
|
+
}
|
|
2728
|
+
var switchFlagCache = new Map;
|
|
2729
|
+
function commandSwitchFlags(command, verb) {
|
|
2730
|
+
const handlers = parseHandlersFor(command, verb);
|
|
2731
|
+
const cacheKey = handlers.join(",");
|
|
2732
|
+
const cached = switchFlagCache.get(cacheKey);
|
|
2733
|
+
if (cached)
|
|
2734
|
+
return cached;
|
|
2735
|
+
const booleans = new Set;
|
|
2736
|
+
const valued = new Set;
|
|
2737
|
+
for (const handler of handlers) {
|
|
2738
|
+
for (const spec of specsOf(handler)) {
|
|
2739
|
+
for (const flag of describeSpecFlags(spec)) {
|
|
2740
|
+
(flag.type === "boolean" ? booleans : valued).add(flag.name.slice(2));
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
const switches = new Set([...booleans].filter((name) => !valued.has(name)));
|
|
2745
|
+
switchFlagCache.set(cacheKey, switches);
|
|
2746
|
+
return switches;
|
|
2747
|
+
}
|
|
2748
|
+
function takesIdPositional(command, verb) {
|
|
2749
|
+
return parseHandlersFor(command, verb).some((handler) => specsOf(handler).some((spec) => (spec.positionals ?? []).some((positional) => !positional.variadic)));
|
|
2750
|
+
}
|
|
2751
|
+
function parseFlag(args, index, flags, beforeCommand, bareFlags, command = "", verb) {
|
|
2752
|
+
const set = (rawKey, value, bare) => {
|
|
2753
|
+
const key2 = canonicalFlagKey(rawKey);
|
|
2696
2754
|
flags[key2] = value;
|
|
2697
2755
|
const at = bareFlags.indexOf(key2);
|
|
2698
2756
|
if (at !== -1)
|
|
@@ -2708,17 +2766,19 @@ function parseFlag(args, index, flags, beforeCommand, bareFlags, command = "") {
|
|
|
2708
2766
|
}
|
|
2709
2767
|
const key = arg.slice(2);
|
|
2710
2768
|
const next = args[index + 1];
|
|
2711
|
-
if (beforeCommand && GLOBAL_BOOLEAN_FLAGS.has(key)) {
|
|
2769
|
+
if (beforeCommand && GLOBAL_BOOLEAN_FLAGS.has(canonicalFlagKey(key))) {
|
|
2712
2770
|
set(key, "true", true);
|
|
2713
2771
|
return index;
|
|
2714
2772
|
}
|
|
2715
|
-
const switchName = key
|
|
2716
|
-
|
|
2773
|
+
const switchName = canonicalFlagKey(key);
|
|
2774
|
+
const parseCommand = parseCommandFor(command, verb);
|
|
2775
|
+
if (META_SWITCHES.has(switchName) || switchName === "agent" && !AGENT_VALUE_COMMANDS.has(parseCommand)) {
|
|
2717
2776
|
set(key, "true", true);
|
|
2718
2777
|
return index;
|
|
2719
2778
|
}
|
|
2720
|
-
const
|
|
2721
|
-
const isSwitch = RAW_BOOLEAN_FLAGS.has(switchName) ||
|
|
2779
|
+
const schemaSwitch = !beforeCommand && commandSwitchFlags(command, verb).has(switchName);
|
|
2780
|
+
const isSwitch = RAW_BOOLEAN_FLAGS.has(switchName) || schemaSwitch;
|
|
2781
|
+
const idFollows = schemaSwitch && takesIdPositional(command, verb);
|
|
2722
2782
|
const takesNext = BOOLEAN_WORDS.has(next?.toLowerCase() ?? "") || !idFollows && (next === "0" || next === "1");
|
|
2723
2783
|
if (!beforeCommand && isSwitch && !takesNext) {
|
|
2724
2784
|
set(key, "true", true);
|
|
@@ -2750,9 +2810,32 @@ function flagsToArgs(flags, positionalKey, positionalValue) {
|
|
|
2750
2810
|
result[positionalKey] = positionalValue;
|
|
2751
2811
|
}
|
|
2752
2812
|
for (const [key, value] of Object.entries(flags)) {
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2813
|
+
result[key.replace(/-/g, "_")] = value;
|
|
2814
|
+
}
|
|
2815
|
+
return result;
|
|
2816
|
+
}
|
|
2817
|
+
var DECIMAL_TEXT = /^[+-]?(?:\d+\.?\d*|\.\d+)$/;
|
|
2818
|
+
function coerceNumericFlags(schema, args, command) {
|
|
2819
|
+
const shape = schemaShape(schema);
|
|
2820
|
+
const result = { ...args };
|
|
2821
|
+
for (const [key, value] of Object.entries(args)) {
|
|
2822
|
+
if (typeof value !== "string" || !Object.hasOwn(shape, key))
|
|
2823
|
+
continue;
|
|
2824
|
+
const info = describeField(shape[key]);
|
|
2825
|
+
if (info.type !== "integer" && info.type !== "number")
|
|
2826
|
+
continue;
|
|
2827
|
+
const text = value.trim();
|
|
2828
|
+
if (!DECIMAL_TEXT.test(text)) {
|
|
2829
|
+
const flag = kebabFlag(key);
|
|
2830
|
+
const kind = info.type === "integer" ? "a whole number" : "a number";
|
|
2831
|
+
const range = info.min !== undefined && info.max !== undefined ? ` from ${info.min} to ${info.max}` : "";
|
|
2832
|
+
throw new CliInputError(`${flag} must be ${kind}${range} (got '${value}'), so nothing was sent.`, `Pass digits only, e.g. ${flag}=${info.min ?? 1}. See '${canonicalCommandText(`moda ${command}`)} --help'.`, `bad_number:${flag.slice(2)}`, {
|
|
2833
|
+
field: flag,
|
|
2834
|
+
...info.min !== undefined || info.max !== undefined ? { range: { ...info.min !== undefined ? { min: info.min } : {}, ...info.max !== undefined ? { max: info.max } : {} } } : {},
|
|
2835
|
+
suggestedCommands: [`${canonicalCommandText(`moda ${command}`)} --help`]
|
|
2836
|
+
});
|
|
2837
|
+
}
|
|
2838
|
+
result[key] = Number(text);
|
|
2756
2839
|
}
|
|
2757
2840
|
return result;
|
|
2758
2841
|
}
|
|
@@ -2820,6 +2903,7 @@ Silent aliases: conversations = traces list, cluster-conversations = cluster-tra
|
|
|
2820
2903
|
|
|
2821
2904
|
Output: piped stdout, CI or an agent env (CLAUDECODE/CURSOR/MODA_AGENT) => agent JSON; a terminal => prose.
|
|
2822
2905
|
--json | --agent [--stream] | --human Force an output mode (or MODA_FORMAT=human|json|agent|agent-stream)
|
|
2906
|
+
--json is compact when piped; --pretty indents it (indented by default on a terminal)
|
|
2823
2907
|
--profile=NAME --quiet --no-update-check --version --json-schemas
|
|
2824
2908
|
Exit codes: 0 ok, 1 error (errors[0].code says which), 3 degraded, 4 auth required, 5 input required, 130 cancelled.
|
|
2825
2909
|
A command rejects any flag it does not take; the error lists errors[0].allowed and errors[0].did_you_mean.`);
|
|
@@ -3082,8 +3166,27 @@ function legacyNamesHelpLine() {
|
|
|
3082
3166
|
return lines.join(`
|
|
3083
3167
|
`);
|
|
3084
3168
|
}
|
|
3085
|
-
|
|
3086
|
-
|
|
3169
|
+
var ENRICH_CONCURRENCY = BATCH_CONCURRENCY;
|
|
3170
|
+
function enrichedRowBytes(row) {
|
|
3171
|
+
const window = asRecord(row.window);
|
|
3172
|
+
if (window && !("error" in window)) {
|
|
3173
|
+
const { conversation: _conversation, ...rest } = row;
|
|
3174
|
+
return jsonByteLength(rest);
|
|
3175
|
+
}
|
|
3176
|
+
return jsonByteLength(row);
|
|
3177
|
+
}
|
|
3178
|
+
async function enrichWithinBudget(rows, enrich, budgetBytes, skipKey) {
|
|
3179
|
+
let spent = 0;
|
|
3180
|
+
const enriched = await mapWithConcurrency(rows, ENRICH_CONCURRENCY, async (row) => {
|
|
3181
|
+
const out = await enrich(row);
|
|
3182
|
+
if (budgetBytes !== undefined)
|
|
3183
|
+
spent += enrichedRowBytes(out);
|
|
3184
|
+
return out;
|
|
3185
|
+
}, budgetBytes === undefined ? {} : { shouldStart: () => spent <= budgetBytes });
|
|
3186
|
+
return [...enriched, ...rows.slice(enriched.length).map((row) => ({ ...row, [skipKey]: "agent_output_budget" }))];
|
|
3187
|
+
}
|
|
3188
|
+
async function attachWindows(rows, windowSize, budgetBytes) {
|
|
3189
|
+
return enrichWithinBudget(rows, async (row) => {
|
|
3087
3190
|
const anchor = asRecord(row.anchor);
|
|
3088
3191
|
if (!anchor || anchor.no_anchor === true) {
|
|
3089
3192
|
return { ...row, window: null };
|
|
@@ -3112,7 +3215,7 @@ async function attachWindows(rows, windowSize) {
|
|
|
3112
3215
|
const message = err instanceof Error ? err.message : String(err);
|
|
3113
3216
|
return { ...row, window: { error: message } };
|
|
3114
3217
|
}
|
|
3115
|
-
})
|
|
3218
|
+
}, budgetBytes, "window_skipped");
|
|
3116
3219
|
}
|
|
3117
3220
|
var TOOL_FAILURE_DETAIL_DEFAULT_LIMIT = 5;
|
|
3118
3221
|
var TOOL_FAILURE_AGENT_SUBTYPES = 20;
|
|
@@ -3121,7 +3224,8 @@ async function printWithOptionalWindows(enriched, listKey, includeWindow, window
|
|
|
3121
3224
|
let data = enriched;
|
|
3122
3225
|
if (includeWindow) {
|
|
3123
3226
|
const list = Array.isArray(enriched[listKey]) ? enriched[listKey] : [];
|
|
3124
|
-
|
|
3227
|
+
const agentMode = output.mode === "agent" || output.mode === "agent-stream";
|
|
3228
|
+
data = { ...enriched, [listKey]: await attachWindows(list, windowSize, agentMode ? agentMaxOutputBytes() : undefined) };
|
|
3125
3229
|
}
|
|
3126
3230
|
if (finalize)
|
|
3127
3231
|
data = finalize(data, allWarnings);
|
|
@@ -3179,8 +3283,10 @@ function auditCapStatus(data, limit) {
|
|
|
3179
3283
|
const nextCursor = typeof cursorValue === "string" && cursorValue ? cursorValue : null;
|
|
3180
3284
|
return { rowsCapped, spansCapped, hasMoreKnown: hasMore, rows, spans, reason, nextCursor };
|
|
3181
3285
|
}
|
|
3182
|
-
var AUDIT_MIN_PAGE_BYTES = 32768;
|
|
3183
3286
|
var AUDIT_AGENT_RESERVE_BYTES = 16384;
|
|
3287
|
+
function auditMaxBytes(budgetBytes) {
|
|
3288
|
+
return Math.max(1, budgetBytes - Math.min(AUDIT_AGENT_RESERVE_BYTES, Math.floor(budgetBytes / 4)));
|
|
3289
|
+
}
|
|
3184
3290
|
async function fetchAudit(id, query2, maxBytes) {
|
|
3185
3291
|
const pageQuery = new URLSearchParams(query2);
|
|
3186
3292
|
if (maxBytes !== undefined)
|
|
@@ -3188,16 +3294,22 @@ async function fetchAudit(id, query2, maxBytes) {
|
|
|
3188
3294
|
const queryString = pageQuery.toString() ? `?${pageQuery.toString()}` : "";
|
|
3189
3295
|
return callDataAPI(`/audit/${encodeURIComponent(id)}${queryString}`);
|
|
3190
3296
|
}
|
|
3191
|
-
function auditOutput(command, params, data) {
|
|
3192
|
-
const capped = auditCapStatus(data, params.limit ?? AUDIT_DEFAULT_LIMIT);
|
|
3297
|
+
function auditOutput(command, params, data, keep) {
|
|
3193
3298
|
const auditRecord = asRecord(data);
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3299
|
+
const pageSpans = Array.isArray(auditRecord?.spans) ? auditRecord.spans : [];
|
|
3300
|
+
const offset = params.span_offset ?? 0;
|
|
3301
|
+
const end = Math.max(offset, keep === undefined ? pageSpans.length : Math.min(pageSpans.length, offset + keep));
|
|
3302
|
+
const sliced = offset > 0 || end < pageSpans.length;
|
|
3303
|
+
const shown = Math.max(0, Math.min(end, pageSpans.length) - offset);
|
|
3304
|
+
const partial = end < pageSpans.length;
|
|
3305
|
+
const record = auditRecord && sliced ? {
|
|
3306
|
+
...auditRecord,
|
|
3307
|
+
spans: pageSpans.slice(offset, end),
|
|
3308
|
+
span_offset: offset,
|
|
3309
|
+
page_spans: pageSpans.length,
|
|
3310
|
+
...partial ? { next_span_offset: end } : {}
|
|
3311
|
+
} : auditRecord;
|
|
3197
3312
|
const limit = params.limit ?? AUDIT_DEFAULT_LIMIT;
|
|
3198
|
-
const perTrace = "audit one OTLP trace at a time with `moda traces spans <trace_id> --kind=trace` (ids in summary.trace_ids)";
|
|
3199
|
-
const warnings = [];
|
|
3200
|
-
const nextCommands = [];
|
|
3201
3313
|
const auditFlags = (extra) => [
|
|
3202
3314
|
`${canonicalCommandText(`moda ${command}`)} ${shellArg(params.id)}`,
|
|
3203
3315
|
params.kind && params.kind !== "auto" ? `--kind=${params.kind}` : "",
|
|
@@ -3205,6 +3317,52 @@ function auditOutput(command, params, data) {
|
|
|
3205
3317
|
params.include_bodies === true ? "--include-bodies" : "",
|
|
3206
3318
|
...extra
|
|
3207
3319
|
].filter(Boolean).join(" ");
|
|
3320
|
+
const sliceWarnings = [];
|
|
3321
|
+
if (offset > 0 && offset >= pageSpans.length) {
|
|
3322
|
+
sliceWarnings.push(`--span-offset=${offset} is past the ${pageSpans.length} span(s) of this page, so no spans are shown.`);
|
|
3323
|
+
} else if (sliced && !partial) {
|
|
3324
|
+
sliceWarnings.push(`Showing spans ${offset}-${end - 1}, the last part of the ${pageSpans.length} span(s) in this page.`);
|
|
3325
|
+
}
|
|
3326
|
+
if (partial && record) {
|
|
3327
|
+
const continueWith = auditFlags([
|
|
3328
|
+
params.limit ? `--limit=${params.limit}` : "",
|
|
3329
|
+
params.cursor ? `--cursor=${params.cursor}` : "",
|
|
3330
|
+
params.page_bytes ? `--page-bytes=${params.page_bytes}` : "",
|
|
3331
|
+
`--span-offset=${end}`
|
|
3332
|
+
]);
|
|
3333
|
+
const summary = asRecord(record.summary);
|
|
3334
|
+
return {
|
|
3335
|
+
data: {
|
|
3336
|
+
...record,
|
|
3337
|
+
truncated: true,
|
|
3338
|
+
has_more: true,
|
|
3339
|
+
next_cursor: null,
|
|
3340
|
+
...summary ? { summary: { ...summary, has_more: true, next_cursor: null } } : {}
|
|
3341
|
+
},
|
|
3342
|
+
options: {
|
|
3343
|
+
warnings: [
|
|
3344
|
+
`Showing spans ${offset}-${end - 1} of the ${pageSpans.length} in this page to stay under the agent output byte limit (--json has no limit). Read the next ${pageSpans.length - end} with: ${continueWith}. next_cursor is withheld until the last spans of this page are read. Orphan/duplicate analysis covers the whole page.`
|
|
3345
|
+
],
|
|
3346
|
+
nextCommands: [{
|
|
3347
|
+
command: continueWith,
|
|
3348
|
+
purpose: `Read spans ${end}-${pageSpans.length - 1} of this page (left out to fit the agent byte limit).`,
|
|
3349
|
+
mutability: "read",
|
|
3350
|
+
requires_approval: false
|
|
3351
|
+
}]
|
|
3352
|
+
}
|
|
3353
|
+
};
|
|
3354
|
+
}
|
|
3355
|
+
const capped = auditCapStatus(data, limit);
|
|
3356
|
+
if (!capped || !record) {
|
|
3357
|
+
return {
|
|
3358
|
+
data: record ? { ...record, truncated: false, has_more: false, next_cursor: null } : data,
|
|
3359
|
+
options: sliceWarnings.length > 0 ? { warnings: sliceWarnings } : {}
|
|
3360
|
+
};
|
|
3361
|
+
}
|
|
3362
|
+
const perTrace = "audit one OTLP trace at a time with `moda traces spans <trace_id> --kind=trace` (ids in summary.trace_ids)";
|
|
3363
|
+
const shownText = sliced ? `${shown} shown here (${pageSpans.length} in this page)` : `${capped.spans} shown`;
|
|
3364
|
+
const warnings = [...sliceWarnings];
|
|
3365
|
+
const nextCommands = [];
|
|
3208
3366
|
if (capped.nextCursor) {
|
|
3209
3367
|
nextCommands.push({
|
|
3210
3368
|
command: auditFlags([params.limit ? `--limit=${params.limit}` : "", `--cursor=${capped.nextCursor}`]),
|
|
@@ -3214,20 +3372,20 @@ function auditOutput(command, params, data) {
|
|
|
3214
3372
|
});
|
|
3215
3373
|
}
|
|
3216
3374
|
if (capped.reason === "byte_limit") {
|
|
3217
|
-
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 ${
|
|
3375
|
+
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 ${shownText}. 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.`);
|
|
3218
3376
|
} else if (capped.reason === "span_limit" && capped.nextCursor) {
|
|
3219
|
-
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 ${
|
|
3377
|
+
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 ${shownText}. Continue with the next_cursor page. Orphan/duplicate analysis covers this page only.`);
|
|
3220
3378
|
} else if (capped.rowsCapped) {
|
|
3221
3379
|
const bigger = Math.min(AUDIT_MAX_LIMIT, limit * 5);
|
|
3222
|
-
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 ${
|
|
3223
|
-
if (limit < AUDIT_MAX_LIMIT && !params.cursor) {
|
|
3380
|
+
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 ${shownText}. ` + (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)}.`));
|
|
3381
|
+
if (limit < AUDIT_MAX_LIMIT && !params.cursor && !sliced) {
|
|
3224
3382
|
nextCommands.push({ command: auditFlags([`--limit=${bigger}`]), purpose: "Read more ingest rows of this trace.", mutability: "read", requires_approval: false });
|
|
3225
3383
|
}
|
|
3226
3384
|
} else {
|
|
3227
3385
|
warnings.push(`Spans were capped: the server returned ${capped.spans} span(s) and marked the response truncated. ${perTrace[0].toUpperCase()}${perTrace.slice(1)}.`);
|
|
3228
3386
|
}
|
|
3229
3387
|
return {
|
|
3230
|
-
data: { ...
|
|
3388
|
+
data: { ...record, truncated: true, has_more: capped.rowsCapped, next_cursor: capped.nextCursor },
|
|
3231
3389
|
options: { warnings, ...nextCommands.length > 0 ? { nextCommands } : {} }
|
|
3232
3390
|
};
|
|
3233
3391
|
}
|
|
@@ -3416,8 +3574,8 @@ async function runTailCommand(params, context) {
|
|
|
3416
3574
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
3417
3575
|
}
|
|
3418
3576
|
}
|
|
3419
|
-
async function attachWorldState(rows) {
|
|
3420
|
-
return
|
|
3577
|
+
async function attachWorldState(rows, budgetBytes) {
|
|
3578
|
+
return enrichWithinBudget(rows, async (row) => {
|
|
3421
3579
|
const conv_id = asString(row.conversation_id) ?? "";
|
|
3422
3580
|
if (!conv_id) {
|
|
3423
3581
|
return { ...row, world_state: null };
|
|
@@ -3429,7 +3587,7 @@ async function attachWorldState(rows) {
|
|
|
3429
3587
|
const message = err instanceof Error ? err.message : String(err);
|
|
3430
3588
|
return { ...row, world_state: { error: message } };
|
|
3431
3589
|
}
|
|
3432
|
-
})
|
|
3590
|
+
}, budgetBytes, "world_state_skipped");
|
|
3433
3591
|
}
|
|
3434
3592
|
function recordSearchTelemetry(context, data) {
|
|
3435
3593
|
const record = asRecord(data);
|
|
@@ -3539,7 +3697,7 @@ function parseCommandFlags(schema, args, command, options = {}) {
|
|
|
3539
3697
|
]);
|
|
3540
3698
|
const unknown = Object.keys(args).filter((key) => !known.has(key));
|
|
3541
3699
|
if (unknown.includes("dry_run")) {
|
|
3542
|
-
throw new CliInputError(`'moda ${command}' has no dry-run mode, so nothing was sent.`, `Remove --dry-run
|
|
3700
|
+
throw new CliInputError(`'moda ${command}' has no dry-run mode (it does not write), so nothing was sent.`, `Remove --dry-run. Every write takes --dry-run: problems feedback|reopen|link, review, feedback, signals <write subcommands>, sync, registry push, harness sync/delete/register, prompts sync/decide, skills sync/policy.`, "unsupported_dry_run");
|
|
3543
3701
|
}
|
|
3544
3702
|
if (unknown.length > 0) {
|
|
3545
3703
|
const flagKeys = [
|
|
@@ -3575,8 +3733,9 @@ function parseCommandFlags(schema, args, command, options = {}) {
|
|
|
3575
3733
|
suggestedCommands: [helpCommand]
|
|
3576
3734
|
});
|
|
3577
3735
|
}
|
|
3736
|
+
const typed = coerceNumericFlags(schema, args, command);
|
|
3578
3737
|
try {
|
|
3579
|
-
return schema.parse(
|
|
3738
|
+
return schema.parse(typed);
|
|
3580
3739
|
} catch (error) {
|
|
3581
3740
|
if (error instanceof z.ZodError)
|
|
3582
3741
|
attachFlagDetails(error, schema, command);
|
|
@@ -3608,13 +3767,6 @@ function specParseOptions(spec) {
|
|
|
3608
3767
|
...spec.flagAliases ? { flagAliases: spec.flagAliases } : {}
|
|
3609
3768
|
};
|
|
3610
3769
|
}
|
|
3611
|
-
function withStringIdFlags(args, flags) {
|
|
3612
|
-
for (const flag of ["user-id", "conversation-id"]) {
|
|
3613
|
-
if (flags[flag] !== undefined)
|
|
3614
|
-
args[flag.replace(/-/g, "_")] = flags[flag];
|
|
3615
|
-
}
|
|
3616
|
-
return args;
|
|
3617
|
-
}
|
|
3618
3770
|
function shellArg(value) {
|
|
3619
3771
|
if (/^[A-Za-z0-9_.,:@%+\/=-]+$/.test(value))
|
|
3620
3772
|
return value;
|
|
@@ -3722,8 +3874,7 @@ async function searchFallbackToTraces(params) {
|
|
|
3722
3874
|
return;
|
|
3723
3875
|
const pagination = asRecord(traces.pagination);
|
|
3724
3876
|
const total = asNumber(pagination?.total) ?? rows.length;
|
|
3725
|
-
const
|
|
3726
|
-
const hasMore = pagination?.has_more === true || (asNumber(pagination?.total) ?? 0) > nextOffset;
|
|
3877
|
+
const serverHasMore = pagination?.has_more === true;
|
|
3727
3878
|
const traceMode = asString(traces.search_mode) ?? "keyword";
|
|
3728
3879
|
const literal = traceMode === "keyword";
|
|
3729
3880
|
if (params.mode === "keyword" && !literal) {
|
|
@@ -3745,33 +3896,46 @@ async function searchFallbackToTraces(params) {
|
|
|
3745
3896
|
}));
|
|
3746
3897
|
const first = asString(rows[0].conversation_id);
|
|
3747
3898
|
const describe = literal ? "trace-level keyword matches (each trace contains the words)" : `trace-level ${traceMode}-ranked related traces (they may not contain the exact words; fallback.total is the candidate pool, not a match count)`;
|
|
3748
|
-
const
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3899
|
+
const page = (kept) => {
|
|
3900
|
+
const omitted = rows.length - kept;
|
|
3901
|
+
const nextOffset = offset + kept;
|
|
3902
|
+
const hasMore = omitted > 0 || serverHasMore || total > nextOffset;
|
|
3903
|
+
const nextCommands = [];
|
|
3904
|
+
if (first) {
|
|
3905
|
+
nextCommands.push({ command: `moda traces spans ${shellArg(first)}`, purpose: "Open the top trace to locate the relevant message.", mutability: "read", requires_approval: false });
|
|
3906
|
+
}
|
|
3907
|
+
if (hasMore) {
|
|
3908
|
+
nextCommands.push({
|
|
3909
|
+
command: `${tracesCommand} --offset=${nextOffset}`,
|
|
3910
|
+
purpose: omitted > 0 ? `Read the ${omitted} trace-level result(s) left out to fit the agent byte limit, and the ones after them.` : "Page through further trace-level results.",
|
|
3911
|
+
mutability: "read",
|
|
3912
|
+
requires_approval: false
|
|
3913
|
+
});
|
|
3914
|
+
}
|
|
3915
|
+
return {
|
|
3916
|
+
returned: kept,
|
|
3917
|
+
offset,
|
|
3918
|
+
hasMore,
|
|
3919
|
+
data: {
|
|
3920
|
+
results: results.slice(0, kept),
|
|
3921
|
+
fallback: {
|
|
3922
|
+
source: "traces_search",
|
|
3923
|
+
reason: "message_index_empty",
|
|
3924
|
+
grain: "trace",
|
|
3925
|
+
search_mode: traceMode,
|
|
3926
|
+
total,
|
|
3927
|
+
total_kind: literal ? "matches" : "candidate_pool",
|
|
3928
|
+
has_more: hasMore,
|
|
3929
|
+
...offset > 0 ? { offset } : {},
|
|
3930
|
+
...hasMore ? { next_offset: nextOffset } : {},
|
|
3931
|
+
...omitted > 0 ? { omitted_for_byte_limit: omitted } : {}
|
|
3932
|
+
}
|
|
3933
|
+
},
|
|
3934
|
+
warning: `Message-level search returned 0 results, so these are ${describe}, from \`moda traces --search\`. Each result is a whole trace, not a message, with no score or message index. Open one with \`moda traces spans <conversation_id>\` or \`moda traces get <conversation_id>\` to find the matching turn.` + (omitted > 0 ? ` ${kept} of the ${rows.length} trace-level results read are shown, to stay under the agent output byte limit (--json has no limit); the rest start at --offset=${nextOffset}.` : "") + (hasMore ? " More exist: page with the suggested `moda traces --search ... --offset` command." : ""),
|
|
3935
|
+
nextCommands
|
|
3936
|
+
};
|
|
3774
3937
|
};
|
|
3938
|
+
return { kind: "results", rows: rows.length, page };
|
|
3775
3939
|
}
|
|
3776
3940
|
function capProblemsBlocks(data, limit) {
|
|
3777
3941
|
const truncated = {};
|
|
@@ -3822,6 +3986,31 @@ var NO_POSITIONAL_COMMANDS = new Set([
|
|
|
3822
3986
|
"tail",
|
|
3823
3987
|
"problem-impact"
|
|
3824
3988
|
]);
|
|
3989
|
+
function rejectInapplicableFlags(command, params, keys, why) {
|
|
3990
|
+
const given = keys.filter((key) => params[key] !== undefined);
|
|
3991
|
+
if (given.length === 0)
|
|
3992
|
+
return;
|
|
3993
|
+
const names = given.map(kebabFlag);
|
|
3994
|
+
const shown = canonicalCommandText(`moda ${command}`);
|
|
3995
|
+
throw new CliInputError(`${names.join(", ")} ${given.length > 1 ? "do" : "does"} not apply ${why}, so nothing was sent.`, `Drop ${names.join(", ")}, or pick the mode that uses ${given.length > 1 ? "them" : "it"}. See '${shown} --help'.`, `inapplicable_flag:${names[0].slice(2)}`, { field: names[0], suggestedCommands: [`${shown} --help`] });
|
|
3996
|
+
}
|
|
3997
|
+
var BATCH_POSITIONAL_COMMANDS = new Set(["context", "audit", "trace"]);
|
|
3998
|
+
function assertPositionalArity(command, positionals) {
|
|
3999
|
+
const spec = flagSpecFor(command);
|
|
4000
|
+
const expected = spec?.positionals ?? [];
|
|
4001
|
+
if (expected.length === 0 || expected.some((entry) => entry.variadic))
|
|
4002
|
+
return;
|
|
4003
|
+
if (BATCH_POSITIONAL_COMMANDS.has(command))
|
|
4004
|
+
return;
|
|
4005
|
+
if (command === "problem" && positionals[0] === "remainder")
|
|
4006
|
+
return;
|
|
4007
|
+
if (positionals.length <= expected.length)
|
|
4008
|
+
return;
|
|
4009
|
+
const extra = positionals.slice(expected.length);
|
|
4010
|
+
const shown = canonicalCommandText(`moda ${command}`);
|
|
4011
|
+
const takes = expected.map((entry) => `<${entry.name}>`).join(" ");
|
|
4012
|
+
throw new CliInputError(`Unexpected argument${extra.length > 1 ? "s" : ""} '${extra.join(" ")}' for '${shown}', which takes ${takes}, so nothing was sent.`, `Quote a value that has spaces, and pass free text as a flag value (e.g. --reason="..."). Usage: ${canonicalCommandText(usageLine(command, spec))}`, "unexpected_positional", { field: `<${expected[expected.length - 1].name}>`, suggestedCommands: [`${shown} --help`] });
|
|
4013
|
+
}
|
|
3825
4014
|
function assertHandParsedFlags(command, flags, positionals) {
|
|
3826
4015
|
if (command === "signals" && positionals[0]) {
|
|
3827
4016
|
if (SIGNALS_MANAGEMENT_SUBCOMMANDS.has(positionals[0]))
|
|
@@ -3831,6 +4020,7 @@ function assertHandParsedFlags(command, flags, positionals) {
|
|
|
3831
4020
|
if (NO_POSITIONAL_COMMANDS.has(command) && positionals.length > 0) {
|
|
3832
4021
|
throw new CliInputError(`'moda ${command}' takes no arguments, but got '${positionals.join(" ")}'.`, `Nothing was run. See 'moda ${command} --help' for its flags.`);
|
|
3833
4022
|
}
|
|
4023
|
+
assertPositionalArity(command, positionals);
|
|
3834
4024
|
const hand = handParsedFlags(command);
|
|
3835
4025
|
if (!hand)
|
|
3836
4026
|
return;
|
|
@@ -3959,6 +4149,10 @@ async function readContextMetadataOnly(contextPath, query2) {
|
|
|
3959
4149
|
}
|
|
3960
4150
|
var FULL_TRANSCRIPT_DEFAULT_MAX_MESSAGES = 500;
|
|
3961
4151
|
var CONTEXT_RANGE_PAGE_SIZE = 200;
|
|
4152
|
+
var CONTEXT_BUDGET_BYTES_PER_FIRST_PAGE_MESSAGE = 480;
|
|
4153
|
+
function jsonByteLength(value) {
|
|
4154
|
+
return Buffer.byteLength(JSON.stringify(value) ?? "", "utf8");
|
|
4155
|
+
}
|
|
3962
4156
|
var LEGACY_WINDOW_MAX_MESSAGES = 100;
|
|
3963
4157
|
var CONTEXT_MESSAGE_TRUNCATE_CHARS = 500;
|
|
3964
4158
|
var LONE_SURROGATE = /[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g;
|
|
@@ -3996,8 +4190,15 @@ async function readFullTranscript(conversationId, opts = {}) {
|
|
|
3996
4190
|
let nextFrom = null;
|
|
3997
4191
|
let legacy = false;
|
|
3998
4192
|
let cursor = start;
|
|
4193
|
+
let readBytes = 0;
|
|
4194
|
+
const byteBudget = opts.fits ? opts.maxBytes : undefined;
|
|
3999
4195
|
for (;; ) {
|
|
4000
|
-
|
|
4196
|
+
let count = Math.min(CONTEXT_RANGE_PAGE_SIZE, requested - messages.length);
|
|
4197
|
+
if (byteBudget !== undefined) {
|
|
4198
|
+
const average = messages.length > 0 ? readBytes / messages.length : 0;
|
|
4199
|
+
const estimate = average > 0 ? Math.ceil(Math.max(0, byteBudget - readBytes) / average) + 1 : Math.max(1, Math.floor(byteBudget / CONTEXT_BUDGET_BYTES_PER_FIRST_PAGE_MESSAGE));
|
|
4200
|
+
count = Math.max(1, Math.min(count, estimate));
|
|
4201
|
+
}
|
|
4001
4202
|
let page;
|
|
4002
4203
|
try {
|
|
4003
4204
|
page = asRecord(await callDataAPI(`${path}?from=${cursor}&count=${count}`)) ?? {};
|
|
@@ -4015,10 +4216,14 @@ async function readFullTranscript(conversationId, opts = {}) {
|
|
|
4015
4216
|
}
|
|
4016
4217
|
const batch = pageMessages(page);
|
|
4017
4218
|
messages.push(...batch);
|
|
4219
|
+
if (byteBudget !== undefined)
|
|
4220
|
+
readBytes += batch.reduce((sum, message) => sum + jsonByteLength(message) + 1, 0);
|
|
4018
4221
|
const next = asNumber(page.next_from);
|
|
4019
4222
|
nextFrom = page.has_more === true && next !== null && next !== undefined && next > cursor ? next : null;
|
|
4020
4223
|
if (nextFrom === null || batch.length === 0 || messages.length >= requested)
|
|
4021
4224
|
break;
|
|
4225
|
+
if (byteBudget !== undefined && readBytes > byteBudget)
|
|
4226
|
+
break;
|
|
4022
4227
|
cursor = nextFrom;
|
|
4023
4228
|
}
|
|
4024
4229
|
if (legacy) {
|
|
@@ -4115,12 +4320,14 @@ async function readFullTranscript(conversationId, opts = {}) {
|
|
|
4115
4320
|
}
|
|
4116
4321
|
return build(low);
|
|
4117
4322
|
}
|
|
4118
|
-
async function readContextItem(command, params, inputWarnings,
|
|
4323
|
+
async function readContextItem(command, params, inputWarnings, budget) {
|
|
4324
|
+
const fits = budget?.fits;
|
|
4119
4325
|
if (params.all === true) {
|
|
4120
4326
|
const full = await readFullTranscript(params.conversation_id, {
|
|
4121
4327
|
from: params.from,
|
|
4122
4328
|
maxMessages: params.max_messages,
|
|
4123
|
-
fits: fits ? (page) => fits(page.data, { warnings: page.warnings, nextCommands: page.nextCommands }) : undefined
|
|
4329
|
+
fits: fits ? (page) => fits(page.data, { warnings: page.warnings, nextCommands: page.nextCommands }) : undefined,
|
|
4330
|
+
maxBytes: budget?.maxBytes
|
|
4124
4331
|
});
|
|
4125
4332
|
const fullWarnings = [...inputWarnings, ...full.warnings];
|
|
4126
4333
|
return {
|
|
@@ -4154,13 +4361,85 @@ async function readContextItem(command, params, inputWarnings, fits) {
|
|
|
4154
4361
|
warnings.push(`--window=${params.window} is above the maximum of ${CONTEXT_MAX_WINDOW}, so this shows ±${CONTEXT_MAX_WINDOW} messages. For a wider slice run: ${wide}`);
|
|
4155
4362
|
nextCommands.push({ command: wide, purpose: `Read ±${params.window} messages around the anchor.`, mutability: "read", requires_approval: false });
|
|
4156
4363
|
}
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
...
|
|
4364
|
+
const options = (extraWarnings = [], extraNext = []) => {
|
|
4365
|
+
const allWarnings = [...warnings, ...extraWarnings];
|
|
4366
|
+
const allNext = [...nextCommands, ...extraNext];
|
|
4367
|
+
return {
|
|
4368
|
+
...allWarnings.length > 0 ? { warnings: allWarnings } : {},
|
|
4369
|
+
...allNext.length > 0 ? { nextCommands: allNext } : {}
|
|
4370
|
+
};
|
|
4371
|
+
};
|
|
4372
|
+
if (fits && params.metadata_only !== true && !fits(data, options())) {
|
|
4373
|
+
const trimmed = trimContextWindow(params.conversation_id, ctxRecord, params.msg_index, (page, extraWarnings, extraNext) => fits(page, options(extraWarnings, extraNext)));
|
|
4374
|
+
if (trimmed)
|
|
4375
|
+
return { data: trimmed.data, options: options(trimmed.warnings, trimmed.nextCommands) };
|
|
4376
|
+
}
|
|
4377
|
+
return { data, options: options() };
|
|
4378
|
+
}
|
|
4379
|
+
function trimContextWindow(conversationId, record, requestedAnchor, fits) {
|
|
4380
|
+
const ctx = asRecord(record.context);
|
|
4381
|
+
const messages = Array.isArray(ctx?.messages) ? ctx.messages : [];
|
|
4382
|
+
if (!ctx || messages.length <= 1)
|
|
4383
|
+
return;
|
|
4384
|
+
const firstIndex = asNumber(ctx.from_index) ?? asNumber(asRecord(messages[0])?.index) ?? 0;
|
|
4385
|
+
const indexed = messages.map((message, position) => ({
|
|
4386
|
+
message,
|
|
4387
|
+
index: asNumber(asRecord(message)?.index) ?? firstIndex + position
|
|
4388
|
+
}));
|
|
4389
|
+
const center = asNumber(ctx.center_index) ?? requestedAnchor ?? indexed[Math.floor(indexed.length / 2)].index;
|
|
4390
|
+
const requestedFrom = Math.min(asNumber(ctx.from_index) ?? indexed[0].index, indexed[0].index);
|
|
4391
|
+
const requestedTo = Math.max(asNumber(ctx.to_index) ?? indexed[indexed.length - 1].index, indexed[indexed.length - 1].index);
|
|
4392
|
+
const order = [...indexed].sort((left, right) => Math.abs(left.index - center) - Math.abs(right.index - center) || right.index - left.index);
|
|
4393
|
+
const target = shellArg(contextTarget(conversationId));
|
|
4394
|
+
const build = (kept) => {
|
|
4395
|
+
const returned = order.slice(0, kept).sort((left, right) => left.index - right.index);
|
|
4396
|
+
const from = returned[0].index;
|
|
4397
|
+
const to = returned[returned.length - 1].index;
|
|
4398
|
+
const before = from - requestedFrom;
|
|
4399
|
+
const after = requestedTo - to;
|
|
4400
|
+
const nextCommands = [];
|
|
4401
|
+
if (before > 0) {
|
|
4402
|
+
nextCommands.push({
|
|
4403
|
+
command: `moda traces get ${target} --all --from=${requestedFrom} --max-messages=${before}`,
|
|
4404
|
+
purpose: `Read the ${before} message(s) before ${from} that were left out to fit the agent byte limit.`,
|
|
4405
|
+
mutability: "read",
|
|
4406
|
+
requires_approval: false
|
|
4407
|
+
});
|
|
4408
|
+
}
|
|
4409
|
+
if (after > 0) {
|
|
4410
|
+
nextCommands.push({
|
|
4411
|
+
command: `moda traces get ${target} --all --from=${to + 1} --max-messages=${after}`,
|
|
4412
|
+
purpose: `Read the ${after} message(s) after ${to} that were left out to fit the agent byte limit.`,
|
|
4413
|
+
mutability: "read",
|
|
4414
|
+
requires_approval: false
|
|
4415
|
+
});
|
|
4162
4416
|
}
|
|
4417
|
+
const warnings = kept < order.length ? [
|
|
4418
|
+
`Window cut to messages ${from}-${to} around anchor ${center} to stay under the agent output byte limit (--json has no limit); requested ${requestedFrom}-${requestedTo}. Read the rest with: ${nextCommands.map((next) => next.command).join(" and ")}.`
|
|
4419
|
+
] : [];
|
|
4420
|
+
const data = {
|
|
4421
|
+
...record,
|
|
4422
|
+
context: {
|
|
4423
|
+
...ctx,
|
|
4424
|
+
from_index: from,
|
|
4425
|
+
to_index: to,
|
|
4426
|
+
messages: returned.map((entry) => entry.message),
|
|
4427
|
+
...kept < order.length ? { requested_from_index: requestedFrom, requested_to_index: requestedTo, omitted_for_byte_limit: order.length - kept } : {}
|
|
4428
|
+
}
|
|
4429
|
+
};
|
|
4430
|
+
return { data, warnings, nextCommands };
|
|
4163
4431
|
};
|
|
4432
|
+
let low = 1;
|
|
4433
|
+
let high = order.length;
|
|
4434
|
+
while (low < high) {
|
|
4435
|
+
const mid = Math.ceil((low + high) / 2);
|
|
4436
|
+
const page = build(mid);
|
|
4437
|
+
if (fits(page.data, page.warnings, page.nextCommands))
|
|
4438
|
+
low = mid;
|
|
4439
|
+
else
|
|
4440
|
+
high = mid - 1;
|
|
4441
|
+
}
|
|
4442
|
+
return build(low);
|
|
4164
4443
|
}
|
|
4165
4444
|
var AUDIT_USAGE = "Usage: moda traces spans <conversation_id|trace_id> [--kind=auto|trace|conversation] [--include-raw] [--include-bodies] [--limit=N] [--cursor=<next_cursor>]";
|
|
4166
4445
|
var AUDIT_BATCH_USAGE = `Usage: moda traces spans <id> <id> ... | --ids-from=-|<file> [--kind=auto|trace|conversation] [--include-raw] [--include-bodies] [--limit=N] (max ${BATCH_MAX_TARGETS} targets)`;
|
|
@@ -4176,16 +4455,31 @@ async function readAuditItem(command, params, budget) {
|
|
|
4176
4455
|
query2.set("limit", params.limit.toString());
|
|
4177
4456
|
if (params.cursor)
|
|
4178
4457
|
query2.set("cursor", params.cursor);
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4458
|
+
const pageBytes = params.page_bytes ?? (budget ? auditMaxBytes(budget.maxBytes) : undefined);
|
|
4459
|
+
const raw = await fetchAudit(params.id, query2, pageBytes);
|
|
4460
|
+
params = { ...params, page_bytes: pageBytes };
|
|
4461
|
+
const whole = auditOutput(command, params, raw);
|
|
4462
|
+
if (!budget || budget.fits(whole.data, whole.options))
|
|
4463
|
+
return whole;
|
|
4464
|
+
const spans = asRecord(raw)?.spans;
|
|
4465
|
+
const available = (Array.isArray(spans) ? spans.length : 0) - (params.span_offset ?? 0);
|
|
4466
|
+
if (available <= 0)
|
|
4467
|
+
return whole;
|
|
4468
|
+
let low = 1;
|
|
4469
|
+
let high = available;
|
|
4470
|
+
while (low < high) {
|
|
4471
|
+
const mid = Math.ceil((low + high) / 2);
|
|
4472
|
+
const page2 = auditOutput(command, params, raw, mid);
|
|
4473
|
+
if (budget.fits(page2.data, page2.options))
|
|
4474
|
+
low = mid;
|
|
4475
|
+
else
|
|
4476
|
+
high = mid - 1;
|
|
4184
4477
|
}
|
|
4185
|
-
|
|
4478
|
+
const page = auditOutput(command, params, raw, low);
|
|
4479
|
+
if (low === 1 && !budget.fits(page.data, page.options)) {
|
|
4186
4480
|
page.options.warnings = [
|
|
4187
4481
|
...page.options.warnings ?? [],
|
|
4188
|
-
"
|
|
4482
|
+
"One span of this page is larger than the agent output budget, so agent output shortens it. Rerun this command with --json (no byte limit) or a higher MODA_AGENT_MAX_OUTPUT_BYTES to see it whole."
|
|
4189
4483
|
];
|
|
4190
4484
|
}
|
|
4191
4485
|
return page;
|
|
@@ -4202,6 +4496,16 @@ function singleContextTarget(positional, flags) {
|
|
|
4202
4496
|
}
|
|
4203
4497
|
return [target.id, { ...flags, "msg-index": String(target.msgIndex) }];
|
|
4204
4498
|
}
|
|
4499
|
+
async function withAnchorHint(target, read) {
|
|
4500
|
+
try {
|
|
4501
|
+
return await read();
|
|
4502
|
+
} catch (error) {
|
|
4503
|
+
if (!(error instanceof CliNotFoundError) || target.msgIndex === undefined || target.raw.startsWith("{"))
|
|
4504
|
+
throw error;
|
|
4505
|
+
const verbatim = JSON.stringify({ id: target.raw });
|
|
4506
|
+
throw new CliNotFoundError(error.entity, error.id, `${error.message} '${target.raw}' was read as trace "${target.id}" anchored at message ${target.msgIndex} (the @N suffix is a message index). If "${target.raw}" is the whole trace id, pass it as ${shellArg(verbatim)} or --conversation-id=${shellArg(target.raw)}.`, `moda traces get ${shellArg(verbatim)}`);
|
|
4507
|
+
}
|
|
4508
|
+
}
|
|
4205
4509
|
function isBatchRead(positionals, flags) {
|
|
4206
4510
|
return positionals.length > 1 || IDS_FROM_KEYS.some((key) => flags[key] !== undefined);
|
|
4207
4511
|
}
|
|
@@ -4216,14 +4520,41 @@ function splitIdsFrom(flags) {
|
|
|
4216
4520
|
}
|
|
4217
4521
|
return { idsFrom, rest };
|
|
4218
4522
|
}
|
|
4219
|
-
function auditItemByteShare(count) {
|
|
4220
|
-
return Math.max(AUDIT_MIN_PAGE_BYTES, Math.floor((agentMaxOutputBytes() - AUDIT_AGENT_RESERVE_BYTES) / count));
|
|
4221
|
-
}
|
|
4222
4523
|
var BATCH_ENVELOPE_RESERVE_BYTES = 4096;
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4524
|
+
function batchTargetReserveBytes(target) {
|
|
4525
|
+
return 3 * (jsonByteLength(target.raw) + 4) + 16;
|
|
4526
|
+
}
|
|
4527
|
+
var BATCH_MIN_ITEM_SHARE_BYTES = 4096;
|
|
4528
|
+
async function readBatchTargets(targets, agentMode, read) {
|
|
4529
|
+
if (!agentMode) {
|
|
4530
|
+
return mapWithConcurrency(targets, BATCH_CONCURRENCY, (target) => readBatchItem(target, () => read(target, undefined)));
|
|
4531
|
+
}
|
|
4532
|
+
const reserve = targets.reduce((sum, target) => sum + batchTargetReserveBytes(target), BATCH_ENVELOPE_RESERVE_BYTES);
|
|
4533
|
+
const available = Math.max(1, agentMaxOutputBytes() - reserve);
|
|
4534
|
+
const fair = Math.max(BATCH_MIN_ITEM_SHARE_BYTES, Math.floor(available / targets.length));
|
|
4535
|
+
let used = 0;
|
|
4536
|
+
let reserved = 0;
|
|
4537
|
+
const shares = new Map;
|
|
4538
|
+
return mapWithConcurrency(targets, BATCH_CONCURRENCY, async (target, index) => {
|
|
4539
|
+
const share = shares.get(index) ?? fair;
|
|
4540
|
+
try {
|
|
4541
|
+
const result = await readBatchItem(target, () => read(target, { fits: fitsShare(share), maxBytes: share }));
|
|
4542
|
+
used += jsonByteLength(result) + 1;
|
|
4543
|
+
return result;
|
|
4544
|
+
} finally {
|
|
4545
|
+
reserved -= share;
|
|
4546
|
+
}
|
|
4547
|
+
}, {
|
|
4548
|
+
shouldStart: (index) => {
|
|
4549
|
+
const free = available - used - reserved;
|
|
4550
|
+
if (index > 0 && free < BATCH_MIN_ITEM_SHARE_BYTES)
|
|
4551
|
+
return false;
|
|
4552
|
+
const share = Math.max(1, Math.min(fair, free));
|
|
4553
|
+
shares.set(index, share);
|
|
4554
|
+
reserved += share;
|
|
4555
|
+
return true;
|
|
4556
|
+
}
|
|
4557
|
+
});
|
|
4227
4558
|
}
|
|
4228
4559
|
function fitsShare(share) {
|
|
4229
4560
|
return (data, options) => Buffer.byteLength(JSON.stringify({ data, ...options }) ?? "", "utf8") <= share;
|
|
@@ -4335,27 +4666,25 @@ async function runContextBatch(command, positionals, flags, context) {
|
|
|
4335
4666
|
throw new CliInputError(`${base.all === true ? "--all reads whole traces" : "--metadata-only reads no messages"}, so targets cannot carry an @msg_index anchor; drop the flag or the anchors.`, CONTEXT_BATCH_USAGE, "flag_conflict");
|
|
4336
4667
|
}
|
|
4337
4668
|
const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
|
|
4338
|
-
const
|
|
4339
|
-
const results = await mapWithConcurrency(targets, BATCH_CONCURRENCY, (target) => readBatchItem(target, () => {
|
|
4669
|
+
const results = await readBatchTargets(targets, agentMode, (target, budget) => {
|
|
4340
4670
|
const params = {
|
|
4341
4671
|
...base,
|
|
4342
4672
|
conversation_id: target.id,
|
|
4343
4673
|
...target.msgIndex !== undefined ? { msg_index: target.msgIndex } : {}
|
|
4344
4674
|
};
|
|
4345
|
-
return readContextItem(command, params, [],
|
|
4346
|
-
})
|
|
4675
|
+
return withAnchorHint(target, () => readContextItem(command, params, [], budget));
|
|
4676
|
+
});
|
|
4347
4677
|
return writeBatchResult(command, context, targets, results, inputWarnings, rest);
|
|
4348
4678
|
}
|
|
4349
4679
|
async function runAuditBatch(command, positionals, flags, context) {
|
|
4350
4680
|
const { idsFrom, rest } = splitIdsFrom(flags);
|
|
4351
|
-
if (rest.cursor !== undefined) {
|
|
4352
|
-
throw new CliInputError("--cursor continues one trace, so it cannot be used with several ids; follow each item's next_commands instead
|
|
4681
|
+
if (rest.cursor !== undefined || Object.keys(rest).some((key) => key.replace(/_/g, "-") === "span-offset")) {
|
|
4682
|
+
throw new CliInputError(`${rest.cursor !== undefined ? "--cursor" : "--span-offset"} continues one trace, so it cannot be used with several ids; follow each item's next_commands instead.`, AUDIT_BATCH_USAGE, "flag_conflict");
|
|
4353
4683
|
}
|
|
4354
4684
|
const targets = await collectBatchTargets(positionals, idsFrom, { anchors: false, usage: AUDIT_BATCH_USAGE, command });
|
|
4355
4685
|
const base = parseCommandFlags(AuditSchema, flagsToArgs(rest, "id", targets[0].id), command, { positionalKey: "id" });
|
|
4356
4686
|
const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
|
|
4357
|
-
const
|
|
4358
|
-
const results = await mapWithConcurrency(targets, BATCH_CONCURRENCY, (target) => readBatchItem(target, () => readAuditItem(command, { ...base, id: target.id }, share !== undefined ? { maxBytes: Math.max(AUDIT_MIN_PAGE_BYTES, share), fits: fitsShare(share) } : undefined)));
|
|
4687
|
+
const results = await readBatchTargets(targets, agentMode, (target, budget) => readAuditItem(command, { ...base, id: target.id }, budget));
|
|
4359
4688
|
return writeBatchResult(command, context, targets, results, [], rest);
|
|
4360
4689
|
}
|
|
4361
4690
|
async function runCommand(command, positional, flags, positionals = positional ? [positional] : [], context = createCommandContext({ command, positional, positionals, flags })) {
|
|
@@ -4378,6 +4707,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4378
4707
|
const args = flagsToArgs(flags);
|
|
4379
4708
|
const params = parseSpecFlags("clusters", args, command);
|
|
4380
4709
|
if (params.search !== undefined || params.node_id !== undefined) {
|
|
4710
|
+
rejectInapplicableFlags(command, params, ["parent_id", "time_range"], "to a cluster search (--search/--node-id); they page the cluster hierarchy");
|
|
4381
4711
|
const query3 = new URLSearchParams;
|
|
4382
4712
|
if (params.search !== undefined)
|
|
4383
4713
|
query3.set("q", params.search);
|
|
@@ -4394,6 +4724,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4394
4724
|
context.output.writeData(data2, warnings.length > 0 ? { warnings } : undefined);
|
|
4395
4725
|
break;
|
|
4396
4726
|
}
|
|
4727
|
+
rejectInapplicableFlags(command, params, ["mode", "limit"], "without --search or --node-id; they shape a cluster search");
|
|
4397
4728
|
const query2 = new URLSearchParams;
|
|
4398
4729
|
if (params.parent_id)
|
|
4399
4730
|
query2.set("parent_id", params.parent_id);
|
|
@@ -4425,7 +4756,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4425
4756
|
}
|
|
4426
4757
|
case "traces":
|
|
4427
4758
|
case "conversations": {
|
|
4428
|
-
const args =
|
|
4759
|
+
const args = flagsToArgs(flags);
|
|
4429
4760
|
const params = parseSpecFlags("traces", args, command);
|
|
4430
4761
|
const query2 = new URLSearchParams;
|
|
4431
4762
|
if (params.search)
|
|
@@ -4476,7 +4807,8 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4476
4807
|
if (params.include_world_state === true) {
|
|
4477
4808
|
const enriched = asRecord(data) ?? {};
|
|
4478
4809
|
const list = Array.isArray(enriched.conversations) ? enriched.conversations : [];
|
|
4479
|
-
const
|
|
4810
|
+
const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
|
|
4811
|
+
const withState = await attachWorldState(list, agentMode ? agentMaxOutputBytes() : undefined);
|
|
4480
4812
|
context.output.writeData({ ...enriched, conversations: withState }, writeOptions);
|
|
4481
4813
|
} else {
|
|
4482
4814
|
context.output.writeData(data, writeOptions);
|
|
@@ -4484,10 +4816,11 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4484
4816
|
break;
|
|
4485
4817
|
}
|
|
4486
4818
|
case "search": {
|
|
4487
|
-
|
|
4819
|
+
const queryText = positionals.join(" ");
|
|
4820
|
+
if (queryText.trim() === "") {
|
|
4488
4821
|
throw new CliInputError("<query> is required", 'Usage: moda traces search "<query>" [--mode=keyword|semantic|hybrid] [--user-id=ID] [--conversation-id=ID] [--time-range=RANGE] [--since=DATE] [--until=DATE] [--include-tool-io] [--limit=N]');
|
|
4489
4822
|
}
|
|
4490
|
-
const args =
|
|
4823
|
+
const args = flagsToArgs(flags, "q", queryText);
|
|
4491
4824
|
const params = parseSpecFlags("search", args, command);
|
|
4492
4825
|
const query2 = new URLSearchParams;
|
|
4493
4826
|
query2.set("q", params.q);
|
|
@@ -4524,15 +4857,42 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4524
4857
|
break;
|
|
4525
4858
|
}
|
|
4526
4859
|
if (fallback?.kind === "results") {
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4860
|
+
const write = (kept2) => {
|
|
4861
|
+
const page2 = fallback.page(kept2);
|
|
4862
|
+
const merged = {
|
|
4863
|
+
...searchRecord,
|
|
4864
|
+
...page2.data,
|
|
4865
|
+
pagination: {
|
|
4866
|
+
...asRecord(searchRecord.pagination) ?? {},
|
|
4867
|
+
returned: page2.returned,
|
|
4868
|
+
has_more: page2.hasMore,
|
|
4869
|
+
offset: page2.offset,
|
|
4870
|
+
...page2.returned < fallback.rows ? { omitted_for_byte_limit: fallback.rows - page2.returned } : {}
|
|
4871
|
+
}
|
|
4872
|
+
};
|
|
4873
|
+
return { data: merged, options: { warnings: [...warnings, page2.warning], nextCommands: page2.nextCommands } };
|
|
4874
|
+
};
|
|
4875
|
+
const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
|
|
4876
|
+
let kept = fallback.rows;
|
|
4877
|
+
const fitsAgent = (count) => {
|
|
4878
|
+
const page2 = write(count);
|
|
4879
|
+
return agentEnvelopeFits(command, page2.data, page2.options);
|
|
4533
4880
|
};
|
|
4534
|
-
|
|
4535
|
-
|
|
4881
|
+
if (agentMode && kept > 1 && !fitsAgent(kept)) {
|
|
4882
|
+
let low = 1;
|
|
4883
|
+
let high = kept - 1;
|
|
4884
|
+
while (low < high) {
|
|
4885
|
+
const mid = Math.ceil((low + high) / 2);
|
|
4886
|
+
if (fitsAgent(mid))
|
|
4887
|
+
low = mid;
|
|
4888
|
+
else
|
|
4889
|
+
high = mid - 1;
|
|
4890
|
+
}
|
|
4891
|
+
kept = low;
|
|
4892
|
+
}
|
|
4893
|
+
const page = write(kept);
|
|
4894
|
+
recordSearchTelemetry(context, page.data);
|
|
4895
|
+
context.output.writeData(page.data, page.options);
|
|
4536
4896
|
break;
|
|
4537
4897
|
}
|
|
4538
4898
|
}
|
|
@@ -4590,7 +4950,8 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4590
4950
|
return runContextBatch(command, positionals, flags, context);
|
|
4591
4951
|
const { params, warnings: inputWarnings } = parseContextRequest(...singleContextTarget(positional, flags));
|
|
4592
4952
|
const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
|
|
4593
|
-
const
|
|
4953
|
+
const read = () => readContextItem(command, params, inputWarnings, agentMode ? { fits: (data, options) => agentEnvelopeFits(command, data, options), maxBytes: agentMaxOutputBytes() } : undefined);
|
|
4954
|
+
const item = positional === undefined ? await read() : await withAnchorHint(parseBatchTarget(positional, true, CONTEXT_USAGE), read);
|
|
4594
4955
|
context.output.writeData(item.data, item.options);
|
|
4595
4956
|
break;
|
|
4596
4957
|
}
|
|
@@ -4604,15 +4965,12 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4604
4965
|
const args = flagsToArgs(flags, "id", positional);
|
|
4605
4966
|
const params = parseSpecFlags("audit", args, command);
|
|
4606
4967
|
const agentMode = context.output.mode === "agent" || context.output.mode === "agent-stream";
|
|
4607
|
-
const page = await readAuditItem(command, params, agentMode ? {
|
|
4608
|
-
maxBytes: Math.max(AUDIT_MIN_PAGE_BYTES, agentMaxOutputBytes() - AUDIT_AGENT_RESERVE_BYTES),
|
|
4609
|
-
fits: (data, options) => agentEnvelopeFits(command, data, options)
|
|
4610
|
-
} : undefined);
|
|
4968
|
+
const page = await readAuditItem(command, params, agentMode ? { maxBytes: agentMaxOutputBytes(), fits: (data, options) => agentEnvelopeFits(command, data, options) } : undefined);
|
|
4611
4969
|
context.output.writeData(page.data, page.options);
|
|
4612
4970
|
break;
|
|
4613
4971
|
}
|
|
4614
4972
|
case "frustrations": {
|
|
4615
|
-
const args =
|
|
4973
|
+
const args = flagsToArgs(flags);
|
|
4616
4974
|
const params = parseSpecFlags("frustrations", args, command);
|
|
4617
4975
|
const query2 = new URLSearchParams;
|
|
4618
4976
|
if (params.days_back)
|
|
@@ -4775,6 +5133,11 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4775
5133
|
if (view !== undefined && params.full === true) {
|
|
4776
5134
|
throw new CliInputError("--full applies to the dossier only; the --evidence/--reports/--traces/--feedback pages are always whole.", "Usage: moda problems get <problem_id> [--full] | moda problems get <problem_id> --evidence|--reports|--traces|--feedback");
|
|
4777
5135
|
}
|
|
5136
|
+
if (view === undefined) {
|
|
5137
|
+
rejectInapplicableFlags(command, params, ["limit", "cursor", "family", "door"], "to the dossier; they page --evidence/--reports/--traces/--feedback");
|
|
5138
|
+
} else if (view !== "conversations") {
|
|
5139
|
+
rejectInapplicableFlags(command, params, ["family", "door"], `to --${view}; they filter --traces`);
|
|
5140
|
+
}
|
|
4778
5141
|
if (view === undefined) {
|
|
4779
5142
|
const data2 = await callDataAPI(`/problems/${encodeURIComponent(params.id)}`);
|
|
4780
5143
|
const record = asRecord(data2) ?? {};
|
|
@@ -4844,15 +5207,21 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4844
5207
|
throw new CliInputError("--attribution-id must be a UUID.");
|
|
4845
5208
|
}
|
|
4846
5209
|
}
|
|
4847
|
-
const
|
|
5210
|
+
const feedbackPath = `/problems/${params.id}/feedback`;
|
|
5211
|
+
const feedbackBody = {
|
|
5212
|
+
action: params.action,
|
|
5213
|
+
...reason !== "" ? { reason } : {},
|
|
5214
|
+
...params.attribution_id ? { attribution_id: params.attribution_id } : {},
|
|
5215
|
+
...params.new_name ? { new_display_name: params.new_name } : {},
|
|
5216
|
+
actor: params.actor ?? "moda-cli"
|
|
5217
|
+
};
|
|
5218
|
+
if (context.dryRun) {
|
|
5219
|
+
writeDryRun(context, { plane: "data", method: "POST", path: feedbackPath, body: feedbackBody });
|
|
5220
|
+
break;
|
|
5221
|
+
}
|
|
5222
|
+
const data = await callDataAPI(feedbackPath, {
|
|
4848
5223
|
method: "POST",
|
|
4849
|
-
body: JSON.stringify(
|
|
4850
|
-
action: params.action,
|
|
4851
|
-
...reason !== "" ? { reason } : {},
|
|
4852
|
-
...params.attribution_id ? { attribution_id: params.attribution_id } : {},
|
|
4853
|
-
...params.new_name ? { new_display_name: params.new_name } : {},
|
|
4854
|
-
actor: params.actor ?? "moda-cli"
|
|
4855
|
-
}),
|
|
5224
|
+
body: JSON.stringify(feedbackBody),
|
|
4856
5225
|
headers: { "Content-Type": "application/json" }
|
|
4857
5226
|
}, { retries: 0 });
|
|
4858
5227
|
context.output.writeData(data);
|
|
@@ -4903,13 +5272,19 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4903
5272
|
}
|
|
4904
5273
|
const title = params.title?.trim() ?? "";
|
|
4905
5274
|
const teamId = params.team_id?.trim() ?? "";
|
|
4906
|
-
const
|
|
5275
|
+
const linearPath = `/problems/${params.id}/linear`;
|
|
5276
|
+
const linearBody = issue !== "" ? { issue } : {
|
|
5277
|
+
...teamId !== "" ? { team_id: teamId } : {},
|
|
5278
|
+
...title !== "" ? { title } : {},
|
|
5279
|
+
...params.priority !== undefined ? { priority: params.priority } : {}
|
|
5280
|
+
};
|
|
5281
|
+
if (context.dryRun) {
|
|
5282
|
+
writeDryRun(context, { plane: "data", method: "POST", path: linearPath, body: linearBody });
|
|
5283
|
+
break;
|
|
5284
|
+
}
|
|
5285
|
+
const data = await callDataAPI(linearPath, {
|
|
4907
5286
|
method: "POST",
|
|
4908
|
-
body: JSON.stringify(
|
|
4909
|
-
...teamId !== "" ? { team_id: teamId } : {},
|
|
4910
|
-
...title !== "" ? { title } : {},
|
|
4911
|
-
...params.priority !== undefined ? { priority: params.priority } : {}
|
|
4912
|
-
}),
|
|
5287
|
+
body: JSON.stringify(linearBody),
|
|
4913
5288
|
headers: { "Content-Type": "application/json" }
|
|
4914
5289
|
}, { retries: 0 }).catch((error) => {
|
|
4915
5290
|
throw linearApiError(error);
|
|
@@ -4941,6 +5316,7 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4941
5316
|
const params = parseSpecFlags("signal", args, command);
|
|
4942
5317
|
const id = encodeURIComponent(params.id);
|
|
4943
5318
|
if (params.detections !== true) {
|
|
5319
|
+
rejectInapplicableFlags(command, params, ["days_back", "limit", "offset"], "to the signal summary; they page its hits (--detections)");
|
|
4944
5320
|
const data2 = await callDataAPI(`/custom-signals/${id}`);
|
|
4945
5321
|
context.output.writeData(data2);
|
|
4946
5322
|
break;
|
|
@@ -4979,15 +5355,21 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4979
5355
|
...flags["msg-dedup-token"] !== undefined ? { msg_dedup_token: flags["msg-dedup-token"] } : {}
|
|
4980
5356
|
});
|
|
4981
5357
|
const note = params.note?.trim() ?? "";
|
|
4982
|
-
const
|
|
5358
|
+
const labelPath = `/custom-signals/${encodeURIComponent(params.id)}/labels`;
|
|
5359
|
+
const labelBody = {
|
|
5360
|
+
conversation_id: params.conversation_id,
|
|
5361
|
+
label: params.label,
|
|
5362
|
+
...note !== "" ? { note } : {},
|
|
5363
|
+
...params.msg_index !== undefined ? { msg_index: params.msg_index } : {},
|
|
5364
|
+
...params.msg_dedup_token ? { msg_dedup_token: params.msg_dedup_token } : {}
|
|
5365
|
+
};
|
|
5366
|
+
if (context.dryRun) {
|
|
5367
|
+
writeDryRun(context, { plane: "data", method: "POST", path: labelPath, body: labelBody });
|
|
5368
|
+
break;
|
|
5369
|
+
}
|
|
5370
|
+
const data = await callDataAPI(labelPath, {
|
|
4983
5371
|
method: "POST",
|
|
4984
|
-
body: JSON.stringify(
|
|
4985
|
-
conversation_id: params.conversation_id,
|
|
4986
|
-
label: params.label,
|
|
4987
|
-
...note !== "" ? { note } : {},
|
|
4988
|
-
...params.msg_index !== undefined ? { msg_index: params.msg_index } : {},
|
|
4989
|
-
...params.msg_dedup_token ? { msg_dedup_token: params.msg_dedup_token } : {}
|
|
4990
|
-
}),
|
|
5372
|
+
body: JSON.stringify(labelBody),
|
|
4991
5373
|
headers: { "Content-Type": "application/json" }
|
|
4992
5374
|
}, { retries: 0 });
|
|
4993
5375
|
context.output.writeData(data);
|
|
@@ -5019,15 +5401,20 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
5019
5401
|
...flags.note !== undefined ? { note: flags.note } : {}
|
|
5020
5402
|
});
|
|
5021
5403
|
const note = params.verdict === "clear" ? "" : params.note?.trim() ?? "";
|
|
5022
|
-
const
|
|
5404
|
+
const reviewPath = `/problems/${params.id}/detection-reviews`;
|
|
5405
|
+
const reviewBody = {
|
|
5406
|
+
conversation_id: params.conversation_id,
|
|
5407
|
+
attribution_id: params.attribution_id,
|
|
5408
|
+
verdict: params.verdict,
|
|
5409
|
+
...note !== "" ? { note } : {}
|
|
5410
|
+
};
|
|
5411
|
+
if (context.dryRun) {
|
|
5412
|
+
writeDryRun(context, { plane: "data", method: "POST", path: reviewPath, body: reviewBody, generatedFields: ["request_nonce"] });
|
|
5413
|
+
break;
|
|
5414
|
+
}
|
|
5415
|
+
const data = await callDataAPI(reviewPath, {
|
|
5023
5416
|
method: "POST",
|
|
5024
|
-
body: JSON.stringify({
|
|
5025
|
-
conversation_id: params.conversation_id,
|
|
5026
|
-
attribution_id: params.attribution_id,
|
|
5027
|
-
verdict: params.verdict,
|
|
5028
|
-
...note !== "" ? { note } : {},
|
|
5029
|
-
request_nonce: randomUUID2()
|
|
5030
|
-
}),
|
|
5417
|
+
body: JSON.stringify({ ...reviewBody, request_nonce: randomUUID2() }),
|
|
5031
5418
|
headers: { "Content-Type": "application/json" }
|
|
5032
5419
|
});
|
|
5033
5420
|
context.output.writeData(data);
|
|
@@ -5054,50 +5441,77 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
5054
5441
|
...flags["msg-dedup-token"] !== undefined ? { msg_dedup_token: flags["msg-dedup-token"] } : {}
|
|
5055
5442
|
});
|
|
5056
5443
|
const reason = params.reason?.trim() ?? "";
|
|
5057
|
-
|
|
5058
|
-
let data;
|
|
5444
|
+
let request;
|
|
5059
5445
|
switch (params.kind) {
|
|
5060
5446
|
case "problem":
|
|
5061
|
-
|
|
5447
|
+
request = { method: "POST", path: `/problems/${params.target}/feedback`, body: { action: "dismiss", reason, actor: "moda-cli" }, retries: 0 };
|
|
5062
5448
|
break;
|
|
5063
5449
|
case "problem-detection":
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5450
|
+
request = {
|
|
5451
|
+
method: "POST",
|
|
5452
|
+
path: `/problems/${params.target}/detection-reviews`,
|
|
5453
|
+
body: {
|
|
5454
|
+
conversation_id: params.conversation_id,
|
|
5455
|
+
attribution_id: params.attribution_id,
|
|
5456
|
+
verdict: params.undo ? "clear" : "false_positive",
|
|
5457
|
+
...params.undo ? {} : { note: reason }
|
|
5458
|
+
},
|
|
5459
|
+
nonce: true
|
|
5460
|
+
};
|
|
5071
5461
|
break;
|
|
5072
5462
|
case "custom-signal":
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5463
|
+
request = params.undo ? {
|
|
5464
|
+
method: "DELETE",
|
|
5465
|
+
path: `/custom-signals/${encodeURIComponent(params.target)}/labels/${encodeURIComponent(params.conversation_id ?? "")}`,
|
|
5466
|
+
retries: 0
|
|
5467
|
+
} : {
|
|
5468
|
+
method: "POST",
|
|
5469
|
+
path: `/custom-signals/${encodeURIComponent(params.target)}/labels`,
|
|
5470
|
+
body: {
|
|
5471
|
+
conversation_id: params.conversation_id,
|
|
5472
|
+
label: "does_not_fit",
|
|
5473
|
+
note: reason,
|
|
5474
|
+
...params.msg_index !== undefined ? { msg_index: params.msg_index } : {},
|
|
5475
|
+
...params.msg_dedup_token ? { msg_dedup_token: params.msg_dedup_token } : {}
|
|
5476
|
+
},
|
|
5477
|
+
retries: 0
|
|
5478
|
+
};
|
|
5084
5479
|
break;
|
|
5085
5480
|
default: {
|
|
5086
5481
|
const apiKind = { quote: "frustration_quote", tool: "tool_call", laziness: "laziness", hallucination: "hallucination" }[params.kind];
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5482
|
+
request = {
|
|
5483
|
+
method: "POST",
|
|
5484
|
+
path: "/false-positives",
|
|
5485
|
+
body: {
|
|
5486
|
+
kind: apiKind,
|
|
5487
|
+
verdict: params.undo ? "clear" : "false_positive",
|
|
5488
|
+
...params.undo ? {} : { note: reason },
|
|
5489
|
+
conversation_id: params.target,
|
|
5490
|
+
...params.kind === "tool" ? {
|
|
5491
|
+
tool_name: params.tool,
|
|
5492
|
+
...params.tool_use_id ? { tool_use_id: params.tool_use_id } : { msg_index: params.msg_index }
|
|
5493
|
+
} : {},
|
|
5494
|
+
...params.kind === "quote" ? { family: params.family, turn: params.turn, quote: params.quote } : {}
|
|
5495
|
+
},
|
|
5496
|
+
nonce: true
|
|
5497
|
+
};
|
|
5099
5498
|
}
|
|
5100
5499
|
}
|
|
5500
|
+
if (context.dryRun) {
|
|
5501
|
+
writeDryRun(context, {
|
|
5502
|
+
plane: "data",
|
|
5503
|
+
method: request.method,
|
|
5504
|
+
path: request.path,
|
|
5505
|
+
...request.body ? { body: request.body } : {},
|
|
5506
|
+
...request.nonce ? { generatedFields: ["request_nonce"] } : {}
|
|
5507
|
+
});
|
|
5508
|
+
break;
|
|
5509
|
+
}
|
|
5510
|
+
const sentBody = request.body && request.nonce ? { ...request.body, request_nonce: randomUUID2() } : request.body;
|
|
5511
|
+
const data = await callDataAPI(request.path, {
|
|
5512
|
+
method: request.method,
|
|
5513
|
+
...sentBody ? { body: JSON.stringify(sentBody), headers: { "Content-Type": "application/json" } } : {}
|
|
5514
|
+
}, request.retries === undefined ? undefined : { retries: request.retries });
|
|
5101
5515
|
context.output.writeData(data);
|
|
5102
5516
|
break;
|
|
5103
5517
|
}
|
|
@@ -5125,12 +5539,13 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
5125
5539
|
context.output.writeData(data2);
|
|
5126
5540
|
break;
|
|
5127
5541
|
}
|
|
5542
|
+
rejectInapplicableFlags(command, { kind: flags.kind, limit: flags.limit, all: flags.all === "false" ? undefined : flags.all }, ["kind", "limit", "all"], "to one trace's marks (every mark on the trace is listed); they filter the workspace list");
|
|
5128
5543
|
const data = await callDataAPI(`/conversations/${encodeURIComponent(positional)}/false-positives`);
|
|
5129
5544
|
context.output.writeData(data);
|
|
5130
5545
|
break;
|
|
5131
5546
|
}
|
|
5132
5547
|
case "emotions": {
|
|
5133
|
-
const args =
|
|
5548
|
+
const args = flagsToArgs(flags);
|
|
5134
5549
|
const params = parseSpecFlags("emotions", args, command);
|
|
5135
5550
|
const query2 = new URLSearchParams;
|
|
5136
5551
|
if (params.days_back)
|
|
@@ -5158,9 +5573,6 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
5158
5573
|
}
|
|
5159
5574
|
case "laziness": {
|
|
5160
5575
|
const args = flagsToArgs(flags);
|
|
5161
|
-
if (flags["conversation-id"] !== undefined) {
|
|
5162
|
-
args.conversation_id = flags["conversation-id"];
|
|
5163
|
-
}
|
|
5164
5576
|
const params = parseSpecFlags("laziness", args, command);
|
|
5165
5577
|
const query2 = new URLSearchParams;
|
|
5166
5578
|
if (params.days_back)
|
|
@@ -5180,9 +5592,6 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
5180
5592
|
}
|
|
5181
5593
|
case "hallucinations": {
|
|
5182
5594
|
const args = flagsToArgs(flags);
|
|
5183
|
-
if (flags["conversation-id"] !== undefined) {
|
|
5184
|
-
args.conversation_id = flags["conversation-id"];
|
|
5185
|
-
}
|
|
5186
5595
|
const params = parseSpecFlags("hallucinations", args, command);
|
|
5187
5596
|
const query2 = new URLSearchParams;
|
|
5188
5597
|
if (params.days_back)
|
|
@@ -5223,17 +5632,22 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
5223
5632
|
}
|
|
5224
5633
|
case "feedback": {
|
|
5225
5634
|
const { category, severity, note, refs, warnings } = buildFeedbackRequest(positionals, flags, context.bareFlags);
|
|
5635
|
+
const feedbackBody = {
|
|
5636
|
+
category,
|
|
5637
|
+
severity,
|
|
5638
|
+
source: "cli",
|
|
5639
|
+
agent: detectCallerAgent(process.env),
|
|
5640
|
+
cli_version: VERSION,
|
|
5641
|
+
note,
|
|
5642
|
+
...Object.keys(refs).length > 0 ? { refs } : {}
|
|
5643
|
+
};
|
|
5644
|
+
if (context.dryRun) {
|
|
5645
|
+
writeDryRun(context, { plane: "data", method: "POST", path: "/feedback", body: feedbackBody });
|
|
5646
|
+
break;
|
|
5647
|
+
}
|
|
5226
5648
|
const data = await callDataAPI("/feedback", {
|
|
5227
5649
|
method: "POST",
|
|
5228
|
-
body: JSON.stringify(
|
|
5229
|
-
category,
|
|
5230
|
-
severity,
|
|
5231
|
-
source: "cli",
|
|
5232
|
-
agent: detectCallerAgent(process.env),
|
|
5233
|
-
cli_version: VERSION,
|
|
5234
|
-
note,
|
|
5235
|
-
...Object.keys(refs).length > 0 ? { refs } : {}
|
|
5236
|
-
})
|
|
5650
|
+
body: JSON.stringify(feedbackBody)
|
|
5237
5651
|
}, { retries: 0 });
|
|
5238
5652
|
context.output.writeData({ submitted: true, category, severity, ...asRecord(data) ?? {} }, {
|
|
5239
5653
|
summary: "Feedback submitted. Thank you for helping us improve!",
|
|
@@ -5382,7 +5796,7 @@ var commandRegistry = createCommandRegistry([
|
|
|
5382
5796
|
resetApiRequestCountBeforeRun: true,
|
|
5383
5797
|
telemetry: "result-and-error",
|
|
5384
5798
|
handler: async (context) => {
|
|
5385
|
-
const { runInit } = await import("./index-
|
|
5799
|
+
const { runInit } = await import("./index-2kf3fnnb.js");
|
|
5386
5800
|
if (context.outputMode === "agent-stream") {
|
|
5387
5801
|
context.output.writeEvent({
|
|
5388
5802
|
event: "started",
|
|
@@ -5432,7 +5846,7 @@ var commandRegistry = createCommandRegistry([
|
|
|
5432
5846
|
resetApiRequestCountBeforeRun: true,
|
|
5433
5847
|
telemetry: "result-and-error",
|
|
5434
5848
|
handler: async (context) => {
|
|
5435
|
-
const { runProvision } = await import("./provision-
|
|
5849
|
+
const { runProvision } = await import("./provision-gn8raqpt.js");
|
|
5436
5850
|
return runProvision(context, {
|
|
5437
5851
|
resumeCommand: buildResumeCommand("provision", context.rawFlags)
|
|
5438
5852
|
});
|
|
@@ -6259,11 +6673,13 @@ var commandRegistry = createCommandRegistry([
|
|
|
6259
6673
|
{
|
|
6260
6674
|
name: "ab",
|
|
6261
6675
|
description: "Run a judged prompt A/B replay experiment",
|
|
6262
|
-
usage: "moda prompts ab --baseline=<path> --candidate=<path> [--traces=<ids>|--set-id=<id>|--auto-generate] [--yes]",
|
|
6676
|
+
usage: "moda prompts ab --baseline=<path> --candidate=<path> [--traces=<ids>|--problem=<problem_id> [--max-cases=<n>]|--set-id=<id>|--auto-generate] [--yes]",
|
|
6263
6677
|
flags: [
|
|
6264
6678
|
{ name: "--baseline=<path>", description: "Prompt file to treat as the control." },
|
|
6265
6679
|
{ name: "--candidate=<path>", description: "Prompt file to treat as the variant." },
|
|
6266
6680
|
{ name: "--traces=<ids>", description: "Comma-separated trace ids (conversation_id values) to replay, at most 100. Each case uses Moda's distilled scenario and success criteria for the trace; a trace that cannot be distilled falls back to its opening user message with generic criteria, and the run warns. Legacy alias: --conversations=<ids>." },
|
|
6681
|
+
{ name: "--problem=<problem_id>", description: "Replay the Problem's failing traces: one case per affected trace, starting at its evidence msg_index (like --traces=<id>@<n>), read from `moda problems get <id> --evidence`, then `--traces` if needed. Prints the traces it chose. Not combinable with --traces, --set-id, --auto-generate, or --cases." },
|
|
6682
|
+
{ name: "--max-cases=<n>", description: "With --problem: replay at most this many traces (default 10, max 100)." },
|
|
6267
6683
|
{ name: "--cases=<n>", description: "Cases to auto-generate (default 5, max 100)." },
|
|
6268
6684
|
{ name: "--seeds=<n>", description: "Repeats per case per arm (default 3, max 10)." },
|
|
6269
6685
|
{ name: "--model=<slug>", description: "Assistant model for both arms; must be in the replay allowlist unless --allow-any-model." },
|
|
@@ -6271,7 +6687,8 @@ var commandRegistry = createCommandRegistry([
|
|
|
6271
6687
|
{ name: "--promote", description: "Make this run's replay set the tenant's primary replay set. Off by default, so an ad-hoc A/B never replaces it." }
|
|
6272
6688
|
],
|
|
6273
6689
|
examples: [
|
|
6274
|
-
"moda prompts ab --baseline=prompts/agent.prompt.md --candidate=prompts/agent-v2.prompt.md --traces=conv_1,conv_2"
|
|
6690
|
+
"moda prompts ab --baseline=prompts/agent.prompt.md --candidate=prompts/agent-v2.prompt.md --traces=conv_1,conv_2",
|
|
6691
|
+
"moda prompts ab --baseline=prompts/agent.prompt.md --candidate=prompts/agent-v2.prompt.md --problem=<problem_id>"
|
|
6275
6692
|
]
|
|
6276
6693
|
},
|
|
6277
6694
|
{
|
|
@@ -6451,6 +6868,9 @@ ${error.suggestions.map((s) => ` ${s}`).join(`
|
|
|
6451
6868
|
`)}
|
|
6452
6869
|
`);
|
|
6453
6870
|
}
|
|
6871
|
+
} else if (error instanceof ApiError && error.statusCode === 404) {
|
|
6872
|
+
safeWriteStderr2(`${errorToAgentError(error).message}
|
|
6873
|
+
`);
|
|
6454
6874
|
} else if (error instanceof ApiError) {
|
|
6455
6875
|
safeWriteStderr2(`API error (HTTP ${error.statusCode}): ${error.responseBody || error.message}
|
|
6456
6876
|
`);
|
|
@@ -6545,6 +6965,50 @@ function assertRoutedFlags(route, flags) {
|
|
|
6545
6965
|
const args = Object.fromEntries(Object.keys(flags).filter((flag) => !aliases.has(flag.replace(/_/g, "-"))).map((flag) => [flag.replace(/-/g, "_"), true]));
|
|
6546
6966
|
parseCommandFlags(shape.passthrough(), args, route.label, route.flagAliases ? { flagAliases: route.flagAliases } : {});
|
|
6547
6967
|
}
|
|
6968
|
+
var EMPTY_VALUE_ALLOWED = {
|
|
6969
|
+
context: new Set(["msg-index", "message-index"])
|
|
6970
|
+
};
|
|
6971
|
+
var EXAMPLE_CLEARABLE_FLAGS = new Set(["note", "blurb"]);
|
|
6972
|
+
function assertFlagValues(handler, positionals, typedFlags, bareFlags, route) {
|
|
6973
|
+
const name = route?.handler ?? handler;
|
|
6974
|
+
const spec = flagSpecFor(name);
|
|
6975
|
+
if (spec?.idRefFlags)
|
|
6976
|
+
return;
|
|
6977
|
+
const bare = new Set(bareFlags ?? []);
|
|
6978
|
+
const empty = new Set(Object.keys(typedFlags).filter((key) => String(typedFlags[key]).trim() === ""));
|
|
6979
|
+
if (bare.size === 0 && empty.size === 0)
|
|
6980
|
+
return;
|
|
6981
|
+
const handlerPositionals = route?.positionals ?? positionals;
|
|
6982
|
+
const managementSubcommand = name === "signals" && SIGNALS_MANAGEMENT_SUBCOMMANDS.has(handlerPositionals[0] ?? "");
|
|
6983
|
+
const examplesUpdate = name === "signals" && handlerPositionals[0] === "examples" && handlerPositionals.includes("update");
|
|
6984
|
+
const valued = new Set;
|
|
6985
|
+
const switches = new Set(RAW_BOOLEAN_FLAGS);
|
|
6986
|
+
for (const entry of specsOf(name)) {
|
|
6987
|
+
for (const flag of describeSpecFlags(entry))
|
|
6988
|
+
(flag.type === "boolean" ? switches : valued).add(flag.name.slice(2));
|
|
6989
|
+
for (const positional of entry.positionals ?? [])
|
|
6990
|
+
if (positional.key)
|
|
6991
|
+
valued.add(positional.key.replace(/_/g, "-"));
|
|
6992
|
+
}
|
|
6993
|
+
for (const flag of route?.allowedFlags ?? [])
|
|
6994
|
+
if (!switches.has(flag))
|
|
6995
|
+
valued.add(flag);
|
|
6996
|
+
for (const [key, value] of Object.entries(typedFlags)) {
|
|
6997
|
+
const isBare = bare.has(key) && value === "true";
|
|
6998
|
+
if (!isBare && !empty.has(key))
|
|
6999
|
+
continue;
|
|
7000
|
+
if (!isBare && EMPTY_VALUE_ALLOWED[name]?.has(key))
|
|
7001
|
+
continue;
|
|
7002
|
+
if (!isBare && examplesUpdate && EXAMPLE_CLEARABLE_FLAGS.has(key))
|
|
7003
|
+
continue;
|
|
7004
|
+
const target = route?.flagAliases?.[key] ?? key;
|
|
7005
|
+
const takesValue = valued.has(key) || valued.has(target) || managementSubcommand && !switches.has(key) && !GLOBAL_FLAG_NAMES.has(key);
|
|
7006
|
+
if (!takesValue || switches.has(key))
|
|
7007
|
+
continue;
|
|
7008
|
+
const shown = canonicalCommandText(`moda ${route?.leaf.path ?? handler}`);
|
|
7009
|
+
throw new CliInputError(`--${key} needs a value, so nothing was sent.`, `Pass it as --${key}=VALUE (an empty shell variable leaves the flag without one). See '${shown} --help'.`, `missing_value:${key}`, { field: `--${key}`, suggestedCommands: [`${shown} --help`] });
|
|
7010
|
+
}
|
|
7011
|
+
}
|
|
6548
7012
|
function renameErrorCommands(error, renames) {
|
|
6549
7013
|
if (error instanceof CliInputError) {
|
|
6550
7014
|
const details2 = error.details;
|
|
@@ -6562,6 +7026,7 @@ function renameErrorCommands(error, renames) {
|
|
|
6562
7026
|
}
|
|
6563
7027
|
async function dispatchParsedCommandInner(typedParsed, startedAt) {
|
|
6564
7028
|
const { command, flags } = typedParsed;
|
|
7029
|
+
canonicalizeFlagKeys(flags, typedParsed.bareFlags);
|
|
6565
7030
|
const switchError = normalizeBooleanFlags(flags);
|
|
6566
7031
|
const typedFlags = stripUpdateCheckFlag(stripTelemetryFlag(flags));
|
|
6567
7032
|
const telemetryDisabled = isTelemetryDisabled(flags);
|
|
@@ -6574,6 +7039,15 @@ async function dispatchParsedCommandInner(typedParsed, startedAt) {
|
|
|
6574
7039
|
if (command && printTreeHelp(typedParsed))
|
|
6575
7040
|
return 0;
|
|
6576
7041
|
const helpTarget = command && command !== "--help" && command !== "-h" ? commandRegistry.find(command) : undefined;
|
|
7042
|
+
if (!helpTarget && command && command !== "--help" && command !== "-h") {
|
|
7043
|
+
const mode = resolveOutputMode(typedFlags, { isTTY: process.stdout.isTTY, env: process.env });
|
|
7044
|
+
const error = unknownCommandError(command);
|
|
7045
|
+
if (mode === "agent" || mode === "agent-stream" || mode === "json")
|
|
7046
|
+
printAgentCliError(command, error, mode);
|
|
7047
|
+
else
|
|
7048
|
+
printCliError(error);
|
|
7049
|
+
return 1;
|
|
7050
|
+
}
|
|
6577
7051
|
if (helpTarget) {
|
|
6578
7052
|
const subcommandName = typedParsed.positional;
|
|
6579
7053
|
const subcommandEntry = subcommandName ? helpTarget.subcommands?.find((entry) => entry.name === subcommandName) : undefined;
|
|
@@ -6688,14 +7162,14 @@ async function dispatchParsedCommandInner(typedParsed, startedAt) {
|
|
|
6688
7162
|
}
|
|
6689
7163
|
if (switchError)
|
|
6690
7164
|
return failEarly(switchError, definition, identity);
|
|
6691
|
-
|
|
6692
|
-
|
|
7165
|
+
try {
|
|
7166
|
+
if (route)
|
|
6693
7167
|
assertRoutedFlags(route, typedFlags);
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
|
|
7168
|
+
assertFlagValues(definition.name, parsed.positionals, typedFlags, typedParsed.bareFlags, route);
|
|
7169
|
+
} catch (error) {
|
|
7170
|
+
if (!(error instanceof CliInputError))
|
|
7171
|
+
throw error;
|
|
7172
|
+
return failEarly(error, definition, identity);
|
|
6699
7173
|
}
|
|
6700
7174
|
if (commandFlags.profile === "true" || commandFlags.profile === "") {
|
|
6701
7175
|
return failEarly(new CliInputError("--profile needs a value, e.g. --profile=prod.", "List profiles with 'moda profiles list'.", "missing_value:profile"), definition, identity);
|
|
@@ -6854,6 +7328,7 @@ if (isMain) {
|
|
|
6854
7328
|
}
|
|
6855
7329
|
export {
|
|
6856
7330
|
unknownCommandError,
|
|
7331
|
+
trimContextWindow,
|
|
6857
7332
|
telemetrySubcommandNames,
|
|
6858
7333
|
telemetryCommandName,
|
|
6859
7334
|
stripLoneSurrogates,
|
|
@@ -6873,10 +7348,14 @@ export {
|
|
|
6873
7348
|
dispatchParsedCommand,
|
|
6874
7349
|
detectCallerAgent,
|
|
6875
7350
|
describeLinearLink,
|
|
7351
|
+
commandSwitchFlags,
|
|
6876
7352
|
commandRegistry,
|
|
7353
|
+
coerceNumericFlags,
|
|
6877
7354
|
cliValidationReason,
|
|
6878
7355
|
classifyCliError,
|
|
6879
7356
|
capProblemsBlocks,
|
|
7357
|
+
canonicalizeFlagKeys,
|
|
7358
|
+
canonicalFlagKey,
|
|
6880
7359
|
buildResumeCommand,
|
|
6881
7360
|
auditCapStatus,
|
|
6882
7361
|
TELEMETRY_EXEMPT_COMMANDS,
|