@openviking/opencode-plugin 0.1.6 → 0.2.1
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/INSTALL-ZH.md +50 -49
- package/INSTALL.md +50 -49
- package/README.md +57 -126
- package/index.mjs +20 -12
- package/lib/config.mjs +284 -0
- package/lib/mcp-config.mjs +21 -0
- package/lib/memory-recall.mjs +21 -172
- package/lib/memory-session.mjs +301 -478
- package/lib/repo-context.mjs +5 -5
- package/lib/session-inject.mjs +71 -0
- package/lib/shared/capture-utils.mjs +500 -0
- package/lib/shared/credentials.mjs +207 -0
- package/lib/shared/debug-log.mjs +58 -0
- package/lib/shared/mcp-proxy-core.mjs +451 -0
- package/lib/shared/pending-queue.mjs +439 -0
- package/lib/shared/profile-inject.mjs +294 -0
- package/lib/shared/recall-core.mjs +302 -0
- package/lib/shared/session-model.mjs +57 -0
- package/lib/shared/setup-wizard.mjs +111 -0
- package/lib/shared/uri-guard.mjs +56 -0
- package/lib/shared/workspace-peer.mjs +16 -0
- package/lib/utils.mjs +34 -117
- package/lib/viking-uri-guard.mjs +9 -24
- package/package.json +8 -7
- package/scripts/setup.mjs +8 -0
- package/servers/mcp-proxy.mjs +69 -0
- package/servers/mcp-proxy.test.mjs +100 -0
- package/lib/code-tools.mjs +0 -134
- package/lib/memadd-local.mjs +0 -118
- package/lib/memory-tools.mjs +0 -410
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
// GENERATED FROM examples/memory-plugin-shared/lib. DO NOT EDIT.
|
|
2
|
+
/**
|
|
3
|
+
* Shared stdio -> streamable-HTTP MCP proxy core for OpenViking memory plugins.
|
|
4
|
+
*
|
|
5
|
+
* Harness-specific entrypoints provide credential/config loading; this module
|
|
6
|
+
* owns only transport, session retry, SSE parsing, and protocol-clean stdio.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { statSync } from "node:fs";
|
|
10
|
+
import { createInterface } from "node:readline";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_PROTOCOL_VERSION = "2025-06-18";
|
|
13
|
+
const DELETE_TIMEOUT_MS = 2000;
|
|
14
|
+
const MAX_CONCURRENT_REQUESTS = 16;
|
|
15
|
+
|
|
16
|
+
class HttpStatusError extends Error {
|
|
17
|
+
constructor(status, statusText, bodyText, messages = []) {
|
|
18
|
+
super(`HTTP ${status}${statusText ? ` ${statusText}` : ""}`);
|
|
19
|
+
this.name = "HttpStatusError";
|
|
20
|
+
this.status = status;
|
|
21
|
+
this.statusText = statusText;
|
|
22
|
+
this.bodyText = bodyText;
|
|
23
|
+
this.messages = messages;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function snapshotPaths(paths) {
|
|
28
|
+
const out = new Map();
|
|
29
|
+
for (const path of paths || []) {
|
|
30
|
+
try {
|
|
31
|
+
const st = statSync(path);
|
|
32
|
+
out.set(path, `${st.mtimeMs}:${st.size}`);
|
|
33
|
+
} catch {
|
|
34
|
+
out.set(path, "missing");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function snapshotsDiffer(a, b) {
|
|
41
|
+
if (a.size !== b.size) return true;
|
|
42
|
+
for (const [key, value] of a.entries()) {
|
|
43
|
+
if (b.get(key) !== value) return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createSemaphore(limit) {
|
|
49
|
+
let active = 0;
|
|
50
|
+
const queue = [];
|
|
51
|
+
return async function acquire() {
|
|
52
|
+
if (active >= limit) {
|
|
53
|
+
await new Promise((resolve) => queue.push(resolve));
|
|
54
|
+
}
|
|
55
|
+
active += 1;
|
|
56
|
+
let released = false;
|
|
57
|
+
return () => {
|
|
58
|
+
if (released) return;
|
|
59
|
+
released = true;
|
|
60
|
+
active -= 1;
|
|
61
|
+
const next = queue.shift();
|
|
62
|
+
if (next) next();
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isRequest(message) {
|
|
68
|
+
return Object.prototype.hasOwnProperty.call(message || {}, "id");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function messageId(message) {
|
|
72
|
+
return isRequest(message) ? message.id : undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function errorResponse(id, code, message, data) {
|
|
76
|
+
const error = { code, message };
|
|
77
|
+
if (data !== undefined) error.data = data;
|
|
78
|
+
return { jsonrpc: "2.0", id: id ?? null, error };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseMaybeJson(text) {
|
|
82
|
+
const trimmed = String(text || "").trim();
|
|
83
|
+
if (!trimmed) return null;
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(trimmed);
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function parseSseMessages(text) {
|
|
92
|
+
const messages = [];
|
|
93
|
+
let dataLines = [];
|
|
94
|
+
|
|
95
|
+
function flush() {
|
|
96
|
+
if (dataLines.length === 0) return;
|
|
97
|
+
const data = dataLines.join("\n").trim();
|
|
98
|
+
dataLines = [];
|
|
99
|
+
if (!data || data === "[DONE]") return;
|
|
100
|
+
messages.push(JSON.parse(data));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const rawLine of String(text || "").split(/\r?\n/)) {
|
|
104
|
+
if (rawLine === "") {
|
|
105
|
+
flush();
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (rawLine.startsWith(":")) continue;
|
|
109
|
+
const colon = rawLine.indexOf(":");
|
|
110
|
+
const field = colon === -1 ? rawLine : rawLine.slice(0, colon);
|
|
111
|
+
let value = colon === -1 ? "" : rawLine.slice(colon + 1);
|
|
112
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
113
|
+
if (field === "data") dataLines.push(value);
|
|
114
|
+
}
|
|
115
|
+
flush();
|
|
116
|
+
return messages;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function parseHttpBody(contentType, text) {
|
|
120
|
+
const ctype = String(contentType || "").toLowerCase();
|
|
121
|
+
if (!String(text || "").trim()) return [];
|
|
122
|
+
if (ctype.includes("text/event-stream")) return parseSseMessages(text);
|
|
123
|
+
const json = parseMaybeJson(text);
|
|
124
|
+
return json == null ? [] : [json];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function serializeBodyForError(bodyText) {
|
|
128
|
+
const parsed = parseMaybeJson(bodyText);
|
|
129
|
+
if (parsed?.error?.message) return parsed.error.message;
|
|
130
|
+
if (parsed?.detail) return typeof parsed.detail === "string" ? parsed.detail : JSON.stringify(parsed.detail);
|
|
131
|
+
const compact = String(bodyText || "").replace(/\s+/g, " ").trim();
|
|
132
|
+
return compact.slice(0, 500);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function cloneMessage(message) {
|
|
136
|
+
return JSON.parse(JSON.stringify(message));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function createOpenVikingMcpProxy({
|
|
140
|
+
stdin = process.stdin,
|
|
141
|
+
stdout = process.stdout,
|
|
142
|
+
readConfig,
|
|
143
|
+
loggerFactory,
|
|
144
|
+
fetchImpl = globalThis.fetch,
|
|
145
|
+
} = {}) {
|
|
146
|
+
if (typeof fetchImpl !== "function") {
|
|
147
|
+
throw new Error("global fetch is required; use Node.js 18 or newer");
|
|
148
|
+
}
|
|
149
|
+
if (typeof readConfig !== "function") {
|
|
150
|
+
throw new Error("readConfig function is required");
|
|
151
|
+
}
|
|
152
|
+
if (typeof loggerFactory !== "function") {
|
|
153
|
+
throw new Error("loggerFactory function is required");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let proxyConfig = readConfig();
|
|
157
|
+
let logger = loggerFactory("mcp-proxy", proxyConfig);
|
|
158
|
+
let watchedSnapshot = snapshotPaths(proxyConfig.watchedPaths);
|
|
159
|
+
let sessionId = "";
|
|
160
|
+
let initializeRequest = null;
|
|
161
|
+
let initializedNotification = null;
|
|
162
|
+
let protocolVersion = DEFAULT_PROTOCOL_VERSION;
|
|
163
|
+
let initializeInFlight = null;
|
|
164
|
+
let reinitializeInFlight = null;
|
|
165
|
+
let stdoutChain = Promise.resolve();
|
|
166
|
+
let shuttingDown = false;
|
|
167
|
+
let reinitCounter = 0;
|
|
168
|
+
const acquire = createSemaphore(MAX_CONCURRENT_REQUESTS);
|
|
169
|
+
|
|
170
|
+
function log(stage, data) {
|
|
171
|
+
try {
|
|
172
|
+
logger.log(stage, data);
|
|
173
|
+
} catch { /* debug logging must never affect protocol IO */ }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function logError(stage, err) {
|
|
177
|
+
try {
|
|
178
|
+
logger.logError(stage, err);
|
|
179
|
+
} catch { /* debug logging must never affect protocol IO */ }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function reloadConfig(reason) {
|
|
183
|
+
proxyConfig = readConfig();
|
|
184
|
+
logger = loggerFactory("mcp-proxy", proxyConfig);
|
|
185
|
+
watchedSnapshot = snapshotPaths(proxyConfig.watchedPaths);
|
|
186
|
+
log("credentials_reloaded", {
|
|
187
|
+
reason,
|
|
188
|
+
credentialSource: proxyConfig.credentialSource,
|
|
189
|
+
credentialPath: proxyConfig.credentialPath,
|
|
190
|
+
mcpUrl: proxyConfig.mcpUrl,
|
|
191
|
+
hasApiKey: Boolean(proxyConfig.apiKey),
|
|
192
|
+
hasIdentity: Boolean(proxyConfig.account || proxyConfig.user),
|
|
193
|
+
hasPeer: Boolean(proxyConfig.peerId),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function reloadIfCredentialFilesChanged(reason) {
|
|
198
|
+
const next = snapshotPaths(proxyConfig.watchedPaths);
|
|
199
|
+
if (!snapshotsDiffer(watchedSnapshot, next)) return false;
|
|
200
|
+
reloadConfig(reason);
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function headersForRequest(includeSession = true, message = null) {
|
|
205
|
+
const headers = {
|
|
206
|
+
"Content-Type": "application/json",
|
|
207
|
+
"Accept": "application/json, text/event-stream",
|
|
208
|
+
"MCP-Protocol-Version": message?.params?.protocolVersion || protocolVersion,
|
|
209
|
+
};
|
|
210
|
+
if (includeSession && sessionId) headers["Mcp-Session-Id"] = sessionId;
|
|
211
|
+
if (proxyConfig.apiKey) headers.Authorization = `Bearer ${proxyConfig.apiKey}`;
|
|
212
|
+
if (proxyConfig.account) headers["X-OpenViking-Account"] = proxyConfig.account;
|
|
213
|
+
if (proxyConfig.user) headers["X-OpenViking-User"] = proxyConfig.user;
|
|
214
|
+
if (proxyConfig.peerId) headers["X-OpenViking-Actor-Peer"] = proxyConfig.peerId;
|
|
215
|
+
return headers;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function writeMessage(obj) {
|
|
219
|
+
const line = `${JSON.stringify(obj)}\n`;
|
|
220
|
+
stdoutChain = stdoutChain
|
|
221
|
+
.then(() => new Promise((resolve) => stdout.write(line, resolve)))
|
|
222
|
+
.catch(() => {});
|
|
223
|
+
return stdoutChain;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function mapError(message, err) {
|
|
227
|
+
const id = messageId(message);
|
|
228
|
+
if (err instanceof HttpStatusError) {
|
|
229
|
+
if (err.status === 401 || err.status === 403) {
|
|
230
|
+
return errorResponse(
|
|
231
|
+
id,
|
|
232
|
+
-32001,
|
|
233
|
+
`OpenViking MCP authentication failed (HTTP ${err.status}). Check ~/.openviking/ovcli.conf or OPENVIKING_API_KEY, and verify the configured account/user for trusted mode.`,
|
|
234
|
+
{
|
|
235
|
+
status: err.status,
|
|
236
|
+
credentialSource: proxyConfig.credentialSource,
|
|
237
|
+
credentialPath: proxyConfig.credentialPath || undefined,
|
|
238
|
+
serverMessage: serializeBodyForError(err.bodyText) || undefined,
|
|
239
|
+
},
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
return errorResponse(
|
|
243
|
+
id,
|
|
244
|
+
-32002,
|
|
245
|
+
`OpenViking MCP upstream returned HTTP ${err.status}.`,
|
|
246
|
+
{
|
|
247
|
+
status: err.status,
|
|
248
|
+
serverMessage: serializeBodyForError(err.bodyText) || undefined,
|
|
249
|
+
},
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
253
|
+
return errorResponse(
|
|
254
|
+
id,
|
|
255
|
+
-32001,
|
|
256
|
+
`OpenViking MCP request failed. Check the configured URL (${proxyConfig.mcpUrl}) and that 'ov serve' is running.`,
|
|
257
|
+
{ cause: msg },
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function postToMcp(message, { includeSession = true, timeoutMs = proxyConfig.timeoutMs } = {}) {
|
|
262
|
+
const release = await acquire();
|
|
263
|
+
const controller = new AbortController();
|
|
264
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
265
|
+
try {
|
|
266
|
+
const res = await fetchImpl(proxyConfig.mcpUrl, {
|
|
267
|
+
method: "POST",
|
|
268
|
+
headers: headersForRequest(includeSession, message),
|
|
269
|
+
body: JSON.stringify(message),
|
|
270
|
+
signal: controller.signal,
|
|
271
|
+
});
|
|
272
|
+
const text = await res.text();
|
|
273
|
+
const messages = parseHttpBody(res.headers.get("content-type"), text);
|
|
274
|
+
const nextSessionId = res.headers.get("mcp-session-id");
|
|
275
|
+
if (nextSessionId) sessionId = nextSessionId;
|
|
276
|
+
if (!res.ok) {
|
|
277
|
+
throw new HttpStatusError(res.status, res.statusText, text, messages);
|
|
278
|
+
}
|
|
279
|
+
return { status: res.status, messages };
|
|
280
|
+
} finally {
|
|
281
|
+
clearTimeout(timer);
|
|
282
|
+
release();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async function deleteSession() {
|
|
287
|
+
if (!sessionId || shuttingDown) return;
|
|
288
|
+
shuttingDown = true;
|
|
289
|
+
const controller = new AbortController();
|
|
290
|
+
const timer = setTimeout(() => controller.abort(), DELETE_TIMEOUT_MS);
|
|
291
|
+
try {
|
|
292
|
+
await fetchImpl(proxyConfig.mcpUrl, {
|
|
293
|
+
method: "DELETE",
|
|
294
|
+
headers: headersForRequest(true),
|
|
295
|
+
signal: controller.signal,
|
|
296
|
+
});
|
|
297
|
+
} catch (err) {
|
|
298
|
+
logError("delete_session_failed", err);
|
|
299
|
+
} finally {
|
|
300
|
+
clearTimeout(timer);
|
|
301
|
+
sessionId = "";
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function reinitialize(failedSessionId) {
|
|
306
|
+
if (!initializeRequest) {
|
|
307
|
+
throw new Error("MCP session expired before initialize parameters were cached");
|
|
308
|
+
}
|
|
309
|
+
if (reinitializeInFlight) return reinitializeInFlight;
|
|
310
|
+
reinitializeInFlight = (async () => {
|
|
311
|
+
if (failedSessionId && sessionId && sessionId !== failedSessionId) return;
|
|
312
|
+
const reinit = cloneMessage(initializeRequest);
|
|
313
|
+
reinit.id = `openviking-proxy-reinit-${Date.now()}-${++reinitCounter}`;
|
|
314
|
+
sessionId = "";
|
|
315
|
+
const result = await postToMcp(reinit, { includeSession: false });
|
|
316
|
+
if (initializedNotification) {
|
|
317
|
+
await postToMcp(cloneMessage(initializedNotification), { includeSession: true });
|
|
318
|
+
}
|
|
319
|
+
log("reinitialized", { mcpUrl: proxyConfig.mcpUrl, status: result.status, sessionId: Boolean(sessionId) });
|
|
320
|
+
})().finally(() => {
|
|
321
|
+
reinitializeInFlight = null;
|
|
322
|
+
});
|
|
323
|
+
return reinitializeInFlight;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function sendWithRetry(message, { expectsResponse }) {
|
|
327
|
+
if (message.method !== "initialize" && initializeInFlight) {
|
|
328
|
+
await initializeInFlight.catch(() => {});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const failedSessionId = sessionId;
|
|
332
|
+
try {
|
|
333
|
+
return await postToMcp(message, { includeSession: message.method !== "initialize" });
|
|
334
|
+
} catch (err) {
|
|
335
|
+
if (err instanceof HttpStatusError && (err.status === 401 || err.status === 403)) {
|
|
336
|
+
if (reloadIfCredentialFilesChanged("auth_failure")) {
|
|
337
|
+
sessionId = "";
|
|
338
|
+
if (message.method !== "initialize" && initializeRequest) {
|
|
339
|
+
await reinitialize(failedSessionId);
|
|
340
|
+
}
|
|
341
|
+
return await postToMcp(message, { includeSession: message.method !== "initialize" });
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (
|
|
345
|
+
err instanceof HttpStatusError
|
|
346
|
+
&& (err.status === 400 || err.status === 404)
|
|
347
|
+
&& message.method !== "initialize"
|
|
348
|
+
&& initializeRequest
|
|
349
|
+
) {
|
|
350
|
+
await reinitialize(failedSessionId);
|
|
351
|
+
return await postToMcp(message, { includeSession: true });
|
|
352
|
+
}
|
|
353
|
+
if (!expectsResponse) {
|
|
354
|
+
logError("notification_failed", err);
|
|
355
|
+
}
|
|
356
|
+
throw err;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async function handleMessage(message) {
|
|
361
|
+
if (!message || typeof message !== "object" || message.jsonrpc !== "2.0") {
|
|
362
|
+
await writeMessage(errorResponse(null, -32600, "Invalid JSON-RPC message"));
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const expectsResponse = isRequest(message);
|
|
367
|
+
if (message.method === "initialize") {
|
|
368
|
+
initializeRequest = cloneMessage(message);
|
|
369
|
+
protocolVersion = message.params?.protocolVersion || protocolVersion || DEFAULT_PROTOCOL_VERSION;
|
|
370
|
+
sessionId = "";
|
|
371
|
+
}
|
|
372
|
+
if (message.method === "notifications/initialized") {
|
|
373
|
+
initializedNotification = cloneMessage(message);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
const send = sendWithRetry(message, { expectsResponse });
|
|
378
|
+
if (message.method === "initialize") {
|
|
379
|
+
initializeInFlight = send
|
|
380
|
+
.catch(() => {})
|
|
381
|
+
.finally(() => {
|
|
382
|
+
initializeInFlight = null;
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
const result = await send;
|
|
386
|
+
if (!expectsResponse) return;
|
|
387
|
+
if (result.messages.length === 0) {
|
|
388
|
+
await writeMessage(errorResponse(message.id, -32003, "OpenViking MCP upstream returned an empty response"));
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
for (const outbound of result.messages) {
|
|
392
|
+
await writeMessage(outbound);
|
|
393
|
+
}
|
|
394
|
+
} catch (err) {
|
|
395
|
+
if (expectsResponse) {
|
|
396
|
+
await writeMessage(mapError(message, err));
|
|
397
|
+
} else {
|
|
398
|
+
logError("notification_unhandled", err);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async function handleLine(line) {
|
|
404
|
+
const trimmed = line.trim();
|
|
405
|
+
if (!trimmed) return;
|
|
406
|
+
let message;
|
|
407
|
+
try {
|
|
408
|
+
message = JSON.parse(trimmed);
|
|
409
|
+
} catch {
|
|
410
|
+
await writeMessage(errorResponse(null, -32700, "Parse error"));
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
await handleMessage(message);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async function closeAndExit(code = 0) {
|
|
417
|
+
await deleteSession();
|
|
418
|
+
await stdoutChain;
|
|
419
|
+
process.exit(code);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async function closeSession() {
|
|
423
|
+
await deleteSession();
|
|
424
|
+
await stdoutChain;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function start() {
|
|
428
|
+
log("start", {
|
|
429
|
+
mcpUrl: proxyConfig.mcpUrl,
|
|
430
|
+
credentialSource: proxyConfig.credentialSource,
|
|
431
|
+
credentialPath: proxyConfig.credentialPath,
|
|
432
|
+
hasApiKey: Boolean(proxyConfig.apiKey),
|
|
433
|
+
});
|
|
434
|
+
const rl = createInterface({ input: stdin, crlfDelay: Infinity, terminal: false });
|
|
435
|
+
rl.on("line", (line) => {
|
|
436
|
+
void handleLine(line);
|
|
437
|
+
});
|
|
438
|
+
rl.on("close", () => {
|
|
439
|
+
void closeAndExit(0);
|
|
440
|
+
});
|
|
441
|
+
process.on("SIGINT", () => {
|
|
442
|
+
void closeAndExit(130);
|
|
443
|
+
});
|
|
444
|
+
process.on("SIGTERM", () => {
|
|
445
|
+
void closeAndExit(143);
|
|
446
|
+
});
|
|
447
|
+
return { close: () => closeAndExit(0) };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return { start, handleMessage, parseSseMessages, closeSession };
|
|
451
|
+
}
|