@amodalai/runtime 0.2.1 → 0.2.3

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 (157) hide show
  1. package/dist/src/agent/local-server.js +70 -3
  2. package/dist/src/agent/local-server.js.map +1 -1
  3. package/dist/src/agent/local-server.test.js +0 -3
  4. package/dist/src/agent/local-server.test.js.map +1 -1
  5. package/dist/src/agent/loop-types.d.ts +0 -5
  6. package/dist/src/agent/loop.test.js +1 -2
  7. package/dist/src/agent/loop.test.js.map +1 -1
  8. package/dist/src/agent/page-builder.js +20 -17
  9. package/dist/src/agent/page-builder.js.map +1 -1
  10. package/dist/src/agent/proactive/proactive-runner.test.js +0 -1
  11. package/dist/src/agent/proactive/proactive-runner.test.js.map +1 -1
  12. package/dist/src/agent/routes/admin-chat-abort.test.js +1 -0
  13. package/dist/src/agent/routes/admin-chat-abort.test.js.map +1 -1
  14. package/dist/src/agent/routes/admin-chat.js +0 -1
  15. package/dist/src/agent/routes/admin-chat.js.map +1 -1
  16. package/dist/src/agent/routes/files.js +46 -52
  17. package/dist/src/agent/routes/files.js.map +1 -1
  18. package/dist/src/agent/routes/inspect.js +4 -6
  19. package/dist/src/agent/routes/inspect.js.map +1 -1
  20. package/dist/src/agent/routes/task.test.js +0 -1
  21. package/dist/src/agent/routes/task.test.js.map +1 -1
  22. package/dist/src/agent/snapshot-server.js +37 -1
  23. package/dist/src/agent/snapshot-server.js.map +1 -1
  24. package/dist/src/agent/states/dispatching.js +0 -1
  25. package/dist/src/agent/states/dispatching.js.map +1 -1
  26. package/dist/src/agent/states/streaming.js +7 -27
  27. package/dist/src/agent/states/streaming.js.map +1 -1
  28. package/dist/src/agent/states/thinking.js +28 -1
  29. package/dist/src/agent/states/thinking.js.map +1 -1
  30. package/dist/src/agent/tool-executor-local.test.js +0 -1
  31. package/dist/src/agent/tool-executor-local.test.js.map +1 -1
  32. package/dist/src/agent/tool-harness-template.js +0 -1
  33. package/dist/src/agent/tool-harness-template.js.map +1 -1
  34. package/dist/src/api/create-agent.js +1 -2
  35. package/dist/src/api/create-agent.js.map +1 -1
  36. package/dist/src/api/types.d.ts +1 -3
  37. package/dist/src/channels/bootstrap.d.ts +59 -0
  38. package/dist/src/channels/bootstrap.js +89 -0
  39. package/dist/src/channels/bootstrap.js.map +1 -0
  40. package/dist/src/channels/channel-session-mapper.d.ts +42 -0
  41. package/dist/src/channels/channel-session-mapper.js +91 -0
  42. package/dist/src/channels/channel-session-mapper.js.map +1 -0
  43. package/dist/src/channels/dedup-cache.d.ts +17 -0
  44. package/dist/src/channels/dedup-cache.js +51 -0
  45. package/dist/src/channels/dedup-cache.js.map +1 -0
  46. package/dist/src/{agent/user-context-fetcher.test.d.ts → channels/dedup-cache.test.d.ts} +1 -1
  47. package/dist/src/channels/dedup-cache.test.js +51 -0
  48. package/dist/src/channels/dedup-cache.test.js.map +1 -0
  49. package/dist/src/channels/errors.d.ts +28 -0
  50. package/dist/src/channels/errors.js +38 -0
  51. package/dist/src/channels/errors.js.map +1 -0
  52. package/dist/src/channels/in-memory-session-mapper.d.ts +34 -0
  53. package/dist/src/channels/in-memory-session-mapper.js +50 -0
  54. package/dist/src/channels/in-memory-session-mapper.js.map +1 -0
  55. package/dist/src/channels/plugin-loader.d.ts +20 -0
  56. package/dist/src/channels/plugin-loader.js +136 -0
  57. package/dist/src/channels/plugin-loader.js.map +1 -0
  58. package/dist/src/channels/plugin-loader.test.d.ts +6 -0
  59. package/dist/src/channels/plugin-loader.test.js +113 -0
  60. package/dist/src/channels/plugin-loader.test.js.map +1 -0
  61. package/dist/src/channels/routes.d.ts +29 -0
  62. package/dist/src/channels/routes.js +165 -0
  63. package/dist/src/channels/routes.js.map +1 -0
  64. package/dist/src/config.d.ts +0 -2
  65. package/dist/src/config.js +0 -1
  66. package/dist/src/config.js.map +1 -1
  67. package/dist/src/config.test.js +0 -2
  68. package/dist/src/config.test.js.map +1 -1
  69. package/dist/src/context/compiler.js +11 -34
  70. package/dist/src/context/compiler.js.map +1 -1
  71. package/dist/src/context/compiler.test.js +7 -60
  72. package/dist/src/context/compiler.test.js.map +1 -1
  73. package/dist/src/context/types.d.ts +0 -4
  74. package/dist/src/index.d.ts +13 -0
  75. package/dist/src/index.js +10 -0
  76. package/dist/src/index.js.map +1 -1
  77. package/dist/src/providers/create-provider.js +1 -0
  78. package/dist/src/providers/create-provider.js.map +1 -1
  79. package/dist/src/providers/failover.js +8 -0
  80. package/dist/src/providers/failover.js.map +1 -1
  81. package/dist/src/providers/search-provider.js +9 -2
  82. package/dist/src/providers/search-provider.js.map +1 -1
  83. package/dist/src/providers/types.d.ts +10 -3
  84. package/dist/src/routes/ai-stream.d.ts +3 -4
  85. package/dist/src/routes/ai-stream.js +1 -2
  86. package/dist/src/routes/ai-stream.js.map +1 -1
  87. package/dist/src/routes/chat-stream.d.ts +3 -1
  88. package/dist/src/routes/chat-stream.js +1 -1
  89. package/dist/src/routes/chat-stream.js.map +1 -1
  90. package/dist/src/routes/chat.js +0 -1
  91. package/dist/src/routes/chat.js.map +1 -1
  92. package/dist/src/routes/session-resolver.d.ts +10 -2
  93. package/dist/src/routes/session-resolver.js +21 -10
  94. package/dist/src/routes/session-resolver.js.map +1 -1
  95. package/dist/src/routes/session-resolver.test.js +110 -14
  96. package/dist/src/routes/session-resolver.test.js.map +1 -1
  97. package/dist/src/server.d.ts +29 -1
  98. package/dist/src/server.js +27 -0
  99. package/dist/src/server.js.map +1 -1
  100. package/dist/src/session/drizzle-session-store.d.ts +2 -1
  101. package/dist/src/session/drizzle-session-store.js +1 -0
  102. package/dist/src/session/drizzle-session-store.js.map +1 -1
  103. package/dist/src/session/manager.js +0 -3
  104. package/dist/src/session/manager.js.map +1 -1
  105. package/dist/src/session/manager.test.js +1 -0
  106. package/dist/src/session/manager.test.js.map +1 -1
  107. package/dist/src/session/pglite-session-store.js +16 -0
  108. package/dist/src/session/pglite-session-store.js.map +1 -1
  109. package/dist/src/session/postgres-session-store.js +15 -0
  110. package/dist/src/session/postgres-session-store.js.map +1 -1
  111. package/dist/src/session/session-builder.d.ts +0 -3
  112. package/dist/src/session/session-builder.js +1 -5
  113. package/dist/src/session/session-builder.js.map +1 -1
  114. package/dist/src/session/session-builder.test.js +3 -6
  115. package/dist/src/session/session-builder.test.js.map +1 -1
  116. package/dist/src/session/tool-context-factory.d.ts +0 -5
  117. package/dist/src/session/tool-context-factory.js +0 -1
  118. package/dist/src/session/tool-context-factory.js.map +1 -1
  119. package/dist/src/session/tool-context-factory.test.js +1 -3
  120. package/dist/src/session/tool-context-factory.test.js.map +1 -1
  121. package/dist/src/session/types.d.ts +0 -4
  122. package/dist/src/stores/schema.d.ts +111 -0
  123. package/dist/src/stores/schema.js +15 -0
  124. package/dist/src/stores/schema.js.map +1 -1
  125. package/dist/src/tools/admin-file-tools.js +2 -2
  126. package/dist/src/tools/admin-file-tools.js.map +1 -1
  127. package/dist/src/tools/admin-file-tools.test.js +9 -7
  128. package/dist/src/tools/admin-file-tools.test.js.map +1 -1
  129. package/dist/src/tools/custom-tool-adapter.js +0 -1
  130. package/dist/src/tools/custom-tool-adapter.js.map +1 -1
  131. package/dist/src/tools/custom-tool-adapter.test.js +0 -1
  132. package/dist/src/tools/custom-tool-adapter.test.js.map +1 -1
  133. package/dist/src/tools/fetch-url-tool.test.js +0 -1
  134. package/dist/src/tools/fetch-url-tool.test.js.map +1 -1
  135. package/dist/src/tools/mcp-tool-adapter.test.js +0 -1
  136. package/dist/src/tools/mcp-tool-adapter.test.js.map +1 -1
  137. package/dist/src/tools/registry.test.js +0 -1
  138. package/dist/src/tools/registry.test.js.map +1 -1
  139. package/dist/src/tools/request-tool.test.js +0 -1
  140. package/dist/src/tools/request-tool.test.js.map +1 -1
  141. package/dist/src/tools/store-tools.test.js +0 -1
  142. package/dist/src/tools/store-tools.test.js.map +1 -1
  143. package/dist/src/tools/types.d.ts +0 -5
  144. package/dist/src/tools/web-search-tool.test.js +0 -1
  145. package/dist/src/tools/web-search-tool.test.js.map +1 -1
  146. package/dist/src/types.d.ts +0 -4
  147. package/dist/src/types.js +0 -2
  148. package/dist/src/types.js.map +1 -1
  149. package/dist/src/types.test.js +0 -3
  150. package/dist/src/types.test.js.map +1 -1
  151. package/dist/tsconfig.tsbuildinfo +1 -1
  152. package/package.json +3 -3
  153. package/dist/src/agent/user-context-fetcher.d.ts +0 -25
  154. package/dist/src/agent/user-context-fetcher.js +0 -79
  155. package/dist/src/agent/user-context-fetcher.js.map +0 -1
  156. package/dist/src/agent/user-context-fetcher.test.js +0 -121
  157. package/dist/src/agent/user-context-fetcher.test.js.map +0 -1
@@ -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>>;
@@ -0,0 +1,136 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ /**
7
+ * Dynamic loader for channel plugins.
8
+ *
9
+ * Loading order (first match wins):
10
+ * 1. Local repo: `channels/{type}/index.ts` — compiled with esbuild
11
+ * 2. Installed npm package: `@amodalai/channel-{type}`
12
+ *
13
+ * Both sources must export a default `ChannelPlugin`. Local channels
14
+ * let users iterate on custom adapters (e.g. in-app chat widget)
15
+ * without publishing a package.
16
+ */
17
+ import { existsSync, mkdirSync } from 'node:fs';
18
+ import path from 'node:path';
19
+ import { pathToFileURL } from 'node:url';
20
+ import { ChannelPluginError, ChannelConfigError } from './errors.js';
21
+ /**
22
+ * Load and initialize channel adapters for all configured channels.
23
+ */
24
+ export async function loadChannelPlugins(opts) {
25
+ const { channelsConfig, repoPath, logger } = opts;
26
+ const adapters = new Map();
27
+ for (const [channelType, rawConfig] of Object.entries(channelsConfig)) {
28
+ const mod = await importChannelModule(channelType, repoPath, opts.packages ?? [], logger);
29
+ // Validate the default export satisfies ChannelPlugin shape
30
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- validating shape below
31
+ const plugin = mod.default;
32
+ if (!plugin ||
33
+ typeof plugin.channelType !== 'string' ||
34
+ !plugin.configSchema ||
35
+ typeof plugin.createAdapter !== 'function') {
36
+ throw new ChannelPluginError(`Channel "${channelType}" does not export a valid ChannelPlugin as its default export`, { channelType });
37
+ }
38
+ // Validate config against the plugin's schema
39
+ let validatedConfig;
40
+ try {
41
+ validatedConfig = plugin.configSchema.parse(rawConfig);
42
+ }
43
+ catch (cause) {
44
+ throw new ChannelConfigError(`Invalid config for channel "${channelType}"`, { channelType, cause });
45
+ }
46
+ // Create the adapter
47
+ const adapter = plugin.createAdapter(validatedConfig);
48
+ adapters.set(channelType, adapter);
49
+ }
50
+ return adapters;
51
+ }
52
+ // ---------------------------------------------------------------------------
53
+ // Module resolution: local repo first, then npm
54
+ // ---------------------------------------------------------------------------
55
+ const LOCAL_ENTRY_FILES = ['index.ts', 'index.js', 'index.mjs'];
56
+ /**
57
+ * Find a channel package from the declared packages list.
58
+ * Matches packages ending with `channel-{type}` (any scope).
59
+ * E.g. channelType "foo" matches "@amodalai/channel-foo" or "@myorg/channel-foo".
60
+ */
61
+ function findChannelPackage(channelType, packages) {
62
+ const suffix = `channel-${channelType}`;
63
+ return packages.find((pkg) => {
64
+ const shortName = pkg.split('/').pop() ?? pkg;
65
+ return shortName === suffix;
66
+ });
67
+ }
68
+ async function importChannelModule(channelType, repoPath, packages, logger) {
69
+ // 1. Check local repo: channels/{type}/index.ts
70
+ const localDir = path.join(repoPath, 'channels', channelType);
71
+ const localEntry = LOCAL_ENTRY_FILES
72
+ .map((f) => path.join(localDir, f))
73
+ .find((p) => existsSync(p));
74
+ if (localEntry) {
75
+ return importLocalChannel(channelType, localEntry, logger);
76
+ }
77
+ // 2. Find matching package from the declared packages array
78
+ const packageName = findChannelPackage(channelType, packages);
79
+ if (!packageName) {
80
+ throw new ChannelPluginError(`Channel "${channelType}" not found. Either create channels/${channelType}/index.ts in your repo, or add a channel-${channelType} package to the packages array in amodal.json.`, { channelType });
81
+ }
82
+ // 3. Import from node_modules using the resolved path
83
+ const packageDir = path.join(repoPath, 'node_modules', ...packageName.split('/'));
84
+ const pkgJsonPath = path.join(packageDir, 'package.json');
85
+ if (!existsSync(pkgJsonPath)) {
86
+ throw new ChannelPluginError(`Channel package "${packageName}" declared in amodal.json but not installed. Run: npm install`, { channelType });
87
+ }
88
+ try {
89
+ const { readFileSync } = await import('node:fs');
90
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- parsing external JSON
91
+ const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
92
+ const mainField = String(pkgJson['main'] ?? 'dist/index.js');
93
+ const entryPath = path.resolve(packageDir, mainField);
94
+ if (!entryPath.startsWith(path.resolve(packageDir))) {
95
+ throw new ChannelPluginError(`Channel package "${packageName}" has invalid main field that escapes package directory`, { channelType });
96
+ }
97
+ const moduleUrl = pathToFileURL(entryPath).href;
98
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- dynamic import returns unknown module shape
99
+ const mod = await import(moduleUrl);
100
+ logger.info('channel_plugin_loaded', { channelType, source: 'package', package: packageName });
101
+ return mod;
102
+ }
103
+ catch (cause) {
104
+ if (cause instanceof ChannelPluginError)
105
+ throw cause;
106
+ throw new ChannelPluginError(`Failed to load channel plugin "${packageName}"`, { channelType, cause });
107
+ }
108
+ }
109
+ async function importLocalChannel(channelType, entryPath, logger) {
110
+ let importPath = entryPath;
111
+ // Compile .ts to .mjs with esbuild (same pattern as LocalToolExecutor)
112
+ if (entryPath.endsWith('.ts')) {
113
+ const { build } = await import('esbuild');
114
+ const outDir = path.join(path.dirname(entryPath), '.build');
115
+ const outFile = path.join(outDir, `${channelType}.mjs`);
116
+ mkdirSync(outDir, { recursive: true });
117
+ await build({
118
+ entryPoints: [entryPath],
119
+ outfile: outFile,
120
+ bundle: true,
121
+ format: 'esm',
122
+ platform: 'node',
123
+ logLevel: 'warning',
124
+ // Mark @amodalai/* as external so it resolves from the runtime's
125
+ // node_modules, not bundled into the output.
126
+ external: ['@amodalai/*'],
127
+ });
128
+ importPath = outFile;
129
+ }
130
+ const moduleUrl = pathToFileURL(importPath).href;
131
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- dynamic import returns unknown module shape
132
+ const mod = await import(moduleUrl);
133
+ logger.info('channel_plugin_loaded', { channelType, source: 'local', path: entryPath });
134
+ return mod;
135
+ }
136
+ //# sourceMappingURL=plugin-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-loader.js","sourceRoot":"","sources":["../../../src/channels/plugin-loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ export {};
@@ -0,0 +1,113 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
7
+ import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
8
+ import path from 'node:path';
9
+ import { loadChannelPlugins } from './plugin-loader.js';
10
+ import { ChannelPluginError, ChannelConfigError } from './errors.js';
11
+ const mockLogger = {
12
+ info: vi.fn(),
13
+ warn: vi.fn(),
14
+ error: vi.fn(),
15
+ debug: vi.fn(),
16
+ };
17
+ /** Helper — no local channels dir exists at this fake path. */
18
+ const NO_LOCAL = '/tmp/nonexistent-repo-path';
19
+ /**
20
+ * Create a fake npm channel package in a temp directory so that
21
+ * the file-based resolution in plugin-loader can find it.
22
+ */
23
+ function createFakePackage(repoPath, packageName, pluginModule) {
24
+ const pkgDir = path.join(repoPath, 'node_modules', ...packageName.split('/'));
25
+ const distDir = path.join(pkgDir, 'dist');
26
+ mkdirSync(distDir, { recursive: true });
27
+ writeFileSync(path.join(pkgDir, 'package.json'), JSON.stringify({ name: packageName, main: 'dist/index.mjs' }));
28
+ writeFileSync(path.join(distDir, 'index.mjs'), pluginModule);
29
+ }
30
+ const TEMP_REPO = '/tmp/plugin-loader-test-repo';
31
+ beforeEach(() => {
32
+ mkdirSync(TEMP_REPO, { recursive: true });
33
+ vi.clearAllMocks();
34
+ });
35
+ afterEach(() => {
36
+ rmSync(TEMP_REPO, { recursive: true, force: true });
37
+ });
38
+ describe('loadChannelPlugins', () => {
39
+ it('throws ChannelPluginError for missing package (no local, no npm)', async () => {
40
+ await expect(loadChannelPlugins({
41
+ channelsConfig: { 'nonexistent-channel': {} },
42
+ repoPath: NO_LOCAL,
43
+ logger: mockLogger,
44
+ })).rejects.toThrow(ChannelPluginError);
45
+ });
46
+ it('throws ChannelPluginError for invalid plugin export', async () => {
47
+ createFakePackage(TEMP_REPO, '@amodalai/channel-badshape', 'export default {notAPlugin: true};');
48
+ await expect(loadChannelPlugins({
49
+ channelsConfig: { badshape: {} },
50
+ repoPath: TEMP_REPO,
51
+ packages: ['@amodalai/channel-badshape'],
52
+ logger: mockLogger,
53
+ })).rejects.toThrow(ChannelPluginError);
54
+ });
55
+ it('throws ChannelConfigError for invalid config', async () => {
56
+ // Inline config schema that rejects anything without 'required' field
57
+ const pluginCode = `
58
+ export default {
59
+ channelType: 'testvalid',
60
+ configSchema: {
61
+ parse(data) {
62
+ if (!data || typeof data.required !== 'string') throw new Error('missing required field');
63
+ return data;
64
+ },
65
+ },
66
+ createAdapter: (cfg) => ({channelType: 'testvalid', parseIncoming: async () => null, sendMessage: async () => {}}),
67
+ };
68
+ `;
69
+ createFakePackage(TEMP_REPO, '@amodalai/channel-testvalid', pluginCode);
70
+ await expect(loadChannelPlugins({
71
+ channelsConfig: { testvalid: { wrong: 'field' } },
72
+ repoPath: TEMP_REPO,
73
+ packages: ['@amodalai/channel-testvalid'],
74
+ logger: mockLogger,
75
+ })).rejects.toThrow(ChannelConfigError);
76
+ });
77
+ it('loads a valid npm plugin and returns adapter map', async () => {
78
+ const pluginCode = `
79
+ export default {
80
+ channelType: 'testok',
81
+ configSchema: { parse(data) { return data; } },
82
+ createAdapter: () => ({channelType: 'testok', parseIncoming: async () => null, sendMessage: async () => {}}),
83
+ };
84
+ `;
85
+ createFakePackage(TEMP_REPO, '@amodalai/channel-testok', pluginCode);
86
+ const adapters = await loadChannelPlugins({
87
+ channelsConfig: { testok: { token: 'abc' } },
88
+ repoPath: TEMP_REPO,
89
+ packages: ['@amodalai/channel-testok'],
90
+ logger: mockLogger,
91
+ });
92
+ expect(adapters.size).toBe(1);
93
+ expect(adapters.get('testok')).toBeDefined();
94
+ expect(adapters.get('testok')?.channelType).toBe('testok');
95
+ });
96
+ it('error message mentions both local and npm when channel not found', async () => {
97
+ try {
98
+ await loadChannelPlugins({
99
+ channelsConfig: { missing: {} },
100
+ repoPath: NO_LOCAL,
101
+ logger: mockLogger,
102
+ });
103
+ expect.unreachable('should have thrown');
104
+ }
105
+ catch (err) {
106
+ expect(err).toBeInstanceOf(ChannelPluginError);
107
+ const msg = err.message;
108
+ expect(msg).toContain('channels/missing/index.ts');
109
+ expect(msg).toContain('channel-missing');
110
+ }
111
+ });
112
+ });
113
+ //# sourceMappingURL=plugin-loader.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-loader.test.js","sourceRoot":"","sources":["../../../src/channels/plugin-loader.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ /**
7
+ * Express router for inbound messaging channel webhooks.
8
+ *
9
+ * Mirrors the `createWebhookRouter` pattern from routes/webhooks.ts:
10
+ * rate-limited, dispatches to registered channel adapters, and runs
11
+ * the message through the existing SessionManager.
12
+ *
13
+ * Route: POST /channels/:channelType/webhook
14
+ */
15
+ import { Router } from 'express';
16
+ import type { ChannelAdapter, ChannelSessionMapper } from '@amodalai/types';
17
+ import type { StandaloneSessionManager } from '../session/manager.js';
18
+ import type { MessageDedupCache } from './dedup-cache.js';
19
+ import type { RuntimeEventBus } from '../events/event-bus.js';
20
+ import type { Logger } from '../logger.js';
21
+ export interface ChannelsRouterOptions {
22
+ adapters: Map<string, ChannelAdapter>;
23
+ sessionMapper: ChannelSessionMapper;
24
+ sessionManager: StandaloneSessionManager;
25
+ dedupCache: MessageDedupCache;
26
+ eventBus: RuntimeEventBus;
27
+ logger: Logger;
28
+ }
29
+ export declare function createChannelsRouter(options: ChannelsRouterOptions): Router;
@@ -0,0 +1,165 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ /**
7
+ * Express router for inbound messaging channel webhooks.
8
+ *
9
+ * Mirrors the `createWebhookRouter` pattern from routes/webhooks.ts:
10
+ * rate-limited, dispatches to registered channel adapters, and runs
11
+ * the message through the existing SessionManager.
12
+ *
13
+ * Route: POST /channels/:channelType/webhook
14
+ */
15
+ import { Router } from 'express';
16
+ import rateLimit from 'express-rate-limit';
17
+ import { asyncHandler } from '../routes/route-helpers.js';
18
+ const ROUTE_PATH = '/:channelType/webhook';
19
+ const RESET_COMMANDS = new Set(['/reset', '/start', '/new']);
20
+ const PREVIEW_LENGTH = 50;
21
+ const FALLBACK_ERROR_MESSAGE = 'Something went wrong. Please try again.';
22
+ export function createChannelsRouter(options) {
23
+ const { adapters, sessionMapper, sessionManager, dedupCache, eventBus, logger } = options;
24
+ const router = Router();
25
+ const limiter = rateLimit({
26
+ windowMs: 60 * 1000,
27
+ max: 100,
28
+ standardHeaders: true,
29
+ legacyHeaders: false,
30
+ validate: { xForwardedForHeader: false },
31
+ });
32
+ router.post(ROUTE_PATH, limiter, asyncHandler(async (req, res) => {
33
+ const channelType = req.params['channelType'] ?? '';
34
+ const adapter = adapters.get(channelType);
35
+ if (!adapter) {
36
+ res.status(404).json({ error: `Unknown channel type: ${channelType}` });
37
+ return;
38
+ }
39
+ // Build framework-agnostic request for the adapter
40
+ const webhookReq = {
41
+ headers: req.headers,
42
+ body: req.body,
43
+ };
44
+ // Parse and verify the incoming message
45
+ const msg = await adapter.parseIncoming(webhookReq);
46
+ if (!msg) {
47
+ // Rejected (bad signature, unauthorized sender, or non-text update).
48
+ // Always return 200 — platforms retry on non-200.
49
+ eventBus.emit({
50
+ type: 'channel_auth_rejected',
51
+ channelType,
52
+ reason: 'rejected',
53
+ });
54
+ res.status(200).json({ ok: true });
55
+ return;
56
+ }
57
+ // Dedup check (platforms may resend on slow 200s)
58
+ if (dedupCache.isDuplicate(msg.channelType, msg.messageId)) {
59
+ logger.debug('channel_message_duplicate', { channelType, messageId: msg.messageId });
60
+ res.status(200).json({ ok: true });
61
+ return;
62
+ }
63
+ // Handle special reset commands
64
+ if (RESET_COMMANDS.has(msg.text.trim().toLowerCase())) {
65
+ await sessionMapper.resetSession(msg.channelType, msg.channelUserId);
66
+ await adapter.sendMessage({ channelType: msg.channelType, conversationId: msg.conversationId }, 'Session reset. Send a message to start a new conversation.');
67
+ res.status(200).json({ ok: true });
68
+ return;
69
+ }
70
+ // Emit inbound event
71
+ eventBus.emit({
72
+ type: 'channel_message_received',
73
+ channelType: msg.channelType,
74
+ channelUserId: msg.channelUserId,
75
+ sessionId: '', // Will be filled after session lookup
76
+ messagePreview: msg.text.slice(0, PREVIEW_LENGTH),
77
+ });
78
+ try {
79
+ // Find or create session
80
+ let { sessionId } = await sessionMapper.findOrCreateSession(msg.channelType, msg.channelUserId, msg.channelUserDisplay);
81
+ // If the session isn't in memory (evicted or server restarted),
82
+ // reset the stale mapping and create a fresh session.
83
+ if (!sessionManager.get(sessionId)) {
84
+ logger.debug('channel_session_stale', { channelType: msg.channelType, sessionId });
85
+ await sessionMapper.resetSession(msg.channelType, msg.channelUserId);
86
+ const fresh = await sessionMapper.findOrCreateSession(msg.channelType, msg.channelUserId, msg.channelUserDisplay);
87
+ sessionId = fresh.sessionId;
88
+ }
89
+ // Notify the adapter that processing is starting (adapter decides how to show it)
90
+ const cleanup = adapter.startProcessing
91
+ ? await adapter.startProcessing(msg.conversationId)
92
+ : null;
93
+ // Run the message through the agent loop, collecting the full reply
94
+ const replyParts = [];
95
+ try {
96
+ const stream = sessionManager.runMessage(sessionId, msg.text);
97
+ for await (const event of stream) {
98
+ if (event.type === 'text_delta' && 'content' in event) {
99
+ replyParts.push(String(event.content));
100
+ }
101
+ }
102
+ }
103
+ finally {
104
+ try {
105
+ cleanup?.stop();
106
+ }
107
+ catch (stopErr) {
108
+ logger.error('channel_cleanup_stop_failed', {
109
+ channelType: msg.channelType,
110
+ error: stopErr instanceof Error ? stopErr.message : String(stopErr),
111
+ });
112
+ }
113
+ }
114
+ const fullReply = replyParts.join('');
115
+ if (fullReply.length > 0) {
116
+ // Let the adapter deliver the response (may edit a placeholder or send fresh)
117
+ let delivered = false;
118
+ if (cleanup) {
119
+ try {
120
+ delivered = await cleanup.finish(fullReply);
121
+ }
122
+ catch (finishErr) {
123
+ logger.error('channel_cleanup_finish_failed', {
124
+ channelType: msg.channelType,
125
+ error: finishErr instanceof Error ? finishErr.message : String(finishErr),
126
+ });
127
+ }
128
+ }
129
+ if (!delivered) {
130
+ await adapter.sendMessage({ channelType: msg.channelType, conversationId: msg.conversationId }, fullReply);
131
+ }
132
+ eventBus.emit({
133
+ type: 'channel_reply_sent',
134
+ channelType: msg.channelType,
135
+ channelUserId: msg.channelUserId,
136
+ sessionId,
137
+ replyPreview: fullReply.slice(0, PREVIEW_LENGTH),
138
+ });
139
+ }
140
+ }
141
+ catch (err) {
142
+ logger.error('channel_message_failed', {
143
+ channelType: msg.channelType,
144
+ channelUserId: msg.channelUserId,
145
+ messageId: msg.messageId,
146
+ error: err instanceof Error ? err.message : String(err),
147
+ });
148
+ // Send a user-friendly error message
149
+ try {
150
+ await adapter.sendMessage({ channelType: msg.channelType, conversationId: msg.conversationId }, FALLBACK_ERROR_MESSAGE);
151
+ }
152
+ catch {
153
+ // If even the error message fails, just log — can't do more
154
+ logger.error('channel_error_reply_failed', {
155
+ channelType: msg.channelType,
156
+ channelUserId: msg.channelUserId,
157
+ });
158
+ }
159
+ }
160
+ // Always return 200 to prevent platform retries
161
+ res.status(200).json({ ok: true });
162
+ }));
163
+ return router;
164
+ }
165
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../../src/channels/routes.ts"],"names":[],"mappings":"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"}
@@ -16,8 +16,6 @@ export interface AgentConfig {
16
16
  readonly version: string;
17
17
  /** Agent description */
18
18
  readonly description?: string;
19
- /** Standing instructions for the LLM (userContext from amodal.json) */
20
- readonly userContext?: string;
21
19
  /** Custom base system prompt (overrides default) */
22
20
  readonly basePrompt?: string;
23
21
  /** Subagent names to disable */