verybot 0.1.8

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 (277) hide show
  1. package/README.md +167 -0
  2. package/dist/aliases/store.d.ts +21 -0
  3. package/dist/aliases/store.js +148 -0
  4. package/dist/aliases/types.d.ts +6 -0
  5. package/dist/aliases/types.js +1 -0
  6. package/dist/brain/agent-registry.d.ts +96 -0
  7. package/dist/brain/agent-registry.js +141 -0
  8. package/dist/brain/agent.d.ts +167 -0
  9. package/dist/brain/agent.js +932 -0
  10. package/dist/brain/channel-store.d.ts +27 -0
  11. package/dist/brain/channel-store.js +78 -0
  12. package/dist/brain/compaction.d.ts +37 -0
  13. package/dist/brain/compaction.js +214 -0
  14. package/dist/brain/context.d.ts +43 -0
  15. package/dist/brain/context.js +139 -0
  16. package/dist/brain/delegation-store.d.ts +33 -0
  17. package/dist/brain/delegation-store.js +106 -0
  18. package/dist/brain/loop.d.ts +24 -0
  19. package/dist/brain/loop.js +318 -0
  20. package/dist/brain/mcp-adapter.d.ts +43 -0
  21. package/dist/brain/mcp-adapter.js +244 -0
  22. package/dist/brain/memory-extractor.d.ts +26 -0
  23. package/dist/brain/memory-extractor.js +82 -0
  24. package/dist/brain/providers.d.ts +14 -0
  25. package/dist/brain/providers.js +85 -0
  26. package/dist/brain/queue.d.ts +18 -0
  27. package/dist/brain/queue.js +111 -0
  28. package/dist/brain/run-tools.d.ts +50 -0
  29. package/dist/brain/run-tools.js +136 -0
  30. package/dist/brain/session-key.d.ts +23 -0
  31. package/dist/brain/session-key.js +41 -0
  32. package/dist/brain/session-state.d.ts +36 -0
  33. package/dist/brain/session-state.js +51 -0
  34. package/dist/brain/session-store.d.ts +50 -0
  35. package/dist/brain/session-store.js +207 -0
  36. package/dist/brain/session.d.ts +32 -0
  37. package/dist/brain/session.js +75 -0
  38. package/dist/brain/task-subscriber.d.ts +56 -0
  39. package/dist/brain/task-subscriber.js +317 -0
  40. package/dist/brain/user-content.d.ts +16 -0
  41. package/dist/brain/user-content.js +32 -0
  42. package/dist/brain/utils.d.ts +4 -0
  43. package/dist/brain/utils.js +26 -0
  44. package/dist/brain/worker-coordinator.d.ts +25 -0
  45. package/dist/brain/worker-coordinator.js +83 -0
  46. package/dist/channels/commands.d.ts +50 -0
  47. package/dist/channels/commands.js +132 -0
  48. package/dist/channels/discord/channel.d.ts +29 -0
  49. package/dist/channels/discord/channel.js +159 -0
  50. package/dist/channels/discord/markdown.d.ts +19 -0
  51. package/dist/channels/discord/markdown.js +62 -0
  52. package/dist/channels/manager.d.ts +29 -0
  53. package/dist/channels/manager.js +100 -0
  54. package/dist/channels/slack/channel.d.ts +37 -0
  55. package/dist/channels/slack/channel.js +227 -0
  56. package/dist/channels/slack/markdown.d.ts +19 -0
  57. package/dist/channels/slack/markdown.js +62 -0
  58. package/dist/channels/specs.d.ts +32 -0
  59. package/dist/channels/specs.js +99 -0
  60. package/dist/channels/telegram/channel.d.ts +29 -0
  61. package/dist/channels/telegram/channel.js +182 -0
  62. package/dist/channels/telegram/markdown.d.ts +17 -0
  63. package/dist/channels/telegram/markdown.js +66 -0
  64. package/dist/channels/types.d.ts +26 -0
  65. package/dist/channels/types.js +1 -0
  66. package/dist/channels/whatsapp/channel.d.ts +34 -0
  67. package/dist/channels/whatsapp/channel.js +276 -0
  68. package/dist/channels/whatsapp/markdown.d.ts +20 -0
  69. package/dist/channels/whatsapp/markdown.js +51 -0
  70. package/dist/cli/claude-login.d.ts +5 -0
  71. package/dist/cli/claude-login.js +47 -0
  72. package/dist/cli/config.d.ts +5 -0
  73. package/dist/cli/config.js +78 -0
  74. package/dist/cli/index.d.ts +11 -0
  75. package/dist/cli/index.js +96 -0
  76. package/dist/computer/browser/actions.d.ts +31 -0
  77. package/dist/computer/browser/actions.js +148 -0
  78. package/dist/computer/browser/context-manager.d.ts +28 -0
  79. package/dist/computer/browser/context-manager.js +78 -0
  80. package/dist/computer/browser/manager.d.ts +91 -0
  81. package/dist/computer/browser/manager.js +344 -0
  82. package/dist/computer/browser/profile-badge.d.ts +13 -0
  83. package/dist/computer/browser/profile-badge.js +67 -0
  84. package/dist/computer/browser/screenshot.d.ts +5 -0
  85. package/dist/computer/browser/screenshot.js +21 -0
  86. package/dist/computer/browser/snapshot.d.ts +30 -0
  87. package/dist/computer/browser/snapshot.js +242 -0
  88. package/dist/computer/browser/tools.d.ts +5 -0
  89. package/dist/computer/browser/tools.js +167 -0
  90. package/dist/computer/browser/types.d.ts +26 -0
  91. package/dist/computer/browser/types.js +1 -0
  92. package/dist/computer/desktop/adapter.d.ts +25 -0
  93. package/dist/computer/desktop/adapter.js +11 -0
  94. package/dist/computer/desktop/macos.d.ts +24 -0
  95. package/dist/computer/desktop/macos.js +223 -0
  96. package/dist/computer/desktop/tools.d.ts +25 -0
  97. package/dist/computer/desktop/tools.js +114 -0
  98. package/dist/config/agent-config.d.ts +55 -0
  99. package/dist/config/agent-config.js +16 -0
  100. package/dist/config/model-catalog.d.ts +22 -0
  101. package/dist/config/model-catalog.js +112 -0
  102. package/dist/config/model-spec.d.ts +8 -0
  103. package/dist/config/model-spec.js +66 -0
  104. package/dist/config/store.d.ts +25 -0
  105. package/dist/config/store.js +143 -0
  106. package/dist/config.d.ts +110 -0
  107. package/dist/config.js +259 -0
  108. package/dist/control-ui/assets/index-Cbl7G5Sc.css +1 -0
  109. package/dist/control-ui/assets/index-Cu1P4C62.js +266 -0
  110. package/dist/control-ui/assets/noto-sans-cyrillic-ext-wght-normal-DSNfmdVt.woff2 +0 -0
  111. package/dist/control-ui/assets/noto-sans-cyrillic-wght-normal-B2hlT84T.woff2 +0 -0
  112. package/dist/control-ui/assets/noto-sans-devanagari-wght-normal-Cv-Vwajv.woff2 +0 -0
  113. package/dist/control-ui/assets/noto-sans-greek-ext-wght-normal-12T8GTDR.woff2 +0 -0
  114. package/dist/control-ui/assets/noto-sans-greek-wght-normal-Ymb6dZNd.woff2 +0 -0
  115. package/dist/control-ui/assets/noto-sans-latin-ext-wght-normal-W1qJv59z.woff2 +0 -0
  116. package/dist/control-ui/assets/noto-sans-latin-wght-normal-BYSzYMf3.woff2 +0 -0
  117. package/dist/control-ui/assets/noto-sans-vietnamese-wght-normal-DLTJy58D.woff2 +0 -0
  118. package/dist/control-ui/index.html +14 -0
  119. package/dist/control-ui/vite.svg +1 -0
  120. package/dist/events.d.ts +2 -0
  121. package/dist/events.js +11 -0
  122. package/dist/gateway/broadcast.d.ts +5 -0
  123. package/dist/gateway/broadcast.js +33 -0
  124. package/dist/gateway/methods/aliases.d.ts +17 -0
  125. package/dist/gateway/methods/aliases.js +22 -0
  126. package/dist/gateway/methods/chat.d.ts +33 -0
  127. package/dist/gateway/methods/chat.js +37 -0
  128. package/dist/gateway/methods/config.d.ts +14 -0
  129. package/dist/gateway/methods/config.js +24 -0
  130. package/dist/gateway/methods/models.d.ts +10 -0
  131. package/dist/gateway/methods/models.js +14 -0
  132. package/dist/gateway/methods/playbooks.d.ts +45 -0
  133. package/dist/gateway/methods/playbooks.js +488 -0
  134. package/dist/gateway/methods/prompt-templates.d.ts +27 -0
  135. package/dist/gateway/methods/prompt-templates.js +106 -0
  136. package/dist/gateway/methods/scheduler.d.ts +62 -0
  137. package/dist/gateway/methods/scheduler.js +129 -0
  138. package/dist/gateway/methods/sessions.d.ts +44 -0
  139. package/dist/gateway/methods/sessions.js +111 -0
  140. package/dist/gateway/methods/system.d.ts +12 -0
  141. package/dist/gateway/methods/system.js +39 -0
  142. package/dist/gateway/methods/tasks.d.ts +40 -0
  143. package/dist/gateway/methods/tasks.js +151 -0
  144. package/dist/gateway/methods/teams.d.ts +69 -0
  145. package/dist/gateway/methods/teams.js +376 -0
  146. package/dist/gateway/methods/tools.d.ts +6 -0
  147. package/dist/gateway/methods/tools.js +7 -0
  148. package/dist/gateway/methods/whatsapp.d.ts +19 -0
  149. package/dist/gateway/methods/whatsapp.js +35 -0
  150. package/dist/gateway/rpc.d.ts +38 -0
  151. package/dist/gateway/rpc.js +79 -0
  152. package/dist/gateway/server.d.ts +9 -0
  153. package/dist/gateway/server.js +137 -0
  154. package/dist/index.d.ts +1 -0
  155. package/dist/index.js +254 -0
  156. package/dist/integrations/github.d.ts +7 -0
  157. package/dist/integrations/github.js +133 -0
  158. package/dist/integrations/mcp.d.ts +7 -0
  159. package/dist/integrations/mcp.js +106 -0
  160. package/dist/integrations/registry.d.ts +47 -0
  161. package/dist/integrations/registry.js +332 -0
  162. package/dist/integrations/scanner.d.ts +10 -0
  163. package/dist/integrations/scanner.js +122 -0
  164. package/dist/integrations/twitter.d.ts +10 -0
  165. package/dist/integrations/twitter.js +120 -0
  166. package/dist/integrations/types.d.ts +72 -0
  167. package/dist/integrations/types.js +1 -0
  168. package/dist/logger.d.ts +16 -0
  169. package/dist/logger.js +104 -0
  170. package/dist/markdown/chunk.d.ts +9 -0
  171. package/dist/markdown/chunk.js +52 -0
  172. package/dist/markdown/ir.d.ts +37 -0
  173. package/dist/markdown/ir.js +529 -0
  174. package/dist/markdown/render.d.ts +22 -0
  175. package/dist/markdown/render.js +148 -0
  176. package/dist/markdown/table-render.d.ts +43 -0
  177. package/dist/markdown/table-render.js +219 -0
  178. package/dist/markdown/tables.d.ts +17 -0
  179. package/dist/markdown/tables.js +27 -0
  180. package/dist/memory/embedding.d.ts +16 -0
  181. package/dist/memory/embedding.js +66 -0
  182. package/dist/memory/explicit.d.ts +16 -0
  183. package/dist/memory/explicit.js +29 -0
  184. package/dist/memory/extractor.d.ts +13 -0
  185. package/dist/memory/extractor.js +82 -0
  186. package/dist/memory/search.d.ts +15 -0
  187. package/dist/memory/search.js +57 -0
  188. package/dist/memory/session-learning.d.ts +23 -0
  189. package/dist/memory/session-learning.js +55 -0
  190. package/dist/memory/store.d.ts +36 -0
  191. package/dist/memory/store.js +334 -0
  192. package/dist/memory/types.d.ts +9 -0
  193. package/dist/memory/types.js +2 -0
  194. package/dist/paths.d.ts +28 -0
  195. package/dist/paths.js +48 -0
  196. package/dist/prompt-templates/builtins/index.d.ts +4 -0
  197. package/dist/prompt-templates/builtins/index.js +5 -0
  198. package/dist/prompt-templates/builtins/planner.d.ts +4 -0
  199. package/dist/prompt-templates/builtins/planner.js +77 -0
  200. package/dist/prompt-templates/store.d.ts +45 -0
  201. package/dist/prompt-templates/store.js +224 -0
  202. package/dist/prompt-templates/types.d.ts +10 -0
  203. package/dist/prompt-templates/types.js +1 -0
  204. package/dist/scheduler/connected-channels.d.ts +24 -0
  205. package/dist/scheduler/connected-channels.js +57 -0
  206. package/dist/scheduler/scheduler.d.ts +22 -0
  207. package/dist/scheduler/scheduler.js +132 -0
  208. package/dist/scheduler/store.d.ts +27 -0
  209. package/dist/scheduler/store.js +205 -0
  210. package/dist/scheduler/types.d.ts +29 -0
  211. package/dist/scheduler/types.js +1 -0
  212. package/dist/security/command-validator.d.ts +22 -0
  213. package/dist/security/command-validator.js +160 -0
  214. package/dist/security/docker-sandbox.d.ts +48 -0
  215. package/dist/security/docker-sandbox.js +218 -0
  216. package/dist/security/env-filter.d.ts +8 -0
  217. package/dist/security/env-filter.js +41 -0
  218. package/dist/skills/loader.d.ts +33 -0
  219. package/dist/skills/loader.js +132 -0
  220. package/dist/skills/prompt.d.ts +6 -0
  221. package/dist/skills/prompt.js +17 -0
  222. package/dist/skills/read-tool.d.ts +7 -0
  223. package/dist/skills/read-tool.js +24 -0
  224. package/dist/skills/scanner.d.ts +6 -0
  225. package/dist/skills/scanner.js +73 -0
  226. package/dist/skills/types.d.ts +15 -0
  227. package/dist/skills/types.js +1 -0
  228. package/dist/tasks/inline-attachment-content.d.ts +9 -0
  229. package/dist/tasks/inline-attachment-content.js +64 -0
  230. package/dist/tasks/store.d.ts +112 -0
  231. package/dist/tasks/store.js +519 -0
  232. package/dist/tasks/types.d.ts +129 -0
  233. package/dist/tasks/types.js +80 -0
  234. package/dist/teams/status-config.d.ts +8 -0
  235. package/dist/teams/status-config.js +40 -0
  236. package/dist/teams/store.d.ts +111 -0
  237. package/dist/teams/store.js +671 -0
  238. package/dist/teams/types.d.ts +30 -0
  239. package/dist/teams/types.js +1 -0
  240. package/dist/tools/bash.d.ts +18 -0
  241. package/dist/tools/bash.js +64 -0
  242. package/dist/tools/channel-history.d.ts +10 -0
  243. package/dist/tools/channel-history.js +43 -0
  244. package/dist/tools/delegate.d.ts +20 -0
  245. package/dist/tools/delegate.js +299 -0
  246. package/dist/tools/fs.d.ts +4 -0
  247. package/dist/tools/fs.js +335 -0
  248. package/dist/tools/integration-toggle.d.ts +14 -0
  249. package/dist/tools/integration-toggle.js +47 -0
  250. package/dist/tools/memory.d.ts +13 -0
  251. package/dist/tools/memory.js +59 -0
  252. package/dist/tools/prompt-templates.d.ts +7 -0
  253. package/dist/tools/prompt-templates.js +133 -0
  254. package/dist/tools/registry.d.ts +6 -0
  255. package/dist/tools/registry.js +9 -0
  256. package/dist/tools/schedule.d.ts +8 -0
  257. package/dist/tools/schedule.js +219 -0
  258. package/dist/tools/speak.d.ts +10 -0
  259. package/dist/tools/speak.js +56 -0
  260. package/dist/tools/tasks.d.ts +67 -0
  261. package/dist/tools/tasks.js +288 -0
  262. package/dist/tools/teams.d.ts +22 -0
  263. package/dist/tools/teams.js +470 -0
  264. package/dist/tools/web-fetch.d.ts +3 -0
  265. package/dist/tools/web-fetch.js +22 -0
  266. package/dist/tts/edge.d.ts +10 -0
  267. package/dist/tts/edge.js +60 -0
  268. package/dist/tts/speak.d.ts +12 -0
  269. package/dist/tts/speak.js +81 -0
  270. package/dist/tts/transcribe.d.ts +5 -0
  271. package/dist/tts/transcribe.js +40 -0
  272. package/dist/utils.d.ts +5 -0
  273. package/dist/utils.js +22 -0
  274. package/dist/version.d.ts +1 -0
  275. package/dist/version.js +13 -0
  276. package/package.json +102 -0
  277. package/verybot.js +2 -0
@@ -0,0 +1,47 @@
1
+ import type { ToolSet } from "ai";
2
+ import type { ConfigStore } from "../config/store.js";
3
+ import type { Config } from "../config.js";
4
+ import type { Integration } from "./types.js";
5
+ /**
6
+ * Holds all available integrations (initialized at startup).
7
+ * Tools are NOT registered globally — they are injected per-session
8
+ * based on which integrations are active.
9
+ */
10
+ export declare class IntegrationRegistry {
11
+ private integrations;
12
+ private mcpConfigSignatures;
13
+ register(integration: Integration): void;
14
+ recordMcpConfigSignature(name: string, server: Config["mcpServers"][string]): void;
15
+ get(name: string): Integration | undefined;
16
+ has(name: string): boolean;
17
+ /** Get all available integration names. */
18
+ get names(): string[];
19
+ /** Get tools for the given active integration names. */
20
+ getToolsFor(active: Set<string>): ToolSet;
21
+ /** Get system prompts for the given active integration names. */
22
+ getPromptsFor(active: Set<string>): string[];
23
+ /** Clean up all integrations that have a cleanup method (e.g. MCP connections). */
24
+ cleanupAll(): Promise<void>;
25
+ /**
26
+ * Re-discover builtin, user, and MCP integrations from the ConfigStore.
27
+ * Adds newly-configured integrations, removes ones whose keys were deleted.
28
+ */
29
+ refresh(store: ConfigStore, integrationsDir: string, config: Config): Promise<void>;
30
+ /** Re-discover builtin integrations. */
31
+ private refreshBuiltins;
32
+ /** Re-discover user integrations from the integrations directory. */
33
+ private refreshUserIntegrations;
34
+ private static mcpSignature;
35
+ /** Reconcile MCP integrations against config.mcpServers (add/update/remove). */
36
+ private refreshMcpIntegrations;
37
+ /** Build a compact listing of all available integrations for the system prompt. */
38
+ buildListing(active?: Set<string>): string;
39
+ }
40
+ /**
41
+ * Initialize all configured integrations and return a registry.
42
+ * Builtin integrations are auto-discovered via ConfigAdapter.configKeys.
43
+ * User integrations are loaded from the integrations directory.
44
+ * MCP servers are loaded from config.mcpServers.
45
+ * Continues past individual failures (logs error, skips that integration).
46
+ */
47
+ export declare function loadIntegrations(config: Config, store: ConfigStore): Promise<IntegrationRegistry>;
@@ -0,0 +1,332 @@
1
+ import { INTEGRATIONS_DIR } from "../paths.js";
2
+ import { createGithubIntegration } from "./github.js";
3
+ import { createTwitterIntegration } from "./twitter.js";
4
+ import { createMcpIntegration } from "./mcp.js";
5
+ import { scanUserIntegrations } from "./scanner.js";
6
+ import { logger } from "../logger.js";
7
+ /** Registry of builtin integration factories keyed by id. */
8
+ const BUILTIN_FACTORIES = {
9
+ github: (c) => createGithubIntegration(c),
10
+ twitter: (c) => createTwitterIntegration(c),
11
+ };
12
+ /**
13
+ * Holds all available integrations (initialized at startup).
14
+ * Tools are NOT registered globally — they are injected per-session
15
+ * based on which integrations are active.
16
+ */
17
+ export class IntegrationRegistry {
18
+ integrations = new Map();
19
+ mcpConfigSignatures = new Map();
20
+ register(integration) {
21
+ this.integrations.set(integration.name, integration);
22
+ }
23
+ recordMcpConfigSignature(name, server) {
24
+ this.mcpConfigSignatures.set(name, IntegrationRegistry.mcpSignature(server));
25
+ }
26
+ get(name) {
27
+ return this.integrations.get(name);
28
+ }
29
+ has(name) {
30
+ return this.integrations.has(name);
31
+ }
32
+ /** Get all available integration names. */
33
+ get names() {
34
+ return Array.from(this.integrations.keys());
35
+ }
36
+ /** Get tools for the given active integration names. */
37
+ getToolsFor(active) {
38
+ const tools = {};
39
+ for (const name of active) {
40
+ const integration = this.integrations.get(name);
41
+ if (integration)
42
+ Object.assign(tools, integration.tools.tools);
43
+ }
44
+ return tools;
45
+ }
46
+ /** Get system prompts for the given active integration names. */
47
+ getPromptsFor(active) {
48
+ const prompts = [];
49
+ for (const name of active) {
50
+ const integration = this.integrations.get(name);
51
+ if (integration?.tools.systemPrompt)
52
+ prompts.push(integration.tools.systemPrompt);
53
+ }
54
+ return prompts;
55
+ }
56
+ /** Clean up all integrations that have a cleanup method (e.g. MCP connections). */
57
+ async cleanupAll() {
58
+ for (const [name, integration] of this.integrations) {
59
+ if (integration.tools.cleanup) {
60
+ try {
61
+ await integration.tools.cleanup();
62
+ }
63
+ catch (err) {
64
+ logger.warn(`Integration "${name}" cleanup failed: ${err instanceof Error ? err.message : err}`);
65
+ }
66
+ }
67
+ }
68
+ }
69
+ /**
70
+ * Re-discover builtin, user, and MCP integrations from the ConfigStore.
71
+ * Adds newly-configured integrations, removes ones whose keys were deleted.
72
+ */
73
+ async refresh(store, integrationsDir, config) {
74
+ try {
75
+ await this.refreshBuiltins(store);
76
+ }
77
+ catch (err) {
78
+ logger.error(`Builtin integration refresh failed: ${err instanceof Error ? err.message : err}`);
79
+ }
80
+ try {
81
+ await this.refreshUserIntegrations(store, integrationsDir);
82
+ }
83
+ catch (err) {
84
+ logger.error(`User integration refresh failed: ${err instanceof Error ? err.message : err}`);
85
+ }
86
+ try {
87
+ await this.refreshMcpIntegrations(config);
88
+ }
89
+ catch (err) {
90
+ logger.error(`MCP integration refresh failed: ${err instanceof Error ? err.message : err}`);
91
+ }
92
+ }
93
+ /** Re-discover builtin integrations. */
94
+ async refreshBuiltins(store) {
95
+ for (const [id, factory] of Object.entries(BUILTIN_FACTORIES)) {
96
+ const resolved = resolveBuiltinConfig(id, store);
97
+ const existing = this.integrations.get(id);
98
+ if (resolved && !existing) {
99
+ const integration = factory(resolved);
100
+ try {
101
+ await integration.tools.initialize();
102
+ this.register(integration);
103
+ logger.info(`Integration "${id}" discovered and ready`);
104
+ }
105
+ catch (err) {
106
+ logger.error(`Integration "${id}" failed to initialize: ${err instanceof Error ? err.message : err}`);
107
+ }
108
+ }
109
+ else if (!resolved && existing && existing.source !== "mcp" && existing.source !== "user") {
110
+ if (existing.tools.cleanup) {
111
+ try {
112
+ await existing.tools.cleanup();
113
+ }
114
+ catch { /* best-effort */ }
115
+ }
116
+ this.integrations.delete(id);
117
+ logger.info(`Integration "${id}" removed (config keys no longer present)`);
118
+ }
119
+ }
120
+ }
121
+ /** Re-discover user integrations from the integrations directory. */
122
+ async refreshUserIntegrations(store, dir) {
123
+ const scanned = await scanUserIntegrations(dir, store);
124
+ const scannedIds = new Set(scanned.map((i) => i.id));
125
+ // Remove user integrations that are no longer present on disk
126
+ for (const [name, existing] of this.integrations) {
127
+ if (existing.source === "user" && !scannedIds.has(existing.id)) {
128
+ if (existing.tools.cleanup) {
129
+ try {
130
+ await existing.tools.cleanup();
131
+ }
132
+ catch { /* best-effort */ }
133
+ }
134
+ this.integrations.delete(name);
135
+ logger.info(`User integration "${name}" removed (file no longer present)`);
136
+ }
137
+ }
138
+ // Add/update user integrations
139
+ for (const integration of scanned) {
140
+ const existing = this.integrations.get(integration.name);
141
+ if (existing)
142
+ continue; // Already registered — skip re-init
143
+ try {
144
+ await integration.tools.initialize();
145
+ this.register(integration);
146
+ logger.info(`User integration "${integration.name}" ready (${Object.keys(integration.tools.tools).length} tools)`);
147
+ }
148
+ catch (err) {
149
+ logger.error(`User integration "${integration.name}" failed to initialize: ${err instanceof Error ? err.message : err} — skipping`);
150
+ }
151
+ }
152
+ }
153
+ static mcpSignature(server) {
154
+ const sortedEnv = Object.fromEntries(Object.entries(server.env ?? {}).sort(([a], [b]) => a.localeCompare(b)));
155
+ return JSON.stringify({
156
+ command: server.command ?? null,
157
+ args: server.args ?? [],
158
+ env: sortedEnv,
159
+ url: server.url ?? null,
160
+ });
161
+ }
162
+ /** Reconcile MCP integrations against config.mcpServers (add/update/remove). */
163
+ async refreshMcpIntegrations(config) {
164
+ const desiredNames = new Set(Object.keys(config.mcpServers));
165
+ const existingMcp = [...this.integrations.values()].filter((i) => i.source === "mcp");
166
+ // Remove deleted MCP servers.
167
+ for (const integration of existingMcp) {
168
+ if (desiredNames.has(integration.name))
169
+ continue;
170
+ if (integration.tools.cleanup) {
171
+ try {
172
+ await integration.tools.cleanup();
173
+ }
174
+ catch {
175
+ /* best-effort */
176
+ }
177
+ }
178
+ this.integrations.delete(integration.name);
179
+ this.mcpConfigSignatures.delete(integration.name);
180
+ logger.info(`MCP integration "${integration.name}" removed (config entry deleted)`);
181
+ }
182
+ // Add or update configured MCP servers.
183
+ for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
184
+ const nextSignature = IntegrationRegistry.mcpSignature(serverConfig);
185
+ const prevSignature = this.mcpConfigSignatures.get(name);
186
+ const existing = this.integrations.get(name);
187
+ const unchangedMcp = existing?.source === "mcp" && prevSignature === nextSignature;
188
+ if (unchangedMcp)
189
+ continue;
190
+ if (existing?.source === "mcp") {
191
+ if (existing.tools.cleanup) {
192
+ try {
193
+ await existing.tools.cleanup();
194
+ }
195
+ catch {
196
+ /* best-effort */
197
+ }
198
+ }
199
+ this.integrations.delete(name);
200
+ }
201
+ try {
202
+ const integration = await createMcpIntegration(name, serverConfig);
203
+ await integration.tools.initialize();
204
+ this.register(integration);
205
+ this.recordMcpConfigSignature(name, serverConfig);
206
+ logger.info(`MCP integration "${name}" refreshed`);
207
+ }
208
+ catch (err) {
209
+ logger.error(`MCP server "${name}" failed to refresh: ${err instanceof Error ? err.message : err} — skipping`);
210
+ }
211
+ }
212
+ }
213
+ /** Build a compact listing of all available integrations for the system prompt. */
214
+ buildListing(active) {
215
+ if (this.integrations.size === 0)
216
+ return "";
217
+ const lines = [];
218
+ for (const [name, integration] of this.integrations) {
219
+ const toolNames = Object.keys(integration.tools.tools).join(", ");
220
+ const status = active?.has(name) ? " (enabled)" : " (disabled)";
221
+ const sourceTag = integration.source === "mcp" ? " [MCP]" :
222
+ integration.source === "user" ? " [user]" : "";
223
+ lines.push(` - **${name}**${sourceTag}${status}: ${toolNames}`);
224
+ }
225
+ return `## Integrations
226
+ Integrations provide external tools (APIs, MCP servers, services). Items marked [MCP] are connected MCP servers. Items marked [user] are user-defined integrations.
227
+ You can enable or disable integrations mid-session using the \`enable_integration\` and \`disable_integration\` tools.
228
+ When the user asks about integrations, MCP servers, or available tools, refer to this list.
229
+
230
+ <available_integrations>
231
+ ${lines.join("\n")}
232
+ </available_integrations>`;
233
+ }
234
+ }
235
+ /**
236
+ * Resolve config values for a builtin integration by reading flat keys
237
+ * from the ConfigStore via the integration's ConfigAdapter.configKeys mapping.
238
+ * Returns the assembled config object, or null if required keys are missing.
239
+ */
240
+ function resolveBuiltinConfig(id, store) {
241
+ const factory = BUILTIN_FACTORIES[id];
242
+ if (!factory)
243
+ return null;
244
+ // Build a probe integration to extract ConfigAdapter metadata.
245
+ // This is cheap — the factory doesn't call external APIs until initialize().
246
+ const dummyConfig = getDummyConfig(id);
247
+ const probe = factory(dummyConfig);
248
+ if (!probe.config)
249
+ return null;
250
+ const data = store.load();
251
+ const assembled = {};
252
+ let hasRequired = false;
253
+ for (const [field, flatKey] of Object.entries(probe.config.configKeys)) {
254
+ const val = data[flatKey];
255
+ if (typeof val === "string" && val) {
256
+ assembled[field] = val;
257
+ hasRequired = true;
258
+ }
259
+ }
260
+ // Validate with the schema — if it fails, the required keys are missing
261
+ const result = probe.config.schema.safeParse(assembled);
262
+ if (!result.success) {
263
+ if (hasRequired) {
264
+ logger.warn(`Integration "${id}" config incomplete: ${result.error.message}`);
265
+ }
266
+ return null;
267
+ }
268
+ return assembled;
269
+ }
270
+ /** Minimal dummy config per integration id (never hits network). */
271
+ function getDummyConfig(id) {
272
+ switch (id) {
273
+ case "github":
274
+ return { token: "dummy" };
275
+ case "twitter":
276
+ return { bearerToken: "dummy" };
277
+ default:
278
+ return {};
279
+ }
280
+ }
281
+ /**
282
+ * Initialize all configured integrations and return a registry.
283
+ * Builtin integrations are auto-discovered via ConfigAdapter.configKeys.
284
+ * User integrations are loaded from the integrations directory.
285
+ * MCP servers are loaded from config.mcpServers.
286
+ * Continues past individual failures (logs error, skips that integration).
287
+ */
288
+ export async function loadIntegrations(config, store) {
289
+ const registry = new IntegrationRegistry();
290
+ const candidates = [];
291
+ // Auto-discover builtin integrations via ConfigAdapter
292
+ for (const [id, factory] of Object.entries(BUILTIN_FACTORIES)) {
293
+ const resolved = resolveBuiltinConfig(id, store);
294
+ if (resolved) {
295
+ candidates.push(factory(resolved));
296
+ }
297
+ }
298
+ // User integrations from directory
299
+ try {
300
+ const userIntegrations = await scanUserIntegrations(INTEGRATIONS_DIR, store);
301
+ candidates.push(...userIntegrations);
302
+ }
303
+ catch (err) {
304
+ logger.error(`User integration scan failed: ${err instanceof Error ? err.message : err}`);
305
+ }
306
+ // MCP servers (no ConfigAdapter — loaded from config.mcpServers)
307
+ for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
308
+ try {
309
+ const integration = await createMcpIntegration(name, serverConfig);
310
+ candidates.push(integration);
311
+ }
312
+ catch (err) {
313
+ logger.error(`MCP server "${name}" failed to connect: ${err instanceof Error ? err.message : err} — skipping`);
314
+ }
315
+ }
316
+ for (const integration of candidates) {
317
+ try {
318
+ await integration.tools.initialize();
319
+ registry.register(integration);
320
+ if (integration.source === "mcp") {
321
+ const serverConfig = config.mcpServers[integration.name];
322
+ if (serverConfig)
323
+ registry.recordMcpConfigSignature(integration.name, serverConfig);
324
+ }
325
+ logger.info(`Integration "${integration.name}" ready (${Object.keys(integration.tools.tools).length} tools)`);
326
+ }
327
+ catch (err) {
328
+ logger.error(`Integration "${integration.name}" failed to initialize: ${err instanceof Error ? err.message : err} — skipping`);
329
+ }
330
+ }
331
+ return registry;
332
+ }
@@ -0,0 +1,10 @@
1
+ import type { ConfigStore } from "../config/store.js";
2
+ import type { Integration } from "./types.js";
3
+ /**
4
+ * Scan a directory for user integration files, resolve their config
5
+ * from the ConfigStore, and return ready-to-register Integrations.
6
+ *
7
+ * Each file must default-export an IntegrationDefinition.
8
+ * Files whose required config keys are missing are silently skipped.
9
+ */
10
+ export declare function scanUserIntegrations(dir: string, store: ConfigStore): Promise<Integration[]>;
@@ -0,0 +1,122 @@
1
+ import { readdir, stat } from "fs/promises";
2
+ import { join, resolve } from "path";
3
+ import { logger } from "../logger.js";
4
+ /** File extensions we attempt to import as integration modules. */
5
+ const IMPORTABLE_EXTENSIONS = new Set([".ts", ".js", ".mjs"]);
6
+ /**
7
+ * Scan a directory for user integration files, resolve their config
8
+ * from the ConfigStore, and return ready-to-register Integrations.
9
+ *
10
+ * Each file must default-export an IntegrationDefinition.
11
+ * Files whose required config keys are missing are silently skipped.
12
+ */
13
+ export async function scanUserIntegrations(dir, store) {
14
+ const absDir = resolve(dir);
15
+ let entries;
16
+ try {
17
+ entries = await readdir(absDir);
18
+ }
19
+ catch {
20
+ // Directory doesn't exist yet — not an error
21
+ return [];
22
+ }
23
+ const integrations = [];
24
+ const seenIds = new Set();
25
+ for (const entry of entries) {
26
+ const ext = entry.slice(entry.lastIndexOf("."));
27
+ if (!IMPORTABLE_EXTENSIONS.has(ext))
28
+ continue;
29
+ const filePath = join(absDir, entry);
30
+ const fileStat = await stat(filePath).catch(() => null);
31
+ if (!fileStat?.isFile())
32
+ continue;
33
+ try {
34
+ const integration = await loadUserIntegration(filePath, store);
35
+ if (!integration)
36
+ continue; // config keys not present — skip silently
37
+ if (seenIds.has(integration.id)) {
38
+ logger.warn(`Duplicate user integration id "${integration.id}" at ${filePath}, skipping`);
39
+ continue;
40
+ }
41
+ seenIds.add(integration.id);
42
+ integrations.push(integration);
43
+ }
44
+ catch (err) {
45
+ logger.error(`User integration "${entry}" failed to load: ${err instanceof Error ? err.message : err}`);
46
+ }
47
+ }
48
+ return integrations;
49
+ }
50
+ /**
51
+ * Import a single user integration file, resolve config, and return an Integration.
52
+ * Returns null if required config keys are missing.
53
+ */
54
+ async function loadUserIntegration(filePath, store) {
55
+ // Dynamic import — Bun handles .ts natively
56
+ const mod = await import(filePath);
57
+ const def = mod.default ?? mod;
58
+ if (!def.id || typeof def.id !== "string") {
59
+ throw new Error("Missing or invalid 'id' (must be a non-empty string)");
60
+ }
61
+ if (!def.name || typeof def.name !== "string") {
62
+ throw new Error("Missing or invalid 'name' (must be a non-empty string)");
63
+ }
64
+ if (typeof def.create !== "function") {
65
+ throw new Error("Missing 'create' function");
66
+ }
67
+ // Resolve config from ConfigStore using configKeys mapping
68
+ const config = resolveConfig(def, store);
69
+ if (config === null)
70
+ return null;
71
+ const toolAdapter = await def.create(config);
72
+ // Validate the returned ToolAdapter shape
73
+ if (!toolAdapter || typeof toolAdapter !== "object") {
74
+ throw new Error("create() must return a ToolAdapter object");
75
+ }
76
+ if (!toolAdapter.tools || typeof toolAdapter.tools !== "object") {
77
+ throw new Error("create() returned ToolAdapter missing 'tools' (must be a ToolSet object)");
78
+ }
79
+ if (typeof toolAdapter.initialize !== "function") {
80
+ throw new Error("create() returned ToolAdapter missing 'initialize' (must be a function)");
81
+ }
82
+ return {
83
+ id: def.id,
84
+ name: def.name,
85
+ source: "user",
86
+ tools: toolAdapter,
87
+ config: def.configKeys
88
+ ? {
89
+ schema: def.configSchema ?? noopSchema,
90
+ configKeys: def.configKeys,
91
+ }
92
+ : undefined,
93
+ };
94
+ }
95
+ /**
96
+ * Resolve flat config keys from the ConfigStore into a config object.
97
+ * Returns null if validation fails (required keys missing).
98
+ */
99
+ function resolveConfig(def, store) {
100
+ if (!def.configKeys)
101
+ return {};
102
+ const data = store.load();
103
+ const assembled = {};
104
+ for (const [field, flatKey] of Object.entries(def.configKeys)) {
105
+ const val = data[flatKey];
106
+ if (typeof val === "string" && val) {
107
+ assembled[field] = val;
108
+ }
109
+ }
110
+ // Validate with schema if provided
111
+ if (def.configSchema) {
112
+ const result = def.configSchema.safeParse(assembled);
113
+ if (!result.success)
114
+ return null;
115
+ }
116
+ return assembled;
117
+ }
118
+ /** Passthrough schema for integrations without configSchema. */
119
+ const noopSchema = {
120
+ safeParse: (val) => ({ success: true, data: val }),
121
+ parse: (val) => val,
122
+ };
@@ -0,0 +1,10 @@
1
+ import type { Integration } from "./types.js";
2
+ interface TwitterConfig {
3
+ bearerToken: string;
4
+ apiKey?: string;
5
+ apiSecret?: string;
6
+ accessToken?: string;
7
+ accessSecret?: string;
8
+ }
9
+ export declare function createTwitterIntegration(config: TwitterConfig): Integration;
10
+ export {};
@@ -0,0 +1,120 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+ import { TwitterApi } from "twitter-api-v2";
4
+ import { logger } from "../logger.js";
5
+ const MAX_TWEET_LENGTH = 280;
6
+ const DEFAULT_SEARCH_LIMIT = 10;
7
+ /** Zod schema for Twitter integration config. */
8
+ const twitterConfigSchema = z.object({
9
+ bearerToken: z.string().min(1),
10
+ apiKey: z.string().optional(),
11
+ apiSecret: z.string().optional(),
12
+ accessToken: z.string().optional(),
13
+ accessSecret: z.string().optional(),
14
+ });
15
+ /** Flat config.json keys → TwitterConfig fields. */
16
+ const TWITTER_CONFIG_KEYS = {
17
+ bearerToken: "TWITTER_BEARER_TOKEN",
18
+ apiKey: "TWITTER_API_KEY",
19
+ apiSecret: "TWITTER_API_SECRET",
20
+ accessToken: "TWITTER_ACCESS_TOKEN",
21
+ accessSecret: "TWITTER_ACCESS_SECRET",
22
+ };
23
+ export function createTwitterIntegration(config) {
24
+ // Read-only client (bearer token) for search
25
+ const readClient = new TwitterApi(config.bearerToken);
26
+ // Read-write client (OAuth 1.0a) for posting — only if credentials provided
27
+ const hasWriteAccess = config.apiKey && config.apiSecret && config.accessToken && config.accessSecret;
28
+ const writeClient = hasWriteAccess
29
+ ? new TwitterApi({
30
+ appKey: config.apiKey,
31
+ appSecret: config.apiSecret,
32
+ accessToken: config.accessToken,
33
+ accessSecret: config.accessSecret,
34
+ })
35
+ : null;
36
+ const tools = {
37
+ twitter_search: tool({
38
+ description: "Search for recent tweets matching a query",
39
+ inputSchema: z.object({
40
+ query: z.string().describe("Search query (Twitter search syntax)"),
41
+ limit: z
42
+ .number()
43
+ .optional()
44
+ .describe(`Max results (default: ${DEFAULT_SEARCH_LIMIT}, max: 100)`),
45
+ }),
46
+ execute: async ({ query, limit }) => {
47
+ try {
48
+ const result = await readClient.v2.search(query, {
49
+ max_results: Math.min(limit ?? DEFAULT_SEARCH_LIMIT, 100),
50
+ "tweet.fields": ["created_at", "author_id", "public_metrics"],
51
+ });
52
+ const tweets = result.data?.data;
53
+ if (!tweets?.length)
54
+ return "No tweets found.";
55
+ return tweets
56
+ .map((t) => {
57
+ const metrics = t.public_metrics;
58
+ const stats = metrics
59
+ ? ` [${metrics.like_count} likes, ${metrics.retweet_count} RTs]`
60
+ : "";
61
+ return `@${t.author_id} (${t.created_at}): ${t.text}${stats}`;
62
+ })
63
+ .join("\n\n");
64
+ }
65
+ catch (err) {
66
+ const msg = err instanceof Error ? err.message : String(err);
67
+ logger.error(`twitter_search failed: ${msg}`);
68
+ return `Error searching tweets: ${msg}`;
69
+ }
70
+ },
71
+ }),
72
+ };
73
+ // Only register posting tool if write credentials are available
74
+ if (writeClient) {
75
+ tools.twitter_post = tool({
76
+ description: `Post a tweet to Twitter/X (max ${MAX_TWEET_LENGTH} characters)`,
77
+ inputSchema: z.object({
78
+ text: z
79
+ .string()
80
+ .max(MAX_TWEET_LENGTH)
81
+ .describe(`Tweet text (max ${MAX_TWEET_LENGTH} characters)`),
82
+ }),
83
+ execute: async ({ text }) => {
84
+ try {
85
+ const { data } = await writeClient.v2.tweet(text);
86
+ return `Posted tweet: https://twitter.com/i/status/${data.id}`;
87
+ }
88
+ catch (err) {
89
+ const msg = err instanceof Error ? err.message : String(err);
90
+ logger.error(`twitter_post failed: ${msg}`);
91
+ return `Error posting tweet: ${msg}`;
92
+ }
93
+ },
94
+ });
95
+ }
96
+ const toolNames = Object.keys(tools).join(", ");
97
+ return {
98
+ id: "twitter",
99
+ name: "twitter",
100
+ source: "builtin",
101
+ tools: {
102
+ tools,
103
+ systemPrompt: [
104
+ "## Twitter/X Integration",
105
+ `You have Twitter tools available: ${toolNames}.`,
106
+ "- `twitter_search`: Search recent tweets (last 7 days)",
107
+ writeClient ? "- `twitter_post`: Post a new tweet (max 280 characters)" : "",
108
+ ]
109
+ .filter(Boolean)
110
+ .join("\n"),
111
+ initialize: async () => {
112
+ logger.info(`Twitter integration ready (search: yes, post: ${writeClient ? "yes" : "no — missing OAuth credentials"})`);
113
+ },
114
+ },
115
+ config: {
116
+ schema: twitterConfigSchema,
117
+ configKeys: TWITTER_CONFIG_KEYS,
118
+ },
119
+ };
120
+ }