@xiaohhhh1/canvas-agent 0.4.8 → 0.4.9

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.
@@ -1,4 +1,5 @@
1
1
  export declare const AGENT_REPLACED_EXIT_CODE = 75;
2
2
  export declare function shouldRestartAgent(code: number | null, stopping: boolean): boolean;
3
+ export declare function shouldReplaceSupervisorLock(ownerPid: number, ageMs: number, ownerAlive: boolean): boolean;
3
4
  /** Keep the local HTTP worker alive, but step aside when a newer package replaces it. */
4
5
  export declare function startHttpSupervisor(): void;
@@ -1,17 +1,112 @@
1
1
  import { spawn } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { CONFIG_DIR } from "../config.js";
2
5
  export const AGENT_REPLACED_EXIT_CODE = 75;
3
6
  const RESTART_DELAY_MS = 1000;
7
+ const SUPERVISOR_LOCK_FILE = path.join(CONFIG_DIR, "canvas-agent-watch.lock");
8
+ const SUPERVISOR_HEARTBEAT_MS = 5000;
9
+ const SUPERVISOR_LOCK_STALE_MS = 15000;
10
+ const SUPERVISOR_TAKEOVER_WAIT_MS = 6000;
11
+ const SUPERVISOR_TAKEOVER_RETRY_MS = 100;
4
12
  export function shouldRestartAgent(code, stopping) {
5
13
  return !stopping && code !== AGENT_REPLACED_EXIT_CODE;
6
14
  }
15
+ export function shouldReplaceSupervisorLock(ownerPid, ageMs, ownerAlive) {
16
+ return !Number.isInteger(ownerPid) || ownerPid <= 0 || !ownerAlive || ageMs > SUPERVISOR_LOCK_STALE_MS;
17
+ }
18
+ function processIsAlive(pid) {
19
+ try {
20
+ process.kill(pid, 0);
21
+ return true;
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ function sleepSync(delayMs) {
28
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delayMs);
29
+ }
30
+ function acquireSupervisorLock() {
31
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
32
+ const deadline = Date.now() + SUPERVISOR_TAKEOVER_WAIT_MS;
33
+ while (Date.now() <= deadline) {
34
+ let descriptor;
35
+ try {
36
+ descriptor = fs.openSync(SUPERVISOR_LOCK_FILE, "wx");
37
+ fs.writeFileSync(descriptor, String(process.pid));
38
+ return descriptor;
39
+ }
40
+ catch (error) {
41
+ if (descriptor !== undefined) {
42
+ try {
43
+ fs.closeSync(descriptor);
44
+ }
45
+ catch { }
46
+ try {
47
+ fs.unlinkSync(SUPERVISOR_LOCK_FILE);
48
+ }
49
+ catch { }
50
+ }
51
+ if (error.code !== "EEXIST")
52
+ throw error;
53
+ try {
54
+ const ownerPid = Number(fs.readFileSync(SUPERVISOR_LOCK_FILE, "utf8").trim());
55
+ const ageMs = Date.now() - fs.statSync(SUPERVISOR_LOCK_FILE).mtimeMs;
56
+ if (shouldReplaceSupervisorLock(ownerPid, ageMs, processIsAlive(ownerPid))) {
57
+ fs.unlinkSync(SUPERVISOR_LOCK_FILE);
58
+ continue;
59
+ }
60
+ }
61
+ catch (readError) {
62
+ if (readError.code !== "ENOENT") {
63
+ try {
64
+ fs.unlinkSync(SUPERVISOR_LOCK_FILE);
65
+ }
66
+ catch { }
67
+ }
68
+ continue;
69
+ }
70
+ sleepSync(SUPERVISOR_TAKEOVER_RETRY_MS);
71
+ }
72
+ }
73
+ return undefined;
74
+ }
75
+ function releaseSupervisorLock(descriptor) {
76
+ try {
77
+ fs.closeSync(descriptor);
78
+ }
79
+ catch { }
80
+ try {
81
+ if (fs.readFileSync(SUPERVISOR_LOCK_FILE, "utf8").trim() === String(process.pid)) {
82
+ fs.unlinkSync(SUPERVISOR_LOCK_FILE);
83
+ }
84
+ }
85
+ catch { }
86
+ }
7
87
  /** Keep the local HTTP worker alive, but step aside when a newer package replaces it. */
8
88
  export function startHttpSupervisor() {
9
89
  const entry = process.argv[1];
10
90
  if (!entry)
11
91
  throw new Error("无法定位 Canvas Agent 启动文件");
92
+ const lockDescriptor = acquireSupervisorLock();
93
+ if (lockDescriptor === undefined)
94
+ return;
12
95
  let child;
13
96
  let stopping = false;
14
97
  let restartTimer;
98
+ const lockHeartbeat = setInterval(() => {
99
+ try {
100
+ fs.utimesSync(SUPERVISOR_LOCK_FILE, new Date(), new Date());
101
+ }
102
+ catch { }
103
+ }, SUPERVISOR_HEARTBEAT_MS);
104
+ lockHeartbeat.unref();
105
+ const exit = () => {
106
+ clearInterval(lockHeartbeat);
107
+ releaseSupervisorLock(lockDescriptor);
108
+ process.exit(0);
109
+ };
15
110
  const launch = () => {
16
111
  if (stopping)
17
112
  return;
@@ -19,7 +114,7 @@ export function startHttpSupervisor() {
19
114
  child.once("exit", (code) => {
20
115
  child = undefined;
21
116
  if (!shouldRestartAgent(code, stopping))
22
- return void process.exit(0);
117
+ return exit();
23
118
  restartTimer = setTimeout(launch, RESTART_DELAY_MS);
24
119
  });
25
120
  };
@@ -30,7 +125,7 @@ export function startHttpSupervisor() {
30
125
  if (restartTimer)
31
126
  clearTimeout(restartTimer);
32
127
  child?.kill();
33
- setTimeout(() => process.exit(0), 2000).unref();
128
+ setTimeout(exit, 2000).unref();
34
129
  };
35
130
  process.once("SIGINT", stop);
36
131
  process.once("SIGTERM", stop);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiaohhhh1/canvas-agent",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",