@geminixiang/mikan 0.4.8 → 0.5.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 (237) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +13 -13
  3. package/dist/adapter.d.ts +1 -1
  4. package/dist/adapter.d.ts.map +1 -1
  5. package/dist/adapter.js.map +1 -1
  6. package/dist/adapters/discord/bot.d.ts +9 -6
  7. package/dist/adapters/discord/bot.d.ts.map +1 -1
  8. package/dist/adapters/discord/bot.js +22 -22
  9. package/dist/adapters/discord/bot.js.map +1 -1
  10. package/dist/adapters/discord/context.d.ts +6 -6
  11. package/dist/adapters/discord/context.d.ts.map +1 -1
  12. package/dist/adapters/discord/context.js +3 -3
  13. package/dist/adapters/discord/context.js.map +1 -1
  14. package/dist/adapters/discord/types.d.ts +2 -2
  15. package/dist/adapters/discord/types.d.ts.map +1 -1
  16. package/dist/adapters/discord/types.js.map +1 -1
  17. package/dist/adapters/intake.d.ts +2 -2
  18. package/dist/adapters/intake.d.ts.map +1 -1
  19. package/dist/adapters/intake.js +4 -4
  20. package/dist/adapters/intake.js.map +1 -1
  21. package/dist/adapters/shared.d.ts +3 -3
  22. package/dist/adapters/shared.d.ts.map +1 -1
  23. package/dist/adapters/shared.js +2 -2
  24. package/dist/adapters/shared.js.map +1 -1
  25. package/dist/adapters/slack/bot.d.ts +10 -7
  26. package/dist/adapters/slack/bot.d.ts.map +1 -1
  27. package/dist/adapters/slack/bot.js +46 -46
  28. package/dist/adapters/slack/bot.js.map +1 -1
  29. package/dist/adapters/slack/context.d.ts +6 -6
  30. package/dist/adapters/slack/context.d.ts.map +1 -1
  31. package/dist/adapters/slack/context.js +2 -2
  32. package/dist/adapters/slack/context.js.map +1 -1
  33. package/dist/adapters/slack/response-lifecycle.d.ts +6 -6
  34. package/dist/adapters/slack/response-lifecycle.d.ts.map +1 -1
  35. package/dist/adapters/slack/response-lifecycle.js +3 -3
  36. package/dist/adapters/slack/response-lifecycle.js.map +1 -1
  37. package/dist/adapters/slack/session.d.ts.map +1 -1
  38. package/dist/adapters/slack/tools/attach.d.ts.map +1 -1
  39. package/dist/adapters/slack/tools/block-kit.d.ts.map +1 -1
  40. package/dist/adapters/slack/types.d.ts +1 -1
  41. package/dist/adapters/slack/types.d.ts.map +1 -1
  42. package/dist/adapters/slack/types.js.map +1 -1
  43. package/dist/adapters/streaming.d.ts.map +1 -1
  44. package/dist/adapters/telegram/bot.d.ts +10 -7
  45. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  46. package/dist/adapters/telegram/bot.js +21 -21
  47. package/dist/adapters/telegram/bot.js.map +1 -1
  48. package/dist/adapters/telegram/context.d.ts +6 -6
  49. package/dist/adapters/telegram/context.d.ts.map +1 -1
  50. package/dist/adapters/telegram/context.js +3 -3
  51. package/dist/adapters/telegram/context.js.map +1 -1
  52. package/dist/adapters/telegram/html.d.ts.map +1 -1
  53. package/dist/adapters/telegram/types.d.ts +2 -2
  54. package/dist/adapters/telegram/types.d.ts.map +1 -1
  55. package/dist/adapters/telegram/types.js.map +1 -1
  56. package/dist/adapters/types.d.ts +6 -6
  57. package/dist/adapters/types.d.ts.map +1 -1
  58. package/dist/adapters/types.js.map +1 -1
  59. package/dist/agent-events.d.ts +5 -0
  60. package/dist/agent-events.d.ts.map +1 -0
  61. package/dist/agent-events.js +26 -0
  62. package/dist/agent-events.js.map +1 -0
  63. package/dist/agent.d.ts +7 -7
  64. package/dist/agent.d.ts.map +1 -1
  65. package/dist/agent.js +84 -41
  66. package/dist/agent.js.map +1 -1
  67. package/dist/commands/admin.d.ts.map +1 -1
  68. package/dist/commands/admin.js +1 -1
  69. package/dist/commands/admin.js.map +1 -1
  70. package/dist/commands/auto-reply.d.ts.map +1 -1
  71. package/dist/commands/auto-reply.js +3 -3
  72. package/dist/commands/auto-reply.js.map +1 -1
  73. package/dist/commands/login.d.ts.map +1 -1
  74. package/dist/commands/login.js +1 -1
  75. package/dist/commands/login.js.map +1 -1
  76. package/dist/commands/model.d.ts.map +1 -1
  77. package/dist/commands/model.js +5 -5
  78. package/dist/commands/model.js.map +1 -1
  79. package/dist/commands/new.d.ts.map +1 -1
  80. package/dist/commands/new.js +2 -2
  81. package/dist/commands/new.js.map +1 -1
  82. package/dist/commands/parse.d.ts.map +1 -1
  83. package/dist/commands/registry.d.ts.map +1 -1
  84. package/dist/commands/sandbox.d.ts.map +1 -1
  85. package/dist/commands/sandbox.js +5 -5
  86. package/dist/commands/sandbox.js.map +1 -1
  87. package/dist/commands/session-view.d.ts.map +1 -1
  88. package/dist/commands/session-view.js +1 -1
  89. package/dist/commands/session-view.js.map +1 -1
  90. package/dist/commands/types.d.ts +5 -5
  91. package/dist/commands/types.d.ts.map +1 -1
  92. package/dist/commands/types.js.map +1 -1
  93. package/dist/commands/utils.d.ts +3 -3
  94. package/dist/commands/utils.d.ts.map +1 -1
  95. package/dist/commands/utils.js +6 -6
  96. package/dist/commands/utils.js.map +1 -1
  97. package/dist/config.d.ts.map +1 -1
  98. package/dist/context.d.ts.map +1 -1
  99. package/dist/download.d.ts.map +1 -1
  100. package/dist/events.d.ts +2 -2
  101. package/dist/events.d.ts.map +1 -1
  102. package/dist/events.js.map +1 -1
  103. package/dist/execution-resolver.d.ts.map +1 -1
  104. package/dist/index.d.ts +3 -3
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js +2 -2
  107. package/dist/index.js.map +1 -1
  108. package/dist/log.d.ts.map +1 -1
  109. package/dist/main.d.ts.map +1 -1
  110. package/dist/main.js +35 -27
  111. package/dist/main.js.map +1 -1
  112. package/dist/model-registry.d.ts.map +1 -1
  113. package/dist/observability/instrument.d.ts.map +1 -1
  114. package/dist/observability/sentry.d.ts.map +1 -1
  115. package/dist/observability/sentry.js +27 -9
  116. package/dist/observability/sentry.js.map +1 -1
  117. package/dist/observability/types.d.ts.map +1 -1
  118. package/dist/platform-messages.d.ts +2 -2
  119. package/dist/platform-messages.d.ts.map +1 -1
  120. package/dist/platform-messages.js +2 -2
  121. package/dist/platform-messages.js.map +1 -1
  122. package/dist/portal-shell.d.ts.map +1 -1
  123. package/dist/provisioner.d.ts.map +1 -1
  124. package/dist/runtime/{conversation-orchestrator.d.ts → agent-run-controller.d.ts} +5 -5
  125. package/dist/runtime/agent-run-controller.d.ts.map +1 -0
  126. package/dist/runtime/{conversation-orchestrator.js → agent-run-controller.js} +17 -17
  127. package/dist/runtime/agent-run-controller.js.map +1 -0
  128. package/dist/runtime/conversation-runtime.d.ts +4 -0
  129. package/dist/runtime/conversation-runtime.d.ts.map +1 -0
  130. package/dist/runtime/{session-runtime.js → conversation-runtime.js} +12 -12
  131. package/dist/runtime/conversation-runtime.js.map +1 -0
  132. package/dist/runtime/types.d.ts +9 -9
  133. package/dist/runtime/types.d.ts.map +1 -1
  134. package/dist/runtime/types.js.map +1 -1
  135. package/dist/sandbox/cloudflare.d.ts.map +1 -1
  136. package/dist/sandbox/container.d.ts.map +1 -1
  137. package/dist/sandbox/errors.d.ts.map +1 -1
  138. package/dist/sandbox/firecracker.d.ts.map +1 -1
  139. package/dist/sandbox/host.d.ts.map +1 -1
  140. package/dist/sandbox/image.d.ts.map +1 -1
  141. package/dist/sandbox/index.d.ts.map +1 -1
  142. package/dist/sandbox/path-context.d.ts.map +1 -1
  143. package/dist/sandbox/types.d.ts.map +1 -1
  144. package/dist/sandbox/utils.d.ts.map +1 -1
  145. package/dist/sessions/{chat-session-manager.d.ts → agent-memory-file-manager.d.ts} +6 -6
  146. package/dist/sessions/agent-memory-file-manager.d.ts.map +1 -0
  147. package/dist/sessions/{chat-session-manager.js → agent-memory-file-manager.js} +16 -16
  148. package/dist/sessions/agent-memory-file-manager.js.map +1 -0
  149. package/dist/sessions/metadata.d.ts.map +1 -1
  150. package/dist/sessions/policy.d.ts.map +1 -1
  151. package/dist/sessions/store.d.ts.map +1 -1
  152. package/dist/sessions/store.js +4 -4
  153. package/dist/sessions/store.js.map +1 -1
  154. package/dist/sessions/types.d.ts +3 -3
  155. package/dist/sessions/types.d.ts.map +1 -1
  156. package/dist/sessions/types.js.map +1 -1
  157. package/dist/store.d.ts +3 -0
  158. package/dist/store.d.ts.map +1 -1
  159. package/dist/store.js +1 -1
  160. package/dist/store.js.map +1 -1
  161. package/dist/tool-diagnostics.d.ts.map +1 -1
  162. package/dist/tools/bash.d.ts.map +1 -1
  163. package/dist/tools/edit.d.ts.map +1 -1
  164. package/dist/tools/event.d.ts.map +1 -1
  165. package/dist/tools/index.d.ts.map +1 -1
  166. package/dist/tools/read.d.ts.map +1 -1
  167. package/dist/tools/sandbox.d.ts.map +1 -1
  168. package/dist/tools/truncate.d.ts.map +1 -1
  169. package/dist/tools/types.d.ts.map +1 -1
  170. package/dist/tools/write.d.ts.map +1 -1
  171. package/dist/trigger.d.ts +2 -2
  172. package/dist/trigger.d.ts.map +1 -1
  173. package/dist/trigger.js +1 -1
  174. package/dist/trigger.js.map +1 -1
  175. package/dist/types.d.ts +47 -24
  176. package/dist/types.d.ts.map +1 -1
  177. package/dist/types.js +1 -1
  178. package/dist/types.js.map +1 -1
  179. package/dist/utils/date.d.ts +1 -1
  180. package/dist/utils/date.d.ts.map +1 -1
  181. package/dist/utils/date.js +1 -1
  182. package/dist/utils/date.js.map +1 -1
  183. package/dist/utils/env.d.ts.map +1 -1
  184. package/dist/utils/file-guards.d.ts.map +1 -1
  185. package/dist/utils/fs-atomic.d.ts.map +1 -1
  186. package/dist/utils/html.d.ts.map +1 -1
  187. package/dist/utils/http-body.d.ts.map +1 -1
  188. package/dist/vault/index.d.ts.map +1 -1
  189. package/dist/vault/routing.d.ts.map +1 -1
  190. package/dist/vault/types.d.ts.map +1 -1
  191. package/dist/web/admin/portal.d.ts.map +1 -1
  192. package/dist/web/admin/portal.js +1 -1
  193. package/dist/web/admin/portal.js.map +1 -1
  194. package/dist/web/admin/provider-models.d.ts.map +1 -1
  195. package/dist/web/admin/store.d.ts.map +1 -1
  196. package/dist/web/admin/types.d.ts +1 -1
  197. package/dist/web/admin/types.d.ts.map +1 -1
  198. package/dist/web/admin/types.js.map +1 -1
  199. package/dist/web/agent-events/portal.d.ts +3 -0
  200. package/dist/web/agent-events/portal.d.ts.map +1 -0
  201. package/dist/web/agent-events/portal.js +18 -0
  202. package/dist/web/agent-events/portal.js.map +1 -0
  203. package/dist/web/login/oauth.d.ts.map +1 -1
  204. package/dist/web/login/oauth.js.map +1 -1
  205. package/dist/web/login/portal.d.ts +2 -14
  206. package/dist/web/login/portal.d.ts.map +1 -1
  207. package/dist/web/login/portal.js +53 -130
  208. package/dist/web/login/portal.js.map +1 -1
  209. package/dist/web/login/store.d.ts.map +1 -1
  210. package/dist/web/login/types.d.ts.map +1 -1
  211. package/dist/web/request.d.ts +3 -0
  212. package/dist/web/request.d.ts.map +1 -0
  213. package/dist/web/request.js +13 -0
  214. package/dist/web/request.js.map +1 -0
  215. package/dist/web/server.d.ts +28 -0
  216. package/dist/web/server.d.ts.map +1 -0
  217. package/dist/web/server.js +66 -0
  218. package/dist/web/server.js.map +1 -0
  219. package/dist/web/session-view/command.d.ts.map +1 -1
  220. package/dist/web/session-view/portal.d.ts.map +1 -1
  221. package/dist/web/session-view/portal.js +8 -8
  222. package/dist/web/session-view/portal.js.map +1 -1
  223. package/dist/web/session-view/service.d.ts.map +1 -1
  224. package/dist/web/session-view/store.d.ts.map +1 -1
  225. package/dist/web/session-view/types.d.ts +4 -4
  226. package/dist/web/session-view/types.d.ts.map +1 -1
  227. package/dist/web/session-view/types.js.map +1 -1
  228. package/dist/web/token-store.d.ts.map +1 -1
  229. package/dist/web/types.d.ts.map +1 -1
  230. package/package.json +44 -20
  231. package/dist/runtime/conversation-orchestrator.d.ts.map +0 -1
  232. package/dist/runtime/conversation-orchestrator.js.map +0 -1
  233. package/dist/runtime/session-runtime.d.ts +0 -4
  234. package/dist/runtime/session-runtime.d.ts.map +0 -1
  235. package/dist/runtime/session-runtime.js.map +0 -1
  236. package/dist/sessions/chat-session-manager.d.ts.map +0 -1
  237. package/dist/sessions/chat-session-manager.js.map +0 -1
@@ -1,11 +1,9 @@
1
1
  import { createHash, randomBytes } from "crypto";
2
- import { createServer } from "http";
3
- import { handleAdminRequest } from "../admin/portal.js";
4
2
  import { escapeHtml } from "../../utils/html.js";
5
3
  import { readRawBody } from "../../utils/http-body.js";
6
4
  import { renderPortalShell } from "../../portal-shell.js";
7
5
  import { resolveLinkBaseUrl } from "../../config.js";
8
- import { handleSessionViewRequest, } from "../session-view/portal.js";
6
+ import { requestBaseUrl } from "../request.js";
9
7
  import { getOAuthServices, resolveOAuthService, } from "./oauth.js";
10
8
  import * as log from "../../log.js";
11
9
  import { reportUserFacingError } from "../../observability/sentry.js";
@@ -178,7 +176,7 @@ const SECRET_PRESETS = [
178
176
  ],
179
177
  },
180
178
  ];
181
- // ── startLinkServer ────────────────────────────────────────────────────────────
179
+ // ── request handler ───────────────────────────────────────────────────────────
182
180
  /**
183
181
  * Start a small HTTP server that receives credential onboarding callbacks from the web portal.
184
182
  *
@@ -189,137 +187,62 @@ const SECRET_PRESETS = [
189
187
  * POST /api/oauth/start — creates provider OAuth redirect URL
190
188
  * GET /oauth/callback — OAuth callback endpoint
191
189
  */
192
- export function startLinkServer(port, linkTokenStore, vaultManager, notify, sessionViewTokenStore, sessionViewInteractive, adminOptions) {
190
+ export function createLoginRequestHandler(linkTokenStore, vaultManager, notify) {
193
191
  const oauthStates = new Map();
194
- const server = createServer(async (req, res) => {
195
- try {
196
- const url = new URL(req.url ?? "/", requestBaseUrl(req));
197
- if (req.method === "GET" && url.pathname === "/health") {
198
- res.writeHead(200, { "Content-Type": "application/json" });
199
- res.end(JSON.stringify({ ok: true }));
200
- return;
192
+ return (req, res, url) => {
193
+ if (req.method === "GET" && url.pathname === "/link") {
194
+ const rawToken = url.searchParams.get("token") ?? "";
195
+ const linkToken = linkTokenStore.peek(rawToken);
196
+ if (!linkToken) {
197
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
198
+ res.end(renderErrorPage("This link is invalid or has expired. Ask the bot for a new /login link."));
199
+ return true;
201
200
  }
202
- if (adminOptions?.adminTokenStore &&
203
- handleAdminRequest(req, res, url, {
204
- vaultManager,
205
- linkTokenStore,
206
- sessionViewTokenStore,
207
- adminTokenStore: adminOptions.adminTokenStore,
208
- portalBaseUrl: resolveLinkBaseUrl() ?? undefined,
209
- workingDir: adminOptions.workingDir,
210
- runtime: adminOptions.runtime,
211
- sandbox: adminOptions.sandbox,
212
- botsByPlatform: adminOptions.botsByPlatform,
213
- })) {
214
- return;
215
- }
216
- if (await handleSessionViewRequest(req, res, url, sessionViewTokenStore, sessionViewInteractive)) {
217
- return;
218
- }
219
- if (req.method === "GET" && url.pathname === "/link") {
220
- const rawToken = url.searchParams.get("token") ?? "";
221
- const linkToken = linkTokenStore.peek(rawToken);
222
- if (!linkToken) {
223
- res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
224
- res.end(renderErrorPage("This link is invalid or has expired. Ask the bot for a new /login link."));
225
- return;
226
- }
227
- const oauthServiceHint = linkToken.providerId
228
- ? resolveOAuthService(linkToken.providerId)
229
- : undefined;
230
- const oauthServices = getOAuthServices();
231
- const defaultMode = oauthServiceHint ? "oauth" : "api_key";
232
- const existingSecrets = describeVaultSecrets(vaultManager, linkToken.vaultId);
233
- const title = oauthServiceHint ? `${oauthServiceHint.label} OAuth` : "Store Secret";
234
- const helpText = oauthServiceHint
235
- ? `Authorize ${oauthServiceHint.label} and store tokens in your vault.`
236
- : "Set any environment variable key/value pair in your vault.";
237
- const secretLabel = "Secret value";
238
- const placeholder = "sk-...";
239
- const initialEnvKey = "";
240
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
241
- res.end(renderCredentialPage(rawToken, title, defaultMode, initialEnvKey, secretLabel, placeholder, helpText, oauthServices, oauthServiceHint?.id, existingSecrets));
242
- return;
243
- }
244
- if (req.method === "POST" && url.pathname === "/api/link/complete") {
245
- if (!enforceCsrf(req, res))
246
- return;
247
- void readJsonBody(req, res, async (body) => {
248
- await handleLinkComplete(body, linkTokenStore, vaultManager, notify, res);
249
- });
250
- return;
251
- }
252
- if (req.method === "POST" && url.pathname === "/api/oauth/start") {
253
- if (!enforceCsrf(req, res))
254
- return;
255
- void readJsonBody(req, res, async (body) => {
256
- await handleOAuthStart(body, req, linkTokenStore, oauthStates, res);
257
- });
258
- return;
259
- }
260
- if (req.method === "GET" && url.pathname === "/oauth/callback") {
261
- void handleOAuthCallback(url, req, linkTokenStore, vaultManager, notify, oauthStates, res).catch((err) => {
262
- log.logWarning("OAuth callback failed", err.message);
263
- reportUserFacingError(err, {
264
- domain: "login",
265
- surface: "oauth",
266
- operation: "oauth_callback",
267
- severity: "error",
268
- context: { route: "/oauth/callback" },
269
- });
270
- res.writeHead(500, { "Content-Type": "text/html; charset=utf-8" });
271
- res.end(renderErrorPage("OAuth callback failed. Please retry /login."));
272
- });
273
- return;
274
- }
275
- res.writeHead(404);
276
- res.end();
201
+ const oauthServiceHint = linkToken.providerId
202
+ ? resolveOAuthService(linkToken.providerId)
203
+ : undefined;
204
+ const oauthServices = getOAuthServices();
205
+ const defaultMode = oauthServiceHint ? "oauth" : "api_key";
206
+ const existingSecrets = describeVaultSecrets(vaultManager, linkToken.vaultId);
207
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
208
+ res.end(renderCredentialPage(rawToken, oauthServiceHint ? `${oauthServiceHint.label} OAuth` : "Store Secret", defaultMode, "", "Secret value", "sk-...", oauthServiceHint
209
+ ? `Authorize ${oauthServiceHint.label} and store tokens in your vault.`
210
+ : "Set any environment variable key/value pair in your vault.", oauthServices, oauthServiceHint?.id, existingSecrets));
211
+ return true;
277
212
  }
278
- catch (err) {
279
- log.logWarning("Link server request error", err instanceof Error ? err.message : String(err));
280
- if (!res.headersSent) {
281
- res.writeHead(500, { "Content-Type": "application/json" });
282
- }
283
- res.end(JSON.stringify({ error: "Internal server error" }));
213
+ if (req.method === "POST" && url.pathname === "/api/link/complete") {
214
+ if (!enforceCsrf(req, res))
215
+ return true;
216
+ void readJsonBody(req, res, async (body) => {
217
+ await handleLinkComplete(body, linkTokenStore, vaultManager, notify, res);
218
+ });
219
+ return true;
284
220
  }
285
- });
286
- // Bind to loopback when MIKAN_LINK_URL is unset so the credential UI and OAuth
287
- // callbacks are not exposed on public interfaces by default. Production
288
- // deployments set MIKAN_LINK_URL and are expected to front this server with a
289
- // reverse proxy, which can still reach it via 0.0.0.0.
290
- const bindHost = resolveLinkBaseUrl() ? undefined : "127.0.0.1";
291
- server.listen(port, bindHost, () => {
292
- log.logInfo(`Link callback server listening on ${bindHost ?? "0.0.0.0"}:${port}`);
293
- if (!resolveLinkBaseUrl()) {
294
- log.logWarning("MIKAN_LINK_URL is not set — bound to 127.0.0.1 and OAuth redirect_uri will be " +
295
- "derived from request headers (Host / X-Forwarded-*). Set " +
296
- "MIKAN_LINK_URL=https://your-host.example.com for production.");
221
+ if (req.method === "POST" && url.pathname === "/api/oauth/start") {
222
+ if (!enforceCsrf(req, res))
223
+ return true;
224
+ void readJsonBody(req, res, async (body) => {
225
+ await handleOAuthStart(body, req, linkTokenStore, oauthStates, res);
226
+ });
227
+ return true;
297
228
  }
298
- });
299
- server.on("error", (err) => {
300
- log.logWarning("Link server error", err.message);
301
- });
302
- return server;
303
- }
304
- /**
305
- * Resolve the externally-visible base URL of this server.
306
- *
307
- * Prefers MIKAN_LINK_URL (see config.ts) so the OAuth `redirect_uri` is
308
- * deterministic and not influenced by attacker-controlled request headers.
309
- * Falls back to Host / X-Forwarded-* only when no base URL is configured
310
- * — intended for local development.
311
- */
312
- function requestBaseUrl(req) {
313
- const configured = resolveLinkBaseUrl();
314
- if (configured)
315
- return configured;
316
- const protoRaw = req.headers["x-forwarded-proto"]?.split(",")[0]?.trim();
317
- const proto = protoRaw || "http";
318
- const host = (req.headers["x-forwarded-host"]?.split(",")[0]?.trim() ??
319
- req.headers.host ??
320
- `localhost`) ||
321
- `localhost`;
322
- return `${proto}://${host}`;
229
+ if (req.method === "GET" && url.pathname === "/oauth/callback") {
230
+ void handleOAuthCallback(url, req, linkTokenStore, vaultManager, notify, oauthStates, res).catch((err) => {
231
+ log.logWarning("OAuth callback failed", err.message);
232
+ reportUserFacingError(err, {
233
+ domain: "login",
234
+ surface: "oauth",
235
+ operation: "oauth_callback",
236
+ severity: "error",
237
+ context: { route: "/oauth/callback" },
238
+ });
239
+ res.writeHead(500, { "Content-Type": "text/html; charset=utf-8" });
240
+ res.end(renderErrorPage("OAuth callback failed. Please retry /login."));
241
+ });
242
+ return true;
243
+ }
244
+ return false;
245
+ };
323
246
  }
324
247
  /**
325
248
  * Block cross-site POSTs to the credential endpoints. Two defenses: