@geminilight/mindos 0.6.5 → 0.6.7

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.
@@ -42,26 +42,40 @@ function killByPort(port: number) {
42
42
  *
43
43
  * Unlike /api/restart which restarts the entire MindOS (Web + MCP),
44
44
  * this endpoint only restarts the MCP server. The Web UI stays up.
45
+ *
46
+ * When running under Desktop's ProcessManager, the crash handler
47
+ * auto-respawns MCP when it exits. We wait for the port to free,
48
+ * then spawn only if nothing re-bound (i.e. CLI mode with no crash
49
+ * handler). This avoids spawning a duplicate that races for the port.
45
50
  */
46
51
  export async function POST() {
47
52
  try {
48
53
  const cfg = readConfig();
49
- const mcpPort = (cfg.mcpPort as number) ?? 8781;
54
+ const mcpPort = Number(process.env.MINDOS_MCP_PORT) || Number(cfg.mcpPort) || 8781;
50
55
  const webPort = process.env.MINDOS_WEB_PORT || '3456';
51
- const authToken = cfg.authToken as string | undefined;
56
+ const authToken = process.env.AUTH_TOKEN || (cfg.authToken as string | undefined);
57
+ const managed = process.env.MINDOS_MANAGED === '1';
52
58
 
53
59
  // Step 1: Kill process on MCP port
54
60
  killByPort(mcpPort);
55
61
 
56
- // Step 2: Wait briefly for port to free
57
- await new Promise(r => setTimeout(r, 1000));
62
+ if (managed) {
63
+ // Desktop ProcessManager will auto-respawn MCP via its crash handler.
64
+ return NextResponse.json({ ok: true, port: mcpPort, note: 'ProcessManager will respawn' });
65
+ }
66
+
67
+ // Step 2 (CLI mode only): Wait for port to free, then spawn a new MCP
68
+ const portFree = await waitForPortFree(mcpPort, 5000);
69
+ if (!portFree) {
70
+ return NextResponse.json({ error: `MCP port ${mcpPort} still in use after kill` }, { status: 500 });
71
+ }
58
72
 
59
- // Step 3: Spawn new MCP server
60
73
  const root = process.env.MINDOS_PROJECT_ROOT || resolve(process.cwd(), '..');
61
74
  const mcpDir = resolve(root, 'mcp');
62
75
 
63
- if (!existsSync(resolve(mcpDir, 'node_modules'))) {
64
- return NextResponse.json({ error: 'MCP dependencies not installed' }, { status: 500 });
76
+ const mcpBundle = resolve(mcpDir, 'dist', 'index.cjs');
77
+ if (!existsSync(mcpBundle)) {
78
+ return NextResponse.json({ error: 'MCP bundle not found — reinstall @geminilight/mindos' }, { status: 500 });
65
79
  }
66
80
 
67
81
  const env: NodeJS.ProcessEnv = {
@@ -73,7 +87,7 @@ export async function POST() {
73
87
  ...(authToken ? { AUTH_TOKEN: authToken } : {}),
74
88
  };
75
89
 
76
- const child = spawn('npx', ['tsx', 'src/index.ts'], {
90
+ const child = spawn(process.execPath, [mcpBundle], {
77
91
  cwd: mcpDir,
78
92
  detached: true,
79
93
  stdio: 'ignore',
@@ -87,3 +101,22 @@ export async function POST() {
87
101
  return NextResponse.json({ error: message }, { status: 500 });
88
102
  }
89
103
  }
104
+
105
+ function isPortInUse(port: number): Promise<boolean> {
106
+ return new Promise((res) => {
107
+ const net = require('net');
108
+ const server = net.createServer();
109
+ server.once('error', () => res(true));
110
+ server.once('listening', () => { server.close(); res(false); });
111
+ server.listen(port, '127.0.0.1');
112
+ });
113
+ }
114
+
115
+ async function waitForPortFree(port: number, timeoutMs: number): Promise<boolean> {
116
+ const start = Date.now();
117
+ while (Date.now() - start < timeoutMs) {
118
+ if (!(await isPortInUse(port))) return true;
119
+ await new Promise(r => setTimeout(r, 300));
120
+ }
121
+ return false;
122
+ }
@@ -63,7 +63,7 @@ export async function GET() {
63
63
  const kbStats = getCachedKbStats(mindRoot);
64
64
 
65
65
  // Detect MCP status from environment / config
66
- const mcpPort = Number(process.env.MCP_PORT) || 3457;
66
+ const mcpPort = Number(process.env.MINDOS_MCP_PORT) || Number(process.env.MCP_PORT) || 8781;
67
67
 
68
68
  return NextResponse.json({
69
69
  system: {
package/bin/cli.js CHANGED
@@ -433,7 +433,7 @@ const commands = {
433
433
  if (process.env.MCP_TRANSPORT === 'http') {
434
434
  process.env.MCP_PORT = process.env.MINDOS_MCP_PORT || '8781';
435
435
  }
436
- run(`npx tsx src/index.ts`, resolve(ROOT, 'mcp'));
436
+ run(`node dist/index.cjs`, resolve(ROOT, 'mcp'));
437
437
  },
438
438
 
439
439
  // ── stop / restart ─────────────────────────────────────────────────────────
@@ -1,59 +1,21 @@
1
- import { execSync, spawn } from 'node:child_process';
2
- import { existsSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
- import { arch, platform } from 'node:os';
5
4
  import { ROOT, CONFIG_PATH } from './constants.js';
6
- import { bold, red, yellow } from './colors.js';
7
- import { npmInstall } from './utils.js';
8
-
9
- /**
10
- * If mcp/node_modules was installed on a different platform (e.g. Linux CI → macOS user),
11
- * native packages like esbuild will crash. Detect via stamp or @esbuild heuristics, then reinstall.
12
- */
13
- function ensureMcpNativeDeps() {
14
- const mcpDir = resolve(ROOT, 'mcp');
15
- const nm = resolve(mcpDir, 'node_modules');
16
- if (!existsSync(nm)) return;
17
-
18
- const host = `${platform()}-${arch()}`;
19
- const stamp = resolve(mcpDir, '.mindos-npm-ci-platform');
20
- let needsReinstall = false;
21
-
22
- if (existsSync(stamp)) {
23
- try {
24
- needsReinstall = readFileSync(stamp, 'utf-8').trim() !== host;
25
- } catch { /* fall through */ }
26
- } else {
27
- const esbuildDir = resolve(nm, '@esbuild');
28
- if (existsSync(esbuildDir)) {
29
- try {
30
- const names = readdirSync(esbuildDir);
31
- if (names.length > 0 && !names.includes(host)) needsReinstall = true;
32
- } catch { /* ignore */ }
33
- }
34
- }
35
-
36
- if (!needsReinstall) return;
37
-
38
- console.log(yellow('MCP dependencies were built for another platform — reinstalling...'));
39
- rmSync(nm, { recursive: true, force: true });
40
- npmInstall(mcpDir, '--no-workspaces');
41
- try { writeFileSync(stamp, host, 'utf-8'); } catch { /* non-fatal */ }
42
- }
5
+ import { bold, red } from './colors.js';
43
6
 
44
7
  export function spawnMcp(verbose = false) {
45
8
  const mcpPort = process.env.MINDOS_MCP_PORT || '8781';
46
9
  const webPort = process.env.MINDOS_WEB_PORT || '3456';
47
- // Ensure mcp/node_modules exists (auto-install on first run)
48
- const mcpSdk = resolve(ROOT, 'mcp', 'node_modules', '@modelcontextprotocol', 'sdk', 'package.json');
49
- if (!existsSync(mcpSdk)) {
50
- console.log(yellow('Installing MCP dependencies (first run)...\n'));
51
- npmInstall(resolve(ROOT, 'mcp'), '--no-workspaces');
10
+
11
+ const mcpBundle = resolve(ROOT, 'mcp', 'dist', 'index.cjs');
12
+ if (!existsSync(mcpBundle)) {
13
+ throw new Error(
14
+ `MCP bundle not found: ${mcpBundle}\n` +
15
+ `This MindOS installation may be corrupted. Try: npm install -g @geminilight/mindos@latest`,
16
+ );
52
17
  }
53
- ensureMcpNativeDeps();
54
18
 
55
- // Read AUTH_TOKEN directly from config to avoid stale system env overriding
56
- // the user's configured token. Config is the source of truth for auth.
57
19
  let configAuthToken;
58
20
  try {
59
21
  const cfg = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
@@ -69,7 +31,7 @@ export function spawnMcp(verbose = false) {
69
31
  ...(configAuthToken ? { AUTH_TOKEN: configAuthToken } : {}),
70
32
  ...(verbose ? { MCP_VERBOSE: '1' } : {}),
71
33
  };
72
- const child = spawn('npx', ['tsx', 'src/index.ts'], {
34
+ const child = spawn(process.execPath, [mcpBundle], {
73
35
  cwd: resolve(ROOT, 'mcp'),
74
36
  stdio: 'inherit',
75
37
  env,
package/bin/lib/stop.js CHANGED
@@ -111,7 +111,7 @@ export function stopMindos(opts = {}) {
111
111
  if (!pids.length && portKilled === 0) {
112
112
  // Last resort: pattern match (for envs without lsof)
113
113
  try { execSync('pkill -f "next start|next dev" 2>/dev/null || true', { stdio: ['ignore', 'inherit', 'inherit'] }); } catch {}
114
- try { execSync('pkill -f "mcp/src/index" 2>/dev/null || true', { stdio: ['ignore', 'inherit', 'inherit'] }); } catch {}
114
+ try { execSync('pkill -f "mcp/(src/index|dist/index)" 2>/dev/null || true', { stdio: ['ignore', 'inherit', 'inherit'] }); } catch {}
115
115
  }
116
116
 
117
117
  if (!pids.length) console.log(green('\u2714 Done'));