@schlessera/brain-ui-server 0.15.0 → 0.16.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 (144) hide show
  1. package/README.md +3 -0
  2. package/dist/agent/backend.d.ts.map +1 -1
  3. package/dist/agent/backend.js +6 -2
  4. package/dist/agent/backend.js.map +1 -1
  5. package/dist/app.d.ts +9 -0
  6. package/dist/app.d.ts.map +1 -1
  7. package/dist/app.js +37 -6
  8. package/dist/app.js.map +1 -1
  9. package/dist/config/env.d.ts +8 -0
  10. package/dist/config/env.d.ts.map +1 -1
  11. package/dist/config/env.js +48 -0
  12. package/dist/config/env.js.map +1 -1
  13. package/dist/cron/scheduler.d.ts +3 -0
  14. package/dist/cron/scheduler.d.ts.map +1 -1
  15. package/dist/cron/scheduler.js +11 -3
  16. package/dist/cron/scheduler.js.map +1 -1
  17. package/dist/db/client.d.ts +10 -1
  18. package/dist/db/client.d.ts.map +1 -1
  19. package/dist/db/client.js +5 -14
  20. package/dist/db/client.js.map +1 -1
  21. package/dist/db/settings.d.ts +2 -1
  22. package/dist/db/settings.d.ts.map +1 -1
  23. package/dist/db/settings.js +6 -2
  24. package/dist/db/settings.js.map +1 -1
  25. package/dist/index.d.ts +4 -2
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +6 -2
  28. package/dist/index.js.map +1 -1
  29. package/dist/middleware/auth.d.ts +3 -2
  30. package/dist/middleware/auth.d.ts.map +1 -1
  31. package/dist/middleware/auth.js +28 -9
  32. package/dist/middleware/auth.js.map +1 -1
  33. package/dist/middleware/passkeys.d.ts +10 -0
  34. package/dist/middleware/passkeys.d.ts.map +1 -1
  35. package/dist/middleware/passkeys.js +19 -3
  36. package/dist/middleware/passkeys.js.map +1 -1
  37. package/dist/observability/index.d.ts +73 -0
  38. package/dist/observability/index.d.ts.map +1 -0
  39. package/dist/observability/index.js +74 -0
  40. package/dist/observability/index.js.map +1 -0
  41. package/dist/observability/loggers.d.ts +62 -0
  42. package/dist/observability/loggers.d.ts.map +1 -0
  43. package/dist/observability/loggers.js +109 -0
  44. package/dist/observability/loggers.js.map +1 -0
  45. package/dist/observability/meter.d.ts +39 -0
  46. package/dist/observability/meter.d.ts.map +1 -0
  47. package/dist/observability/meter.js +95 -0
  48. package/dist/observability/meter.js.map +1 -0
  49. package/dist/observability/types.d.ts +67 -0
  50. package/dist/observability/types.d.ts.map +1 -0
  51. package/dist/observability/types.js +44 -0
  52. package/dist/observability/types.js.map +1 -0
  53. package/dist/routes/brain.d.ts.map +1 -1
  54. package/dist/routes/brain.js +9 -1
  55. package/dist/routes/brain.js.map +1 -1
  56. package/dist/routes/files.d.ts +2 -0
  57. package/dist/routes/files.d.ts.map +1 -1
  58. package/dist/routes/files.js +7 -7
  59. package/dist/routes/files.js.map +1 -1
  60. package/dist/routes/graph.d.ts +2 -0
  61. package/dist/routes/graph.d.ts.map +1 -1
  62. package/dist/routes/graph.js +7 -7
  63. package/dist/routes/graph.js.map +1 -1
  64. package/dist/routes/health.d.ts +3 -0
  65. package/dist/routes/health.d.ts.map +1 -1
  66. package/dist/routes/health.js +3 -0
  67. package/dist/routes/health.js.map +1 -1
  68. package/dist/routes/models.d.ts +3 -0
  69. package/dist/routes/models.d.ts.map +1 -1
  70. package/dist/routes/models.js +5 -1
  71. package/dist/routes/models.js.map +1 -1
  72. package/dist/routes/render.d.ts +2 -1
  73. package/dist/routes/render.d.ts.map +1 -1
  74. package/dist/routes/render.js +2 -2
  75. package/dist/routes/render.js.map +1 -1
  76. package/dist/routes/share.d.ts +3 -0
  77. package/dist/routes/share.d.ts.map +1 -1
  78. package/dist/routes/share.js +3 -3
  79. package/dist/routes/share.js.map +1 -1
  80. package/dist/share/staging.d.ts +3 -2
  81. package/dist/share/staging.d.ts.map +1 -1
  82. package/dist/share/staging.js +12 -4
  83. package/dist/share/staging.js.map +1 -1
  84. package/dist/voice/keyterm-builder.d.ts +4 -1
  85. package/dist/voice/keyterm-builder.d.ts.map +1 -1
  86. package/dist/voice/keyterm-builder.js +13 -6
  87. package/dist/voice/keyterm-builder.js.map +1 -1
  88. package/dist/ws/bridge.d.ts.map +1 -1
  89. package/dist/ws/bridge.js +6 -1
  90. package/dist/ws/bridge.js.map +1 -1
  91. package/dist/ws/connection.d.ts +15 -0
  92. package/dist/ws/connection.d.ts.map +1 -1
  93. package/dist/ws/connection.js +55 -8
  94. package/dist/ws/connection.js.map +1 -1
  95. package/dist/ws/dispatch.d.ts +22 -1
  96. package/dist/ws/dispatch.d.ts.map +1 -1
  97. package/dist/ws/dispatch.js +33 -15
  98. package/dist/ws/dispatch.js.map +1 -1
  99. package/dist/ws/host.d.ts +37 -0
  100. package/dist/ws/host.d.ts.map +1 -1
  101. package/dist/ws/host.js +37 -0
  102. package/dist/ws/host.js.map +1 -1
  103. package/dist/ws/rate-limit.d.ts +58 -0
  104. package/dist/ws/rate-limit.d.ts.map +1 -0
  105. package/dist/ws/rate-limit.js +62 -0
  106. package/dist/ws/rate-limit.js.map +1 -0
  107. package/dist/ws/run-session.d.ts +3 -0
  108. package/dist/ws/run-session.d.ts.map +1 -1
  109. package/dist/ws/run-session.js +23 -5
  110. package/dist/ws/run-session.js.map +1 -1
  111. package/dist/ws/turns.d.ts +13 -0
  112. package/dist/ws/turns.d.ts.map +1 -1
  113. package/dist/ws/turns.js +11 -1
  114. package/dist/ws/turns.js.map +1 -1
  115. package/package.json +5 -3
  116. package/src/agent/backend.ts +8 -6
  117. package/src/app.ts +46 -6
  118. package/src/config/env.ts +57 -0
  119. package/src/cron/scheduler.ts +14 -3
  120. package/src/db/client.ts +16 -5
  121. package/src/db/settings.ts +7 -2
  122. package/src/index.ts +37 -2
  123. package/src/middleware/auth.ts +30 -14
  124. package/src/middleware/passkeys.ts +29 -5
  125. package/src/observability/index.ts +150 -0
  126. package/src/observability/loggers.ts +174 -0
  127. package/src/observability/meter.ts +190 -0
  128. package/src/observability/types.ts +101 -0
  129. package/src/routes/brain.ts +10 -1
  130. package/src/routes/files.ts +9 -8
  131. package/src/routes/graph.ts +9 -8
  132. package/src/routes/health.ts +7 -0
  133. package/src/routes/models.ts +8 -5
  134. package/src/routes/render.ts +3 -2
  135. package/src/routes/share.ts +6 -3
  136. package/src/share/staging.ts +18 -4
  137. package/src/voice/keyterm-builder.ts +17 -8
  138. package/src/ws/bridge.ts +5 -1
  139. package/src/ws/connection.ts +63 -12
  140. package/src/ws/dispatch.ts +44 -15
  141. package/src/ws/host.ts +54 -0
  142. package/src/ws/rate-limit.ts +86 -0
  143. package/src/ws/run-session.ts +26 -7
  144. package/src/ws/turns.ts +19 -3
@@ -5,11 +5,25 @@ import { withTurnScope } from "./frames.js";
5
5
  import { sendSessionHistory } from "./history.js";
6
6
  import { handleClientMessage } from "./dispatch.js";
7
7
  export { websocket };
8
- /** Build the Hono WebSocket upgrade handler bound to one host. */
9
- export function createWsUpgrade(host) {
10
- return upgradeWebSocket(() => ({
8
+ /**
9
+ * The socket lifecycle handlers for one host, separate from the Hono upgrade
10
+ * that wraps them.
11
+ *
12
+ * Split out so the real frame path is reachable from a test without standing
13
+ * up an HTTP server: `createWsHandlers(host).onMessage(...)` runs exactly what
14
+ * production runs, which is what makes an assertion about a dropped frame an
15
+ * assertion about the shipped code rather than about a re-implementation.
16
+ */
17
+ export function createWsHandlers(host) {
18
+ // One bucket per connection, created here so it lives and dies with the
19
+ // socket rather than in a map keyed by something a peer controls.
20
+ const limiter = host.newRateLimiter();
21
+ // Per-connection negotiation state: what revision this client declared.
22
+ // Lives with the socket, like the limiter.
23
+ const connection = {};
24
+ return {
11
25
  async onOpen(_evt, ws) {
12
- console.log("[ws] Client connected");
26
+ host.log.emit({ severityText: "INFO", body: "client connected" });
13
27
  const { coordinator, catalog } = host;
14
28
  // Handshake first (rev 2, additive): protocol revision + coarse
15
29
  // capabilities, so the client can gate behavior instead of sniffing.
@@ -43,7 +57,11 @@ export function createWsUpgrade(host) {
43
57
  sendSessionHistory(ws, sid, history);
44
58
  }
45
59
  catch (err) {
46
- console.error("[ws] snapshot-on-connect failed:", err);
60
+ host.log.emit({
61
+ severityText: "ERROR",
62
+ body: "snapshot-on-connect failed",
63
+ attributes: { error: err instanceof Error ? err.message : String(err) },
64
+ });
47
65
  }
48
66
  finally {
49
67
  host.clients.add(ws);
@@ -71,8 +89,20 @@ export function createWsUpgrade(host) {
71
89
  // underlying POOLED ArrayBuffer (byteOffset/byteLength discarded), so a
72
90
  // binary frame cannot be decoded correctly here. The protocol is JSON
73
91
  // text; reject anything else rather than parse a slab.
92
+ // Metered BEFORE parsing: the point is to bound work an unmetered peer
93
+ // can make this process do, and parsing is most of that work.
94
+ if (limiter && !limiter.take().allowed) {
95
+ host.reportDroppedFrame("rate_limited");
96
+ host.sendMessage(ws, {
97
+ type: "error",
98
+ code: "RATE_LIMITED",
99
+ message: "Too many frames; slow down.",
100
+ });
101
+ return;
102
+ }
74
103
  const raw = evt.data;
75
104
  if (typeof raw !== "string") {
105
+ host.reportDroppedFrame("binary_frame");
76
106
  host.sendMessage(ws, {
77
107
  type: "error",
78
108
  code: "PARSE_ERROR",
@@ -82,6 +112,9 @@ export function createWsUpgrade(host) {
82
112
  }
83
113
  const parsed = parseClientMessage(raw);
84
114
  if (!parsed.ok) {
115
+ // The reason is the parser's own bounded message, never the frame:
116
+ // the payload is caller-supplied and capped at 12 MB.
117
+ host.reportDroppedFrame("parse_error", parsed.error);
85
118
  host.sendMessage(ws, { type: "error", code: "PARSE_ERROR", message: parsed.error });
86
119
  return;
87
120
  }
@@ -89,7 +122,17 @@ export function createWsUpgrade(host) {
89
122
  // unhandled rejection with no frame sent.
90
123
  void Promise.resolve()
91
124
  .then(() => handleClientMessage(host, ws, parsed.message))
92
- .catch(() => {
125
+ .catch((err) => {
126
+ // This used to swallow the cause entirely: the client got a generic
127
+ // frame and the server kept no record of what threw.
128
+ host.log.emit({
129
+ severityText: "ERROR",
130
+ body: "client message handler failed",
131
+ attributes: {
132
+ "frame.type": parsed.message.type,
133
+ error: err instanceof Error ? err.message : String(err),
134
+ },
135
+ });
93
136
  host.sendMessage(ws, {
94
137
  type: "error",
95
138
  code: "INTERNAL_ERROR",
@@ -98,7 +141,7 @@ export function createWsUpgrade(host) {
98
141
  });
99
142
  },
100
143
  onClose(_evt, ws) {
101
- console.log("[ws] Client disconnected");
144
+ host.log.emit({ severityText: "INFO", body: "client disconnected" });
102
145
  host.clients.remove(ws);
103
146
  // Turns keep running in the background. Only reject pending interactive
104
147
  // requests once the LAST client leaves — while another client remains it
@@ -109,6 +152,10 @@ export function createWsUpgrade(host) {
109
152
  host.coordinator.drainPendingForTurn(turn, "Client disconnected");
110
153
  }
111
154
  },
112
- }));
155
+ };
156
+ }
157
+ /** Build the Hono WebSocket upgrade handler bound to one host. */
158
+ export function createWsUpgrade(host) {
159
+ return upgradeWebSocket(() => createWsHandlers(host));
113
160
  }
114
161
  //# sourceMappingURL=connection.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/ws/connection.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/ws/connection.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,26 @@
1
1
  import type { ClientMessage } from "@schlessera/brain-ui-sdk/protocol";
2
2
  import type { WSContext } from "./clients.js";
3
3
  import type { WsHost } from "./host.js";
4
- export declare function handleClientMessage(host: WsHost, ws: WSContext, msg: ClientMessage): Promise<void>;
4
+ /** Per-connection negotiation state, owned by the socket handler. */
5
+ export interface ConnectionState {
6
+ /** Revision the client declared via `client_hello`; absent means rev 2. */
7
+ protocolRev?: number;
8
+ }
9
+ /**
10
+ * Does this reply's echoed turnId identify the turn that raised the request?
11
+ *
12
+ * A wrong id is always refused: a stale echo from before a reconnect or a
13
+ * follow-up would resolve a different turn's pending promise.
14
+ *
15
+ * A MISSING id depends on who is speaking. A client that declared rev 3
16
+ * promised to echo, so silence means the reply cannot be correlated and is
17
+ * refused. A client that declared nothing is rev 2, where the field is
18
+ * optional, and is still tolerated — that tolerance is the deprecation window,
19
+ * and it is what lets this be enforced at all without breaking clients that
20
+ * predate `client_hello`.
21
+ */
22
+ export declare function turnIdMatches(pending: {
23
+ turnId: string;
24
+ }, echoed: string | undefined, requireEcho: boolean): boolean;
25
+ export declare function handleClientMessage(host: WsHost, ws: WSContext, msg: ClientMessage, connection?: ConnectionState): Promise<void>;
5
26
  //# sourceMappingURL=dispatch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/ws/dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAK9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAYxC,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,IAAI,CAAC,CAmKf"}
1
+ {"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/ws/dispatch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAK9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC,qEAAqE;AACrE,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EAC3B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,WAAW,EAAE,OAAO,GACnB,OAAO,CAGT;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,aAAa,EAClB,UAAU,GAAE,eAAoB,GAC/B,OAAO,CAAC,IAAI,CAAC,CA4Kf"}
@@ -1,19 +1,37 @@
1
+ import { PROTOCOL_REV_CLIENT_ECHO } from "@schlessera/brain-ui-sdk/protocol";
1
2
  import { locationErrorText } from "./frames.js";
2
3
  import { sendSessionHistory } from "./history.js";
3
4
  import { validateAttachments } from "./attachments.js";
4
5
  import { handleChatMessage } from "./run-session.js";
5
6
  /**
6
- * A client MAY echo the request's turnId (rev 2). When it does, it must match
7
- * the turn that raised the request — a stale echo from before a reconnect or
8
- * a follow-up would otherwise resolve the wrong turn's pending promise.
9
- * Absent turnId stays valid: the field is optional on the wire.
7
+ * Does this reply's echoed turnId identify the turn that raised the request?
8
+ *
9
+ * A wrong id is always refused: a stale echo from before a reconnect or a
10
+ * follow-up would resolve a different turn's pending promise.
11
+ *
12
+ * A MISSING id depends on who is speaking. A client that declared rev 3
13
+ * promised to echo, so silence means the reply cannot be correlated and is
14
+ * refused. A client that declared nothing is rev 2, where the field is
15
+ * optional, and is still tolerated — that tolerance is the deprecation window,
16
+ * and it is what lets this be enforced at all without breaking clients that
17
+ * predate `client_hello`.
10
18
  */
11
- function turnIdMatches(pending, echoed) {
12
- return echoed === undefined || echoed === pending.turnId;
19
+ export function turnIdMatches(pending, echoed, requireEcho) {
20
+ if (echoed === undefined)
21
+ return !requireEcho;
22
+ return echoed === pending.turnId;
13
23
  }
14
- export async function handleClientMessage(host, ws, msg) {
24
+ export async function handleClientMessage(host, ws, msg, connection = {}) {
15
25
  const { coordinator, catalog } = host;
26
+ const requireEcho = (connection.protocolRev ?? 2) >= PROTOCOL_REV_CLIENT_ECHO;
16
27
  switch (msg.type) {
28
+ case "client_hello": {
29
+ // Record what this connection speaks. Never rejected on version: a
30
+ // future client declaring rev 9 is simply held to the rules this host
31
+ // knows, and an unknown capability flag is ignored.
32
+ connection.protocolRev = msg.protocolRev;
33
+ return;
34
+ }
17
35
  case "chat_message": {
18
36
  const attachmentResult = validateAttachments(msg.attachments);
19
37
  if (!attachmentResult.ok) {
@@ -36,7 +54,7 @@ export async function handleClientMessage(host, ws, msg) {
36
54
  }
37
55
  case "ask_user_response": {
38
56
  const pending = coordinator.pendingAskUser.get(msg.requestId);
39
- if (pending && turnIdMatches(pending, msg.turnId)) {
57
+ if (pending && turnIdMatches(pending, msg.turnId, requireEcho)) {
40
58
  coordinator.pendingAskUser.delete(msg.requestId);
41
59
  pending.resolve({ answers: msg.answers, annotations: msg.annotations });
42
60
  }
@@ -44,7 +62,7 @@ export async function handleClientMessage(host, ws, msg) {
44
62
  }
45
63
  case "ask_user_cancel": {
46
64
  const pending = coordinator.pendingAskUser.get(msg.requestId);
47
- if (pending && turnIdMatches(pending, msg.turnId)) {
65
+ if (pending && turnIdMatches(pending, msg.turnId, requireEcho)) {
48
66
  coordinator.pendingAskUser.delete(msg.requestId);
49
67
  pending.reject(new Error(msg.reason || "User cancelled the question"));
50
68
  }
@@ -52,7 +70,7 @@ export async function handleClientMessage(host, ws, msg) {
52
70
  }
53
71
  case "location_response": {
54
72
  const pending = coordinator.pendingLocation.get(msg.requestId);
55
- if (pending && turnIdMatches(pending, msg.turnId)) {
73
+ if (pending && turnIdMatches(pending, msg.turnId, requireEcho)) {
56
74
  coordinator.pendingLocation.delete(msg.requestId);
57
75
  pending.resolve({ coords: msg.coords, timestamp: msg.timestamp });
58
76
  }
@@ -60,7 +78,7 @@ export async function handleClientMessage(host, ws, msg) {
60
78
  }
61
79
  case "location_error": {
62
80
  const pending = coordinator.pendingLocation.get(msg.requestId);
63
- if (pending && turnIdMatches(pending, msg.turnId)) {
81
+ if (pending && turnIdMatches(pending, msg.turnId, requireEcho)) {
64
82
  coordinator.pendingLocation.delete(msg.requestId);
65
83
  pending.reject(new Error(locationErrorText(msg.code, msg.message)));
66
84
  }
@@ -68,7 +86,7 @@ export async function handleClientMessage(host, ws, msg) {
68
86
  }
69
87
  case "mask_response": {
70
88
  const pending = coordinator.pendingMask.get(msg.requestId);
71
- if (pending && turnIdMatches(pending, msg.turnId)) {
89
+ if (pending && turnIdMatches(pending, msg.turnId, requireEcho)) {
72
90
  coordinator.pendingMask.delete(msg.requestId);
73
91
  // Decoded here rather than in the tool: the boundary already validated
74
92
  // the base64 and its size, so the backend gets bytes it can trust.
@@ -78,7 +96,7 @@ export async function handleClientMessage(host, ws, msg) {
78
96
  }
79
97
  case "mask_error": {
80
98
  const pending = coordinator.pendingMask.get(msg.requestId);
81
- if (pending && turnIdMatches(pending, msg.turnId)) {
99
+ if (pending && turnIdMatches(pending, msg.turnId, requireEcho)) {
82
100
  coordinator.pendingMask.delete(msg.requestId);
83
101
  pending.reject(new Error(msg.code === "cancelled"
84
102
  ? `The user did not mark an area${msg.message ? `: ${msg.message}` : ""}`
@@ -88,7 +106,7 @@ export async function handleClientMessage(host, ws, msg) {
88
106
  }
89
107
  case "tool_approval": {
90
108
  const pending = coordinator.pendingApprovals.get(msg.toolUseId);
91
- if (pending && turnIdMatches(pending, msg.turnId)) {
109
+ if (pending && turnIdMatches(pending, msg.turnId, requireEcho)) {
92
110
  coordinator.pendingApprovals.delete(msg.toolUseId);
93
111
  pending.resolve(msg.updatedInput
94
112
  ? { behavior: "allow", updatedInput: msg.updatedInput }
@@ -98,7 +116,7 @@ export async function handleClientMessage(host, ws, msg) {
98
116
  }
99
117
  case "tool_denial": {
100
118
  const pending = coordinator.pendingApprovals.get(msg.toolUseId);
101
- if (pending && turnIdMatches(pending, msg.turnId)) {
119
+ if (pending && turnIdMatches(pending, msg.turnId, requireEcho)) {
102
120
  coordinator.pendingApprovals.delete(msg.toolUseId);
103
121
  pending.resolve({ behavior: "deny", message: msg.message });
104
122
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../../src/ws/dispatch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../../src/ws/dispatch.ts"],"names":[],"mappings":"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"}
package/dist/ws/host.d.ts CHANGED
@@ -3,6 +3,8 @@ import { ClientSet, type WSContext } from "./clients.js";
3
3
  import { TurnCoordinator } from "./turns.js";
4
4
  import type { SessionCatalog } from "./session-catalog.js";
5
5
  import type { BackendRegistry } from "../agent/backend.js";
6
+ import { type Observability } from "../observability/index.js";
7
+ import { FrameRateLimiter } from "./rate-limit.js";
6
8
  /**
7
9
  * Budget for the host-side follow-up queue of ONE session (backends without a
8
10
  * native `followUp`; with one, messages go into the running turn and none of
@@ -41,6 +43,22 @@ export interface WsHostOptions {
41
43
  * it dynamic; createApp passes the resolved config value.
42
44
  */
43
45
  maxConcurrentSessions?: () => number;
46
+ /**
47
+ * Where this coordinator reports. Injected rather than reached for, so two
48
+ * apps in one process report separately and a test can assert on what the
49
+ * socket layer actually said. Defaults to silence: an embedder that never
50
+ * passes one gets no output, not a surprise stream on stdout.
51
+ */
52
+ observability?: Observability;
53
+ /**
54
+ * Inbound frame metering policy, per connection. Omitted (or a rate of 0)
55
+ * means no metering — which is what a test wants, and what an embedder
56
+ * fronting the socket with its own limiter wants.
57
+ */
58
+ wsRate?: {
59
+ ratePerSecond: number;
60
+ burst: number;
61
+ };
44
62
  }
45
63
  /**
46
64
  * Everything one WebSocket coordinator instance owns: turn state, the session
@@ -56,7 +74,26 @@ export declare class WsHost {
56
74
  appName: string;
57
75
  turnTimeoutMs: number;
58
76
  maxConcurrentSessions: () => number;
77
+ readonly observability: Observability;
78
+ readonly wsRate: {
79
+ ratePerSecond: number;
80
+ burst: number;
81
+ } | null;
82
+ /** Scoped instruments, resolved once — `[ws]` is the existing log prefix. */
83
+ readonly log: ReturnType<Observability["logger"]>;
84
+ private readonly framesDropped;
59
85
  constructor(options: WsHostOptions);
86
+ /** A metering bucket for one new connection, or null when metering is off. */
87
+ newRateLimiter(): FrameRateLimiter | null;
88
+ /**
89
+ * Record a frame this server refused, and say so.
90
+ *
91
+ * Before this existed, every rejection answered the client and vanished:
92
+ * `parseClientMessage` failures were never logged, so the validation we
93
+ * already shipped had no observability at all. `reason` is a bounded token,
94
+ * never the frame body — the payload is caller-supplied and can be 12 MB.
95
+ */
96
+ reportDroppedFrame(reason: string, detail?: string): void;
60
97
  /** Fan a frame out to every attached client (size-bounded per frame). */
61
98
  sendToClients(msg: ServerMessage): void;
62
99
  /** Send a frame to one specific socket (size-bounded). */
@@ -1 +1 @@
1
- {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/ws/host.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAE,SAAS,EAAU,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAQ3D;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB,QAAmB,CAAC;AACjD,eAAO,MAAM,eAAe,QAAmB,CAAC;AAEhD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,QAAQ,EAAE,eAAe,CAAC;IAC1B,+DAA+D;IAC/D,OAAO,EAAE,cAAc,CAAC;IACxB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,MAAM,CAAC;CACtC;AAED;;;;;GAKG;AACH,qBAAa,MAAM;IACjB,QAAQ,CAAC,WAAW,kBAAyB;IAC7C,QAAQ,CAAC,OAAO,YAAmB;IACnC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,OAAO,EAAE,cAAc,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB,EAAE,MAAM,MAAM,CAAC;gBAExB,OAAO,EAAE,aAAa;IASlC,yEAAyE;IACzE,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI;IAIvC,0DAA0D;IAC1D,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,aAAa,GAAG,IAAI;CAGrD"}
1
+ {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/ws/host.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAE,SAAS,EAAU,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAA6B,KAAK,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1F,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAQnD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB,QAAmB,CAAC;AACjD,eAAO,MAAM,eAAe,QAAmB,CAAC;AAEhD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,QAAQ,EAAE,eAAe,CAAC;IAC1B,+DAA+D;IAC/D,OAAO,EAAE,cAAc,CAAC;IACxB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,MAAM,CAAC;IACrC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;;OAIG;IACH,MAAM,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CACnD;AAED;;;;;GAKG;AACH,qBAAa,MAAM;IACjB,QAAQ,CAAC,WAAW,kBAAyB;IAC7C,QAAQ,CAAC,OAAO,YAAmB;IACnC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,OAAO,EAAE,cAAc,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB,EAAE,MAAM,MAAM,CAAC;IACpC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACjE,6EAA6E;IAC7E,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAE5B;gBAEU,OAAO,EAAE,aAAa;IAmBlC,8EAA8E;IAC9E,cAAc,IAAI,gBAAgB,GAAG,IAAI;IAIzC;;;;;;;OAOG;IACH,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IASzD,yEAAyE;IACzE,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI;IAIvC,0DAA0D;IAC1D,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,aAAa,GAAG,IAAI;CAGrD"}
package/dist/ws/host.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { ClientSet, sendTo } from "./clients.js";
2
2
  import { TurnCoordinator } from "./turns.js";
3
+ import { createSilentObservability } from "../observability/index.js";
4
+ import { FrameRateLimiter } from "./rate-limit.js";
3
5
  /** Host-side turn timeout. The backend no longer times out — the host owns it. */
4
6
  const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
5
7
  /** Default cap on concurrently RUNNING sessions (MAX_CONCURRENT_SESSIONS). */
@@ -41,6 +43,11 @@ export class WsHost {
41
43
  appName;
42
44
  turnTimeoutMs;
43
45
  maxConcurrentSessions;
46
+ observability;
47
+ wsRate;
48
+ /** Scoped instruments, resolved once — `[ws]` is the existing log prefix. */
49
+ log;
50
+ framesDropped;
44
51
  constructor(options) {
45
52
  this.registry = options.registry;
46
53
  this.catalog = options.catalog;
@@ -48,6 +55,36 @@ export class WsHost {
48
55
  this.turnTimeoutMs = options.turnTimeoutMs ?? DEFAULT_TIMEOUT_MS;
49
56
  this.maxConcurrentSessions =
50
57
  options.maxConcurrentSessions ?? (() => DEFAULT_MAX_CONCURRENT_SESSIONS);
58
+ this.observability = options.observability ?? createSilentObservability();
59
+ this.wsRate =
60
+ options.wsRate && options.wsRate.ratePerSecond > 0 ? options.wsRate : null;
61
+ this.log = this.observability.logger("ws");
62
+ this.framesDropped = this.observability
63
+ .meter("ws")
64
+ .createCounter("ws.frames.dropped", {
65
+ description: "Inbound frames refused before reaching a handler",
66
+ });
67
+ this.coordinator.log = this.log;
68
+ }
69
+ /** A metering bucket for one new connection, or null when metering is off. */
70
+ newRateLimiter() {
71
+ return this.wsRate ? new FrameRateLimiter(this.wsRate) : null;
72
+ }
73
+ /**
74
+ * Record a frame this server refused, and say so.
75
+ *
76
+ * Before this existed, every rejection answered the client and vanished:
77
+ * `parseClientMessage` failures were never logged, so the validation we
78
+ * already shipped had no observability at all. `reason` is a bounded token,
79
+ * never the frame body — the payload is caller-supplied and can be 12 MB.
80
+ */
81
+ reportDroppedFrame(reason, detail) {
82
+ this.framesDropped.add(1, { reason, direction: "inbound" });
83
+ this.log.emit({
84
+ severityText: "WARN",
85
+ body: "inbound frame rejected",
86
+ attributes: detail ? { reason, detail } : { reason },
87
+ });
51
88
  }
52
89
  /** Fan a frame out to every attached client (size-bounded per frame). */
53
90
  sendToClients(msg) {
@@ -1 +1 @@
1
- {"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/ws/host.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAkB,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAI7C,kFAAkF;AAClF,MAAM,kBAAkB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,aAAa;AAExD,8EAA8E;AAC9E,MAAM,+BAA+B,GAAG,CAAC,CAAC;AAE1C;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AACjD,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhD;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAmBpC;;;;;GAKG;AACH,MAAM,OAAO,MAAM;IACR,WAAW,GAAG,IAAI,eAAe,EAAE,CAAC;IACpC,OAAO,GAAG,IAAI,SAAS,EAAE,CAAC;IAC1B,QAAQ,CAAkB;IACnC,OAAO,CAAiB;IACxB,OAAO,CAAS;IAChB,aAAa,CAAS;IACtB,qBAAqB,CAAe;IAEpC,YAAY,OAAsB;QAChC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,UAAU,CAAC;QAC7C,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,kBAAkB,CAAC;QACjE,IAAI,CAAC,qBAAqB;YACxB,OAAO,CAAC,qBAAqB,IAAI,CAAC,GAAG,EAAE,CAAC,+BAA+B,CAAC,CAAC;IAC7E,CAAC;IAED,yEAAyE;IACzE,aAAa,CAAC,GAAkB;QAC9B,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC9B,CAAC;IAED,0DAA0D;IAC1D,WAAW,CAAC,EAAa,EAAE,GAAkB;QAC3C,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAClB,CAAC;CACF"}
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/ws/host.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Per-connection inbound frame metering.
3
+ *
4
+ * Frames are already size-, cardinality- and depth-bounded, so the cheap CPU
5
+ * amplification is closed. What was left unmetered is VOLUME: a flood of
6
+ * individually valid frames behind the auth guard. That is a smaller threat
7
+ * than an unauthenticated one — it needs a session — but "the attacker had to
8
+ * log in first" is not a bound.
9
+ *
10
+ * A token bucket rather than a fixed window, because the traffic this protects
11
+ * is bursty by nature and a fixed window punishes the wrong thing. Opening the
12
+ * app fires a handful of frames at once (resume, environment, a first
13
+ * message); an approval storm during a busy turn is a dozen in a second. Both
14
+ * are legitimate and both fit in the burst. What does not fit is a sustained
15
+ * rate no human produces.
16
+ *
17
+ * Inbound traffic is naturally low-rate — deltas flow the other way — so the
18
+ * defaults are generous enough that no real client should ever see one of
19
+ * these, which is the property that makes it safe to turn on by default.
20
+ */
21
+ /** Injected so tests need no real time. */
22
+ export interface Clock {
23
+ now(): number;
24
+ }
25
+ export interface RateLimitOptions {
26
+ /** Sustained frames per second. */
27
+ ratePerSecond: number;
28
+ /** Frames absorbable in one burst before the sustained rate applies. */
29
+ burst: number;
30
+ clock?: Clock;
31
+ }
32
+ export interface RateLimitDecision {
33
+ allowed: boolean;
34
+ /** Tokens left after this decision, for reporting. */
35
+ remaining: number;
36
+ }
37
+ /**
38
+ * One bucket per connection. Not shared, and not keyed by anything
39
+ * user-supplied: a map keyed by session or IP is itself an unbounded
40
+ * allocation an attacker controls, which is the classic way a rate limiter
41
+ * becomes the memory-exhaustion bug it was added to prevent. This lives on the
42
+ * socket and dies with it.
43
+ */
44
+ export declare class FrameRateLimiter {
45
+ private tokens;
46
+ private lastRefill;
47
+ private readonly ratePerMs;
48
+ private readonly burst;
49
+ private readonly clock;
50
+ /** Frames refused since the connection opened. */
51
+ private rejected;
52
+ constructor(options: RateLimitOptions);
53
+ /** Take one token. False means this frame should be refused. */
54
+ take(): RateLimitDecision;
55
+ /** How many frames this connection has had refused. */
56
+ get rejectedCount(): number;
57
+ }
58
+ //# sourceMappingURL=rate-limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/ws/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,2CAA2C;AAC3C,MAAM,WAAW,KAAK;IACpB,GAAG,IAAI,MAAM,CAAC;CACf;AAID,MAAM,WAAW,gBAAgB;IAC/B,mCAAmC;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,kDAAkD;IAClD,OAAO,CAAC,QAAQ,CAAK;gBAET,OAAO,EAAE,gBAAgB;IAQrC,gEAAgE;IAChE,IAAI,IAAI,iBAAiB;IAczB,uDAAuD;IACvD,IAAI,aAAa,IAAI,MAAM,CAE1B;CACF"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Per-connection inbound frame metering.
3
+ *
4
+ * Frames are already size-, cardinality- and depth-bounded, so the cheap CPU
5
+ * amplification is closed. What was left unmetered is VOLUME: a flood of
6
+ * individually valid frames behind the auth guard. That is a smaller threat
7
+ * than an unauthenticated one — it needs a session — but "the attacker had to
8
+ * log in first" is not a bound.
9
+ *
10
+ * A token bucket rather than a fixed window, because the traffic this protects
11
+ * is bursty by nature and a fixed window punishes the wrong thing. Opening the
12
+ * app fires a handful of frames at once (resume, environment, a first
13
+ * message); an approval storm during a busy turn is a dozen in a second. Both
14
+ * are legitimate and both fit in the burst. What does not fit is a sustained
15
+ * rate no human produces.
16
+ *
17
+ * Inbound traffic is naturally low-rate — deltas flow the other way — so the
18
+ * defaults are generous enough that no real client should ever see one of
19
+ * these, which is the property that makes it safe to turn on by default.
20
+ */
21
+ const realClock = { now: () => Date.now() };
22
+ /**
23
+ * One bucket per connection. Not shared, and not keyed by anything
24
+ * user-supplied: a map keyed by session or IP is itself an unbounded
25
+ * allocation an attacker controls, which is the classic way a rate limiter
26
+ * becomes the memory-exhaustion bug it was added to prevent. This lives on the
27
+ * socket and dies with it.
28
+ */
29
+ export class FrameRateLimiter {
30
+ tokens;
31
+ lastRefill;
32
+ ratePerMs;
33
+ burst;
34
+ clock;
35
+ /** Frames refused since the connection opened. */
36
+ rejected = 0;
37
+ constructor(options) {
38
+ this.burst = Math.max(1, options.burst);
39
+ this.ratePerMs = Math.max(0, options.ratePerSecond) / 1000;
40
+ this.clock = options.clock ?? realClock;
41
+ this.tokens = this.burst;
42
+ this.lastRefill = this.clock.now();
43
+ }
44
+ /** Take one token. False means this frame should be refused. */
45
+ take() {
46
+ const now = this.clock.now();
47
+ const elapsed = Math.max(0, now - this.lastRefill);
48
+ this.lastRefill = now;
49
+ this.tokens = Math.min(this.burst, this.tokens + elapsed * this.ratePerMs);
50
+ if (this.tokens < 1) {
51
+ this.rejected++;
52
+ return { allowed: false, remaining: 0 };
53
+ }
54
+ this.tokens -= 1;
55
+ return { allowed: true, remaining: Math.floor(this.tokens) };
56
+ }
57
+ /** How many frames this connection has had refused. */
58
+ get rejectedCount() {
59
+ return this.rejected;
60
+ }
61
+ }
62
+ //# sourceMappingURL=rate-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/ws/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAOH,MAAM,SAAS,GAAU,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;AAgBnD;;;;;;GAMG;AACH,MAAM,OAAO,gBAAgB;IACnB,MAAM,CAAS;IACf,UAAU,CAAS;IACV,SAAS,CAAS;IAClB,KAAK,CAAS;IACd,KAAK,CAAQ;IAC9B,kDAAkD;IAC1C,QAAQ,GAAG,CAAC,CAAC;IAErB,YAAY,OAAyB;QACnC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;QAC3D,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,SAAS,CAAC;QACxC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;IACrC,CAAC;IAED,gEAAgE;IAChE,IAAI;QACF,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;QACnD,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC;QAE3E,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,IAAI,CAAC,QAAQ,EAAE,CAAC;YAChB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;QAC1C,CAAC;QACD,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC;QACjB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;IAC/D,CAAC;IAED,uDAAuD;IACvD,IAAI,aAAa;QACf,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;CACF"}
@@ -12,6 +12,8 @@ export declare function runSession(host: WsHost, initial: {
12
12
  attachments: ChatImageAttachment[];
13
13
  providerId?: string;
14
14
  client?: ClientEnvironment;
15
+ /** Client correlation id for a new conversation; echoed on session_info. */
16
+ draftId?: string;
15
17
  }): Promise<void>;
16
18
  /** Dispatch a chat_message: follow-up to a running session, or a new session. */
17
19
  export declare function handleChatMessage(host: WsHost, ws: WSContext, msg: {
@@ -20,5 +22,6 @@ export declare function handleChatMessage(host: WsHost, ws: WSContext, msg: {
20
22
  attachments: ChatImageAttachment[];
21
23
  providerId?: string;
22
24
  client?: ClientEnvironment;
25
+ draftId?: string;
23
26
  }): Promise<void>;
24
27
  //# sourceMappingURL=run-session.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"run-session.d.ts","sourceRoot":"","sources":["../../src/ws/run-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAChG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAM9C,OAAO,EAAwD,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAE9F;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IACP,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,mBAAmB,EAAE,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B,GACA,OAAO,CAAC,IAAI,CAAC,CA0Gf;AAOD,iFAAiF;AACjF,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,SAAS,EACb,GAAG,EAAE;IACH,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,mBAAmB,EAAE,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B,GACA,OAAO,CAAC,IAAI,CAAC,CA0Ff"}
1
+ {"version":3,"file":"run-session.d.ts","sourceRoot":"","sources":["../../src/ws/run-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAChG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAM9C,OAAO,EAAwD,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAE9F;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IACP,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,mBAAmB,EAAE,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GACA,OAAO,CAAC,IAAI,CAAC,CAiHf;AAOD,iFAAiF;AACjF,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,SAAS,EACb,GAAG,EAAE;IACH,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,mBAAmB,EAAE,CAAC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GACA,OAAO,CAAC,IAAI,CAAC,CAmGf"}