cprime-supergateway 3.4.6 → 3.4.8
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/.claude/settings.local.json +8 -0
- package/dist/gateways/stdioToSse.js +90 -11
- package/package.json +1 -1
- package/src/gateways/stdioToSse.ts +105 -21
|
@@ -41,17 +41,96 @@ export async function stdioToSse(args) {
|
|
|
41
41
|
logger.info(` - messagePath: ${messagePath}`);
|
|
42
42
|
logger.info(` - CORS: ${corsOrigin ? `enabled (${serializeCorsOrigin({ corsOrigin })})` : 'disabled'}`);
|
|
43
43
|
logger.info(` - Health endpoints: ${healthEndpoints.length ? healthEndpoints.join(', ') : '(none)'}`);
|
|
44
|
-
|
|
44
|
+
let isShuttingDown = false;
|
|
45
|
+
onSignals({
|
|
46
|
+
logger,
|
|
47
|
+
cleanup: () => {
|
|
48
|
+
isShuttingDown = true;
|
|
49
|
+
child.kill();
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
const sessions = {};
|
|
45
53
|
const child = spawn(stdioCmd, {
|
|
46
54
|
shell: true,
|
|
47
55
|
env: { ...process.env, ...decryptedEnvs },
|
|
48
56
|
});
|
|
57
|
+
/** Coalesce rapid stderr bursts into one DB log after this idle gap (ms). */
|
|
58
|
+
const STDERR_LOG_DEBOUNCE_MS = 400;
|
|
59
|
+
let stderrLogBuffer = '';
|
|
60
|
+
let stderrLogFlushTimer = null;
|
|
61
|
+
const flushStderrToLogs = () => {
|
|
62
|
+
stderrLogFlushTimer = null;
|
|
63
|
+
if (!stderrLogBuffer)
|
|
64
|
+
return;
|
|
65
|
+
const text = stderrLogBuffer;
|
|
66
|
+
stderrLogBuffer = '';
|
|
67
|
+
logger.error(`Child stderr: ${text}`);
|
|
68
|
+
for (const [sid, session] of Object.entries(sessions)) {
|
|
69
|
+
logService.log(text, 'system', logger, {
|
|
70
|
+
...session,
|
|
71
|
+
sessionId: sid,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const scheduleStderrLogFlush = () => {
|
|
76
|
+
if (stderrLogFlushTimer)
|
|
77
|
+
clearTimeout(stderrLogFlushTimer);
|
|
78
|
+
stderrLogFlushTimer = setTimeout(flushStderrToLogs, STDERR_LOG_DEBOUNCE_MS);
|
|
79
|
+
};
|
|
80
|
+
const broadcastChildError = (errorParams) => {
|
|
81
|
+
const notification = {
|
|
82
|
+
jsonrpc: '2.0',
|
|
83
|
+
method: 'error',
|
|
84
|
+
params: errorParams,
|
|
85
|
+
};
|
|
86
|
+
for (const [sid, session] of Object.entries(sessions)) {
|
|
87
|
+
try {
|
|
88
|
+
session.transport.send(notification);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
logger.error(`Failed to send child error to session ${sid}:`, err);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const logChildError = async (errorData) => {
|
|
96
|
+
for (const [sid, session] of Object.entries(sessions)) {
|
|
97
|
+
await logService.log(errorData, 'system', logger, {
|
|
98
|
+
ip: session.ip,
|
|
99
|
+
userId: session.userId,
|
|
100
|
+
sessionId: sid,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
child.on('error', (err) => {
|
|
105
|
+
logger.error(`Child process error: ${err.message}`);
|
|
106
|
+
const errorData = {
|
|
107
|
+
type: 'spawn-error',
|
|
108
|
+
message: err.message,
|
|
109
|
+
code: err.code ?? null,
|
|
110
|
+
timestamp: new Date().toISOString(),
|
|
111
|
+
};
|
|
112
|
+
broadcastChildError(errorData);
|
|
113
|
+
logChildError(errorData);
|
|
114
|
+
});
|
|
49
115
|
child.on('exit', (code, signal) => {
|
|
116
|
+
if (stderrLogFlushTimer) {
|
|
117
|
+
clearTimeout(stderrLogFlushTimer);
|
|
118
|
+
stderrLogFlushTimer = null;
|
|
119
|
+
}
|
|
120
|
+
flushStderrToLogs();
|
|
50
121
|
logger.error(`Child exited: code=${code}, signal=${signal}`);
|
|
51
|
-
|
|
122
|
+
if (isShuttingDown || code === 0)
|
|
123
|
+
return;
|
|
124
|
+
const errorData = {
|
|
125
|
+
type: 'exit',
|
|
126
|
+
exitCode: code,
|
|
127
|
+
signal: signal ?? null,
|
|
128
|
+
message: `Child process exited unexpectedly (code=${code}, signal=${signal})`,
|
|
129
|
+
timestamp: new Date().toISOString(),
|
|
130
|
+
};
|
|
131
|
+
broadcastChildError(errorData);
|
|
132
|
+
logChildError(errorData);
|
|
52
133
|
});
|
|
53
|
-
const server = new Server({ name: 'supergateway', version: getVersion() }, { capabilities: {} });
|
|
54
|
-
const sessions = {};
|
|
55
134
|
const app = express();
|
|
56
135
|
if (corsOrigin) {
|
|
57
136
|
app.use(cors({ origin: corsOrigin }));
|
|
@@ -76,6 +155,7 @@ export async function stdioToSse(args) {
|
|
|
76
155
|
res,
|
|
77
156
|
headers,
|
|
78
157
|
});
|
|
158
|
+
const server = new Server({ name: 'supergateway', version: getVersion() }, { capabilities: {} });
|
|
79
159
|
const sseTransport = new SSEServerTransport(`${baseUrl}${messagePath}`, res);
|
|
80
160
|
await server.connect(sseTransport);
|
|
81
161
|
const sessionId = sseTransport.sessionId;
|
|
@@ -93,6 +173,10 @@ export async function stdioToSse(args) {
|
|
|
93
173
|
...sessions[sessionId],
|
|
94
174
|
sessionId,
|
|
95
175
|
});
|
|
176
|
+
if (child.killed || child.exitCode !== null) {
|
|
177
|
+
logger.error(`Cannot forward message to child — process is not running (session ${sessionId})`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
96
180
|
child.stdin.write(JSON.stringify(msg) + '\n');
|
|
97
181
|
};
|
|
98
182
|
sseTransport.onclose = () => {
|
|
@@ -169,12 +253,7 @@ export async function stdioToSse(args) {
|
|
|
169
253
|
});
|
|
170
254
|
});
|
|
171
255
|
child.stderr.on('data', (chunk) => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
logService.log(chunk.toString('utf8'), 'system', logger, {
|
|
175
|
-
...session,
|
|
176
|
-
sessionId: sid,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
256
|
+
stderrLogBuffer += chunk.toString('utf8');
|
|
257
|
+
scheduleStderrLogFlush();
|
|
179
258
|
});
|
|
180
259
|
}
|
package/package.json
CHANGED
|
@@ -90,22 +90,15 @@ export async function stdioToSse(args: StdioToSseArgs) {
|
|
|
90
90
|
` - Health endpoints: ${healthEndpoints.length ? healthEndpoints.join(', ') : '(none)'}`,
|
|
91
91
|
)
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
logger.error(`Child exited: code=${code}, signal=${signal}`)
|
|
101
|
-
process.exit(code ?? 1)
|
|
93
|
+
let isShuttingDown = false
|
|
94
|
+
onSignals({
|
|
95
|
+
logger,
|
|
96
|
+
cleanup: () => {
|
|
97
|
+
isShuttingDown = true
|
|
98
|
+
child.kill()
|
|
99
|
+
},
|
|
102
100
|
})
|
|
103
101
|
|
|
104
|
-
const server = new Server(
|
|
105
|
-
{ name: 'supergateway', version: getVersion() },
|
|
106
|
-
{ capabilities: {} },
|
|
107
|
-
)
|
|
108
|
-
|
|
109
102
|
const sessions: Record<
|
|
110
103
|
string,
|
|
111
104
|
{
|
|
@@ -116,6 +109,92 @@ export async function stdioToSse(args: StdioToSseArgs) {
|
|
|
116
109
|
}
|
|
117
110
|
> = {}
|
|
118
111
|
|
|
112
|
+
const child: ChildProcessWithoutNullStreams = spawn(stdioCmd, {
|
|
113
|
+
shell: true,
|
|
114
|
+
env: { ...process.env, ...decryptedEnvs },
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
/** Coalesce rapid stderr bursts into one DB log after this idle gap (ms). */
|
|
118
|
+
const STDERR_LOG_DEBOUNCE_MS = 400
|
|
119
|
+
let stderrLogBuffer = ''
|
|
120
|
+
let stderrLogFlushTimer: ReturnType<typeof setTimeout> | null = null
|
|
121
|
+
|
|
122
|
+
const flushStderrToLogs = () => {
|
|
123
|
+
stderrLogFlushTimer = null
|
|
124
|
+
if (!stderrLogBuffer) return
|
|
125
|
+
const text = stderrLogBuffer
|
|
126
|
+
stderrLogBuffer = ''
|
|
127
|
+
logger.error(`Child stderr: ${text}`)
|
|
128
|
+
for (const [sid, session] of Object.entries(sessions)) {
|
|
129
|
+
logService.log(text, 'system', logger, {
|
|
130
|
+
...session,
|
|
131
|
+
sessionId: sid,
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const scheduleStderrLogFlush = () => {
|
|
137
|
+
if (stderrLogFlushTimer) clearTimeout(stderrLogFlushTimer)
|
|
138
|
+
stderrLogFlushTimer = setTimeout(flushStderrToLogs, STDERR_LOG_DEBOUNCE_MS)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const broadcastChildError = (errorParams: Record<string, unknown>) => {
|
|
142
|
+
const notification: JSONRPCMessage = {
|
|
143
|
+
jsonrpc: '2.0',
|
|
144
|
+
method: 'error',
|
|
145
|
+
params: errorParams,
|
|
146
|
+
}
|
|
147
|
+
for (const [sid, session] of Object.entries(sessions)) {
|
|
148
|
+
try {
|
|
149
|
+
session.transport.send(notification)
|
|
150
|
+
} catch (err) {
|
|
151
|
+
logger.error(`Failed to send child error to session ${sid}:`, err)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const logChildError = async (errorData: Record<string, unknown>) => {
|
|
157
|
+
for (const [sid, session] of Object.entries(sessions)) {
|
|
158
|
+
await logService.log(errorData, 'system', logger, {
|
|
159
|
+
ip: session.ip,
|
|
160
|
+
userId: session.userId,
|
|
161
|
+
sessionId: sid,
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
child.on('error', (err) => {
|
|
167
|
+
logger.error(`Child process error: ${err.message}`)
|
|
168
|
+
const errorData = {
|
|
169
|
+
type: 'spawn-error',
|
|
170
|
+
message: err.message,
|
|
171
|
+
code: (err as NodeJS.ErrnoException).code ?? null,
|
|
172
|
+
timestamp: new Date().toISOString(),
|
|
173
|
+
}
|
|
174
|
+
broadcastChildError(errorData)
|
|
175
|
+
logChildError(errorData)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
child.on('exit', (code, signal) => {
|
|
179
|
+
if (stderrLogFlushTimer) {
|
|
180
|
+
clearTimeout(stderrLogFlushTimer)
|
|
181
|
+
stderrLogFlushTimer = null
|
|
182
|
+
}
|
|
183
|
+
flushStderrToLogs()
|
|
184
|
+
|
|
185
|
+
logger.error(`Child exited: code=${code}, signal=${signal}`)
|
|
186
|
+
if (isShuttingDown || code === 0) return
|
|
187
|
+
const errorData = {
|
|
188
|
+
type: 'exit',
|
|
189
|
+
exitCode: code,
|
|
190
|
+
signal: signal ?? null,
|
|
191
|
+
message: `Child process exited unexpectedly (code=${code}, signal=${signal})`,
|
|
192
|
+
timestamp: new Date().toISOString(),
|
|
193
|
+
}
|
|
194
|
+
broadcastChildError(errorData)
|
|
195
|
+
logChildError(errorData)
|
|
196
|
+
})
|
|
197
|
+
|
|
119
198
|
const app = express()
|
|
120
199
|
|
|
121
200
|
if (corsOrigin) {
|
|
@@ -145,6 +224,10 @@ export async function stdioToSse(args: StdioToSseArgs) {
|
|
|
145
224
|
headers,
|
|
146
225
|
})
|
|
147
226
|
|
|
227
|
+
const server = new Server(
|
|
228
|
+
{ name: 'supergateway', version: getVersion() },
|
|
229
|
+
{ capabilities: {} },
|
|
230
|
+
)
|
|
148
231
|
const sseTransport = new SSEServerTransport(`${baseUrl}${messagePath}`, res)
|
|
149
232
|
await server.connect(sseTransport)
|
|
150
233
|
|
|
@@ -164,6 +247,12 @@ export async function stdioToSse(args: StdioToSseArgs) {
|
|
|
164
247
|
...sessions[sessionId],
|
|
165
248
|
sessionId,
|
|
166
249
|
})
|
|
250
|
+
if (child.killed || child.exitCode !== null) {
|
|
251
|
+
logger.error(
|
|
252
|
+
`Cannot forward message to child — process is not running (session ${sessionId})`,
|
|
253
|
+
)
|
|
254
|
+
return
|
|
255
|
+
}
|
|
167
256
|
child.stdin.write(JSON.stringify(msg) + '\n')
|
|
168
257
|
}
|
|
169
258
|
|
|
@@ -246,12 +335,7 @@ export async function stdioToSse(args: StdioToSseArgs) {
|
|
|
246
335
|
})
|
|
247
336
|
|
|
248
337
|
child.stderr.on('data', (chunk: Buffer) => {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
logService.log(chunk.toString('utf8'), 'system', logger, {
|
|
252
|
-
...session,
|
|
253
|
-
sessionId: sid,
|
|
254
|
-
})
|
|
255
|
-
}
|
|
338
|
+
stderrLogBuffer += chunk.toString('utf8')
|
|
339
|
+
scheduleStderrLogFlush()
|
|
256
340
|
})
|
|
257
341
|
}
|