@moda-ai/cli 1.55.24 → 1.55.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-b4tdzy9q.js → agent-t7dxmpdq.js} +2 -2
- package/dist/{cli-rt26sbz7.js → cli-m0045npe.js} +3 -0
- package/dist/{cli-y2ty9vtv.js → cli-m805msp6.js} +203 -16
- package/dist/{cli-vg88mg20.js → cli-mebf5vtr.js} +1 -1
- package/dist/cli.js +14 -11
- package/dist/{doctor-3r19ry92.js → doctor-q21mr5ya.js} +2 -2
- package/dist/{fix-exvxzvtz.js → fix-0qdtdz5h.js} +1 -1
- package/dist/{index-e1pf1qz2.js → index-4b1ybz51.js} +3 -3
- package/dist/{prompts-cvzt994j.js → prompts-3h5jcjrw.js} +1 -1
- package/dist/{skills-tadh1jss.js → skills-vq3njc8a.js} +1 -1
- package/dist/{sync-g8wtecz1.js → sync-hq9dpqpr.js} +2 -2
- package/package.json +1 -1
- package/skills/integration/index.json +2 -2
- package/skills/moda-cli/SKILL.md +4 -1
- package/skills/moda-cli/references/production-intelligence.md +3 -2
- package/skills/moda-cli/references/prompts.md +6 -2
|
@@ -70,6 +70,9 @@ function renderFixesBlock() {
|
|
|
70
70
|
"- Propose the change in the code, prompt, tool definition, or skill that",
|
|
71
71
|
" causes the issue, and cite the Moda problem or trace ids it is based on",
|
|
72
72
|
" in the PR description.",
|
|
73
|
+
"- Test a prompt change on the Problem's failing traces before it ships (a paid",
|
|
74
|
+
" replay; prompt changes only today): `moda prompts ab --problem=<problem_id>",
|
|
75
|
+
" --baseline=<prompt_key> --candidate=<edited prompt file>`.",
|
|
73
76
|
"- Prompts live in `.moda/prompts.yml` (version 2: `prompt_paths` globs plus",
|
|
74
77
|
" `sources:` mappings for prompts defined in code); after `moda prompts sync`,",
|
|
75
78
|
" pass `prompt: lock.prompts['<key>']` to createThinkTelemetry/createAI7Telemetry.",
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
createCommandContext
|
|
18
18
|
} from "./cli-qvg26twp.js";
|
|
19
19
|
import {
|
|
20
|
+
UUID_RE,
|
|
20
21
|
assertFlags,
|
|
21
22
|
assertPositionals
|
|
22
23
|
} from "./cli-dn91fb56.js";
|
|
@@ -5561,6 +5562,9 @@ var DEFAULT_SEEDS_PER_CASE = 3;
|
|
|
5561
5562
|
var DEFAULT_AUTO_GENERATE_CASES = 5;
|
|
5562
5563
|
var MAX_AUTO_GENERATE_CASES = 100;
|
|
5563
5564
|
var MAX_REPLAY_TRACES = 100;
|
|
5565
|
+
var DEFAULT_PROBLEM_MAX_CASES = 10;
|
|
5566
|
+
var PROBLEM_PAGE_LIMIT = 50;
|
|
5567
|
+
var PROBLEM_MAX_PAGES = 4;
|
|
5564
5568
|
var PLAYOUT_CONFIRM_THRESHOLD = 200;
|
|
5565
5569
|
var REPLAY_MODEL_ALLOWLIST = [
|
|
5566
5570
|
"openai/gpt-5.6-luna",
|
|
@@ -5589,7 +5593,7 @@ async function runPromptAb(flags, profileOptions, context) {
|
|
|
5589
5593
|
const baselineSource = flags.baseline || flags["baseline-file"] || flags["baseline-key"];
|
|
5590
5594
|
const candidateSource = flags.candidate || flags["candidate-file"] || flags["candidate-key"];
|
|
5591
5595
|
if (!baselineSource || !candidateSource) {
|
|
5592
|
-
throw new Error("Usage: moda prompts ab --baseline=<file|key> --candidate=<file|key> [--auto-generate [--cases=1-100]|--set-id=ID|--traces=id1,id2[@msg_index] (max 100)] [--seeds=1-10] [--model=<slug>] [--yes] (legacy alias: --conversations=)");
|
|
5596
|
+
throw new Error("Usage: moda prompts ab --baseline=<file|key> --candidate=<file|key> [--auto-generate [--cases=1-100]|--set-id=ID|--traces=id1,id2[@msg_index] (max 100)|--problem=<problem_id> [--max-cases=1-100]] [--seeds=1-10] [--model=<slug>] [--yes] (legacy alias: --conversations=)");
|
|
5593
5597
|
}
|
|
5594
5598
|
assertArmSourceShape(baselineSource, "baseline");
|
|
5595
5599
|
assertArmSourceShape(candidateSource, "candidate");
|
|
@@ -5599,6 +5603,10 @@ async function runPromptAb(flags, profileOptions, context) {
|
|
|
5599
5603
|
const pollIntervalMs = parsePositiveInt(flags["poll-interval"], DEFAULT_POLL_INTERVAL_MS, 120000, "--poll-interval");
|
|
5600
5604
|
const assistantModel = assertAllowedModel(flags.model ?? flags["assistant-model"], flags, "--model");
|
|
5601
5605
|
const plan = await planReplaySet(flags, tenantId, profileOptions);
|
|
5606
|
+
const problemCases = plan.kind === "traces" ? plan.problem : undefined;
|
|
5607
|
+
if (problemCases && context.outputMode === "human") {
|
|
5608
|
+
process.stderr.write(describeProblemCases(problemCases));
|
|
5609
|
+
}
|
|
5602
5610
|
const plannedPlayouts = plan.cases === null ? null : plan.cases * seedsPerCase * 2;
|
|
5603
5611
|
assertPlayoutBudget(plannedPlayouts, { seedsPerCase, cases: plan.cases }, context.yes, rerunWithYes("prompts ab", [], flags));
|
|
5604
5612
|
if (context.outputMode === "human") {
|
|
@@ -5644,6 +5652,7 @@ async function runPromptAb(flags, profileOptions, context) {
|
|
|
5644
5652
|
if (noWait) {
|
|
5645
5653
|
context.output.writeData({
|
|
5646
5654
|
replaySetId,
|
|
5655
|
+
...problemCases ? { problemCases } : {},
|
|
5647
5656
|
...caseSources ? { caseSources } : {},
|
|
5648
5657
|
...warnings.length ? { warnings } : {},
|
|
5649
5658
|
runId: enqueue.runId,
|
|
@@ -5673,6 +5682,7 @@ async function runPromptAb(flags, profileOptions, context) {
|
|
|
5673
5682
|
}
|
|
5674
5683
|
const result = {
|
|
5675
5684
|
replaySetId,
|
|
5685
|
+
...problemCases ? { problemCases } : {},
|
|
5676
5686
|
...caseSources ? { caseSources } : {},
|
|
5677
5687
|
...warnings.length ? { warnings } : {},
|
|
5678
5688
|
plannedPlayouts,
|
|
@@ -5801,6 +5811,9 @@ function resolvePromptArmSpec(source, label, opts = {}) {
|
|
|
5801
5811
|
};
|
|
5802
5812
|
}
|
|
5803
5813
|
async function planReplaySet(flags, tenantId, profileOptions) {
|
|
5814
|
+
const problemPlan = await planProblemCases(flags);
|
|
5815
|
+
if (problemPlan)
|
|
5816
|
+
return problemPlan;
|
|
5804
5817
|
const existingSetId = (flags["set-id"] || flags["replay-set-id"] || "").trim();
|
|
5805
5818
|
if (existingSetId) {
|
|
5806
5819
|
const snapshot = await fetchReplaySetCases(tenantId, existingSetId, profileOptions);
|
|
@@ -5821,6 +5834,151 @@ async function planReplaySet(flags, tenantId, profileOptions) {
|
|
|
5821
5834
|
cases: parseCappedInt(flags.cases ?? flags["case-count"], DEFAULT_AUTO_GENERATE_CASES, MAX_AUTO_GENERATE_CASES, "--cases")
|
|
5822
5835
|
};
|
|
5823
5836
|
}
|
|
5837
|
+
var PROBLEM_CASE_CONFLICTS = [
|
|
5838
|
+
["traces", "--traces"],
|
|
5839
|
+
["conversations", "--conversations"],
|
|
5840
|
+
["conversation-ids", "--conversation-ids"],
|
|
5841
|
+
["set-id", "--set-id"],
|
|
5842
|
+
["replay-set-id", "--replay-set-id"],
|
|
5843
|
+
["cases", "--cases"],
|
|
5844
|
+
["case-count", "--case-count"]
|
|
5845
|
+
];
|
|
5846
|
+
async function planProblemCases(flags) {
|
|
5847
|
+
const raw = flags.problem;
|
|
5848
|
+
if (raw === undefined) {
|
|
5849
|
+
if (flags["max-cases"] !== undefined) {
|
|
5850
|
+
throw new CliInputError("--max-cases applies only with --problem, so nothing was sent.", "Use --cases=N with --auto-generate, or pass --problem=<problem_id>.");
|
|
5851
|
+
}
|
|
5852
|
+
return null;
|
|
5853
|
+
}
|
|
5854
|
+
const problemId = raw.trim();
|
|
5855
|
+
const conflicts = PROBLEM_CASE_CONFLICTS.filter(([key]) => flags[key] !== undefined).map(([, name]) => name);
|
|
5856
|
+
if (flags["auto-generate"] === "true")
|
|
5857
|
+
conflicts.push("--auto-generate");
|
|
5858
|
+
if (conflicts.length) {
|
|
5859
|
+
throw new CliInputError(`--problem cannot be combined with ${conflicts.join(", ")}: each picks the replay cases, so nothing was sent.`, "Pass one of --problem=<problem_id> [--max-cases=N], --traces=<ids>, --set-id=<id>, or --auto-generate [--cases=N].");
|
|
5860
|
+
}
|
|
5861
|
+
if (!UUID_RE.test(problemId)) {
|
|
5862
|
+
throw new CliInputError(`--problem needs a canonical problem UUID; got "${problemId}", so nothing was sent.`, "Run `moda problems` to find the problem_id.");
|
|
5863
|
+
}
|
|
5864
|
+
const maxCases = parsePositiveInt(flags["max-cases"], DEFAULT_PROBLEM_MAX_CASES, MAX_REPLAY_TRACES, "--max-cases");
|
|
5865
|
+
const selection = await fetchProblemCases(problemId, maxCases);
|
|
5866
|
+
if (!selection.traces.length) {
|
|
5867
|
+
throw new CliInputError(`Problem ${problemId} has no affected traces to replay, so nothing was sent.`, `Check the id with \`moda problems get ${problemId}\`, or pass --traces=<ids>.`);
|
|
5868
|
+
}
|
|
5869
|
+
return {
|
|
5870
|
+
kind: "traces",
|
|
5871
|
+
traces: selection.traces.map((c) => c.msg_index === null ? c.conversation_id : `${c.conversation_id}@${c.msg_index}`),
|
|
5872
|
+
cases: selection.traces.length,
|
|
5873
|
+
problem: selection
|
|
5874
|
+
};
|
|
5875
|
+
}
|
|
5876
|
+
async function readProblemPages(problemId, view, enough, drain = false) {
|
|
5877
|
+
const rows = [];
|
|
5878
|
+
let enoughAt = null;
|
|
5879
|
+
let cursor = null;
|
|
5880
|
+
let resolvedId = null;
|
|
5881
|
+
let hasMore = false;
|
|
5882
|
+
for (let page = 0;page < PROBLEM_MAX_PAGES; page++) {
|
|
5883
|
+
const query = new URLSearchParams({ limit: String(PROBLEM_PAGE_LIMIT) });
|
|
5884
|
+
if (cursor)
|
|
5885
|
+
query.set("cursor", cursor);
|
|
5886
|
+
const data = await callDataAPI(`/problems/${encodeURIComponent(problemId)}/${view}?${query.toString()}`);
|
|
5887
|
+
if (typeof data?.problem_id === "string" && data.problem_id)
|
|
5888
|
+
resolvedId = data.problem_id;
|
|
5889
|
+
const pageRows = data?.[view];
|
|
5890
|
+
if (Array.isArray(pageRows))
|
|
5891
|
+
rows.push(...pageRows);
|
|
5892
|
+
const next = data?.pagination?.next_cursor;
|
|
5893
|
+
hasMore = data?.pagination?.has_more === true && typeof next === "string" && next !== "";
|
|
5894
|
+
if (enoughAt === null && enough(rows))
|
|
5895
|
+
enoughAt = rows.length;
|
|
5896
|
+
if (!hasMore || enoughAt !== null && !drain)
|
|
5897
|
+
break;
|
|
5898
|
+
cursor = next;
|
|
5899
|
+
}
|
|
5900
|
+
return { rows, enoughAt, resolvedId, hasMore };
|
|
5901
|
+
}
|
|
5902
|
+
async function fetchProblemCases(problemId, maxCases) {
|
|
5903
|
+
const anchored = (evidenceRows, traceRows2) => selectProblemCases(evidenceRows, traceRows2, MAX_REPLAY_TRACES).traces.filter((c) => c.msg_index !== null).length;
|
|
5904
|
+
const evidence = await readProblemPages(problemId, "evidence", (rows) => anchored(rows, []) >= maxCases, true);
|
|
5905
|
+
const choiceRows = evidence.enoughAt === null ? evidence.rows : evidence.rows.slice(0, evidence.enoughAt);
|
|
5906
|
+
let traceRows = [];
|
|
5907
|
+
let tracesHaveMore = false;
|
|
5908
|
+
let resolvedId = evidence.resolvedId;
|
|
5909
|
+
if (anchored(choiceRows, []) < maxCases) {
|
|
5910
|
+
const traces2 = await readProblemPages(problemId, "conversations", (rows) => anchored(evidence.rows, rows) >= maxCases);
|
|
5911
|
+
traceRows = traces2.rows;
|
|
5912
|
+
tracesHaveMore = traces2.hasMore;
|
|
5913
|
+
resolvedId ??= traces2.resolvedId;
|
|
5914
|
+
}
|
|
5915
|
+
const selected = selectProblemCases(choiceRows, traceRows, maxCases);
|
|
5916
|
+
const all = selectProblemCases(evidence.rows, traceRows, Number.MAX_SAFE_INTEGER);
|
|
5917
|
+
const earliestByTrace = new Map(all.traces.map((c) => [c.conversation_id, c]));
|
|
5918
|
+
const traces = selected.traces.map((c) => {
|
|
5919
|
+
const first = earliestByTrace.get(c.conversation_id);
|
|
5920
|
+
return first && first.msg_index !== null && (c.msg_index === null || first.msg_index < c.msg_index) ? first : c;
|
|
5921
|
+
});
|
|
5922
|
+
const found = all.found;
|
|
5923
|
+
const resolved = resolvedId ?? problemId;
|
|
5924
|
+
return {
|
|
5925
|
+
problemId: resolved,
|
|
5926
|
+
...resolved !== problemId ? { requestedProblemId: problemId } : {},
|
|
5927
|
+
maxCases,
|
|
5928
|
+
traces,
|
|
5929
|
+
found,
|
|
5930
|
+
moreAvailable: found > traces.length || evidence.hasMore || tracesHaveMore
|
|
5931
|
+
};
|
|
5932
|
+
}
|
|
5933
|
+
function anchorIndex(value) {
|
|
5934
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
5935
|
+
}
|
|
5936
|
+
function selectProblemCases(evidenceRows, traceRows, maxCases) {
|
|
5937
|
+
const byTrace = new Map;
|
|
5938
|
+
const consider = (conversationId, msgIndex, source, family) => {
|
|
5939
|
+
if (typeof conversationId !== "string" || !conversationId.trim())
|
|
5940
|
+
return;
|
|
5941
|
+
const id = conversationId.trim();
|
|
5942
|
+
const existing = byTrace.get(id);
|
|
5943
|
+
const signalFamily = typeof family === "string" && family ? family : undefined;
|
|
5944
|
+
if (existing && (msgIndex === null || existing.msg_index !== null && existing.msg_index <= msgIndex))
|
|
5945
|
+
return;
|
|
5946
|
+
byTrace.set(id, {
|
|
5947
|
+
conversation_id: id,
|
|
5948
|
+
msg_index: msgIndex,
|
|
5949
|
+
source,
|
|
5950
|
+
...signalFamily ? { signal_family: signalFamily } : {}
|
|
5951
|
+
});
|
|
5952
|
+
};
|
|
5953
|
+
for (const row of evidenceRows) {
|
|
5954
|
+
const r = row ?? {};
|
|
5955
|
+
consider(r.conversation_id, anchorIndex(r.anchor_msg_index), "evidence", r.signal_family);
|
|
5956
|
+
}
|
|
5957
|
+
for (const row of traceRows) {
|
|
5958
|
+
const r = row ?? {};
|
|
5959
|
+
const anchor = r.latest_anchor ?? {};
|
|
5960
|
+
consider(r.conversation_id, anchorIndex(anchor.anchor_msg_index), "trace", undefined);
|
|
5961
|
+
}
|
|
5962
|
+
const all = [...byTrace.values()];
|
|
5963
|
+
const ordered = [...all.filter((c) => c.msg_index !== null), ...all.filter((c) => c.msg_index === null)];
|
|
5964
|
+
return { traces: ordered.slice(0, maxCases), found: all.length };
|
|
5965
|
+
}
|
|
5966
|
+
function describeProblemCases(selection) {
|
|
5967
|
+
const requested = selection.requestedProblemId ? ` (requested ${selection.requestedProblemId})` : "";
|
|
5968
|
+
const lines = [
|
|
5969
|
+
`Replaying ${selection.traces.length} trace(s) from Problem ${selection.problemId}${requested}, each from its evidence anchor:`
|
|
5970
|
+
];
|
|
5971
|
+
for (const c of selection.traces) {
|
|
5972
|
+
const where = c.msg_index === null ? " (no anchor: replays from the first message)" : ` @ msg ${c.msg_index}`;
|
|
5973
|
+
lines.push(` ${c.conversation_id}${where}${c.signal_family ? ` [${c.signal_family}]` : ""}`);
|
|
5974
|
+
}
|
|
5975
|
+
if (selection.moreAvailable) {
|
|
5976
|
+
lines.push(`Capped at --max-cases=${selection.maxCases}; the Problem has more affected traces.`);
|
|
5977
|
+
}
|
|
5978
|
+
return `${lines.join(`
|
|
5979
|
+
`)}
|
|
5980
|
+
`;
|
|
5981
|
+
}
|
|
5824
5982
|
async function fetchReplaySetCases(tenantId, setId, profileOptions) {
|
|
5825
5983
|
try {
|
|
5826
5984
|
const set = await callControlAPI(`/tenants/${encodeURIComponent(tenantId)}/replay-sets/${encodeURIComponent(setId)}`, { method: "GET" }, profileOptions);
|
|
@@ -5876,7 +6034,8 @@ async function ensureReplaySet(plan, flags, tenantId, profileOptions) {
|
|
|
5876
6034
|
if (plan.kind === "existing")
|
|
5877
6035
|
return { setId: plan.setId, warnings: [] };
|
|
5878
6036
|
if (plan.kind === "traces") {
|
|
5879
|
-
|
|
6037
|
+
const defaultName = plan.problem ? `Prompt A/B problem ${plan.problem.problemId.slice(0, 8)} (${plan.traces.length} trace(s))` : undefined;
|
|
6038
|
+
return createSetFromTraces(plan.traces, flags, tenantId, profileOptions, defaultName);
|
|
5880
6039
|
}
|
|
5881
6040
|
const caseCount = plan.cases;
|
|
5882
6041
|
const lookbackDays = parsePositiveInt(flags["lookback-days"], 30, 365, "--lookback-days");
|
|
@@ -5896,7 +6055,7 @@ function parseTraceRef(ref) {
|
|
|
5896
6055
|
return { conversationId: ref.trim() };
|
|
5897
6056
|
return { conversationId: match[1], startMsgIndex: Number(match[2]) };
|
|
5898
6057
|
}
|
|
5899
|
-
async function createSetFromTraces(conversationIds, flags, tenantId, profileOptions) {
|
|
6058
|
+
async function createSetFromTraces(conversationIds, flags, tenantId, profileOptions, defaultName) {
|
|
5900
6059
|
const refs = [...new Set(conversationIds)].map(parseTraceRef);
|
|
5901
6060
|
const traces = [...new Set(refs.map((ref) => ref.conversationId))];
|
|
5902
6061
|
const startByConversation = new Map;
|
|
@@ -5904,12 +6063,16 @@ async function createSetFromTraces(conversationIds, flags, tenantId, profileOpti
|
|
|
5904
6063
|
if (ref.startMsgIndex !== undefined)
|
|
5905
6064
|
startByConversation.set(ref.conversationId, ref.startMsgIndex);
|
|
5906
6065
|
}
|
|
5907
|
-
const name = (flags.name ?? flags["set-name"] ?? `Prompt A/B ${traces.length} trace(s)`).trim();
|
|
6066
|
+
const name = (flags.name ?? flags["set-name"] ?? defaultName ?? `Prompt A/B ${traces.length} trace(s)`).trim();
|
|
5908
6067
|
const base = `/tenants/${encodeURIComponent(tenantId)}/replay-sets`;
|
|
6068
|
+
const traceAnchors = traces.filter((id) => startByConversation.has(id)).map((id) => ({ conversationId: id, msgIndex: startByConversation.get(id) }));
|
|
5909
6069
|
let set = null;
|
|
5910
6070
|
let unavailable = null;
|
|
5911
6071
|
try {
|
|
5912
|
-
set = await callControlAPI(`${base}/auto-generate`, {
|
|
6072
|
+
set = await callControlAPI(`${base}/auto-generate`, {
|
|
6073
|
+
method: "POST",
|
|
6074
|
+
body: JSON.stringify({ name, conversationIds: traces, ...traceAnchors.length ? { traceAnchors } : {} })
|
|
6075
|
+
}, profileOptions, { timeoutMs: 300000, retries: 0 });
|
|
5913
6076
|
if (!set?.id)
|
|
5914
6077
|
throw new Error("Auto-generate did not return a replay set id");
|
|
5915
6078
|
} catch (error) {
|
|
@@ -5933,14 +6096,15 @@ async function createSetFromTraces(conversationIds, flags, tenantId, profileOpti
|
|
|
5933
6096
|
const update = {};
|
|
5934
6097
|
if (fallback.length && index !== undefined && existing.position !== index)
|
|
5935
6098
|
update.position = index;
|
|
5936
|
-
if (startMsgIndex !== undefined)
|
|
6099
|
+
if (startMsgIndex !== undefined && existing.sourceStartMsgIndex !== startMsgIndex) {
|
|
5937
6100
|
update.sourceStartMsgIndex = startMsgIndex;
|
|
6101
|
+
}
|
|
5938
6102
|
if (!Object.keys(update).length)
|
|
5939
6103
|
continue;
|
|
5940
6104
|
await callControlAPI(`${base}/${encodeURIComponent(set.id)}/cases/${encodeURIComponent(existing.id)}`, { method: "PUT", body: JSON.stringify(update) }, profileOptions);
|
|
5941
6105
|
}
|
|
5942
6106
|
for (const conversationId of fallback) {
|
|
5943
|
-
const scenario = await loadScenarioFromConversation(conversationId);
|
|
6107
|
+
const scenario = await loadScenarioFromConversation(conversationId, startByConversation.get(conversationId));
|
|
5944
6108
|
await callControlAPI(`${base}/${encodeURIComponent(set.id)}/cases`, {
|
|
5945
6109
|
method: "POST",
|
|
5946
6110
|
body: JSON.stringify({
|
|
@@ -5960,19 +6124,40 @@ async function createSetFromTraces(conversationIds, flags, tenantId, profileOpti
|
|
|
5960
6124
|
}
|
|
5961
6125
|
return { setId: set.id, caseSources: { distilled, fallback }, warnings };
|
|
5962
6126
|
}
|
|
5963
|
-
|
|
6127
|
+
var ANCHOR_REQUEST_MAX_READS = 20;
|
|
6128
|
+
var CONTEXT_WINDOW = 10;
|
|
6129
|
+
async function loadScenarioFromConversation(conversationId, startMsgIndex) {
|
|
6130
|
+
const anchored = startMsgIndex !== undefined && startMsgIndex > 0;
|
|
6131
|
+
const userRequest = (messages, atOrBefore) => messages.filter((message) => message.role === "user" && String(message.content ?? "").trim() && (atOrBefore === undefined || typeof message.index === "number" && message.index <= atOrBefore));
|
|
6132
|
+
const readContext = async (msgIndex, window2) => {
|
|
6133
|
+
const data = await callDataAPI(`/conversations/${encodeURIComponent(conversationId)}/context?msg_index=${msgIndex}&window=${window2}`);
|
|
6134
|
+
return data.context?.messages ?? data.messages ?? [];
|
|
6135
|
+
};
|
|
5964
6136
|
try {
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
6137
|
+
if (anchored) {
|
|
6138
|
+
let center = startMsgIndex;
|
|
6139
|
+
for (let read = 0;read < ANCHOR_REQUEST_MAX_READS; read++) {
|
|
6140
|
+
const lead = userRequest(await readContext(center, CONTEXT_WINDOW), startMsgIndex).pop();
|
|
6141
|
+
const request = String(lead?.content ?? "").trim();
|
|
6142
|
+
if (request) {
|
|
6143
|
+
return `Earlier in the conversation the user and the agent already talked; the replay keeps those turns as history. ` + `Now the user says: "${request}"
|
|
6144
|
+
|
|
6145
|
+
Help them with this request using the available tools.`;
|
|
6146
|
+
}
|
|
6147
|
+
if (center - CONTEXT_WINDOW <= 0)
|
|
6148
|
+
break;
|
|
6149
|
+
center = Math.max(0, center - (2 * CONTEXT_WINDOW + 1));
|
|
6150
|
+
}
|
|
6151
|
+
} else {
|
|
6152
|
+
const request = String(userRequest(await readContext(0, 5))[0]?.content ?? "").trim();
|
|
6153
|
+
if (request) {
|
|
6154
|
+
return `The user says: "${request}"
|
|
5971
6155
|
|
|
5972
6156
|
Replay the conversation faithfully and help them accomplish their goal.`;
|
|
6157
|
+
}
|
|
5973
6158
|
}
|
|
5974
6159
|
} catch {}
|
|
5975
|
-
return `Replay conversation ${conversationId}: help the user accomplish their original goal using the available tools.`;
|
|
6160
|
+
return anchored ? `Replay conversation ${conversationId} from message ${startMsgIndex}: help the user with their request at that point using the available tools.` : `Replay conversation ${conversationId}: help the user accomplish their original goal using the available tools.`;
|
|
5976
6161
|
}
|
|
5977
6162
|
async function fetchLatestRun(tenantId, replaySetId, runId, profileOptions) {
|
|
5978
6163
|
return callControlAPI(`/tenants/${encodeURIComponent(tenantId)}/replay-sets/${encodeURIComponent(replaySetId)}/runs/latest?runId=${encodeURIComponent(runId)}`, { method: "GET" }, profileOptions, { timeoutMs: 60000, retries: 2 });
|
|
@@ -6505,7 +6690,9 @@ var PROMPT_WRITE_SUBCOMMAND_FLAGS = {
|
|
|
6505
6690
|
"set-id",
|
|
6506
6691
|
"set-name",
|
|
6507
6692
|
"tenant-id",
|
|
6508
|
-
"allow-any-model"
|
|
6693
|
+
"allow-any-model",
|
|
6694
|
+
"problem",
|
|
6695
|
+
"max-cases"
|
|
6509
6696
|
],
|
|
6510
6697
|
propose: [
|
|
6511
6698
|
"gate",
|
package/dist/cli.js
CHANGED
|
@@ -2522,31 +2522,31 @@ function leafManifestFields(leaf, commands) {
|
|
|
2522
2522
|
import { fileURLToPath } from "url";
|
|
2523
2523
|
import { realpathSync } from "fs";
|
|
2524
2524
|
async function runPromptsCommand(...args) {
|
|
2525
|
-
return (await import("./prompts-
|
|
2525
|
+
return (await import("./prompts-3h5jcjrw.js")).runPromptsCommand(...args);
|
|
2526
2526
|
}
|
|
2527
2527
|
async function runSkillsCommand(...args) {
|
|
2528
|
-
return (await import("./skills-
|
|
2528
|
+
return (await import("./skills-vq3njc8a.js")).runSkillsCommand(...args);
|
|
2529
2529
|
}
|
|
2530
2530
|
async function runFixCommand(...args) {
|
|
2531
|
-
return (await import("./fix-
|
|
2531
|
+
return (await import("./fix-0qdtdz5h.js")).runFixCommand(...args);
|
|
2532
2532
|
}
|
|
2533
2533
|
async function runFixesCommand(...args) {
|
|
2534
|
-
return (await import("./fix-
|
|
2534
|
+
return (await import("./fix-0qdtdz5h.js")).runFixesCommand(...args);
|
|
2535
2535
|
}
|
|
2536
2536
|
async function runHarnessCommand(...args) {
|
|
2537
2537
|
return (await import("./harness-vzy7n7ea.js")).runHarnessCommand(...args);
|
|
2538
2538
|
}
|
|
2539
2539
|
async function runSyncCommand(...args) {
|
|
2540
|
-
return (await import("./sync-
|
|
2540
|
+
return (await import("./sync-hq9dpqpr.js")).runSyncCommand(...args);
|
|
2541
2541
|
}
|
|
2542
2542
|
async function runRegistryCommand(...args) {
|
|
2543
2543
|
return (await import("./registry-s2vdsfw8.js")).runRegistryCommand(...args);
|
|
2544
2544
|
}
|
|
2545
2545
|
async function runDoctorCommand(...args) {
|
|
2546
|
-
return (await import("./doctor-
|
|
2546
|
+
return (await import("./doctor-q21mr5ya.js")).runDoctorCommand(...args);
|
|
2547
2547
|
}
|
|
2548
2548
|
async function runStatusCommand(...args) {
|
|
2549
|
-
return (await import("./doctor-
|
|
2549
|
+
return (await import("./doctor-q21mr5ya.js")).runStatusCommand(...args);
|
|
2550
2550
|
}
|
|
2551
2551
|
async function runAuthCommand(...args) {
|
|
2552
2552
|
return (await import("./profile-commands-6e2dgv72.js")).runAuthCommand(...args);
|
|
@@ -2558,7 +2558,7 @@ async function runProfilesCommand(...args) {
|
|
|
2558
2558
|
return (await import("./profile-commands-6e2dgv72.js")).runProfilesCommand(...args);
|
|
2559
2559
|
}
|
|
2560
2560
|
async function runAgentCommand(...args) {
|
|
2561
|
-
return (await import("./agent-
|
|
2561
|
+
return (await import("./agent-t7dxmpdq.js")).runAgentCommand(...args);
|
|
2562
2562
|
}
|
|
2563
2563
|
async function runProductionCommand(...args) {
|
|
2564
2564
|
return (await import("./production-intelligence-c8e0xw3n.js")).runProductionCommand(...args);
|
|
@@ -5382,7 +5382,7 @@ var commandRegistry = createCommandRegistry([
|
|
|
5382
5382
|
resetApiRequestCountBeforeRun: true,
|
|
5383
5383
|
telemetry: "result-and-error",
|
|
5384
5384
|
handler: async (context) => {
|
|
5385
|
-
const { runInit } = await import("./index-
|
|
5385
|
+
const { runInit } = await import("./index-4b1ybz51.js");
|
|
5386
5386
|
if (context.outputMode === "agent-stream") {
|
|
5387
5387
|
context.output.writeEvent({
|
|
5388
5388
|
event: "started",
|
|
@@ -6259,11 +6259,13 @@ var commandRegistry = createCommandRegistry([
|
|
|
6259
6259
|
{
|
|
6260
6260
|
name: "ab",
|
|
6261
6261
|
description: "Run a judged prompt A/B replay experiment",
|
|
6262
|
-
usage: "moda prompts ab --baseline=<path> --candidate=<path> [--traces=<ids>|--set-id=<id>|--auto-generate] [--yes]",
|
|
6262
|
+
usage: "moda prompts ab --baseline=<path> --candidate=<path> [--traces=<ids>|--problem=<problem_id> [--max-cases=<n>]|--set-id=<id>|--auto-generate] [--yes]",
|
|
6263
6263
|
flags: [
|
|
6264
6264
|
{ name: "--baseline=<path>", description: "Prompt file to treat as the control." },
|
|
6265
6265
|
{ name: "--candidate=<path>", description: "Prompt file to treat as the variant." },
|
|
6266
6266
|
{ name: "--traces=<ids>", description: "Comma-separated trace ids (conversation_id values) to replay, at most 100. Each case uses Moda's distilled scenario and success criteria for the trace; a trace that cannot be distilled falls back to its opening user message with generic criteria, and the run warns. Legacy alias: --conversations=<ids>." },
|
|
6267
|
+
{ name: "--problem=<problem_id>", description: "Replay the Problem's failing traces: one case per affected trace, starting at its evidence msg_index (like --traces=<id>@<n>), read from `moda problems get <id> --evidence`, then `--traces` if needed. Prints the traces it chose. Not combinable with --traces, --set-id, --auto-generate, or --cases." },
|
|
6268
|
+
{ name: "--max-cases=<n>", description: "With --problem: replay at most this many traces (default 10, max 100)." },
|
|
6267
6269
|
{ name: "--cases=<n>", description: "Cases to auto-generate (default 5, max 100)." },
|
|
6268
6270
|
{ name: "--seeds=<n>", description: "Repeats per case per arm (default 3, max 10)." },
|
|
6269
6271
|
{ name: "--model=<slug>", description: "Assistant model for both arms; must be in the replay allowlist unless --allow-any-model." },
|
|
@@ -6271,7 +6273,8 @@ var commandRegistry = createCommandRegistry([
|
|
|
6271
6273
|
{ name: "--promote", description: "Make this run's replay set the tenant's primary replay set. Off by default, so an ad-hoc A/B never replaces it." }
|
|
6272
6274
|
],
|
|
6273
6275
|
examples: [
|
|
6274
|
-
"moda prompts ab --baseline=prompts/agent.prompt.md --candidate=prompts/agent-v2.prompt.md --traces=conv_1,conv_2"
|
|
6276
|
+
"moda prompts ab --baseline=prompts/agent.prompt.md --candidate=prompts/agent-v2.prompt.md --traces=conv_1,conv_2",
|
|
6277
|
+
"moda prompts ab --baseline=prompts/agent.prompt.md --candidate=prompts/agent-v2.prompt.md --problem=<problem_id>"
|
|
6275
6278
|
]
|
|
6276
6279
|
},
|
|
6277
6280
|
{
|
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
promptsDiscoveredCheck,
|
|
4
4
|
runDoctorCommand,
|
|
5
5
|
runStatusCommand
|
|
6
|
-
} from "./cli-
|
|
6
|
+
} from "./cli-mebf5vtr.js";
|
|
7
7
|
import"./cli-0wr3483h.js";
|
|
8
|
-
import"./cli-
|
|
8
|
+
import"./cli-m805msp6.js";
|
|
9
9
|
import"./cli-mm48br00.js";
|
|
10
10
|
import"./cli-7xc585hc.js";
|
|
11
11
|
import"./cli-97fnztpy.js";
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import"./cli-9hc5bg2r.js";
|
|
5
5
|
import {
|
|
6
6
|
buildDoctorReport
|
|
7
|
-
} from "./cli-
|
|
7
|
+
} from "./cli-mebf5vtr.js";
|
|
8
8
|
import {
|
|
9
9
|
ConnectCodeError,
|
|
10
10
|
connectWithCode,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
initPrompts,
|
|
17
17
|
runPromptSync
|
|
18
|
-
} from "./cli-
|
|
18
|
+
} from "./cli-m805msp6.js";
|
|
19
19
|
import"./cli-mm48br00.js";
|
|
20
20
|
import {
|
|
21
21
|
canonicalText,
|
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
import {
|
|
48
48
|
agentsMdProblem,
|
|
49
49
|
runSkillSync
|
|
50
|
-
} from "./cli-
|
|
50
|
+
} from "./cli-m0045npe.js";
|
|
51
51
|
import {
|
|
52
52
|
createCommandContext
|
|
53
53
|
} from "./cli-qvg26twp.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runPromptsCommand
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-m805msp6.js";
|
|
4
4
|
import"./cli-mm48br00.js";
|
|
5
5
|
import {
|
|
6
6
|
runHarnessCommand
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import"./cli-97fnztpy.js";
|
|
9
9
|
import {
|
|
10
10
|
runSkillsCommand
|
|
11
|
-
} from "./cli-
|
|
11
|
+
} from "./cli-m0045npe.js";
|
|
12
12
|
import"./cli-qvg26twp.js";
|
|
13
13
|
import"./cli-dn91fb56.js";
|
|
14
14
|
import"./cli-9btx12wp.js";
|
package/package.json
CHANGED
package/skills/moda-cli/SKILL.md
CHANGED
|
@@ -82,7 +82,10 @@ on search, traces, and clusters. `moda <noun> <verb> --help` shows which.
|
|
|
82
82
|
2. `moda problems get <problem_id> --evidence` and open two or three anchors with
|
|
83
83
|
`moda traces get <id> --msg-index=<n>` before deciding on a cause.
|
|
84
84
|
3. Make the change in code; cite the problem id and trace ids in the commit or PR.
|
|
85
|
-
4.
|
|
85
|
+
4. Test a prompt change on the Problem's failing traces (a paid replay, so ask
|
|
86
|
+
first; prompt changes only today):
|
|
87
|
+
`moda prompts ab --problem=<problem_id> --baseline=<prompt_key> --candidate=<edited file>`.
|
|
88
|
+
5. After it ships, and only if the user agrees:
|
|
86
89
|
`moda problems feedback <problem_id> --action=mark_fixed`.
|
|
87
90
|
More: `references/production-intelligence.md`.
|
|
88
91
|
|
|
@@ -90,7 +90,8 @@ Rules that keep the fix grounded:
|
|
|
90
90
|
- Cite the Moda problem id and the trace ids you relied on in the PR
|
|
91
91
|
description, so a reviewer can open the same evidence.
|
|
92
92
|
- To test a prompt change against real traffic before shipping it, A/B it with
|
|
93
|
-
`moda prompts ab --baseline=<path> --candidate=<path> --
|
|
94
|
-
|
|
93
|
+
`moda prompts ab --baseline=<path> --candidate=<path> --problem=<problem_id>`
|
|
94
|
+
(replays the Problem's failing traces from their evidence anchors) or
|
|
95
|
+
`--traces=<ids>` (see section 7).
|
|
95
96
|
- Mark the Problem fixed only after the change ships. If the issue is detected
|
|
96
97
|
again, the Problem comes back; `moda problems reopen` reopens it by hand.
|
|
@@ -72,7 +72,10 @@ read, they fail (exit 1) with the number and the exact command to re-run with `-
|
|
|
72
72
|
Only add `--yes` when the user has agreed to that spend. Limits: `--cases`
|
|
73
73
|
default 5, max 100; `--seeds` default 3, max 10; `--traces` at most 100
|
|
74
74
|
(`--traces=conv_id@412` replays that trace from msg_index 412, with the
|
|
75
|
-
earlier turns as history);
|
|
75
|
+
earlier turns as history, and writes its case from the messages around 412);
|
|
76
|
+
`--problem=<problem_id>` picks a Problem's affected traces, each anchored at
|
|
77
|
+
its earliest evidence msg_index, up to
|
|
78
|
+
`--max-cases` (default 10, max 100), and prints them (`problemCases`);
|
|
76
79
|
`--model` / `--assistant-model` must be one of `openai/gpt-5.6-luna`,
|
|
77
80
|
`openai/gpt-4o-mini`, `openai/gpt-4o`, `anthropic/claude-sonnet-4-5`,
|
|
78
81
|
`anthropic/claude-haiku-4-5`, `google/gemini-2.5-flash`,
|
|
@@ -80,7 +83,8 @@ earlier turns as history);
|
|
|
80
83
|
|
|
81
84
|
`prompts ab --traces=<ids>` builds one case per trace from Moda's distilled
|
|
82
85
|
scenario and success criteria for that trace. A trace Moda cannot distill
|
|
83
|
-
falls back to its opening user message
|
|
86
|
+
falls back to its opening user message (for `id@N`, the user message leading
|
|
87
|
+
into N) with three generic criteria (which long
|
|
84
88
|
traces pass easily); the run warns and lists it under `caseSources.fallback`.
|
|
85
89
|
`prompts ab` never makes its replay set the tenant's primary set unless you
|
|
86
90
|
pass `--promote`.
|