@lesscap/baton-cli 0.7.3 → 0.7.5

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 +99 -16
  2. package/package.json +3 -3
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: {
@@ -5375,6 +5376,17 @@ var labelAttachments = (attachments) => {
5375
5376
  var isMessageFor = (msg, name) => !msg.to || msg.to.length === 0 || msg.to.includes(name);
5376
5377
 
5377
5378
  // ../shared/src/domain/session-event.ts
5379
+ var agentMessageText = (payload) => {
5380
+ if (typeof payload !== "object" || payload === null) return null;
5381
+ const event = payload;
5382
+ if (!["item.started", "item.updated", "item.completed"].includes(String(event.type))) return null;
5383
+ const item = event.item;
5384
+ if (typeof item !== "object" || item === null) return null;
5385
+ const { type, id, text } = item;
5386
+ if (type !== "agent_message" || typeof id !== "string" || typeof text !== "string") return null;
5387
+ const trimmed = text.trim();
5388
+ return trimmed ? { id, text: trimmed } : null;
5389
+ };
5378
5390
  var startedMessageIds = (events) => {
5379
5391
  const ids = /* @__PURE__ */ new Set();
5380
5392
  for (const e of events) {
@@ -6462,6 +6474,7 @@ var findTranscriptPath = (agentSessionId) => {
6462
6474
  };
6463
6475
  var isRecord = (v) => typeof v === "object" && v !== null;
6464
6476
  var textOf = (rec) => {
6477
+ if (typeof rec.text === "string") return rec.text.trim();
6465
6478
  const message = rec.message;
6466
6479
  const content = isRecord(message) ? message.content : rec.content;
6467
6480
  if (typeof content === "string") return content.trim();
@@ -6492,6 +6505,30 @@ var parseFirstExchange = (content) => {
6492
6505
  }
6493
6506
  return userText || assistantText ? { userText, assistantText } : null;
6494
6507
  };
6508
+ var parseFirstExchangeFromEvents = (events) => {
6509
+ let userText = "";
6510
+ const itemOrder = [];
6511
+ const agentText = /* @__PURE__ */ new Map();
6512
+ const legacyText = [];
6513
+ for (const event of events) {
6514
+ if (!userText && event.type === "user_message" && isRecord(event.payload)) {
6515
+ if (typeof event.payload.text === "string") userText = event.payload.text.trim();
6516
+ continue;
6517
+ }
6518
+ if (!userText) continue;
6519
+ if (event.type === "turn_complete" || event.type === "turn_error") break;
6520
+ const agent = event.type === "agent_event" ? agentMessageText(event.payload) : null;
6521
+ if (agent) {
6522
+ if (!agentText.has(agent.id)) itemOrder.push(agent.id);
6523
+ agentText.set(agent.id, agent.text);
6524
+ } else if (event.type === "sdk_event" && isRecord(event.payload)) {
6525
+ const text = event.payload.type === "assistant" ? textOf(event.payload) : "";
6526
+ if (text) legacyText.push(text);
6527
+ }
6528
+ }
6529
+ const assistantText = itemOrder.map((id) => agentText.get(id) ?? "").filter(Boolean).join("\n") || legacyText.join("\n");
6530
+ return userText || assistantText ? { userText, assistantText } : null;
6531
+ };
6495
6532
  var readFirstExchange = (agentSessionId) => {
6496
6533
  const path = findTranscriptPath(agentSessionId);
6497
6534
  if (!path) return null;
@@ -7785,16 +7822,26 @@ var createLiveness = (deps) => {
7785
7822
  import { spawn } from "node:child_process";
7786
7823
  import { randomUUID } from "node:crypto";
7787
7824
  import { existsSync as existsSync5 } from "node:fs";
7788
- import { dirname as dirname2, join as join7 } from "node:path";
7825
+ import { dirname as dirname2, join as join8 } from "node:path";
7789
7826
  import { fileURLToPath } from "node:url";
7790
7827
  import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
7791
7828
 
7829
+ // src/session/runner/title-codex.ts
7830
+ import { mkdtemp, rm } from "node:fs/promises";
7831
+ import { tmpdir as tmpdir3 } from "node:os";
7832
+ import { join as join7 } from "node:path";
7833
+ import { Codex as Codex2 } from "@openai/codex-sdk";
7834
+
7792
7835
  // src/session/runner/title.ts
7793
7836
  var clip = (s, max) => s.length > max ? s.slice(0, max) : s;
7794
7837
  var isDeclined = (title) => title.replace(/[^a-z]/gi, "").toUpperCase() === "NONE";
7795
7838
  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();
7839
+ var titleOutcome = (raw) => {
7840
+ const title = sanitizeTitle(raw);
7841
+ return !title || isDeclined(title) ? { kind: "declined" } : { kind: "titled", title };
7842
+ };
7796
7843
  var TITLE_TIMEOUT_MS = 9e4;
7797
- var buildPrompt2 = (userText, assistantText) => {
7844
+ var buildTitlePrompt = (userText, assistantText) => {
7798
7845
  const exchange = [
7799
7846
  `User: ${clip(userText, 800)}`,
7800
7847
  assistantText ? `Assistant: ${clip(assistantText, 800)}` : ""
@@ -7829,7 +7876,7 @@ var generateTitle = async (input) => {
7829
7876
  let failure = "";
7830
7877
  try {
7831
7878
  const messages = queryFn({
7832
- prompt: buildPrompt2(userText, assistantText),
7879
+ prompt: buildTitlePrompt(userText, assistantText),
7833
7880
  options: {
7834
7881
  cwd: worktreePath,
7835
7882
  // A one-shot summarizer, not an agent: replace the ~18k-token Claude
@@ -7869,8 +7916,41 @@ var generateTitle = async (input) => {
7869
7916
  const tail = stderrTail.length > 0 ? ` | stderr: ${stderrTail.join(" / ")}` : "";
7870
7917
  return { kind: "error", reason: `${failure}${tail}` };
7871
7918
  }
7872
- const title = sanitizeTitle(out);
7873
- return !title || isDeclined(title) ? { kind: "declined" } : { kind: "titled", title };
7919
+ return titleOutcome(out);
7920
+ };
7921
+
7922
+ // src/session/runner/title-codex.ts
7923
+ var generateTitleWithCodex = async (input) => {
7924
+ const { userText, assistantText } = input;
7925
+ if (`${userText}${assistantText}`.trim().length < 4) return { kind: "declined" };
7926
+ const abort = new AbortController();
7927
+ const timer = setTimeout(() => abort.abort(), TITLE_TIMEOUT_MS);
7928
+ let cwd = "";
7929
+ try {
7930
+ cwd = await mkdtemp(join7(tmpdir3(), "baton-title-"));
7931
+ const client = input.client ?? new Codex2();
7932
+ const thread = client.startThread({
7933
+ workingDirectory: cwd,
7934
+ skipGitRepoCheck: true,
7935
+ sandboxMode: "read-only",
7936
+ approvalPolicy: "never",
7937
+ networkAccessEnabled: false,
7938
+ webSearchMode: "disabled"
7939
+ });
7940
+ const result = await thread.run(buildTitlePrompt(userText, assistantText), {
7941
+ signal: abort.signal
7942
+ });
7943
+ return titleOutcome(result.finalResponse);
7944
+ } catch (error) {
7945
+ return {
7946
+ kind: "error",
7947
+ reason: abort.signal.aborted ? `timeout after ${TITLE_TIMEOUT_MS / 1e3}s` : String(error).slice(0, 300)
7948
+ };
7949
+ } finally {
7950
+ clearTimeout(timer);
7951
+ if (cwd) await rm(cwd, { recursive: true, force: true }).catch(() => {
7952
+ });
7953
+ }
7874
7954
  };
7875
7955
 
7876
7956
  // src/worker/proc.ts
@@ -7885,7 +7965,7 @@ var killProcessGroup = (proc) => {
7885
7965
  // src/worker/session-supervisor.ts
7886
7966
  var binPath = () => {
7887
7967
  const here = dirname2(fileURLToPath(import.meta.url));
7888
- const devShim = join7(here, "..", "..", "bin", "baton.mjs");
7968
+ const devShim = join8(here, "..", "..", "bin", "baton.mjs");
7889
7969
  return existsSync5(devShim) ? devShim : fileURLToPath(import.meta.url);
7890
7970
  };
7891
7971
  var createSessionSupervisor = (deps) => {
@@ -7939,7 +8019,7 @@ var createSessionSupervisor = (deps) => {
7939
8019
  if (!session2.agentSessionId || !worktreePath) {
7940
8020
  const sessionCode = randomUUID();
7941
8021
  const agentSessionId = session2.agentKind === "codex" ? `pending:${sessionCode}` : sessionCode;
7942
- worktreePath = join7(worktreeDir, slug(`${name}-${sessionCode.slice(0, 8)}`));
8022
+ worktreePath = join8(worktreeDir, slug(`${name}-${sessionCode.slice(0, 8)}`));
7943
8023
  const base = await syncedBase();
7944
8024
  if (wasCanceled()) return log(`session #${sessionId} start canceled during git sync`);
7945
8025
  createWorktree({
@@ -7957,7 +8037,7 @@ var createSessionSupervisor = (deps) => {
7957
8037
  log(`recreated session #${sessionId} worktree (was missing) \u2192 ${worktreePath}`);
7958
8038
  }
7959
8039
  ensureExcluded(repo, PROJECT_CONFIG_NAME);
7960
- saveProjectConfig(join7(worktreePath, PROJECT_CONFIG_NAME), worktreeConfig(cfg));
8040
+ saveProjectConfig(join8(worktreePath, PROJECT_CONFIG_NAME), worktreeConfig(cfg));
7961
8041
  if (hasTerminal(sessionId))
7962
8042
  return log(`session #${sessionId} terminal opened mid-start \u2014 skipping headless start`);
7963
8043
  spawnChild(sessionId, worktreePath);
@@ -8000,10 +8080,13 @@ var createSessionSupervisor = (deps) => {
8000
8080
  };
8001
8081
  const title = async (sessionId, agentSessionId, worktreePath) => {
8002
8082
  const session2 = await client.sessions.get(sessionId);
8003
- if (session2.agentKind !== "claude-code") return log(`title #${sessionId}: unsupported agent`);
8004
- const exchange = readFirstExchange(agentSessionId);
8083
+ const events = await client.sessions.listEvents(sessionId).catch(() => []);
8084
+ const exchange = parseFirstExchangeFromEvents(events) ?? (session2.agentKind === "claude-code" ? readFirstExchange(agentSessionId) : null);
8005
8085
  if (!exchange) return log(`title #${sessionId}: no transcript yet, skipping`);
8006
- const outcome = await generateTitle({
8086
+ const outcome = session2.agentKind === "codex" ? await generateTitleWithCodex({
8087
+ userText: exchange.userText,
8088
+ assistantText: exchange.assistantText
8089
+ }) : await generateTitle({
8007
8090
  worktreePath,
8008
8091
  userText: exchange.userText,
8009
8092
  assistantText: exchange.assistantText,
@@ -8058,14 +8141,14 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
8058
8141
  // src/worker/pty.ts
8059
8142
  import { existsSync as existsSync6, readdirSync as readdirSync2 } from "node:fs";
8060
8143
  import { homedir as homedir3 } from "node:os";
8061
- import { join as join8 } from "node:path";
8144
+ import { join as join9 } from "node:path";
8062
8145
  var MAX_TERMINALS = 10;
8063
8146
  var ptyArgs = (agentSessionId, hasJsonl) => hasJsonl ? ["--resume", agentSessionId] : ["--session-id", agentSessionId];
8064
8147
  var hasSessionJsonl = (agentSessionId) => {
8065
- const root = join8(homedir3(), ".claude", "projects");
8148
+ const root = join9(homedir3(), ".claude", "projects");
8066
8149
  if (!existsSync6(root)) return false;
8067
8150
  for (const dir of readdirSync2(root))
8068
- if (existsSync6(join8(root, dir, `${agentSessionId}.jsonl`))) return true;
8151
+ if (existsSync6(join9(root, dir, `${agentSessionId}.jsonl`))) return true;
8069
8152
  return false;
8070
8153
  };
8071
8154
  var serverTerminalWsUrl = (server, sessionId) => `${server.replace(/^http/, "ws")}/workers/me/terminal/ws?sessionId=${sessionId}`;
@@ -8233,8 +8316,8 @@ var runWorkerDaemon = async (cfg, client, signal) => {
8233
8316
  import { randomUUID as randomUUID2 } from "node:crypto";
8234
8317
  import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
8235
8318
  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");
8319
+ import { dirname as dirname3, join as join10 } from "node:path";
8320
+ var machineIdPath = (env = process.env) => join10(env.XDG_DATA_HOME ?? join10(env.HOME ?? homedir4(), ".local/share"), "baton", "machine-id");
8238
8321
  var readOrCreateMachineId = (path = machineIdPath()) => {
8239
8322
  if (existsSync8(path)) {
8240
8323
  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.5",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -25,8 +25,8 @@
25
25
  "@baton/shared": "0.0.0"
26
26
  },
27
27
  "dependencies": {
28
- "@anthropic-ai/claude-agent-sdk": "^0.3.209",
29
- "@openai/codex-sdk": "^0.144.4",
28
+ "@anthropic-ai/claude-agent-sdk": "^0.3.220",
29
+ "@openai/codex-sdk": "^0.146.0",
30
30
  "node-pty": "^1.1.0",
31
31
  "ws": "^8.21.0"
32
32
  },