@moda-ai/cli 1.24.0 → 1.26.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 +62 -24
- package/dist/{cli-vc7zddj1.js → cli-59yacef3.js} +14 -1
- package/dist/{cli-8s69sg7f.js → cli-9xw5bq1j.js} +1 -1
- package/dist/{cli-f2f0twjw.js → cli-g931ntph.js} +13 -2
- package/dist/{cli-tdttb4cv.js → cli-pq6rte0w.js} +2 -2
- package/dist/{cli-0t9z27pc.js → cli-xf5nx5hc.js} +6 -6
- package/dist/cli.js +658 -31
- package/dist/{harness-github-actions-ar3sfmqq.js → harness-github-actions-ct8fwzdr.js} +2 -2
- package/dist/{index-as6n8qxp.js → index-efv643ec.js} +9 -5
- package/dist/{provision-dphktm75.js → provision-r0cq3g2v.js} +3 -3
- package/package.json +1 -1
- package/skills/integration/index.json +2 -2
- package/skills/moda-cli/SKILL.md +22 -11
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
runPromptsCommand,
|
|
6
6
|
runSkillsCommand,
|
|
7
7
|
runStatusCommand
|
|
8
|
-
} from "./cli-
|
|
8
|
+
} from "./cli-xf5nx5hc.js";
|
|
9
9
|
import {
|
|
10
10
|
ApiError,
|
|
11
11
|
HARNESS_REPORT_APPROVAL_PATH,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
summarizeHarness,
|
|
27
27
|
terminalStyles,
|
|
28
28
|
validateHarnessReport
|
|
29
|
-
} from "./cli-
|
|
29
|
+
} from "./cli-g931ntph.js";
|
|
30
30
|
import {
|
|
31
31
|
authFetch,
|
|
32
32
|
clearAuthSession,
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
loadAuthSession,
|
|
35
35
|
login,
|
|
36
36
|
revokeCliSessionToken
|
|
37
|
-
} from "./cli-
|
|
37
|
+
} from "./cli-9xw5bq1j.js";
|
|
38
38
|
import {
|
|
39
39
|
AGENT_ERROR_CODES,
|
|
40
40
|
CliAccessError,
|
|
@@ -79,7 +79,7 @@ import {
|
|
|
79
79
|
validateConfig,
|
|
80
80
|
wasOutputCancelled,
|
|
81
81
|
writeSecretJsonFile
|
|
82
|
-
} from "./cli-
|
|
82
|
+
} from "./cli-59yacef3.js";
|
|
83
83
|
|
|
84
84
|
// src/cli.ts
|
|
85
85
|
import { z as z2 } from "zod";
|
|
@@ -92,7 +92,11 @@ var OverviewSchema = z.object({
|
|
|
92
92
|
});
|
|
93
93
|
var ClustersSchema = z.object({
|
|
94
94
|
parent_id: z.string().optional(),
|
|
95
|
-
time_range: z.enum(["all", "1h", "3d", "7d", "24h", "30d", "90d"]).default("all").optional()
|
|
95
|
+
time_range: z.enum(["all", "1h", "3d", "7d", "24h", "30d", "90d"]).default("all").optional(),
|
|
96
|
+
search: z.string().min(2).max(200).optional(),
|
|
97
|
+
node_id: z.string().min(1).max(512).optional(),
|
|
98
|
+
mode: z.enum(["fuzzy", "semantic", "hybrid"]).optional(),
|
|
99
|
+
limit: z.number().min(1).max(50).optional()
|
|
96
100
|
});
|
|
97
101
|
var ClusterConversationsSchema = z.object({
|
|
98
102
|
node_id: z.string(),
|
|
@@ -114,7 +118,14 @@ var ConversationsSchema = z.object({
|
|
|
114
118
|
var WorldStateSchema = z.object({
|
|
115
119
|
conversation_id: z.string(),
|
|
116
120
|
summary_only: boolFlag(),
|
|
117
|
-
event_limit: z.number().min(1).max(5000).default(2000).optional()
|
|
121
|
+
event_limit: z.number().min(1).max(5000).default(2000).optional(),
|
|
122
|
+
snapshot: boolFlag(),
|
|
123
|
+
msg_index: z.number().min(0).max(1e5).optional(),
|
|
124
|
+
block_index: z.number().min(0).max(1e5).optional(),
|
|
125
|
+
segment_index: z.number().min(0).max(65535).optional(),
|
|
126
|
+
replay: boolFlag(),
|
|
127
|
+
format: z.enum(["frames", "fold-inputs"]).optional(),
|
|
128
|
+
message_count: z.number().min(1).max(1e6).optional()
|
|
118
129
|
});
|
|
119
130
|
var ContextSchema = z.object({
|
|
120
131
|
conversation_id: z.string(),
|
|
@@ -135,6 +146,73 @@ var ProblemsSchema = z.object({
|
|
|
135
146
|
days_back: z.number().min(1).max(90).default(30).optional(),
|
|
136
147
|
limit: z.number().min(1).max(25).default(25).optional()
|
|
137
148
|
});
|
|
149
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
150
|
+
var PROBLEM_FAMILIES = [
|
|
151
|
+
"tool_failure",
|
|
152
|
+
"emotion",
|
|
153
|
+
"laziness",
|
|
154
|
+
"hallucination",
|
|
155
|
+
"prm_dip"
|
|
156
|
+
];
|
|
157
|
+
var PROBLEM_DOORS = ["rubric_match", "local_causal_chain"];
|
|
158
|
+
var ProblemSchema = z.object({
|
|
159
|
+
id: z.string().min(1),
|
|
160
|
+
evidence: boolFlag(),
|
|
161
|
+
reports: boolFlag(),
|
|
162
|
+
conversations: boolFlag(),
|
|
163
|
+
feedback: boolFlag(),
|
|
164
|
+
limit: z.number().min(1).max(50).optional(),
|
|
165
|
+
cursor: z.string().optional(),
|
|
166
|
+
family: z.enum(PROBLEM_FAMILIES).optional(),
|
|
167
|
+
door: z.enum(PROBLEM_DOORS).optional()
|
|
168
|
+
});
|
|
169
|
+
var PROBLEM_FEEDBACK_ACTIONS = [
|
|
170
|
+
"mark_fixed",
|
|
171
|
+
"dismiss",
|
|
172
|
+
"flag_attribution",
|
|
173
|
+
"rename"
|
|
174
|
+
];
|
|
175
|
+
var ProblemFeedbackSchema = z.object({
|
|
176
|
+
id: z.string().min(1),
|
|
177
|
+
action: z.enum(PROBLEM_FEEDBACK_ACTIONS),
|
|
178
|
+
reason: z.string().max(2000).optional(),
|
|
179
|
+
attribution_id: z.string().max(64).optional(),
|
|
180
|
+
new_name: z.string().max(255).optional(),
|
|
181
|
+
actor: z.string().max(255).optional()
|
|
182
|
+
});
|
|
183
|
+
var EMOTION_FAMILIES = [
|
|
184
|
+
"frustration",
|
|
185
|
+
"sadness",
|
|
186
|
+
"confusion",
|
|
187
|
+
"anxiety",
|
|
188
|
+
"trust",
|
|
189
|
+
"positive"
|
|
190
|
+
];
|
|
191
|
+
var EmotionsSchema = z.object({
|
|
192
|
+
days_back: z.number().min(1).max(90).default(7).optional(),
|
|
193
|
+
limit: z.number().min(1).max(20).default(10).optional(),
|
|
194
|
+
offset: z.number().min(0).max(1e4).optional(),
|
|
195
|
+
family: z.enum(EMOTION_FAMILIES).optional()
|
|
196
|
+
});
|
|
197
|
+
var HallucinationsSchema = z.object({
|
|
198
|
+
days_back: z.number().min(1).max(90).default(7).optional(),
|
|
199
|
+
limit: z.number().min(1).max(20).default(10).optional(),
|
|
200
|
+
offset: z.number().min(0).max(1e4).optional(),
|
|
201
|
+
conversation_id: z.string().max(200).optional(),
|
|
202
|
+
kind: z.enum(["contradicted", "verified"]).optional()
|
|
203
|
+
});
|
|
204
|
+
var StepScoresSchema = z.object({
|
|
205
|
+
conversation_id: z.string().min(1)
|
|
206
|
+
});
|
|
207
|
+
var TAIL_SIGNALS = ["conversations", "emotions", "all"];
|
|
208
|
+
var TailSchema = z.object({
|
|
209
|
+
interval: z.number().min(5).max(3600).default(15).optional(),
|
|
210
|
+
signal: z.enum(TAIL_SIGNALS).default("conversations").optional(),
|
|
211
|
+
once: boolFlag(),
|
|
212
|
+
limit: z.number().min(1).max(50).default(20).optional(),
|
|
213
|
+
max_events: z.number().min(1).max(1e5).optional(),
|
|
214
|
+
full_scan: boolFlag()
|
|
215
|
+
});
|
|
138
216
|
var ToolFailureDetailSchema = z.object({
|
|
139
217
|
tool_name: z.string(),
|
|
140
218
|
subtype: z.string().optional(),
|
|
@@ -158,7 +236,7 @@ var SearchSchema = z.object({
|
|
|
158
236
|
user_id: z.string().optional(),
|
|
159
237
|
time_range: z.enum(TIME_RANGES).default("all").optional(),
|
|
160
238
|
limit: z.number().min(1).max(100).default(20).optional(),
|
|
161
|
-
include_tool_io:
|
|
239
|
+
include_tool_io: boolFlag()
|
|
162
240
|
});
|
|
163
241
|
var FEEDBACK_CATEGORIES = [
|
|
164
242
|
"bad_cluster_label",
|
|
@@ -1173,6 +1251,10 @@ var ONLINE_PROBE_TIMEOUT_MS = 5000;
|
|
|
1173
1251
|
function subcommand(context) {
|
|
1174
1252
|
return context.positionals[0];
|
|
1175
1253
|
}
|
|
1254
|
+
function unknownSubcommandError(parent, attempted, valid) {
|
|
1255
|
+
const message = attempted ? `Unknown ${parent} subcommand '${attempted}'. Valid subcommands: ${valid.join(", ")}.` : `A ${parent} subcommand is required. Valid subcommands: ${valid.join(", ")}.`;
|
|
1256
|
+
return new CliInputError(message, `Usage: moda ${parent} <${valid.join("|")}>`);
|
|
1257
|
+
}
|
|
1176
1258
|
function requirePositional(context, index, usage) {
|
|
1177
1259
|
const value = context.positionals[index];
|
|
1178
1260
|
if (!value)
|
|
@@ -1318,7 +1400,7 @@ async function runAuthCommand(context) {
|
|
|
1318
1400
|
return { exitCode: EXIT_OK };
|
|
1319
1401
|
}
|
|
1320
1402
|
default:
|
|
1321
|
-
throw
|
|
1403
|
+
throw unknownSubcommandError("auth", subcommand(context), ["login", "logout", "status", "token", "whoami"]);
|
|
1322
1404
|
}
|
|
1323
1405
|
}
|
|
1324
1406
|
async function runProfilesCommand(context) {
|
|
@@ -1396,12 +1478,12 @@ async function runProfilesCommand(context) {
|
|
|
1396
1478
|
return { exitCode: 0 };
|
|
1397
1479
|
}
|
|
1398
1480
|
default:
|
|
1399
|
-
throw
|
|
1481
|
+
throw unknownSubcommandError("profiles", subcommand(context), ["list", "use", "create", "doctor"]);
|
|
1400
1482
|
}
|
|
1401
1483
|
}
|
|
1402
1484
|
async function runConfigCommand(context) {
|
|
1403
1485
|
if (subcommand(context) !== "show") {
|
|
1404
|
-
throw
|
|
1486
|
+
throw unknownSubcommandError("config", subcommand(context), ["show"]);
|
|
1405
1487
|
}
|
|
1406
1488
|
const config = loadProfileConfig({ env: context.env });
|
|
1407
1489
|
const profile = getActiveProfile({ profile: context.profile, env: context.env });
|
|
@@ -1460,7 +1542,7 @@ async function runKeysCommand(context) {
|
|
|
1460
1542
|
throw new CliInputError(`moda keys ${action} is waiting on Moda Cloud key lifecycle support for ${keyId}.`, `Required endpoint: ${KEY_LIFECYCLE_ENDPOINT_REQUIREMENTS[action]}`);
|
|
1461
1543
|
}
|
|
1462
1544
|
default:
|
|
1463
|
-
throw
|
|
1545
|
+
throw unknownSubcommandError("keys", subcommand(context), ["list", "create", "rotate", "revoke"]);
|
|
1464
1546
|
}
|
|
1465
1547
|
}
|
|
1466
1548
|
|
|
@@ -3855,10 +3937,16 @@ Commands:
|
|
|
3855
3937
|
world-state <conversation_id> Get a conversation's world state (slots/threads/events)
|
|
3856
3938
|
context <conversation_id> Get windowed conversation context
|
|
3857
3939
|
audit <conversation_id|trace_id> Raw span/trace audit (spans, hierarchy, orphans, duplicates)
|
|
3858
|
-
frustrations Get user frustration detections
|
|
3940
|
+
frustrations Get user frustration detections (legacy single-family; see emotions)
|
|
3941
|
+
emotions Multi-family emotion detections (frustration, sadness, confusion, anxiety, trust, positive)
|
|
3942
|
+
hallucinations Grounding detections: contradicted/verified outputs with rule breakdown
|
|
3859
3943
|
tool-failures Get tool failure overview
|
|
3860
3944
|
tool-failure-detail <tool_name> Get per-tool failure detail
|
|
3861
3945
|
problems Rank cross-signal Problems by root cause (what to fix first)
|
|
3946
|
+
problem <problem_id> Open one Problem: dossier, or --evidence/--reports/--conversations/--feedback
|
|
3947
|
+
problem-feedback <problem_id> Mark a Problem fixed, dismiss, rename, or flag a bad attribution
|
|
3948
|
+
step-scores <conversation_id> Graph-PRM step scores (per-segment curves, first bad step, rollup)
|
|
3949
|
+
tail Live-tail new conversations/detections as NDJSON (one line per item)
|
|
3862
3950
|
feedback "<note>" Flag wrong/missing data or CLI quirks to the Moda team
|
|
3863
3951
|
|
|
3864
3952
|
Prompt management:
|
|
@@ -3924,6 +4012,52 @@ Search flags:
|
|
|
3924
4012
|
World-state flags:
|
|
3925
4013
|
--summary-only Return segment summaries only (omit the event stream)
|
|
3926
4014
|
--event-limit=N Max world-state events to return (1-5000, default 2000)
|
|
4015
|
+
--snapshot Folded slots at an exact tick (what the agent believed at that turn)
|
|
4016
|
+
--msg-index=N --block-index=M Snapshot tick (defaults 0; --segment-index=K scopes to a segment)
|
|
4017
|
+
--replay State-over-time replay; frames mode REQUIRES --message-count=N
|
|
4018
|
+
--format=frames|fold-inputs Replay format (default frames)
|
|
4019
|
+
--message-count=N Number of frames to fold (frames mode)
|
|
4020
|
+
|
|
4021
|
+
Emotions flags:
|
|
4022
|
+
--family=F frustration|sadness|confusion|anxiety|trust|positive
|
|
4023
|
+
--days-back=N --limit=N Window 1-90 (default 7); page size 1-20 (default 10)
|
|
4024
|
+
--offset=N Skip N detections (0-10000)
|
|
4025
|
+
|
|
4026
|
+
Hallucinations flags:
|
|
4027
|
+
--kind=contradicted|verified Narrow the detections list
|
|
4028
|
+
--conversation-id=ID Scope summary + list to one conversation
|
|
4029
|
+
--days-back=N --limit=N Window 1-90 (default 7); page size 1-20 (default 10)
|
|
4030
|
+
|
|
4031
|
+
Problem flags (moda problem <id>):
|
|
4032
|
+
--evidence|--reports|--conversations|--feedback
|
|
4033
|
+
Open one sub-resource page (at most one)
|
|
4034
|
+
--limit=N --cursor=TOKEN Keyset paging (1-50; pass next_cursor back verbatim)
|
|
4035
|
+
--family=F --door=D Filter --conversations (families: tool_failure|emotion|laziness|hallucination|prm_dip)
|
|
4036
|
+
|
|
4037
|
+
Problem-feedback flags:
|
|
4038
|
+
--action=A mark_fixed|dismiss|flag_attribution|rename (required)
|
|
4039
|
+
--reason=TEXT Required for dismiss and flag_attribution
|
|
4040
|
+
--attribution-id=UUID Required for flag_attribution (ids come from problem --evidence)
|
|
4041
|
+
--new-name=NAME Required for rename
|
|
4042
|
+
|
|
4043
|
+
Tail flags:
|
|
4044
|
+
--signal=S conversations|emotions|all (default conversations)
|
|
4045
|
+
--interval=N Poll every N seconds (5-3600, default 15)
|
|
4046
|
+
--once One poll, then exit (baseline page)
|
|
4047
|
+
--limit=N --max-events=N Page size per poll; stop after N stdout records
|
|
4048
|
+
(metadata included; a slot is reserved for the
|
|
4049
|
+
emotions coverage record, which falls back to
|
|
4050
|
+
stderr if the bound leaves no room)
|
|
4051
|
+
--full-scan Emotions: scan the whole window instead of the
|
|
4052
|
+
top-scoring prefix (many more requests). /emotions
|
|
4053
|
+
is score-ranked with no cursor, so the default
|
|
4054
|
+
follows the highest-scoring detections and emits a
|
|
4055
|
+
tail_coverage line stating exactly what it saw.
|
|
4056
|
+
|
|
4057
|
+
Clusters search flags:
|
|
4058
|
+
--search="query" Find clusters by name/meaning (task-cluster search)
|
|
4059
|
+
--node-id=ID Deterministically resolve one cluster node (deep link)
|
|
4060
|
+
--mode=fuzzy|semantic|hybrid Cluster search mode (default hybrid)
|
|
3927
4061
|
|
|
3928
4062
|
Feedback flags:
|
|
3929
4063
|
--category=CAT bad_cluster_label|mismatched_frustration|missing_data|noisy_data|
|
|
@@ -3975,12 +4109,12 @@ Examples:
|
|
|
3975
4109
|
moda profiles list
|
|
3976
4110
|
moda profiles use staging
|
|
3977
4111
|
moda config show --profile=prod
|
|
3978
|
-
moda harness analyze --summary
|
|
3979
|
-
moda harness analyze --remote
|
|
3980
|
-
moda harness analyze --analyst=claude --tui
|
|
3981
|
-
moda harness analyze --analyst=claude --no-tui
|
|
3982
|
-
moda harness analyze --analyst=claude --summary
|
|
3983
|
-
moda harness analyze --analyst=local-scan --summary
|
|
4112
|
+
moda harness analyze --experimental --summary
|
|
4113
|
+
moda harness analyze --experimental --remote
|
|
4114
|
+
moda harness analyze --experimental --analyst=claude --tui
|
|
4115
|
+
moda harness analyze --experimental --analyst=claude --no-tui
|
|
4116
|
+
moda harness analyze --experimental --analyst=claude --summary
|
|
4117
|
+
moda harness analyze --experimental --analyst=local-scan --summary
|
|
3984
4118
|
moda harness pull --sync
|
|
3985
4119
|
moda harness validate-report
|
|
3986
4120
|
moda harness approve --yes
|
|
@@ -4009,6 +4143,16 @@ Examples:
|
|
|
4009
4143
|
moda search "billing error" --mode=hybrid
|
|
4010
4144
|
moda search "refund flow" --mode=semantic --time-range=7d --limit=10
|
|
4011
4145
|
moda world-state <conversation_id> --summary-only
|
|
4146
|
+
moda world-state <conversation_id> --snapshot --msg-index=42
|
|
4147
|
+
moda world-state <conversation_id> --replay --message-count=50
|
|
4148
|
+
moda clusters --search="billing disputes"
|
|
4149
|
+
moda emotions --family=confusion --days-back=14
|
|
4150
|
+
moda hallucinations --kind=contradicted
|
|
4151
|
+
moda problems --days-back=7
|
|
4152
|
+
moda problem <problem_id> --evidence --limit=10
|
|
4153
|
+
moda problem-feedback <problem_id> --action=mark_fixed
|
|
4154
|
+
moda step-scores <conversation_id>
|
|
4155
|
+
moda tail --signal=all --interval=30
|
|
4012
4156
|
moda frustrations --days-back=14 --limit=20
|
|
4013
4157
|
moda feedback "cluster label looks wrong" --category=bad_cluster_label --cluster-id=<node_id>
|
|
4014
4158
|
moda context <conversation_id> --window=3
|
|
@@ -4020,6 +4164,25 @@ Examples:
|
|
|
4020
4164
|
moda prompts ab --baseline=prompts/agent.prompt.md --candidate=prompts/agent-v2.prompt.md --conversations=conv_1,conv_2
|
|
4021
4165
|
moda skills pull`);
|
|
4022
4166
|
}
|
|
4167
|
+
function printCommandHelp(definition) {
|
|
4168
|
+
const lines = [];
|
|
4169
|
+
lines.push(`moda ${definition.name} — ${definition.description}`);
|
|
4170
|
+
if (definition.aliases && definition.aliases.length > 0) {
|
|
4171
|
+
lines.push(`Aliases: ${definition.aliases.join(", ")}`);
|
|
4172
|
+
}
|
|
4173
|
+
if (definition.mutability === "write") {
|
|
4174
|
+
lines.push("This command can mutate state.");
|
|
4175
|
+
}
|
|
4176
|
+
if (definition.examples.length > 0) {
|
|
4177
|
+
lines.push("", "Examples:");
|
|
4178
|
+
for (const example of definition.examples) {
|
|
4179
|
+
lines.push(` ${example}`);
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
lines.push("", "Run 'moda --help' for the full command list and common flags", "(--json, --agent, --profile, --days-back, --limit, --time-range, ...).");
|
|
4183
|
+
console.error(lines.join(`
|
|
4184
|
+
`));
|
|
4185
|
+
}
|
|
4023
4186
|
async function attachWindows(rows, windowSize) {
|
|
4024
4187
|
return Promise.all(rows.map(async (row) => {
|
|
4025
4188
|
const anchor = asRecord(row.anchor);
|
|
@@ -4051,14 +4214,201 @@ async function attachWindows(rows, windowSize) {
|
|
|
4051
4214
|
}
|
|
4052
4215
|
}));
|
|
4053
4216
|
}
|
|
4054
|
-
async function printWithOptionalWindows(enriched, listKey, includeWindow, windowSize, output) {
|
|
4217
|
+
async function printWithOptionalWindows(enriched, listKey, includeWindow, windowSize, output, warnings) {
|
|
4218
|
+
const writeOptions = warnings && warnings.length > 0 ? { warnings } : undefined;
|
|
4055
4219
|
if (!includeWindow) {
|
|
4056
|
-
output.writeData(enriched);
|
|
4220
|
+
output.writeData(enriched, writeOptions);
|
|
4057
4221
|
return;
|
|
4058
4222
|
}
|
|
4059
4223
|
const list = Array.isArray(enriched[listKey]) ? enriched[listKey] : [];
|
|
4060
4224
|
const withWindows = await attachWindows(list, windowSize);
|
|
4061
|
-
output.writeData({ ...enriched, [listKey]: withWindows });
|
|
4225
|
+
output.writeData({ ...enriched, [listKey]: withWindows }, writeOptions);
|
|
4226
|
+
}
|
|
4227
|
+
function notFoundWarning(entity, id, isMissing) {
|
|
4228
|
+
if (!isMissing)
|
|
4229
|
+
return [];
|
|
4230
|
+
return [
|
|
4231
|
+
`No ${entity} found for id "${id}" — it may not exist in this tenant (the Data API returns an empty result rather than an error for unknown ids).`
|
|
4232
|
+
];
|
|
4233
|
+
}
|
|
4234
|
+
var TAIL_SEEN_MAX = 5000;
|
|
4235
|
+
var EMOTIONS_TAIL_PAGE_SIZE = 20;
|
|
4236
|
+
var EMOTIONS_TAIL_MAX_PAGES = 25;
|
|
4237
|
+
var EMOTIONS_OFFSET_CEILING = 1e4;
|
|
4238
|
+
var EMOTIONS_FULL_SCAN_MAX_PAGES = Math.floor(EMOTIONS_OFFSET_CEILING / EMOTIONS_TAIL_PAGE_SIZE) + 1;
|
|
4239
|
+
var EMOTIONS_SEEN_MAX = EMOTIONS_FULL_SCAN_MAX_PAGES * EMOTIONS_TAIL_PAGE_SIZE + 2000;
|
|
4240
|
+
var CONVERSATIONS_SEEN_MAX = TAIL_SEEN_MAX;
|
|
4241
|
+
var TAIL_LIMITS = {
|
|
4242
|
+
emotionsPageSize: EMOTIONS_TAIL_PAGE_SIZE,
|
|
4243
|
+
emotionsFullScanMaxPages: EMOTIONS_FULL_SCAN_MAX_PAGES,
|
|
4244
|
+
emotionsSeenMax: EMOTIONS_SEEN_MAX,
|
|
4245
|
+
conversationsSeenMax: CONVERSATIONS_SEEN_MAX,
|
|
4246
|
+
independentKeyspaces: true
|
|
4247
|
+
};
|
|
4248
|
+
async function runTailCommand(params, context) {
|
|
4249
|
+
const intervalMs = (params.interval ?? 15) * 1000;
|
|
4250
|
+
const signal = params.signal ?? "conversations";
|
|
4251
|
+
const limit = params.limit ?? 20;
|
|
4252
|
+
const seenConversations = new Set;
|
|
4253
|
+
const seenEmotions = new Set;
|
|
4254
|
+
let emitted = 0;
|
|
4255
|
+
let evictionReported = false;
|
|
4256
|
+
const rememberIn = (seen, capacity, key) => {
|
|
4257
|
+
if (seen.has(key))
|
|
4258
|
+
return false;
|
|
4259
|
+
if (seen.size >= capacity) {
|
|
4260
|
+
const iterator = seen.values();
|
|
4261
|
+
for (let i = 0;i < Math.max(1, Math.floor(capacity / 10)); i++) {
|
|
4262
|
+
const next = iterator.next();
|
|
4263
|
+
if (next.done)
|
|
4264
|
+
break;
|
|
4265
|
+
seen.delete(next.value);
|
|
4266
|
+
}
|
|
4267
|
+
if (!evictionReported) {
|
|
4268
|
+
evictionReported = true;
|
|
4269
|
+
emitMeta({
|
|
4270
|
+
type: "tail_dedupe_evicted",
|
|
4271
|
+
capacity,
|
|
4272
|
+
detail: "Dedupe memory reached capacity and oldest ids were dropped; items seen long ago may re-appear once. Restart the tail, or narrow --signal, if exact-once matters."
|
|
4273
|
+
});
|
|
4274
|
+
}
|
|
4275
|
+
}
|
|
4276
|
+
seen.add(key);
|
|
4277
|
+
return true;
|
|
4278
|
+
};
|
|
4279
|
+
const rememberConversation = (key) => rememberIn(seenConversations, CONVERSATIONS_SEEN_MAX, key);
|
|
4280
|
+
const rememberEmotion = (key) => rememberIn(seenEmotions, EMOTIONS_SEEN_MAX, key);
|
|
4281
|
+
const emitsCoverage = signal === "emotions" || signal === "all";
|
|
4282
|
+
const metaReserve = params.max_events !== undefined && emitsCoverage && params.max_events >= 2 ? 1 : 0;
|
|
4283
|
+
const dataBudget = params.max_events === undefined ? undefined : Math.max(0, params.max_events - metaReserve);
|
|
4284
|
+
const capReached = () => dataBudget !== undefined && emitted >= dataBudget;
|
|
4285
|
+
const emitLine = (item) => {
|
|
4286
|
+
if (capReached())
|
|
4287
|
+
return;
|
|
4288
|
+
console.log(JSON.stringify(item));
|
|
4289
|
+
emitted++;
|
|
4290
|
+
};
|
|
4291
|
+
const emitMeta = (item) => {
|
|
4292
|
+
if (params.max_events !== undefined && emitted >= params.max_events) {
|
|
4293
|
+
safeWriteStderr(`${JSON.stringify(item)}
|
|
4294
|
+
`);
|
|
4295
|
+
return;
|
|
4296
|
+
}
|
|
4297
|
+
console.log(JSON.stringify(item));
|
|
4298
|
+
emitted++;
|
|
4299
|
+
};
|
|
4300
|
+
const pollConversations = async () => {
|
|
4301
|
+
const data = await callDataAPI(`/conversations?time_range=1h&limit=${limit}`);
|
|
4302
|
+
const record = asRecord(data) ?? {};
|
|
4303
|
+
const rows = Array.isArray(record.conversations) ? record.conversations : [];
|
|
4304
|
+
for (const row of [...rows].reverse()) {
|
|
4305
|
+
const conversation = asRecord(row);
|
|
4306
|
+
if (!conversation)
|
|
4307
|
+
continue;
|
|
4308
|
+
const id = asString(conversation.conversation_id) ?? asString(conversation.id);
|
|
4309
|
+
if (!id)
|
|
4310
|
+
continue;
|
|
4311
|
+
const timestamp = asString(conversation.last_timestamp) ?? asString(conversation.timestamp) ?? asString(conversation.last_activity) ?? "";
|
|
4312
|
+
const messageCount = asNumber(conversation.message_count);
|
|
4313
|
+
if (capReached())
|
|
4314
|
+
return;
|
|
4315
|
+
if (!rememberConversation(`c:${id}:${timestamp}:${messageCount ?? ""}`))
|
|
4316
|
+
continue;
|
|
4317
|
+
emitLine({
|
|
4318
|
+
type: "conversation",
|
|
4319
|
+
conversation_id: id,
|
|
4320
|
+
timestamp: timestamp || null,
|
|
4321
|
+
message_count: messageCount,
|
|
4322
|
+
environment: conversation.environment ?? null,
|
|
4323
|
+
cluster_name: conversation.cluster_name ?? null,
|
|
4324
|
+
summary: conversation.summary ?? null
|
|
4325
|
+
});
|
|
4326
|
+
}
|
|
4327
|
+
};
|
|
4328
|
+
const pollEmotions = async () => {
|
|
4329
|
+
const maxPages = params.full_scan === true ? EMOTIONS_FULL_SCAN_MAX_PAGES : EMOTIONS_TAIL_MAX_PAGES;
|
|
4330
|
+
const collected = [];
|
|
4331
|
+
let offset = 0;
|
|
4332
|
+
let pages = 0;
|
|
4333
|
+
let truncated = false;
|
|
4334
|
+
let total = null;
|
|
4335
|
+
for (;; ) {
|
|
4336
|
+
const data = await callDataAPI(`/emotions?days_back=1&limit=${EMOTIONS_TAIL_PAGE_SIZE}&offset=${offset}`);
|
|
4337
|
+
const record = asRecord(data) ?? {};
|
|
4338
|
+
const rows = Array.isArray(record.detections) ? record.detections : [];
|
|
4339
|
+
collected.push(...rows.map((row) => asRecord(row)).filter((row) => row !== null));
|
|
4340
|
+
pages++;
|
|
4341
|
+
const pagination = asRecord(record.pagination) ?? {};
|
|
4342
|
+
total = asNumber(pagination.total) ?? total;
|
|
4343
|
+
const hasMore = pagination.has_more === true;
|
|
4344
|
+
if (!hasMore || rows.length === 0)
|
|
4345
|
+
break;
|
|
4346
|
+
if (pages >= maxPages) {
|
|
4347
|
+
truncated = true;
|
|
4348
|
+
break;
|
|
4349
|
+
}
|
|
4350
|
+
if (offset + EMOTIONS_TAIL_PAGE_SIZE > EMOTIONS_OFFSET_CEILING) {
|
|
4351
|
+
truncated = true;
|
|
4352
|
+
break;
|
|
4353
|
+
}
|
|
4354
|
+
offset += EMOTIONS_TAIL_PAGE_SIZE;
|
|
4355
|
+
}
|
|
4356
|
+
collected.sort((a, b) => (asString(a.detected_at) ?? "").localeCompare(asString(b.detected_at) ?? ""));
|
|
4357
|
+
for (const detection of collected) {
|
|
4358
|
+
if (capReached())
|
|
4359
|
+
break;
|
|
4360
|
+
const detectionId = asString(detection.detection_id);
|
|
4361
|
+
if (!detectionId || !rememberEmotion(`e:${detectionId}`))
|
|
4362
|
+
continue;
|
|
4363
|
+
emitLine({
|
|
4364
|
+
type: "emotion",
|
|
4365
|
+
detection_id: detectionId,
|
|
4366
|
+
conversation_id: detection.conversation_id ?? null,
|
|
4367
|
+
family: detection.family ?? null,
|
|
4368
|
+
primary_cause: detection.primary_cause ?? null,
|
|
4369
|
+
detected_at: detection.detected_at ?? null
|
|
4370
|
+
});
|
|
4371
|
+
}
|
|
4372
|
+
if (truncated || total !== null && collected.length < total) {
|
|
4373
|
+
const scanned = collected.length;
|
|
4374
|
+
const coveragePct = total && total > 0 ? Math.round(scanned / total * 1000) / 10 : null;
|
|
4375
|
+
emitMeta({
|
|
4376
|
+
type: "tail_coverage",
|
|
4377
|
+
signal: "emotions",
|
|
4378
|
+
complete: false,
|
|
4379
|
+
scanned,
|
|
4380
|
+
total,
|
|
4381
|
+
coverage_pct: coveragePct,
|
|
4382
|
+
pages_scanned: pages,
|
|
4383
|
+
detail: `Scanned ${scanned}${total !== null ? ` of ${total}` : ""} detection(s) in the last day` + `${coveragePct !== null ? ` (${coveragePct}%)` : ""}. /emotions is ranked by score with no ` + "time ordering or cursor, so this follows the highest-scoring detections and " + "lower-scoring ones may not appear. Use --full-scan for completeness (many more " + "requests, up to the API offset ceiling of 10000), or `moda emotions --offset=N` to page manually."
|
|
4384
|
+
});
|
|
4385
|
+
} else if (total !== null) {
|
|
4386
|
+
emitMeta({
|
|
4387
|
+
type: "tail_coverage",
|
|
4388
|
+
signal: "emotions",
|
|
4389
|
+
complete: true,
|
|
4390
|
+
scanned: collected.length,
|
|
4391
|
+
total,
|
|
4392
|
+
coverage_pct: 100,
|
|
4393
|
+
pages_scanned: pages,
|
|
4394
|
+
detail: "Scanned the full window for this poll."
|
|
4395
|
+
});
|
|
4396
|
+
}
|
|
4397
|
+
};
|
|
4398
|
+
if (params.once !== true && context.output.mode === "human") {
|
|
4399
|
+
console.error(`Tailing ${signal} every ${intervalMs / 1000}s (Ctrl-C to stop). One JSON line per new item.`);
|
|
4400
|
+
}
|
|
4401
|
+
for (;; ) {
|
|
4402
|
+
if (signal === "conversations" || signal === "all")
|
|
4403
|
+
await pollConversations();
|
|
4404
|
+
if (signal === "emotions" || signal === "all")
|
|
4405
|
+
await pollEmotions();
|
|
4406
|
+
if (params.once === true)
|
|
4407
|
+
break;
|
|
4408
|
+
if (params.max_events !== undefined && emitted >= params.max_events)
|
|
4409
|
+
break;
|
|
4410
|
+
await new Promise((resolve2) => setTimeout(resolve2, intervalMs));
|
|
4411
|
+
}
|
|
4062
4412
|
}
|
|
4063
4413
|
async function attachWorldState(rows) {
|
|
4064
4414
|
return Promise.all(rows.map(async (row) => {
|
|
@@ -4130,6 +4480,23 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4130
4480
|
case "clusters": {
|
|
4131
4481
|
const args = flagsToArgs(flags);
|
|
4132
4482
|
const params = ClustersSchema.parse(args);
|
|
4483
|
+
if (params.search !== undefined || params.node_id !== undefined) {
|
|
4484
|
+
const query2 = new URLSearchParams;
|
|
4485
|
+
if (params.search !== undefined)
|
|
4486
|
+
query2.set("q", params.search);
|
|
4487
|
+
if (params.node_id !== undefined)
|
|
4488
|
+
query2.set("node_id", params.node_id);
|
|
4489
|
+
if (params.mode)
|
|
4490
|
+
query2.set("mode", params.mode);
|
|
4491
|
+
if (params.limit)
|
|
4492
|
+
query2.set("limit", params.limit.toString());
|
|
4493
|
+
const data2 = await callDataAPI(`/task-clusters/search?${query2.toString()}`);
|
|
4494
|
+
const record = asRecord(data2) ?? {};
|
|
4495
|
+
const matches = Array.isArray(record.matches) ? record.matches : [];
|
|
4496
|
+
const warnings = params.node_id !== undefined && matches.length === 0 ? notFoundWarning("cluster", params.node_id, true) : [];
|
|
4497
|
+
context.output.writeData(data2, warnings.length > 0 ? { warnings } : undefined);
|
|
4498
|
+
break;
|
|
4499
|
+
}
|
|
4133
4500
|
const query = new URLSearchParams;
|
|
4134
4501
|
if (params.parent_id)
|
|
4135
4502
|
query.set("parent_id", params.parent_id);
|
|
@@ -4153,7 +4520,9 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4153
4520
|
query.set("offset", params.offset.toString());
|
|
4154
4521
|
const queryString = query.toString() ? `?${query.toString()}` : "";
|
|
4155
4522
|
const data = await callDataAPI(`/clusters/${params.node_id}/conversations${queryString}`);
|
|
4156
|
-
|
|
4523
|
+
const clusterRecord = asRecord(data) ?? {};
|
|
4524
|
+
const warnings = notFoundWarning("cluster", params.node_id, clusterRecord.cluster === null || clusterRecord.cluster === undefined);
|
|
4525
|
+
context.output.writeData(data, warnings.length > 0 ? { warnings } : undefined);
|
|
4157
4526
|
break;
|
|
4158
4527
|
}
|
|
4159
4528
|
case "conversations": {
|
|
@@ -4211,7 +4580,16 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4211
4580
|
query.set("include_tool_io", "true");
|
|
4212
4581
|
const data = await callDataAPI(`/search?${query.toString()}`);
|
|
4213
4582
|
recordSearchTelemetry(context, data);
|
|
4214
|
-
|
|
4583
|
+
const searchRecord = asRecord(data) ?? {};
|
|
4584
|
+
const degradeReason = asString(searchRecord.degrade_reason);
|
|
4585
|
+
const actualMode = asString(searchRecord.search_mode);
|
|
4586
|
+
const warnings = [];
|
|
4587
|
+
if (degradeReason && degradeReason !== "none") {
|
|
4588
|
+
warnings.push(`Search degraded to keyword mode (${degradeReason}): the requested ${params.mode ?? "hybrid"} ranking did not run, and keyword scores are not comparable to semantic scores. Retry, or use --mode=keyword with exact identifiers.`);
|
|
4589
|
+
} else if (params.mode && actualMode && actualMode !== params.mode && params.mode !== "hybrid") {
|
|
4590
|
+
warnings.push(`Search ran in ${actualMode} mode instead of the requested ${params.mode} mode.`);
|
|
4591
|
+
}
|
|
4592
|
+
context.output.writeData(data, warnings.length > 0 ? { warnings } : undefined);
|
|
4215
4593
|
break;
|
|
4216
4594
|
}
|
|
4217
4595
|
case "world-state": {
|
|
@@ -4220,6 +4598,37 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4220
4598
|
}
|
|
4221
4599
|
const args = flagsToArgs(flags, "conversation_id", positional);
|
|
4222
4600
|
const params = WorldStateSchema.parse(args);
|
|
4601
|
+
if (params.snapshot === true && params.replay === true) {
|
|
4602
|
+
throw new CliInputError("Choose either --snapshot or --replay, not both.", "Usage: moda world-state <conversation_id> [--snapshot --msg-index=N [--block-index=M] [--segment-index=K]] | [--replay --message-count=N [--format=frames|fold-inputs]]");
|
|
4603
|
+
}
|
|
4604
|
+
if (params.snapshot === true) {
|
|
4605
|
+
const query2 = new URLSearchParams;
|
|
4606
|
+
query2.set("msg_index", (params.msg_index ?? 0).toString());
|
|
4607
|
+
if (params.block_index !== undefined)
|
|
4608
|
+
query2.set("block_index", params.block_index.toString());
|
|
4609
|
+
if (params.segment_index !== undefined)
|
|
4610
|
+
query2.set("segment_index", params.segment_index.toString());
|
|
4611
|
+
const data2 = await callDataAPI(`/conversations/${params.conversation_id}/world-state/snapshot?${query2.toString()}`);
|
|
4612
|
+
const snapshotRecord = asRecord(data2) ?? {};
|
|
4613
|
+
const warnings2 = notFoundWarning("world state", params.conversation_id, snapshotRecord.found === false);
|
|
4614
|
+
context.output.writeData(data2, warnings2.length > 0 ? { warnings: warnings2 } : undefined);
|
|
4615
|
+
break;
|
|
4616
|
+
}
|
|
4617
|
+
if (params.replay === true) {
|
|
4618
|
+
const format = params.format ?? "frames";
|
|
4619
|
+
if (format === "frames" && params.message_count === undefined) {
|
|
4620
|
+
throw new CliInputError("--message-count=N is required with --replay in frames mode (the API returns no frames without it). Use --format=fold-inputs for the raw keyframes/events without a count.");
|
|
4621
|
+
}
|
|
4622
|
+
const query2 = new URLSearchParams;
|
|
4623
|
+
query2.set("format", format === "fold-inputs" ? "fold_inputs" : "frames");
|
|
4624
|
+
if (params.message_count !== undefined)
|
|
4625
|
+
query2.set("message_count", params.message_count.toString());
|
|
4626
|
+
const data2 = await callDataAPI(`/conversations/${params.conversation_id}/world-state/replay?${query2.toString()}`);
|
|
4627
|
+
const replayRecord = asRecord(data2) ?? {};
|
|
4628
|
+
const warnings2 = notFoundWarning("world state", params.conversation_id, replayRecord.hasData === false);
|
|
4629
|
+
context.output.writeData(data2, warnings2.length > 0 ? { warnings: warnings2 } : undefined);
|
|
4630
|
+
break;
|
|
4631
|
+
}
|
|
4223
4632
|
const query = new URLSearchParams;
|
|
4224
4633
|
if (params.summary_only === true)
|
|
4225
4634
|
query.set("summary_only", "1");
|
|
@@ -4227,7 +4636,9 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4227
4636
|
query.set("event_limit", params.event_limit.toString());
|
|
4228
4637
|
const queryString = query.toString() ? `?${query.toString()}` : "";
|
|
4229
4638
|
const data = await callDataAPI(`/conversations/${params.conversation_id}/world-state${queryString}`);
|
|
4230
|
-
|
|
4639
|
+
const wsRecord = asRecord(data) ?? {};
|
|
4640
|
+
const warnings = notFoundWarning("world state", params.conversation_id, wsRecord.hasData === false);
|
|
4641
|
+
context.output.writeData(data, warnings.length > 0 ? { warnings } : undefined);
|
|
4231
4642
|
break;
|
|
4232
4643
|
}
|
|
4233
4644
|
case "context": {
|
|
@@ -4243,7 +4654,9 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4243
4654
|
query.set("window", params.window.toString());
|
|
4244
4655
|
const queryString = query.toString() ? `?${query.toString()}` : "";
|
|
4245
4656
|
const data = await callDataAPI(`/conversations/${params.conversation_id}/context${queryString}`);
|
|
4246
|
-
|
|
4657
|
+
const ctxRecord = asRecord(data) ?? {};
|
|
4658
|
+
const warnings = notFoundWarning("conversation", params.conversation_id, asNumber(ctxRecord.total_messages) === 0);
|
|
4659
|
+
context.output.writeData(data, warnings.length > 0 ? { warnings } : undefined);
|
|
4247
4660
|
break;
|
|
4248
4661
|
}
|
|
4249
4662
|
case "audit":
|
|
@@ -4305,7 +4718,13 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4305
4718
|
query.set("offset", params.offset.toString());
|
|
4306
4719
|
const queryString = query.toString() ? `?${query.toString()}` : "";
|
|
4307
4720
|
const data = await callDataAPI(`/tool-failures/${params.tool_name}${queryString}`);
|
|
4308
|
-
|
|
4721
|
+
const tfRecord = asRecord(data) ?? {};
|
|
4722
|
+
const subtypeCount = Array.isArray(tfRecord.subtypes) ? tfRecord.subtypes.length : 0;
|
|
4723
|
+
const exampleCount = Array.isArray(tfRecord.examples) ? tfRecord.examples.length : 0;
|
|
4724
|
+
const warnings = subtypeCount === 0 && exampleCount === 0 ? [
|
|
4725
|
+
`No failures found for tool "${params.tool_name}" in the last ${params.days_back ?? 7} day(s) — the tool may have no failures in this window, or the name may not exist in this tenant. Run \`moda tool-failures\` to list tools with recorded failures, or widen --days-back.`
|
|
4726
|
+
] : [];
|
|
4727
|
+
await printWithOptionalWindows(withToolFailureAnchors(data), "examples", params.include_window === true, params.window ?? 1, context.output, warnings);
|
|
4309
4728
|
break;
|
|
4310
4729
|
}
|
|
4311
4730
|
case "problems": {
|
|
@@ -4325,6 +4744,149 @@ async function runCommand(command, positional, flags, positionals = positional ?
|
|
|
4325
4744
|
context.output.writeData(withLink);
|
|
4326
4745
|
break;
|
|
4327
4746
|
}
|
|
4747
|
+
case "problem": {
|
|
4748
|
+
if (!positional) {
|
|
4749
|
+
throw new CliInputError("<problem_id> is required", "Usage: moda problem <problem_id> [--evidence|--reports|--conversations|--feedback] [--limit=N] [--cursor=TOKEN] [--family=F] [--door=D]");
|
|
4750
|
+
}
|
|
4751
|
+
const args = flagsToArgs(flags, "id", positional);
|
|
4752
|
+
const params = ProblemSchema.parse(args);
|
|
4753
|
+
const views = ["evidence", "reports", "conversations", "feedback"].filter((view2) => params[view2] === true);
|
|
4754
|
+
if (views.length > 1) {
|
|
4755
|
+
throw new CliInputError(`Choose at most one of --evidence, --reports, --conversations, --feedback (got ${views.map((v) => `--${v}`).join(" ")}).`, "Usage: moda problem <problem_id> [--evidence|--reports|--conversations|--feedback]");
|
|
4756
|
+
}
|
|
4757
|
+
const view = views[0];
|
|
4758
|
+
if (view === undefined) {
|
|
4759
|
+
const data2 = await callDataAPI(`/problems/${encodeURIComponent(params.id)}`);
|
|
4760
|
+
const record = asRecord(data2) ?? {};
|
|
4761
|
+
const warnings = record.found === false ? [
|
|
4762
|
+
`No problem found for id "${params.id}" — it may not exist in this tenant, or may have been merged away (the API returns found:false rather than an error).`
|
|
4763
|
+
] : [];
|
|
4764
|
+
context.output.writeData(data2, warnings.length > 0 ? { warnings } : undefined);
|
|
4765
|
+
break;
|
|
4766
|
+
}
|
|
4767
|
+
if (!UUID_RE.test(params.id)) {
|
|
4768
|
+
throw new CliInputError(`--${view} requires a canonical problem UUID; got "${params.id}". Run \`moda problems\` or \`moda problem <id>\` first to resolve the id.`);
|
|
4769
|
+
}
|
|
4770
|
+
const query = new URLSearchParams;
|
|
4771
|
+
if (params.limit)
|
|
4772
|
+
query.set("limit", params.limit.toString());
|
|
4773
|
+
if (params.cursor)
|
|
4774
|
+
query.set("cursor", params.cursor);
|
|
4775
|
+
if (view === "conversations") {
|
|
4776
|
+
if (params.family)
|
|
4777
|
+
query.set("family", params.family);
|
|
4778
|
+
if (params.door)
|
|
4779
|
+
query.set("door", params.door);
|
|
4780
|
+
}
|
|
4781
|
+
const queryString = query.toString() ? `?${query.toString()}` : "";
|
|
4782
|
+
const data = await callDataAPI(`/problems/${params.id}/${view}${queryString}`);
|
|
4783
|
+
context.output.writeData(data);
|
|
4784
|
+
break;
|
|
4785
|
+
}
|
|
4786
|
+
case "problem-feedback": {
|
|
4787
|
+
if (!positional) {
|
|
4788
|
+
throw new CliInputError("<problem_id> is required", `Usage: moda problem-feedback <problem_id> --action=${PROBLEM_FEEDBACK_ACTIONS.join("|")} [--reason=TEXT] [--attribution-id=UUID] [--new-name=NAME]`);
|
|
4789
|
+
}
|
|
4790
|
+
const params = ProblemFeedbackSchema.parse({
|
|
4791
|
+
id: positional,
|
|
4792
|
+
...flags.action !== undefined ? { action: flags.action } : {},
|
|
4793
|
+
...flags.reason !== undefined ? { reason: flags.reason } : {},
|
|
4794
|
+
...flags["attribution-id"] !== undefined ? { attribution_id: flags["attribution-id"] } : {},
|
|
4795
|
+
...flags["new-name"] !== undefined ? { new_name: flags["new-name"] } : {},
|
|
4796
|
+
...flags.actor !== undefined ? { actor: flags.actor } : {}
|
|
4797
|
+
});
|
|
4798
|
+
if (!UUID_RE.test(params.id)) {
|
|
4799
|
+
throw new CliInputError(`Problem id must be a canonical UUID; got "${params.id}".`);
|
|
4800
|
+
}
|
|
4801
|
+
const reason = params.reason?.trim() ?? "";
|
|
4802
|
+
if ((params.action === "dismiss" || params.action === "flag_attribution") && reason === "") {
|
|
4803
|
+
throw new CliInputError(`--reason is required for --action=${params.action}.`);
|
|
4804
|
+
}
|
|
4805
|
+
if (params.action === "rename" && (params.new_name?.trim() ?? "") === "") {
|
|
4806
|
+
throw new CliInputError("--new-name is required for --action=rename.");
|
|
4807
|
+
}
|
|
4808
|
+
if (params.action === "flag_attribution") {
|
|
4809
|
+
if (params.attribution_id === undefined || params.attribution_id.trim() === "") {
|
|
4810
|
+
throw new CliInputError("--attribution-id=<uuid> is required for --action=flag_attribution. Run `moda problem <problem_id> --evidence` to list attribution ids.");
|
|
4811
|
+
}
|
|
4812
|
+
if (!UUID_RE.test(params.attribution_id)) {
|
|
4813
|
+
throw new CliInputError("--attribution-id must be a UUID.");
|
|
4814
|
+
}
|
|
4815
|
+
}
|
|
4816
|
+
const data = await callDataAPI(`/problems/${params.id}/feedback`, {
|
|
4817
|
+
method: "POST",
|
|
4818
|
+
body: JSON.stringify({
|
|
4819
|
+
action: params.action,
|
|
4820
|
+
...reason !== "" ? { reason } : {},
|
|
4821
|
+
...params.attribution_id ? { attribution_id: params.attribution_id } : {},
|
|
4822
|
+
...params.new_name ? { new_display_name: params.new_name } : {},
|
|
4823
|
+
actor: params.actor ?? "moda-cli"
|
|
4824
|
+
}),
|
|
4825
|
+
headers: { "Content-Type": "application/json" }
|
|
4826
|
+
}, { retries: 0 });
|
|
4827
|
+
context.output.writeData(data);
|
|
4828
|
+
break;
|
|
4829
|
+
}
|
|
4830
|
+
case "emotions": {
|
|
4831
|
+
const args = flagsToArgs(flags);
|
|
4832
|
+
const params = EmotionsSchema.parse(args);
|
|
4833
|
+
const query = new URLSearchParams;
|
|
4834
|
+
if (params.days_back)
|
|
4835
|
+
query.set("days_back", params.days_back.toString());
|
|
4836
|
+
if (params.limit)
|
|
4837
|
+
query.set("limit", params.limit.toString());
|
|
4838
|
+
if (params.offset !== undefined)
|
|
4839
|
+
query.set("offset", params.offset.toString());
|
|
4840
|
+
if (params.family)
|
|
4841
|
+
query.set("family", params.family);
|
|
4842
|
+
const queryString = query.toString() ? `?${query.toString()}` : "";
|
|
4843
|
+
const data = await callDataAPI(`/emotions${queryString}`);
|
|
4844
|
+
context.output.writeData(data);
|
|
4845
|
+
break;
|
|
4846
|
+
}
|
|
4847
|
+
case "hallucinations": {
|
|
4848
|
+
const args = flagsToArgs(flags);
|
|
4849
|
+
if (flags["conversation-id"] !== undefined) {
|
|
4850
|
+
args.conversation_id = flags["conversation-id"];
|
|
4851
|
+
}
|
|
4852
|
+
const params = HallucinationsSchema.parse(args);
|
|
4853
|
+
const query = new URLSearchParams;
|
|
4854
|
+
if (params.days_back)
|
|
4855
|
+
query.set("days_back", params.days_back.toString());
|
|
4856
|
+
if (params.limit)
|
|
4857
|
+
query.set("limit", params.limit.toString());
|
|
4858
|
+
if (params.offset !== undefined)
|
|
4859
|
+
query.set("offset", params.offset.toString());
|
|
4860
|
+
if (params.conversation_id)
|
|
4861
|
+
query.set("conversation_id", params.conversation_id);
|
|
4862
|
+
if (params.kind)
|
|
4863
|
+
query.set("kind", params.kind);
|
|
4864
|
+
const queryString = query.toString() ? `?${query.toString()}` : "";
|
|
4865
|
+
const data = await callDataAPI(`/hallucinations${queryString}`);
|
|
4866
|
+
context.output.writeData(data);
|
|
4867
|
+
break;
|
|
4868
|
+
}
|
|
4869
|
+
case "step-scores": {
|
|
4870
|
+
if (!positional) {
|
|
4871
|
+
throw new CliInputError("<conversation_id> is required", "Usage: moda step-scores <conversation_id>");
|
|
4872
|
+
}
|
|
4873
|
+
const params = StepScoresSchema.parse({ conversation_id: positional });
|
|
4874
|
+
const data = await callDataAPI(`/conversations/${encodeURIComponent(params.conversation_id)}/step-scores`);
|
|
4875
|
+
const record = asRecord(data) ?? {};
|
|
4876
|
+
const steps = Array.isArray(record.steps) ? record.steps : [];
|
|
4877
|
+
const segments = Array.isArray(record.segments) ? record.segments : [];
|
|
4878
|
+
const warnings = steps.length === 0 && segments.length === 0 ? [
|
|
4879
|
+
`No step scores found for conversation "${params.conversation_id}" — it may not exist in this tenant, or has not been scored yet.`
|
|
4880
|
+
] : [];
|
|
4881
|
+
context.output.writeData(data, warnings.length > 0 ? { warnings } : undefined);
|
|
4882
|
+
break;
|
|
4883
|
+
}
|
|
4884
|
+
case "tail": {
|
|
4885
|
+
const args = flagsToArgs(flags);
|
|
4886
|
+
const params = TailSchema.parse(args);
|
|
4887
|
+
await runTailCommand(params, context);
|
|
4888
|
+
break;
|
|
4889
|
+
}
|
|
4328
4890
|
case "feedback": {
|
|
4329
4891
|
const note = positionals.join(" ").trim();
|
|
4330
4892
|
if (note === "") {
|
|
@@ -4463,7 +5025,7 @@ var commandRegistry = createCommandRegistry([
|
|
|
4463
5025
|
resetApiRequestCountBeforeRun: true,
|
|
4464
5026
|
telemetry: "result",
|
|
4465
5027
|
handler: async (context) => {
|
|
4466
|
-
const { runInit } = await import("./index-
|
|
5028
|
+
const { runInit } = await import("./index-efv643ec.js");
|
|
4467
5029
|
if (context.outputMode === "agent-stream") {
|
|
4468
5030
|
context.output.writeEvent({
|
|
4469
5031
|
event: "started",
|
|
@@ -4504,7 +5066,7 @@ var commandRegistry = createCommandRegistry([
|
|
|
4504
5066
|
resetApiRequestCountBeforeRun: true,
|
|
4505
5067
|
telemetry: "result-and-error",
|
|
4506
5068
|
handler: async (context) => {
|
|
4507
|
-
const { runProvision } = await import("./provision-
|
|
5069
|
+
const { runProvision } = await import("./provision-r0cq3g2v.js");
|
|
4508
5070
|
return runProvision(context, {
|
|
4509
5071
|
resumeCommand: buildResumeCommand("provision", context.rawFlags)
|
|
4510
5072
|
});
|
|
@@ -4617,8 +5179,8 @@ var commandRegistry = createCommandRegistry([
|
|
|
4617
5179
|
name: "harness",
|
|
4618
5180
|
description: "Analyze, validate, approve, sync, delete, and explain the local multi-agent harness graph",
|
|
4619
5181
|
examples: [
|
|
4620
|
-
"moda harness analyze --summary",
|
|
4621
|
-
"moda harness analyze --remote",
|
|
5182
|
+
"moda harness analyze --experimental --summary",
|
|
5183
|
+
"moda harness analyze --experimental --remote",
|
|
4622
5184
|
"moda harness pull --sync",
|
|
4623
5185
|
"moda harness approve --yes",
|
|
4624
5186
|
"moda harness sync --from-report .moda/harness-report.json",
|
|
@@ -4775,6 +5337,65 @@ var commandRegistry = createCommandRegistry([
|
|
|
4775
5337
|
examples: ["moda problems", "moda problems --days-back=7 --limit=10"],
|
|
4776
5338
|
...dataApiDefaults
|
|
4777
5339
|
}),
|
|
5340
|
+
legacyCommand({
|
|
5341
|
+
name: "problem",
|
|
5342
|
+
description: "Open one Problem: dossier, or --evidence/--reports/--conversations/--feedback pages",
|
|
5343
|
+
examples: [
|
|
5344
|
+
"moda problem <problem_id>",
|
|
5345
|
+
"moda problem <problem_id> --evidence --limit=10",
|
|
5346
|
+
"moda problem <problem_id> --reports",
|
|
5347
|
+
"moda problem <problem_id> --conversations --family=tool_failure",
|
|
5348
|
+
"moda problem <problem_id> --feedback"
|
|
5349
|
+
],
|
|
5350
|
+
...dataApiDefaults
|
|
5351
|
+
}),
|
|
5352
|
+
legacyCommand({
|
|
5353
|
+
name: "problem-feedback",
|
|
5354
|
+
description: "Submit Problem feedback: mark fixed, dismiss, rename, or flag a bad attribution",
|
|
5355
|
+
examples: [
|
|
5356
|
+
"moda problem-feedback <problem_id> --action=mark_fixed",
|
|
5357
|
+
'moda problem-feedback <problem_id> --action=dismiss --reason="not actionable"',
|
|
5358
|
+
'moda problem-feedback <problem_id> --action=rename --new-name="Better title"',
|
|
5359
|
+
'moda problem-feedback <problem_id> --action=flag_attribution --attribution-id=<uuid> --reason="wrong conversation"'
|
|
5360
|
+
],
|
|
5361
|
+
...dataApiDefaults,
|
|
5362
|
+
mutability: "write"
|
|
5363
|
+
}),
|
|
5364
|
+
legacyCommand({
|
|
5365
|
+
name: "emotions",
|
|
5366
|
+
description: "Multi-family emotion detections (frustration, sadness, confusion, anxiety, trust, positive)",
|
|
5367
|
+
examples: [
|
|
5368
|
+
"moda emotions --days-back=7",
|
|
5369
|
+
"moda emotions --family=confusion --limit=20"
|
|
5370
|
+
],
|
|
5371
|
+
...dataApiDefaults
|
|
5372
|
+
}),
|
|
5373
|
+
legacyCommand({
|
|
5374
|
+
name: "hallucinations",
|
|
5375
|
+
description: "Grounding detections: contradicted/verified outputs with rule breakdown",
|
|
5376
|
+
examples: [
|
|
5377
|
+
"moda hallucinations --days-back=30",
|
|
5378
|
+
"moda hallucinations --kind=contradicted --limit=20",
|
|
5379
|
+
"moda hallucinations --conversation-id=<id>"
|
|
5380
|
+
],
|
|
5381
|
+
...dataApiDefaults
|
|
5382
|
+
}),
|
|
5383
|
+
legacyCommand({
|
|
5384
|
+
name: "step-scores",
|
|
5385
|
+
description: "Graph-PRM step scores for a conversation (per-segment curves, first bad step, rollup)",
|
|
5386
|
+
examples: ["moda step-scores <conversation_id>"],
|
|
5387
|
+
...dataApiDefaults
|
|
5388
|
+
}),
|
|
5389
|
+
legacyCommand({
|
|
5390
|
+
name: "tail",
|
|
5391
|
+
description: "Live-tail new conversations and detections as NDJSON (one JSON line per item)",
|
|
5392
|
+
examples: [
|
|
5393
|
+
"moda tail",
|
|
5394
|
+
"moda tail --signal=emotions --interval=30",
|
|
5395
|
+
"moda tail --once"
|
|
5396
|
+
],
|
|
5397
|
+
...dataApiDefaults
|
|
5398
|
+
}),
|
|
4778
5399
|
legacyCommand({
|
|
4779
5400
|
name: "feedback",
|
|
4780
5401
|
description: "Flag wrong/missing data or CLI quirks to the Moda team",
|
|
@@ -4888,7 +5509,12 @@ async function dispatchParsedCommand(parsed, startedAt = Date.now()) {
|
|
|
4888
5509
|
return 0;
|
|
4889
5510
|
}
|
|
4890
5511
|
if (command === "--help" || command === "-h" || flags.help === "true") {
|
|
4891
|
-
|
|
5512
|
+
const helpTarget = command && command !== "--help" && command !== "-h" ? commandRegistry.find(command) : undefined;
|
|
5513
|
+
if (helpTarget) {
|
|
5514
|
+
printCommandHelp(helpTarget);
|
|
5515
|
+
} else {
|
|
5516
|
+
printHelp();
|
|
5517
|
+
}
|
|
4892
5518
|
return 0;
|
|
4893
5519
|
}
|
|
4894
5520
|
if (command === "--json-schemas" || flags["json-schemas"] === "true") {
|
|
@@ -5069,5 +5695,6 @@ export {
|
|
|
5069
5695
|
commandRegistry,
|
|
5070
5696
|
classifyCliError,
|
|
5071
5697
|
buildResumeCommand,
|
|
5698
|
+
TAIL_LIMITS,
|
|
5072
5699
|
CliInputError
|
|
5073
5700
|
};
|