@wayai/cli 0.3.125 → 0.3.126

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/index.js CHANGED
@@ -8927,6 +8927,11 @@ var init_api_client = __esm({
8927
8927
  if (fixture) params.set("fixture", fixture);
8928
8928
  return this.request("POST", `/api/evals/sessions/${sessionId}/run?${params.toString()}`);
8929
8929
  }
8930
+ /** Cancel a running eval session. A session that already finished is left unchanged. */
8931
+ async stopEvalSession(hubId, sessionId) {
8932
+ const params = new URLSearchParams({ hub_id: hubId });
8933
+ return this.request("POST", `/api/evals/sessions/${sessionId}/stop?${params.toString()}`);
8934
+ }
8930
8935
  async getEvalSessionDetails(hubId, sessionId) {
8931
8936
  const params = new URLSearchParams({ hub_id: hubId });
8932
8937
  return this.request("GET", `/api/evals/sessions/${sessionId}?${params.toString()}`);
@@ -9332,6 +9337,43 @@ var init_api_client = __esm({
9332
9337
  }
9333
9338
  });
9334
9339
 
9340
+ // src/lib/errors.ts
9341
+ function extractApiMessage(err) {
9342
+ if (err instanceof ApiError) {
9343
+ try {
9344
+ const parsed = JSON.parse(err.body);
9345
+ if (parsed.message) return parsed.message;
9346
+ if (parsed.error) return parsed.error;
9347
+ } catch {
9348
+ }
9349
+ return err.body || err.message;
9350
+ }
9351
+ return err instanceof Error ? err.message : String(err);
9352
+ }
9353
+ function friendlyHint(err) {
9354
+ if (isNetworkError(err)) {
9355
+ return "Couldn't reach WayAI \u2014 check your network connection and try again.";
9356
+ }
9357
+ if (!(err instanceof ApiError)) return null;
9358
+ if (err.body.includes("FREE_PLAN_RESTRICTION")) {
9359
+ return "Publishing to production requires a paid plan. Upgrade your organization to publish or sync.";
9360
+ }
9361
+ const { status } = err;
9362
+ if (status === 401) return "Your session may have expired. Run `wayai login` to re-authenticate.";
9363
+ if (status === 402) return "Quota exceeded for your plan. Review usage or upgrade your plan.";
9364
+ if (status === 403) return "Access denied. Your token may lack permission for this hub or resource.";
9365
+ if (status === 429) return "Rate limited. Wait a moment and try again.";
9366
+ if (status >= 500) return "WayAI server error \u2014 likely temporary. Try again shortly.";
9367
+ return null;
9368
+ }
9369
+ var init_errors2 = __esm({
9370
+ "src/lib/errors.ts"() {
9371
+ "use strict";
9372
+ init_api_client();
9373
+ init_network_error();
9374
+ }
9375
+ });
9376
+
9335
9377
  // ../../packages/core/dist/index.js
9336
9378
  function isPublicIdentifier(value) {
9337
9379
  return UUID_RE.test(value) || HEX_RE.test(value) || WORKOS_ID_RE.test(value);
@@ -19772,6 +19814,126 @@ var init_evals = __esm({
19772
19814
  }
19773
19815
  });
19774
19816
 
19817
+ // src/lib/eval-session-control.ts
19818
+ function formatEvalSessionStopCommand(sessionId, hubId) {
19819
+ return `wayai eval session stop ${sessionId} --hub ${hubId}`;
19820
+ }
19821
+ function formatEvalResultsCommand(sessionId, hubId) {
19822
+ return `wayai eval-results --session ${sessionId} --hub ${hubId}`;
19823
+ }
19824
+ function classifyStopOutcome(response) {
19825
+ return response.data.stopped ? { kind: "cancelled" } : { kind: "already_finished", status: response.data.session.session_status };
19826
+ }
19827
+ function isTerminalStopFailure(err) {
19828
+ return err instanceof ApiError && err.status >= 400 && err.status < 500 && err.status !== 408 && err.status !== 429;
19829
+ }
19830
+ async function stopEvalSessionWithRetries(stop, opts = {}) {
19831
+ const attempts = opts.attempts ?? STOP_ATTEMPTS;
19832
+ const attemptTimeoutMs = opts.attemptTimeoutMs ?? STOP_ATTEMPT_TIMEOUT_MS;
19833
+ const backoffMs = opts.backoffMs ?? STOP_BACKOFF_MS;
19834
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
19835
+ let lastError = new Error("Stop was never attempted");
19836
+ for (let attempt = 1; attempt <= attempts; attempt++) {
19837
+ let timeout;
19838
+ try {
19839
+ return await Promise.race([
19840
+ stop(),
19841
+ new Promise((_resolve, reject) => {
19842
+ timeout = setTimeout(
19843
+ () => reject(new Error(`Stop attempt ${attempt} timed out after ${attemptTimeoutMs}ms`)),
19844
+ attemptTimeoutMs
19845
+ );
19846
+ })
19847
+ ]);
19848
+ } catch (err) {
19849
+ lastError = err;
19850
+ if (isTerminalStopFailure(err)) break;
19851
+ } finally {
19852
+ if (timeout !== void 0) clearTimeout(timeout);
19853
+ }
19854
+ const delay2 = backoffMs[attempt - 1];
19855
+ if (attempt < attempts && delay2) await sleep2(delay2);
19856
+ }
19857
+ throw lastError;
19858
+ }
19859
+ function installEvalSignalHandlers(input) {
19860
+ const exit = input.exit ?? ((code) => process.exit(code));
19861
+ const manualStopCommand = formatEvalSessionStopCommand(input.sessionId, input.hubId);
19862
+ let signalCount = 0;
19863
+ let disposed = false;
19864
+ let settle;
19865
+ const settled = new Promise((resolve6) => {
19866
+ settle = resolve6;
19867
+ });
19868
+ const listeners = /* @__PURE__ */ new Map();
19869
+ const dispose = () => {
19870
+ if (disposed) return;
19871
+ disposed = true;
19872
+ for (const [signal, listener] of listeners) process.off(signal, listener);
19873
+ };
19874
+ const finish = (exitCode) => {
19875
+ dispose();
19876
+ settle();
19877
+ exit(exitCode);
19878
+ };
19879
+ const handle = (signal) => {
19880
+ signalCount += 1;
19881
+ const signalExitCode = SIGNAL_EXIT_CODE[signal];
19882
+ if (signalCount > 1) {
19883
+ console.error("\nSecond signal received; exiting immediately. The remote eval session may still be running.");
19884
+ console.error(`Run: ${manualStopCommand}`);
19885
+ finish(signalExitCode);
19886
+ return;
19887
+ }
19888
+ console.error(`
19889
+ ${signal} received; stopping remote eval session ${input.sessionId}...`);
19890
+ void stopEvalSessionWithRetries(input.stop).then((response) => {
19891
+ if (signalCount > 1) return;
19892
+ const outcome = classifyStopOutcome(response);
19893
+ if (outcome.kind === "already_finished") {
19894
+ console.error(`Session had already finished (${outcome.status}) \u2014 nothing was cancelled.`);
19895
+ console.error(`See its results: ${formatEvalResultsCommand(input.sessionId, input.hubId)}`);
19896
+ } else {
19897
+ console.error("Remote session cancellation requested.");
19898
+ console.error("A turn already in progress may still be finishing \u2014 wait for it before starting another run.");
19899
+ }
19900
+ finish(signalExitCode);
19901
+ }).catch((err) => {
19902
+ if (signalCount > 1) return;
19903
+ console.error(`Failed to confirm remote cancellation: ${extractApiMessage(err)}`);
19904
+ console.error(`The session may still be running. Run: ${manualStopCommand}`);
19905
+ finish(1);
19906
+ });
19907
+ };
19908
+ for (const signal of HANDLED_SIGNALS) {
19909
+ const listener = () => handle(signal);
19910
+ listeners.set(signal, listener);
19911
+ process.on(signal, listener);
19912
+ }
19913
+ return {
19914
+ manualStopCommand,
19915
+ async interrupted() {
19916
+ if (signalCount === 0) return false;
19917
+ await settled;
19918
+ return true;
19919
+ },
19920
+ dispose
19921
+ };
19922
+ }
19923
+ var SIGNAL_EXIT_CODE, HANDLED_SIGNALS, STOP_ATTEMPTS, STOP_ATTEMPT_TIMEOUT_MS, STOP_BACKOFF_MS;
19924
+ var init_eval_session_control = __esm({
19925
+ "src/lib/eval-session-control.ts"() {
19926
+ "use strict";
19927
+ init_api_client();
19928
+ init_errors2();
19929
+ SIGNAL_EXIT_CODE = { SIGINT: 130, SIGTERM: 143, SIGHUP: 129 };
19930
+ HANDLED_SIGNALS = Object.keys(SIGNAL_EXIT_CODE);
19931
+ STOP_ATTEMPTS = 3;
19932
+ STOP_ATTEMPT_TIMEOUT_MS = 1e4;
19933
+ STOP_BACKOFF_MS = [500, 1e3];
19934
+ }
19935
+ });
19936
+
19775
19937
  // src/commands/run-eval.ts
19776
19938
  var run_eval_exports = {};
19777
19939
  __export(run_eval_exports, {
@@ -19858,6 +20020,31 @@ async function runEvalCommand(args2) {
19858
20020
  const sessionName = `CLI run ${now.toISOString().slice(0, 16).replace("T", " ")}`;
19859
20021
  const createResult = await client.createEvalSession(hubId, sessionName, scenarioSetId, pacing);
19860
20022
  const sessionId = createResult.data.session.eval_session_id;
20023
+ const signalController = installEvalSignalHandlers({
20024
+ hubId,
20025
+ sessionId,
20026
+ stop: () => client.stopEvalSession(hubId, sessionId)
20027
+ });
20028
+ try {
20029
+ await pollEvalSession({
20030
+ client,
20031
+ hubId,
20032
+ sessionId,
20033
+ sessionName,
20034
+ pacing,
20035
+ fixtureOverride,
20036
+ noWait,
20037
+ timeoutSeconds,
20038
+ signal: signalController
20039
+ });
20040
+ } finally {
20041
+ signalController.dispose();
20042
+ }
20043
+ }
20044
+ async function pollEvalSession(input) {
20045
+ const { client, hubId, sessionId, sessionName, pacing, fixtureOverride, noWait, timeoutSeconds } = input;
20046
+ const signalController = input.signal;
20047
+ const manualStopCommand = signalController.manualStopCommand;
19861
20048
  console.log(`Created session: ${sessionName} (${sessionId})`);
19862
20049
  if (pacing) {
19863
20050
  const desc = pacing.preset === "custom" ? `custom (${pacing.custom_interval_ms}ms/run)` : pacing.preset;
@@ -19870,21 +20057,27 @@ async function runEvalCommand(args2) {
19870
20057
  console.log(`Fixture: ${seed.fixture}${seed.target_base ? ` (base: ${seed.target_base})` : ""}`);
19871
20058
  }
19872
20059
  } catch (err) {
20060
+ if (await signalController.interrupted()) return;
19873
20061
  console.error(`
19874
20062
  Failed to start session: ${extractApiMessage(err)}`);
20063
+ console.error(`Session ID: ${sessionId}`);
20064
+ console.error(`Stop it if it started anyway: ${manualStopCommand}`);
19875
20065
  process.exit(1);
19876
20066
  }
20067
+ if (await signalController.interrupted()) return;
19877
20068
  console.log("Session started");
19878
20069
  if (noWait) {
19879
20070
  console.log(`
19880
20071
  Session ID: ${sessionId}`);
19881
20072
  console.log("Use `wayai eval-results --session " + sessionId + "` to check results.");
20073
+ console.log(`Stop it early with: ${manualStopCommand}`);
19882
20074
  return;
19883
20075
  }
19884
20076
  const deadline = Date.now() + timeoutSeconds * 1e3;
19885
20077
  let consecutivePollErrors = 0;
19886
20078
  while (Date.now() < deadline) {
19887
20079
  await sleep(POLL_INTERVAL_MS);
20080
+ if (await signalController.interrupted()) return;
19888
20081
  let details;
19889
20082
  try {
19890
20083
  details = await client.getEvalSessionDetails(hubId, sessionId);
@@ -19894,7 +20087,8 @@ Session ID: ${sessionId}`);
19894
20087
  console.error(`
19895
20088
 
19896
20089
  Lost connection to the eval session: ${extractApiMessage(err)}`);
19897
- console.error(`The session may still be running. Check results: wayai eval-results --session ${sessionId}`);
20090
+ console.error(`The session may still be running. Stop it with: ${manualStopCommand}`);
20091
+ console.error(`Check results: wayai eval-results --session ${sessionId}`);
19898
20092
  process.exit(1);
19899
20093
  }
19900
20094
  continue;
@@ -19904,6 +20098,7 @@ Lost connection to the eval session: ${extractApiMessage(err)}`);
19904
20098
  const totalRuns = results.reduce((sum, r) => sum + r.total_runs, 0);
19905
20099
  const completedRuns = results.reduce((sum, r) => sum + r.successful_runs + r.failed_runs, 0);
19906
20100
  if (TERMINAL_STATUSES.has(session.session_status)) {
20101
+ if (await signalController.interrupted()) return;
19907
20102
  console.log("");
19908
20103
  console.log(`Session ${session.session_status}: ${session.session_name} (${session.eval_session_id})
19909
20104
  `);
@@ -19925,6 +20120,7 @@ Could not load failure details (${extractApiMessage(err)}).`);
19925
20120
  console.log(`
19926
20121
 
19927
20122
  Timeout after ${timeoutSeconds}s. Session is still running.`);
20123
+ console.log(`Stop it with: ${manualStopCommand}`);
19928
20124
  console.log(`Check results: wayai eval-results --session ${sessionId}`);
19929
20125
  }
19930
20126
  function sleep(ms) {
@@ -19951,18 +20147,6 @@ function parsePacing(raw) {
19951
20147
  }
19952
20148
  return { preset: value };
19953
20149
  }
19954
- function extractApiMessage(err) {
19955
- if (err instanceof ApiError) {
19956
- try {
19957
- const parsed = JSON.parse(err.body);
19958
- if (parsed.message) return parsed.message;
19959
- if (parsed.error) return parsed.error;
19960
- } catch {
19961
- }
19962
- return err.body || err.message;
19963
- }
19964
- return err instanceof Error ? err.message : String(err);
19965
- }
19966
20150
  var TERMINAL_STATUSES, POLL_INTERVAL_MS, DEFAULT_TIMEOUT_S, MAX_CONSECUTIVE_POLL_ERRORS;
19967
20151
  var init_run_eval = __esm({
19968
20152
  "src/commands/run-eval.ts"() {
@@ -19971,9 +20155,11 @@ var init_run_eval = __esm({
19971
20155
  init_repo_config();
19972
20156
  init_workspace();
19973
20157
  init_api_client();
20158
+ init_errors2();
19974
20159
  init_eval_format();
19975
20160
  init_dist();
19976
20161
  init_contracts();
20162
+ init_eval_session_control();
19977
20163
  TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "awaiting_review"]);
19978
20164
  POLL_INTERVAL_MS = 3e3;
19979
20165
  DEFAULT_TIMEOUT_S = 600;
@@ -20352,6 +20538,54 @@ var init_journey_capture = __esm({
20352
20538
  }
20353
20539
  });
20354
20540
 
20541
+ // src/commands/eval-session-stop.ts
20542
+ var eval_session_stop_exports = {};
20543
+ __export(eval_session_stop_exports, {
20544
+ evalSessionStopCommand: () => evalSessionStopCommand
20545
+ });
20546
+ async function evalSessionStopCommand(args2) {
20547
+ const hubId = resolveActiveHubId(args2);
20548
+ const { config, accessToken } = await requireAuth();
20549
+ requireRepoConfig();
20550
+ const sessionId = args2.find((arg) => !arg.startsWith("-"));
20551
+ if (!sessionId) {
20552
+ console.error("Usage: wayai eval session stop <session_id> [--hub <id>]");
20553
+ process.exit(1);
20554
+ }
20555
+ if (!UUID_RE2.test(sessionId)) {
20556
+ console.error(`Invalid session id: ${sessionId}`);
20557
+ process.exit(1);
20558
+ }
20559
+ const client = new ApiClient({ apiUrl: config.api_url, accessToken });
20560
+ try {
20561
+ const outcome = classifyStopOutcome(await client.stopEvalSession(hubId, sessionId));
20562
+ if (outcome.kind === "already_finished") {
20563
+ console.log(`Eval session ${sessionId} had already finished (${outcome.status}) \u2014 nothing was cancelled.`);
20564
+ console.log(`See its results: ${formatEvalResultsCommand(sessionId, hubId)}`);
20565
+ } else {
20566
+ console.log(`Cancellation requested for eval session ${sessionId}.`);
20567
+ console.log("A turn already in progress may still be finishing \u2014 wait for it before starting another run.");
20568
+ }
20569
+ } catch (err) {
20570
+ if (err instanceof ApiError && err.status === 404) {
20571
+ console.error(`Eval session not found: ${sessionId}`);
20572
+ process.exit(1);
20573
+ }
20574
+ throw err;
20575
+ }
20576
+ }
20577
+ var init_eval_session_stop = __esm({
20578
+ "src/commands/eval-session-stop.ts"() {
20579
+ "use strict";
20580
+ init_auth();
20581
+ init_repo_config();
20582
+ init_workspace();
20583
+ init_api_client();
20584
+ init_eval_session_control();
20585
+ init_utils();
20586
+ }
20587
+ });
20588
+
20355
20589
  // src/commands/eval-session-delete.ts
20356
20590
  var eval_session_delete_exports = {};
20357
20591
  __export(eval_session_delete_exports, {
@@ -20457,6 +20691,11 @@ async function evalCommand(args2) {
20457
20691
  }
20458
20692
  case "session": {
20459
20693
  const [sessionSub, ...sessionRest] = rest;
20694
+ if (sessionSub === "stop") {
20695
+ const { evalSessionStopCommand: evalSessionStopCommand2 } = await Promise.resolve().then(() => (init_eval_session_stop(), eval_session_stop_exports));
20696
+ await evalSessionStopCommand2(sessionRest);
20697
+ return;
20698
+ }
20460
20699
  if (sessionSub === "delete") {
20461
20700
  const { evalSessionDeleteCommand: evalSessionDeleteCommand2 } = await Promise.resolve().then(() => (init_eval_session_delete(), eval_session_delete_exports));
20462
20701
  await evalSessionDeleteCommand2(sessionRest);
@@ -20490,6 +20729,8 @@ Subcommands:
20490
20729
  exchange as a scenario YAML in evals/.
20491
20730
  journey capture <conversation_id> Capture a real conversation's FULL
20492
20731
  transcript as a platform-managed journey.
20732
+ session stop <session_id> Cancel a running eval session (e.g. one
20733
+ left behind by an interrupted run-eval).
20493
20734
  session delete <session_id> Delete one eval session (and its
20494
20735
  conversations). Destructive, irreversible.
20495
20736
  session delete --all Delete every eval session on the hub.
@@ -20511,6 +20752,7 @@ Examples:
20511
20752
  wayai eval capture 11111111-2222-3333-4444-555555555555
20512
20753
  wayai eval capture <conv_id> --set regression-suite --name "Cancel order"
20513
20754
  wayai eval journey capture <conv_id> --name "Refund happy path"
20755
+ wayai eval session stop 11111111-2222-3333-4444-555555555555
20514
20756
  wayai eval session delete 11111111-2222-3333-4444-555555555555
20515
20757
  wayai eval session delete --all -y
20516
20758
 
@@ -24282,34 +24524,13 @@ var init_update = __esm({
24282
24524
 
24283
24525
  // src/index.ts
24284
24526
  init_sentry();
24527
+ init_errors2();
24528
+ init_mask_secrets();
24529
+ init_utils();
24285
24530
  import { readFileSync as readFileSync21 } from "fs";
24286
24531
  import { fileURLToPath as fileURLToPath2 } from "url";
24287
24532
  import { dirname as dirname9, join as join28 } from "path";
24288
24533
 
24289
- // src/lib/errors.ts
24290
- init_api_client();
24291
- init_network_error();
24292
- function friendlyHint(err) {
24293
- if (isNetworkError(err)) {
24294
- return "Couldn't reach WayAI \u2014 check your network connection and try again.";
24295
- }
24296
- if (!(err instanceof ApiError)) return null;
24297
- if (err.body.includes("FREE_PLAN_RESTRICTION")) {
24298
- return "Publishing to production requires a paid plan. Upgrade your organization to publish or sync.";
24299
- }
24300
- const { status } = err;
24301
- if (status === 401) return "Your session may have expired. Run `wayai login` to re-authenticate.";
24302
- if (status === 402) return "Quota exceeded for your plan. Review usage or upgrade your plan.";
24303
- if (status === 403) return "Access denied. Your token may lack permission for this hub or resource.";
24304
- if (status === 429) return "Rate limited. Wait a moment and try again.";
24305
- if (status >= 500) return "WayAI server error \u2014 likely temporary. Try again shortly.";
24306
- return null;
24307
- }
24308
-
24309
- // src/index.ts
24310
- init_mask_secrets();
24311
- init_utils();
24312
-
24313
24534
  // src/lib/version-refresh.ts
24314
24535
  init_version_cache();
24315
24536
  init_skill_version();
@@ -24718,6 +24939,7 @@ Commands:
24718
24939
  run-eval Run all enabled evals and show results
24719
24940
  eval-results View results from a completed eval session
24720
24941
  eval capture Capture a real conversation as a scenario YAML
24942
+ eval session stop Cancel a running eval session (recovery for an interrupted run-eval)
24721
24943
  eval session delete Delete one eval session (\`--all\` for every session on the hub)
24722
24944
  sync-skills Sync skills to Anthropic/OpenAI provider connections
24723
24945
  sync-mcp Re-discover an MCP connection's tools (refresh stale schemas)