@hivemind-os/collective-shim 0.2.4 → 0.2.6

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.
@@ -6,9 +6,9 @@ $ tsup
6
6
  CLI Target: es2022
7
7
  CLI Cleaning output folder
8
8
  ESM Build start
9
- ESM dist/index.js 11.41 KB
10
- ESM dist/index.js.map 22.04 KB
11
- ESM ⚡️ Build success in 28ms
9
+ ESM dist/index.js 13.19 KB
10
+ ESM dist/index.js.map 26.04 KB
11
+ ESM ⚡️ Build success in 38ms
12
12
  DTS Build start
13
- DTS ⚡️ Build success in 2570ms
13
+ DTS ⚡️ Build success in 2285ms
14
14
  DTS dist/index.d.ts 20.00 B
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import { promisify } from "util";
8
8
 
9
9
  // src/daemon-launcher.ts
10
10
  import { spawn, spawnSync } from "child_process";
11
+ import { readFileSync, unlinkSync } from "fs";
11
12
  import { createRequire } from "module";
12
13
  import net from "net";
13
14
  import { homedir, userInfo } from "os";
@@ -15,6 +16,7 @@ import { resolve } from "path";
15
16
  import { setTimeout as delay } from "timers/promises";
16
17
  import { fileURLToPath } from "url";
17
18
  var require2 = createRequire(import.meta.url);
19
+ var { version: SHIM_VERSION } = require2("../package.json");
18
20
  var LEGACY_WINDOWS_PIPE_PATH = "\\\\.\\pipe\\hivemind-collective";
19
21
  function getDefaultIpcPath() {
20
22
  return process.env.COLLECTIVE_IPC_PATH ?? (process.platform === "win32" ? `${LEGACY_WINDOWS_PIPE_PATH}-${sanitizePipeSegment(getCurrentUsername())}` : resolve(homedir(), ".hivemind-os/collective", "mesh.sock"));
@@ -101,10 +103,53 @@ function sanitizePipeSegment(value) {
101
103
  const sanitized = leaf.trim().toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "");
102
104
  return sanitized || "unknown-user";
103
105
  }
106
+ async function stopDaemon(options) {
107
+ const { pidFile, ipcPath, timeoutMs = 1e4 } = options;
108
+ let pid;
109
+ try {
110
+ pid = parseInt(readFileSync(pidFile, "utf8").trim(), 10);
111
+ } catch {
112
+ return;
113
+ }
114
+ if (isNaN(pid)) {
115
+ try {
116
+ unlinkSync(pidFile);
117
+ } catch {
118
+ }
119
+ return;
120
+ }
121
+ try {
122
+ process.kill(pid, "SIGTERM");
123
+ } catch {
124
+ try {
125
+ unlinkSync(pidFile);
126
+ } catch {
127
+ }
128
+ return;
129
+ }
130
+ const deadline = Date.now() + timeoutMs;
131
+ while (Date.now() < deadline) {
132
+ if (!await isDaemonRunning(ipcPath)) {
133
+ return;
134
+ }
135
+ await delay(200);
136
+ }
137
+ try {
138
+ process.kill(pid, "SIGKILL");
139
+ } catch {
140
+ }
141
+ try {
142
+ unlinkSync(pidFile);
143
+ } catch {
144
+ }
145
+ }
104
146
 
105
147
  // src/ipc-client.ts
148
+ import { createRequire as createRequire2 } from "module";
106
149
  import { randomUUID } from "crypto";
107
150
  import net2 from "net";
151
+ var require3 = createRequire2(import.meta.url);
152
+ var { version: SHIM_VERSION2 } = require3("../package.json");
108
153
  var IpcClient = class {
109
154
  constructor(ipcPath) {
110
155
  this.ipcPath = ipcPath;
@@ -140,7 +185,7 @@ var IpcClient = class {
140
185
  });
141
186
  socket.on("error", () => void 0);
142
187
  this.helloId = `shim-hello-${randomUUID()}`;
143
- await new Promise((resolve2, reject) => {
188
+ return new Promise((resolve2, reject) => {
144
189
  this.helloWaiter = { resolve: resolve2, reject };
145
190
  this.send({
146
191
  jsonrpc: "2.0",
@@ -149,6 +194,7 @@ var IpcClient = class {
149
194
  params: {
150
195
  appName,
151
196
  pid: process.pid,
197
+ shimVersion: SHIM_VERSION2,
152
198
  ...process.env.COLLECTIVE_PROFILE ? { profile: process.env.COLLECTIVE_PROFILE } : {}
153
199
  }
154
200
  });
@@ -194,7 +240,11 @@ var IpcClient = class {
194
240
  if (message.error && typeof message.error === "object") {
195
241
  waiter.reject(new Error(String(message.error.message ?? "shim_hello failed")));
196
242
  } else {
197
- waiter.resolve();
243
+ const result = message.result ?? {};
244
+ waiter.resolve({
245
+ daemonVersion: typeof result.daemonVersion === "string" ? result.daemonVersion : void 0,
246
+ connectionId: typeof result.connectionId === "string" ? result.connectionId : void 0
247
+ });
198
248
  }
199
249
  } else {
200
250
  for (const handler of this.messageHandlers) {
@@ -238,6 +288,7 @@ async function createBridge(options = {}) {
238
288
  const stderr = options.stderr ?? process.stderr;
239
289
  const exit = options.exit ?? ((code) => process.exit(code));
240
290
  const ensureDaemon = options.ensureDaemon ?? ensureDaemonRunning;
291
+ const stopDaemonFn = options.stopDaemon ?? stopDaemon;
241
292
  const launcherOptions = {
242
293
  ipcPath: options.ipcPath ?? getDefaultIpcPath(),
243
294
  pidFile: options.pidFile ?? getDefaultPidFile(),
@@ -269,6 +320,7 @@ async function createBridge(options = {}) {
269
320
  }
270
321
  }
271
322
  };
323
+ let upgraded = false;
272
324
  const connect = async () => {
273
325
  await ensureDaemon(launcherOptions);
274
326
  const deadline = Date.now() + launcherOptions.startupTimeoutMs;
@@ -285,7 +337,18 @@ async function createBridge(options = {}) {
285
337
  }
286
338
  });
287
339
  try {
288
- await next.connect(appName);
340
+ const hello = await next.connect(appName);
341
+ if (!upgraded && hello.daemonVersion && hello.daemonVersion !== SHIM_VERSION) {
342
+ upgraded = true;
343
+ stderr.write(
344
+ `mesh-shim: Upgrading daemon from v${hello.daemonVersion} to v${SHIM_VERSION}
345
+ `
346
+ );
347
+ next.close();
348
+ await stopDaemonFn({ pidFile: launcherOptions.pidFile, ipcPath: launcherOptions.ipcPath });
349
+ await ensureDaemon(launcherOptions);
350
+ continue;
351
+ }
289
352
  client = next;
290
353
  flushPending();
291
354
  return;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bridge.ts","../src/daemon-launcher.ts","../src/ipc-client.ts","../src/index.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { basename } from 'node:path';\nimport { setTimeout as delay } from 'node:timers/promises';\nimport { promisify } from 'node:util';\n\nimport {\n ensureDaemonRunning,\n getDefaultIpcPath,\n getDefaultPidFile,\n resolveDaemonBin,\n type LauncherOptions,\n} from './daemon-launcher.js';\nimport { IpcClient } from './ipc-client.js';\n\nconst execFileAsync = promisify(execFile);\n\nexport interface BridgeOptions {\n ipcPath?: string;\n pidFile?: string;\n daemonBin?: string;\n startupTimeoutMs?: number;\n appName?: string;\n stdin?: NodeJS.ReadableStream;\n stdout?: NodeJS.WritableStream;\n stderr?: NodeJS.WritableStream;\n exit?: (code: number) => void;\n ensureDaemon?: (options: LauncherOptions) => Promise<void>;\n}\n\nexport interface BridgeHandle {\n close(): void;\n}\n\nexport async function startShim(): Promise<void> {\n await createBridge();\n}\n\nexport async function createBridge(options: BridgeOptions = {}): Promise<BridgeHandle> {\n const stdin = options.stdin ?? process.stdin;\n const stdout = options.stdout ?? process.stdout;\n const stderr = options.stderr ?? process.stderr;\n const exit = options.exit ?? ((code: number) => process.exit(code));\n const ensureDaemon = options.ensureDaemon ?? ensureDaemonRunning;\n const launcherOptions: LauncherOptions = {\n ipcPath: options.ipcPath ?? getDefaultIpcPath(),\n pidFile: options.pidFile ?? getDefaultPidFile(),\n daemonBin: options.daemonBin ?? resolveDaemonBin(),\n startupTimeoutMs: options.startupTimeoutMs ?? 30_000,\n };\n const appName = options.appName ?? (await guessAppName());\n\n let client: IpcClient | undefined;\n let stdinBuffer = '';\n let closing = false;\n let reconnecting: Promise<void> | undefined;\n const pending: string[] = [];\n\n const writeError = (message: string) => {\n stdout.write(`${JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32000, message } })}\\n`);\n stderr.write(`mesh-shim: ${message}\\n`);\n };\n\n const flushPending = () => {\n if (!client) {\n return;\n }\n\n while (pending.length > 0) {\n try {\n client.sendRaw(pending[0] as string);\n pending.shift();\n } catch {\n return;\n }\n }\n };\n\n const connect = async () => {\n await ensureDaemon(launcherOptions);\n const deadline = Date.now() + launcherOptions.startupTimeoutMs;\n while (true) {\n const next = new IpcClient(launcherOptions.ipcPath);\n next.onMessage((message) => {\n stdout.write(`${JSON.stringify(message)}\\n`);\n });\n next.onClose(() => {\n if (!closing && client === next) {\n client = undefined;\n void reconnect();\n }\n });\n\n try {\n await next.connect(appName);\n client = next;\n flushPending();\n return;\n } catch (error) {\n next.close();\n if (Date.now() >= deadline) {\n throw error;\n }\n await delay(200);\n }\n }\n };\n\n const reconnect = async () => {\n if (reconnecting) {\n return reconnecting;\n }\n\n reconnecting = (async () => {\n try {\n await connect();\n } catch (error) {\n if (!closing) {\n closing = true;\n client?.close();\n writeError(`Unable to reach mesh daemon: ${(error as Error).message}`);\n exit(1);\n }\n } finally {\n reconnecting = undefined;\n }\n })();\n\n return reconnecting;\n };\n\n try {\n await connect();\n } catch (error) {\n closing = true;\n writeError(`Unable to start mesh daemon: ${(error as Error).message}`);\n exit(1);\n return { close: () => undefined };\n }\n\n if ('setEncoding' in stdin && typeof stdin.setEncoding === 'function') {\n stdin.setEncoding('utf8');\n }\n\n const handleChunk = (chunk: string | Buffer) => {\n stdinBuffer += chunk.toString();\n let newlineIndex = stdinBuffer.indexOf('\\n');\n while (newlineIndex >= 0) {\n const line = stdinBuffer.slice(0, newlineIndex).trim();\n stdinBuffer = stdinBuffer.slice(newlineIndex + 1);\n if (line) {\n if (client) {\n try {\n client.sendRaw(line);\n } catch {\n pending.push(line);\n void reconnect();\n }\n } else {\n pending.push(line);\n void reconnect();\n }\n }\n newlineIndex = stdinBuffer.indexOf('\\n');\n }\n };\n\n const shutdown = () => {\n if (closing) {\n return;\n }\n\n closing = true;\n stdin.off('data', handleChunk);\n stdin.off('end', shutdown);\n stdin.off('close', shutdown);\n client?.close();\n exit(0);\n };\n\n stdin.on('data', handleChunk);\n stdin.on('end', shutdown);\n stdin.on('close', shutdown);\n if ('resume' in stdin && typeof stdin.resume === 'function') {\n stdin.resume();\n }\n\n return { close: shutdown };\n}\n\nasync function guessAppName(): Promise<string> {\n if (process.env.COLLECTIVE_APP_NAME) {\n return process.env.COLLECTIVE_APP_NAME;\n }\n\n const arg = process.argv\n .map((value) => basename(value).toLowerCase())\n .find((value) => value && !['node', 'node.exe', 'mesh-shim', 'index.js'].includes(value));\n if (arg) {\n return arg.replace(/\\.(cmd|exe)$/i, '');\n }\n\n try {\n const result =\n process.platform === 'win32'\n ? await execFileAsync(\n 'powershell',\n ['-NoProfile', '-Command', `(Get-CimInstance Win32_Process -Filter \"ProcessId = ${process.ppid}\").Name`],\n { windowsHide: true },\n )\n : await execFileAsync('ps', ['-o', 'comm=', '-p', String(process.ppid)]);\n const name = basename(result.stdout.trim()).replace(/\\.(cmd|exe)$/i, '').toLowerCase();\n return name || 'unknown';\n } catch {\n return 'unknown';\n }\n}\n","import { spawn, spawnSync } from 'node:child_process';\nimport { createRequire } from 'node:module';\nimport net from 'node:net';\nimport { homedir, userInfo } from 'node:os';\nimport { resolve } from 'node:path';\nimport { setTimeout as delay } from 'node:timers/promises';\nimport { fileURLToPath } from 'node:url';\n\nconst require = createRequire(import.meta.url);\n\nexport interface LauncherOptions {\n ipcPath: string;\n pidFile: string;\n daemonBin: string;\n startupTimeoutMs: number;\n}\n\nconst LEGACY_WINDOWS_PIPE_PATH = '\\\\\\\\.\\\\pipe\\\\hivemind-collective';\n\nexport function getDefaultIpcPath(): string {\n return process.env.COLLECTIVE_IPC_PATH ??\n (process.platform === 'win32'\n ? `${LEGACY_WINDOWS_PIPE_PATH}-${sanitizePipeSegment(getCurrentUsername())}`\n : resolve(homedir(), '.hivemind-os/collective', 'mesh.sock'));\n}\n\nexport function getDefaultPidFile(): string {\n return process.env.COLLECTIVE_PID_FILE ?? resolve(homedir(), '.hivemind-os/collective', 'daemon.pid');\n}\n\nexport function resolveDaemonBin(): string {\n if (process.env.COLLECTIVE_DAEMON_BIN) {\n return process.env.COLLECTIVE_DAEMON_BIN;\n }\n\n try {\n return require.resolve('@hivemind-os/collective-daemon');\n } catch {\n if (commandExists('collective-daemon')) {\n return 'collective-daemon';\n }\n\n // Monorepo fallback (only works when running from packages/shim/)\n return fileURLToPath(new URL('../../daemon/dist/index.js', import.meta.url));\n }\n}\n\nexport async function isDaemonRunning(ipcPath: string): Promise<boolean> {\n return new Promise((resolvePromise) => {\n const socket = net.connect(ipcPath);\n const finish = (running: boolean) => {\n socket.removeAllListeners();\n socket.destroy();\n resolvePromise(running);\n };\n\n socket.once('connect', () => {\n finish(true);\n });\n socket.once('error', () => {\n finish(false);\n });\n });\n}\n\nexport async function ensureDaemonRunning(options: LauncherOptions): Promise<void> {\n if (await isDaemonRunning(options.ipcPath)) {\n return;\n }\n\n const command = options.daemonBin.endsWith('.js') ? process.execPath : options.daemonBin;\n const args = options.daemonBin.endsWith('.js') ? [options.daemonBin] : [];\n const child = spawn(command, args, { detached: true, stdio: ['ignore', 'ignore', 'pipe'] });\n let stderrOutput = '';\n child.stderr?.setEncoding('utf8');\n child.stderr?.on('data', (chunk: string) => {\n stderrOutput += chunk;\n });\n child.unref();\n child.stderr?.unref();\n\n let exited = false;\n let exitCode: number | null = null;\n child.once('exit', (code) => {\n exited = true;\n exitCode = code;\n });\n\n const deadline = Date.now() + options.startupTimeoutMs;\n while (Date.now() < deadline) {\n await delay(200);\n if (await isDaemonRunning(options.ipcPath)) {\n return;\n }\n if (exited) {\n const detail = stderrOutput.trim() ? `\\n${stderrOutput.trim()}` : '';\n throw new Error(`Daemon process exited with code ${exitCode} before IPC was ready.${detail}`);\n }\n }\n\n throw new Error(`Timed out waiting for daemon IPC at ${options.ipcPath} (pid file: ${options.pidFile})`);\n}\n\nfunction commandExists(command: string): boolean {\n const probe = process.platform === 'win32' ? 'where' : 'which';\n return spawnSync(probe, [command], { stdio: 'ignore' }).status === 0;\n}\n\nfunction getCurrentUsername(): string {\n try {\n return userInfo().username;\n } catch {\n return process.env.USERNAME ?? process.env.USER ?? 'unknown-user';\n }\n}\n\nfunction sanitizePipeSegment(value: string): string {\n const leaf = value.split(/[\\\\/]+/).filter(Boolean).at(-1) ?? value;\n const sanitized = leaf.trim().toLowerCase().replace(/[^a-z0-9_.-]+/g, '-').replace(/^-+|-+$/g, '');\n return sanitized || 'unknown-user';\n}\n","import { randomUUID } from 'node:crypto';\nimport net from 'node:net';\n\ntype MessageHandler = (message: object) => void;\ntype CloseHandler = () => void;\n\nexport class IpcClient {\n private socket?: net.Socket;\n private buffer = '';\n private readonly messageHandlers = new Set<MessageHandler>();\n private readonly closeHandlers = new Set<CloseHandler>();\n private helloId?: string;\n private helloWaiter?: { resolve: () => void; reject: (error: Error) => void };\n\n constructor(private readonly ipcPath: string) {}\n\n async connect(appName = 'unknown'): Promise<void> {\n this.close();\n const socket = await new Promise<net.Socket>((resolve, reject) => {\n const client = net.connect(this.ipcPath, () => {\n client.off('error', reject);\n resolve(client);\n });\n client.once('error', reject);\n });\n\n this.socket = socket;\n socket.setEncoding('utf8');\n socket.setNoDelay(true);\n socket.on('data', (chunk: string | Buffer) => {\n this.buffer += chunk.toString();\n this.drainBuffer();\n });\n socket.on('close', () => {\n this.handleClose();\n });\n socket.on('end', () => {\n this.handleClose();\n });\n socket.on('error', () => undefined);\n\n this.helloId = `shim-hello-${randomUUID()}`;\n await new Promise<void>((resolve, reject) => {\n this.helloWaiter = { resolve, reject };\n this.send({\n jsonrpc: '2.0',\n id: this.helloId as string,\n method: 'shim_hello',\n params: {\n appName,\n pid: process.pid,\n ...(process.env.COLLECTIVE_PROFILE ? { profile: process.env.COLLECTIVE_PROFILE } : {}),\n },\n });\n });\n }\n\n send(message: object): void {\n this.sendRaw(JSON.stringify(message));\n }\n\n sendRaw(line: string): void {\n const socket = this.socket;\n if (!socket || socket.destroyed) {\n throw new Error('IPC client is not connected');\n }\n\n socket.write(`${line.trimEnd()}\\n`);\n }\n\n onMessage(handler: (message: object) => void): void {\n this.messageHandlers.add(handler);\n }\n\n onClose(handler: () => void): void {\n this.closeHandlers.add(handler);\n }\n\n close(): void {\n const socket = this.socket;\n this.socket = undefined;\n this.buffer = '';\n this.rejectHello(new Error('IPC connection closed'));\n if (socket && !socket.destroyed) {\n socket.destroy();\n }\n }\n\n private drainBuffer(): void {\n let newlineIndex = this.buffer.indexOf('\\n');\n while (newlineIndex >= 0) {\n const line = this.buffer.slice(0, newlineIndex).trim();\n this.buffer = this.buffer.slice(newlineIndex + 1);\n if (line) {\n const message = JSON.parse(line) as Record<string, unknown>;\n if (this.helloWaiter && message.id === this.helloId) {\n const waiter = this.helloWaiter;\n this.helloWaiter = undefined;\n this.helloId = undefined;\n if (message.error && typeof message.error === 'object') {\n waiter.reject(new Error(String((message.error as { message?: unknown }).message ?? 'shim_hello failed')));\n } else {\n waiter.resolve();\n }\n } else {\n for (const handler of this.messageHandlers) {\n handler(message);\n }\n }\n }\n newlineIndex = this.buffer.indexOf('\\n');\n }\n }\n\n private handleClose(): void {\n if (!this.socket && !this.helloWaiter) {\n return;\n }\n\n this.socket = undefined;\n this.buffer = '';\n this.rejectHello(new Error('IPC connection closed'));\n for (const handler of this.closeHandlers) {\n handler();\n }\n }\n\n private rejectHello(error: Error): void {\n if (!this.helloWaiter) {\n return;\n }\n\n const waiter = this.helloWaiter;\n this.helloWaiter = undefined;\n this.helloId = undefined;\n waiter.reject(error);\n }\n}\n","#!/usr/bin/env node\n\nimport { startShim } from './bridge.js';\n\nstartShim().catch((err) => {\n process.stderr.write(`mesh-shim fatal: ${err.message}\\n`);\n process.exit(1);\n});\n"],"mappings":"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","names":["delay","require","net","resolve","delay"]}
1
+ {"version":3,"sources":["../src/bridge.ts","../src/daemon-launcher.ts","../src/ipc-client.ts","../src/index.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { basename } from 'node:path';\nimport { setTimeout as delay } from 'node:timers/promises';\nimport { promisify } from 'node:util';\n\nimport {\n ensureDaemonRunning,\n getDefaultIpcPath,\n getDefaultPidFile,\n resolveDaemonBin,\n stopDaemon,\n SHIM_VERSION,\n type LauncherOptions,\n} from './daemon-launcher.js';\nimport { IpcClient } from './ipc-client.js';\n\nconst execFileAsync = promisify(execFile);\n\nexport interface BridgeOptions {\n ipcPath?: string;\n pidFile?: string;\n daemonBin?: string;\n startupTimeoutMs?: number;\n appName?: string;\n stdin?: NodeJS.ReadableStream;\n stdout?: NodeJS.WritableStream;\n stderr?: NodeJS.WritableStream;\n exit?: (code: number) => void;\n ensureDaemon?: (options: LauncherOptions) => Promise<void>;\n stopDaemon?: (options: { pidFile: string; ipcPath: string }) => Promise<void>;\n}\n\nexport interface BridgeHandle {\n close(): void;\n}\n\nexport async function startShim(): Promise<void> {\n await createBridge();\n}\n\nexport async function createBridge(options: BridgeOptions = {}): Promise<BridgeHandle> {\n const stdin = options.stdin ?? process.stdin;\n const stdout = options.stdout ?? process.stdout;\n const stderr = options.stderr ?? process.stderr;\n const exit = options.exit ?? ((code: number) => process.exit(code));\n const ensureDaemon = options.ensureDaemon ?? ensureDaemonRunning;\n const stopDaemonFn = options.stopDaemon ?? stopDaemon;\n const launcherOptions: LauncherOptions = {\n ipcPath: options.ipcPath ?? getDefaultIpcPath(),\n pidFile: options.pidFile ?? getDefaultPidFile(),\n daemonBin: options.daemonBin ?? resolveDaemonBin(),\n startupTimeoutMs: options.startupTimeoutMs ?? 30_000,\n };\n const appName = options.appName ?? (await guessAppName());\n\n let client: IpcClient | undefined;\n let stdinBuffer = '';\n let closing = false;\n let reconnecting: Promise<void> | undefined;\n const pending: string[] = [];\n\n const writeError = (message: string) => {\n stdout.write(`${JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32000, message } })}\\n`);\n stderr.write(`mesh-shim: ${message}\\n`);\n };\n\n const flushPending = () => {\n if (!client) {\n return;\n }\n\n while (pending.length > 0) {\n try {\n client.sendRaw(pending[0] as string);\n pending.shift();\n } catch {\n return;\n }\n }\n };\n\n let upgraded = false;\n\n const connect = async () => {\n await ensureDaemon(launcherOptions);\n const deadline = Date.now() + launcherOptions.startupTimeoutMs;\n while (true) {\n const next = new IpcClient(launcherOptions.ipcPath);\n next.onMessage((message) => {\n stdout.write(`${JSON.stringify(message)}\\n`);\n });\n next.onClose(() => {\n if (!closing && client === next) {\n client = undefined;\n void reconnect();\n }\n });\n\n try {\n const hello = await next.connect(appName);\n\n // Version mismatch: stop old daemon and restart (once per session)\n if (!upgraded && hello.daemonVersion && hello.daemonVersion !== SHIM_VERSION) {\n upgraded = true;\n stderr.write(\n `mesh-shim: Upgrading daemon from v${hello.daemonVersion} to v${SHIM_VERSION}\\n`,\n );\n next.close();\n await stopDaemonFn({ pidFile: launcherOptions.pidFile, ipcPath: launcherOptions.ipcPath });\n await ensureDaemon(launcherOptions);\n continue;\n }\n\n client = next;\n flushPending();\n return;\n } catch (error) {\n next.close();\n if (Date.now() >= deadline) {\n throw error;\n }\n await delay(200);\n }\n }\n };\n\n const reconnect = async () => {\n if (reconnecting) {\n return reconnecting;\n }\n\n reconnecting = (async () => {\n try {\n await connect();\n } catch (error) {\n if (!closing) {\n closing = true;\n client?.close();\n writeError(`Unable to reach mesh daemon: ${(error as Error).message}`);\n exit(1);\n }\n } finally {\n reconnecting = undefined;\n }\n })();\n\n return reconnecting;\n };\n\n try {\n await connect();\n } catch (error) {\n closing = true;\n writeError(`Unable to start mesh daemon: ${(error as Error).message}`);\n exit(1);\n return { close: () => undefined };\n }\n\n if ('setEncoding' in stdin && typeof stdin.setEncoding === 'function') {\n stdin.setEncoding('utf8');\n }\n\n const handleChunk = (chunk: string | Buffer) => {\n stdinBuffer += chunk.toString();\n let newlineIndex = stdinBuffer.indexOf('\\n');\n while (newlineIndex >= 0) {\n const line = stdinBuffer.slice(0, newlineIndex).trim();\n stdinBuffer = stdinBuffer.slice(newlineIndex + 1);\n if (line) {\n if (client) {\n try {\n client.sendRaw(line);\n } catch {\n pending.push(line);\n void reconnect();\n }\n } else {\n pending.push(line);\n void reconnect();\n }\n }\n newlineIndex = stdinBuffer.indexOf('\\n');\n }\n };\n\n const shutdown = () => {\n if (closing) {\n return;\n }\n\n closing = true;\n stdin.off('data', handleChunk);\n stdin.off('end', shutdown);\n stdin.off('close', shutdown);\n client?.close();\n exit(0);\n };\n\n stdin.on('data', handleChunk);\n stdin.on('end', shutdown);\n stdin.on('close', shutdown);\n if ('resume' in stdin && typeof stdin.resume === 'function') {\n stdin.resume();\n }\n\n return { close: shutdown };\n}\n\nasync function guessAppName(): Promise<string> {\n if (process.env.COLLECTIVE_APP_NAME) {\n return process.env.COLLECTIVE_APP_NAME;\n }\n\n const arg = process.argv\n .map((value) => basename(value).toLowerCase())\n .find((value) => value && !['node', 'node.exe', 'mesh-shim', 'index.js'].includes(value));\n if (arg) {\n return arg.replace(/\\.(cmd|exe)$/i, '');\n }\n\n try {\n const result =\n process.platform === 'win32'\n ? await execFileAsync(\n 'powershell',\n ['-NoProfile', '-Command', `(Get-CimInstance Win32_Process -Filter \"ProcessId = ${process.ppid}\").Name`],\n { windowsHide: true },\n )\n : await execFileAsync('ps', ['-o', 'comm=', '-p', String(process.ppid)]);\n const name = basename(result.stdout.trim()).replace(/\\.(cmd|exe)$/i, '').toLowerCase();\n return name || 'unknown';\n } catch {\n return 'unknown';\n }\n}\n","import { spawn, spawnSync } from 'node:child_process';\nimport { readFileSync, unlinkSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport net from 'node:net';\nimport { homedir, userInfo } from 'node:os';\nimport { resolve } from 'node:path';\nimport { setTimeout as delay } from 'node:timers/promises';\nimport { fileURLToPath } from 'node:url';\n\nconst require = createRequire(import.meta.url);\nconst { version: SHIM_VERSION } = require('../package.json') as { version: string };\n\nexport { SHIM_VERSION };\n\nexport interface LauncherOptions {\n ipcPath: string;\n pidFile: string;\n daemonBin: string;\n startupTimeoutMs: number;\n}\n\nconst LEGACY_WINDOWS_PIPE_PATH = '\\\\\\\\.\\\\pipe\\\\hivemind-collective';\n\nexport function getDefaultIpcPath(): string {\n return process.env.COLLECTIVE_IPC_PATH ??\n (process.platform === 'win32'\n ? `${LEGACY_WINDOWS_PIPE_PATH}-${sanitizePipeSegment(getCurrentUsername())}`\n : resolve(homedir(), '.hivemind-os/collective', 'mesh.sock'));\n}\n\nexport function getDefaultPidFile(): string {\n return process.env.COLLECTIVE_PID_FILE ?? resolve(homedir(), '.hivemind-os/collective', 'daemon.pid');\n}\n\nexport function resolveDaemonBin(): string {\n if (process.env.COLLECTIVE_DAEMON_BIN) {\n return process.env.COLLECTIVE_DAEMON_BIN;\n }\n\n try {\n return require.resolve('@hivemind-os/collective-daemon');\n } catch {\n if (commandExists('collective-daemon')) {\n return 'collective-daemon';\n }\n\n // Monorepo fallback (only works when running from packages/shim/)\n return fileURLToPath(new URL('../../daemon/dist/index.js', import.meta.url));\n }\n}\n\nexport async function isDaemonRunning(ipcPath: string): Promise<boolean> {\n return new Promise((resolvePromise) => {\n const socket = net.connect(ipcPath);\n const finish = (running: boolean) => {\n socket.removeAllListeners();\n socket.destroy();\n resolvePromise(running);\n };\n\n socket.once('connect', () => {\n finish(true);\n });\n socket.once('error', () => {\n finish(false);\n });\n });\n}\n\nexport async function ensureDaemonRunning(options: LauncherOptions): Promise<void> {\n if (await isDaemonRunning(options.ipcPath)) {\n return;\n }\n\n const command = options.daemonBin.endsWith('.js') ? process.execPath : options.daemonBin;\n const args = options.daemonBin.endsWith('.js') ? [options.daemonBin] : [];\n const child = spawn(command, args, { detached: true, stdio: ['ignore', 'ignore', 'pipe'] });\n let stderrOutput = '';\n child.stderr?.setEncoding('utf8');\n child.stderr?.on('data', (chunk: string) => {\n stderrOutput += chunk;\n });\n child.unref();\n child.stderr?.unref();\n\n let exited = false;\n let exitCode: number | null = null;\n child.once('exit', (code) => {\n exited = true;\n exitCode = code;\n });\n\n const deadline = Date.now() + options.startupTimeoutMs;\n while (Date.now() < deadline) {\n await delay(200);\n if (await isDaemonRunning(options.ipcPath)) {\n return;\n }\n if (exited) {\n const detail = stderrOutput.trim() ? `\\n${stderrOutput.trim()}` : '';\n throw new Error(`Daemon process exited with code ${exitCode} before IPC was ready.${detail}`);\n }\n }\n\n throw new Error(`Timed out waiting for daemon IPC at ${options.ipcPath} (pid file: ${options.pidFile})`);\n}\n\nfunction commandExists(command: string): boolean {\n const probe = process.platform === 'win32' ? 'where' : 'which';\n return spawnSync(probe, [command], { stdio: 'ignore' }).status === 0;\n}\n\nfunction getCurrentUsername(): string {\n try {\n return userInfo().username;\n } catch {\n return process.env.USERNAME ?? process.env.USER ?? 'unknown-user';\n }\n}\n\nfunction sanitizePipeSegment(value: string): string {\n const leaf = value.split(/[\\\\/]+/).filter(Boolean).at(-1) ?? value;\n const sanitized = leaf.trim().toLowerCase().replace(/[^a-z0-9_.-]+/g, '-').replace(/^-+|-+$/g, '');\n return sanitized || 'unknown-user';\n}\n\n/**\n * Stop the running daemon by reading its PID file and sending SIGTERM.\n * Waits for the IPC socket to go down before returning.\n */\nexport async function stopDaemon(options: { pidFile: string; ipcPath: string; timeoutMs?: number }): Promise<void> {\n const { pidFile, ipcPath, timeoutMs = 10_000 } = options;\n\n let pid: number;\n try {\n pid = parseInt(readFileSync(pidFile, 'utf8').trim(), 10);\n } catch {\n // No PID file — daemon may already be dead\n return;\n }\n\n if (isNaN(pid)) {\n try { unlinkSync(pidFile); } catch { /* ignore */ }\n return;\n }\n\n // Send SIGTERM (on Windows, process.kill sends a termination signal)\n try {\n process.kill(pid, 'SIGTERM');\n } catch {\n // Process already gone\n try { unlinkSync(pidFile); } catch { /* ignore */ }\n return;\n }\n\n // Wait for the IPC socket to go down\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (!(await isDaemonRunning(ipcPath))) {\n return;\n }\n await delay(200);\n }\n\n // Force kill if still alive\n try {\n process.kill(pid, 'SIGKILL');\n } catch { /* already gone */ }\n try { unlinkSync(pidFile); } catch { /* ignore */ }\n}\n","import { createRequire } from 'node:module';\nimport { randomUUID } from 'node:crypto';\nimport net from 'node:net';\n\nconst require = createRequire(import.meta.url);\nconst { version: SHIM_VERSION } = require('../package.json') as { version: string };\n\ntype MessageHandler = (message: object) => void;\ntype CloseHandler = () => void;\n\nexport interface HelloResult {\n daemonVersion?: string;\n connectionId?: string;\n}\n\nexport class IpcClient {\n private socket?: net.Socket;\n private buffer = '';\n private readonly messageHandlers = new Set<MessageHandler>();\n private readonly closeHandlers = new Set<CloseHandler>();\n private helloId?: string;\n private helloWaiter?: { resolve: (result: HelloResult) => void; reject: (error: Error) => void };\n\n constructor(private readonly ipcPath: string) {}\n\n async connect(appName = 'unknown'): Promise<HelloResult> {\n this.close();\n const socket = await new Promise<net.Socket>((resolve, reject) => {\n const client = net.connect(this.ipcPath, () => {\n client.off('error', reject);\n resolve(client);\n });\n client.once('error', reject);\n });\n\n this.socket = socket;\n socket.setEncoding('utf8');\n socket.setNoDelay(true);\n socket.on('data', (chunk: string | Buffer) => {\n this.buffer += chunk.toString();\n this.drainBuffer();\n });\n socket.on('close', () => {\n this.handleClose();\n });\n socket.on('end', () => {\n this.handleClose();\n });\n socket.on('error', () => undefined);\n\n this.helloId = `shim-hello-${randomUUID()}`;\n return new Promise<HelloResult>((resolve, reject) => {\n this.helloWaiter = { resolve, reject };\n this.send({\n jsonrpc: '2.0',\n id: this.helloId as string,\n method: 'shim_hello',\n params: {\n appName,\n pid: process.pid,\n shimVersion: SHIM_VERSION,\n ...(process.env.COLLECTIVE_PROFILE ? { profile: process.env.COLLECTIVE_PROFILE } : {}),\n },\n });\n });\n }\n\n send(message: object): void {\n this.sendRaw(JSON.stringify(message));\n }\n\n sendRaw(line: string): void {\n const socket = this.socket;\n if (!socket || socket.destroyed) {\n throw new Error('IPC client is not connected');\n }\n\n socket.write(`${line.trimEnd()}\\n`);\n }\n\n onMessage(handler: (message: object) => void): void {\n this.messageHandlers.add(handler);\n }\n\n onClose(handler: () => void): void {\n this.closeHandlers.add(handler);\n }\n\n close(): void {\n const socket = this.socket;\n this.socket = undefined;\n this.buffer = '';\n this.rejectHello(new Error('IPC connection closed'));\n if (socket && !socket.destroyed) {\n socket.destroy();\n }\n }\n\n private drainBuffer(): void {\n let newlineIndex = this.buffer.indexOf('\\n');\n while (newlineIndex >= 0) {\n const line = this.buffer.slice(0, newlineIndex).trim();\n this.buffer = this.buffer.slice(newlineIndex + 1);\n if (line) {\n const message = JSON.parse(line) as Record<string, unknown>;\n if (this.helloWaiter && message.id === this.helloId) {\n const waiter = this.helloWaiter;\n this.helloWaiter = undefined;\n this.helloId = undefined;\n if (message.error && typeof message.error === 'object') {\n waiter.reject(new Error(String((message.error as { message?: unknown }).message ?? 'shim_hello failed')));\n } else {\n const result = (message.result ?? {}) as Record<string, unknown>;\n waiter.resolve({\n daemonVersion: typeof result.daemonVersion === 'string' ? result.daemonVersion : undefined,\n connectionId: typeof result.connectionId === 'string' ? result.connectionId : undefined,\n });\n }\n } else {\n for (const handler of this.messageHandlers) {\n handler(message);\n }\n }\n }\n newlineIndex = this.buffer.indexOf('\\n');\n }\n }\n\n private handleClose(): void {\n if (!this.socket && !this.helloWaiter) {\n return;\n }\n\n this.socket = undefined;\n this.buffer = '';\n this.rejectHello(new Error('IPC connection closed'));\n for (const handler of this.closeHandlers) {\n handler();\n }\n }\n\n private rejectHello(error: Error): void {\n if (!this.helloWaiter) {\n return;\n }\n\n const waiter = this.helloWaiter;\n this.helloWaiter = undefined;\n this.helloId = undefined;\n waiter.reject(error);\n }\n}\n","#!/usr/bin/env node\n\nimport { startShim } from './bridge.js';\n\nstartShim().catch((err) => {\n process.stderr.write(`mesh-shim fatal: ${err.message}\\n`);\n process.exit(1);\n});\n"],"mappings":"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","names":["delay","require","require","createRequire","net","require","SHIM_VERSION","resolve","delay"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hivemind-os/collective-shim",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "dependencies": {
11
11
  "pino": "^9.0.0",
12
- "@hivemind-os/collective-daemon": "0.2.4"
12
+ "@hivemind-os/collective-daemon": "0.2.6"
13
13
  },
14
14
  "devDependencies": {
15
15
  "vitest": "^3.0.0",
package/src/bridge.ts CHANGED
@@ -8,6 +8,8 @@ import {
8
8
  getDefaultIpcPath,
9
9
  getDefaultPidFile,
10
10
  resolveDaemonBin,
11
+ stopDaemon,
12
+ SHIM_VERSION,
11
13
  type LauncherOptions,
12
14
  } from './daemon-launcher.js';
13
15
  import { IpcClient } from './ipc-client.js';
@@ -25,6 +27,7 @@ export interface BridgeOptions {
25
27
  stderr?: NodeJS.WritableStream;
26
28
  exit?: (code: number) => void;
27
29
  ensureDaemon?: (options: LauncherOptions) => Promise<void>;
30
+ stopDaemon?: (options: { pidFile: string; ipcPath: string }) => Promise<void>;
28
31
  }
29
32
 
30
33
  export interface BridgeHandle {
@@ -41,6 +44,7 @@ export async function createBridge(options: BridgeOptions = {}): Promise<BridgeH
41
44
  const stderr = options.stderr ?? process.stderr;
42
45
  const exit = options.exit ?? ((code: number) => process.exit(code));
43
46
  const ensureDaemon = options.ensureDaemon ?? ensureDaemonRunning;
47
+ const stopDaemonFn = options.stopDaemon ?? stopDaemon;
44
48
  const launcherOptions: LauncherOptions = {
45
49
  ipcPath: options.ipcPath ?? getDefaultIpcPath(),
46
50
  pidFile: options.pidFile ?? getDefaultPidFile(),
@@ -75,6 +79,8 @@ export async function createBridge(options: BridgeOptions = {}): Promise<BridgeH
75
79
  }
76
80
  };
77
81
 
82
+ let upgraded = false;
83
+
78
84
  const connect = async () => {
79
85
  await ensureDaemon(launcherOptions);
80
86
  const deadline = Date.now() + launcherOptions.startupTimeoutMs;
@@ -91,7 +97,20 @@ export async function createBridge(options: BridgeOptions = {}): Promise<BridgeH
91
97
  });
92
98
 
93
99
  try {
94
- await next.connect(appName);
100
+ const hello = await next.connect(appName);
101
+
102
+ // Version mismatch: stop old daemon and restart (once per session)
103
+ if (!upgraded && hello.daemonVersion && hello.daemonVersion !== SHIM_VERSION) {
104
+ upgraded = true;
105
+ stderr.write(
106
+ `mesh-shim: Upgrading daemon from v${hello.daemonVersion} to v${SHIM_VERSION}\n`,
107
+ );
108
+ next.close();
109
+ await stopDaemonFn({ pidFile: launcherOptions.pidFile, ipcPath: launcherOptions.ipcPath });
110
+ await ensureDaemon(launcherOptions);
111
+ continue;
112
+ }
113
+
95
114
  client = next;
96
115
  flushPending();
97
116
  return;
@@ -1,4 +1,5 @@
1
1
  import { spawn, spawnSync } from 'node:child_process';
2
+ import { readFileSync, unlinkSync } from 'node:fs';
2
3
  import { createRequire } from 'node:module';
3
4
  import net from 'node:net';
4
5
  import { homedir, userInfo } from 'node:os';
@@ -7,6 +8,9 @@ import { setTimeout as delay } from 'node:timers/promises';
7
8
  import { fileURLToPath } from 'node:url';
8
9
 
9
10
  const require = createRequire(import.meta.url);
11
+ const { version: SHIM_VERSION } = require('../package.json') as { version: string };
12
+
13
+ export { SHIM_VERSION };
10
14
 
11
15
  export interface LauncherOptions {
12
16
  ipcPath: string;
@@ -119,3 +123,48 @@ function sanitizePipeSegment(value: string): string {
119
123
  const sanitized = leaf.trim().toLowerCase().replace(/[^a-z0-9_.-]+/g, '-').replace(/^-+|-+$/g, '');
120
124
  return sanitized || 'unknown-user';
121
125
  }
126
+
127
+ /**
128
+ * Stop the running daemon by reading its PID file and sending SIGTERM.
129
+ * Waits for the IPC socket to go down before returning.
130
+ */
131
+ export async function stopDaemon(options: { pidFile: string; ipcPath: string; timeoutMs?: number }): Promise<void> {
132
+ const { pidFile, ipcPath, timeoutMs = 10_000 } = options;
133
+
134
+ let pid: number;
135
+ try {
136
+ pid = parseInt(readFileSync(pidFile, 'utf8').trim(), 10);
137
+ } catch {
138
+ // No PID file — daemon may already be dead
139
+ return;
140
+ }
141
+
142
+ if (isNaN(pid)) {
143
+ try { unlinkSync(pidFile); } catch { /* ignore */ }
144
+ return;
145
+ }
146
+
147
+ // Send SIGTERM (on Windows, process.kill sends a termination signal)
148
+ try {
149
+ process.kill(pid, 'SIGTERM');
150
+ } catch {
151
+ // Process already gone
152
+ try { unlinkSync(pidFile); } catch { /* ignore */ }
153
+ return;
154
+ }
155
+
156
+ // Wait for the IPC socket to go down
157
+ const deadline = Date.now() + timeoutMs;
158
+ while (Date.now() < deadline) {
159
+ if (!(await isDaemonRunning(ipcPath))) {
160
+ return;
161
+ }
162
+ await delay(200);
163
+ }
164
+
165
+ // Force kill if still alive
166
+ try {
167
+ process.kill(pid, 'SIGKILL');
168
+ } catch { /* already gone */ }
169
+ try { unlinkSync(pidFile); } catch { /* ignore */ }
170
+ }
package/src/ipc-client.ts CHANGED
@@ -1,20 +1,29 @@
1
+ import { createRequire } from 'node:module';
1
2
  import { randomUUID } from 'node:crypto';
2
3
  import net from 'node:net';
3
4
 
5
+ const require = createRequire(import.meta.url);
6
+ const { version: SHIM_VERSION } = require('../package.json') as { version: string };
7
+
4
8
  type MessageHandler = (message: object) => void;
5
9
  type CloseHandler = () => void;
6
10
 
11
+ export interface HelloResult {
12
+ daemonVersion?: string;
13
+ connectionId?: string;
14
+ }
15
+
7
16
  export class IpcClient {
8
17
  private socket?: net.Socket;
9
18
  private buffer = '';
10
19
  private readonly messageHandlers = new Set<MessageHandler>();
11
20
  private readonly closeHandlers = new Set<CloseHandler>();
12
21
  private helloId?: string;
13
- private helloWaiter?: { resolve: () => void; reject: (error: Error) => void };
22
+ private helloWaiter?: { resolve: (result: HelloResult) => void; reject: (error: Error) => void };
14
23
 
15
24
  constructor(private readonly ipcPath: string) {}
16
25
 
17
- async connect(appName = 'unknown'): Promise<void> {
26
+ async connect(appName = 'unknown'): Promise<HelloResult> {
18
27
  this.close();
19
28
  const socket = await new Promise<net.Socket>((resolve, reject) => {
20
29
  const client = net.connect(this.ipcPath, () => {
@@ -40,7 +49,7 @@ export class IpcClient {
40
49
  socket.on('error', () => undefined);
41
50
 
42
51
  this.helloId = `shim-hello-${randomUUID()}`;
43
- await new Promise<void>((resolve, reject) => {
52
+ return new Promise<HelloResult>((resolve, reject) => {
44
53
  this.helloWaiter = { resolve, reject };
45
54
  this.send({
46
55
  jsonrpc: '2.0',
@@ -49,6 +58,7 @@ export class IpcClient {
49
58
  params: {
50
59
  appName,
51
60
  pid: process.pid,
61
+ shimVersion: SHIM_VERSION,
52
62
  ...(process.env.COLLECTIVE_PROFILE ? { profile: process.env.COLLECTIVE_PROFILE } : {}),
53
63
  },
54
64
  });
@@ -100,7 +110,11 @@ export class IpcClient {
100
110
  if (message.error && typeof message.error === 'object') {
101
111
  waiter.reject(new Error(String((message.error as { message?: unknown }).message ?? 'shim_hello failed')));
102
112
  } else {
103
- waiter.resolve();
113
+ const result = (message.result ?? {}) as Record<string, unknown>;
114
+ waiter.resolve({
115
+ daemonVersion: typeof result.daemonVersion === 'string' ? result.daemonVersion : undefined,
116
+ connectionId: typeof result.connectionId === 'string' ? result.connectionId : undefined,
117
+ });
104
118
  }
105
119
  } else {
106
120
  for (const handler of this.messageHandlers) {
@@ -80,6 +80,23 @@ class NdjsonReader {
80
80
  }
81
81
  }
82
82
 
83
+ class NdjsonLineReader {
84
+ private collected: string[] = [];
85
+
86
+ constructor(stream: NodeJS.ReadableStream) {
87
+ if ('setEncoding' in stream && typeof stream.setEncoding === 'function') {
88
+ stream.setEncoding('utf8');
89
+ }
90
+ stream.on('data', (chunk: string | Buffer) => {
91
+ this.collected.push(chunk.toString());
92
+ });
93
+ }
94
+
95
+ lines(): string[] {
96
+ return [...this.collected];
97
+ }
98
+ }
99
+
83
100
  class MockIpcServer {
84
101
  private server?: net.Server;
85
102
  private socket?: net.Socket;
@@ -284,4 +301,190 @@ describe('bridge', () => {
284
301
  expect(exit).toHaveBeenCalledWith(0);
285
302
  expect(ensureDaemon).toHaveBeenCalledTimes(2);
286
303
  });
304
+
305
+ it('restarts daemon when version mismatch is detected', async () => {
306
+ const dir = await createTestDir();
307
+ const ipcPath = createIpcPath(dir);
308
+ const pidFile = resolve(dir, 'daemon.pid');
309
+
310
+ // Phase 1: old daemon with mismatched version
311
+ let oldServer = new MockIpcServer(ipcPath);
312
+ await oldServer.start();
313
+
314
+ const stopDaemonMock = vi.fn(async () => {
315
+ // Simulate stopDaemon: tear down the old server
316
+ await oldServer.stop();
317
+ });
318
+
319
+ // Track ensureDaemon calls — on the 2nd call (after upgrade), start the "new" daemon
320
+ let newServer: MockIpcServer | undefined;
321
+ let newServerReady: () => void;
322
+ const newServerPromise = new Promise<void>((r) => { newServerReady = r; });
323
+ const ensureDaemon = vi.fn(async () => {
324
+ if (ensureDaemon.mock.calls.length > 1) {
325
+ // Start the "new" daemon after stopDaemon cleaned up the old one
326
+ newServer = new MockIpcServer(ipcPath);
327
+ await newServer.start();
328
+ newServerReady();
329
+ }
330
+ });
331
+
332
+ const stdin = new PassThrough();
333
+ const stdout = new PassThrough();
334
+ const stderr = new PassThrough();
335
+ const stderrReader = new NdjsonLineReader(stderr);
336
+ const exit = vi.fn();
337
+
338
+ const { SHIM_VERSION: shimVersion } = await import('../src/daemon-launcher.js');
339
+
340
+ const bridgePromise = createBridge({
341
+ ipcPath,
342
+ pidFile,
343
+ daemonBin: 'mesh-daemon',
344
+ appName: 'claude-desktop',
345
+ stdin,
346
+ stdout,
347
+ stderr,
348
+ exit,
349
+ ensureDaemon,
350
+ stopDaemon: stopDaemonMock,
351
+ startupTimeoutMs: 5_000,
352
+ });
353
+
354
+ // Old daemon receives hello, responds with OLD version
355
+ const oldHello = await oldServer.nextMessage();
356
+ expect(oldHello).toMatchObject({ method: 'shim_hello' });
357
+ oldServer.send({
358
+ jsonrpc: '2.0',
359
+ id: oldHello.id,
360
+ result: { acknowledged: true, connectionId: 'old', daemonVersion: '0.0.1' },
361
+ });
362
+
363
+ // After mismatch, shim should call stopDaemon and ensureDaemon again
364
+ // Wait for the new server to be ready
365
+ await newServerPromise;
366
+ // New daemon receives hello, responds with CURRENT version
367
+ const newHello = await newServer!.nextMessage();
368
+ expect(newHello).toMatchObject({ method: 'shim_hello' });
369
+ newServer!.send({
370
+ jsonrpc: '2.0',
371
+ id: newHello.id,
372
+ result: { acknowledged: true, connectionId: 'new', daemonVersion: shimVersion },
373
+ });
374
+
375
+ const bridge = await bridgePromise;
376
+
377
+ // Verify the bridge works after upgrade
378
+ stdin.write('{"jsonrpc":"2.0","id":"test","method":"ping"}\n');
379
+ const forwarded = await newServer!.nextMessage();
380
+ expect(forwarded).toEqual({ jsonrpc: '2.0', id: 'test', method: 'ping' });
381
+
382
+ // Verify stopDaemon was called
383
+ expect(stopDaemonMock).toHaveBeenCalledTimes(1);
384
+ // ensureDaemon called twice: initial + after upgrade
385
+ expect(ensureDaemon).toHaveBeenCalledTimes(2);
386
+
387
+ // Verify stderr logged the upgrade
388
+ const stderrOutput = stderrReader.lines().join('');
389
+ expect(stderrOutput).toContain('Upgrading daemon from v0.0.1');
390
+ expect(stderrOutput).toContain(shimVersion);
391
+
392
+ bridge.close();
393
+ expect(exit).toHaveBeenCalledWith(0);
394
+ });
395
+
396
+ it('accepts matching daemon version without restart', async () => {
397
+ const dir = await createTestDir();
398
+ const ipcPath = createIpcPath(dir);
399
+
400
+ const server = new MockIpcServer(ipcPath);
401
+ await server.start();
402
+
403
+ const stopDaemonMock = vi.fn();
404
+ const ensureDaemon = vi.fn(async () => undefined);
405
+
406
+ const stdin = new PassThrough();
407
+ const stdout = new PassThrough();
408
+ const stderr = new PassThrough();
409
+ const exit = vi.fn();
410
+
411
+ const { SHIM_VERSION: shimVersion } = await import('../src/daemon-launcher.js');
412
+
413
+ const bridgePromise = createBridge({
414
+ ipcPath,
415
+ pidFile: resolve(dir, 'daemon.pid'),
416
+ daemonBin: 'mesh-daemon',
417
+ appName: 'claude-desktop',
418
+ stdin,
419
+ stdout,
420
+ stderr,
421
+ exit,
422
+ ensureDaemon,
423
+ stopDaemon: stopDaemonMock,
424
+ });
425
+
426
+ const hello = await server.nextMessage();
427
+ server.send({
428
+ jsonrpc: '2.0',
429
+ id: hello.id,
430
+ result: { acknowledged: true, connectionId: 'ok', daemonVersion: shimVersion },
431
+ });
432
+
433
+ const bridge = await bridgePromise;
434
+
435
+ // Should NOT have called stopDaemon
436
+ expect(stopDaemonMock).not.toHaveBeenCalled();
437
+ expect(ensureDaemon).toHaveBeenCalledTimes(1);
438
+
439
+ bridge.close();
440
+ expect(exit).toHaveBeenCalledWith(0);
441
+ });
442
+
443
+ it('skips version check for old daemons without daemonVersion', async () => {
444
+ const dir = await createTestDir();
445
+ const ipcPath = createIpcPath(dir);
446
+
447
+ const server = new MockIpcServer(ipcPath);
448
+ await server.start();
449
+
450
+ const stopDaemonMock = vi.fn();
451
+ const ensureDaemon = vi.fn(async () => undefined);
452
+
453
+ const stdin = new PassThrough();
454
+ const stdout = new PassThrough();
455
+ const stderr = new PassThrough();
456
+ const exit = vi.fn();
457
+
458
+ const bridgePromise = createBridge({
459
+ ipcPath,
460
+ pidFile: resolve(dir, 'daemon.pid'),
461
+ daemonBin: 'mesh-daemon',
462
+ appName: 'claude-desktop',
463
+ stdin,
464
+ stdout,
465
+ stderr,
466
+ exit,
467
+ ensureDaemon,
468
+ stopDaemon: stopDaemonMock,
469
+ });
470
+
471
+ const hello = await server.nextMessage();
472
+ // Old daemon that doesn't send daemonVersion
473
+ server.send({
474
+ jsonrpc: '2.0',
475
+ id: hello.id,
476
+ result: { acknowledged: true, connectionId: 'legacy' },
477
+ });
478
+
479
+ const bridge = await bridgePromise;
480
+
481
+ // Old daemons without version should be force-upgraded
482
+ // (daemonVersion is undefined, which !== SHIM_VERSION, but
483
+ // we only upgrade when daemonVersion is present and differs)
484
+ expect(stopDaemonMock).not.toHaveBeenCalled();
485
+ expect(ensureDaemon).toHaveBeenCalledTimes(1);
486
+
487
+ bridge.close();
488
+ expect(exit).toHaveBeenCalledWith(0);
489
+ });
287
490
  });