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.
Files changed (83) hide show
  1. package/README.md +95 -16
  2. package/dist/a2a/index.d.ts +5 -5
  3. package/dist/a2a/index.js +3 -3
  4. package/dist/a2a/index.js.map +1 -1
  5. package/dist/acp/index.d.ts +5 -5
  6. package/dist/acp/index.js +4 -4
  7. package/dist/acp/index.js.map +1 -1
  8. package/dist/{agent-BrkbZyOT.d.ts → agent-1nFVUP9E.d.ts} +319 -15
  9. package/dist/{cache-DVqaCX8v.d.ts → cache-DsRqxx6v.d.ts} +1 -1
  10. package/dist/{chunk-BGG2E6JD.js → chunk-3HEYCV26.js} +1 -1
  11. package/dist/chunk-3SK5GCI6.js +75 -0
  12. package/dist/chunk-3SK5GCI6.js.map +1 -0
  13. package/dist/{chunk-NBDFQYUZ.js → chunk-4HW6LN6D.js} +4784 -2411
  14. package/dist/chunk-4HW6LN6D.js.map +1 -0
  15. package/dist/{chunk-7ZMN7XJE.js → chunk-5JN4SPI7.js} +6 -6
  16. package/dist/chunk-5JN4SPI7.js.map +1 -0
  17. package/dist/{chunk-CPFHEPW4.js → chunk-CS6WNDCF.js} +73 -41
  18. package/dist/chunk-CS6WNDCF.js.map +1 -0
  19. package/dist/chunk-EKOGVTBT.js +472 -0
  20. package/dist/chunk-EKOGVTBT.js.map +1 -0
  21. package/dist/{chunk-KY6ZPWHO.js → chunk-HEQQQGK5.js} +47 -28
  22. package/dist/chunk-HEQQQGK5.js.map +1 -0
  23. package/dist/{chunk-QTJ7VTJY.js → chunk-HL6JCRZJ.js} +1599 -481
  24. package/dist/chunk-HL6JCRZJ.js.map +1 -0
  25. package/dist/chunk-L3L3FG5T.js +16 -0
  26. package/dist/chunk-L3L3FG5T.js.map +1 -0
  27. package/dist/cli/index.js +36 -30
  28. package/dist/cli/index.js.map +1 -1
  29. package/dist/client/index.d.ts +2 -2
  30. package/dist/{headless-Q7XHHZIW.js → headless-FFU2DESQ.js} +3 -4
  31. package/dist/headless-FFU2DESQ.js.map +1 -0
  32. package/dist/index.d.ts +218 -68
  33. package/dist/index.js +37 -23
  34. package/dist/lsp/index.d.ts +4 -4
  35. package/dist/mcp/index.d.ts +5 -5
  36. package/dist/mcp/index.js +2 -1
  37. package/dist/mcp/index.js.map +1 -1
  38. package/dist/{provider-factory-34MSWJZ3.js → provider-factory-KCLIF34X.js} +2 -2
  39. package/dist/providers/anthropic.d.ts +2 -2
  40. package/dist/providers/anthropic.js +5 -3
  41. package/dist/providers/anthropic.js.map +1 -1
  42. package/dist/providers/bedrock.d.ts +2 -2
  43. package/dist/providers/bedrock.js +5 -3
  44. package/dist/providers/bedrock.js.map +1 -1
  45. package/dist/providers/gemini.d.ts +2 -1
  46. package/dist/providers/gemini.js +133 -95
  47. package/dist/providers/gemini.js.map +1 -1
  48. package/dist/providers/ollama.d.ts +13 -0
  49. package/dist/{ollama-YNXAYP3R.js → providers/ollama.js} +6 -4
  50. package/dist/providers/ollama.js.map +1 -0
  51. package/dist/providers/openai.d.ts +4 -1
  52. package/dist/providers/openai.js +2 -1
  53. package/dist/providers/openrouter.d.ts +1 -1
  54. package/dist/providers/openrouter.js +2 -1
  55. package/dist/providers/openrouter.js.map +1 -1
  56. package/dist/providers/vertex.d.ts +4 -2
  57. package/dist/providers/vertex.js +6 -3
  58. package/dist/providers/vertex.js.map +1 -1
  59. package/dist/{resolve-XM52G7YE.js → resolve-4JA2BBDA.js} +2 -2
  60. package/dist/server/index.d.ts +35 -20
  61. package/dist/server/index.js +276 -207
  62. package/dist/server/index.js.map +1 -1
  63. package/dist/{server-Cg1yWGaV.d.ts → server-CHMxuWKq.d.ts} +1 -1
  64. package/dist/{types-DwdzmXfs.d.ts → types-CD0rUKKT.d.ts} +2 -0
  65. package/dist/{types-3c88cRKH.d.ts → types-LrU4LRmX.d.ts} +28 -0
  66. package/dist/{types-CwKKucOF.d.ts → types-RPKUTu1k.d.ts} +27 -2
  67. package/dist/uuid-RVN2T26F.js +8 -0
  68. package/dist/uuid-RVN2T26F.js.map +1 -0
  69. package/dist/zod-7YXKWYMC.js +12 -0
  70. package/dist/zod-7YXKWYMC.js.map +1 -0
  71. package/package.json +22 -13
  72. package/dist/chunk-2ZTGQLYK.js +0 -356
  73. package/dist/chunk-2ZTGQLYK.js.map +0 -1
  74. package/dist/chunk-7ZMN7XJE.js.map +0 -1
  75. package/dist/chunk-CPFHEPW4.js.map +0 -1
  76. package/dist/chunk-KY6ZPWHO.js.map +0 -1
  77. package/dist/chunk-NBDFQYUZ.js.map +0 -1
  78. package/dist/chunk-QTJ7VTJY.js.map +0 -1
  79. package/dist/headless-Q7XHHZIW.js.map +0 -1
  80. package/dist/ollama-YNXAYP3R.js.map +0 -1
  81. /package/dist/{chunk-BGG2E6JD.js.map → chunk-3HEYCV26.js.map} +0 -0
  82. /package/dist/{provider-factory-34MSWJZ3.js.map → provider-factory-KCLIF34X.js.map} +0 -0
  83. /package/dist/{resolve-XM52G7YE.js.map → resolve-4JA2BBDA.js.map} +0 -0
@@ -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 ?? "0.0.0.0";
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.destroySession(session);
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.reapIdleSessions(), interval);
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.createSessionState(requestedId, overrides);
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
- this.clearSseKeepalive(session);
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
- for (const buffered of session.eventBuffer) {
212
- if (resumeAfterSeq && buffered.seq <= resumeAfterSeq) continue;
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
- this.clearSseKeepalive(session);
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
- this.clearPendingPermissionTimer(session);
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
- this.clearPendingInputTimer(session);
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.destroySession(session);
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 = JSON.parse(typeof raw === "string" ? raw : raw.toString());
306
- await this.handleWsMessage(ws, msg, wsSessions, req);
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.destroySession(session);
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
- // Session management
572
+ // Agent runners
375
573
  // -------------------------------------------------------------------------
376
- createSessionState(requestedId, overrides) {
377
- const sessionId = requestedId ?? randomUUID();
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: (req) => this.bridgePermission(session.id, req),
400
- userInputHandler: (q) => this.bridgeUserInput(session.id, q)
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
- this.emitSseEvent(session, event);
588
+ pushEvent(session, event);
410
589
  session.lastActivity = Date.now();
411
590
  }
412
591
  } catch (err) {
413
592
  if (err.name !== "AbortError") {
414
- this.emitSseEvent(session, {
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
- this.clearSseKeepalive(session);
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,