@cello-protocol/connect 0.0.69 → 0.0.70

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cello-protocol/connect",
3
- "version": "0.0.69",
3
+ "version": "0.0.70",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -9,17 +9,9 @@
9
9
  "publishConfig": {
10
10
  "access": "public"
11
11
  },
12
- "main": "./dist/index.js",
13
- "types": "./dist/index.d.ts",
14
12
  "bin": {
15
13
  "cello-mcp": "./dist/bin/cello-mcp.js"
16
14
  },
17
- "exports": {
18
- ".": {
19
- "import": "./dist/index.js",
20
- "types": "./dist/index.d.ts"
21
- }
22
- },
23
15
  "files": [
24
16
  "dist/",
25
17
  "package.json",
@@ -30,16 +22,15 @@
30
22
  "@libp2p/peer-id": "^6.0.8",
31
23
  "@modelcontextprotocol/sdk": "^1.29.0",
32
24
  "zod": "^4.4.2",
33
- "@cello-protocol/client": "0.0.50",
34
- "@cello-protocol/transport": "0.0.18",
35
- "@cello-protocol/crypto": "0.0.18"
25
+ "@cello-protocol/crypto": "0.0.19",
26
+ "@cello-protocol/transport": "0.0.19"
36
27
  },
37
28
  "devDependencies": {
38
29
  "@claude-flow/testing": "3.0.0-alpha.6",
39
30
  "@types/node": "^25.6.2",
40
31
  "cbor-x": "^1.6.0",
41
32
  "it-length-prefixed": "^10.0.1",
42
- "@cello-protocol/daemon": "0.0.51"
33
+ "@cello-protocol/daemon": "0.0.52"
43
34
  },
44
35
  "scripts": {
45
36
  "typecheck": "tsc --build",
package/dist/config.d.ts DELETED
@@ -1,30 +0,0 @@
1
- /**
2
- * CELLO adapter runtime configuration.
3
- *
4
- * AC-003 (REPOSPLIT-002): CELLO_DIRECTORY_URL defaults to the production directory
5
- * ALB endpoint. No CELLO_RELAY_MULTIADDR constant exists — relay multiaddr is
6
- * dynamically assigned per-session by the directory.
7
- */
8
- /** Production directory HTTP endpoint (ALB / Route53 — ALB terminates TLS, internal HTTP). */
9
- export declare const PRODUCTION_DIRECTORY_URL = "http://directory-us1.cello.mygentic.ai";
10
- /**
11
- * Resolve the directory URL from the environment, falling back to the
12
- * production endpoint when CELLO_DIRECTORY_URL is not set.
13
- *
14
- * @param env - process.env or a test substitute
15
- */
16
- export declare function resolveDirectoryUrl(env?: Record<string, string | undefined>): string;
17
- /**
18
- * Auto-discover the directory multiaddr via GET /bootstrap on the directory HTTP endpoint.
19
- *
20
- * Pseudocode (fetch strategy):
21
- * 1. GET ${directoryUrl}/bootstrap with 5s AbortController timeout
22
- * 2. On HTTP 200 + JSON { multiaddr: string } containing "/p2p/" → return multiaddr
23
- * 3. On HTTP non-200, network error, or invalid JSON → return null
24
- *
25
- * @param directoryUrl - Base URL of the directory HTTP endpoint
26
- * @param fetchFn - fetch implementation (injectable for testing)
27
- * @returns The resolved multiaddr string, or null on failure
28
- */
29
- export declare function fetchBootstrapMultiaddr(directoryUrl: string, fetchFn?: typeof fetch): Promise<string | null>;
30
- //# sourceMappingURL=config.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,8FAA8F;AAC9F,eAAO,MAAM,wBAAwB,2CAA2C,CAAC;AAEjF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GAAG,MAAM,CAEjG;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,uBAAuB,CAC3C,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmBxB"}
package/dist/config.js DELETED
@@ -1,53 +0,0 @@
1
- /**
2
- * CELLO adapter runtime configuration.
3
- *
4
- * AC-003 (REPOSPLIT-002): CELLO_DIRECTORY_URL defaults to the production directory
5
- * ALB endpoint. No CELLO_RELAY_MULTIADDR constant exists — relay multiaddr is
6
- * dynamically assigned per-session by the directory.
7
- */
8
- /** Production directory HTTP endpoint (ALB / Route53 — ALB terminates TLS, internal HTTP). */
9
- export const PRODUCTION_DIRECTORY_URL = "http://directory-us1.cello.mygentic.ai";
10
- /**
11
- * Resolve the directory URL from the environment, falling back to the
12
- * production endpoint when CELLO_DIRECTORY_URL is not set.
13
- *
14
- * @param env - process.env or a test substitute
15
- */
16
- export function resolveDirectoryUrl(env = process.env) {
17
- return env["CELLO_DIRECTORY_URL"] ?? PRODUCTION_DIRECTORY_URL;
18
- }
19
- /**
20
- * Auto-discover the directory multiaddr via GET /bootstrap on the directory HTTP endpoint.
21
- *
22
- * Pseudocode (fetch strategy):
23
- * 1. GET ${directoryUrl}/bootstrap with 5s AbortController timeout
24
- * 2. On HTTP 200 + JSON { multiaddr: string } containing "/p2p/" → return multiaddr
25
- * 3. On HTTP non-200, network error, or invalid JSON → return null
26
- *
27
- * @param directoryUrl - Base URL of the directory HTTP endpoint
28
- * @param fetchFn - fetch implementation (injectable for testing)
29
- * @returns The resolved multiaddr string, or null on failure
30
- */
31
- export async function fetchBootstrapMultiaddr(directoryUrl, fetchFn = fetch) {
32
- const bootstrapUrl = `${directoryUrl.replace(/\/$/, "")}/bootstrap`;
33
- const ac = new AbortController();
34
- const timeout = setTimeout(() => ac.abort(), 5000);
35
- try {
36
- const resp = await fetchFn(bootstrapUrl, { signal: ac.signal });
37
- if (!resp.ok) {
38
- return null;
39
- }
40
- const json = await resp.json();
41
- if (typeof json.multiaddr === "string" && json.multiaddr.includes("/p2p/")) {
42
- return json.multiaddr;
43
- }
44
- return null;
45
- }
46
- catch {
47
- return null;
48
- }
49
- finally {
50
- clearTimeout(timeout);
51
- }
52
- }
53
- //# sourceMappingURL=config.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,8FAA8F;AAC9F,MAAM,CAAC,MAAM,wBAAwB,GAAG,wCAAwC,CAAC;AAEjF;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAA0C,OAAO,CAAC,GAAG;IACvF,OAAO,GAAG,CAAC,qBAAqB,CAAC,IAAI,wBAAwB,CAAC;AAChE,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,YAAoB,EACpB,UAAwB,KAAK;IAE7B,MAAM,YAAY,GAAG,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC;IACpE,MAAM,EAAE,GAAG,IAAI,eAAe,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,CAAC;IACnD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;QAChE,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAA6B,CAAC;QAC1D,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,IAAI,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3E,OAAO,IAAI,CAAC,SAAS,CAAC;QACxB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;AACH,CAAC"}
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export { resolveDirectoryUrl, PRODUCTION_DIRECTORY_URL, fetchBootstrapMultiaddr } from "./config.js";
2
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js DELETED
@@ -1,2 +0,0 @@
1
- export { resolveDirectoryUrl, PRODUCTION_DIRECTORY_URL, fetchBootstrapMultiaddr } from "./config.js";
2
- //# sourceMappingURL=index.js.map
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC"}
@@ -1,72 +0,0 @@
1
- /**
2
- * lock-file.ts — PID lock file manager for single-instance cello-mcp process
3
- *
4
- * CELLO-M6B-001: Every new cello-mcp startup kills any prior process holding
5
- * the same lock file, ensuring exactly one cello-mcp per agent at all times.
6
- *
7
- * Pseudocode (Phase P):
8
- *
9
- * acquireLockFile(lockFilePath, opts?):
10
- * 1. Read lockFilePath. If it doesn't exist, skip to step 7.
11
- * 2. Parse PID from file content (trim whitespace, parseInt).
12
- * 3. Check if process with that PID is running (kill(priorPid, 0)).
13
- * - If ESRCH (no such process) → stale lock → skip to step 7.
14
- * - If EPERM (permission denied) → process exists but owned by another user → warn and continue to step 7.
15
- * - If process is running and killable → proceed to step 4.
16
- * 4. Send SIGTERM to prior process.
17
- * 5. Poll every 100ms for up to 5 seconds, checking if process still exists (kill(priorPid, 0)).
18
- * 6. If still running after 5s, send SIGKILL. Log at warn level.
19
- * 7. Create lock file directory (mkdir -p) if needed.
20
- * 8. Write current process.pid to lockFilePath.
21
- * 9. Return a cleanup function that removes the lock file.
22
- *
23
- * releaseLockFile(lockFilePath):
24
- * 1. Call fs.unlinkSync(lockFilePath), catch ENOENT silently.
25
- *
26
- * Dependency injection for testability:
27
- * - killFn: (pid: number, signal: number | NodeJS.Signals) => void
28
- * Defaults to process.kill. Tests inject a mock that throws for EPERM scenarios.
29
- */
30
- /**
31
- * Get the lock file path for a given agent name.
32
- * Used by both cello-mcp.ts and tests to ensure path logic is consistent.
33
- *
34
- * @param agentName - Optional agent name (M7+ multi-agent support)
35
- * @param baseDir - Base directory for .cello (defaults to homedir())
36
- * @returns Absolute path to the lock file
37
- */
38
- export declare function getLockFilePath(agentName: string | null, baseDir?: string): string;
39
- export interface LockFileOptions {
40
- /**
41
- * Injected kill function for testing. Defaults to process.kill.
42
- * Tests can inject a mock that throws { code: 'EPERM' } to simulate cross-user scenarios.
43
- */
44
- killFn?: (pid: number, signal: number | NodeJS.Signals) => void;
45
- /**
46
- * Logger for observability events. If not provided, no events are logged.
47
- */
48
- logger?: {
49
- info: (event: string, context: Record<string, unknown>) => void;
50
- warn: (event: string, context: Record<string, unknown>) => void;
51
- };
52
- }
53
- /**
54
- * Acquire the PID lock file. If a prior process is running, kill it (SIGTERM → wait → SIGKILL).
55
- * Returns a cleanup function that releases the lock file on exit.
56
- *
57
- * AC-001, AC-002, AC-004, SI-001
58
- */
59
- export declare function acquireLockFile(lockFilePath: string, opts?: LockFileOptions): Promise<() => void>;
60
- /**
61
- * Release the lock file. Called on SIGTERM/SIGINT/normal exit.
62
- * AC-003
63
- *
64
- * @param lockFilePath - Path to the lock file to remove
65
- * @param logger - Optional logger for observability events
66
- * @param unlinkFn - Injected unlink function for testing (defaults to fs.unlinkSync)
67
- */
68
- export declare function releaseLockFile(lockFilePath: string, logger?: {
69
- info: (event: string, context: Record<string, unknown>) => void;
70
- warn: (event: string, context: Record<string, unknown>) => void;
71
- }, unlinkFn?: (path: string) => void): void;
72
- //# sourceMappingURL=lock-file.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"lock-file.d.ts","sourceRoot":"","sources":["../src/lock-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAMH;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,OAAO,SAAY,GAClB,MAAM,CAIR;AAED,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,OAAO,KAAK,IAAI,CAAC;IAChE;;OAEG;IACH,MAAM,CAAC,EAAE;QACP,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAChE,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;KACjE,CAAC;CACH;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,YAAY,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,eAAe,GACrB,OAAO,CAAC,MAAM,IAAI,CAAC,CAgMrB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,YAAY,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE;IACP,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAChE,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CACjE,EACD,QAAQ,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAiB,GAC5C,IAAI,CAcN"}
package/dist/lock-file.js DELETED
@@ -1,270 +0,0 @@
1
- /**
2
- * lock-file.ts — PID lock file manager for single-instance cello-mcp process
3
- *
4
- * CELLO-M6B-001: Every new cello-mcp startup kills any prior process holding
5
- * the same lock file, ensuring exactly one cello-mcp per agent at all times.
6
- *
7
- * Pseudocode (Phase P):
8
- *
9
- * acquireLockFile(lockFilePath, opts?):
10
- * 1. Read lockFilePath. If it doesn't exist, skip to step 7.
11
- * 2. Parse PID from file content (trim whitespace, parseInt).
12
- * 3. Check if process with that PID is running (kill(priorPid, 0)).
13
- * - If ESRCH (no such process) → stale lock → skip to step 7.
14
- * - If EPERM (permission denied) → process exists but owned by another user → warn and continue to step 7.
15
- * - If process is running and killable → proceed to step 4.
16
- * 4. Send SIGTERM to prior process.
17
- * 5. Poll every 100ms for up to 5 seconds, checking if process still exists (kill(priorPid, 0)).
18
- * 6. If still running after 5s, send SIGKILL. Log at warn level.
19
- * 7. Create lock file directory (mkdir -p) if needed.
20
- * 8. Write current process.pid to lockFilePath.
21
- * 9. Return a cleanup function that removes the lock file.
22
- *
23
- * releaseLockFile(lockFilePath):
24
- * 1. Call fs.unlinkSync(lockFilePath), catch ENOENT silently.
25
- *
26
- * Dependency injection for testability:
27
- * - killFn: (pid: number, signal: number | NodeJS.Signals) => void
28
- * Defaults to process.kill. Tests inject a mock that throws for EPERM scenarios.
29
- */
30
- import { readFileSync, writeFileSync, unlinkSync, mkdirSync } from "node:fs";
31
- import { dirname, join } from "node:path";
32
- import { homedir } from "node:os";
33
- /**
34
- * Get the lock file path for a given agent name.
35
- * Used by both cello-mcp.ts and tests to ensure path logic is consistent.
36
- *
37
- * @param agentName - Optional agent name (M7+ multi-agent support)
38
- * @param baseDir - Base directory for .cello (defaults to homedir())
39
- * @returns Absolute path to the lock file
40
- */
41
- export function getLockFilePath(agentName, baseDir = homedir()) {
42
- return agentName
43
- ? join(baseDir, ".cello", "agents", agentName, "cello-mcp.pid")
44
- : join(baseDir, ".cello", "cello-mcp.pid");
45
- }
46
- /**
47
- * Acquire the PID lock file. If a prior process is running, kill it (SIGTERM → wait → SIGKILL).
48
- * Returns a cleanup function that releases the lock file on exit.
49
- *
50
- * AC-001, AC-002, AC-004, SI-001
51
- */
52
- export async function acquireLockFile(lockFilePath, opts) {
53
- const killFn = opts?.killFn ?? process.kill.bind(process);
54
- const logger = opts?.logger;
55
- // Step 1-3: Check for prior process
56
- let priorPid = null;
57
- try {
58
- const content = readFileSync(lockFilePath, "utf8").trim();
59
- priorPid = parseInt(content, 10);
60
- if (isNaN(priorPid) || priorPid <= 0) {
61
- // Invalid or dangerous PID — treat as stale.
62
- // priorPid === 0: process.kill(0, ...) sends signal to the entire process group.
63
- // priorPid < 0: process.kill(-1, ...) sends signal to all processes the user can signal.
64
- // Both are POSIX semantics — neither refers to the intended prior cello-mcp instance.
65
- priorPid = null;
66
- }
67
- }
68
- catch (err) {
69
- // ENOENT = no lock file exists → proceed to write
70
- if (err.code !== "ENOENT") {
71
- // Unexpected read error (permissions, etc) — non-fatal, log and continue
72
- logger?.warn("client.startup.lock.read.failed", {
73
- reason: err.message,
74
- });
75
- }
76
- priorPid = null;
77
- }
78
- if (priorPid !== null) {
79
- // Check if process is running
80
- let isRunning = false;
81
- try {
82
- killFn(priorPid, 0); // signal 0 = check existence without killing
83
- isRunning = true;
84
- }
85
- catch (err) {
86
- const code = err.code;
87
- if (code === "ESRCH") {
88
- // No such process — stale lock
89
- isRunning = false;
90
- }
91
- else if (code === "EPERM") {
92
- // Process exists but owned by another user (SI-001)
93
- logger?.warn("client.startup.lock.eperm", {
94
- priorPid,
95
- reason: "process owned by another user",
96
- });
97
- isRunning = false; // Treat as stale — we cannot kill it, proceed with our own lock
98
- }
99
- else {
100
- // Unknown error — log and treat as not running
101
- logger?.warn("client.startup.lock.check.failed", {
102
- priorPid,
103
- reason: err.message,
104
- });
105
- isRunning = false;
106
- }
107
- }
108
- if (isRunning) {
109
- // Step 4: Send SIGTERM
110
- // Track whether SIGTERM was successfully delivered. If the kill call throws
111
- // (race: process exited between the existence check and the kill call), we
112
- // know the process is already gone — we must NOT emit prior.process.killed,
113
- // because the taxonomy entry says "after successfully killing a prior process."
114
- let sigTermDelivered = false;
115
- try {
116
- killFn(priorPid, "SIGTERM");
117
- sigTermDelivered = true;
118
- }
119
- catch (err) {
120
- // Kill failed (race: process exited between check and kill) — non-fatal.
121
- // The process is already gone; skip the polling phase and proceed to write.
122
- logger?.warn("client.startup.prior.kill.failed", {
123
- priorPid,
124
- signal: "SIGTERM",
125
- reason: err.message,
126
- });
127
- }
128
- // Only poll if SIGTERM was actually delivered
129
- let stillRunning = sigTermDelivered;
130
- if (sigTermDelivered) {
131
- // Step 5: Poll for up to 5 seconds
132
- const startTime = Date.now();
133
- const timeout = 5000;
134
- // Check immediately after SIGTERM (most processes exit quickly)
135
- try {
136
- killFn(priorPid, 0);
137
- stillRunning = true;
138
- }
139
- catch (err) {
140
- if (err.code === "ESRCH") {
141
- // ESRCH = no such process — process is gone
142
- stillRunning = false;
143
- }
144
- // EPERM or unknown — process may still exist; treat as still running
145
- }
146
- // Only poll if still running after immediate check
147
- while (stillRunning && Date.now() - startTime < timeout) {
148
- await new Promise((resolve) => setTimeout(resolve, 100));
149
- try {
150
- killFn(priorPid, 0);
151
- stillRunning = true;
152
- }
153
- catch (err) {
154
- if (err.code === "ESRCH") {
155
- // ESRCH = no such process — process is gone
156
- stillRunning = false;
157
- break;
158
- }
159
- // EPERM or unknown — process may still exist; treat as still running
160
- }
161
- }
162
- }
163
- // Step 6: Escalate to SIGKILL if needed
164
- if (stillRunning) {
165
- try {
166
- killFn(priorPid, "SIGKILL");
167
- // Poll after SIGKILL to verify the kernel has reaped the process before
168
- // we write the lock file. SIGKILL is unblockable but process cleanup
169
- // (releasing file locks, closing fds) is not instantaneous — especially
170
- // on loaded systems. Poll up to 500ms in 50ms intervals.
171
- const sigkillDeadline = Date.now() + 500;
172
- let reaped = false;
173
- while (Date.now() < sigkillDeadline) {
174
- await new Promise((resolve) => setTimeout(resolve, 50));
175
- try {
176
- killFn(priorPid, 0);
177
- // Still running — keep polling
178
- }
179
- catch (err) {
180
- if (err.code === "ESRCH") {
181
- // ESRCH = no such process — reaping confirmed
182
- reaped = true;
183
- break;
184
- }
185
- // EPERM or unknown — not yet confirmed reaped; continue polling
186
- }
187
- }
188
- // Log a single event regardless of whether reaping was confirmed.
189
- // reaped=false on a very loaded system means SIGKILL was sent but the kernel
190
- // hasn't cleaned up within 500ms — the process is dying, we proceed anyway.
191
- logger?.warn("client.startup.prior.process.killed", {
192
- priorPid,
193
- signal: "SIGKILL",
194
- reaped,
195
- });
196
- }
197
- catch (err) {
198
- logger?.warn("client.startup.prior.kill.failed", {
199
- priorPid,
200
- signal: "SIGKILL",
201
- reason: err.message,
202
- });
203
- }
204
- }
205
- else if (sigTermDelivered) {
206
- // Graceful exit — SIGTERM was delivered and the process exited on its own.
207
- // Only emit this event when we successfully sent the signal (sigTermDelivered=true)
208
- // and the process is confirmed gone (stillRunning=false). Do NOT emit when
209
- // SIGTERM threw (the process was already dead before we could send the signal).
210
- logger?.info("client.startup.prior.process.killed", {
211
- priorPid,
212
- signal: "SIGTERM",
213
- });
214
- }
215
- }
216
- }
217
- // Step 7-8: Write own PID to lock file
218
- // Per canonical taxonomy and story observability spec: lock file write failure is
219
- // NON-FATAL. Log at warn level and proceed — orphan detection is disabled but the
220
- // process can still serve MCP tool calls. The operator will see the warn event and
221
- // can diagnose the filesystem permission issue separately.
222
- try {
223
- mkdirSync(dirname(lockFilePath), { recursive: true });
224
- writeFileSync(lockFilePath, String(process.pid), "utf8");
225
- logger?.info("client.startup.lock.acquired", { pid: process.pid });
226
- }
227
- catch (err) {
228
- logger?.warn("client.startup.lock.write.failed", {
229
- reason: err.message,
230
- });
231
- // Non-fatal: return a no-op cleanup since we have no lock to release
232
- return () => { };
233
- }
234
- // Step 9: Return idempotent cleanup function.
235
- // The function may be called from multiple paths (exit event + signal/exception handler).
236
- // The released flag suppresses the second call to prevent a duplicate
237
- // client.startup.lock.released log event in the wild (I-3 finding).
238
- let released = false;
239
- return () => {
240
- if (!released) {
241
- released = true;
242
- releaseLockFile(lockFilePath, logger);
243
- }
244
- };
245
- }
246
- /**
247
- * Release the lock file. Called on SIGTERM/SIGINT/normal exit.
248
- * AC-003
249
- *
250
- * @param lockFilePath - Path to the lock file to remove
251
- * @param logger - Optional logger for observability events
252
- * @param unlinkFn - Injected unlink function for testing (defaults to fs.unlinkSync)
253
- */
254
- export function releaseLockFile(lockFilePath, logger, unlinkFn = unlinkSync) {
255
- try {
256
- unlinkFn(lockFilePath);
257
- logger?.info("client.startup.lock.released", { pid: process.pid });
258
- }
259
- catch (err) {
260
- // ENOENT is expected if cleanup ran twice — silent
261
- if (err.code !== "ENOENT") {
262
- // Unexpected unlink failure (EACCES, EROFS, etc.) — non-fatal but observable
263
- logger?.warn("client.startup.lock.release.failed", {
264
- reason: err.message,
265
- pid: process.pid,
266
- });
267
- }
268
- }
269
- }
270
- //# sourceMappingURL=lock-file.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"lock-file.js","sourceRoot":"","sources":["../src/lock-file.ts"],"names":[],"mappings":"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"}