@lesscap/baton-cli 0.2.5 → 0.2.6
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/baton.mjs +40 -15
- package/package.json +2 -1
package/dist/baton.mjs
CHANGED
|
@@ -2514,26 +2514,36 @@ import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
|
2514
2514
|
var clip = (s, max) => s.length > max ? s.slice(0, max) : s;
|
|
2515
2515
|
var isDeclined = (title) => title.replace(/[^a-z]/gi, "").toUpperCase() === "NONE";
|
|
2516
2516
|
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().slice(0, 30).replace(/[\s。.,,、;;::!!??.]+$/, "").trim();
|
|
2517
|
-
var TITLE_TIMEOUT_MS =
|
|
2518
|
-
var
|
|
2519
|
-
const { worktreePath, userText, assistantText, queryFn } = input;
|
|
2520
|
-
if (`${userText}${assistantText}`.trim().length < 4) return null;
|
|
2517
|
+
var TITLE_TIMEOUT_MS = 9e4;
|
|
2518
|
+
var buildPrompt2 = (userText, assistantText) => {
|
|
2521
2519
|
const exchange = [
|
|
2522
2520
|
`User: ${clip(userText, 800)}`,
|
|
2523
2521
|
assistantText ? `Assistant: ${clip(assistantText, 800)}` : ""
|
|
2524
2522
|
].filter(Boolean).join("\n");
|
|
2525
|
-
|
|
2523
|
+
return `Reply with ONLY a short title for this session: a noun phrase like a filename \u2014 at most 6 words or ~12 Chinese characters. It must NOT be a sentence, no punctuation, no markdown, no quotes, and never an opener like "\u597D\u7684"/"\u6536\u5230"/"I'll". Only if the exchange below has no real content at all (e.g. just a greeting or a test ping), reply with exactly NONE.
|
|
2526
2524
|
|
|
2527
2525
|
${exchange}`;
|
|
2526
|
+
};
|
|
2527
|
+
var generateTitle = async (input) => {
|
|
2528
|
+
const { worktreePath, userText, assistantText, queryFn } = input;
|
|
2529
|
+
if (`${userText}${assistantText}`.trim().length < 4) return { kind: "declined" };
|
|
2528
2530
|
const exe = claudeExecutable();
|
|
2529
2531
|
const abort = new AbortController();
|
|
2530
2532
|
const timer = setTimeout(() => abort.abort(), TITLE_TIMEOUT_MS);
|
|
2533
|
+
const stderrTail = [];
|
|
2531
2534
|
let out = "";
|
|
2535
|
+
let resultSeen = false;
|
|
2536
|
+
let failure = "";
|
|
2532
2537
|
try {
|
|
2533
2538
|
const messages = queryFn({
|
|
2534
|
-
prompt,
|
|
2539
|
+
prompt: buildPrompt2(userText, assistantText),
|
|
2535
2540
|
options: {
|
|
2536
2541
|
cwd: worktreePath,
|
|
2542
|
+
// A one-shot summarizer, not an agent: replace the ~18k-token Claude
|
|
2543
|
+
// Code preset system prompt. Inside the agent harness the model
|
|
2544
|
+
// routinely answers NONE to this ask (verified against reclaude);
|
|
2545
|
+
// a plain summarizer persona titles reliably — and ~100× cheaper.
|
|
2546
|
+
systemPrompt: "You title chat sessions.",
|
|
2537
2547
|
// Don't load the worktree's CLAUDE.md / project settings — they steer the
|
|
2538
2548
|
// model into verbose summaries instead of a terse title.
|
|
2539
2549
|
settingSources: [],
|
|
@@ -2541,20 +2551,33 @@ ${exchange}`;
|
|
|
2541
2551
|
allowedTools: [],
|
|
2542
2552
|
includePartialMessages: false,
|
|
2543
2553
|
abortController: abort,
|
|
2554
|
+
stderr: (line) => {
|
|
2555
|
+
if (line.trim()) stderrTail.push(line.trim().slice(0, 200));
|
|
2556
|
+
if (stderrTail.length > 3) stderrTail.shift();
|
|
2557
|
+
},
|
|
2544
2558
|
...exe ? { pathToClaudeCodeExecutable: exe } : {}
|
|
2545
2559
|
}
|
|
2546
2560
|
});
|
|
2547
2561
|
for await (const m of messages) {
|
|
2548
|
-
if (m.type
|
|
2549
|
-
|
|
2562
|
+
if (m.type !== "result") continue;
|
|
2563
|
+
resultSeen = true;
|
|
2564
|
+
const r = m;
|
|
2565
|
+
if (r.subtype === "success" && typeof r.result === "string") out = r.result;
|
|
2566
|
+
else
|
|
2567
|
+
failure = `result ${r.subtype ?? "unknown"}${typeof r.result === "string" ? `: ${r.result.slice(0, 200)}` : ""}`;
|
|
2550
2568
|
}
|
|
2551
|
-
} catch {
|
|
2552
|
-
|
|
2569
|
+
} catch (e) {
|
|
2570
|
+
failure = abort.signal.aborted ? `timeout after ${TITLE_TIMEOUT_MS / 1e3}s` : String(e).slice(0, 300);
|
|
2553
2571
|
} finally {
|
|
2554
2572
|
clearTimeout(timer);
|
|
2555
2573
|
}
|
|
2574
|
+
if (!failure && !resultSeen) failure = "stream ended without a result message";
|
|
2575
|
+
if (failure) {
|
|
2576
|
+
const tail = stderrTail.length > 0 ? ` | stderr: ${stderrTail.join(" / ")}` : "";
|
|
2577
|
+
return { kind: "error", reason: `${failure}${tail}` };
|
|
2578
|
+
}
|
|
2556
2579
|
const title = sanitizeTitle(out);
|
|
2557
|
-
return !title || isDeclined(title) ?
|
|
2580
|
+
return !title || isDeclined(title) ? { kind: "declined" } : { kind: "titled", title };
|
|
2558
2581
|
};
|
|
2559
2582
|
|
|
2560
2583
|
// src/session/worktree.ts
|
|
@@ -2694,15 +2717,17 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2694
2717
|
const onTitle = async (sessionId, agentSessionId, worktreePath) => {
|
|
2695
2718
|
const exchange = readFirstExchange(agentSessionId);
|
|
2696
2719
|
if (!exchange) return log(`title #${sessionId}: no transcript yet, skipping`);
|
|
2697
|
-
const
|
|
2720
|
+
const outcome = await generateTitle({
|
|
2698
2721
|
worktreePath,
|
|
2699
2722
|
userText: exchange.userText,
|
|
2700
2723
|
assistantText: exchange.assistantText,
|
|
2701
2724
|
queryFn: query2
|
|
2702
2725
|
});
|
|
2703
|
-
if (
|
|
2704
|
-
|
|
2705
|
-
|
|
2726
|
+
if (outcome.kind === "error") return log(`title #${sessionId} failed: ${outcome.reason}`);
|
|
2727
|
+
if (outcome.kind === "declined")
|
|
2728
|
+
return log(`title #${sessionId}: not enough to title yet, skipping`);
|
|
2729
|
+
await client.sessions.setName(sessionId, outcome.title, cfg.apiToken);
|
|
2730
|
+
log(`\u270E titled session #${sessionId} \u2192 ${outcome.title}`);
|
|
2706
2731
|
};
|
|
2707
2732
|
const reconcile = async () => {
|
|
2708
2733
|
const owned = (await client.sessions.listByProject(cfg.projectId)).filter(
|