@amodalai/runtime 0.2.0 → 0.2.2

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 (268) hide show
  1. package/dist/src/__fixtures__/README.md +4 -0
  2. package/dist/src/{agent/user-context-fetcher.test.d.ts → __fixtures__/e2e.test.d.ts} +1 -1
  3. package/dist/src/__fixtures__/e2e.test.js +211 -0
  4. package/dist/src/__fixtures__/e2e.test.js.map +1 -0
  5. package/dist/src/__fixtures__/smoke-agent/automations/delivery-callback-test.json +9 -0
  6. package/dist/src/__fixtures__/smoke-agent/connections/mock-mcp/spec.json +1 -1
  7. package/dist/src/__fixtures__/smoke.test.js +715 -29
  8. package/dist/src/__fixtures__/smoke.test.js.map +1 -1
  9. package/dist/src/__fixtures__/test-env.d.ts +27 -0
  10. package/dist/src/__fixtures__/test-env.js +64 -0
  11. package/dist/src/__fixtures__/test-env.js.map +1 -0
  12. package/dist/src/__fixtures__/test-helpers.d.ts +30 -0
  13. package/dist/src/__fixtures__/test-helpers.js +120 -0
  14. package/dist/src/__fixtures__/test-helpers.js.map +1 -0
  15. package/dist/src/agent/agent-types.d.ts +22 -0
  16. package/dist/src/agent/agent-types.js.map +1 -1
  17. package/dist/src/agent/automation-bridge.d.ts +9 -0
  18. package/dist/src/agent/automation-bridge.js +26 -0
  19. package/dist/src/agent/automation-bridge.js.map +1 -1
  20. package/dist/src/agent/automation-bridge.test.js +63 -0
  21. package/dist/src/agent/automation-bridge.test.js.map +1 -1
  22. package/dist/src/agent/local-server.d.ts +0 -7
  23. package/dist/src/agent/local-server.js +274 -87
  24. package/dist/src/agent/local-server.js.map +1 -1
  25. package/dist/src/agent/local-server.test.js +14 -11
  26. package/dist/src/agent/local-server.test.js.map +1 -1
  27. package/dist/src/agent/loop-types.d.ts +81 -7
  28. package/dist/src/agent/loop-types.js +4 -0
  29. package/dist/src/agent/loop-types.js.map +1 -1
  30. package/dist/src/agent/loop.js +16 -3
  31. package/dist/src/agent/loop.js.map +1 -1
  32. package/dist/src/agent/loop.test.js +572 -10
  33. package/dist/src/agent/loop.test.js.map +1 -1
  34. package/dist/src/agent/page-builder.js +20 -17
  35. package/dist/src/agent/page-builder.js.map +1 -1
  36. package/dist/src/agent/proactive/delivery-router.d.ts +68 -0
  37. package/dist/src/agent/proactive/delivery-router.js +337 -0
  38. package/dist/src/agent/proactive/delivery-router.js.map +1 -0
  39. package/dist/src/agent/proactive/delivery-router.test.d.ts +6 -0
  40. package/dist/src/agent/proactive/delivery-router.test.js +455 -0
  41. package/dist/src/agent/proactive/delivery-router.test.js.map +1 -0
  42. package/dist/src/agent/proactive/proactive-runner.d.ts +23 -1
  43. package/dist/src/agent/proactive/proactive-runner.js +42 -10
  44. package/dist/src/agent/proactive/proactive-runner.js.map +1 -1
  45. package/dist/src/agent/proactive/proactive-runner.test.js +0 -3
  46. package/dist/src/agent/proactive/proactive-runner.test.js.map +1 -1
  47. package/dist/src/agent/routes/admin-chat-abort.test.d.ts +6 -0
  48. package/dist/src/agent/routes/admin-chat-abort.test.js +206 -0
  49. package/dist/src/agent/routes/admin-chat-abort.test.js.map +1 -0
  50. package/dist/src/agent/routes/admin-chat.js +0 -3
  51. package/dist/src/agent/routes/admin-chat.js.map +1 -1
  52. package/dist/src/agent/routes/files.js +46 -52
  53. package/dist/src/agent/routes/files.js.map +1 -1
  54. package/dist/src/agent/routes/inspect.js +4 -6
  55. package/dist/src/agent/routes/inspect.js.map +1 -1
  56. package/dist/src/agent/routes/task.test.js +0 -3
  57. package/dist/src/agent/routes/task.test.js.map +1 -1
  58. package/dist/src/agent/snapshot-server.js +37 -3
  59. package/dist/src/agent/snapshot-server.js.map +1 -1
  60. package/dist/src/agent/states/compacting.js +5 -3
  61. package/dist/src/agent/states/compacting.js.map +1 -1
  62. package/dist/src/agent/states/confirming.js +3 -0
  63. package/dist/src/agent/states/confirming.js.map +1 -1
  64. package/dist/src/agent/states/dispatching.js +45 -2
  65. package/dist/src/agent/states/dispatching.js.map +1 -1
  66. package/dist/src/agent/states/executing.js +225 -81
  67. package/dist/src/agent/states/executing.js.map +1 -1
  68. package/dist/src/agent/states/streaming.js +14 -0
  69. package/dist/src/agent/states/streaming.js.map +1 -1
  70. package/dist/src/agent/states/thinking.d.ts +1 -1
  71. package/dist/src/agent/states/thinking.js +246 -29
  72. package/dist/src/agent/states/thinking.js.map +1 -1
  73. package/dist/src/agent/token-estimate.d.ts +20 -6
  74. package/dist/src/agent/token-estimate.js +24 -3
  75. package/dist/src/agent/token-estimate.js.map +1 -1
  76. package/dist/src/agent/token-estimate.test.d.ts +6 -0
  77. package/dist/src/agent/token-estimate.test.js +44 -0
  78. package/dist/src/agent/token-estimate.test.js.map +1 -0
  79. package/dist/src/agent/tool-executor-local.test.js +0 -1
  80. package/dist/src/agent/tool-executor-local.test.js.map +1 -1
  81. package/dist/src/agent/tool-harness-template.js +0 -1
  82. package/dist/src/agent/tool-harness-template.js.map +1 -1
  83. package/dist/src/api/create-agent.js +1 -5
  84. package/dist/src/api/create-agent.js.map +1 -1
  85. package/dist/src/api/types.d.ts +1 -5
  86. package/dist/src/channels/bootstrap.d.ts +59 -0
  87. package/dist/src/channels/bootstrap.js +84 -0
  88. package/dist/src/channels/bootstrap.js.map +1 -0
  89. package/dist/src/channels/channel-session-mapper.d.ts +42 -0
  90. package/dist/src/channels/channel-session-mapper.js +91 -0
  91. package/dist/src/channels/channel-session-mapper.js.map +1 -0
  92. package/dist/src/channels/dedup-cache.d.ts +17 -0
  93. package/dist/src/channels/dedup-cache.js +51 -0
  94. package/dist/src/channels/dedup-cache.js.map +1 -0
  95. package/dist/src/channels/dedup-cache.test.d.ts +6 -0
  96. package/dist/src/channels/dedup-cache.test.js +51 -0
  97. package/dist/src/channels/dedup-cache.test.js.map +1 -0
  98. package/dist/src/channels/errors.d.ts +28 -0
  99. package/dist/src/channels/errors.js +38 -0
  100. package/dist/src/channels/errors.js.map +1 -0
  101. package/dist/src/channels/in-memory-session-mapper.d.ts +34 -0
  102. package/dist/src/channels/in-memory-session-mapper.js +50 -0
  103. package/dist/src/channels/in-memory-session-mapper.js.map +1 -0
  104. package/dist/src/channels/plugin-loader.d.ts +20 -0
  105. package/dist/src/channels/plugin-loader.js +136 -0
  106. package/dist/src/channels/plugin-loader.js.map +1 -0
  107. package/dist/src/channels/plugin-loader.test.d.ts +6 -0
  108. package/dist/src/channels/plugin-loader.test.js +113 -0
  109. package/dist/src/channels/plugin-loader.test.js.map +1 -0
  110. package/dist/src/channels/routes.d.ts +29 -0
  111. package/dist/src/channels/routes.js +165 -0
  112. package/dist/src/channels/routes.js.map +1 -0
  113. package/dist/src/config.d.ts +0 -2
  114. package/dist/src/config.js +0 -1
  115. package/dist/src/config.js.map +1 -1
  116. package/dist/src/config.test.js +0 -2
  117. package/dist/src/config.test.js.map +1 -1
  118. package/dist/src/context/compiler.js +11 -34
  119. package/dist/src/context/compiler.js.map +1 -1
  120. package/dist/src/context/compiler.test.js +7 -60
  121. package/dist/src/context/compiler.test.js.map +1 -1
  122. package/dist/src/context/types.d.ts +0 -4
  123. package/dist/src/env-ref.d.ts +13 -0
  124. package/dist/src/env-ref.js +31 -0
  125. package/dist/src/env-ref.js.map +1 -0
  126. package/dist/src/env-ref.test.d.ts +6 -0
  127. package/dist/src/env-ref.test.js +34 -0
  128. package/dist/src/env-ref.test.js.map +1 -0
  129. package/dist/src/errors.d.ts +15 -0
  130. package/dist/src/errors.js +22 -0
  131. package/dist/src/errors.js.map +1 -1
  132. package/dist/src/errors.test.js +2 -2
  133. package/dist/src/errors.test.js.map +1 -1
  134. package/dist/src/events/event-bus.d.ts +54 -0
  135. package/dist/src/events/event-bus.js +84 -0
  136. package/dist/src/events/event-bus.js.map +1 -0
  137. package/dist/src/events/event-bus.test.d.ts +6 -0
  138. package/dist/src/events/event-bus.test.js +112 -0
  139. package/dist/src/events/event-bus.test.js.map +1 -0
  140. package/dist/src/events/events-route.d.ts +36 -0
  141. package/dist/src/events/events-route.js +80 -0
  142. package/dist/src/events/events-route.js.map +1 -0
  143. package/dist/src/events/events-route.test.d.ts +6 -0
  144. package/dist/src/events/events-route.test.js +134 -0
  145. package/dist/src/events/events-route.test.js.map +1 -0
  146. package/dist/src/events/store-event-wrapper.d.ts +19 -0
  147. package/dist/src/events/store-event-wrapper.js +57 -0
  148. package/dist/src/events/store-event-wrapper.js.map +1 -0
  149. package/dist/src/events/store-event-wrapper.test.d.ts +6 -0
  150. package/dist/src/events/store-event-wrapper.test.js +91 -0
  151. package/dist/src/events/store-event-wrapper.test.js.map +1 -0
  152. package/dist/src/index.d.ts +13 -0
  153. package/dist/src/index.js +10 -0
  154. package/dist/src/index.js.map +1 -1
  155. package/dist/src/middleware/auth.d.ts +0 -2
  156. package/dist/src/middleware/auth.js.map +1 -1
  157. package/dist/src/providers/search-provider.d.ts +64 -0
  158. package/dist/src/providers/search-provider.js +174 -0
  159. package/dist/src/providers/search-provider.js.map +1 -0
  160. package/dist/src/providers/types.d.ts +8 -0
  161. package/dist/src/routes/ai-stream.d.ts +18 -4
  162. package/dist/src/routes/ai-stream.js +10 -2
  163. package/dist/src/routes/ai-stream.js.map +1 -1
  164. package/dist/src/routes/chat-stream.d.ts +9 -1
  165. package/dist/src/routes/chat-stream.js +3 -1
  166. package/dist/src/routes/chat-stream.js.map +1 -1
  167. package/dist/src/routes/chat.d.ts +6 -0
  168. package/dist/src/routes/chat.js +2 -1
  169. package/dist/src/routes/chat.js.map +1 -1
  170. package/dist/src/routes/session-resolver.d.ts +15 -2
  171. package/dist/src/routes/session-resolver.js +22 -25
  172. package/dist/src/routes/session-resolver.js.map +1 -1
  173. package/dist/src/routes/session-resolver.test.js +117 -20
  174. package/dist/src/routes/session-resolver.test.js.map +1 -1
  175. package/dist/src/server.d.ts +35 -1
  176. package/dist/src/server.js +33 -0
  177. package/dist/src/server.js.map +1 -1
  178. package/dist/src/session/drizzle-session-store.d.ts +57 -0
  179. package/dist/src/session/drizzle-session-store.js +204 -0
  180. package/dist/src/session/drizzle-session-store.js.map +1 -0
  181. package/dist/src/session/manager.d.ts +6 -3
  182. package/dist/src/session/manager.js +46 -19
  183. package/dist/src/session/manager.js.map +1 -1
  184. package/dist/src/session/manager.test.js +12 -18
  185. package/dist/src/session/manager.test.js.map +1 -1
  186. package/dist/src/session/pglite-session-store.d.ts +23 -0
  187. package/dist/src/session/pglite-session-store.js +86 -0
  188. package/dist/src/session/pglite-session-store.js.map +1 -0
  189. package/dist/src/session/postgres-session-store.d.ts +44 -0
  190. package/dist/src/session/postgres-session-store.js +153 -0
  191. package/dist/src/session/postgres-session-store.js.map +1 -0
  192. package/dist/src/session/session-builder.d.ts +0 -5
  193. package/dist/src/session/session-builder.js +22 -6
  194. package/dist/src/session/session-builder.js.map +1 -1
  195. package/dist/src/session/session-builder.test.js +3 -8
  196. package/dist/src/session/session-builder.test.js.map +1 -1
  197. package/dist/src/session/session-store-selector.d.ts +49 -0
  198. package/dist/src/session/session-store-selector.js +60 -0
  199. package/dist/src/session/session-store-selector.js.map +1 -0
  200. package/dist/src/session/session-store-selector.test.d.ts +6 -0
  201. package/dist/src/session/session-store-selector.test.js +79 -0
  202. package/dist/src/session/session-store-selector.test.js.map +1 -0
  203. package/dist/src/session/store.d.ts +146 -32
  204. package/dist/src/session/store.js +126 -138
  205. package/dist/src/session/store.js.map +1 -1
  206. package/dist/src/session/store.test.js +385 -107
  207. package/dist/src/session/store.test.js.map +1 -1
  208. package/dist/src/session/tool-context-factory.d.ts +3 -7
  209. package/dist/src/session/tool-context-factory.js +1 -3
  210. package/dist/src/session/tool-context-factory.js.map +1 -1
  211. package/dist/src/session/tool-context-factory.test.js +1 -6
  212. package/dist/src/session/tool-context-factory.test.js.map +1 -1
  213. package/dist/src/session/types.d.ts +13 -10
  214. package/dist/src/stores/schema.d.ts +111 -34
  215. package/dist/src/stores/schema.js +21 -4
  216. package/dist/src/stores/schema.js.map +1 -1
  217. package/dist/src/tools/admin-file-tools.d.ts +29 -0
  218. package/dist/src/tools/admin-file-tools.js +527 -13
  219. package/dist/src/tools/admin-file-tools.js.map +1 -1
  220. package/dist/src/tools/admin-file-tools.test.js +380 -9
  221. package/dist/src/tools/admin-file-tools.test.js.map +1 -1
  222. package/dist/src/tools/custom-tool-adapter.js +0 -1
  223. package/dist/src/tools/custom-tool-adapter.js.map +1 -1
  224. package/dist/src/tools/custom-tool-adapter.test.js +0 -2
  225. package/dist/src/tools/custom-tool-adapter.test.js.map +1 -1
  226. package/dist/src/tools/dispatch-tool.d.ts +4 -4
  227. package/dist/src/tools/fetch-url-tool.d.ts +23 -0
  228. package/dist/src/tools/fetch-url-tool.js +333 -0
  229. package/dist/src/tools/fetch-url-tool.js.map +1 -0
  230. package/dist/src/tools/fetch-url-tool.test.d.ts +6 -0
  231. package/dist/src/tools/fetch-url-tool.test.js +227 -0
  232. package/dist/src/tools/fetch-url-tool.test.js.map +1 -0
  233. package/dist/src/tools/mcp-tool-adapter.test.js +0 -2
  234. package/dist/src/tools/mcp-tool-adapter.test.js.map +1 -1
  235. package/dist/src/tools/registry.test.js +0 -2
  236. package/dist/src/tools/registry.test.js.map +1 -1
  237. package/dist/src/tools/request-tool.test.js +0 -2
  238. package/dist/src/tools/request-tool.test.js.map +1 -1
  239. package/dist/src/tools/store-tools.test.js +0 -2
  240. package/dist/src/tools/store-tools.test.js.map +1 -1
  241. package/dist/src/tools/types.d.ts +20 -7
  242. package/dist/src/tools/web-search-tool.d.ts +31 -0
  243. package/dist/src/tools/web-search-tool.js +170 -0
  244. package/dist/src/tools/web-search-tool.js.map +1 -0
  245. package/dist/src/tools/web-search-tool.test.d.ts +6 -0
  246. package/dist/src/tools/web-search-tool.test.js +152 -0
  247. package/dist/src/tools/web-search-tool.test.js.map +1 -0
  248. package/dist/src/tools/web-tools-shared.d.ts +21 -0
  249. package/dist/src/tools/web-tools-shared.js +32 -0
  250. package/dist/src/tools/web-tools-shared.js.map +1 -0
  251. package/dist/src/types.d.ts +20 -4
  252. package/dist/src/types.js +13 -2
  253. package/dist/src/types.js.map +1 -1
  254. package/dist/src/types.test.js +0 -3
  255. package/dist/src/types.test.js.map +1 -1
  256. package/dist/tsconfig.tsbuildinfo +1 -1
  257. package/package.json +17 -3
  258. package/dist/src/agent/session-store.d.ts +0 -71
  259. package/dist/src/agent/session-store.js +0 -151
  260. package/dist/src/agent/session-store.js.map +0 -1
  261. package/dist/src/agent/user-context-fetcher.d.ts +0 -25
  262. package/dist/src/agent/user-context-fetcher.js +0 -79
  263. package/dist/src/agent/user-context-fetcher.js.map +0 -1
  264. package/dist/src/agent/user-context-fetcher.test.js +0 -121
  265. package/dist/src/agent/user-context-fetcher.test.js.map +0 -1
  266. package/dist/src/session/admin-file-tools.d.ts +0 -136
  267. package/dist/src/session/admin-file-tools.js +0 -240
  268. package/dist/src/session/admin-file-tools.js.map +0 -1
@@ -41,11 +41,7 @@ export interface AgentConfig {
41
41
  /** A running agent instance. */
42
42
  export interface Agent {
43
43
  /** Create a new chat session. */
44
- createSession(opts?: {
45
- tenantId?: string;
46
- userId?: string;
47
- userRoles?: string[];
48
- }): AgentSession;
44
+ createSession(): AgentSession;
49
45
  /** Resume an existing session by ID. Returns null if not found. */
50
46
  resumeSession(sessionId: string): Promise<AgentSession | null>;
51
47
  /** Get the agent's compiled system prompt. */
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ /**
7
+ * Shared channel bootstrap sequence.
8
+ *
9
+ * Used by local-server.ts, snapshot-server.ts, and available for
10
+ * hosting layers that call createServer(). Loads channel plugins,
11
+ * wires the session mapper, and creates the Express router.
12
+ */
13
+ import type { Router } from 'express';
14
+ import type { ChannelAdapter, ChannelSessionMapper } from '@amodalai/types';
15
+ import type { StandaloneSessionManager } from '../session/manager.js';
16
+ import type { SessionComponents } from '../session/session-builder.js';
17
+ import type { RuntimeEventBus } from '../events/event-bus.js';
18
+ import type { Logger } from '../logger.js';
19
+ import type { CreateChannelSession } from './channel-session-mapper.js';
20
+ /** A discovered channel from the bundle (matches AgentBundle['channels'][n]). */
21
+ interface BundleChannel {
22
+ channelType: string;
23
+ packageName: string;
24
+ packageDir: string;
25
+ config: Record<string, unknown>;
26
+ }
27
+ export interface BootstrapChannelsOptions {
28
+ /** Discovered channel plugins from the bundle. */
29
+ channels: BundleChannel[];
30
+ /** Repo path for local channel discovery + node_modules resolution. */
31
+ repoPath: string;
32
+ /** The `packages` array from amodal.json. */
33
+ packages?: string[];
34
+ /**
35
+ * Pre-wired session mapper. The caller chooses the implementation:
36
+ * - DrizzleChannelSessionMapper (local-server, hosted with DB)
37
+ * - InMemoryChannelSessionMapper (snapshot-server, testing)
38
+ */
39
+ sessionMapper: ChannelSessionMapper & {
40
+ setSessionFactory(f: CreateChannelSession): void;
41
+ };
42
+ sessionManager: StandaloneSessionManager;
43
+ /** Factory that builds session components for new channel sessions. */
44
+ buildSessionComponents: () => SessionComponents;
45
+ /** App ID for sessions created by channels (e.g. 'local'). */
46
+ appId?: string;
47
+ eventBus: RuntimeEventBus;
48
+ logger: Logger;
49
+ }
50
+ export interface BootstrapChannelsResult {
51
+ adapters: Map<string, ChannelAdapter>;
52
+ router: Router;
53
+ }
54
+ /**
55
+ * Load channel plugins, wire the session factory, and create the
56
+ * Express router. Returns null if loading fails (error is logged).
57
+ */
58
+ export declare function bootstrapChannels(opts: BootstrapChannelsOptions): Promise<BootstrapChannelsResult | null>;
59
+ export {};
@@ -0,0 +1,84 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { loadChannelPlugins } from './plugin-loader.js';
7
+ import { createChannelsRouter } from './routes.js';
8
+ import { MessageDedupCache } from './dedup-cache.js';
9
+ import { resolveEnvRef } from '../env-ref.js';
10
+ /**
11
+ * Load channel plugins, wire the session factory, and create the
12
+ * Express router. Returns null if loading fails (error is logged).
13
+ */
14
+ export async function bootstrapChannels(opts) {
15
+ const { channels, repoPath, sessionMapper, sessionManager, buildSessionComponents, eventBus, logger } = opts;
16
+ const appId = opts.appId ?? 'local';
17
+ if (channels.length === 0) {
18
+ return null;
19
+ }
20
+ try {
21
+ // Build and resolve env:VAR refs in channel config values
22
+ const channelsConfig = {};
23
+ for (const ch of channels) {
24
+ channelsConfig[ch.channelType] = resolveChannelConfig(ch.config);
25
+ }
26
+ // Load and validate plugins
27
+ const adapters = await loadChannelPlugins({ channelsConfig, repoPath, packages: opts.packages, logger });
28
+ logger.info('channels_loaded', { channels: [...adapters.keys()] });
29
+ // Wire session factory — creates chat sessions for channel users
30
+ sessionMapper.setSessionFactory((origin) => {
31
+ const components = buildSessionComponents();
32
+ const channelNote = `\n\n[Channel context: This user is messaging you via ${origin.channelType}. Keep responses concise and conversational. User: ${origin.channelUserDisplay ?? origin.channelUserId}.]`;
33
+ const session = sessionManager.create({
34
+ provider: components.provider,
35
+ toolRegistry: components.toolRegistry,
36
+ permissionChecker: components.permissionChecker,
37
+ systemPrompt: components.systemPrompt + channelNote,
38
+ toolContextFactory: components.toolContextFactory,
39
+ appId,
40
+ metadata: { channelOrigin: origin },
41
+ });
42
+ return { sessionId: session.id };
43
+ });
44
+ // Build the router
45
+ const router = createChannelsRouter({
46
+ adapters,
47
+ sessionMapper,
48
+ sessionManager,
49
+ dedupCache: new MessageDedupCache(),
50
+ eventBus,
51
+ logger,
52
+ });
53
+ return { adapters, router };
54
+ }
55
+ catch (err) {
56
+ logger.warn('channels_load_failed', {
57
+ error: err instanceof Error ? err.message : String(err),
58
+ hint: 'Server will start without messaging channels',
59
+ });
60
+ return null;
61
+ }
62
+ }
63
+ // ---------------------------------------------------------------------------
64
+ // Helpers
65
+ // ---------------------------------------------------------------------------
66
+ /**
67
+ * Resolve `env:VAR_NAME` references in a channel config block.
68
+ */
69
+ function resolveChannelConfig(config) {
70
+ const resolved = {};
71
+ for (const [k, v] of Object.entries(config)) {
72
+ if (typeof v === 'string') {
73
+ resolved[k] = resolveEnvRef(v) ?? v;
74
+ }
75
+ else if (Array.isArray(v)) {
76
+ resolved[k] = v.map((item) => typeof item === 'string' ? (resolveEnvRef(item) ?? item) : item);
77
+ }
78
+ else {
79
+ resolved[k] = v;
80
+ }
81
+ }
82
+ return resolved;
83
+ }
84
+ //# sourceMappingURL=bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../../../src/channels/bootstrap.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import type { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core';
7
+ import type { ChannelSessionMapper, ChannelSessionMapResult, ChannelOrigin } from '@amodalai/types';
8
+ import type { Logger } from '../logger.js';
9
+ type AnyPgDatabase = PgDatabase<PgQueryResultHKT, Record<string, unknown>>;
10
+ export interface ChannelSessionMapperOptions {
11
+ db: AnyPgDatabase;
12
+ logger: Logger;
13
+ eventBus?: {
14
+ emit(payload: {
15
+ type: string;
16
+ [key: string]: unknown;
17
+ }): unknown;
18
+ };
19
+ }
20
+ /**
21
+ * Create a new session for a channel user. The caller provides the
22
+ * session factory so the mapper stays decoupled from session creation
23
+ * details (provider selection, tool registration, etc.).
24
+ */
25
+ export type CreateChannelSession = (channelOrigin: ChannelOrigin) => {
26
+ sessionId: string;
27
+ };
28
+ export declare class DrizzleChannelSessionMapper implements ChannelSessionMapper {
29
+ private readonly db;
30
+ private readonly logger;
31
+ private readonly eventBus?;
32
+ private createSession;
33
+ constructor(opts: ChannelSessionMapperOptions);
34
+ /**
35
+ * Wire the session factory after construction. Called by the local-server
36
+ * wiring code once all session components are available.
37
+ */
38
+ setSessionFactory(factory: CreateChannelSession): void;
39
+ findOrCreateSession(channelType: string, channelUserId: string, displayName?: string): Promise<ChannelSessionMapResult>;
40
+ resetSession(channelType: string, channelUserId: string): Promise<void>;
41
+ }
42
+ export {};
@@ -0,0 +1,91 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ /**
7
+ * Drizzle-based channel session mapper.
8
+ *
9
+ * Maps (channelType, channelUserId) → sessionId using the
10
+ * `channel_sessions` table. Shares the same database connection pool
11
+ * as the session store to avoid opening a second connection.
12
+ */
13
+ import { eq, and, sql } from 'drizzle-orm';
14
+ import { channelSessions } from '../stores/schema.js';
15
+ export class DrizzleChannelSessionMapper {
16
+ db;
17
+ logger;
18
+ eventBus;
19
+ createSession = null;
20
+ constructor(opts) {
21
+ this.db = opts.db;
22
+ this.logger = opts.logger;
23
+ this.eventBus = opts.eventBus;
24
+ }
25
+ /**
26
+ * Wire the session factory after construction. Called by the local-server
27
+ * wiring code once all session components are available.
28
+ */
29
+ setSessionFactory(factory) {
30
+ this.createSession = factory;
31
+ }
32
+ async findOrCreateSession(channelType, channelUserId, displayName) {
33
+ // Try to find an existing mapping and touch last_active_at in one query
34
+ const rows = await this.db
35
+ .update(channelSessions)
36
+ .set({ lastActiveAt: sql `NOW()` })
37
+ .where(and(eq(channelSessions.channelType, channelType), eq(channelSessions.channelUserId, channelUserId)))
38
+ .returning({ sessionId: channelSessions.sessionId });
39
+ if (rows.length > 0) {
40
+ const { sessionId } = rows[0];
41
+ this.logger.debug('channel_session_found', { channelType, channelUserId, sessionId });
42
+ return { sessionId, isNew: false };
43
+ }
44
+ // Create new session
45
+ if (!this.createSession) {
46
+ throw new Error('Channel session mapper: session factory not set. Call setSessionFactory() first.');
47
+ }
48
+ const channelOrigin = {
49
+ channelType,
50
+ channelUserId,
51
+ channelUserDisplay: displayName,
52
+ };
53
+ const { sessionId } = this.createSession(channelOrigin);
54
+ // Atomic upsert — if a concurrent request inserted first, return the existing row
55
+ const inserted = await this.db
56
+ .insert(channelSessions)
57
+ .values({
58
+ channelType,
59
+ channelUserId,
60
+ sessionId,
61
+ metadata: { channelUserDisplay: displayName },
62
+ })
63
+ .onConflictDoUpdate({
64
+ target: [channelSessions.channelType, channelSessions.channelUserId],
65
+ set: { lastActiveAt: sql `NOW()` },
66
+ })
67
+ .returning({ sessionId: channelSessions.sessionId });
68
+ const finalSessionId = inserted[0].sessionId;
69
+ const isNew = finalSessionId === sessionId;
70
+ if (isNew) {
71
+ this.logger.info('channel_session_created', { channelType, channelUserId, sessionId: finalSessionId });
72
+ this.eventBus?.emit({
73
+ type: 'channel_session_created',
74
+ channelType,
75
+ channelUserId,
76
+ sessionId: finalSessionId,
77
+ });
78
+ }
79
+ else {
80
+ this.logger.debug('channel_session_found', { channelType, channelUserId, sessionId: finalSessionId });
81
+ }
82
+ return { sessionId: finalSessionId, isNew };
83
+ }
84
+ async resetSession(channelType, channelUserId) {
85
+ await this.db
86
+ .delete(channelSessions)
87
+ .where(and(eq(channelSessions.channelType, channelType), eq(channelSessions.channelUserId, channelUserId)));
88
+ this.logger.info('channel_session_reset', { channelType, channelUserId });
89
+ }
90
+ }
91
+ //# sourceMappingURL=channel-session-mapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel-session-mapper.js","sourceRoot":"","sources":["../../../src/channels/channel-session-mapper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ export declare class MessageDedupCache {
7
+ private readonly seen;
8
+ private readonly ttlMs;
9
+ constructor(ttlMs?: number);
10
+ /**
11
+ * Returns `true` if this message was already seen (duplicate).
12
+ * Returns `false` and records the message if it's new.
13
+ */
14
+ isDuplicate(channelType: string, messageId: string): boolean;
15
+ get size(): number;
16
+ private evict;
17
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ /**
7
+ * In-memory dedup cache for channel webhook retries.
8
+ *
9
+ * Messaging platforms may resend webhook payloads when the server
10
+ * doesn't respond with 200 quickly enough. This cache prevents the same
11
+ * message from being processed twice.
12
+ *
13
+ * Keyed by `${channelType}:${messageId}`, with a configurable TTL.
14
+ * Lazy eviction keeps the fast path allocation-free.
15
+ */
16
+ const DEFAULT_TTL_MS = 60_000;
17
+ const EVICTION_THRESHOLD = 1000;
18
+ export class MessageDedupCache {
19
+ seen = new Map();
20
+ ttlMs;
21
+ constructor(ttlMs = DEFAULT_TTL_MS) {
22
+ this.ttlMs = ttlMs;
23
+ }
24
+ /**
25
+ * Returns `true` if this message was already seen (duplicate).
26
+ * Returns `false` and records the message if it's new.
27
+ */
28
+ isDuplicate(channelType, messageId) {
29
+ const key = `${channelType}:${messageId}`;
30
+ const now = Date.now();
31
+ if (this.seen.size > EVICTION_THRESHOLD) {
32
+ this.evict(now);
33
+ }
34
+ if (this.seen.has(key)) {
35
+ return true;
36
+ }
37
+ this.seen.set(key, now);
38
+ return false;
39
+ }
40
+ get size() {
41
+ return this.seen.size;
42
+ }
43
+ evict(now) {
44
+ for (const [k, ts] of this.seen) {
45
+ if (now - ts > this.ttlMs) {
46
+ this.seen.delete(k);
47
+ }
48
+ }
49
+ }
50
+ }
51
+ //# sourceMappingURL=dedup-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedup-cache.js","sourceRoot":"","sources":["../../../src/channels/dedup-cache.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;GASG;AAEH,MAAM,cAAc,GAAG,MAAM,CAAC;AAC9B,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEhC,MAAM,OAAO,iBAAiB;IACX,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACjC,KAAK,CAAS;IAE/B,YAAY,KAAK,GAAG,cAAc;QAChC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED;;;OAGG;IACH,WAAW,CAAC,WAAmB,EAAE,SAAiB;QAChD,MAAM,GAAG,GAAG,GAAG,WAAW,IAAI,SAAS,EAAE,CAAC;QAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEvB,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,kBAAkB,EAAE,CAAC;YACxC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACxB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IACxB,CAAC;IAEO,KAAK,CAAC,GAAW;QACvB,KAAK,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAChC,IAAI,GAAG,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC1B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ export {};
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { describe, it, expect, beforeEach } from 'vitest';
7
+ import { MessageDedupCache } from './dedup-cache.js';
8
+ describe('MessageDedupCache', () => {
9
+ let cache;
10
+ beforeEach(() => {
11
+ cache = new MessageDedupCache();
12
+ });
13
+ it('returns false for new messages', () => {
14
+ expect(cache.isDuplicate('telegram', '123')).toBe(false);
15
+ });
16
+ it('returns true for duplicate messages', () => {
17
+ cache.isDuplicate('telegram', '123');
18
+ expect(cache.isDuplicate('telegram', '123')).toBe(true);
19
+ });
20
+ it('treats different message IDs as distinct', () => {
21
+ cache.isDuplicate('telegram', '123');
22
+ expect(cache.isDuplicate('telegram', '456')).toBe(false);
23
+ });
24
+ it('treats different channel types as distinct', () => {
25
+ cache.isDuplicate('telegram', '123');
26
+ expect(cache.isDuplicate('slack', '123')).toBe(false);
27
+ });
28
+ it('tracks cache size', () => {
29
+ cache.isDuplicate('telegram', '1');
30
+ cache.isDuplicate('telegram', '2');
31
+ expect(cache.size).toBe(2);
32
+ });
33
+ it('evicts stale entries when threshold is exceeded', () => {
34
+ // Use a very short TTL for testing
35
+ const shortCache = new MessageDedupCache(1); // 1ms TTL
36
+ // Fill past eviction threshold
37
+ for (let i = 0; i < 1001; i++) {
38
+ shortCache.isDuplicate('telegram', String(i));
39
+ }
40
+ // Wait a tick for TTL to expire
41
+ const start = Date.now();
42
+ while (Date.now() - start < 5) {
43
+ // busy wait
44
+ }
45
+ // Next isDuplicate should trigger eviction
46
+ shortCache.isDuplicate('telegram', 'trigger');
47
+ // Stale entries should be gone (only 'trigger' remains)
48
+ expect(shortCache.size).toBeLessThanOrEqual(2);
49
+ });
50
+ });
51
+ //# sourceMappingURL=dedup-cache.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedup-cache.test.js","sourceRoot":"","sources":["../../../src/channels/dedup-cache.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAC,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAC,MAAM,QAAQ,CAAC;AACxD,OAAO,EAAC,iBAAiB,EAAC,MAAM,kBAAkB,CAAC;AAEnD,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,IAAI,KAAwB,CAAC;IAE7B,UAAU,CAAC,GAAG,EAAE;QACd,KAAK,GAAG,IAAI,iBAAiB,EAAE,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,KAAK,CAAC,WAAW,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,KAAK,CAAC,WAAW,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,KAAK,CAAC,WAAW,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QACrC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE;QAC3B,KAAK,CAAC,WAAW,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QACnC,KAAK,CAAC,WAAW,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QACnC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,mCAAmC;QACnC,MAAM,UAAU,GAAG,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU;QAEvD,+BAA+B;QAC/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;YAC9B,UAAU,CAAC,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAChD,CAAC;QAED,gCAAgC;QAChC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,CAAC,EAAE,CAAC;YAC9B,YAAY;QACd,CAAC;QAED,2CAA2C;QAC3C,UAAU,CAAC,WAAW,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QAC9C,wDAAwD;QACxD,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { AmodalError } from '../errors.js';
7
+ /**
8
+ * Error loading a channel plugin package (missing, invalid export shape).
9
+ */
10
+ export declare class ChannelPluginError extends AmodalError {
11
+ readonly channelType: string;
12
+ constructor(message: string, options: {
13
+ channelType: string;
14
+ cause?: unknown;
15
+ context?: Record<string, unknown>;
16
+ });
17
+ }
18
+ /**
19
+ * Error validating a channel's config block against its plugin schema.
20
+ */
21
+ export declare class ChannelConfigError extends AmodalError {
22
+ readonly channelType: string;
23
+ constructor(message: string, options: {
24
+ channelType: string;
25
+ cause?: unknown;
26
+ context?: Record<string, unknown>;
27
+ });
28
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { AmodalError } from '../errors.js';
7
+ // ---------------------------------------------------------------------------
8
+ // Channel plugin errors
9
+ // ---------------------------------------------------------------------------
10
+ /**
11
+ * Error loading a channel plugin package (missing, invalid export shape).
12
+ */
13
+ export class ChannelPluginError extends AmodalError {
14
+ channelType;
15
+ constructor(message, options) {
16
+ super('CHANNEL_PLUGIN_ERROR', message, {
17
+ channelType: options.channelType,
18
+ ...options.context,
19
+ }, options.cause);
20
+ this.name = 'ChannelPluginError';
21
+ this.channelType = options.channelType;
22
+ }
23
+ }
24
+ /**
25
+ * Error validating a channel's config block against its plugin schema.
26
+ */
27
+ export class ChannelConfigError extends AmodalError {
28
+ channelType;
29
+ constructor(message, options) {
30
+ super('CHANNEL_CONFIG_ERROR', message, {
31
+ channelType: options.channelType,
32
+ ...options.context,
33
+ }, options.cause);
34
+ this.name = 'ChannelConfigError';
35
+ this.channelType = options.channelType;
36
+ }
37
+ }
38
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/channels/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAC,WAAW,EAAC,MAAM,cAAc,CAAC;AAEzC,8EAA8E;AAC9E,wBAAwB;AACxB,8EAA8E;AAE9E;;GAEG;AACH,MAAM,OAAO,kBAAmB,SAAQ,WAAW;IACxC,WAAW,CAAS;IAE7B,YACE,OAAe,EACf,OAIC;QAED,KAAK,CAAC,sBAAsB,EAAE,OAAO,EAAE;YACrC,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,GAAG,OAAO,CAAC,OAAO;SACnB,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QAClB,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IACzC,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,kBAAmB,SAAQ,WAAW;IACxC,WAAW,CAAS;IAE7B,YACE,OAAe,EACf,OAIC;QAED,KAAK,CAAC,sBAAsB,EAAE,OAAO,EAAE;YACrC,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,GAAG,OAAO,CAAC,OAAO;SACnB,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QAClB,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IACzC,CAAC;CACF"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ /**
7
+ * In-memory channel session mapper.
8
+ *
9
+ * Simple Map-backed implementation for environments without a database
10
+ * (snapshot preview server, testing). Sessions are not persisted across
11
+ * restarts — that's fine for preview and test use cases.
12
+ */
13
+ import type { ChannelSessionMapper, ChannelSessionMapResult } from '@amodalai/types';
14
+ import type { CreateChannelSession } from './channel-session-mapper.js';
15
+ import type { Logger } from '../logger.js';
16
+ export interface InMemoryChannelSessionMapperOptions {
17
+ logger: Logger;
18
+ eventBus?: {
19
+ emit(payload: {
20
+ type: string;
21
+ [key: string]: unknown;
22
+ }): unknown;
23
+ };
24
+ }
25
+ export declare class InMemoryChannelSessionMapper implements ChannelSessionMapper {
26
+ private readonly sessions;
27
+ private readonly logger;
28
+ private readonly eventBus?;
29
+ private createSession;
30
+ constructor(opts: InMemoryChannelSessionMapperOptions);
31
+ setSessionFactory(factory: CreateChannelSession): void;
32
+ findOrCreateSession(channelType: string, channelUserId: string, displayName?: string): Promise<ChannelSessionMapResult>;
33
+ resetSession(channelType: string, channelUserId: string): Promise<void>;
34
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ export class InMemoryChannelSessionMapper {
7
+ sessions = new Map();
8
+ logger;
9
+ eventBus;
10
+ createSession = null;
11
+ constructor(opts) {
12
+ this.logger = opts.logger;
13
+ this.eventBus = opts.eventBus;
14
+ }
15
+ setSessionFactory(factory) {
16
+ this.createSession = factory;
17
+ }
18
+ async findOrCreateSession(channelType, channelUserId, displayName) {
19
+ const key = `${channelType}:${channelUserId}`;
20
+ const existing = this.sessions.get(key);
21
+ if (existing) {
22
+ this.logger.debug('channel_session_found', { channelType, channelUserId, sessionId: existing });
23
+ return { sessionId: existing, isNew: false };
24
+ }
25
+ if (!this.createSession) {
26
+ throw new Error('Channel session mapper: session factory not set. Call setSessionFactory() first.');
27
+ }
28
+ const channelOrigin = {
29
+ channelType,
30
+ channelUserId,
31
+ channelUserDisplay: displayName,
32
+ };
33
+ const { sessionId } = this.createSession(channelOrigin);
34
+ this.sessions.set(key, sessionId);
35
+ this.logger.info('channel_session_created', { channelType, channelUserId, sessionId });
36
+ this.eventBus?.emit({
37
+ type: 'channel_session_created',
38
+ channelType,
39
+ channelUserId,
40
+ sessionId,
41
+ });
42
+ return { sessionId, isNew: true };
43
+ }
44
+ async resetSession(channelType, channelUserId) {
45
+ const key = `${channelType}:${channelUserId}`;
46
+ this.sessions.delete(key);
47
+ this.logger.info('channel_session_reset', { channelType, channelUserId });
48
+ }
49
+ }
50
+ //# sourceMappingURL=in-memory-session-mapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-memory-session-mapper.js","sourceRoot":"","sources":["../../../src/channels/in-memory-session-mapper.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAqBH,MAAM,OAAO,4BAA4B;IACtB,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IACrC,MAAM,CAAS;IACf,QAAQ,CAAmD;IACpE,aAAa,GAAgC,IAAI,CAAC;IAE1D,YAAY,IAAyC;QACnD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAChC,CAAC;IAED,iBAAiB,CAAC,OAA6B;QAC7C,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,mBAAmB,CACvB,WAAmB,EACnB,aAAqB,EACrB,WAAoB;QAEpB,MAAM,GAAG,GAAG,GAAG,WAAW,IAAI,aAAa,EAAE,CAAC;QAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAExC,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,uBAAuB,EAAE,EAAC,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,EAAC,CAAC,CAAC;YAC9F,OAAO,EAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAC,CAAC;QAC7C,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,kFAAkF,CAAC,CAAC;QACtG,CAAC;QAED,MAAM,aAAa,GAAkB;YACnC,WAAW;YACX,aAAa;YACb,kBAAkB,EAAE,WAAW;SAChC,CAAC;QACF,MAAM,EAAC,SAAS,EAAC,GAAG,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC;QACtD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAElC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,EAAC,WAAW,EAAE,aAAa,EAAE,SAAS,EAAC,CAAC,CAAC;QACrF,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC;YAClB,IAAI,EAAE,yBAAyB;YAC/B,WAAW;YACX,aAAa;YACb,SAAS;SACV,CAAC,CAAC;QAEH,OAAO,EAAC,SAAS,EAAE,KAAK,EAAE,IAAI,EAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,YAAY,CAChB,WAAmB,EACnB,aAAqB;QAErB,MAAM,GAAG,GAAG,GAAG,WAAW,IAAI,aAAa,EAAE,CAAC;QAC9C,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE,EAAC,WAAW,EAAE,aAAa,EAAC,CAAC,CAAC;IAC1E,CAAC;CACF"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import type { ChannelAdapter } from '@amodalai/types';
7
+ import type { Logger } from '../logger.js';
8
+ export interface LoadChannelPluginsOptions {
9
+ /** The `channels` block from amodal.json (env refs already resolved). */
10
+ channelsConfig: Record<string, unknown>;
11
+ /** Absolute path to the repo root (for local channel discovery + node_modules). */
12
+ repoPath: string;
13
+ /** The `packages` array from amodal.json — used to find channel packages. */
14
+ packages?: string[];
15
+ logger: Logger;
16
+ }
17
+ /**
18
+ * Load and initialize channel adapters for all configured channels.
19
+ */
20
+ export declare function loadChannelPlugins(opts: LoadChannelPluginsOptions): Promise<Map<string, ChannelAdapter>>;