@moda-ai/cli 1.25.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-jntzpem2.js → cli-g931ntph.js} +2 -2
- package/dist/{cli-tdttb4cv.js → cli-pq6rte0w.js} +2 -2
- package/dist/{cli-kqvjt12y.js → cli-xf5nx5hc.js} +3 -3
- package/dist/cli.js +650 -23
- package/dist/{harness-github-actions-rjyc9a7z.js → harness-github-actions-ct8fwzdr.js} +2 -2
- package/dist/{index-fm9ey3ks.js → index-efv643ec.js} +5 -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/README.md
CHANGED
|
@@ -34,49 +34,87 @@ Get your API key from [moda.dev/settings](https://moda.dev/settings).
|
|
|
34
34
|
|
|
35
35
|
## Usage
|
|
36
36
|
|
|
37
|
+
Find things by meaning, then drill in:
|
|
38
|
+
|
|
37
39
|
```bash
|
|
38
|
-
moda
|
|
39
|
-
moda
|
|
40
|
+
moda search "user wants a refund" --mode=semantic # Semantic/keyword/hybrid message search
|
|
41
|
+
moda search "stripe.charges.create" --mode=keyword # Exact identifiers, incl. tool calls
|
|
42
|
+
moda search "checkout" --include-tool-io # Search tool inputs/outputs too
|
|
43
|
+
moda context <conversation_id> --msg-index=5 # Read the exact turn
|
|
44
|
+
moda audit <conversation_id|trace_id> # Raw span/trace audit
|
|
45
|
+
```
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
moda clusters --parent-id=node-abc # Drill into a category
|
|
47
|
+
Production intelligence:
|
|
43
48
|
|
|
44
|
-
|
|
49
|
+
```bash
|
|
50
|
+
moda overview --days-back=30 # Health briefing (KPIs, clusters, activity)
|
|
51
|
+
moda ask "what should I fix first?" # Grounded answer with evidence
|
|
52
|
+
moda investigate # Ranked issues worth fixing
|
|
53
|
+
moda problems # Cross-signal Problems by root cause
|
|
54
|
+
moda problem <problem_id> # One Problem: full dossier
|
|
55
|
+
moda problem <problem_id> --evidence # ...attribution evidence (keyset paged)
|
|
56
|
+
moda problem <problem_id> --reports # ...investigation reports
|
|
57
|
+
moda problem <problem_id> --conversations # ...affected conversations
|
|
58
|
+
moda problem-feedback <problem_id> --action=mark_fixed # Close the loop from the terminal
|
|
59
|
+
```
|
|
45
60
|
|
|
46
|
-
|
|
47
|
-
moda conversations --time-range=24h # Filter by time
|
|
48
|
-
moda conversations --environment=production # Filter by environment
|
|
61
|
+
Signals:
|
|
49
62
|
|
|
50
|
-
|
|
51
|
-
moda
|
|
63
|
+
```bash
|
|
64
|
+
moda emotions --family=confusion # Multi-family emotion detections
|
|
65
|
+
moda hallucinations --kind=contradicted # Grounding: contradicted/verified outputs
|
|
66
|
+
moda frustrations --include-window --window=1 # Legacy single-family frustration view
|
|
67
|
+
moda tool-failures # Tool failure overview
|
|
68
|
+
moda tool-failure-detail <tool_name> --include-window
|
|
69
|
+
moda step-scores <conversation_id> # Graph-PRM per-step reward curves
|
|
70
|
+
```
|
|
52
71
|
|
|
53
|
-
|
|
54
|
-
moda frustrations --days-back=14 --limit=20
|
|
55
|
-
moda frustrations --include-window --window=1 # Attach 3-message slice per row
|
|
72
|
+
Conversations, clusters, memory:
|
|
56
73
|
|
|
57
|
-
|
|
58
|
-
moda
|
|
59
|
-
moda
|
|
74
|
+
```bash
|
|
75
|
+
moda conversations --search="error" --environment=production
|
|
76
|
+
moda clusters # Walk the topic hierarchy
|
|
77
|
+
moda clusters --search="billing disputes" # Find a cluster by meaning
|
|
78
|
+
moda world-state <conversation_id> # Agent memory (slots/threads/events)
|
|
79
|
+
moda world-state <id> --snapshot --msg-index=42 # What the agent believed at turn 42
|
|
80
|
+
moda world-state <id> --replay --message-count=50 # State evolution frame by frame
|
|
81
|
+
```
|
|
60
82
|
|
|
61
|
-
|
|
62
|
-
moda feedback "..." --category=missing_data --conversation-id=<id>
|
|
83
|
+
Live tail (one JSON line per new item — `tail -f` for your agent):
|
|
63
84
|
|
|
64
|
-
|
|
85
|
+
```bash
|
|
86
|
+
moda tail # New conversations, every 15s
|
|
87
|
+
moda tail --signal=all --interval=30 # Conversations + emotion detections
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Prompt management:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
65
93
|
moda prompts init # Create .moda/prompts.yml
|
|
66
94
|
moda prompts status # Read-only local prompt status
|
|
67
|
-
moda prompts diff # Read-only local prompt diff/status
|
|
68
95
|
moda prompts sync # Upload changed prompt versions
|
|
69
|
-
moda prompts sync --watch # Sync when local prompt hashes change
|
|
70
96
|
moda prompts promote support.triage --label=prod --version=pver_abc123
|
|
71
97
|
```
|
|
72
98
|
|
|
73
|
-
|
|
99
|
+
Feedback:
|
|
74
100
|
|
|
75
101
|
```bash
|
|
76
|
-
moda
|
|
77
|
-
moda frustrations | jq '.frustrations[].primary_cause'
|
|
102
|
+
moda feedback "cluster label looks wrong" --category=bad_cluster_label
|
|
78
103
|
```
|
|
79
104
|
|
|
105
|
+
Output adapts to where it runs: piped/CI/agent environments get schema-versioned
|
|
106
|
+
JSON (`moda.agent.v1`), an interactive terminal gets human prose. Force a mode
|
|
107
|
+
with `--json`, `--agent`, or `--human`. In JSON modes, pipe to `jq`:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
moda overview | jq '.data.metrics'
|
|
111
|
+
moda frustrations | jq '.data.frustrations[].primary_cause'
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Every command supports `moda <command> --help` for scoped usage, and
|
|
115
|
+
`moda manifest --json` describes the full machine protocol (commands, exit
|
|
116
|
+
codes, event schema) for agents.
|
|
117
|
+
|
|
80
118
|
## Prompt Management
|
|
81
119
|
|
|
82
120
|
Prompt management is code-first. Keep prompts in `prompts/**/*.prompt.md` or
|
|
@@ -1301,6 +1301,19 @@ function createAgentEvent(command, event, state) {
|
|
|
1301
1301
|
};
|
|
1302
1302
|
return redactDeep(stamped);
|
|
1303
1303
|
}
|
|
1304
|
+
function formatValidationMessage(error) {
|
|
1305
|
+
const issues = error.issues;
|
|
1306
|
+
if (error.name !== "ZodError" || !Array.isArray(issues) || issues.length === 0) {
|
|
1307
|
+
return error.message;
|
|
1308
|
+
}
|
|
1309
|
+
const parts = issues.map((issue) => {
|
|
1310
|
+
const record = issue;
|
|
1311
|
+
const path = Array.isArray(record.path) && record.path.length > 0 ? record.path.join(".") : "input";
|
|
1312
|
+
const message = typeof record.message === "string" ? record.message : "is invalid";
|
|
1313
|
+
return `${path}: ${message}`;
|
|
1314
|
+
});
|
|
1315
|
+
return `Validation error — ${parts.join("; ")}`;
|
|
1316
|
+
}
|
|
1304
1317
|
function errorToAgentError(error) {
|
|
1305
1318
|
if (error instanceof CliInputRequiredError) {
|
|
1306
1319
|
return {
|
|
@@ -1344,7 +1357,7 @@ function errorToAgentError(error) {
|
|
|
1344
1357
|
if (error instanceof CliInputError || error.name === "ZodError") {
|
|
1345
1358
|
return {
|
|
1346
1359
|
code: "input_error",
|
|
1347
|
-
message: error
|
|
1360
|
+
message: formatValidationMessage(error),
|
|
1348
1361
|
cause: "usage" in error && typeof error.usage === "string" ? error.usage : undefined,
|
|
1349
1362
|
suggested_commands: "usage" in error && typeof error.usage === "string" ? extractSuggestedCommands(error.usage) : undefined,
|
|
1350
1363
|
blocking: true
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
resolveTenantId,
|
|
24
24
|
shouldInventoryFile,
|
|
25
25
|
stringOption
|
|
26
|
-
} from "./cli-
|
|
26
|
+
} from "./cli-59yacef3.js";
|
|
27
27
|
|
|
28
28
|
// src/api-client.ts
|
|
29
29
|
var REQUEST_TIMEOUT_MS = 30000;
|
|
@@ -3338,7 +3338,7 @@ async function runHarnessCommand(context) {
|
|
|
3338
3338
|
elapsed_ms: Date.now() - context.startedAt
|
|
3339
3339
|
});
|
|
3340
3340
|
if (context.flags["github-actions"] === "true") {
|
|
3341
|
-
const { runGithubActionsAnalyze } = await import("./harness-github-actions-
|
|
3341
|
+
const { runGithubActionsAnalyze } = await import("./harness-github-actions-ct8fwzdr.js");
|
|
3342
3342
|
const result = await runGithubActionsAnalyze(rootDir, {
|
|
3343
3343
|
writeReport: (report2) => {
|
|
3344
3344
|
const normalized = normalizeHarnessReport(report2);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
authFetch,
|
|
3
3
|
getCachedLoginContext
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-9xw5bq1j.js";
|
|
5
5
|
import {
|
|
6
6
|
CliAccessError,
|
|
7
7
|
CliInputError,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
resolveTenantIdFromActiveProfile,
|
|
16
16
|
saveCliConfig,
|
|
17
17
|
writeSecretJsonFile
|
|
18
|
-
} from "./cli-
|
|
18
|
+
} from "./cli-59yacef3.js";
|
|
19
19
|
|
|
20
20
|
// src/init/tenant.ts
|
|
21
21
|
import { randomUUID } from "node:crypto";
|
|
@@ -11,11 +11,11 @@ import {
|
|
|
11
11
|
renderStatusHuman,
|
|
12
12
|
scanHarness,
|
|
13
13
|
validateHarnessReport
|
|
14
|
-
} from "./cli-
|
|
14
|
+
} from "./cli-g931ntph.js";
|
|
15
15
|
import {
|
|
16
16
|
isAuthSessionValid,
|
|
17
17
|
loadAuthSession
|
|
18
|
-
} from "./cli-
|
|
18
|
+
} from "./cli-9xw5bq1j.js";
|
|
19
19
|
import {
|
|
20
20
|
CliInputError,
|
|
21
21
|
createCommandContext,
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
resolveTenantId,
|
|
32
32
|
secretFilePathFromRef,
|
|
33
33
|
validateConfig
|
|
34
|
-
} from "./cli-
|
|
34
|
+
} from "./cli-59yacef3.js";
|
|
35
35
|
|
|
36
36
|
// src/prompts.ts
|
|
37
37
|
import { execFileSync } from "node:child_process";
|
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|
|
|
@@ -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
|
});
|
|
@@ -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
|
};
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
runPromptSync,
|
|
10
10
|
runSkillSync,
|
|
11
11
|
upsertSkillManifestRecord
|
|
12
|
-
} from "./cli-
|
|
12
|
+
} from "./cli-xf5nx5hc.js";
|
|
13
13
|
import {
|
|
14
14
|
codingAgentDisplayName,
|
|
15
15
|
describeCodingAgentEvent,
|
|
@@ -27,14 +27,14 @@ import {
|
|
|
27
27
|
runHarnessCommand,
|
|
28
28
|
startRemoteAnalyze,
|
|
29
29
|
stripAnsi
|
|
30
|
-
} from "./cli-
|
|
30
|
+
} from "./cli-g931ntph.js";
|
|
31
31
|
import {
|
|
32
32
|
selectTenantAndCreateKey
|
|
33
|
-
} from "./cli-
|
|
33
|
+
} from "./cli-pq6rte0w.js";
|
|
34
34
|
import {
|
|
35
35
|
loadToken,
|
|
36
36
|
login
|
|
37
|
-
} from "./cli-
|
|
37
|
+
} from "./cli-9xw5bq1j.js";
|
|
38
38
|
import {
|
|
39
39
|
CliAuthError,
|
|
40
40
|
CliInputError,
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
createCommandContext,
|
|
43
43
|
resolveIngestUrl,
|
|
44
44
|
resolveModaBaseUrl
|
|
45
|
-
} from "./cli-
|
|
45
|
+
} from "./cli-59yacef3.js";
|
|
46
46
|
|
|
47
47
|
// src/init/index.ts
|
|
48
48
|
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
selectTenantAndCreateKey
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-pq6rte0w.js";
|
|
4
4
|
import {
|
|
5
5
|
loadAuthSession
|
|
6
|
-
} from "./cli-
|
|
6
|
+
} from "./cli-9xw5bq1j.js";
|
|
7
7
|
import {
|
|
8
8
|
CliAuthError,
|
|
9
9
|
resolveIngestUrl,
|
|
10
10
|
resolveModaBaseUrl,
|
|
11
11
|
stringOption
|
|
12
|
-
} from "./cli-
|
|
12
|
+
} from "./cli-59yacef3.js";
|
|
13
13
|
|
|
14
14
|
// src/provision.ts
|
|
15
15
|
async function runProvision(context, options = {}) {
|
package/package.json
CHANGED
package/skills/moda-cli/SKILL.md
CHANGED
|
@@ -16,7 +16,7 @@ or promote remote state (`prompts sync`, `prompts promote`).
|
|
|
16
16
|
|
|
17
17
|
**`moda search` is the primary way to find anything by content.** It runs
|
|
18
18
|
message-grain semantic, keyword, or hybrid retrieval across every message —
|
|
19
|
-
including tool calls and tool results — and returns ranked snippets with
|
|
19
|
+
including tool calls and tool results with `--include-tool-io` — and returns ranked snippets with
|
|
20
20
|
direct anchors (`conversation_id` + `message_index`) you can hand straight to
|
|
21
21
|
`moda context`. Reach for it first whenever the question is "where did X
|
|
22
22
|
happen?" or "find conversations/tool calls about Y". Use `moda conversations`
|
|
@@ -55,9 +55,9 @@ Optional:
|
|
|
55
55
|
|
|
56
56
|
- `MODA_BASE_URL` defaults to `https://moda.dev`; override only for
|
|
57
57
|
self-hosted or staging.
|
|
58
|
-
- `MODA_SKILL_VERSION` — export to `2.
|
|
58
|
+
- `MODA_SKILL_VERSION` — export to `2.2.0` so search-adoption telemetry can
|
|
59
59
|
attribute usage to this skill version. Set it once per session:
|
|
60
|
-
`export MODA_SKILL_VERSION=2.
|
|
60
|
+
`export MODA_SKILL_VERSION=2.2.0`.
|
|
61
61
|
|
|
62
62
|
## Setup
|
|
63
63
|
|
|
@@ -391,9 +391,10 @@ moda search "checkout error" --time-range=7d --limit=10
|
|
|
391
391
|
moda search "stripe.charges.create failed" --user-id=<id>
|
|
392
392
|
```
|
|
393
393
|
|
|
394
|
-
Searches conversation messages at message grain
|
|
395
|
-
tool results** (tool name, input arguments, and
|
|
396
|
-
so it finds where an agent *did* something, not just where
|
|
394
|
+
Searches conversation messages at message grain. Pass `--include-tool-io` to
|
|
395
|
+
also search **tool calls and tool results** (tool name, input arguments, and
|
|
396
|
+
output previews), so it finds where an agent *did* something, not just where
|
|
397
|
+
it talked about it — tool-IO search is opt-in, not the default.
|
|
397
398
|
|
|
398
399
|
Flags: `--mode=keyword|semantic|hybrid` (default `hybrid`), `--user-id`,
|
|
399
400
|
`--time-range` (`all|1h|3d|7d|24h|30d|90d`, default `all`), `--limit` (1–100,
|
|
@@ -710,9 +711,10 @@ moda feedback "a cancelled call is counted as a tool failure" \
|
|
|
710
711
|
## Validation — how to know it worked
|
|
711
712
|
|
|
712
713
|
- Every successful command exits 0 and prints valid JSON to stdout.
|
|
713
|
-
- `moda ask`
|
|
714
|
-
|
|
715
|
-
exit `1` as the only hard error (see
|
|
714
|
+
- `moda ask` may exit `3` (degraded): a real, lower-trust answer synthesized
|
|
715
|
+
from local evidence — not a failure (`investigate`/`failures` always exit
|
|
716
|
+
`0` on success). Treat exit `1` as the only hard error (see
|
|
717
|
+
[Agent protocol](#agent-protocol)).
|
|
716
718
|
- Errors print to stderr with non-zero exit. Check stderr before claiming
|
|
717
719
|
success.
|
|
718
720
|
- If the JSON has `"conversations": []` or `"frustrations": []`, the call
|
|
@@ -768,13 +770,22 @@ npx: `npx -p @moda-ai/cli moda <command>`.
|
|
|
768
770
|
| `moda overview` | Dashboard KPIs, top clusters, recent activity |
|
|
769
771
|
| `moda ask "<question>"` | Natural-language production/harness answer with evidence (exit `3` = degraded local fallback) |
|
|
770
772
|
| `moda investigate` | Rank production issues with evidence + next commands |
|
|
771
|
-
| `moda clusters` | Browse topic cluster hierarchy |
|
|
773
|
+
| `moda clusters` | Browse topic cluster hierarchy; `--search="q"` finds clusters by meaning, `--node-id=ID` resolves a deep link |
|
|
772
774
|
| `moda cluster-conversations <node_id>` | Conversations in a cluster |
|
|
773
775
|
| `moda conversations` | List/filter conversations by structured fields |
|
|
774
776
|
| `moda context <conversation_id>` | Windowed message context (max 5 per side) |
|
|
775
|
-
| `moda frustrations` | User frustration detections with evidence |
|
|
777
|
+
| `moda frustrations` | User frustration detections with evidence (legacy single-family; prefer `emotions`) |
|
|
778
|
+
| `moda emotions` | Multi-family emotion detections: frustration, sadness, confusion, anxiety, trust, positive (`--family=F`, limit 1–20) |
|
|
779
|
+
| `moda hallucinations` | Grounding detections: contradicted/verified outputs, rule breakdown (`--kind=contradicted\|verified`, `--conversation-id=ID`) |
|
|
776
780
|
| `moda tool-failures` | Tool failure overview |
|
|
777
781
|
| `moda tool-failure-detail <tool_name>` | Per-tool failure breakdown + examples |
|
|
782
|
+
| `moda problems` | Rank cross-signal Problems by root cause (what to fix first) |
|
|
783
|
+
| `moda problem <problem_id>` | One Problem: dossier, or `--evidence`/`--reports`/`--conversations`/`--feedback` pages (`--limit` 1–50, `--cursor` verbatim keyset token) |
|
|
784
|
+
| `moda problem-feedback <problem_id>` | Write: `--action=mark_fixed\|dismiss\|flag_attribution\|rename`. `--reason` required for dismiss/flag_attribution; `--new-name` for rename; `--attribution-id` (UUID) required for flag_attribution |
|
|
785
|
+
| `moda step-scores <conversation_id>` | Graph-PRM step scores: per-segment curves, first bad step, rollup |
|
|
786
|
+
| `moda world-state <conversation_id>` | Agent memory: slots/threads/events; `--summary-only`; `--snapshot --msg-index=N` (state at a turn); `--replay --message-count=N` (state over time) |
|
|
787
|
+
| `moda failures` | Production failures worth fixing first |
|
|
788
|
+
| `moda tail` | Live tail: one JSON line per new conversation/detection (`--signal=conversations\|emotions\|all`, `--interval=N`, `--once`, `--max-events=N`). **Emotions caveat:** `/emotions` is ranked by score with no time ordering or cursor, so the tail follows the *highest-scoring* detections rather than everything; each poll emits a `tail_coverage` line with `scanned`/`total`/`coverage_pct`/`complete`. Pass `--full-scan` for a complete window scan (many more requests, capped by the API's offset ceiling of 10000). |
|
|
778
789
|
| `moda feedback "<note>"` | Flag wrong/missing data or CLI quirks to the Moda team |
|
|
779
790
|
| `moda prompts status` | Read-only local prompt status |
|
|
780
791
|
| `moda prompts diff` | Read-only local prompt diff/status |
|