verybot 0.1.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.
Potentially problematic release.
This version of verybot might be problematic. Click here for more details.
- package/dist/brain/agent-registry.d.ts +75 -0
- package/dist/brain/agent-registry.js +124 -0
- package/dist/brain/agent.d.ts +146 -0
- package/dist/brain/agent.js +680 -0
- package/dist/brain/channel-store.d.ts +27 -0
- package/dist/brain/channel-store.js +78 -0
- package/dist/brain/compaction.d.ts +37 -0
- package/dist/brain/compaction.js +214 -0
- package/dist/brain/context.d.ts +33 -0
- package/dist/brain/context.js +77 -0
- package/dist/brain/delegation-store.d.ts +33 -0
- package/dist/brain/delegation-store.js +106 -0
- package/dist/brain/loop.d.ts +21 -0
- package/dist/brain/loop.js +161 -0
- package/dist/brain/mcp-adapter.d.ts +39 -0
- package/dist/brain/mcp-adapter.js +227 -0
- package/dist/brain/memory-extractor.d.ts +26 -0
- package/dist/brain/memory-extractor.js +82 -0
- package/dist/brain/providers.d.ts +10 -0
- package/dist/brain/providers.js +69 -0
- package/dist/brain/queue.d.ts +18 -0
- package/dist/brain/queue.js +84 -0
- package/dist/brain/run-tools.d.ts +47 -0
- package/dist/brain/run-tools.js +84 -0
- package/dist/brain/session-key.d.ts +23 -0
- package/dist/brain/session-key.js +41 -0
- package/dist/brain/session-state.d.ts +36 -0
- package/dist/brain/session-state.js +51 -0
- package/dist/brain/session-store.d.ts +50 -0
- package/dist/brain/session-store.js +207 -0
- package/dist/brain/session.d.ts +32 -0
- package/dist/brain/session.js +75 -0
- package/dist/brain/utils.d.ts +4 -0
- package/dist/brain/utils.js +26 -0
- package/dist/brain/worker-coordinator.d.ts +25 -0
- package/dist/brain/worker-coordinator.js +83 -0
- package/dist/channels/commands.d.ts +35 -0
- package/dist/channels/commands.js +65 -0
- package/dist/channels/discord/channel.d.ts +18 -0
- package/dist/channels/discord/channel.js +154 -0
- package/dist/channels/discord/markdown.d.ts +19 -0
- package/dist/channels/discord/markdown.js +62 -0
- package/dist/channels/manager.d.ts +29 -0
- package/dist/channels/manager.js +100 -0
- package/dist/channels/slack/channel.d.ts +26 -0
- package/dist/channels/slack/channel.js +207 -0
- package/dist/channels/slack/markdown.d.ts +19 -0
- package/dist/channels/slack/markdown.js +62 -0
- package/dist/channels/specs.d.ts +21 -0
- package/dist/channels/specs.js +96 -0
- package/dist/channels/telegram/channel.d.ts +18 -0
- package/dist/channels/telegram/channel.js +156 -0
- package/dist/channels/telegram/markdown.d.ts +17 -0
- package/dist/channels/telegram/markdown.js +66 -0
- package/dist/channels/types.d.ts +26 -0
- package/dist/channels/types.js +1 -0
- package/dist/channels/whatsapp/channel.d.ts +23 -0
- package/dist/channels/whatsapp/channel.js +242 -0
- package/dist/channels/whatsapp/markdown.d.ts +20 -0
- package/dist/channels/whatsapp/markdown.js +51 -0
- package/dist/cli/config.d.ts +5 -0
- package/dist/cli/config.js +78 -0
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +13 -0
- package/dist/computer/browser/actions.d.ts +31 -0
- package/dist/computer/browser/actions.js +148 -0
- package/dist/computer/browser/manager.d.ts +55 -0
- package/dist/computer/browser/manager.js +496 -0
- package/dist/computer/browser/profile-badge.d.ts +13 -0
- package/dist/computer/browser/profile-badge.js +67 -0
- package/dist/computer/browser/screenshot.d.ts +5 -0
- package/dist/computer/browser/screenshot.js +21 -0
- package/dist/computer/browser/snapshot.d.ts +30 -0
- package/dist/computer/browser/snapshot.js +242 -0
- package/dist/computer/browser/tools.d.ts +5 -0
- package/dist/computer/browser/tools.js +167 -0
- package/dist/computer/desktop/adapter.d.ts +25 -0
- package/dist/computer/desktop/adapter.js +11 -0
- package/dist/computer/desktop/macos.d.ts +24 -0
- package/dist/computer/desktop/macos.js +223 -0
- package/dist/computer/desktop/tools.d.ts +25 -0
- package/dist/computer/desktop/tools.js +114 -0
- package/dist/config/agent-config.d.ts +41 -0
- package/dist/config/agent-config.js +14 -0
- package/dist/config/model-catalog.d.ts +22 -0
- package/dist/config/model-catalog.js +99 -0
- package/dist/config/store.d.ts +25 -0
- package/dist/config/store.js +143 -0
- package/dist/config.d.ts +103 -0
- package/dist/config.js +224 -0
- package/dist/control-ui/assets/index-BANXNUyt.js +143 -0
- package/dist/control-ui/assets/index-BSUFrP9R.css +1 -0
- package/dist/control-ui/assets/noto-sans-cyrillic-ext-wght-normal-DSNfmdVt.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-cyrillic-wght-normal-B2hlT84T.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-devanagari-wght-normal-Cv-Vwajv.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-greek-ext-wght-normal-12T8GTDR.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-greek-wght-normal-Ymb6dZNd.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-latin-ext-wght-normal-W1qJv59z.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-latin-wght-normal-BYSzYMf3.woff2 +0 -0
- package/dist/control-ui/assets/noto-sans-vietnamese-wght-normal-DLTJy58D.woff2 +0 -0
- package/dist/control-ui/index.html +14 -0
- package/dist/control-ui/vite.svg +1 -0
- package/dist/events.d.ts +2 -0
- package/dist/events.js +11 -0
- package/dist/gateway/broadcast.d.ts +5 -0
- package/dist/gateway/broadcast.js +33 -0
- package/dist/gateway/methods/chat.d.ts +24 -0
- package/dist/gateway/methods/chat.js +19 -0
- package/dist/gateway/methods/config.d.ts +13 -0
- package/dist/gateway/methods/config.js +14 -0
- package/dist/gateway/methods/models.d.ts +10 -0
- package/dist/gateway/methods/models.js +14 -0
- package/dist/gateway/methods/prompt-templates.d.ts +23 -0
- package/dist/gateway/methods/prompt-templates.js +82 -0
- package/dist/gateway/methods/scheduler.d.ts +62 -0
- package/dist/gateway/methods/scheduler.js +129 -0
- package/dist/gateway/methods/sessions.d.ts +26 -0
- package/dist/gateway/methods/sessions.js +54 -0
- package/dist/gateway/methods/skills.d.ts +35 -0
- package/dist/gateway/methods/skills.js +202 -0
- package/dist/gateway/methods/system.d.ts +12 -0
- package/dist/gateway/methods/system.js +39 -0
- package/dist/gateway/methods/tasks.d.ts +21 -0
- package/dist/gateway/methods/tasks.js +46 -0
- package/dist/gateway/methods/teams.d.ts +70 -0
- package/dist/gateway/methods/teams.js +374 -0
- package/dist/gateway/methods/tools.d.ts +6 -0
- package/dist/gateway/methods/tools.js +7 -0
- package/dist/gateway/methods/whatsapp.d.ts +19 -0
- package/dist/gateway/methods/whatsapp.js +35 -0
- package/dist/gateway/rpc.d.ts +38 -0
- package/dist/gateway/rpc.js +75 -0
- package/dist/gateway/server.d.ts +4 -0
- package/dist/gateway/server.js +133 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +212 -0
- package/dist/integrations/github.d.ts +7 -0
- package/dist/integrations/github.js +133 -0
- package/dist/integrations/mcp.d.ts +7 -0
- package/dist/integrations/mcp.js +106 -0
- package/dist/integrations/registry.d.ts +43 -0
- package/dist/integrations/registry.js +258 -0
- package/dist/integrations/scanner.d.ts +10 -0
- package/dist/integrations/scanner.js +122 -0
- package/dist/integrations/twitter.d.ts +10 -0
- package/dist/integrations/twitter.js +120 -0
- package/dist/integrations/types.d.ts +72 -0
- package/dist/integrations/types.js +1 -0
- package/dist/logger.d.ts +16 -0
- package/dist/logger.js +104 -0
- package/dist/markdown/chunk.d.ts +9 -0
- package/dist/markdown/chunk.js +52 -0
- package/dist/markdown/ir.d.ts +37 -0
- package/dist/markdown/ir.js +529 -0
- package/dist/markdown/render.d.ts +22 -0
- package/dist/markdown/render.js +148 -0
- package/dist/markdown/table-render.d.ts +43 -0
- package/dist/markdown/table-render.js +219 -0
- package/dist/markdown/tables.d.ts +17 -0
- package/dist/markdown/tables.js +27 -0
- package/dist/memory/embedding.d.ts +16 -0
- package/dist/memory/embedding.js +66 -0
- package/dist/memory/extractor.d.ts +6 -0
- package/dist/memory/extractor.js +72 -0
- package/dist/memory/search.d.ts +15 -0
- package/dist/memory/search.js +57 -0
- package/dist/memory/store.d.ts +34 -0
- package/dist/memory/store.js +328 -0
- package/dist/memory/types.d.ts +9 -0
- package/dist/memory/types.js +2 -0
- package/dist/paths.d.ts +20 -0
- package/dist/paths.js +29 -0
- package/dist/prompt-templates/builtins.d.ts +2 -0
- package/dist/prompt-templates/builtins.js +72 -0
- package/dist/prompt-templates/store.d.ts +39 -0
- package/dist/prompt-templates/store.js +174 -0
- package/dist/prompt-templates/types.d.ts +10 -0
- package/dist/prompt-templates/types.js +1 -0
- package/dist/scheduler/connected-channels.d.ts +24 -0
- package/dist/scheduler/connected-channels.js +57 -0
- package/dist/scheduler/scheduler.d.ts +22 -0
- package/dist/scheduler/scheduler.js +132 -0
- package/dist/scheduler/store.d.ts +27 -0
- package/dist/scheduler/store.js +205 -0
- package/dist/scheduler/types.d.ts +29 -0
- package/dist/scheduler/types.js +1 -0
- package/dist/security/command-validator.d.ts +22 -0
- package/dist/security/command-validator.js +160 -0
- package/dist/security/docker-sandbox.d.ts +48 -0
- package/dist/security/docker-sandbox.js +218 -0
- package/dist/security/env-filter.d.ts +8 -0
- package/dist/security/env-filter.js +41 -0
- package/dist/skills/loader.d.ts +33 -0
- package/dist/skills/loader.js +132 -0
- package/dist/skills/prompt.d.ts +6 -0
- package/dist/skills/prompt.js +17 -0
- package/dist/skills/read-tool.d.ts +7 -0
- package/dist/skills/read-tool.js +24 -0
- package/dist/skills/scanner.d.ts +6 -0
- package/dist/skills/scanner.js +73 -0
- package/dist/skills/types.d.ts +15 -0
- package/dist/skills/types.js +1 -0
- package/dist/tasks/store.d.ts +47 -0
- package/dist/tasks/store.js +193 -0
- package/dist/tasks/types.d.ts +75 -0
- package/dist/tasks/types.js +32 -0
- package/dist/teams/store.d.ts +78 -0
- package/dist/teams/store.js +420 -0
- package/dist/teams/types.d.ts +23 -0
- package/dist/teams/types.js +1 -0
- package/dist/tools/bash.d.ts +16 -0
- package/dist/tools/bash.js +62 -0
- package/dist/tools/channel-history.d.ts +10 -0
- package/dist/tools/channel-history.js +43 -0
- package/dist/tools/delegate.d.ts +16 -0
- package/dist/tools/delegate.js +216 -0
- package/dist/tools/fs.d.ts +4 -0
- package/dist/tools/fs.js +335 -0
- package/dist/tools/integration-toggle.d.ts +14 -0
- package/dist/tools/integration-toggle.js +47 -0
- package/dist/tools/memory.d.ts +13 -0
- package/dist/tools/memory.js +65 -0
- package/dist/tools/registry.d.ts +6 -0
- package/dist/tools/registry.js +9 -0
- package/dist/tools/schedule.d.ts +8 -0
- package/dist/tools/schedule.js +219 -0
- package/dist/tools/speak.d.ts +10 -0
- package/dist/tools/speak.js +56 -0
- package/dist/tools/tasks.d.ts +29 -0
- package/dist/tools/tasks.js +92 -0
- package/dist/tools/teams.d.ts +7 -0
- package/dist/tools/teams.js +180 -0
- package/dist/tools/web-fetch.d.ts +3 -0
- package/dist/tools/web-fetch.js +22 -0
- package/dist/tts/edge.d.ts +10 -0
- package/dist/tts/edge.js +60 -0
- package/dist/tts/speak.d.ts +12 -0
- package/dist/tts/speak.js +81 -0
- package/dist/tts/transcribe.d.ts +5 -0
- package/dist/tts/transcribe.js +40 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +22 -0
- package/package.json +90 -0
- package/verybot.js +2 -0
|
@@ -0,0 +1,258 @@
|
|
|
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
|
+
register(integration) {
|
|
20
|
+
this.integrations.set(integration.name, integration);
|
|
21
|
+
}
|
|
22
|
+
get(name) {
|
|
23
|
+
return this.integrations.get(name);
|
|
24
|
+
}
|
|
25
|
+
has(name) {
|
|
26
|
+
return this.integrations.has(name);
|
|
27
|
+
}
|
|
28
|
+
/** Get all available integration names. */
|
|
29
|
+
get names() {
|
|
30
|
+
return Array.from(this.integrations.keys());
|
|
31
|
+
}
|
|
32
|
+
/** Get tools for the given active integration names. */
|
|
33
|
+
getToolsFor(active) {
|
|
34
|
+
const tools = {};
|
|
35
|
+
for (const name of active) {
|
|
36
|
+
const integration = this.integrations.get(name);
|
|
37
|
+
if (integration)
|
|
38
|
+
Object.assign(tools, integration.tools.tools);
|
|
39
|
+
}
|
|
40
|
+
return tools;
|
|
41
|
+
}
|
|
42
|
+
/** Get system prompts for the given active integration names. */
|
|
43
|
+
getPromptsFor(active) {
|
|
44
|
+
const prompts = [];
|
|
45
|
+
for (const name of active) {
|
|
46
|
+
const integration = this.integrations.get(name);
|
|
47
|
+
if (integration?.tools.systemPrompt)
|
|
48
|
+
prompts.push(integration.tools.systemPrompt);
|
|
49
|
+
}
|
|
50
|
+
return prompts;
|
|
51
|
+
}
|
|
52
|
+
/** Clean up all integrations that have a cleanup method (e.g. MCP connections). */
|
|
53
|
+
async cleanupAll() {
|
|
54
|
+
for (const [name, integration] of this.integrations) {
|
|
55
|
+
if (integration.tools.cleanup) {
|
|
56
|
+
try {
|
|
57
|
+
await integration.tools.cleanup();
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
logger.warn(`Integration "${name}" cleanup failed: ${err instanceof Error ? err.message : err}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Re-discover builtin and user integrations from the ConfigStore.
|
|
67
|
+
* Adds newly-configured integrations, removes ones whose keys were deleted.
|
|
68
|
+
* MCP integrations are left untouched (they're long-lived connections).
|
|
69
|
+
*/
|
|
70
|
+
async refresh(store, integrationsDir) {
|
|
71
|
+
try {
|
|
72
|
+
await this.refreshBuiltins(store);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
logger.error(`Builtin integration refresh failed: ${err instanceof Error ? err.message : err}`);
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
await this.refreshUserIntegrations(store, integrationsDir);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
logger.error(`User integration refresh failed: ${err instanceof Error ? err.message : err}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Re-discover builtin integrations. */
|
|
85
|
+
async refreshBuiltins(store) {
|
|
86
|
+
for (const [id, factory] of Object.entries(BUILTIN_FACTORIES)) {
|
|
87
|
+
const resolved = resolveBuiltinConfig(id, store);
|
|
88
|
+
const existing = this.integrations.get(id);
|
|
89
|
+
if (resolved && !existing) {
|
|
90
|
+
const integration = factory(resolved);
|
|
91
|
+
try {
|
|
92
|
+
await integration.tools.initialize();
|
|
93
|
+
this.register(integration);
|
|
94
|
+
logger.info(`Integration "${id}" discovered and ready`);
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
logger.error(`Integration "${id}" failed to initialize: ${err instanceof Error ? err.message : err}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else if (!resolved && existing && existing.source !== "mcp" && existing.source !== "user") {
|
|
101
|
+
if (existing.tools.cleanup) {
|
|
102
|
+
try {
|
|
103
|
+
await existing.tools.cleanup();
|
|
104
|
+
}
|
|
105
|
+
catch { /* best-effort */ }
|
|
106
|
+
}
|
|
107
|
+
this.integrations.delete(id);
|
|
108
|
+
logger.info(`Integration "${id}" removed (config keys no longer present)`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** Re-discover user integrations from the integrations directory. */
|
|
113
|
+
async refreshUserIntegrations(store, dir) {
|
|
114
|
+
const scanned = await scanUserIntegrations(dir, store);
|
|
115
|
+
const scannedIds = new Set(scanned.map((i) => i.id));
|
|
116
|
+
// Remove user integrations that are no longer present on disk
|
|
117
|
+
for (const [name, existing] of this.integrations) {
|
|
118
|
+
if (existing.source === "user" && !scannedIds.has(existing.id)) {
|
|
119
|
+
if (existing.tools.cleanup) {
|
|
120
|
+
try {
|
|
121
|
+
await existing.tools.cleanup();
|
|
122
|
+
}
|
|
123
|
+
catch { /* best-effort */ }
|
|
124
|
+
}
|
|
125
|
+
this.integrations.delete(name);
|
|
126
|
+
logger.info(`User integration "${name}" removed (file no longer present)`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Add/update user integrations
|
|
130
|
+
for (const integration of scanned) {
|
|
131
|
+
const existing = this.integrations.get(integration.name);
|
|
132
|
+
if (existing)
|
|
133
|
+
continue; // Already registered — skip re-init
|
|
134
|
+
try {
|
|
135
|
+
await integration.tools.initialize();
|
|
136
|
+
this.register(integration);
|
|
137
|
+
logger.info(`User integration "${integration.name}" ready (${Object.keys(integration.tools.tools).length} tools)`);
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
logger.error(`User integration "${integration.name}" failed to initialize: ${err instanceof Error ? err.message : err} — skipping`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/** Build a compact listing of all available integrations for the system prompt. */
|
|
145
|
+
buildListing(active) {
|
|
146
|
+
if (this.integrations.size === 0)
|
|
147
|
+
return "";
|
|
148
|
+
const lines = [];
|
|
149
|
+
for (const [name, integration] of this.integrations) {
|
|
150
|
+
const toolNames = Object.keys(integration.tools.tools).join(", ");
|
|
151
|
+
const status = active?.has(name) ? " (enabled)" : " (disabled)";
|
|
152
|
+
const sourceTag = integration.source === "mcp" ? " [MCP]" :
|
|
153
|
+
integration.source === "user" ? " [user]" : "";
|
|
154
|
+
lines.push(` - **${name}**${sourceTag}${status}: ${toolNames}`);
|
|
155
|
+
}
|
|
156
|
+
return `## Integrations
|
|
157
|
+
Integrations provide external tools (APIs, MCP servers, services). Items marked [MCP] are connected MCP servers. Items marked [user] are user-defined integrations.
|
|
158
|
+
You can enable or disable integrations mid-session using the \`enable_integration\` and \`disable_integration\` tools.
|
|
159
|
+
When the user asks about integrations, MCP servers, or available tools, refer to this list.
|
|
160
|
+
|
|
161
|
+
<available_integrations>
|
|
162
|
+
${lines.join("\n")}
|
|
163
|
+
</available_integrations>`;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Resolve config values for a builtin integration by reading flat keys
|
|
168
|
+
* from the ConfigStore via the integration's ConfigAdapter.configKeys mapping.
|
|
169
|
+
* Returns the assembled config object, or null if required keys are missing.
|
|
170
|
+
*/
|
|
171
|
+
function resolveBuiltinConfig(id, store) {
|
|
172
|
+
const factory = BUILTIN_FACTORIES[id];
|
|
173
|
+
if (!factory)
|
|
174
|
+
return null;
|
|
175
|
+
// Build a probe integration to extract ConfigAdapter metadata.
|
|
176
|
+
// This is cheap — the factory doesn't call external APIs until initialize().
|
|
177
|
+
const dummyConfig = getDummyConfig(id);
|
|
178
|
+
const probe = factory(dummyConfig);
|
|
179
|
+
if (!probe.config)
|
|
180
|
+
return null;
|
|
181
|
+
const data = store.load();
|
|
182
|
+
const assembled = {};
|
|
183
|
+
let hasRequired = false;
|
|
184
|
+
for (const [field, flatKey] of Object.entries(probe.config.configKeys)) {
|
|
185
|
+
const val = data[flatKey];
|
|
186
|
+
if (typeof val === "string" && val) {
|
|
187
|
+
assembled[field] = val;
|
|
188
|
+
hasRequired = true;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Validate with the schema — if it fails, the required keys are missing
|
|
192
|
+
const result = probe.config.schema.safeParse(assembled);
|
|
193
|
+
if (!result.success) {
|
|
194
|
+
if (hasRequired) {
|
|
195
|
+
logger.warn(`Integration "${id}" config incomplete: ${result.error.message}`);
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
return assembled;
|
|
200
|
+
}
|
|
201
|
+
/** Minimal dummy config per integration id (never hits network). */
|
|
202
|
+
function getDummyConfig(id) {
|
|
203
|
+
switch (id) {
|
|
204
|
+
case "github":
|
|
205
|
+
return { token: "dummy" };
|
|
206
|
+
case "twitter":
|
|
207
|
+
return { bearerToken: "dummy" };
|
|
208
|
+
default:
|
|
209
|
+
return {};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Initialize all configured integrations and return a registry.
|
|
214
|
+
* Builtin integrations are auto-discovered via ConfigAdapter.configKeys.
|
|
215
|
+
* User integrations are loaded from the integrations directory.
|
|
216
|
+
* MCP servers are loaded from config.mcpServers.
|
|
217
|
+
* Continues past individual failures (logs error, skips that integration).
|
|
218
|
+
*/
|
|
219
|
+
export async function loadIntegrations(config, store) {
|
|
220
|
+
const registry = new IntegrationRegistry();
|
|
221
|
+
const candidates = [];
|
|
222
|
+
// Auto-discover builtin integrations via ConfigAdapter
|
|
223
|
+
for (const [id, factory] of Object.entries(BUILTIN_FACTORIES)) {
|
|
224
|
+
const resolved = resolveBuiltinConfig(id, store);
|
|
225
|
+
if (resolved) {
|
|
226
|
+
candidates.push(factory(resolved));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// User integrations from directory
|
|
230
|
+
try {
|
|
231
|
+
const userIntegrations = await scanUserIntegrations(INTEGRATIONS_DIR, store);
|
|
232
|
+
candidates.push(...userIntegrations);
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
logger.error(`User integration scan failed: ${err instanceof Error ? err.message : err}`);
|
|
236
|
+
}
|
|
237
|
+
// MCP servers (no ConfigAdapter — loaded from config.mcpServers)
|
|
238
|
+
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
|
|
239
|
+
try {
|
|
240
|
+
const integration = await createMcpIntegration(name, serverConfig);
|
|
241
|
+
candidates.push(integration);
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
logger.error(`MCP server "${name}" failed to connect: ${err instanceof Error ? err.message : err} — skipping`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
for (const integration of candidates) {
|
|
248
|
+
try {
|
|
249
|
+
await integration.tools.initialize();
|
|
250
|
+
registry.register(integration);
|
|
251
|
+
logger.info(`Integration "${integration.name}" ready (${Object.keys(integration.tools.tools).length} tools)`);
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
logger.error(`Integration "${integration.name}" failed to initialize: ${err instanceof Error ? err.message : err} — skipping`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return registry;
|
|
258
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { ToolSet } from "ai";
|
|
2
|
+
import type { ZodType } from "zod";
|
|
3
|
+
export interface ToolAdapter {
|
|
4
|
+
tools: ToolSet;
|
|
5
|
+
/** Optional system prompt section with usage guidance */
|
|
6
|
+
systemPrompt?: string;
|
|
7
|
+
/** Initialize the integration (validate credentials, etc.) */
|
|
8
|
+
initialize(): Promise<void>;
|
|
9
|
+
/** Clean up resources (close connections, kill subprocesses). */
|
|
10
|
+
cleanup?(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export interface ConfigAdapter {
|
|
13
|
+
/** Zod schema describing the config fields this integration needs */
|
|
14
|
+
schema: ZodType;
|
|
15
|
+
/** Which flat keys from config.json map to this integration's config.
|
|
16
|
+
* Key = integration config field name, value = config.json flat key.
|
|
17
|
+
* e.g. { token: "GITHUB_TOKEN", defaultOwner: "GITHUB_DEFAULT_OWNER" }
|
|
18
|
+
*/
|
|
19
|
+
configKeys: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
export interface Integration {
|
|
22
|
+
/** Machine identifier (e.g. "github", "twitter") */
|
|
23
|
+
id: string;
|
|
24
|
+
/** Human-readable display name */
|
|
25
|
+
name: string;
|
|
26
|
+
/** Source type: "builtin" for GitHub/Twitter, "mcp" for MCP servers, "user" for user-defined. */
|
|
27
|
+
source?: "builtin" | "mcp" | "user";
|
|
28
|
+
tools: ToolAdapter;
|
|
29
|
+
config?: ConfigAdapter;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* What a user integration file default-exports.
|
|
33
|
+
* Drop a .ts/.js file in ~/.verybot/integrations/ with this shape.
|
|
34
|
+
*
|
|
35
|
+
* Example:
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { tool } from "ai";
|
|
38
|
+
* import { z } from "zod";
|
|
39
|
+
*
|
|
40
|
+
* export default {
|
|
41
|
+
* id: "weather",
|
|
42
|
+
* name: "Weather",
|
|
43
|
+
* configKeys: { apiKey: "WEATHER_API_KEY" },
|
|
44
|
+
* create(config) {
|
|
45
|
+
* return {
|
|
46
|
+
* tools: { weather_get: tool({ ... }) },
|
|
47
|
+
* systemPrompt: "You have a weather tool.",
|
|
48
|
+
* async initialize() { },
|
|
49
|
+
* };
|
|
50
|
+
* },
|
|
51
|
+
* } satisfies IntegrationDefinition;
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export interface IntegrationDefinition {
|
|
55
|
+
/** Unique machine identifier */
|
|
56
|
+
id: string;
|
|
57
|
+
/** Human-readable display name */
|
|
58
|
+
name: string;
|
|
59
|
+
/**
|
|
60
|
+
* Maps integration config field names to flat config.json keys.
|
|
61
|
+
* The scanner resolves these from the ConfigStore before calling create().
|
|
62
|
+
* If a required key (per the Zod schema) is missing, the integration is skipped.
|
|
63
|
+
*/
|
|
64
|
+
configKeys?: Record<string, string>;
|
|
65
|
+
/** Optional Zod schema to validate the resolved config. */
|
|
66
|
+
configSchema?: ZodType;
|
|
67
|
+
/**
|
|
68
|
+
* Factory that receives the resolved config object and returns a ToolAdapter.
|
|
69
|
+
* Called only if configKeys resolve successfully (or if configKeys is omitted).
|
|
70
|
+
*/
|
|
71
|
+
create(config: Record<string, unknown>): ToolAdapter | Promise<ToolAdapter>;
|
|
72
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import winston from "winston";
|
|
2
|
+
export interface LogEntry {
|
|
3
|
+
ts: string;
|
|
4
|
+
level: string;
|
|
5
|
+
message: string;
|
|
6
|
+
}
|
|
7
|
+
type LogSubscriber = (entry: LogEntry) => void;
|
|
8
|
+
/**
|
|
9
|
+
* Register a subscriber for real-time log entries.
|
|
10
|
+
* Returns an unsubscribe function.
|
|
11
|
+
*/
|
|
12
|
+
export declare function addLogSubscriber(cb: LogSubscriber): () => void;
|
|
13
|
+
/** Return a snapshot of the current log buffer in chronological order. */
|
|
14
|
+
export declare function getLogBuffer(): LogEntry[];
|
|
15
|
+
export declare const logger: winston.Logger;
|
|
16
|
+
export {};
|