openwriter 0.5.1 → 0.5.3

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/dist/bin/pad.js CHANGED
@@ -19,6 +19,34 @@
19
19
  // Redirect all console output to stderr so MCP stdio protocol stays clean on stdout
20
20
  const originalLog = console.log;
21
21
  console.log = (...args) => console.error(...args);
22
+ // ── Crash guards ──
23
+ // The MCP StdioServerTransport writes to stdout with no error handler.
24
+ // When Claude Code closes the pipe, the write throws EPIPE and kills the process,
25
+ // taking the HTTP server (browser UI) down with it. Catch everything so the
26
+ // HTTP server survives MCP disconnects.
27
+ process.on('uncaughtException', (err) => {
28
+ // EPIPE / ERR_STREAM_DESTROYED from broken MCP pipe — non-fatal, ignore
29
+ if (err?.code === 'EPIPE' || err?.code === 'ERR_STREAM_DESTROYED')
30
+ return;
31
+ console.error('[FATAL] Uncaught exception:', err);
32
+ });
33
+ process.on('unhandledRejection', (reason) => {
34
+ console.error('[WARN] Unhandled rejection:', reason);
35
+ });
36
+ // Catch broken-pipe errors on stdout directly (MCP transport writes here)
37
+ process.stdout.on('error', (err) => {
38
+ if (err?.code === 'EPIPE' || err?.code === 'ERR_STREAM_DESTROYED')
39
+ return;
40
+ console.error('[stdout error]', err);
41
+ });
42
+ // Monitor stdin lifecycle — when Claude Code closes the pipe, stdin ends.
43
+ // Log it so we know exactly when MCP disconnects.
44
+ process.stdin.on('end', () => {
45
+ console.error('[MCP] stdin EOF — Claude Code disconnected. HTTP server still running.');
46
+ });
47
+ process.stdin.on('close', () => {
48
+ console.error('[MCP] stdin closed.');
49
+ });
22
50
  // Only light imports here — helpers.js uses fs/path/os/crypto (all Node stdlib)
23
51
  import { createConnection } from 'net';
24
52
  import { readConfig, saveConfig } from '../server/helpers.js';
@@ -76,15 +104,42 @@ else {
76
104
  process.env.AV_API_KEY = avApiKey;
77
105
  if (avBackendUrl)
78
106
  process.env.AV_BACKEND_URL = avBackendUrl;
79
- // Fast port check determines primary vs client mode
80
- const portTaken = await new Promise((resolve) => {
81
- const socket = createConnection({ port, host: '127.0.0.1' });
82
- socket.once('connect', () => { socket.destroy(); resolve(true); });
83
- socket.once('error', () => { resolve(false); });
84
- });
85
- if (portTaken) {
107
+ // Port check with health verification detects orphaned servers
108
+ async function checkPort() {
109
+ const taken = await new Promise((resolve) => {
110
+ const socket = createConnection({ port, host: '127.0.0.1' });
111
+ socket.once('connect', () => { socket.destroy(); resolve(true); });
112
+ socket.once('error', () => { resolve(false); });
113
+ });
114
+ if (!taken)
115
+ return 'free';
116
+ // Port is taken — verify it's a healthy OpenWriter server
117
+ try {
118
+ const res = await fetch(`http://127.0.0.1:${port}/api/status`, { signal: AbortSignal.timeout(2000) });
119
+ return res.ok ? 'healthy' : 'orphaned';
120
+ }
121
+ catch {
122
+ return 'orphaned';
123
+ }
124
+ }
125
+ let portState = await checkPort();
126
+ // Orphaned server: wait for it to die, then claim primary mode
127
+ if (portState === 'orphaned') {
128
+ console.error(`[OpenWriter] Port ${port} held by unresponsive process — waiting for release...`);
129
+ await new Promise(r => setTimeout(r, 3000));
130
+ portState = await checkPort();
131
+ if (portState === 'orphaned') {
132
+ // Still held — wait once more
133
+ await new Promise(r => setTimeout(r, 3000));
134
+ portState = await checkPort();
135
+ }
136
+ if (portState !== 'free') {
137
+ console.error(`[OpenWriter] Port ${port} still unavailable — entering client mode`);
138
+ }
139
+ }
140
+ if (portState === 'healthy') {
86
141
  // Client mode: proxy MCP calls to existing primary server via HTTP
87
- console.error(`[OpenWriter] Port ${port} in use — entering client mode (proxying to existing server)`);
142
+ console.error(`[OpenWriter] Port ${port} in use by healthy server — entering client mode`);
88
143
  const { startMcpClientServer } = await import('../server/mcp-client.js');
89
144
  startMcpClientServer(port).catch((err) => {
90
145
  console.error('[MCP-Client] Failed to start:', err);
@@ -100,6 +155,8 @@ else {
100
155
  });
101
156
  // Deferred: load Express + plugins (heavy deps) after MCP is connecting
102
157
  const { startHttpServer } = await import('../server/index.js');
103
- startHttpServer({ port, noOpen, plugins });
158
+ startHttpServer({ port, noOpen, plugins }).catch((err) => {
159
+ console.error('[HTTP] Failed to start:', err);
160
+ });
104
161
  }
105
162
  }