@geminixiang/mikan 1.0.0-beta.80 → 1.0.0-beta.82

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 (67) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/adapters/github/bot.d.ts.map +1 -1
  3. package/dist/adapters/github/bot.js +4 -4
  4. package/dist/adapters/github/bot.js.map +1 -1
  5. package/dist/adapters/github/client.d.ts +4 -0
  6. package/dist/adapters/github/client.d.ts.map +1 -1
  7. package/dist/adapters/github/client.js +41 -23
  8. package/dist/adapters/github/client.js.map +1 -1
  9. package/dist/adapters/github/github-ops.js +16 -12
  10. package/dist/adapters/github/github-ops.js.map +1 -1
  11. package/dist/adapters/slack/bot.d.ts.map +1 -1
  12. package/dist/adapters/slack/bot.js +6 -3
  13. package/dist/adapters/slack/bot.js.map +1 -1
  14. package/dist/adapters/web/server.d.ts +2 -0
  15. package/dist/adapters/web/server.d.ts.map +1 -1
  16. package/dist/adapters/web/server.js +11 -4
  17. package/dist/adapters/web/server.js.map +1 -1
  18. package/dist/adapters/web/session-view/portal.d.ts.map +1 -1
  19. package/dist/adapters/web/session-view/portal.js +84 -63
  20. package/dist/adapters/web/session-view/portal.js.map +1 -1
  21. package/dist/adapters/web/session-view/types.d.ts +2 -0
  22. package/dist/adapters/web/session-view/types.d.ts.map +1 -1
  23. package/dist/adapters/web/session-view/types.js.map +1 -1
  24. package/dist/env-manifest.d.ts +1 -0
  25. package/dist/env-manifest.d.ts.map +1 -1
  26. package/dist/env-manifest.js +7 -0
  27. package/dist/env-manifest.js.map +1 -1
  28. package/dist/harness/loop-guard.d.ts +10 -0
  29. package/dist/harness/loop-guard.d.ts.map +1 -0
  30. package/dist/harness/loop-guard.js +111 -0
  31. package/dist/harness/loop-guard.js.map +1 -0
  32. package/dist/harness/presenter.d.ts.map +1 -1
  33. package/dist/harness/presenter.js +159 -142
  34. package/dist/harness/presenter.js.map +1 -1
  35. package/dist/harness/prompt.d.ts +1 -1
  36. package/dist/harness/prompt.d.ts.map +1 -1
  37. package/dist/harness/prompt.js +14 -21
  38. package/dist/harness/prompt.js.map +1 -1
  39. package/dist/harness/run-events.d.ts +10 -0
  40. package/dist/harness/run-events.d.ts.map +1 -0
  41. package/dist/harness/run-events.js +129 -0
  42. package/dist/harness/run-events.js.map +1 -0
  43. package/dist/harness/runner.d.ts.map +1 -1
  44. package/dist/harness/runner.js +79 -49
  45. package/dist/harness/runner.js.map +1 -1
  46. package/dist/harness/session.d.ts +5 -0
  47. package/dist/harness/session.d.ts.map +1 -1
  48. package/dist/harness/session.js +71 -1
  49. package/dist/harness/session.js.map +1 -1
  50. package/dist/harness/tools/react.d.ts +1 -0
  51. package/dist/harness/tools/react.d.ts.map +1 -1
  52. package/dist/harness/tools/react.js +3 -2
  53. package/dist/harness/tools/react.js.map +1 -1
  54. package/dist/harness/types.d.ts +61 -0
  55. package/dist/harness/types.d.ts.map +1 -1
  56. package/dist/harness/types.js.map +1 -1
  57. package/dist/main.js +11 -8
  58. package/dist/main.js.map +1 -1
  59. package/dist/runtime/conversation-runtime.js +1 -0
  60. package/dist/runtime/conversation-runtime.js.map +1 -1
  61. package/dist/runtime/types.d.ts +2 -1
  62. package/dist/runtime/types.d.ts.map +1 -1
  63. package/dist/runtime/types.js.map +1 -1
  64. package/dist/types.d.ts +2 -1
  65. package/dist/types.d.ts.map +1 -1
  66. package/dist/types.js.map +1 -1
  67. package/package.json +2 -2
@@ -13,6 +13,7 @@ import type { Workspace } from "../../office/types.js";
13
13
  import type { GithubWebhookOptions } from "../github/types.js";
14
14
  interface StartWebServerOptions {
15
15
  port: number;
16
+ host: string;
16
17
  linkTokenStore: InMemoryLinkTokenStore;
17
18
  vaultManager: VaultManager;
18
19
  notify: NotifyFn;
@@ -29,5 +30,6 @@ interface StartWebServerOptions {
29
30
  githubWebhook?: GithubWebhookOptions;
30
31
  }
31
32
  export declare function startWebServer(options: StartWebServerOptions): Server;
33
+ export declare function closeWebServer(server: Server, graceMs: number): Promise<void>;
32
34
  export {};
33
35
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/adapters/web/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,MAAM,EAAuB,MAAM,WAAW,CAAC;AACjG,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAGjE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,yBAAyB,CAAC;AAC7E,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,0BAA0B,CAAC;AAC9E,OAAO,KAAK,EAAU,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAE/D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAG/D,UAAU,qBAAqB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,sBAAsB,CAAC;IACvC,YAAY,EAAE,YAAY,CAAC;IAC3B,MAAM,EAAE,QAAQ,CAAC;IACjB,qBAAqB,CAAC,EAAE,6BAA6B,CAAC;IACtD,sBAAsB,CAAC,EAAE,6BAA6B,CAAC;IACvD,YAAY,CAAC,EAAE;QACb,eAAe,EAAE,uBAAuB,CAAC;QACzC,SAAS,CAAC,EAAE,SAAS,CAAC;QACtB,OAAO,CAAC,EAAE,kBAAkB,CAAC;QAC7B,OAAO,CAAC,EAAE,aAAa,CAAC;QACxB,cAAc,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC;QAC7D,cAAc,CAAC,EAAE,MAAM,iBAAiB,GAAG,SAAS,CAAC;KACtD,CAAC;IACF,aAAa,CAAC,EAAE,oBAAoB,CAAC;CACtC;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAwFrE"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/adapters/web/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,MAAM,EAAuB,MAAM,WAAW,CAAC;AACjG,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAGjE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,yBAAyB,CAAC;AAC7E,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,0BAA0B,CAAC;AAC9E,OAAO,KAAK,EAAU,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAE/D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAG/D,UAAU,qBAAqB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,sBAAsB,CAAC;IACvC,YAAY,EAAE,YAAY,CAAC;IAC3B,MAAM,EAAE,QAAQ,CAAC;IACjB,qBAAqB,CAAC,EAAE,6BAA6B,CAAC;IACtD,sBAAsB,CAAC,EAAE,6BAA6B,CAAC;IACvD,YAAY,CAAC,EAAE;QACb,eAAe,EAAE,uBAAuB,CAAC;QACzC,SAAS,CAAC,EAAE,SAAS,CAAC;QACtB,OAAO,CAAC,EAAE,kBAAkB,CAAC;QAC7B,OAAO,CAAC,EAAE,aAAa,CAAC;QACxB,cAAc,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC;QAC7D,cAAc,CAAC,EAAE,MAAM,iBAAiB,GAAG,SAAS,CAAC;KACtD,CAAC;IACF,aAAa,CAAC,EAAE,oBAAoB,CAAC;CACtC;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAuFrE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAO7E"}
@@ -55,11 +55,10 @@ export function startWebServer(options) {
55
55
  res.end(JSON.stringify({ error: "Internal server error" }));
56
56
  }
57
57
  });
58
- const bindHost = resolveLinkBaseUrl() ? undefined : "127.0.0.1";
59
- server.listen(options.port, bindHost, () => {
60
- log.logInfo(`Web server listening on ${bindHost ?? "0.0.0.0"}:${options.port}`);
58
+ server.listen(options.port, options.host, () => {
59
+ log.logInfo(`Web server listening on ${options.host}:${options.port}`);
61
60
  if (!resolveLinkBaseUrl()) {
62
- log.logWarning("MIKAN_LINK_URL is not set — bound to 127.0.0.1 and OAuth redirect_uri will be " +
61
+ log.logWarning("MIKAN_LINK_URL is not set — OAuth redirect_uri will be " +
63
62
  "derived from request headers (Host / X-Forwarded-*). Set " +
64
63
  "MIKAN_LINK_URL=https://your-host.example.com for production.");
65
64
  }
@@ -69,4 +68,12 @@ export function startWebServer(options) {
69
68
  });
70
69
  return server;
71
70
  }
71
+ export function closeWebServer(server, graceMs) {
72
+ const closed = new Promise((resolve, reject) => {
73
+ server.close((error) => (error ? reject(error) : resolve()));
74
+ });
75
+ server.closeIdleConnections();
76
+ const forceClose = setTimeout(() => server.closeAllConnections(), graceMs);
77
+ return closed.finally(() => clearTimeout(forceClose));
78
+ }
72
79
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/adapters/web/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\nimport type { MessagingBot, PlatformName } from \"../../types.js\";\nimport { resolveLinkBaseUrl } from \"../../env-manifest.js\";\nimport * as log from \"../../log.js\";\nimport type { SandboxConfig } from \"../../sandbox/types.js\";\nimport { OfficeEventStore, type EventScheduleSink } from \"../../events/index.js\";\nimport type { VaultManager } from \"../../vault/types.js\";\nimport { handleAdminRequest } from \"./admin/portal.js\";\nimport type { AdminRuntimeBridge } from \"./admin/types.js\";\nimport type { InMemoryAdminTokenStore } from \"./admin/portal.js\";\nimport { createLoginRequestHandler } from \"./login/portal.js\";\nimport { requestBaseUrl } from \"./portal-shell.js\";\nimport type { InMemoryLinkTokenStore } from \"./login/portal.js\";\nimport type { NotifyFn } from \"./login/types.js\";\nimport { handleSessionViewRequest } from \"./session-view/portal.js\";\nimport type { SessionViewInteractiveOptions } from \"./session-view/types.js\";\nimport type { InMemorySessionViewTokenStore } from \"./session-view/portal.js\";\nimport type { Office, Workspace } from \"../../office/types.js\";\nimport { handleGithubWebhookRequest } from \"../github/webhook.js\";\nimport type { GithubWebhookOptions } from \"../github/types.js\";\nimport { errorMessage } from \"../../unknown-values.js\";\n\ninterface StartWebServerOptions {\n port: number;\n linkTokenStore: InMemoryLinkTokenStore;\n vaultManager: VaultManager;\n notify: NotifyFn;\n sessionViewTokenStore?: InMemorySessionViewTokenStore;\n sessionViewInteractive?: SessionViewInteractiveOptions;\n adminOptions?: {\n adminTokenStore: InMemoryAdminTokenStore;\n workspace?: Workspace;\n runtime?: AdminRuntimeBridge;\n sandbox?: SandboxConfig;\n botsByPlatform?: Partial<Record<PlatformName, MessagingBot>>;\n eventScheduler?: () => EventScheduleSink | undefined;\n };\n githubWebhook?: GithubWebhookOptions;\n}\n\nexport function startWebServer(options: StartWebServerOptions): Server {\n const loginHandler = createLoginRequestHandler(\n options.linkTokenStore,\n options.vaultManager,\n options.notify,\n );\n\n const adminEventStore = (office: Office) =>\n new OfficeEventStore(office, options.adminOptions?.eventScheduler?.());\n\n const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n try {\n const url = new URL(req.url ?? \"/\", requestBaseUrl(req));\n\n if (req.method === \"GET\" && url.pathname === \"/health\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: true }));\n return;\n }\n\n if (\n options.githubWebhook &&\n (await handleGithubWebhookRequest(req, res, url, options.githubWebhook))\n ) {\n return;\n }\n\n const adminOptions = options.adminOptions;\n if (\n adminOptions?.adminTokenStore &&\n (await handleAdminRequest(req, res, url, {\n vaultManager: options.vaultManager,\n linkTokenStore: options.linkTokenStore,\n sessionViewTokenStore: options.sessionViewTokenStore,\n adminTokenStore: adminOptions.adminTokenStore,\n portalBaseUrl: resolveLinkBaseUrl() ?? undefined,\n workspace: adminOptions.workspace,\n eventStore: adminEventStore,\n runtime: adminOptions.runtime,\n sandbox: adminOptions.sandbox,\n botsByPlatform: adminOptions.botsByPlatform,\n }))\n ) {\n return;\n }\n\n if (\n await handleSessionViewRequest(\n req,\n res,\n url,\n options.sessionViewTokenStore,\n options.sessionViewInteractive,\n )\n ) {\n return;\n }\n\n if (loginHandler(req, res, url)) return;\n\n res.writeHead(404);\n res.end();\n } catch (err) {\n log.logWarning(\"Web server request error\", errorMessage(err));\n if (!res.headersSent) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n }\n res.end(JSON.stringify({ error: \"Internal server error\" }));\n }\n });\n\n const bindHost = resolveLinkBaseUrl() ? undefined : \"127.0.0.1\";\n server.listen(options.port, bindHost, () => {\n log.logInfo(`Web server listening on ${bindHost ?? \"0.0.0.0\"}:${options.port}`);\n if (!resolveLinkBaseUrl()) {\n log.logWarning(\n \"MIKAN_LINK_URL is not set — bound to 127.0.0.1 and OAuth redirect_uri will be \" +\n \"derived from request headers (Host / X-Forwarded-*). Set \" +\n \"MIKAN_LINK_URL=https://your-host.example.com for production.\",\n );\n }\n });\n\n server.on(\"error\", (err) => {\n log.logWarning(\"Web server error\", err.message);\n });\n\n return server;\n}\n"]}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/adapters/web/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\nimport type { MessagingBot, PlatformName } from \"../../types.js\";\nimport { resolveLinkBaseUrl } from \"../../env-manifest.js\";\nimport * as log from \"../../log.js\";\nimport type { SandboxConfig } from \"../../sandbox/types.js\";\nimport { OfficeEventStore, type EventScheduleSink } from \"../../events/index.js\";\nimport type { VaultManager } from \"../../vault/types.js\";\nimport { handleAdminRequest } from \"./admin/portal.js\";\nimport type { AdminRuntimeBridge } from \"./admin/types.js\";\nimport type { InMemoryAdminTokenStore } from \"./admin/portal.js\";\nimport { createLoginRequestHandler } from \"./login/portal.js\";\nimport { requestBaseUrl } from \"./portal-shell.js\";\nimport type { InMemoryLinkTokenStore } from \"./login/portal.js\";\nimport type { NotifyFn } from \"./login/types.js\";\nimport { handleSessionViewRequest } from \"./session-view/portal.js\";\nimport type { SessionViewInteractiveOptions } from \"./session-view/types.js\";\nimport type { InMemorySessionViewTokenStore } from \"./session-view/portal.js\";\nimport type { Office, Workspace } from \"../../office/types.js\";\nimport { handleGithubWebhookRequest } from \"../github/webhook.js\";\nimport type { GithubWebhookOptions } from \"../github/types.js\";\nimport { errorMessage } from \"../../unknown-values.js\";\n\ninterface StartWebServerOptions {\n port: number;\n host: string;\n linkTokenStore: InMemoryLinkTokenStore;\n vaultManager: VaultManager;\n notify: NotifyFn;\n sessionViewTokenStore?: InMemorySessionViewTokenStore;\n sessionViewInteractive?: SessionViewInteractiveOptions;\n adminOptions?: {\n adminTokenStore: InMemoryAdminTokenStore;\n workspace?: Workspace;\n runtime?: AdminRuntimeBridge;\n sandbox?: SandboxConfig;\n botsByPlatform?: Partial<Record<PlatformName, MessagingBot>>;\n eventScheduler?: () => EventScheduleSink | undefined;\n };\n githubWebhook?: GithubWebhookOptions;\n}\n\nexport function startWebServer(options: StartWebServerOptions): Server {\n const loginHandler = createLoginRequestHandler(\n options.linkTokenStore,\n options.vaultManager,\n options.notify,\n );\n\n const adminEventStore = (office: Office) =>\n new OfficeEventStore(office, options.adminOptions?.eventScheduler?.());\n\n const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n try {\n const url = new URL(req.url ?? \"/\", requestBaseUrl(req));\n\n if (req.method === \"GET\" && url.pathname === \"/health\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ ok: true }));\n return;\n }\n\n if (\n options.githubWebhook &&\n (await handleGithubWebhookRequest(req, res, url, options.githubWebhook))\n ) {\n return;\n }\n\n const adminOptions = options.adminOptions;\n if (\n adminOptions?.adminTokenStore &&\n (await handleAdminRequest(req, res, url, {\n vaultManager: options.vaultManager,\n linkTokenStore: options.linkTokenStore,\n sessionViewTokenStore: options.sessionViewTokenStore,\n adminTokenStore: adminOptions.adminTokenStore,\n portalBaseUrl: resolveLinkBaseUrl() ?? undefined,\n workspace: adminOptions.workspace,\n eventStore: adminEventStore,\n runtime: adminOptions.runtime,\n sandbox: adminOptions.sandbox,\n botsByPlatform: adminOptions.botsByPlatform,\n }))\n ) {\n return;\n }\n\n if (\n await handleSessionViewRequest(\n req,\n res,\n url,\n options.sessionViewTokenStore,\n options.sessionViewInteractive,\n )\n ) {\n return;\n }\n\n if (loginHandler(req, res, url)) return;\n\n res.writeHead(404);\n res.end();\n } catch (err) {\n log.logWarning(\"Web server request error\", errorMessage(err));\n if (!res.headersSent) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n }\n res.end(JSON.stringify({ error: \"Internal server error\" }));\n }\n });\n\n server.listen(options.port, options.host, () => {\n log.logInfo(`Web server listening on ${options.host}:${options.port}`);\n if (!resolveLinkBaseUrl()) {\n log.logWarning(\n \"MIKAN_LINK_URL is not set — OAuth redirect_uri will be \" +\n \"derived from request headers (Host / X-Forwarded-*). Set \" +\n \"MIKAN_LINK_URL=https://your-host.example.com for production.\",\n );\n }\n });\n\n server.on(\"error\", (err) => {\n log.logWarning(\"Web server error\", err.message);\n });\n\n return server;\n}\n\nexport function closeWebServer(server: Server, graceMs: number): Promise<void> {\n const closed = new Promise<void>((resolve, reject) => {\n server.close((error) => (error ? reject(error) : resolve()));\n });\n server.closeIdleConnections();\n const forceClose = setTimeout(() => server.closeAllConnections(), graceMs);\n return closed.finally(() => clearTimeout(forceClose));\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../../../src/adapters/web/session-view/portal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAgBjE,OAAO,KAAK,EAGV,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,KAAK,EAGV,6BAA6B,EAC9B,MAAM,mBAAmB,CAAC;AAmB3B,qBAAa,6BAA8B,SAAQ,kBAAkB,CAAC,gBAAgB,CAAC;IACrF,MAAM,CAAC,OAAO,EAAE,6BAA6B,GAAG,gBAAgB,CAS/D;CACF;AA4DD,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,YAAY,CAAC;AAGhE,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,GAAG,EACR,qBAAqB,CAAC,EAAE,6BAA6B,EACrD,WAAW,CAAC,EAAE,6BAA6B,GAC1C,OAAO,CAAC,OAAO,CAAC,CAuElB;AAwLD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG;IAC1C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB,CA4BA;AA24CD,wBAAgB,0BAA0B,CACxC,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE,MAAM,GACjB,MAAM,GAAG,IAAI,CAKf;AAED,wBAAsB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAyDzF"}
1
+ {"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../../../src/adapters/web/session-view/portal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAgBjE,OAAO,KAAK,EAGV,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,KAAK,EAGV,6BAA6B,EAC9B,MAAM,mBAAmB,CAAC;AAmB3B,qBAAa,6BAA8B,SAAQ,kBAAkB,CAAC,gBAAgB,CAAC;IACrF,MAAM,CAAC,OAAO,EAAE,6BAA6B,GAAG,gBAAgB,CAS/D;CACF;AA6DD,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,YAAY,CAAC;AAIhE,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,GAAG,EACR,qBAAqB,CAAC,EAAE,6BAA6B,EACrD,WAAW,CAAC,EAAE,6BAA6B,GAC1C,OAAO,CAAC,OAAO,CAAC,CAuElB;AAwLD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG;IAC1C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB,CA4BA;AAm6CD,wBAAgB,0BAA0B,CACxC,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE,MAAM,GACjB,MAAM,GAAG,IAAI,CAKf;AAED,wBAAsB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAyDzF"}
@@ -415,15 +415,26 @@ async function handleSessionStreamRequest(req, res, url, sessionViewTokenStore,
415
415
  type: "status",
416
416
  running: interactive.handler.isRunning(createOfficeAddress(entry.platform, entry.conversationId), activeSessionKey),
417
417
  })}\n\n`);
418
- const unsubscribe = sessionViewStreamHub.subscribe(streamKey, (event) => {
419
- res.write(`data: ${JSON.stringify(event)}\n\n`);
418
+ const send = (event) => res.write(`data: ${JSON.stringify(event)}\n\n`);
419
+ const unsubscribe = sessionViewStreamHub.subscribe(streamKey, send);
420
+ const liveRun = createLiveRunProjection(send, async () => {
421
+ const model = await loadSessionViewModel(target.targetSessionFile);
422
+ return {
423
+ type: "refresh",
424
+ timelineHtml: renderTimelineItems(model.items, url.searchParams.get("token") ?? ""),
425
+ updatedAt: formatDate(model.updatedAt),
426
+ entryCount: model.entryCount,
427
+ running: false,
428
+ };
420
429
  });
430
+ const unsubscribeRun = interactive.runEvents.subscribe(createOfficeAddress(entry.platform, entry.conversationId), activeSessionKey, liveRun);
421
431
  const heartbeat = setInterval(() => {
422
432
  res.write(": keep-alive\n\n");
423
433
  }, 15000);
424
434
  req.on("close", () => {
425
435
  clearInterval(heartbeat);
426
436
  unsubscribe();
437
+ unsubscribeRun();
427
438
  });
428
439
  }
429
440
  async function handleSessionMessageRequest(req, res, sessionViewTokenStore, interactive) {
@@ -447,23 +458,15 @@ async function handleSessionMessageRequest(req, res, sessionViewTokenStore, inte
447
458
  json(res, target.status, { ok: false, error: target.message });
448
459
  return;
449
460
  }
450
- const { activeSessionKey, entry, targetSessionFile } = target;
461
+ const { activeSessionKey, entry } = target;
451
462
  if (requestedSessionKey && requestedSessionKey !== activeSessionKey) {
452
463
  json(res, 400, { ok: false, error: "Session target mismatch." });
453
464
  return;
454
465
  }
455
- await dispatchSessionViewMessage({
456
- res,
457
- interactive,
458
- token,
459
- text,
460
- activeSessionKey,
461
- entry,
462
- targetSessionFile,
463
- });
466
+ await dispatchSessionViewMessage({ res, interactive, text, activeSessionKey, entry });
464
467
  }
465
468
  async function dispatchSessionViewMessage(input) {
466
- const { res, interactive, token, text, activeSessionKey, entry, targetSessionFile } = input;
469
+ const { res, interactive, text, activeSessionKey, entry } = input;
467
470
  const bot = interactive.botsByPlatform[entry.platform];
468
471
  if (!bot) {
469
472
  json(res, 503, { ok: false, error: `No bot configured for ${entry.platform}.` });
@@ -477,9 +480,7 @@ async function dispatchSessionViewMessage(input) {
477
480
  platformInfo.users.find((user) => user.id === entry.platformUserId)?.userName ||
478
481
  platformInfo.users.find((user) => user.id === entry.platformUserId)?.displayName ||
479
482
  "unknown";
480
- const responder = createSessionViewResponseContext((event) => {
481
- sessionViewStreamHub.publish(streamKey, event);
482
- });
483
+ const responder = createSilentResponder();
483
484
  const event = createConversationEvent({
484
485
  platform: entry.platform,
485
486
  type: "session_view",
@@ -510,24 +511,7 @@ async function dispatchSessionViewMessage(input) {
510
511
  responder,
511
512
  platform: { ...platformInfo, diagnostics: { showUsageSummary: false } },
512
513
  };
513
- sessionViewStreamHub.publish(streamKey, { type: "status", running: true });
514
- sessionViewStreamHub.publish(streamKey, {
515
- type: "user",
516
- html: renderLiveUserMessage(text, platformUserName),
517
- });
518
- void interactive.handler
519
- .handleEvent(event, bot, context)
520
- .then(async () => {
521
- const model = await loadSessionViewModel(targetSessionFile);
522
- sessionViewStreamHub.publish(streamKey, {
523
- type: "refresh",
524
- timelineHtml: renderTimelineItems(model.items, token),
525
- updatedAt: formatDate(model.updatedAt),
526
- entryCount: model.entryCount,
527
- running: false,
528
- });
529
- })
530
- .catch((error) => {
514
+ void interactive.handler.handleEvent(event, bot, context).catch((error) => {
531
515
  log.logWarning(`[${entry.conversationId}] Session view message failed`, errorMessage(error));
532
516
  reportUserFacingError(error, {
533
517
  domain: "session_view",
@@ -550,36 +534,70 @@ async function dispatchSessionViewMessage(input) {
550
534
  });
551
535
  json(res, 202, { ok: true, accepted: true });
552
536
  }
553
- function createSessionViewResponseContext(publish) {
554
- let accumulatedText = "";
537
+ async function ignoreResponse() { }
538
+ function createSilentResponder() {
539
+ const ignore = ignoreResponse;
555
540
  return {
556
- respond: async (text) => {
557
- accumulatedText = accumulatedText ? `${accumulatedText}\n${text}` : text;
558
- publish({ type: "assistant", html: renderLiveAssistantMessage(accumulatedText) });
559
- },
560
- replaceResponse: async (text) => {
561
- accumulatedText = text;
562
- publish({ type: "assistant", html: renderLiveAssistantMessage(accumulatedText) });
563
- },
564
- respondDiagnostic: async (text, options) => {
565
- if (options?.style === "error") {
566
- publish({ type: "system", html: renderLiveSystemEvent(text, "err") });
567
- }
568
- },
569
- respondToolResult: async (result) => {
570
- publish({ type: "tool", html: renderLiveToolResult(result) });
571
- },
572
- setTyping: async () => {
573
- publish({ type: "status", running: true });
574
- },
575
- setWorking: async (working) => {
576
- publish({ type: "status", running: working });
577
- },
578
- uploadFile: async () => { },
579
- deleteResponse: async () => {
580
- accumulatedText = "";
581
- publish({ type: "assistant_remove" });
582
- },
541
+ respond: ignore,
542
+ replaceResponse: ignore,
543
+ respondDiagnostic: ignore,
544
+ respondToolResult: ignore,
545
+ setTyping: ignore,
546
+ setWorking: ignore,
547
+ uploadFile: ignore,
548
+ deleteResponse: ignore,
549
+ };
550
+ }
551
+ function createLiveRunProjection(send, refresh) {
552
+ let answer = "";
553
+ return (event) => {
554
+ switch (event.type) {
555
+ case "run_started":
556
+ answer = "";
557
+ send({ type: "status", running: true });
558
+ send({ type: "user", html: renderLiveUserMessage(event.text, event.userName) });
559
+ return;
560
+ case "tool_ended":
561
+ send({
562
+ type: "tool",
563
+ html: renderLiveToolResult({
564
+ toolName: event.toolName,
565
+ result: event.resultText,
566
+ isError: event.isError,
567
+ }),
568
+ });
569
+ return;
570
+ case "assistant_delta":
571
+ answer += event.delta;
572
+ send({ type: "assistant", html: renderLiveAssistantMessage(answer) });
573
+ return;
574
+ case "assistant_message":
575
+ answer = "";
576
+ send({ type: "assistant_done" });
577
+ return;
578
+ case "compaction_started":
579
+ send({ type: "system", html: renderLiveSystemEvent("Compacting context…") });
580
+ return;
581
+ case "retry_started":
582
+ send({
583
+ type: "system",
584
+ html: renderLiveSystemEvent(`Retrying (${event.attempt}/${event.maxAttempts})…`),
585
+ });
586
+ return;
587
+ case "budget_exceeded":
588
+ send({
589
+ type: "system",
590
+ html: renderLiveSystemEvent(`Stopped: run budget exceeded (${event.reason})`, "err"),
591
+ });
592
+ return;
593
+ case "run_ended":
594
+ void refresh()
595
+ .then(send)
596
+ .catch((error) => send({ type: "error", message: errorMessage(error) }));
597
+ return;
598
+ default:
599
+ return;
600
+ }
583
601
  };
584
602
  }
585
603
  function renderStatusPage(title, message) {
@@ -722,6 +740,9 @@ const sessionViewScript = `
722
740
  if (liveAssistant?.isConnected) liveAssistant.remove();
723
741
  liveAssistant = null;
724
742
  break;
743
+ case 'assistant_done':
744
+ liveAssistant = null;
745
+ break;
725
746
  case 'refresh':
726
747
  if (timelineList) timelineList.innerHTML = payload.timelineHtml;
727
748
  liveAssistant = null;