@lesscap/baton-cli 0.2.9 → 0.2.10

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 (3) hide show
  1. package/dist/baton.mjs +34 -14
  2. package/package.json +11 -10
  3. package/LICENSE +0 -21
package/dist/baton.mjs CHANGED
@@ -2575,6 +2575,10 @@ var removeWorktree = (repo, worktreePath) => {
2575
2575
  }
2576
2576
  };
2577
2577
 
2578
+ // src/worker/watchdog.ts
2579
+ var stale = (okAt, now, thresholdMs) => now - okAt > thresholdMs;
2580
+ var streamWedged = (isOpen, okAt, now, thresholdMs) => !isOpen && stale(okAt, now, thresholdMs);
2581
+
2578
2582
  // src/worker/daemon.ts
2579
2583
  var binPath = () => {
2580
2584
  const here = dirname2(fileURLToPath(import.meta.url));
@@ -2588,11 +2592,20 @@ var runWorkerDaemon = async (cfg, client, signal) => {
2588
2592
  const log = (m) => console.log(`[worker #${cfg.workerId} ${cfg.name}] ${m}`);
2589
2593
  const HEARTBEAT_MS = 3e4;
2590
2594
  const HEARTBEAT_TIMEOUT_MS = 15e3;
2591
- const MAX_HEARTBEAT_FAILURES = 5;
2592
- let heartbeatFailures = 0;
2595
+ const HEARTBEAT_DEAD_MS = 15e4;
2596
+ const STREAM_STALE_MS = 9e4;
2597
+ const ES_OPEN = 1;
2598
+ let heartbeatOkAt = Date.now();
2599
+ let streamOkAt = Date.now();
2593
2600
  let fatal = false;
2594
2601
  let stop = () => {
2595
2602
  };
2603
+ const trip = (reason) => {
2604
+ if (fatal) return;
2605
+ log(`${reason} \u2014 exiting so the supervisor restarts a clean worker`);
2606
+ fatal = true;
2607
+ stop();
2608
+ };
2596
2609
  const ping = async () => {
2597
2610
  try {
2598
2611
  await Promise.race([
@@ -2601,20 +2614,11 @@ var runWorkerDaemon = async (cfg, client, signal) => {
2601
2614
  (_, reject) => setTimeout(() => reject(new Error("heartbeat timeout")), HEARTBEAT_TIMEOUT_MS)
2602
2615
  )
2603
2616
  ]);
2604
- heartbeatFailures = 0;
2617
+ heartbeatOkAt = Date.now();
2605
2618
  } catch (e) {
2606
- heartbeatFailures += 1;
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
- }
2619
+ log(`heartbeat failed: ${String(e)}`);
2614
2620
  }
2615
2621
  };
2616
- void ping();
2617
- const hb = setInterval(() => void ping(), HEARTBEAT_MS);
2618
2622
  const killChild = (child) => {
2619
2623
  if (child.pid === void 0) return;
2620
2624
  try {
@@ -2716,8 +2720,12 @@ var runWorkerDaemon = async (cfg, client, signal) => {
2716
2720
  }
2717
2721
  })
2718
2722
  });
2719
- es.onopen = () => void reconcile().catch((err) => log(`reconcile failed: ${String(err)}`));
2723
+ es.onopen = () => {
2724
+ streamOkAt = Date.now();
2725
+ void reconcile().catch((err) => log(`reconcile failed: ${String(err)}`));
2726
+ };
2720
2727
  es.onmessage = (e) => {
2728
+ streamOkAt = Date.now();
2721
2729
  try {
2722
2730
  const cmd = JSON.parse(e.data);
2723
2731
  if (cmd.cmd === "session.start")
@@ -2733,6 +2741,18 @@ var runWorkerDaemon = async (cfg, client, signal) => {
2733
2741
  };
2734
2742
  es.onerror = () => log("command stream error (eventsource will retry)");
2735
2743
  log(`listening for commands (server ${cfg.server}, repo ${repo})`);
2744
+ const watchdog = () => {
2745
+ const now = Date.now();
2746
+ if (stale(heartbeatOkAt, now, HEARTBEAT_DEAD_MS))
2747
+ trip(`heartbeat down ~${HEARTBEAT_DEAD_MS / 1e3}s`);
2748
+ else if (streamWedged(es.readyState === ES_OPEN, streamOkAt, now, STREAM_STALE_MS))
2749
+ trip(`command stream wedged ~${STREAM_STALE_MS / 1e3}s`);
2750
+ };
2751
+ void ping();
2752
+ const hb = setInterval(() => {
2753
+ void ping();
2754
+ watchdog();
2755
+ }, HEARTBEAT_MS);
2736
2756
  await new Promise((resolve2) => {
2737
2757
  stop = resolve2;
2738
2758
  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.9",
3
+ "version": "0.2.10",
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.