noumen 0.2.0 → 0.4.0
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/README.md +95 -16
- package/dist/a2a/index.d.ts +5 -5
- package/dist/a2a/index.js +3 -3
- package/dist/a2a/index.js.map +1 -1
- package/dist/acp/index.d.ts +5 -5
- package/dist/acp/index.js +4 -4
- package/dist/acp/index.js.map +1 -1
- package/dist/{agent-BrkbZyOT.d.ts → agent-1nFVUP9E.d.ts} +319 -15
- package/dist/{cache-DVqaCX8v.d.ts → cache-DsRqxx6v.d.ts} +1 -1
- package/dist/{chunk-BGG2E6JD.js → chunk-3HEYCV26.js} +1 -1
- package/dist/chunk-3SK5GCI6.js +75 -0
- package/dist/chunk-3SK5GCI6.js.map +1 -0
- package/dist/{chunk-NBDFQYUZ.js → chunk-4HW6LN6D.js} +4784 -2411
- package/dist/chunk-4HW6LN6D.js.map +1 -0
- package/dist/{chunk-7ZMN7XJE.js → chunk-5JN4SPI7.js} +6 -6
- package/dist/chunk-5JN4SPI7.js.map +1 -0
- package/dist/{chunk-CPFHEPW4.js → chunk-CS6WNDCF.js} +73 -41
- package/dist/chunk-CS6WNDCF.js.map +1 -0
- package/dist/chunk-EKOGVTBT.js +472 -0
- package/dist/chunk-EKOGVTBT.js.map +1 -0
- package/dist/{chunk-KY6ZPWHO.js → chunk-HEQQQGK5.js} +47 -28
- package/dist/chunk-HEQQQGK5.js.map +1 -0
- package/dist/{chunk-QTJ7VTJY.js → chunk-HL6JCRZJ.js} +1599 -481
- package/dist/chunk-HL6JCRZJ.js.map +1 -0
- package/dist/chunk-L3L3FG5T.js +16 -0
- package/dist/chunk-L3L3FG5T.js.map +1 -0
- package/dist/cli/index.js +36 -30
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/{headless-Q7XHHZIW.js → headless-FFU2DESQ.js} +3 -4
- package/dist/headless-FFU2DESQ.js.map +1 -0
- package/dist/index.d.ts +218 -68
- package/dist/index.js +37 -23
- package/dist/lsp/index.d.ts +4 -4
- package/dist/mcp/index.d.ts +5 -5
- package/dist/mcp/index.js +2 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/{provider-factory-34MSWJZ3.js → provider-factory-KCLIF34X.js} +2 -2
- package/dist/providers/anthropic.d.ts +2 -2
- package/dist/providers/anthropic.js +5 -3
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/bedrock.d.ts +2 -2
- package/dist/providers/bedrock.js +5 -3
- package/dist/providers/bedrock.js.map +1 -1
- package/dist/providers/gemini.d.ts +2 -1
- package/dist/providers/gemini.js +133 -95
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/ollama.d.ts +13 -0
- package/dist/{ollama-YNXAYP3R.js → providers/ollama.js} +6 -4
- package/dist/providers/ollama.js.map +1 -0
- package/dist/providers/openai.d.ts +4 -1
- package/dist/providers/openai.js +2 -1
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/openrouter.js +2 -1
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/vertex.d.ts +4 -2
- package/dist/providers/vertex.js +6 -3
- package/dist/providers/vertex.js.map +1 -1
- package/dist/{resolve-XM52G7YE.js → resolve-4JA2BBDA.js} +2 -2
- package/dist/server/index.d.ts +35 -20
- package/dist/server/index.js +276 -207
- package/dist/server/index.js.map +1 -1
- package/dist/{server-Cg1yWGaV.d.ts → server-CHMxuWKq.d.ts} +1 -1
- package/dist/{types-DwdzmXfs.d.ts → types-CD0rUKKT.d.ts} +2 -0
- package/dist/{types-3c88cRKH.d.ts → types-LrU4LRmX.d.ts} +28 -0
- package/dist/{types-CwKKucOF.d.ts → types-RPKUTu1k.d.ts} +27 -2
- package/dist/uuid-RVN2T26F.js +8 -0
- package/dist/uuid-RVN2T26F.js.map +1 -0
- package/dist/zod-7YXKWYMC.js +12 -0
- package/dist/zod-7YXKWYMC.js.map +1 -0
- package/package.json +22 -13
- package/dist/chunk-2ZTGQLYK.js +0 -356
- package/dist/chunk-2ZTGQLYK.js.map +0 -1
- package/dist/chunk-7ZMN7XJE.js.map +0 -1
- package/dist/chunk-CPFHEPW4.js.map +0 -1
- package/dist/chunk-KY6ZPWHO.js.map +0 -1
- package/dist/chunk-NBDFQYUZ.js.map +0 -1
- package/dist/chunk-QTJ7VTJY.js.map +0 -1
- package/dist/headless-Q7XHHZIW.js.map +0 -1
- package/dist/ollama-YNXAYP3R.js.map +0 -1
- /package/dist/{chunk-BGG2E6JD.js.map → chunk-3HEYCV26.js.map} +0 -0
- /package/dist/{provider-factory-34MSWJZ3.js.map → provider-factory-KCLIF34X.js.map} +0 -0
- /package/dist/{resolve-XM52G7YE.js.map → resolve-4JA2BBDA.js.map} +0 -0
package/dist/server/index.js
CHANGED
|
@@ -2,13 +2,209 @@ import "../chunk-DGUM43GV.js";
|
|
|
2
2
|
|
|
3
3
|
// src/server/index.ts
|
|
4
4
|
import { createServer as createHttpServer } from "http";
|
|
5
|
+
|
|
6
|
+
// src/server/session-state.ts
|
|
5
7
|
import { randomUUID } from "crypto";
|
|
8
|
+
var DEFAULT_PENDING_TIMEOUT_MS = 12e4;
|
|
9
|
+
function createSessionState(sessions, requestedId, overrides) {
|
|
10
|
+
if (requestedId && sessions.has(requestedId)) {
|
|
11
|
+
throw new Error(`Session ${requestedId} already exists`);
|
|
12
|
+
}
|
|
13
|
+
const sessionId = requestedId ?? randomUUID();
|
|
14
|
+
const session = {
|
|
15
|
+
id: sessionId,
|
|
16
|
+
abortController: new AbortController(),
|
|
17
|
+
pendingPermission: null,
|
|
18
|
+
pendingInput: null,
|
|
19
|
+
pendingPermissionTimer: null,
|
|
20
|
+
pendingInputTimer: null,
|
|
21
|
+
lastActivity: Date.now(),
|
|
22
|
+
sseResponse: null,
|
|
23
|
+
sseKeepaliveTimer: null,
|
|
24
|
+
eventBuffer: [],
|
|
25
|
+
sequenceNum: 0,
|
|
26
|
+
done: false,
|
|
27
|
+
cwd: overrides.cwd
|
|
28
|
+
};
|
|
29
|
+
sessions.set(sessionId, session);
|
|
30
|
+
return session;
|
|
31
|
+
}
|
|
32
|
+
function destroySession(sessions, session) {
|
|
33
|
+
session.abortController.abort();
|
|
34
|
+
clearSseKeepalive(session);
|
|
35
|
+
clearPendingPermissionTimer(session);
|
|
36
|
+
clearPendingInputTimer(session);
|
|
37
|
+
if (session.pendingPermission) {
|
|
38
|
+
session.pendingPermission.reject(new Error("Session aborted"));
|
|
39
|
+
session.pendingPermission = null;
|
|
40
|
+
}
|
|
41
|
+
if (session.pendingInput) {
|
|
42
|
+
session.pendingInput.reject(new Error("Session aborted"));
|
|
43
|
+
session.pendingInput = null;
|
|
44
|
+
}
|
|
45
|
+
if (session.sseResponse) {
|
|
46
|
+
session.sseResponse.end();
|
|
47
|
+
session.sseResponse = null;
|
|
48
|
+
}
|
|
49
|
+
sessions.delete(session.id);
|
|
50
|
+
}
|
|
51
|
+
function reapIdleSessions(sessions, timeoutMs) {
|
|
52
|
+
if (!timeoutMs) return;
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
for (const session of sessions.values()) {
|
|
55
|
+
if (now - session.lastActivity > timeoutMs) {
|
|
56
|
+
destroySession(sessions, session);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function bridgePermission(sessions, sessionId, timeoutMs) {
|
|
61
|
+
const session = sessions.get(sessionId);
|
|
62
|
+
if (!session) return Promise.reject(new Error("Session not found"));
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
session.pendingPermission = { resolve, reject };
|
|
65
|
+
session.pendingPermissionTimer = setTimeout(() => {
|
|
66
|
+
session.pendingPermissionTimer = null;
|
|
67
|
+
if (session.pendingPermission) {
|
|
68
|
+
session.pendingPermission.reject(new Error("Permission request timed out"));
|
|
69
|
+
session.pendingPermission = null;
|
|
70
|
+
}
|
|
71
|
+
}, timeoutMs);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function bridgeUserInput(sessions, sessionId, timeoutMs) {
|
|
75
|
+
const session = sessions.get(sessionId);
|
|
76
|
+
if (!session) return Promise.reject(new Error("Session not found"));
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
session.pendingInput = { resolve, reject };
|
|
79
|
+
session.pendingInputTimer = setTimeout(() => {
|
|
80
|
+
session.pendingInputTimer = null;
|
|
81
|
+
if (session.pendingInput) {
|
|
82
|
+
session.pendingInput.reject(new Error("User input request timed out"));
|
|
83
|
+
session.pendingInput = null;
|
|
84
|
+
}
|
|
85
|
+
}, timeoutMs);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function clearPendingPermissionTimer(session) {
|
|
89
|
+
if (session.pendingPermissionTimer) {
|
|
90
|
+
clearTimeout(session.pendingPermissionTimer);
|
|
91
|
+
session.pendingPermissionTimer = null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function clearPendingInputTimer(session) {
|
|
95
|
+
if (session.pendingInputTimer) {
|
|
96
|
+
clearTimeout(session.pendingInputTimer);
|
|
97
|
+
session.pendingInputTimer = null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function clearSseKeepalive(session) {
|
|
101
|
+
if (session.sseKeepaliveTimer) {
|
|
102
|
+
clearInterval(session.sseKeepaliveTimer);
|
|
103
|
+
session.sseKeepaliveTimer = null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/server/event-buffer.ts
|
|
108
|
+
var MAX_EVENT_BUFFER = 1e3;
|
|
109
|
+
function serializeEvent(event) {
|
|
110
|
+
if (event.type === "error") {
|
|
111
|
+
return { type: "error", error: { message: event.error.message, name: event.error.name } };
|
|
112
|
+
}
|
|
113
|
+
if (event.type === "retry_exhausted") {
|
|
114
|
+
return { ...event, error: { message: event.error.message, name: event.error.name } };
|
|
115
|
+
}
|
|
116
|
+
if (event.type === "retry_attempt") {
|
|
117
|
+
return { ...event, error: { message: event.error.message, name: event.error.name } };
|
|
118
|
+
}
|
|
119
|
+
return event;
|
|
120
|
+
}
|
|
121
|
+
function pushEvent(session, event) {
|
|
122
|
+
session.sequenceNum++;
|
|
123
|
+
const seq = session.sequenceNum;
|
|
124
|
+
if (session.eventBuffer.length >= MAX_EVENT_BUFFER) {
|
|
125
|
+
session.eventBuffer.shift();
|
|
126
|
+
}
|
|
127
|
+
session.eventBuffer.push({ seq, event });
|
|
128
|
+
if (session.sseResponse) {
|
|
129
|
+
writeSseEventRaw(session.sseResponse, seq, serializeEvent(event));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function getBufferedEventsAfter(buffer, afterSeq) {
|
|
133
|
+
if (!afterSeq) return [...buffer];
|
|
134
|
+
return buffer.filter((e) => e.seq > afterSeq);
|
|
135
|
+
}
|
|
136
|
+
function writeSseEventRaw(res, seq, data) {
|
|
137
|
+
res.write(`id: ${seq}
|
|
138
|
+
data: ${JSON.stringify(data)}
|
|
139
|
+
|
|
140
|
+
`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/server/ws-dispatch.ts
|
|
144
|
+
function parseWsMessage(raw) {
|
|
145
|
+
try {
|
|
146
|
+
return JSON.parse(typeof raw === "string" ? raw : raw.toString());
|
|
147
|
+
} catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async function handleWsMessage(msg, ctx, callbacks) {
|
|
152
|
+
const msgType = msg.type;
|
|
153
|
+
if (msgType === "run") {
|
|
154
|
+
if (ctx.maxSessions && ctx.currentSessionCount >= ctx.maxSessions) {
|
|
155
|
+
return { type: "error", error: "Maximum sessions reached" };
|
|
156
|
+
}
|
|
157
|
+
if (typeof msg.prompt !== "string" || !msg.prompt.trim()) {
|
|
158
|
+
return { type: "error", error: "Missing or empty prompt" };
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const sessionId = await callbacks.onRun(
|
|
162
|
+
msg.prompt,
|
|
163
|
+
msg.sessionId
|
|
164
|
+
);
|
|
165
|
+
return { type: "session_created", sessionId };
|
|
166
|
+
} catch (err) {
|
|
167
|
+
return { type: "error", error: err instanceof Error ? err.message : String(err) };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (msgType === "message") {
|
|
171
|
+
if (typeof msg.prompt !== "string" || !msg.prompt.trim()) {
|
|
172
|
+
return { type: "error", error: "Missing or empty prompt" };
|
|
173
|
+
}
|
|
174
|
+
const sessionId = msg.sessionId;
|
|
175
|
+
if (!sessionId) {
|
|
176
|
+
return { type: "error", error: "Missing sessionId" };
|
|
177
|
+
}
|
|
178
|
+
callbacks.onMessage(sessionId, msg.prompt);
|
|
179
|
+
return { type: "ok" };
|
|
180
|
+
}
|
|
181
|
+
if (msgType === "permission_response") {
|
|
182
|
+
const sessionId = msg.sessionId;
|
|
183
|
+
const { sessionId: _sid, type: _type, ...response } = msg;
|
|
184
|
+
callbacks.onPermissionResponse(sessionId, response);
|
|
185
|
+
return { type: "ok" };
|
|
186
|
+
}
|
|
187
|
+
if (msgType === "input_response") {
|
|
188
|
+
const sessionId = msg.sessionId;
|
|
189
|
+
callbacks.onInputResponse(sessionId, msg.answer ?? "");
|
|
190
|
+
return { type: "ok" };
|
|
191
|
+
}
|
|
192
|
+
if (msgType === "abort") {
|
|
193
|
+
const sessionId = msg.sessionId;
|
|
194
|
+
if (sessionId) callbacks.onAbort(sessionId);
|
|
195
|
+
return { type: "ok" };
|
|
196
|
+
}
|
|
197
|
+
return { type: "ok" };
|
|
198
|
+
}
|
|
199
|
+
function wsSend(ws, data) {
|
|
200
|
+
if (ws.readyState === 1) ws.send(JSON.stringify(data));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/server/index.ts
|
|
6
204
|
var SSE_KEEPALIVE_INTERVAL_MS = 15e3;
|
|
7
205
|
var MAX_BODY_BYTES = 1048576;
|
|
8
|
-
var MAX_EVENT_BUFFER = 1e3;
|
|
9
|
-
var DEFAULT_PENDING_TIMEOUT_MS = 12e4;
|
|
10
|
-
var WS_PING_INTERVAL_MS = 3e4;
|
|
11
206
|
var SHUTDOWN_DRAIN_MS = 500;
|
|
207
|
+
var WS_PING_INTERVAL_MS = 3e4;
|
|
12
208
|
var NoumenServer = class {
|
|
13
209
|
code;
|
|
14
210
|
options;
|
|
@@ -27,7 +223,7 @@ var NoumenServer = class {
|
|
|
27
223
|
}
|
|
28
224
|
this.ensureIdleReaper();
|
|
29
225
|
return new Promise((resolve, reject) => {
|
|
30
|
-
const host = this.options.host ?? "
|
|
226
|
+
const host = this.options.host ?? "127.0.0.1";
|
|
31
227
|
this.httpServer.listen(this.options.port, host, () => resolve());
|
|
32
228
|
this.httpServer.once("error", reject);
|
|
33
229
|
});
|
|
@@ -42,7 +238,7 @@ var NoumenServer = class {
|
|
|
42
238
|
}
|
|
43
239
|
await new Promise((resolve) => setTimeout(resolve, SHUTDOWN_DRAIN_MS));
|
|
44
240
|
for (const session of this.sessions.values()) {
|
|
45
|
-
this.
|
|
241
|
+
destroySession(this.sessions, session);
|
|
46
242
|
}
|
|
47
243
|
if (this.wss) {
|
|
48
244
|
await new Promise((resolve) => this.wss.close(() => resolve()));
|
|
@@ -102,7 +298,7 @@ var NoumenServer = class {
|
|
|
102
298
|
if (this.idleReaperStarted || !this.options.idleTimeoutMs) return;
|
|
103
299
|
this.idleReaperStarted = true;
|
|
104
300
|
const interval = Math.max(this.options.idleTimeoutMs / 2, 1e3);
|
|
105
|
-
this.idleTimer = setInterval(() => this.
|
|
301
|
+
this.idleTimer = setInterval(() => reapIdleSessions(this.sessions, this.options.idleTimeoutMs), interval);
|
|
106
302
|
this.idleTimer.unref();
|
|
107
303
|
}
|
|
108
304
|
// -------------------------------------------------------------------------
|
|
@@ -175,7 +371,7 @@ var NoumenServer = class {
|
|
|
175
371
|
return jsonResponse(res, 429, { error: "Maximum sessions reached" });
|
|
176
372
|
}
|
|
177
373
|
const overrides = await this.resolveConnectionOverrides(req);
|
|
178
|
-
const session = this.
|
|
374
|
+
const session = createSessionState(this.sessions, requestedId, overrides);
|
|
179
375
|
this.runAgentSse(session, prompt, false);
|
|
180
376
|
jsonResponse(res, 201, {
|
|
181
377
|
sessionId: session.id,
|
|
@@ -197,7 +393,7 @@ var NoumenServer = class {
|
|
|
197
393
|
const oldRes = session.sseResponse;
|
|
198
394
|
writeSseEventRaw(oldRes, session.sequenceNum + 1, { type: "subscriber_replaced" });
|
|
199
395
|
oldRes.end();
|
|
200
|
-
|
|
396
|
+
clearSseKeepalive(session);
|
|
201
397
|
session.sseResponse = null;
|
|
202
398
|
}
|
|
203
399
|
res.writeHead(200, {
|
|
@@ -208,8 +404,8 @@ var NoumenServer = class {
|
|
|
208
404
|
});
|
|
209
405
|
const lastEventId = req.headers["last-event-id"];
|
|
210
406
|
const resumeAfterSeq = lastEventId ? parseInt(lastEventId, 10) : 0;
|
|
211
|
-
|
|
212
|
-
|
|
407
|
+
const eventsToReplay = getBufferedEventsAfter(session.eventBuffer, resumeAfterSeq);
|
|
408
|
+
for (const buffered of eventsToReplay) {
|
|
213
409
|
writeSseEventRaw(res, buffered.seq, serializeEvent(buffered.event));
|
|
214
410
|
}
|
|
215
411
|
session.eventBuffer = [];
|
|
@@ -217,7 +413,7 @@ var NoumenServer = class {
|
|
|
217
413
|
this.startSseKeepalive(session);
|
|
218
414
|
res.on("close", () => {
|
|
219
415
|
if (session.sseResponse === res) {
|
|
220
|
-
|
|
416
|
+
clearSseKeepalive(session);
|
|
221
417
|
session.sseResponse = null;
|
|
222
418
|
}
|
|
223
419
|
});
|
|
@@ -227,7 +423,7 @@ var NoumenServer = class {
|
|
|
227
423
|
if (!session) return jsonResponse(res, 404, { error: "Session not found" });
|
|
228
424
|
if (!session.pendingPermission) return jsonResponse(res, 409, { error: "No pending permission request" });
|
|
229
425
|
const body = await readBody(req);
|
|
230
|
-
|
|
426
|
+
clearPendingPermissionTimer(session);
|
|
231
427
|
session.pendingPermission.resolve(body);
|
|
232
428
|
session.pendingPermission = null;
|
|
233
429
|
jsonResponse(res, 200, { ok: true });
|
|
@@ -240,7 +436,7 @@ var NoumenServer = class {
|
|
|
240
436
|
if (typeof body.answer !== "string") {
|
|
241
437
|
return jsonResponse(res, 400, { error: "Missing required field: answer" });
|
|
242
438
|
}
|
|
243
|
-
|
|
439
|
+
clearPendingInputTimer(session);
|
|
244
440
|
session.pendingInput.resolve(body.answer);
|
|
245
441
|
session.pendingInput = null;
|
|
246
442
|
jsonResponse(res, 200, { ok: true });
|
|
@@ -261,7 +457,7 @@ var NoumenServer = class {
|
|
|
261
457
|
handleDeleteSession(sessionId, res) {
|
|
262
458
|
const session = this.sessions.get(sessionId);
|
|
263
459
|
if (!session) return jsonResponse(res, 404, { error: "Session not found" });
|
|
264
|
-
this.
|
|
460
|
+
destroySession(this.sessions, session);
|
|
265
461
|
jsonResponse(res, 200, { ok: true });
|
|
266
462
|
}
|
|
267
463
|
// -------------------------------------------------------------------------
|
|
@@ -300,10 +496,63 @@ var NoumenServer = class {
|
|
|
300
496
|
ws.on("pong", () => {
|
|
301
497
|
pongReceived = true;
|
|
302
498
|
});
|
|
499
|
+
const callbacks = {
|
|
500
|
+
onRun: async (prompt, requestedSessionId) => {
|
|
501
|
+
const overrides = await this.resolveConnectionOverrides(req);
|
|
502
|
+
const session = createSessionState(this.sessions, requestedSessionId, overrides);
|
|
503
|
+
wsSessions.add(session.id);
|
|
504
|
+
this.runAgentWs(session, prompt, ws, false);
|
|
505
|
+
return session.id;
|
|
506
|
+
},
|
|
507
|
+
onMessage: (sessionId, prompt) => {
|
|
508
|
+
const session = this.sessions.get(sessionId);
|
|
509
|
+
if (!session) {
|
|
510
|
+
wsSend(ws, { type: "error", error: "Session not found" });
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (!session.done) {
|
|
514
|
+
wsSend(ws, { type: "error", error: "Session is still running" });
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
session.done = false;
|
|
518
|
+
session.abortController = new AbortController();
|
|
519
|
+
this.runAgentWs(session, prompt, ws, true);
|
|
520
|
+
},
|
|
521
|
+
onPermissionResponse: (sessionId, response) => {
|
|
522
|
+
const session = this.sessions.get(sessionId);
|
|
523
|
+
if (!session?.pendingPermission) return;
|
|
524
|
+
clearPendingPermissionTimer(session);
|
|
525
|
+
session.pendingPermission.resolve(response);
|
|
526
|
+
session.pendingPermission = null;
|
|
527
|
+
},
|
|
528
|
+
onInputResponse: (sessionId, answer) => {
|
|
529
|
+
const session = this.sessions.get(sessionId);
|
|
530
|
+
if (!session?.pendingInput) return;
|
|
531
|
+
clearPendingInputTimer(session);
|
|
532
|
+
session.pendingInput.resolve(answer);
|
|
533
|
+
session.pendingInput = null;
|
|
534
|
+
},
|
|
535
|
+
onAbort: (sessionId) => {
|
|
536
|
+
const session = this.sessions.get(sessionId);
|
|
537
|
+
if (session) destroySession(this.sessions, session);
|
|
538
|
+
}
|
|
539
|
+
};
|
|
303
540
|
ws.on("message", async (raw) => {
|
|
304
541
|
try {
|
|
305
|
-
const msg =
|
|
306
|
-
|
|
542
|
+
const msg = parseWsMessage(raw);
|
|
543
|
+
if (!msg) {
|
|
544
|
+
wsSend(ws, { type: "error", error: "Invalid JSON" });
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const result = await handleWsMessage(msg, {
|
|
548
|
+
maxSessions: this.options.maxSessions,
|
|
549
|
+
currentSessionCount: this.sessions.size
|
|
550
|
+
}, callbacks);
|
|
551
|
+
if (result.type === "error") {
|
|
552
|
+
wsSend(ws, { type: "error", error: result.error });
|
|
553
|
+
} else if (result.type === "session_created") {
|
|
554
|
+
wsSend(ws, { type: "session_created", sessionId: result.sessionId });
|
|
555
|
+
}
|
|
307
556
|
} catch (err) {
|
|
308
557
|
wsSend(ws, { type: "error", error: String(err) });
|
|
309
558
|
}
|
|
@@ -312,106 +561,36 @@ var NoumenServer = class {
|
|
|
312
561
|
clearInterval(pingTimer);
|
|
313
562
|
for (const sid of wsSessions) {
|
|
314
563
|
const session = this.sessions.get(sid);
|
|
315
|
-
if (session) this.
|
|
564
|
+
if (session) destroySession(this.sessions, session);
|
|
316
565
|
}
|
|
317
566
|
});
|
|
318
567
|
ws.on("error", () => {
|
|
319
568
|
clearInterval(pingTimer);
|
|
320
569
|
});
|
|
321
570
|
}
|
|
322
|
-
async handleWsMessage(ws, msg, wsSessions, req) {
|
|
323
|
-
const msgType = msg.type;
|
|
324
|
-
if (msgType === "run") {
|
|
325
|
-
if (this.options.maxSessions && this.sessions.size >= this.options.maxSessions) {
|
|
326
|
-
wsSend(ws, { type: "error", error: "Maximum sessions reached" });
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
const overrides = await this.resolveConnectionOverrides(req);
|
|
330
|
-
const session = this.createSessionState(msg.sessionId, overrides);
|
|
331
|
-
wsSessions.add(session.id);
|
|
332
|
-
wsSend(ws, { type: "session_created", sessionId: session.id });
|
|
333
|
-
this.runAgentWs(session, msg.prompt, ws, false);
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
if (msgType === "message") {
|
|
337
|
-
const session = this.sessions.get(msg.sessionId);
|
|
338
|
-
if (!session) {
|
|
339
|
-
wsSend(ws, { type: "error", error: "Session not found" });
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
if (!session.done) {
|
|
343
|
-
wsSend(ws, { type: "error", error: "Session is still running" });
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
session.done = false;
|
|
347
|
-
session.abortController = new AbortController();
|
|
348
|
-
this.runAgentWs(session, msg.prompt, ws, true);
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
if (msgType === "permission_response") {
|
|
352
|
-
const session = this.sessions.get(msg.sessionId);
|
|
353
|
-
if (!session?.pendingPermission) return;
|
|
354
|
-
this.clearPendingPermissionTimer(session);
|
|
355
|
-
const { sessionId: _sid, type: _type, ...response } = msg;
|
|
356
|
-
session.pendingPermission.resolve(response);
|
|
357
|
-
session.pendingPermission = null;
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
if (msgType === "input_response") {
|
|
361
|
-
const session = this.sessions.get(msg.sessionId);
|
|
362
|
-
if (!session?.pendingInput) return;
|
|
363
|
-
this.clearPendingInputTimer(session);
|
|
364
|
-
session.pendingInput.resolve(msg.answer ?? "");
|
|
365
|
-
session.pendingInput = null;
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
if (msgType === "abort") {
|
|
369
|
-
const session = this.sessions.get(msg.sessionId);
|
|
370
|
-
if (session) this.destroySession(session);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
571
|
// -------------------------------------------------------------------------
|
|
374
|
-
//
|
|
572
|
+
// Agent runners
|
|
375
573
|
// -------------------------------------------------------------------------
|
|
376
|
-
|
|
377
|
-
const
|
|
378
|
-
const session = {
|
|
379
|
-
id: sessionId,
|
|
380
|
-
abortController: new AbortController(),
|
|
381
|
-
pendingPermission: null,
|
|
382
|
-
pendingInput: null,
|
|
383
|
-
pendingPermissionTimer: null,
|
|
384
|
-
pendingInputTimer: null,
|
|
385
|
-
lastActivity: Date.now(),
|
|
386
|
-
sseResponse: null,
|
|
387
|
-
sseKeepaliveTimer: null,
|
|
388
|
-
eventBuffer: [],
|
|
389
|
-
sequenceNum: 0,
|
|
390
|
-
done: false,
|
|
391
|
-
cwd: overrides.cwd
|
|
392
|
-
};
|
|
393
|
-
this.sessions.set(sessionId, session);
|
|
394
|
-
return session;
|
|
395
|
-
}
|
|
396
|
-
makeThread(session, resume) {
|
|
574
|
+
async makeThread(session, resume) {
|
|
575
|
+
const timeoutMs = this.options.pendingTimeoutMs ?? DEFAULT_PENDING_TIMEOUT_MS;
|
|
397
576
|
const handlers = {
|
|
398
577
|
cwd: session.cwd,
|
|
399
|
-
permissionHandler: (
|
|
400
|
-
userInputHandler: (
|
|
578
|
+
permissionHandler: (_req) => bridgePermission(this.sessions, session.id, timeoutMs),
|
|
579
|
+
userInputHandler: (_q) => bridgeUserInput(this.sessions, session.id, timeoutMs)
|
|
401
580
|
};
|
|
402
581
|
return resume ? this.code.resumeThread(session.id, handlers) : this.code.createThread({ sessionId: session.id, ...handlers });
|
|
403
582
|
}
|
|
404
583
|
runAgentSse(session, prompt, resume) {
|
|
405
584
|
const run = async () => {
|
|
406
585
|
try {
|
|
407
|
-
const thread = this.makeThread(session, resume);
|
|
586
|
+
const thread = await this.makeThread(session, resume);
|
|
408
587
|
for await (const event of thread.run(prompt, { signal: session.abortController.signal })) {
|
|
409
|
-
|
|
588
|
+
pushEvent(session, event);
|
|
410
589
|
session.lastActivity = Date.now();
|
|
411
590
|
}
|
|
412
591
|
} catch (err) {
|
|
413
592
|
if (err.name !== "AbortError") {
|
|
414
|
-
|
|
593
|
+
pushEvent(session, {
|
|
415
594
|
type: "error",
|
|
416
595
|
error: err instanceof Error ? err : new Error(String(err))
|
|
417
596
|
});
|
|
@@ -425,7 +604,7 @@ var NoumenServer = class {
|
|
|
425
604
|
runAgentWs(session, prompt, ws, resume) {
|
|
426
605
|
const run = async () => {
|
|
427
606
|
try {
|
|
428
|
-
const thread = this.makeThread(session, resume);
|
|
607
|
+
const thread = await this.makeThread(session, resume);
|
|
429
608
|
for await (const event of thread.run(prompt, { signal: session.abortController.signal })) {
|
|
430
609
|
session.sequenceNum++;
|
|
431
610
|
wsSend(ws, { ...serializeEvent(event), sessionId: session.id, seq: session.sequenceNum });
|
|
@@ -441,50 +620,8 @@ var NoumenServer = class {
|
|
|
441
620
|
};
|
|
442
621
|
run().catch((err) => this.options.onError?.(err instanceof Error ? err : new Error(String(err))));
|
|
443
622
|
}
|
|
444
|
-
emitSseEvent(session, event) {
|
|
445
|
-
session.sequenceNum++;
|
|
446
|
-
const seq = session.sequenceNum;
|
|
447
|
-
if (session.sseResponse) {
|
|
448
|
-
writeSseEventRaw(session.sseResponse, seq, serializeEvent(event));
|
|
449
|
-
} else {
|
|
450
|
-
if (session.eventBuffer.length >= MAX_EVENT_BUFFER) {
|
|
451
|
-
session.eventBuffer.shift();
|
|
452
|
-
}
|
|
453
|
-
session.eventBuffer.push({ seq, event });
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
bridgePermission(sessionId, _request) {
|
|
457
|
-
const session = this.sessions.get(sessionId);
|
|
458
|
-
if (!session) return Promise.reject(new Error("Session not found"));
|
|
459
|
-
const timeoutMs = this.options.pendingTimeoutMs ?? DEFAULT_PENDING_TIMEOUT_MS;
|
|
460
|
-
return new Promise((resolve, reject) => {
|
|
461
|
-
session.pendingPermission = { resolve, reject };
|
|
462
|
-
session.pendingPermissionTimer = setTimeout(() => {
|
|
463
|
-
session.pendingPermissionTimer = null;
|
|
464
|
-
if (session.pendingPermission) {
|
|
465
|
-
session.pendingPermission.reject(new Error("Permission request timed out"));
|
|
466
|
-
session.pendingPermission = null;
|
|
467
|
-
}
|
|
468
|
-
}, timeoutMs);
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
bridgeUserInput(sessionId, _question) {
|
|
472
|
-
const session = this.sessions.get(sessionId);
|
|
473
|
-
if (!session) return Promise.reject(new Error("Session not found"));
|
|
474
|
-
const timeoutMs = this.options.pendingTimeoutMs ?? DEFAULT_PENDING_TIMEOUT_MS;
|
|
475
|
-
return new Promise((resolve, reject) => {
|
|
476
|
-
session.pendingInput = { resolve, reject };
|
|
477
|
-
session.pendingInputTimer = setTimeout(() => {
|
|
478
|
-
session.pendingInputTimer = null;
|
|
479
|
-
if (session.pendingInput) {
|
|
480
|
-
session.pendingInput.reject(new Error("User input request timed out"));
|
|
481
|
-
session.pendingInput = null;
|
|
482
|
-
}
|
|
483
|
-
}, timeoutMs);
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
623
|
startSseKeepalive(session) {
|
|
487
|
-
|
|
624
|
+
clearSseKeepalive(session);
|
|
488
625
|
session.sseKeepaliveTimer = setInterval(() => {
|
|
489
626
|
if (session.sseResponse && !session.sseResponse.destroyed) {
|
|
490
627
|
session.sseResponse.write(":keepalive\n\n");
|
|
@@ -492,53 +629,6 @@ var NoumenServer = class {
|
|
|
492
629
|
}, SSE_KEEPALIVE_INTERVAL_MS);
|
|
493
630
|
session.sseKeepaliveTimer.unref();
|
|
494
631
|
}
|
|
495
|
-
clearSseKeepalive(session) {
|
|
496
|
-
if (session.sseKeepaliveTimer) {
|
|
497
|
-
clearInterval(session.sseKeepaliveTimer);
|
|
498
|
-
session.sseKeepaliveTimer = null;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
clearPendingPermissionTimer(session) {
|
|
502
|
-
if (session.pendingPermissionTimer) {
|
|
503
|
-
clearTimeout(session.pendingPermissionTimer);
|
|
504
|
-
session.pendingPermissionTimer = null;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
clearPendingInputTimer(session) {
|
|
508
|
-
if (session.pendingInputTimer) {
|
|
509
|
-
clearTimeout(session.pendingInputTimer);
|
|
510
|
-
session.pendingInputTimer = null;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
destroySession(session) {
|
|
514
|
-
session.abortController.abort();
|
|
515
|
-
this.clearSseKeepalive(session);
|
|
516
|
-
this.clearPendingPermissionTimer(session);
|
|
517
|
-
this.clearPendingInputTimer(session);
|
|
518
|
-
if (session.pendingPermission) {
|
|
519
|
-
session.pendingPermission.reject(new Error("Session aborted"));
|
|
520
|
-
session.pendingPermission = null;
|
|
521
|
-
}
|
|
522
|
-
if (session.pendingInput) {
|
|
523
|
-
session.pendingInput.reject(new Error("Session aborted"));
|
|
524
|
-
session.pendingInput = null;
|
|
525
|
-
}
|
|
526
|
-
if (session.sseResponse) {
|
|
527
|
-
session.sseResponse.end();
|
|
528
|
-
session.sseResponse = null;
|
|
529
|
-
}
|
|
530
|
-
this.sessions.delete(session.id);
|
|
531
|
-
}
|
|
532
|
-
reapIdleSessions() {
|
|
533
|
-
const timeout = this.options.idleTimeoutMs;
|
|
534
|
-
if (!timeout) return;
|
|
535
|
-
const now = Date.now();
|
|
536
|
-
for (const session of this.sessions.values()) {
|
|
537
|
-
if (now - session.lastActivity > timeout) {
|
|
538
|
-
this.destroySession(session);
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
632
|
async resolveConnectionOverrides(req) {
|
|
543
633
|
if (!this.options.onConnection) return {};
|
|
544
634
|
const auth = await this.authenticate(req) ?? {};
|
|
@@ -567,24 +657,6 @@ function jsonResponse(res, status, body) {
|
|
|
567
657
|
});
|
|
568
658
|
res.end(json);
|
|
569
659
|
}
|
|
570
|
-
function serializeEvent(event) {
|
|
571
|
-
if (event.type === "error") {
|
|
572
|
-
return { type: "error", error: { message: event.error.message, name: event.error.name } };
|
|
573
|
-
}
|
|
574
|
-
if (event.type === "retry_exhausted") {
|
|
575
|
-
return { ...event, error: { message: event.error.message, name: event.error.name } };
|
|
576
|
-
}
|
|
577
|
-
if (event.type === "retry_attempt") {
|
|
578
|
-
return { ...event, error: { message: event.error.message, name: event.error.name } };
|
|
579
|
-
}
|
|
580
|
-
return event;
|
|
581
|
-
}
|
|
582
|
-
function writeSseEventRaw(res, seq, data) {
|
|
583
|
-
res.write(`id: ${seq}
|
|
584
|
-
data: ${JSON.stringify(data)}
|
|
585
|
-
|
|
586
|
-
`);
|
|
587
|
-
}
|
|
588
660
|
function readBody(req) {
|
|
589
661
|
return new Promise((resolve, reject) => {
|
|
590
662
|
let totalBytes = 0;
|
|
@@ -615,9 +687,6 @@ function readBody(req) {
|
|
|
615
687
|
});
|
|
616
688
|
});
|
|
617
689
|
}
|
|
618
|
-
function wsSend(ws, data) {
|
|
619
|
-
if (ws.readyState === 1) ws.send(JSON.stringify(data));
|
|
620
|
-
}
|
|
621
690
|
export {
|
|
622
691
|
NoumenServer,
|
|
623
692
|
createRequestHandler,
|