@lesscap/baton-cli 0.2.0 → 0.2.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.
Files changed (2) hide show
  1. package/dist/baton.mjs +48 -4
  2. package/package.json +1 -1
package/dist/baton.mjs CHANGED
@@ -578,6 +578,7 @@ var sessionsClient = (baseUrl) => {
578
578
  resume: (id) => request(u(`/sessions/${id}/resume`), { method: "POST" }),
579
579
  stop: (id) => request(u(`/sessions/${id}/stop`), { method: "POST" }),
580
580
  clear: (id) => request(u(`/sessions/${id}/clear`), { method: "POST" }),
581
+ abort: (id) => request(u(`/sessions/${id}/abort`), { method: "POST" }),
581
582
  rename: (id, name) => request(u(`/sessions/${id}/rename`), { method: "POST", body: { name } }),
582
583
  listByProject: (projectId) => request(u(`/projects/${projectId}/sessions`), { method: "GET" }),
583
584
  get: (id) => request(u(`/sessions/${id}`), { method: "GET" }),
@@ -1035,6 +1036,22 @@ var resolveSession = async (client, projectId, handle) => {
1035
1036
  throw new Error(`session "${handle}" not found in project ${projectId}`);
1036
1037
  };
1037
1038
 
1039
+ // src/commands/session/abort.ts
1040
+ var sessionAbortCommand = defineCommand({
1041
+ meta: { name: "abort", description: "interrupt the in-flight turn (like Esc); keeps the session" },
1042
+ args: {
1043
+ session: { type: "positional", required: true, description: "session int id or name" },
1044
+ project: { type: "string", description: "project id (overrides .baton.json)" },
1045
+ ...common
1046
+ },
1047
+ run: async ({ args }) => {
1048
+ const c = clientFor(args);
1049
+ const handle = await resolveSession(c, resolveProjectId(args), args.session);
1050
+ const s = await c.sessions.abort(handle.id);
1051
+ console.log(renderOne(s, fmtSession, Boolean(args.json)));
1052
+ }
1053
+ });
1054
+
1038
1055
  // src/commands/session/clear.ts
1039
1056
  var sessionClearCommand = defineCommand({
1040
1057
  meta: { name: "clear", description: "reset the conversation context (keeps the session + worktree)" },
@@ -1807,6 +1824,7 @@ var toTurnResult = (message) => {
1807
1824
  // src/session/runner/turn.ts
1808
1825
  var turnTimeoutMs = () => Number(process.env.BATON_TURN_TIMEOUT_MS) || 30 * 6e4;
1809
1826
  var TIMEOUT = /* @__PURE__ */ Symbol("turn-timeout");
1827
+ var ABORTED = /* @__PURE__ */ Symbol("turn-aborted");
1810
1828
  var finalizeTurn = async (result, worker2, log) => {
1811
1829
  if (!result) {
1812
1830
  log("[exit] stream ended with no result");
@@ -1835,7 +1853,7 @@ var buildPrompt = async (config, rawText, attachments, log, fetchImpl) => {
1835
1853
  log(`[attach] downloaded ${relPaths.length} file(s) \u2192 ${config.worktreePath}/attachments`);
1836
1854
  return augmentPrompt(rawText, relPaths, labelAttachments(attachments));
1837
1855
  };
1838
- var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => console.log(m), envOverlay, fetchImpl) => {
1856
+ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => console.log(m), envOverlay, fetchImpl, externalSignal) => {
1839
1857
  await worker2.emitEvent("turn_start", { messageId: msg.id });
1840
1858
  const payload = msg.payload;
1841
1859
  const rawText = typeof payload?.text === "string" ? payload.text : "";
@@ -1858,12 +1876,23 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
1858
1876
  const timeout = new Promise((resolve) => {
1859
1877
  timer = setTimeout(() => resolve(TIMEOUT), ceiling);
1860
1878
  });
1879
+ const interrupted = new Promise((resolve) => {
1880
+ if (externalSignal?.aborted) resolve(ABORTED);
1881
+ else externalSignal?.addEventListener("abort", () => resolve(ABORTED), { once: true });
1882
+ });
1861
1883
  try {
1862
1884
  const messages = startQuery(config, text, resuming, queryFn, abort, log, envOverlay);
1863
1885
  const consume = streamSdkEvents(messages, worker2);
1864
1886
  void consume.catch(() => {
1865
1887
  });
1866
- const outcome = await Promise.race([consume, timeout]);
1888
+ const outcome = await Promise.race([consume, timeout, interrupted]);
1889
+ if (outcome === ABORTED) {
1890
+ log("[abort] interrupted by user");
1891
+ abort.abort();
1892
+ await worker2.emitEvent("turn_error", { message: "interrupted by user" });
1893
+ await worker2.emitEvent("turn_complete", {});
1894
+ return -1;
1895
+ }
1867
1896
  if (outcome === TIMEOUT) {
1868
1897
  log(`[watchdog] turn exceeded ${ceiling}ms \u2014 aborting claude`);
1869
1898
  abort.abort();
@@ -1884,7 +1913,7 @@ var runTurn = async (config, worker2, msg, resuming, queryFn, log = (m) => conso
1884
1913
  };
1885
1914
 
1886
1915
  // src/session/runner.ts
1887
- var subscribeStream = (url, ESCtor, seen, log, onUserMessage, onReady) => {
1916
+ var subscribeStream = (url, ESCtor, seen, log, onUserMessage, onInterrupt, onReady) => {
1888
1917
  const es = new ESCtor(url);
1889
1918
  es.onopen = onReady;
1890
1919
  es.onmessage = (e) => {
@@ -1893,6 +1922,8 @@ var subscribeStream = (url, ESCtor, seen, log, onUserMessage, onReady) => {
1893
1922
  if (seen.has(ev.sequence)) return;
1894
1923
  seen.add(ev.sequence);
1895
1924
  if (ev.type === "user_message") onUserMessage(ev);
1925
+ else if (ev.type === "system" && ev.payload?.action === "interrupt")
1926
+ onInterrupt();
1896
1927
  } catch {
1897
1928
  }
1898
1929
  };
@@ -1925,6 +1956,7 @@ var runDaemon = async (config, deps, signal) => {
1925
1956
  const pendingQueue = [];
1926
1957
  let busy = false;
1927
1958
  let lastActivity = Date.now();
1959
+ let currentTurn;
1928
1960
  const drain = async () => {
1929
1961
  if (busy) return;
1930
1962
  busy = true;
@@ -1934,6 +1966,8 @@ var runDaemon = async (config, deps, signal) => {
1934
1966
  if (!msg) break;
1935
1967
  const resuming = state.firstSpawnDone;
1936
1968
  log(`\u25B6 msg #${msg.sequence} (${resuming ? "resume" : "first"})`);
1969
+ const turnAbort = new AbortController();
1970
+ currentTurn = turnAbort;
1937
1971
  const code = await runTurn(
1938
1972
  config,
1939
1973
  deps.worker,
@@ -1942,8 +1976,10 @@ var runDaemon = async (config, deps, signal) => {
1942
1976
  qf,
1943
1977
  log,
1944
1978
  deps.env,
1945
- deps.fetchImpl
1979
+ deps.fetchImpl,
1980
+ turnAbort.signal
1946
1981
  );
1982
+ currentTurn = void 0;
1947
1983
  state.firstSpawnDone = true;
1948
1984
  lastActivity = Date.now();
1949
1985
  log(`\u2714 msg #${msg.sequence}${code === 0 ? "" : ` (exit ${code})`}`);
@@ -1964,6 +2000,13 @@ var runDaemon = async (config, deps, signal) => {
1964
2000
  pendingQueue.push(ev);
1965
2001
  void drain();
1966
2002
  },
2003
+ () => {
2004
+ lastActivity = Date.now();
2005
+ if (currentTurn) {
2006
+ log("interrupt \u2014 aborting current turn");
2007
+ currentTurn.abort();
2008
+ }
2009
+ },
1967
2010
  // Report active only now that we're subscribed — before this, a message
1968
2011
  // sent right after spawn would be missed (no replay on the live stream).
1969
2012
  () => {
@@ -2144,6 +2187,7 @@ var session = defineCommand({
2144
2187
  resume: sessionResumeCommand,
2145
2188
  stop: sessionStopCommand,
2146
2189
  clear: sessionClearCommand,
2190
+ abort: sessionAbortCommand,
2147
2191
  rename: sessionRenameCommand,
2148
2192
  rm: sessionRmCommand,
2149
2193
  send: sessionSendCommand,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lesscap/baton-cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "baton CLI — register/run a worker, manage projects/requirements/tasks/sessions",
6
6
  "bin": {