@moda-ai/cli 1.42.0 → 1.43.1

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.
@@ -13,7 +13,7 @@ import {
13
13
  resolvePromptSource,
14
14
  safePromptEvidencePath,
15
15
  scanHarness
16
- } from "./cli-xsmsjny3.js";
16
+ } from "./cli-5tsqk3jt.js";
17
17
  import {
18
18
  SKILL_SOURCE_HARNESS,
19
19
  SKILL_SOURCE_INTEGRATION_BUNDLED,
@@ -26,7 +26,7 @@ import {
26
26
  import {
27
27
  isAuthSessionValid,
28
28
  loadAuthSession
29
- } from "./cli-dyeywy9d.js";
29
+ } from "./cli-rycpwqzm.js";
30
30
  import {
31
31
  CliInputError,
32
32
  createCommandContext,
@@ -43,7 +43,7 @@ import {
43
43
  resolveTenantId,
44
44
  secretFilePathFromRef,
45
45
  validateConfig
46
- } from "./cli-rp912ypf.js";
46
+ } from "./cli-8c60k2p4.js";
47
47
  import {
48
48
  __commonJS,
49
49
  __require,
@@ -5586,6 +5586,11 @@ var REPLAY_MODEL_ALLOWLIST = [
5586
5586
  var DEFAULT_POLL_INTERVAL_MS = 15000;
5587
5587
  var DEFAULT_WAIT_TIMEOUT_MS = 2 * 60 * 60 * 1000;
5588
5588
  var TERMINAL_RUN_STATUSES = new Set(["completed", "skipped", "error"]);
5589
+ var FALLBACK_SUCCESS_CRITERIA = [
5590
+ "The agent understands the user request",
5591
+ "The agent uses tools appropriately when needed",
5592
+ "The agent provides a helpful final answer"
5593
+ ];
5589
5594
  async function runPromptAb(flags, profileOptions, context) {
5590
5595
  validateConfig();
5591
5596
  const tenantId = flags["tenant-id"] || resolveApiTenantId(profileOptions);
@@ -5626,61 +5631,78 @@ async function runPromptAb(flags, profileOptions, context) {
5626
5631
  prod: resolvePromptArmSpec(baselineSource, "baseline"),
5627
5632
  proposed: resolvePromptArmSpec(candidateSource, "candidate")
5628
5633
  };
5629
- const replaySetId = await ensureReplaySet(plan, flags, tenantId, profileOptions);
5630
- const enqueue = await callControlAPI(`/tenants/${encodeURIComponent(tenantId)}/replay-sets/${encodeURIComponent(replaySetId)}/run`, {
5631
- method: "POST",
5632
- body: JSON.stringify({
5633
- promptArms,
5634
- seedsPerCase,
5635
- assistantModel: assistantModel || undefined,
5636
- promotePrimary: flags["no-promote"] !== "true",
5637
- ...plan.kind === "existing" ? replayCasePin(plan.caseIds) : {}
5638
- })
5639
- }, profileOptions, { timeoutMs: 120000, retries: 0 });
5640
- if (context.outputMode === "human")
5641
- warnIfQueued(enqueue);
5642
- const noWait = flags.wait === "false" || flags["no-wait"] === "true";
5643
- if (noWait) {
5644
- context.output.writeData({
5645
- replaySetId,
5646
- runId: enqueue.runId,
5647
- status: enqueue.status,
5648
- message: enqueue.message ?? "Replay comparison queued",
5649
- plannedPlayouts
5650
- });
5651
- return;
5634
+ const { setId: replaySetId, caseSources, warnings } = await ensureReplaySet(plan, flags, tenantId, profileOptions);
5635
+ if (context.outputMode === "human") {
5636
+ for (const warning of warnings)
5637
+ process.stderr.write(`Warning: ${warning}
5638
+ `);
5652
5639
  }
5653
- const deadline = Date.now() + timeoutMs;
5654
- let latest = null;
5655
- while (Date.now() < deadline) {
5656
- latest = await fetchLatestRun(tenantId, replaySetId, enqueue.runId, profileOptions);
5657
- const status = latest.run?.status ?? "";
5658
- const runId = latest.run?.runId ?? "";
5659
- if (TERMINAL_RUN_STATUSES.has(status) && runId === enqueue.runId) {
5660
- break;
5640
+ const writeOptions = warnings.length ? { warnings } : undefined;
5641
+ try {
5642
+ const enqueue = await callControlAPI(`/tenants/${encodeURIComponent(tenantId)}/replay-sets/${encodeURIComponent(replaySetId)}/run`, {
5643
+ method: "POST",
5644
+ body: JSON.stringify({
5645
+ promptArms,
5646
+ seedsPerCase,
5647
+ assistantModel: assistantModel || undefined,
5648
+ promotePrimary: flags.promote === "true" && flags["no-promote"] !== "true",
5649
+ ...plan.kind === "existing" ? replayCasePin(plan.caseIds) : {}
5650
+ })
5651
+ }, profileOptions, { timeoutMs: 120000, retries: 0 });
5652
+ if (context.outputMode === "human")
5653
+ warnIfQueued(enqueue);
5654
+ const noWait = flags.wait === "false" || flags["no-wait"] === "true";
5655
+ if (noWait) {
5656
+ context.output.writeData({
5657
+ replaySetId,
5658
+ ...caseSources ? { caseSources } : {},
5659
+ ...warnings.length ? { warnings } : {},
5660
+ runId: enqueue.runId,
5661
+ status: enqueue.status,
5662
+ message: enqueue.message ?? "Replay comparison queued",
5663
+ plannedPlayouts
5664
+ }, writeOptions);
5665
+ return;
5661
5666
  }
5662
- if (context.outputMode === "human") {
5663
- process.stderr.write(`Waiting for replay run (${waitLabel(latest.run)})...
5667
+ const deadline = Date.now() + timeoutMs;
5668
+ let latest = null;
5669
+ while (Date.now() < deadline) {
5670
+ latest = await fetchLatestRun(tenantId, replaySetId, enqueue.runId, profileOptions);
5671
+ const status = latest.run?.status ?? "";
5672
+ const runId = latest.run?.runId ?? "";
5673
+ if (TERMINAL_RUN_STATUSES.has(status) && runId === enqueue.runId) {
5674
+ break;
5675
+ }
5676
+ if (context.outputMode === "human") {
5677
+ process.stderr.write(`Waiting for replay run (${waitLabel(latest.run)})...
5664
5678
  `);
5679
+ }
5680
+ await sleep(pollIntervalMs);
5665
5681
  }
5666
- await sleep(pollIntervalMs);
5667
- }
5668
- if (!latest?.run || latest.run.runId !== enqueue.runId || !TERMINAL_RUN_STATUSES.has(latest.run.status)) {
5669
- throw new Error(`Timed out after ${timeoutMs}ms waiting for replay run ${enqueue.runId} to finish`);
5670
- }
5671
- const result = {
5672
- replaySetId,
5673
- plannedPlayouts,
5674
- runId: latest.run.runId,
5675
- status: latest.run.status,
5676
- verdict: formatVerdict(latest),
5677
- run: { ...latest.run },
5678
- cases: latest.cases.map((row) => ({ ...row }))
5679
- };
5680
- if (context.outputMode === "human") {
5681
- printHumanVerdict(result);
5682
+ if (!latest?.run || latest.run.runId !== enqueue.runId || !TERMINAL_RUN_STATUSES.has(latest.run.status)) {
5683
+ throw new Error(`Timed out after ${timeoutMs}ms waiting for replay run ${enqueue.runId} to finish`);
5684
+ }
5685
+ const result = {
5686
+ replaySetId,
5687
+ ...caseSources ? { caseSources } : {},
5688
+ ...warnings.length ? { warnings } : {},
5689
+ plannedPlayouts,
5690
+ runId: latest.run.runId,
5691
+ status: latest.run.status,
5692
+ verdict: formatVerdict(latest),
5693
+ run: { ...latest.run },
5694
+ cases: latest.cases.map((row) => ({ ...row }))
5695
+ };
5696
+ if (context.outputMode === "human") {
5697
+ printHumanVerdict(result);
5698
+ }
5699
+ context.output.writeData(result, writeOptions);
5700
+ } catch (error) {
5701
+ if (warnings.length && error && typeof error === "object") {
5702
+ Object.assign(error, { warnings });
5703
+ }
5704
+ throw error;
5682
5705
  }
5683
- context.output.writeData(result);
5684
5706
  }
5685
5707
  function warnIfQueued(enqueue) {
5686
5708
  const { runsAhead, maxActiveRuns } = enqueue;
@@ -5793,7 +5815,7 @@ async function planReplaySet(flags, tenantId, profileOptions) {
5793
5815
  const snapshot = await fetchReplaySetCases(tenantId, existingSetId, profileOptions);
5794
5816
  return { kind: "existing", setId: existingSetId, cases: snapshot?.ids.length ?? null, caseIds: snapshot?.ids };
5795
5817
  }
5796
- const conversations = parseCsv(flags.traces ?? flags.conversations ?? flags["conversation-ids"]);
5818
+ const conversations = [...new Set(parseCsv(flags.traces ?? flags.conversations ?? flags["conversation-ids"]))];
5797
5819
  if (conversations.length > MAX_REPLAY_TRACES) {
5798
5820
  throw new CliInputError(`--traces has ${conversations.length} ids; the limit is ${MAX_REPLAY_TRACES} per replay set, so nothing was sent.`, "Split the traces across several runs, or use --auto-generate.");
5799
5821
  }
@@ -5861,9 +5883,9 @@ function rerunWithYes(command, positionals, flags) {
5861
5883
  }
5862
5884
  async function ensureReplaySet(plan, flags, tenantId, profileOptions) {
5863
5885
  if (plan.kind === "existing")
5864
- return plan.setId;
5886
+ return { setId: plan.setId, warnings: [] };
5865
5887
  if (plan.kind === "traces") {
5866
- return createSetFromConversations(plan.traces, flags, tenantId, profileOptions);
5888
+ return createSetFromTraces(plan.traces, flags, tenantId, profileOptions);
5867
5889
  }
5868
5890
  const caseCount = plan.cases;
5869
5891
  const lookbackDays = parsePositiveInt(flags["lookback-days"], 30, 365, "--lookback-days");
@@ -5875,7 +5897,7 @@ async function ensureReplaySet(plan, flags, tenantId, profileOptions) {
5875
5897
  if (!generated?.id) {
5876
5898
  throw new Error("Auto-generate did not return a replay set id");
5877
5899
  }
5878
- return generated.id;
5900
+ return { setId: generated.id, warnings: [] };
5879
5901
  }
5880
5902
  function parseTraceRef(ref) {
5881
5903
  const match = /^(.+)@(\d+)$/.exec(ref.trim());
@@ -5883,38 +5905,75 @@ function parseTraceRef(ref) {
5883
5905
  return { conversationId: ref.trim() };
5884
5906
  return { conversationId: match[1], startMsgIndex: Number(match[2]) };
5885
5907
  }
5886
- async function createSetFromConversations(conversationIds, flags, tenantId, profileOptions) {
5887
- const name = (flags.name ?? flags["set-name"] ?? `Prompt A/B ${conversationIds.length} trace(s)`).trim();
5888
- const created = await callControlAPI(`/tenants/${encodeURIComponent(tenantId)}/replay-sets`, {
5889
- method: "POST",
5890
- body: JSON.stringify({ name, description: "Created by moda prompts ab" })
5891
- }, profileOptions);
5892
- let position = 0;
5893
- for (const ref of conversationIds) {
5894
- const { conversationId, startMsgIndex } = parseTraceRef(ref);
5908
+ async function createSetFromTraces(conversationIds, flags, tenantId, profileOptions) {
5909
+ const refs = [...new Set(conversationIds)].map(parseTraceRef);
5910
+ const traces = [...new Set(refs.map((ref) => ref.conversationId))];
5911
+ const startByConversation = new Map;
5912
+ for (const ref of refs) {
5913
+ if (ref.startMsgIndex !== undefined)
5914
+ startByConversation.set(ref.conversationId, ref.startMsgIndex);
5915
+ }
5916
+ const name = (flags.name ?? flags["set-name"] ?? `Prompt A/B ${traces.length} trace(s)`).trim();
5917
+ const base = `/tenants/${encodeURIComponent(tenantId)}/replay-sets`;
5918
+ let set = null;
5919
+ let unavailable = null;
5920
+ try {
5921
+ set = await callControlAPI(`${base}/auto-generate`, { method: "POST", body: JSON.stringify({ name, conversationIds: traces }) }, profileOptions, { timeoutMs: 300000, retries: 0 });
5922
+ if (!set?.id)
5923
+ throw new Error("Auto-generate did not return a replay set id");
5924
+ } catch (error) {
5925
+ const status = error?.statusCode;
5926
+ if (status === 401 || status === 403)
5927
+ throw error;
5928
+ unavailable = error instanceof Error ? error.message : String(error);
5929
+ set = null;
5930
+ }
5931
+ const distilledIds = new Set((set?.cases ?? []).map((c) => String(c?.sourceConversationId ?? "")).filter(Boolean));
5932
+ const distilled = traces.filter((id) => distilledIds.has(id));
5933
+ const fallback = traces.filter((id) => !distilledIds.has(id));
5934
+ if (!set) {
5935
+ set = await callControlAPI(base, { method: "POST", body: JSON.stringify({ name, description: "Created by moda prompts ab" }) }, profileOptions);
5936
+ }
5937
+ const requestedIndex = new Map(traces.map((id, index) => [id, index]));
5938
+ for (const existing of set.cases ?? []) {
5939
+ const conversationId = String(existing?.sourceConversationId ?? "");
5940
+ const index = requestedIndex.get(conversationId);
5941
+ const startMsgIndex = startByConversation.get(conversationId);
5942
+ const update = {};
5943
+ if (fallback.length && index !== undefined && existing.position !== index)
5944
+ update.position = index;
5945
+ if (startMsgIndex !== undefined)
5946
+ update.sourceStartMsgIndex = startMsgIndex;
5947
+ if (!Object.keys(update).length)
5948
+ continue;
5949
+ await callControlAPI(`${base}/${encodeURIComponent(set.id)}/cases/${encodeURIComponent(existing.id)}`, { method: "PUT", body: JSON.stringify(update) }, profileOptions);
5950
+ }
5951
+ for (const conversationId of fallback) {
5895
5952
  const scenario = await loadScenarioFromConversation(conversationId);
5896
- await callControlAPI(`/tenants/${encodeURIComponent(tenantId)}/replay-sets/${encodeURIComponent(created.id)}/cases`, {
5953
+ await callControlAPI(`${base}/${encodeURIComponent(set.id)}/cases`, {
5897
5954
  method: "POST",
5898
5955
  body: JSON.stringify({
5899
5956
  title: conversationId,
5900
5957
  scenario,
5901
5958
  sourceConversationId: conversationId,
5902
- ...startMsgIndex !== undefined ? { sourceStartMsgIndex: startMsgIndex } : {},
5903
- successCriteria: [
5904
- "The agent understands the user request",
5905
- "The agent uses tools appropriately when needed",
5906
- "The agent provides a helpful final answer"
5907
- ],
5908
- position: position++
5959
+ ...startByConversation.has(conversationId) ? { sourceStartMsgIndex: startByConversation.get(conversationId) } : {},
5960
+ successCriteria: FALLBACK_SUCCESS_CRITERIA,
5961
+ position: requestedIndex.get(conversationId)
5909
5962
  })
5910
5963
  }, profileOptions);
5911
5964
  }
5912
- return created.id;
5965
+ const warnings = [];
5966
+ if (fallback.length) {
5967
+ const why = unavailable ? `Moda could not distill these traces (${unavailable.slice(0, 600)})` : "Moda could not distill a scenario for these traces";
5968
+ warnings.push(`${why}, so ${fallback.length} of ${traces.length} case(s) use only the opening user message and three generic ` + `success criteria, which long traces pass easily: ${fallback.slice(0, 10).join(", ")}${fallback.length > 10 ? ", …" : ""}. ` + "Edit their scenario/criteria in the dashboard, or pass --set-id with a curated set.");
5969
+ }
5970
+ return { setId: set.id, caseSources: { distilled, fallback }, warnings };
5913
5971
  }
5914
5972
  async function loadScenarioFromConversation(conversationId) {
5915
5973
  try {
5916
- const data = await callDataAPI(`/conversations/${encodeURIComponent(conversationId)}/context?msg_index=0&window=0`);
5917
- const firstUser = (data.messages ?? []).find((message) => message.role === "user" && String(message.content ?? "").trim());
5974
+ const data = await callDataAPI(`/conversations/${encodeURIComponent(conversationId)}/context?msg_index=0&window=5`);
5975
+ const messages = data.context?.messages ?? data.messages ?? [];
5976
+ const firstUser = messages.find((message) => message.role === "user" && String(message.content ?? "").trim());
5918
5977
  const opening = String(firstUser?.content ?? "").trim();
5919
5978
  if (opening) {
5920
5979
  return `The user says: "${opening}"
@@ -6446,6 +6505,7 @@ var PROMPT_WRITE_SUBCOMMAND_FLAGS = {
6446
6505
  "lookback-days",
6447
6506
  "no-promote",
6448
6507
  "no-wait",
6508
+ "promote",
6449
6509
  "poll-interval",
6450
6510
  "replay-set-id",
6451
6511
  "seeds-per-case",
@@ -6647,7 +6707,7 @@ async function runPromptSyncExclusive(flags, profileOptions, options) {
6647
6707
  const analyzedDefinitions = new Map;
6648
6708
  if (flags["no-analyze"] !== "true") {
6649
6709
  options.onProgress?.("Analyzing prompt sources before sync.");
6650
- const { analyzeHarnessPrompts } = await import("./harness-191xt7bq.js");
6710
+ const { analyzeHarnessPrompts } = await import("./harness-sqngqjrd.js");
6651
6711
  const result = await analyzeHarnessPrompts({
6652
6712
  rootDir: process.cwd(),
6653
6713
  existing: established.flatMap((prompt) => prompt.sourceDefinition ? [prompt.sourceDefinition.source] : []),
@@ -25,7 +25,7 @@ import {
25
25
  resolveTenantId,
26
26
  shouldInventoryFile,
27
27
  stringOption
28
- } from "./cli-rp912ypf.js";
28
+ } from "./cli-8c60k2p4.js";
29
29
  import {
30
30
  __commonJS,
31
31
  __require,
@@ -184813,7 +184813,7 @@ async function runHarnessCommand(context) {
184813
184813
  throw new CliInputError("Focused remote prompt analysis is not supported yet.", "Use local --analyst=claude|codex|cursor|local-scan, or drop --concern for a full remote report.");
184814
184814
  }
184815
184815
  if (context.flags["github-actions"] === "true") {
184816
- const { runGithubActionsAnalyze } = await import("./harness-github-actions-1qt4sgxd.js");
184816
+ const { runGithubActionsAnalyze } = await import("./harness-github-actions-pg0azf3h.js");
184817
184817
  const result = await runGithubActionsAnalyze(rootDir, {
184818
184818
  writeReport: (report2) => {
184819
184819
  const normalized = normalizeHarnessReport(report2);
@@ -1213,7 +1213,7 @@ function createCliOutput(options) {
1213
1213
  if (options.quiet || state.terminal)
1214
1214
  return;
1215
1215
  const agentError = errorToAgentError(error);
1216
- const envelope = createAgentErrorEnvelope(options.command || "unknown", agentError, state.runId);
1216
+ const envelope = createAgentErrorEnvelope(options.command || "unknown", agentError, state.runId, errorWarnings(error));
1217
1217
  if (options.mode === "agent-stream") {
1218
1218
  ensureStarted();
1219
1219
  if (error instanceof CliInputRequiredError) {
@@ -1315,11 +1315,16 @@ function agentEnvelopeFits(command, data, options = {}) {
1315
1315
  const envelope = createAgentEnvelope({ command, status: "ok", data, ...options });
1316
1316
  return !envelope.truncation && Buffer.byteLength(JSON.stringify(envelope), "utf8") <= maxBytes;
1317
1317
  }
1318
- function createAgentErrorEnvelope(command, error, runId) {
1318
+ function errorWarnings(error) {
1319
+ const warnings = error?.warnings;
1320
+ return Array.isArray(warnings) && warnings.length ? warnings.filter((w) => typeof w === "string") : undefined;
1321
+ }
1322
+ function createAgentErrorEnvelope(command, error, runId, warnings) {
1319
1323
  return createAgentEnvelope({
1320
1324
  command,
1321
1325
  status: "error",
1322
1326
  data: null,
1327
+ warnings: warnings ?? [],
1323
1328
  meta: runId ? { run_id: runId } : undefined,
1324
1329
  summary: {
1325
1330
  text: error.message,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  authFetch,
3
3
  getCachedLoginContext
4
- } from "./cli-dyeywy9d.js";
4
+ } from "./cli-rycpwqzm.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-rp912ypf.js";
18
+ } from "./cli-8c60k2p4.js";
19
19
 
20
20
  // src/init/tenant.ts
21
21
  import { randomUUID } from "node:crypto";
@@ -7,7 +7,7 @@ import {
7
7
  saveProfileConfig,
8
8
  secretFilePathFromRef,
9
9
  writeSecretJsonFile
10
- } from "./cli-rp912ypf.js";
10
+ } from "./cli-8c60k2p4.js";
11
11
  import {
12
12
  __require
13
13
  } from "./cli-0v6na3yp.js";
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  runPromptsCommand,
9
9
  runSkillsCommand,
10
10
  runStatusCommand
11
- } from "./cli-pr6s56fc.js";
11
+ } from "./cli-141ca3yv.js";
12
12
  import {
13
13
  ApiError,
14
14
  HARNESS_REPORT_APPROVAL_PATH,
@@ -37,7 +37,7 @@ import {
37
37
  terminalStyles,
38
38
  unknownSignalsSubcommandError,
39
39
  validateHarnessReport
40
- } from "./cli-xsmsjny3.js";
40
+ } from "./cli-5tsqk3jt.js";
41
41
  import"./cli-ssfq84k5.js";
42
42
  import {
43
43
  authFetch,
@@ -46,7 +46,7 @@ import {
46
46
  loadAuthSession,
47
47
  login,
48
48
  revokeCliSessionToken
49
- } from "./cli-dyeywy9d.js";
49
+ } from "./cli-rycpwqzm.js";
50
50
  import {
51
51
  AGENT_ERROR_CODES,
52
52
  CliAccessError,
@@ -95,7 +95,7 @@ import {
95
95
  validateConfig,
96
96
  wasOutputCancelled,
97
97
  writeSecretJsonFile
98
- } from "./cli-rp912ypf.js";
98
+ } from "./cli-8c60k2p4.js";
99
99
  import {
100
100
  __require
101
101
  } from "./cli-0v6na3yp.js";
@@ -151,7 +151,7 @@ var WorldStateSchema = z.object({
151
151
  var ContextSchema = z.object({
152
152
  conversation_id: z.string(),
153
153
  msg_index: z.number().min(0).optional(),
154
- window: z.number().min(1).max(5).default(2).optional(),
154
+ window: z.number().int().min(0).optional(),
155
155
  all: boolFlag(),
156
156
  from: z.number().int().min(0).optional(),
157
157
  max_messages: z.number().int().min(1).max(5000).optional()
@@ -8194,6 +8194,8 @@ function pageMessages(page) {
8194
8194
  const messages = asRecord(page.context)?.messages ?? page.messages;
8195
8195
  return (Array.isArray(messages) ? messages : []).map((m) => asRecord(m) ?? {});
8196
8196
  }
8197
+ var CONTEXT_MAX_WINDOW = 5;
8198
+ var CONTEXT_ALL_MAX_MESSAGES = 5000;
8197
8199
  async function readFullTranscript(conversationId, opts = {}) {
8198
8200
  const start = opts.from ?? 0;
8199
8201
  const requested = opts.maxMessages ?? FULL_TRANSCRIPT_DEFAULT_MAX_MESSAGES;
@@ -8579,13 +8581,26 @@ async function runCommand(command, positional, flags, positionals = positional ?
8579
8581
  const query2 = new URLSearchParams;
8580
8582
  if (params.msg_index !== undefined)
8581
8583
  query2.set("msg_index", params.msg_index.toString());
8582
- if (params.window)
8583
- query2.set("window", params.window.toString());
8584
+ const window = params.window === undefined ? undefined : Math.min(params.window, CONTEXT_MAX_WINDOW);
8585
+ if (window !== undefined)
8586
+ query2.set("window", window.toString());
8584
8587
  const queryString = query2.toString() ? `?${query2.toString()}` : "";
8585
8588
  const data = await callDataAPI(`/conversations/${params.conversation_id}/context${queryString}`);
8586
8589
  const ctxRecord = asRecord(data) ?? {};
8587
8590
  const warnings = notFoundWarning("trace", params.conversation_id, asNumber(ctxRecord.total_messages) === 0);
8588
- context.output.writeData(data, warnings.length > 0 ? { warnings } : undefined);
8591
+ const nextCommands = [];
8592
+ if (params.window !== undefined && params.window > CONTEXT_MAX_WINDOW) {
8593
+ const center = asNumber(asRecord(ctxRecord.context)?.center_index) ?? params.msg_index ?? 0;
8594
+ const span = Math.min(params.window * 2 + 1, CONTEXT_ALL_MAX_MESSAGES);
8595
+ const from = Math.max(0, center - Math.floor((span - 1) / 2));
8596
+ const wide = `moda context ${shellArg(params.conversation_id)} --all --from=${from} --max-messages=${span}`;
8597
+ warnings.push(`--window=${params.window} is above the maximum of ${CONTEXT_MAX_WINDOW}, so this shows ±${CONTEXT_MAX_WINDOW} messages. For a wider slice run: ${wide}`);
8598
+ nextCommands.push({ command: wide, purpose: `Read ±${params.window} messages around the anchor.`, mutability: "read", requires_approval: false });
8599
+ }
8600
+ context.output.writeData(data, {
8601
+ ...warnings.length > 0 ? { warnings } : {},
8602
+ ...nextCommands.length > 0 ? { nextCommands } : {}
8603
+ });
8589
8604
  break;
8590
8605
  }
8591
8606
  case "audit":
@@ -9360,7 +9375,7 @@ var commandRegistry = createCommandRegistry([
9360
9375
  resetApiRequestCountBeforeRun: true,
9361
9376
  telemetry: "result",
9362
9377
  handler: async (context) => {
9363
- const { runInit } = await import("./index-ah6yf5x2.js");
9378
+ const { runInit } = await import("./index-cnfwmzew.js");
9364
9379
  if (context.outputMode === "agent-stream") {
9365
9380
  context.output.writeEvent({
9366
9381
  event: "started",
@@ -9401,7 +9416,7 @@ var commandRegistry = createCommandRegistry([
9401
9416
  resetApiRequestCountBeforeRun: true,
9402
9417
  telemetry: "result-and-error",
9403
9418
  handler: async (context) => {
9404
- const { runProvision } = await import("./provision-gabtks29.js");
9419
+ const { runProvision } = await import("./provision-02wqtkvm.js");
9405
9420
  return runProvision(context, {
9406
9421
  resumeCommand: buildResumeCommand("provision", context.rawFlags)
9407
9422
  });
@@ -9828,6 +9843,7 @@ var commandRegistry = createCommandRegistry([
9828
9843
  description: "Get windowed trace context (messages around one turn), or the whole trace with --all",
9829
9844
  examples: [
9830
9845
  "moda context <conversation_id> --window=3",
9846
+ "moda context <conversation_id> --msg-index=40 --window=0",
9831
9847
  "moda context <conversation_id> --all",
9832
9848
  "moda context <conversation_id> --all --from=500 --max-messages=500"
9833
9849
  ],
@@ -10125,11 +10141,12 @@ var commandRegistry = createCommandRegistry([
10125
10141
  flags: [
10126
10142
  { name: "--baseline=<path>", description: "Prompt file to treat as the control." },
10127
10143
  { name: "--candidate=<path>", description: "Prompt file to treat as the variant." },
10128
- { name: "--traces=<ids>", description: "Comma-separated trace ids (conversation_id values) to replay, at most 100. Legacy alias: --conversations=<ids>." },
10144
+ { 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>." },
10129
10145
  { name: "--cases=<n>", description: "Cases to auto-generate (default 5, max 100)." },
10130
10146
  { name: "--seeds=<n>", description: "Repeats per case per arm (default 3, max 10)." },
10131
10147
  { name: "--model=<slug>", description: "Assistant model for both arms; must be in the replay allowlist unless --allow-any-model." },
10132
- { name: "--yes", description: "Confirm a run above 200 planned playouts (cases x seeds x 2 arms), or one whose case count is unknown." }
10148
+ { name: "--yes", description: "Confirm a run above 200 planned playouts (cases x seeds x 2 arms), or one whose case count is unknown." },
10149
+ { 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." }
10133
10150
  ],
10134
10151
  examples: [
10135
10152
  "moda prompts ab --baseline=prompts/agent.prompt.md --candidate=prompts/agent-v2.prompt.md --traces=conv_1,conv_2"
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  buildSourceSnapshot
3
- } from "./cli-xsmsjny3.js";
4
- import"./cli-rp912ypf.js";
3
+ } from "./cli-5tsqk3jt.js";
4
+ import"./cli-8c60k2p4.js";
5
5
  import"./cli-0v6na3yp.js";
6
6
 
7
7
  // src/harness-github-actions.ts
@@ -42,8 +42,8 @@ import {
42
42
  writeHarnessAnalyzeResult,
43
43
  writeHarnessArtifacts,
44
44
  writeHumanProgress
45
- } from "./cli-xsmsjny3.js";
46
- import"./cli-rp912ypf.js";
45
+ } from "./cli-5tsqk3jt.js";
46
+ import"./cli-8c60k2p4.js";
47
47
  import"./cli-0v6na3yp.js";
48
48
  export {
49
49
  writeHumanProgress,
@@ -3,7 +3,7 @@ import {
3
3
  initPrompts,
4
4
  runPromptSync,
5
5
  runSkillSync
6
- } from "./cli-pr6s56fc.js";
6
+ } from "./cli-141ca3yv.js";
7
7
  import {
8
8
  codingAgentDisplayName,
9
9
  describeCodingAgentEvent,
@@ -22,7 +22,7 @@ import {
22
22
  runHarnessCommand,
23
23
  startRemoteAnalyze,
24
24
  stripAnsi
25
- } from "./cli-xsmsjny3.js";
25
+ } from "./cli-5tsqk3jt.js";
26
26
  import {
27
27
  bundledSkillsDir,
28
28
  installIntegrationSkill,
@@ -34,14 +34,14 @@ import {
34
34
  } from "./cli-ssfq84k5.js";
35
35
  import {
36
36
  selectTenantAndCreateKey
37
- } from "./cli-yy8fwg1a.js";
37
+ } from "./cli-j2fapt3g.js";
38
38
  import {
39
39
  ConnectCodeError,
40
40
  connectWithCode,
41
41
  loadAuthSession,
42
42
  loadToken,
43
43
  login
44
- } from "./cli-dyeywy9d.js";
44
+ } from "./cli-rycpwqzm.js";
45
45
  import {
46
46
  CliAuthError,
47
47
  CliInputError,
@@ -49,7 +49,7 @@ import {
49
49
  createCommandContext,
50
50
  resolveIngestUrl,
51
51
  resolveModaBaseUrl
52
- } from "./cli-rp912ypf.js";
52
+ } from "./cli-8c60k2p4.js";
53
53
  import"./cli-0v6na3yp.js";
54
54
 
55
55
  // src/init/index.ts
@@ -5,8 +5,8 @@ import {
5
5
  inspectCodeSelectors,
6
6
  inspectPythonSelectors,
7
7
  safePromptEvidencePath
8
- } from "./cli-xsmsjny3.js";
9
- import"./cli-rp912ypf.js";
8
+ } from "./cli-5tsqk3jt.js";
9
+ import"./cli-8c60k2p4.js";
10
10
  import {
11
11
  __commonJS,
12
12
  __toESM
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  selectTenantAndCreateKey
3
- } from "./cli-yy8fwg1a.js";
3
+ } from "./cli-j2fapt3g.js";
4
4
  import {
5
5
  loadAuthSession
6
- } from "./cli-dyeywy9d.js";
6
+ } from "./cli-rycpwqzm.js";
7
7
  import {
8
8
  CliAuthError,
9
9
  resolveIngestUrl,
10
10
  resolveModaBaseUrl,
11
11
  stringOption
12
- } from "./cli-rp912ypf.js";
12
+ } from "./cli-8c60k2p4.js";
13
13
  import"./cli-0v6na3yp.js";
14
14
 
15
15
  // src/provision.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moda-ai/cli",
3
- "version": "1.42.0",
3
+ "version": "1.43.1",
4
4
  "description": "CLI for Moda - AI agent analytics and observability",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema_version": "moda.skill_index.v1",
3
- "bundled_at": "2026-10-09T02:07:16.635Z",
4
- "cli_version": "1.42.0",
3
+ "bundled_at": "2026-10-09T03:57:10.818Z",
4
+ "cli_version": "1.43.1",
5
5
  "skills": [
6
6
  {
7
7
  "id": "integration-cloudflare-think",
@@ -442,7 +442,9 @@ fail instead of being clamped.
442
442
 
443
443
  `--window=N` on `context`, `frustrations --include-window` and
444
444
  `tool-failure-detail --include-window` is a message half-width (1–5), not a
445
- time window.
445
+ time window. On `context`, `--window=0` returns only the anchor message (plus
446
+ `total_messages`), and a value above 5 is clamped to 5 with a warning that
447
+ prints the `--all --from --max-messages` command for a wider slice.
446
448
 
447
449
  ### Schema introspection
448
450
 
@@ -631,6 +633,7 @@ Filters: `--search`, `--cluster-id`, `--user-id`, `--time-range`
631
633
  moda context <conversation_id> # default window around middle
632
634
  moda context <conversation_id> --msg-index=5 # center on message 5
633
635
  moda context <conversation_id> --window=3 # 3 messages each side (max 5)
636
+ moda context <conversation_id> --msg-index=40 --window=0 # just message 40 + total_messages
634
637
  moda context <conversation_id> --all # whole trace in order, first 500 messages
635
638
  moda context <conversation_id> --all --from=500 --max-messages=500 # continue (max 5000)
636
639
  ```
@@ -767,6 +770,13 @@ earlier turns as history);
767
770
  `anthropic/claude-haiku-4-5`, `google/gemini-2.5-flash`,
768
771
  `google/gemini-2.5-pro` unless you pass `--allow-any-model`.
769
772
 
773
+ `prompts ab --traces=<ids>` builds one case per trace from Moda's distilled
774
+ scenario and success criteria for that trace. A trace Moda cannot distill
775
+ falls back to its opening user message with three generic criteria (which long
776
+ traces pass easily); the run warns and lists it under `caseSources.fallback`.
777
+ `prompts ab` never makes its replay set the tenant's primary set unless you
778
+ pass `--promote`.
779
+
770
780
  Runtime code should render synced prompts through the SDK:
771
781
 
772
782
  ```typescript