@lesscap/baton-cli 0.7.3 → 0.7.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/baton.mjs +96 -16
  2. package/package.json +1 -1
package/dist/baton.mjs CHANGED
@@ -5024,6 +5024,7 @@ var sessionsClient = (baseUrl) => {
5024
5024
  const matches = all.filter((s) => s.name === name);
5025
5025
  return matches.length === 0 ? null : matches[matches.length - 1] ?? null;
5026
5026
  },
5027
+ listEvents: (id) => request(u(`/sessions/${id}/events`), { method: "GET" }),
5027
5028
  sendMessage: (id, text, attachments, planMode) => request(u(`/sessions/${id}/messages`), {
5028
5029
  method: "POST",
5029
5030
  body: {
@@ -6462,6 +6463,7 @@ var findTranscriptPath = (agentSessionId) => {
6462
6463
  };
6463
6464
  var isRecord = (v) => typeof v === "object" && v !== null;
6464
6465
  var textOf = (rec) => {
6466
+ if (typeof rec.text === "string") return rec.text.trim();
6465
6467
  const message = rec.message;
6466
6468
  const content = isRecord(message) ? message.content : rec.content;
6467
6469
  if (typeof content === "string") return content.trim();
@@ -6492,6 +6494,38 @@ var parseFirstExchange = (content) => {
6492
6494
  }
6493
6495
  return userText || assistantText ? { userText, assistantText } : null;
6494
6496
  };
6497
+ var canonicalAgentMessage = (payload) => {
6498
+ if (!isRecord(payload) || !["item.started", "item.updated", "item.completed"].includes(String(payload.type)))
6499
+ return null;
6500
+ const item = payload.item;
6501
+ if (!isRecord(item) || item.type !== "agent_message" || typeof item.id !== "string") return null;
6502
+ const text = textOf(item);
6503
+ return text ? { id: item.id, text } : null;
6504
+ };
6505
+ var parseFirstExchangeFromEvents = (events) => {
6506
+ let userText = "";
6507
+ const itemOrder = [];
6508
+ const agentText = /* @__PURE__ */ new Map();
6509
+ const legacyText = [];
6510
+ for (const event of events) {
6511
+ if (!userText && event.type === "user_message" && isRecord(event.payload)) {
6512
+ if (typeof event.payload.text === "string") userText = event.payload.text.trim();
6513
+ continue;
6514
+ }
6515
+ if (!userText) continue;
6516
+ if (event.type === "turn_complete" || event.type === "turn_error") break;
6517
+ const agent = event.type === "agent_event" ? canonicalAgentMessage(event.payload) : null;
6518
+ if (agent) {
6519
+ if (!agentText.has(agent.id)) itemOrder.push(agent.id);
6520
+ agentText.set(agent.id, agent.text);
6521
+ } else if (event.type === "sdk_event" && isRecord(event.payload)) {
6522
+ const text = event.payload.type === "assistant" ? textOf(event.payload) : "";
6523
+ if (text) legacyText.push(text);
6524
+ }
6525
+ }
6526
+ const assistantText = itemOrder.map((id) => agentText.get(id) ?? "").filter(Boolean).join("\n") || legacyText.join("\n");
6527
+ return userText || assistantText ? { userText, assistantText } : null;
6528
+ };
6495
6529
  var readFirstExchange = (agentSessionId) => {
6496
6530
  const path = findTranscriptPath(agentSessionId);
6497
6531
  if (!path) return null;
@@ -7785,16 +7819,26 @@ var createLiveness = (deps) => {
7785
7819
  import { spawn } from "node:child_process";
7786
7820
  import { randomUUID } from "node:crypto";
7787
7821
  import { existsSync as existsSync5 } from "node:fs";
7788
- import { dirname as dirname2, join as join7 } from "node:path";
7822
+ import { dirname as dirname2, join as join8 } from "node:path";
7789
7823
  import { fileURLToPath } from "node:url";
7790
7824
  import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
7791
7825
 
7826
+ // src/session/runner/title-codex.ts
7827
+ import { mkdtemp, rm } from "node:fs/promises";
7828
+ import { tmpdir as tmpdir3 } from "node:os";
7829
+ import { join as join7 } from "node:path";
7830
+ import { Codex as Codex2 } from "@openai/codex-sdk";
7831
+
7792
7832
  // src/session/runner/title.ts
7793
7833
  var clip = (s, max) => s.length > max ? s.slice(0, max) : s;
7794
7834
  var isDeclined = (title) => title.replace(/[^a-z]/gi, "").toUpperCase() === "NONE";
7795
7835
  var sanitizeTitle = (raw) => raw.replace(/[\r\n]+/g, " ").replace(/[*_`~#>]/g, "").replace(/["']/g, "").replace(/^\s*(?:title|session|标题)\s*[::-]\s*/i, "").replace(/^\s*(?:[-*•]|\d+[.)、])\s+/, "").replace(/\s+/g, " ").trim().replace(/(?:[。.!?,、;;]|[.!?;](?=\s|$)).*$/s, "").trim().slice(0, 30).replace(/[\s。.,,、;;::!!??.]+$/, "").trim();
7836
+ var titleOutcome = (raw) => {
7837
+ const title = sanitizeTitle(raw);
7838
+ return !title || isDeclined(title) ? { kind: "declined" } : { kind: "titled", title };
7839
+ };
7796
7840
  var TITLE_TIMEOUT_MS = 9e4;
7797
- var buildPrompt2 = (userText, assistantText) => {
7841
+ var buildTitlePrompt = (userText, assistantText) => {
7798
7842
  const exchange = [
7799
7843
  `User: ${clip(userText, 800)}`,
7800
7844
  assistantText ? `Assistant: ${clip(assistantText, 800)}` : ""
@@ -7829,7 +7873,7 @@ var generateTitle = async (input) => {
7829
7873
  let failure = "";
7830
7874
  try {
7831
7875
  const messages = queryFn({
7832
- prompt: buildPrompt2(userText, assistantText),
7876
+ prompt: buildTitlePrompt(userText, assistantText),
7833
7877
  options: {
7834
7878
  cwd: worktreePath,
7835
7879
  // A one-shot summarizer, not an agent: replace the ~18k-token Claude
@@ -7869,8 +7913,41 @@ var generateTitle = async (input) => {
7869
7913
  const tail = stderrTail.length > 0 ? ` | stderr: ${stderrTail.join(" / ")}` : "";
7870
7914
  return { kind: "error", reason: `${failure}${tail}` };
7871
7915
  }
7872
- const title = sanitizeTitle(out);
7873
- return !title || isDeclined(title) ? { kind: "declined" } : { kind: "titled", title };
7916
+ return titleOutcome(out);
7917
+ };
7918
+
7919
+ // src/session/runner/title-codex.ts
7920
+ var generateTitleWithCodex = async (input) => {
7921
+ const { userText, assistantText } = input;
7922
+ if (`${userText}${assistantText}`.trim().length < 4) return { kind: "declined" };
7923
+ const abort = new AbortController();
7924
+ const timer = setTimeout(() => abort.abort(), TITLE_TIMEOUT_MS);
7925
+ let cwd = "";
7926
+ try {
7927
+ cwd = await mkdtemp(join7(tmpdir3(), "baton-title-"));
7928
+ const client = input.client ?? new Codex2();
7929
+ const thread = client.startThread({
7930
+ workingDirectory: cwd,
7931
+ skipGitRepoCheck: true,
7932
+ sandboxMode: "read-only",
7933
+ approvalPolicy: "never",
7934
+ networkAccessEnabled: false,
7935
+ webSearchMode: "disabled"
7936
+ });
7937
+ const result = await thread.run(buildTitlePrompt(userText, assistantText), {
7938
+ signal: abort.signal
7939
+ });
7940
+ return titleOutcome(result.finalResponse);
7941
+ } catch (error) {
7942
+ return {
7943
+ kind: "error",
7944
+ reason: abort.signal.aborted ? `timeout after ${TITLE_TIMEOUT_MS / 1e3}s` : String(error).slice(0, 300)
7945
+ };
7946
+ } finally {
7947
+ clearTimeout(timer);
7948
+ if (cwd) await rm(cwd, { recursive: true, force: true }).catch(() => {
7949
+ });
7950
+ }
7874
7951
  };
7875
7952
 
7876
7953
  // src/worker/proc.ts
@@ -7885,7 +7962,7 @@ var killProcessGroup = (proc) => {
7885
7962
  // src/worker/session-supervisor.ts
7886
7963
  var binPath = () => {
7887
7964
  const here = dirname2(fileURLToPath(import.meta.url));
7888
- const devShim = join7(here, "..", "..", "bin", "baton.mjs");
7965
+ const devShim = join8(here, "..", "..", "bin", "baton.mjs");
7889
7966
  return existsSync5(devShim) ? devShim : fileURLToPath(import.meta.url);
7890
7967
  };
7891
7968
  var createSessionSupervisor = (deps) => {
@@ -7939,7 +8016,7 @@ var createSessionSupervisor = (deps) => {
7939
8016
  if (!session2.agentSessionId || !worktreePath) {
7940
8017
  const sessionCode = randomUUID();
7941
8018
  const agentSessionId = session2.agentKind === "codex" ? `pending:${sessionCode}` : sessionCode;
7942
- worktreePath = join7(worktreeDir, slug(`${name}-${sessionCode.slice(0, 8)}`));
8019
+ worktreePath = join8(worktreeDir, slug(`${name}-${sessionCode.slice(0, 8)}`));
7943
8020
  const base = await syncedBase();
7944
8021
  if (wasCanceled()) return log(`session #${sessionId} start canceled during git sync`);
7945
8022
  createWorktree({
@@ -7957,7 +8034,7 @@ var createSessionSupervisor = (deps) => {
7957
8034
  log(`recreated session #${sessionId} worktree (was missing) \u2192 ${worktreePath}`);
7958
8035
  }
7959
8036
  ensureExcluded(repo, PROJECT_CONFIG_NAME);
7960
- saveProjectConfig(join7(worktreePath, PROJECT_CONFIG_NAME), worktreeConfig(cfg));
8037
+ saveProjectConfig(join8(worktreePath, PROJECT_CONFIG_NAME), worktreeConfig(cfg));
7961
8038
  if (hasTerminal(sessionId))
7962
8039
  return log(`session #${sessionId} terminal opened mid-start \u2014 skipping headless start`);
7963
8040
  spawnChild(sessionId, worktreePath);
@@ -8000,10 +8077,13 @@ var createSessionSupervisor = (deps) => {
8000
8077
  };
8001
8078
  const title = async (sessionId, agentSessionId, worktreePath) => {
8002
8079
  const session2 = await client.sessions.get(sessionId);
8003
- if (session2.agentKind !== "claude-code") return log(`title #${sessionId}: unsupported agent`);
8004
- const exchange = readFirstExchange(agentSessionId);
8080
+ const events = await client.sessions.listEvents(sessionId).catch(() => []);
8081
+ const exchange = parseFirstExchangeFromEvents(events) ?? (session2.agentKind === "claude-code" ? readFirstExchange(agentSessionId) : null);
8005
8082
  if (!exchange) return log(`title #${sessionId}: no transcript yet, skipping`);
8006
- const outcome = await generateTitle({
8083
+ const outcome = session2.agentKind === "codex" ? await generateTitleWithCodex({
8084
+ userText: exchange.userText,
8085
+ assistantText: exchange.assistantText
8086
+ }) : await generateTitle({
8007
8087
  worktreePath,
8008
8088
  userText: exchange.userText,
8009
8089
  assistantText: exchange.assistantText,
@@ -8058,14 +8138,14 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
8058
8138
  // src/worker/pty.ts
8059
8139
  import { existsSync as existsSync6, readdirSync as readdirSync2 } from "node:fs";
8060
8140
  import { homedir as homedir3 } from "node:os";
8061
- import { join as join8 } from "node:path";
8141
+ import { join as join9 } from "node:path";
8062
8142
  var MAX_TERMINALS = 10;
8063
8143
  var ptyArgs = (agentSessionId, hasJsonl) => hasJsonl ? ["--resume", agentSessionId] : ["--session-id", agentSessionId];
8064
8144
  var hasSessionJsonl = (agentSessionId) => {
8065
- const root = join8(homedir3(), ".claude", "projects");
8145
+ const root = join9(homedir3(), ".claude", "projects");
8066
8146
  if (!existsSync6(root)) return false;
8067
8147
  for (const dir of readdirSync2(root))
8068
- if (existsSync6(join8(root, dir, `${agentSessionId}.jsonl`))) return true;
8148
+ if (existsSync6(join9(root, dir, `${agentSessionId}.jsonl`))) return true;
8069
8149
  return false;
8070
8150
  };
8071
8151
  var serverTerminalWsUrl = (server, sessionId) => `${server.replace(/^http/, "ws")}/workers/me/terminal/ws?sessionId=${sessionId}`;
@@ -8233,8 +8313,8 @@ var runWorkerDaemon = async (cfg, client, signal) => {
8233
8313
  import { randomUUID as randomUUID2 } from "node:crypto";
8234
8314
  import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
8235
8315
  import { homedir as homedir4 } from "node:os";
8236
- import { dirname as dirname3, join as join9 } from "node:path";
8237
- var machineIdPath = (env = process.env) => join9(env.XDG_DATA_HOME ?? join9(env.HOME ?? homedir4(), ".local/share"), "baton", "machine-id");
8316
+ import { dirname as dirname3, join as join10 } from "node:path";
8317
+ var machineIdPath = (env = process.env) => join10(env.XDG_DATA_HOME ?? join10(env.HOME ?? homedir4(), ".local/share"), "baton", "machine-id");
8238
8318
  var readOrCreateMachineId = (path = machineIdPath()) => {
8239
8319
  if (existsSync8(path)) {
8240
8320
  const s = readFileSync6(path, "utf8").trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lesscap/baton-cli",
3
- "version": "0.7.3",
3
+ "version": "0.7.4",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"