cprime-supergateway 3.4.7 → 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 +89 -10
- package/package.json +1 -1
- package/src/gateways/stdioToSse.ts +101 -16
|
@@ -41,16 +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 sessions = {};
|
|
54
134
|
const app = express();
|
|
55
135
|
if (corsOrigin) {
|
|
56
136
|
app.use(cors({ origin: corsOrigin }));
|
|
@@ -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,15 +90,13 @@ 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
102
|
const sessions: Record<
|
|
@@ -111,6 +109,92 @@ export async function stdioToSse(args: StdioToSseArgs) {
|
|
|
111
109
|
}
|
|
112
110
|
> = {}
|
|
113
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
|
+
|
|
114
198
|
const app = express()
|
|
115
199
|
|
|
116
200
|
if (corsOrigin) {
|
|
@@ -163,6 +247,12 @@ export async function stdioToSse(args: StdioToSseArgs) {
|
|
|
163
247
|
...sessions[sessionId],
|
|
164
248
|
sessionId,
|
|
165
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
|
+
}
|
|
166
256
|
child.stdin.write(JSON.stringify(msg) + '\n')
|
|
167
257
|
}
|
|
168
258
|
|
|
@@ -245,12 +335,7 @@ export async function stdioToSse(args: StdioToSseArgs) {
|
|
|
245
335
|
})
|
|
246
336
|
|
|
247
337
|
child.stderr.on('data', (chunk: Buffer) => {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
logService.log(chunk.toString('utf8'), 'system', logger, {
|
|
251
|
-
...session,
|
|
252
|
-
sessionId: sid,
|
|
253
|
-
})
|
|
254
|
-
}
|
|
338
|
+
stderrLogBuffer += chunk.toString('utf8')
|
|
339
|
+
scheduleStderrLogFlush()
|
|
255
340
|
})
|
|
256
341
|
}
|