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 +66 -9
- package/dist/client/assets/index-BAbqg4Q8.js +210 -0
- package/dist/client/assets/index-BR_sMmFf.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/server/compact.js +30 -1
- package/dist/server/documents.js +303 -2
- package/dist/server/index.js +103 -37
- package/dist/server/marks.js +166 -0
- package/dist/server/mcp.js +272 -55
- package/dist/server/state.js +7 -1
- package/dist/server/workspaces.js +16 -0
- package/dist/server/ws.js +18 -3
- package/package.json +2 -4
- package/skill/SKILL.md +34 -2
- package/dist/client/assets/index-Be3gaGeo.css +0 -1
- package/dist/client/assets/index-BwT1KW6a.js +0 -207
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
|
-
//
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
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
|
}
|