@lesscap/baton-cli 0.2.9 → 0.2.11
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 +52 -18
- package/package.json +11 -10
- package/LICENSE +0 -21
package/dist/baton.mjs
CHANGED
|
@@ -2446,16 +2446,30 @@ import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
|
2446
2446
|
// src/session/runner/title.ts
|
|
2447
2447
|
var clip = (s, max) => s.length > max ? s.slice(0, max) : s;
|
|
2448
2448
|
var isDeclined = (title) => title.replace(/[^a-z]/gi, "").toUpperCase() === "NONE";
|
|
2449
|
-
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();
|
|
2449
|
+
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();
|
|
2450
2450
|
var TITLE_TIMEOUT_MS = 9e4;
|
|
2451
2451
|
var buildPrompt2 = (userText, assistantText) => {
|
|
2452
2452
|
const exchange = [
|
|
2453
2453
|
`User: ${clip(userText, 800)}`,
|
|
2454
2454
|
assistantText ? `Assistant: ${clip(assistantText, 800)}` : ""
|
|
2455
2455
|
].filter(Boolean).join("\n");
|
|
2456
|
-
return
|
|
2457
|
-
|
|
2458
|
-
|
|
2456
|
+
return [
|
|
2457
|
+
"Name the TOPIC of this chat session \u2014 what it is ABOUT \u2014 as a short title:",
|
|
2458
|
+
"a noun phrase like a filename, at most 6 words or ~12 Chinese characters.",
|
|
2459
|
+
"Name the subject, do NOT restate the conclusion or the answer.",
|
|
2460
|
+
"No sentence, no punctuation at all (including Chinese \uFF0C\u3002\u3001\uFF1B\uFF01\uFF1F), no markdown,",
|
|
2461
|
+
`no quotes, and never an opener like "\u597D\u7684"/"\u6536\u5230"/"I'll".`,
|
|
2462
|
+
"",
|
|
2463
|
+
"Examples (exchange \u2192 title):",
|
|
2464
|
+
"User asks whether the material-ui repo needs updating; assistant says it is",
|
|
2465
|
+
"already up to date \u2192 material-ui \u4ED3\u5E93\u66F4\u65B0\u68C0\u67E5",
|
|
2466
|
+
"User: curl the health endpoint and report status \u2192 \u5065\u5EB7\u68C0\u67E5\u63A5\u53E3\u6392\u67E5",
|
|
2467
|
+
"",
|
|
2468
|
+
"Only if the exchange below has no real content at all (just a greeting or a",
|
|
2469
|
+
"test ping) reply with exactly NONE. Otherwise reply with ONLY the title.",
|
|
2470
|
+
"",
|
|
2471
|
+
exchange
|
|
2472
|
+
].join("\n");
|
|
2459
2473
|
};
|
|
2460
2474
|
var generateTitle = async (input) => {
|
|
2461
2475
|
const { worktreePath, userText, assistantText, queryFn } = input;
|
|
@@ -2575,6 +2589,10 @@ var removeWorktree = (repo, worktreePath) => {
|
|
|
2575
2589
|
}
|
|
2576
2590
|
};
|
|
2577
2591
|
|
|
2592
|
+
// src/worker/watchdog.ts
|
|
2593
|
+
var stale = (okAt, now, thresholdMs) => now - okAt > thresholdMs;
|
|
2594
|
+
var streamWedged = (isOpen, okAt, now, thresholdMs) => !isOpen && stale(okAt, now, thresholdMs);
|
|
2595
|
+
|
|
2578
2596
|
// src/worker/daemon.ts
|
|
2579
2597
|
var binPath = () => {
|
|
2580
2598
|
const here = dirname2(fileURLToPath(import.meta.url));
|
|
@@ -2588,11 +2606,20 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2588
2606
|
const log = (m) => console.log(`[worker #${cfg.workerId} ${cfg.name}] ${m}`);
|
|
2589
2607
|
const HEARTBEAT_MS = 3e4;
|
|
2590
2608
|
const HEARTBEAT_TIMEOUT_MS = 15e3;
|
|
2591
|
-
const
|
|
2592
|
-
|
|
2609
|
+
const HEARTBEAT_DEAD_MS = 15e4;
|
|
2610
|
+
const STREAM_STALE_MS = 9e4;
|
|
2611
|
+
const ES_OPEN = 1;
|
|
2612
|
+
let heartbeatOkAt = Date.now();
|
|
2613
|
+
let streamOkAt = Date.now();
|
|
2593
2614
|
let fatal = false;
|
|
2594
2615
|
let stop = () => {
|
|
2595
2616
|
};
|
|
2617
|
+
const trip = (reason) => {
|
|
2618
|
+
if (fatal) return;
|
|
2619
|
+
log(`${reason} \u2014 exiting so the supervisor restarts a clean worker`);
|
|
2620
|
+
fatal = true;
|
|
2621
|
+
stop();
|
|
2622
|
+
};
|
|
2596
2623
|
const ping = async () => {
|
|
2597
2624
|
try {
|
|
2598
2625
|
await Promise.race([
|
|
@@ -2601,20 +2628,11 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2601
2628
|
(_, reject) => setTimeout(() => reject(new Error("heartbeat timeout")), HEARTBEAT_TIMEOUT_MS)
|
|
2602
2629
|
)
|
|
2603
2630
|
]);
|
|
2604
|
-
|
|
2631
|
+
heartbeatOkAt = Date.now();
|
|
2605
2632
|
} catch (e) {
|
|
2606
|
-
|
|
2607
|
-
log(`heartbeat failed (${heartbeatFailures}/${MAX_HEARTBEAT_FAILURES}): ${String(e)}`);
|
|
2608
|
-
if (heartbeatFailures >= MAX_HEARTBEAT_FAILURES) {
|
|
2609
|
-
const downSec = MAX_HEARTBEAT_FAILURES * HEARTBEAT_MS / 1e3;
|
|
2610
|
-
log(`heartbeat down ~${downSec}s \u2014 exiting so the supervisor restarts a clean worker`);
|
|
2611
|
-
fatal = true;
|
|
2612
|
-
stop();
|
|
2613
|
-
}
|
|
2633
|
+
log(`heartbeat failed: ${String(e)}`);
|
|
2614
2634
|
}
|
|
2615
2635
|
};
|
|
2616
|
-
void ping();
|
|
2617
|
-
const hb = setInterval(() => void ping(), HEARTBEAT_MS);
|
|
2618
2636
|
const killChild = (child) => {
|
|
2619
2637
|
if (child.pid === void 0) return;
|
|
2620
2638
|
try {
|
|
@@ -2716,8 +2734,12 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2716
2734
|
}
|
|
2717
2735
|
})
|
|
2718
2736
|
});
|
|
2719
|
-
es.onopen = () =>
|
|
2737
|
+
es.onopen = () => {
|
|
2738
|
+
streamOkAt = Date.now();
|
|
2739
|
+
void reconcile().catch((err) => log(`reconcile failed: ${String(err)}`));
|
|
2740
|
+
};
|
|
2720
2741
|
es.onmessage = (e) => {
|
|
2742
|
+
streamOkAt = Date.now();
|
|
2721
2743
|
try {
|
|
2722
2744
|
const cmd = JSON.parse(e.data);
|
|
2723
2745
|
if (cmd.cmd === "session.start")
|
|
@@ -2733,6 +2755,18 @@ var runWorkerDaemon = async (cfg, client, signal) => {
|
|
|
2733
2755
|
};
|
|
2734
2756
|
es.onerror = () => log("command stream error (eventsource will retry)");
|
|
2735
2757
|
log(`listening for commands (server ${cfg.server}, repo ${repo})`);
|
|
2758
|
+
const watchdog = () => {
|
|
2759
|
+
const now = Date.now();
|
|
2760
|
+
if (stale(heartbeatOkAt, now, HEARTBEAT_DEAD_MS))
|
|
2761
|
+
trip(`heartbeat down ~${HEARTBEAT_DEAD_MS / 1e3}s`);
|
|
2762
|
+
else if (streamWedged(es.readyState === ES_OPEN, streamOkAt, now, STREAM_STALE_MS))
|
|
2763
|
+
trip(`command stream wedged ~${STREAM_STALE_MS / 1e3}s`);
|
|
2764
|
+
};
|
|
2765
|
+
void ping();
|
|
2766
|
+
const hb = setInterval(() => {
|
|
2767
|
+
void ping();
|
|
2768
|
+
watchdog();
|
|
2769
|
+
}, HEARTBEAT_MS);
|
|
2736
2770
|
await new Promise((resolve2) => {
|
|
2737
2771
|
stop = resolve2;
|
|
2738
2772
|
if (signal.aborted) return resolve2();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lesscap/baton-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "baton CLI — register/run a worker, manage projects/requirements/tasks/sessions",
|
|
@@ -10,22 +10,23 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist"
|
|
12
12
|
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"baton": "tsx src/index.ts",
|
|
15
|
+
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --packages=bundle --external:@anthropic-ai/claude-agent-sdk --banner:js=\"#!/usr/bin/env node\" --outfile=dist/baton.mjs && chmod +x dist/baton.mjs",
|
|
16
|
+
"prepublishOnly": "pnpm build",
|
|
17
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
18
|
+
"test": "tsx --test 'src/**/*.test.ts'"
|
|
19
|
+
},
|
|
13
20
|
"devDependencies": {
|
|
21
|
+
"@baton/shared": "workspace:*",
|
|
14
22
|
"@types/node": "^25.9.1",
|
|
15
23
|
"citty": "^0.2.2",
|
|
16
24
|
"esbuild": "^0.28.0",
|
|
17
25
|
"eventsource": "^4.1.0",
|
|
18
26
|
"tsx": "^4.22.4",
|
|
19
|
-
"typescript": "^6.0.3"
|
|
20
|
-
"@baton/shared": "0.0.0"
|
|
27
|
+
"typescript": "^6.0.3"
|
|
21
28
|
},
|
|
22
29
|
"dependencies": {
|
|
23
30
|
"@anthropic-ai/claude-agent-sdk": "^0.3.161"
|
|
24
|
-
},
|
|
25
|
-
"scripts": {
|
|
26
|
-
"baton": "tsx src/index.ts",
|
|
27
|
-
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --packages=bundle --external:@anthropic-ai/claude-agent-sdk --banner:js=\"#!/usr/bin/env node\" --outfile=dist/baton.mjs && chmod +x dist/baton.mjs",
|
|
28
|
-
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
29
|
-
"test": "tsx --test 'src/**/*.test.ts'"
|
|
30
31
|
}
|
|
31
|
-
}
|
|
32
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 bencode
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|