@xulongzhe/clawbench 0.96.0 → 0.97.0

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/bin/clawbench.js CHANGED
@@ -4,7 +4,7 @@ import { resolve, dirname } from "path";
4
4
  import { fileURLToPath } from "url";
5
5
  import { createRequire } from "module";
6
6
  import os from "os";
7
- import { resolvePlatformPackage, resolveBinName } from "./platform.js";
7
+ import { resolvePlatformPackage, resolveBinName, resolveSpawnOptions } from "./platform.js";
8
8
 
9
9
  const __dirname = dirname(fileURLToPath(import.meta.url));
10
10
  const require = createRequire(import.meta.url);
@@ -35,10 +35,10 @@ if (process.env.CLAWBENCH_BINARY_PATH) {
35
35
  }
36
36
  }
37
37
 
38
- const child = spawn(binPath, process.argv.slice(2), {
39
- stdio: "inherit",
40
- env: { ...process.env },
41
- });
38
+ // detached: true makes the server a session leader in its own process group,
39
+ // so a terminal hangup cannot reach it. See resolveSpawnOptions for the full
40
+ // rationale. Signals are forwarded explicitly below, so Ctrl+C still stops it.
41
+ const child = spawn(binPath, process.argv.slice(2), resolveSpawnOptions({ ...process.env }));
42
42
 
43
43
  // 转发信号到子进程
44
44
  process.on("SIGINT", () => child.kill("SIGINT"));
package/bin/platform.js CHANGED
@@ -30,3 +30,24 @@ export function resolvePlatformPackage(platform, arch) {
30
30
  export function resolveBinName(platform) {
31
31
  return platform === "win32" ? "clawbench.exe" : "clawbench";
32
32
  }
33
+
34
+ // Returns the spawn options used to launch the server binary.
35
+ //
36
+ // `detached: true` puts the server in its own session/process group. Without
37
+ // it the server inherits the launcher's group, so a terminal hangup (SSH
38
+ // disconnect, closing the shell) delivers SIGHUP to the whole group and the
39
+ // server dies. `nohup` cannot prevent this: it only sets SIG_IGN on the
40
+ // launcher, and Node resets that disposition to SIG_DFL for itself, so the
41
+ // signal reaches the binary regardless. The directly-installed binary has no
42
+ // launcher layer and survives a hangup, which is why only the npm install
43
+ // showed the problem.
44
+ //
45
+ // Signals are still forwarded explicitly by the launcher, so Ctrl+C and
46
+ // `kill <launcher-pid>` keep stopping the server.
47
+ export function resolveSpawnOptions(env) {
48
+ return {
49
+ stdio: "inherit",
50
+ env,
51
+ detached: true,
52
+ };
53
+ }
@@ -3,6 +3,7 @@ import {
3
3
  PLATFORM_MAP,
4
4
  resolvePlatformPackage,
5
5
  resolveBinName,
6
+ resolveSpawnOptions,
6
7
  } from './platform.js'
7
8
 
8
9
  describe('resolvePlatformPackage', () => {
@@ -53,3 +54,30 @@ describe('PLATFORM_MAP', () => {
53
54
  expect(PLATFORM_MAP['android-arm64']).toBe('@xulongzhe/clawbench-android-arm64')
54
55
  })
55
56
  })
57
+
58
+ describe('resolveSpawnOptions', () => {
59
+ // Regression guard: without detached the server joins the launcher's process
60
+ // group and dies on terminal hangup (SSH disconnect), because `nohup` only
61
+ // shields the launcher — Node resets SIG_IGN to SIG_DFL for itself, so the
62
+ // SIGHUP still reaches the Go binary. The directly-installed binary has no
63
+ // launcher layer, which is why only the npm install was affected.
64
+ it('detaches the server so a terminal hangup cannot kill it', () => {
65
+ expect(resolveSpawnOptions({}).detached).toBe(true)
66
+ })
67
+
68
+ it('passes the environment through to the server', () => {
69
+ const env = { PATH: '/usr/bin', CLAWBENCH_CHILD: '1' }
70
+ expect(resolveSpawnOptions(env).env).toEqual(env)
71
+ })
72
+
73
+ it('keeps stdio inherited so the server logs stay visible', () => {
74
+ expect(resolveSpawnOptions({}).stdio).toBe('inherit')
75
+ })
76
+
77
+ it('returns a fresh object so callers cannot mutate shared state', () => {
78
+ const first = resolveSpawnOptions({ A: '1' })
79
+ const second = resolveSpawnOptions({ A: '2' })
80
+ expect(first).not.toBe(second)
81
+ expect(first.env).not.toBe(second.env)
82
+ })
83
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xulongzhe/clawbench",
3
- "version": "0.96.0",
3
+ "version": "0.97.0",
4
4
  "description": "ClawBench — AI Workbench, United Across Devices. Go backend + Vue3 frontend, runs on PC, Android, and Termux",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,12 +14,12 @@
14
14
  "postinstall": "node install.js"
15
15
  },
16
16
  "optionalDependencies": {
17
- "@xulongzhe/clawbench-linux-x64": "0.96.0",
18
- "@xulongzhe/clawbench-linux-arm64": "0.96.0",
19
- "@xulongzhe/clawbench-android-arm64": "0.96.0",
20
- "@xulongzhe/clawbench-darwin-x64": "0.96.0",
21
- "@xulongzhe/clawbench-darwin-arm64": "0.96.0",
22
- "@xulongzhe/clawbench-win32-x64": "0.96.0"
17
+ "@xulongzhe/clawbench-linux-x64": "0.97.0",
18
+ "@xulongzhe/clawbench-linux-arm64": "0.97.0",
19
+ "@xulongzhe/clawbench-android-arm64": "0.97.0",
20
+ "@xulongzhe/clawbench-darwin-x64": "0.97.0",
21
+ "@xulongzhe/clawbench-darwin-arm64": "0.97.0",
22
+ "@xulongzhe/clawbench-win32-x64": "0.97.0"
23
23
  },
24
24
  "engines": {
25
25
  "node": ">=14"