@jpcasabiancai/cayenne 0.5.3 → 0.5.4

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.
Files changed (2) hide show
  1. package/dist/cli.js +115 -16
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -7753,7 +7753,15 @@ async function askGuard(guard, toolName2, input) {
7753
7753
  return GUARD_FAILED;
7754
7754
  }
7755
7755
  }
7756
- var SESSION_FLAG_SETTINGS, GUARD_FAILED, ASK_TOOL_REASON, ClaudeSession;
7756
+ function asInterrupted(row) {
7757
+ return {
7758
+ ...row,
7759
+ content: row.content.map(
7760
+ (block) => block.type === "result" ? { ...block, isError: false, text: INTERRUPTED } : block
7761
+ )
7762
+ };
7763
+ }
7764
+ var SESSION_FLAG_SETTINGS, GUARD_FAILED, ASK_TOOL_REASON, INTERRUPTED, STOPPED_TURN, ClaudeSession;
7757
7765
  var init_session2 = __esm({
7758
7766
  "src/claude/session.ts"() {
7759
7767
  "use strict";
@@ -7768,6 +7776,8 @@ var init_session2 = __esm({
7768
7776
  };
7769
7777
  GUARD_FAILED = "Refused: the runner could not check this tool call.";
7770
7778
  ASK_TOOL_REASON = "This session was started from a task, so every shell command needs the member's approval.";
7779
+ INTERRUPTED = "interrupted";
7780
+ STOPPED_TURN = "error_during_execution after a requested stop: recorded as interrupted";
7771
7781
  ClaudeSession = class extends EventEmitter {
7772
7782
  constructor(opts) {
7773
7783
  super();
@@ -7786,6 +7796,13 @@ var init_session2 = __esm({
7786
7796
  /** `null` until `accountInfo()` answers, and for good when it cannot. */
7787
7797
  authMode = null;
7788
7798
  authAsked = false;
7799
+ /** A prompt is waiting for its turn, or a turn began (`init`), and no result has closed it. */
7800
+ turnOpen = false;
7801
+ /**
7802
+ * `interrupt()` or `stop()` was asked of the open turn. The CLI reports a turn cut short as
7803
+ * `error_during_execution`; with this set, that result is the stop landing, not a failure.
7804
+ */
7805
+ stopAsked = false;
7789
7806
  start() {
7790
7807
  if (this.q) throw new Error("VALIDATION: session already started");
7791
7808
  const queryFn = this.opts.queryFn ?? sdkQuery;
@@ -7869,6 +7886,7 @@ var init_session2 = __esm({
7869
7886
  })),
7870
7887
  { type: "text", text }
7871
7888
  ] : text;
7889
+ this.turnOpen = true;
7872
7890
  this.inbox.push({
7873
7891
  type: "user",
7874
7892
  message: { role: "user", content },
@@ -7885,6 +7903,7 @@ var init_session2 = __esm({
7885
7903
  return true;
7886
7904
  }
7887
7905
  async interrupt() {
7906
+ if (this.turnOpen) this.stopAsked = true;
7888
7907
  await this.q?.interrupt();
7889
7908
  }
7890
7909
  async setPermissionMode(mode) {
@@ -7901,6 +7920,7 @@ var init_session2 = __esm({
7901
7920
  for (const id of [...this.pending.keys()]) {
7902
7921
  this.expire(id, "session stopped");
7903
7922
  }
7923
+ if (this.turnOpen) this.stopAsked = true;
7904
7924
  void this.q?.interrupt().catch(() => void 0);
7905
7925
  const killer = setTimeout(() => this.abort.abort(), 5e3);
7906
7926
  await this.pumping;
@@ -7955,6 +7975,7 @@ ${this.stderrTail.join("\n")}` : "";
7955
7975
  }
7956
7976
  route(msg) {
7957
7977
  if (msg.type === "system" && msg.subtype === "init") {
7978
+ this.turnOpen = true;
7958
7979
  this.providerSessionId = msg.session_id;
7959
7980
  this.lookUpAuthMode();
7960
7981
  this.emit("init", {
@@ -7974,15 +7995,19 @@ ${this.stderrTail.join("\n")}` : "";
7974
7995
  });
7975
7996
  return;
7976
7997
  }
7998
+ const stopped = msg.type === "result" && this.stopAsked && msg.subtype === "error_during_execution";
7999
+ if (stopped) logger.info({ providerSessionId: this.providerSessionId }, STOPPED_TURN);
7977
8000
  const normalized = normalizeSdkMessage(msg);
7978
- if (normalized) this.emit("message", normalized);
8001
+ if (normalized) this.emit("message", stopped ? asInterrupted(normalized) : normalized);
7979
8002
  if (msg.type === "result") {
8003
+ this.turnOpen = false;
8004
+ this.stopAsked = false;
7980
8005
  this.emit("result", {
7981
- isError: msg.is_error,
8006
+ isError: stopped ? false : msg.is_error,
7982
8007
  durationMs: msg.duration_ms,
7983
8008
  numTurns: msg.num_turns,
7984
8009
  totalCostUsd: msg.total_cost_usd,
7985
- stopReason: msg.stop_reason,
8010
+ stopReason: stopped ? INTERRUPTED : msg.stop_reason,
7986
8011
  authMode: this.authMode
7987
8012
  });
7988
8013
  }
@@ -9965,7 +9990,7 @@ function shrink(event) {
9965
9990
  return null;
9966
9991
  }
9967
9992
  }
9968
- var READ_ONLY_TEXT, REFRESH_WITHIN_MS, betweenTurns, UNREADABLE_MARK_CREDS, roundUsd, UNIQUE_VIOLATION, ROW_WRITE_TIMEOUT_MS, ORPHANED_STATUSES, RESTARTABLE_STATUSES, isAtOrBelow3, DENY_MESSAGE_MAX, EXIT_ERROR_MAX, INITIAL_PROMPT_MAX, TASK_SKILL_LOOKUP_MS, TASK_REFUSED_MODES, OWNER_DRIVEN_KINDS, REPO_OWNER_ONLY, REPO_NO_DUPLICATE, PROVIDER_SESSION_ID, REPO_WORKTREE_GONE, TASK_ASK_TOOLS, MEMORY_PREAMBLE, MEMORY_SOURCE, wrapMemory, PATH_INPUT_TOOLS, PATH_INPUT_KEYS, isRecord, errorCode, SessionManager;
9993
+ var READ_ONLY_TEXT, REFRESH_WITHIN_MS, betweenTurns, UNREADABLE_MARK_CREDS, DRAINED_AFTER_MS, roundUsd, UNIQUE_VIOLATION, ROW_WRITE_TIMEOUT_MS, ORPHANED_STATUSES, RESTARTABLE_STATUSES, isAtOrBelow3, DENY_MESSAGE_MAX, EXIT_ERROR_MAX, INITIAL_PROMPT_MAX, TASK_SKILL_LOOKUP_MS, TASK_REFUSED_MODES, OWNER_DRIVEN_KINDS, REPO_OWNER_ONLY, REPO_NO_DUPLICATE, PROVIDER_SESSION_ID, REPO_WORKTREE_GONE, TASK_ASK_TOOLS, MEMORY_PREAMBLE, MEMORY_SOURCE, wrapMemory, PATH_INPUT_TOOLS, PATH_INPUT_KEYS, isRecord, errorCode, SessionManager;
9969
9994
  var init_manager = __esm({
9970
9995
  "src/sessions/manager.ts"() {
9971
9996
  "use strict";
@@ -10006,6 +10031,7 @@ var init_manager = __esm({
10006
10031
  state: "unavailable",
10007
10032
  readOnlyText: READ_ONLY_UNAVAILABLE
10008
10033
  };
10034
+ DRAINED_AFTER_MS = 2e3;
10009
10035
  roundUsd = (usd) => Math.round(usd * 1e6) / 1e6;
10010
10036
  UNIQUE_VIOLATION = "23505";
10011
10037
  ROW_WRITE_TIMEOUT_MS = 3e4;
@@ -10609,6 +10635,7 @@ var init_manager = __esm({
10609
10635
  );
10610
10636
  await this.setStatus(sessionId, "running");
10611
10637
  live.session.prompt(text, p.clientUuid, staged);
10638
+ this.turnHandedOver(live);
10612
10639
  this.deps.timings?.prompted(sessionId);
10613
10640
  } catch (err) {
10614
10641
  staged.discard();
@@ -10971,12 +10998,78 @@ var init_manager = __esm({
10971
10998
  async interrupt(sessionId) {
10972
10999
  const live = this.live.get(sessionId);
10973
11000
  if (live) {
10974
- await this.provider(() => live.session.interrupt());
11001
+ const { turns } = live;
11002
+ turns.stops = turns.owed;
11003
+ turns.chased = false;
11004
+ turns.early = turns.owed > 0 && !turns.begun && !live.stopping && !live.dropping;
11005
+ if (turns.early) {
11006
+ this.sendStop(sessionId, live);
11007
+ if (live.status === "running") await this.setStatus(sessionId, "idle");
11008
+ return {};
11009
+ }
11010
+ try {
11011
+ await this.provider(() => live.session.interrupt());
11012
+ } catch (err) {
11013
+ turns.stops = 0;
11014
+ throw err;
11015
+ }
10975
11016
  return {};
10976
11017
  }
10977
11018
  await this.settleOrphans([sessionId]);
10978
11019
  return {};
10979
11020
  }
11021
+ /** A stop nobody waits on: a provider that is starting, or between turns, may answer late. */
11022
+ sendStop(sessionId, live) {
11023
+ void live.session.interrupt().catch(
11024
+ (err) => this.deps.log.warn(
11025
+ { err: err instanceof Error ? err.message : String(err), sessionId },
11026
+ "a stop sent ahead of its turn failed"
11027
+ )
11028
+ );
11029
+ }
11030
+ /** A prompt went to the provider. */
11031
+ turnHandedOver(live) {
11032
+ const { turns } = live;
11033
+ const leftover = live.provider === "claude" && turns.stops === 0 && turns.drainedAt !== null && Date.now() - turns.drainedAt > DRAINED_AFTER_MS;
11034
+ if (leftover) {
11035
+ turns.owed = 0;
11036
+ turns.stops = 0;
11037
+ }
11038
+ turns.drainedAt = null;
11039
+ turns.owed += 1;
11040
+ }
11041
+ /** The provider announced a turn (`init`): if a Stop covers it, this is where it lands. */
11042
+ turnBegan(sessionId, live) {
11043
+ if (live.provider !== "claude") return;
11044
+ const { turns } = live;
11045
+ turns.drainedAt = null;
11046
+ if (turns.owed > 0) turns.begun = true;
11047
+ if (turns.stops > 0 && turns.early) this.sendStop(sessionId, live);
11048
+ }
11049
+ /**
11050
+ * The provider produced output. For a turn stopped before it began that means the stop did not
11051
+ * land (a backend with no `init` per turn, or a stop that was lost): it is sent once more, and
11052
+ * `true` tells the caller the row must say `running` again, since a turn is.
11053
+ */
11054
+ turnOutput(sessionId, live) {
11055
+ const { turns } = live;
11056
+ turns.drainedAt = null;
11057
+ if (turns.owed > 0) turns.begun = true;
11058
+ if (turns.stops === 0 || !turns.early || turns.chased) return false;
11059
+ turns.chased = true;
11060
+ this.sendStop(sessionId, live);
11061
+ return live.status === "idle";
11062
+ }
11063
+ /** A turn ended: whatever a Stop still covers is behind it, and has not begun. */
11064
+ turnEnded(live) {
11065
+ const { turns } = live;
11066
+ turns.owed = Math.max(0, turns.owed - 1);
11067
+ turns.stops = Math.max(0, turns.stops - 1);
11068
+ turns.begun = false;
11069
+ turns.drainedAt = Date.now();
11070
+ turns.early = true;
11071
+ turns.chased = false;
11072
+ }
10980
11073
  /**
10981
11074
  * Put rows on this runner that claim a turn in progress back to `idle`, for the chats that have
10982
11075
  * no live session here — at boot every one of them, since nothing is live yet, and again when
@@ -11809,6 +11902,7 @@ var init_manager = __esm({
11809
11902
  permissions: /* @__PURE__ */ new Map(),
11810
11903
  cwd: opts.cwd,
11811
11904
  stopping: false,
11905
+ turns: { owed: 0, begun: false, drainedAt: null, stops: 0, early: false, chased: false },
11812
11906
  dropping: null,
11813
11907
  inflight: /* @__PURE__ */ new Set(),
11814
11908
  writes: Promise.resolve(),
@@ -11975,9 +12069,12 @@ var init_manager = __esm({
11975
12069
  live.inflight.add(task);
11976
12070
  void task.finally(() => live.inflight.delete(task));
11977
12071
  };
11978
- session.on(
11979
- "init",
11980
- (init) => guard(async () => {
12072
+ const output = () => {
12073
+ if (this.turnOutput(sessionId, live)) guard(() => this.setStatus(sessionId, "running"))();
12074
+ };
12075
+ session.on("init", (init) => {
12076
+ this.turnBegan(sessionId, live);
12077
+ return guard(async () => {
11981
12078
  await this.updateSession(sessionId, {
11982
12079
  provider_session_id: init.providerSessionId,
11983
12080
  model: init.model
@@ -11988,9 +12085,10 @@ var init_manager = __esm({
11988
12085
  providerSessionId: init.providerSessionId,
11989
12086
  model: init.model
11990
12087
  });
11991
- })()
11992
- );
12088
+ })();
12089
+ });
11993
12090
  session.on("stream", (s) => {
12091
+ output();
11994
12092
  this.deps.timings?.streamed(sessionId);
11995
12093
  guard(
11996
12094
  () => this.broadcast(sessionId, {
@@ -12001,9 +12099,9 @@ var init_manager = __esm({
12001
12099
  })
12002
12100
  )();
12003
12101
  });
12004
- session.on(
12005
- "message",
12006
- (m) => guard(async () => {
12102
+ session.on("message", (m) => {
12103
+ if (m.role === "assistant" || m.role === "tool") output();
12104
+ return guard(async () => {
12007
12105
  if (m.role === "user" && m.providerUuid && live.echoed.has(m.providerUuid)) return;
12008
12106
  await this.persist(
12009
12107
  sessionId,
@@ -12014,8 +12112,8 @@ var init_manager = __esm({
12014
12112
  m.parentToolUseId,
12015
12113
  m.raw
12016
12114
  );
12017
- })()
12018
- );
12115
+ })();
12116
+ });
12019
12117
  session.on(
12020
12118
  "permission",
12021
12119
  (rawReq) => guard(async () => {
@@ -12104,6 +12202,7 @@ var init_manager = __esm({
12104
12202
  })()
12105
12203
  );
12106
12204
  session.on("result", (r) => {
12205
+ this.turnEnded(live);
12107
12206
  this.deps.timings?.finished(sessionId);
12108
12207
  const lastSeq = live.writes.then(() => live.seq);
12109
12208
  guard(async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jpcasabiancai/cayenne",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "files": [
5
5
  "dist/cli.js"
6
6
  ],