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,24 @@
1
+ import { type AssistantModelMessage, type LanguageModel, type ModelMessage, type ToolSet } from "ai";
2
+ export interface RunLoopResult {
3
+ /** The final text reply. */
4
+ text: string;
5
+ /** All response messages (assistant + tool) generated during the run. */
6
+ responseMessages: ModelMessage[];
7
+ /** Structured assistant content from the model response (text/files/tool parts). */
8
+ assistantContent: AssistantModelMessage["content"] | null;
9
+ }
10
+ export declare function runLoop(opts: {
11
+ model: LanguageModel;
12
+ system: string;
13
+ messages: ModelMessage[];
14
+ tools: ToolSet;
15
+ sessionKey: string;
16
+ abortSignal?: AbortSignal;
17
+ maxSteps?: number;
18
+ /** Worker agent ID (e.g. "researcher"). Undefined for the orchestrator. */
19
+ agentId?: string;
20
+ /** Suppress all WebSocket broadcasts (used for background workers). */
21
+ silent?: boolean;
22
+ /** Human-readable label for logs (e.g. team name instead of UUID). Falls back to sessionKey. */
23
+ sessionLabel?: string;
24
+ }): Promise<RunLoopResult>;
@@ -0,0 +1,318 @@
1
+ import { streamText, stepCountIs, } from "ai";
2
+ import { emit } from "../events.js";
3
+ import { logger } from "../logger.js";
4
+ const NO_RESPONSE_TEXT = "(no response)";
5
+ const IMAGE_ONLY_RESPONSE_TEXT = "Generated an image.";
6
+ export async function runLoop(opts) {
7
+ const { model, system, messages, tools, sessionKey, abortSignal, maxSteps = 20, agentId, silent = false, sessionLabel, } = opts;
8
+ const tag = `[${sessionLabel ?? sessionKey}]`;
9
+ const runStart = Date.now();
10
+ let aborted = false;
11
+ logger.info(`\n${tag} ════════════════════════════════════════`);
12
+ logger.info(`\x1b[36m${tag} model: ${typeof model === "string" ? model : model.modelId} tools: [${Object.keys(tools).join(", ")}]\x1b[0m`);
13
+ logger.info(`${tag} maxSteps: ${maxSteps}`);
14
+ logger.info(`${tag} ── SYSTEM PROMPT ──\n${system}`);
15
+ logger.info(`${tag} ── MESSAGES (${messages.length}) ──`);
16
+ for (const msg of messages) {
17
+ const body = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
18
+ logger.info(`${tag} [${msg.role}] ${body.slice(0, 2000)}`);
19
+ }
20
+ let stepNum = 0;
21
+ let stepStart = Date.now();
22
+ const result = streamText({
23
+ model,
24
+ system,
25
+ messages,
26
+ tools,
27
+ abortSignal,
28
+ stopWhen: stepCountIs(maxSteps),
29
+ onAbort({ steps }) {
30
+ aborted = true;
31
+ logger.info(`${tag} ⛔ aborted after ${steps.length} steps`);
32
+ },
33
+ onStepFinish({ text, reasoningText, toolCalls, toolResults, usage }) {
34
+ stepNum++;
35
+ const elapsed = Date.now() - stepStart;
36
+ logger.info(`${tag} ── step ${stepNum}/${maxSteps} (${elapsed}ms) ──`);
37
+ if (reasoningText) {
38
+ logger.info(`${tag} 🧠 reasoning: ${reasoningText.slice(0, 1500)}`);
39
+ }
40
+ if (toolCalls.length > 0) {
41
+ for (const tc of toolCalls) {
42
+ logger.info(`${tag} 🔧 call: ${tc.toolName}(${JSON.stringify(tc.input)})`);
43
+ }
44
+ if (!silent) {
45
+ emit("agent", {
46
+ sessionKey,
47
+ agentId,
48
+ tools: toolCalls.map((tc) => ({ name: tc.toolName, args: tc.input })),
49
+ });
50
+ }
51
+ }
52
+ if (toolResults.length > 0) {
53
+ for (const tr of toolResults) {
54
+ const out = tr.output;
55
+ // Skip binary/image data from logs
56
+ const isImage = Buffer.isBuffer(out) ||
57
+ (typeof out === "object" && out !== null && "data" in out && Buffer.isBuffer(out.data));
58
+ if (isImage) {
59
+ const size = Buffer.isBuffer(out) ? out.length : out.data.length;
60
+ logger.info(`${tag} ← ${tr.toolName}: [image ${size} bytes]`);
61
+ }
62
+ else {
63
+ const full = String(out);
64
+ if (looksLikeHtml(full)) {
65
+ logger.info(`${tag} ← ${tr.toolName} (${full.length} chars): [HTML] ${extractHtmlSummary(full)}`);
66
+ }
67
+ else {
68
+ logger.info(`${tag} ← ${tr.toolName} (${full.length} chars): ${full.slice(0, 1500)}`);
69
+ }
70
+ }
71
+ }
72
+ }
73
+ if (text) {
74
+ logger.info(`${tag} 💬 text: ${text.slice(0, 500)}`);
75
+ }
76
+ if (usage) {
77
+ logger.info(`${tag} 📊 tokens: ${usage.inputTokens} in / ${usage.outputTokens} out`);
78
+ }
79
+ stepStart = Date.now();
80
+ },
81
+ });
82
+ // Consume fullStream (not textStream) so errors surface here instead of
83
+ // becoming unhandled rejections from an unconsumed internal pipeline.
84
+ let collected = "";
85
+ let lastError = null;
86
+ try {
87
+ for await (const event of result.fullStream) {
88
+ if (event.type === "text-delta") {
89
+ collected += event.text;
90
+ if (!silent) {
91
+ emit("chat", {
92
+ sessionKey,
93
+ agentId,
94
+ state: "delta",
95
+ delta: event.text,
96
+ });
97
+ }
98
+ }
99
+ else if (event.type === "abort") {
100
+ aborted = true;
101
+ }
102
+ else if (event.type === "error") {
103
+ lastError = event.error;
104
+ }
105
+ }
106
+ }
107
+ catch (err) {
108
+ // fullStream flush may throw NoOutputGeneratedError — capture it
109
+ if (!lastError)
110
+ lastError = err;
111
+ if (isAbortLikeError(err))
112
+ aborted = true;
113
+ }
114
+ if (abortSignal?.aborted) {
115
+ aborted = true;
116
+ }
117
+ if (aborted) {
118
+ // Consume result promises to avoid unhandled rejections on early aborts.
119
+ const settled = await Promise.allSettled([Promise.resolve(result), result.response]);
120
+ for (const item of settled) {
121
+ if (item.status === "rejected" && !isAbortLikeError(item.reason)) {
122
+ logger.warn(`${tag} Unexpected abort settle error: ${String(item.reason)}`);
123
+ }
124
+ }
125
+ const totalMs = Date.now() - runStart;
126
+ logger.info(`${tag} ── ABORTED (${collected.length} chars, ${stepNum} steps, ${totalMs}ms) ──`);
127
+ logger.info(`${tag} ════════════════════════════════════════\n`);
128
+ if (!silent && collected.length > 0) {
129
+ emit("chat", {
130
+ sessionKey,
131
+ agentId,
132
+ state: "final",
133
+ message: { role: "assistant", content: collected },
134
+ });
135
+ }
136
+ return {
137
+ text: collected,
138
+ responseMessages: [],
139
+ assistantContent: null,
140
+ };
141
+ }
142
+ // If no text was produced and we captured an error, re-throw it
143
+ // so Agent.handleMessage can send a friendly error to the user.
144
+ if (!collected && lastError) {
145
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
146
+ }
147
+ const final = await result;
148
+ const resp = await result.response;
149
+ const responseMessages = (resp.messages ?? []);
150
+ const assistantContent = findLastAssistantContent(responseMessages);
151
+ const assistantText = extractAssistantText(assistantContent);
152
+ const text = collected.length > 0
153
+ ? collected
154
+ : assistantText.length > 0
155
+ ? assistantText
156
+ : hasImageOutput(assistantContent)
157
+ ? IMAGE_ONLY_RESPONSE_TEXT
158
+ : NO_RESPONSE_TEXT;
159
+ const totalMs = Date.now() - runStart;
160
+ logger.info(`${tag} ── FINAL REPLY (${text.length} chars, ${stepNum} steps, ${totalMs}ms) ──`);
161
+ logger.info(`${tag} ${text}`);
162
+ logger.info(`${tag} ════════════════════════════════════════\n`);
163
+ if (!silent) {
164
+ emit("chat", {
165
+ sessionKey,
166
+ agentId,
167
+ state: "final",
168
+ message: { role: "assistant", content: assistantContent ?? text },
169
+ usage: final.usage,
170
+ });
171
+ }
172
+ return {
173
+ text,
174
+ responseMessages,
175
+ assistantContent,
176
+ };
177
+ }
178
+ function findLastAssistantContent(messages) {
179
+ for (let i = messages.length - 1; i >= 0; i--) {
180
+ const message = messages[i];
181
+ if (message.role === "assistant") {
182
+ return normalizeAssistantContent(message.content);
183
+ }
184
+ }
185
+ return null;
186
+ }
187
+ function normalizeAssistantContent(content) {
188
+ if (!Array.isArray(content))
189
+ return content;
190
+ return content.map((part) => {
191
+ if (!part || typeof part !== "object")
192
+ return part;
193
+ const candidate = part;
194
+ if (candidate.type !== "file")
195
+ return part;
196
+ const data = normalizeDataContent(candidate.data);
197
+ return { ...candidate, data };
198
+ });
199
+ }
200
+ function normalizeDataContent(data) {
201
+ if (Buffer.isBuffer(data)) {
202
+ return data.toString("base64");
203
+ }
204
+ if (data instanceof Uint8Array) {
205
+ return Buffer.from(data).toString("base64");
206
+ }
207
+ if (data instanceof ArrayBuffer) {
208
+ return Buffer.from(data).toString("base64");
209
+ }
210
+ if (data instanceof URL) {
211
+ return data.toString();
212
+ }
213
+ return data;
214
+ }
215
+ function extractAssistantText(content) {
216
+ if (typeof content === "string")
217
+ return content;
218
+ if (!Array.isArray(content))
219
+ return "";
220
+ const texts = [];
221
+ for (const part of content) {
222
+ if (!part || typeof part !== "object")
223
+ continue;
224
+ const candidate = part;
225
+ if (candidate.type === "text" && typeof candidate.text === "string") {
226
+ texts.push(candidate.text);
227
+ continue;
228
+ }
229
+ if (candidate.type === "tool-result" && candidate.output && typeof candidate.output === "object") {
230
+ const output = candidate.output;
231
+ if (output.type === "text" && typeof output.value === "string") {
232
+ texts.push(output.value);
233
+ }
234
+ }
235
+ }
236
+ return texts.join("\n");
237
+ }
238
+ function hasImageOutput(content) {
239
+ if (!Array.isArray(content))
240
+ return false;
241
+ for (const part of content) {
242
+ if (!part || typeof part !== "object")
243
+ continue;
244
+ const candidate = part;
245
+ if (candidate.type === "file" && isImageMediaType(candidate.mediaType)) {
246
+ return true;
247
+ }
248
+ if (candidate.type !== "tool-result" || !candidate.output || typeof candidate.output !== "object") {
249
+ continue;
250
+ }
251
+ const output = candidate.output;
252
+ if (output.type !== "content" || !Array.isArray(output.value))
253
+ continue;
254
+ for (const item of output.value) {
255
+ if (!item || typeof item !== "object")
256
+ continue;
257
+ const contentItem = item;
258
+ if (contentItem.type === "image-data" ||
259
+ contentItem.type === "image-url" ||
260
+ contentItem.type === "image-file-id") {
261
+ return true;
262
+ }
263
+ if (contentItem.type === "media" && isImageMediaType(contentItem.mediaType)) {
264
+ return true;
265
+ }
266
+ if (contentItem.type === "file-data" && isImageMediaType(contentItem.mediaType)) {
267
+ return true;
268
+ }
269
+ }
270
+ }
271
+ return false;
272
+ }
273
+ function isImageMediaType(mediaType) {
274
+ return typeof mediaType === "string" && mediaType.toLowerCase().startsWith("image/");
275
+ }
276
+ function isAbortLikeError(err) {
277
+ if (!(err instanceof Error))
278
+ return false;
279
+ if (err.name === "AbortError")
280
+ return true;
281
+ const msg = err.message.toLowerCase();
282
+ return msg.includes("aborted") || msg.includes("abort");
283
+ }
284
+ /** When the model ends on a tool call, try to pull text from the last tool result. */
285
+ function extractFallbackText(final) {
286
+ for (let i = final.steps.length - 1; i >= 0; i--) {
287
+ const results = final.steps[i].toolResults;
288
+ if (results && results.length > 0) {
289
+ const last = results[results.length - 1].output;
290
+ return typeof last === "string" ? last : JSON.stringify(last);
291
+ }
292
+ }
293
+ return undefined;
294
+ }
295
+ function lastUserMessage(messages) {
296
+ for (let i = messages.length - 1; i >= 0; i--) {
297
+ if (messages[i].role === "user") {
298
+ const content = messages[i].content;
299
+ const text = typeof content === "string" ? content : JSON.stringify(content);
300
+ return text.slice(0, 200);
301
+ }
302
+ }
303
+ return "(empty)";
304
+ }
305
+ /** Quick check for HTML-heavy content that would be noisy in logs. */
306
+ function looksLikeHtml(s) {
307
+ const start = s.slice(0, 500);
308
+ return start.includes("<!DOCTYPE") || start.includes("<html") || (start.match(/<\w/g)?.length ?? 0) > 5;
309
+ }
310
+ /** Pull title + meta description from raw HTML for a compact summary. */
311
+ function extractHtmlSummary(html) {
312
+ const title = html.match(/<title[^>]*>(.*?)<\/title>/is)?.[1]?.trim() ?? "";
313
+ const desc = html.match(/<meta[^>]+name=["']description["'][^>]+content=["']([^"']+)/i)?.[1]?.trim() ??
314
+ html.match(/<meta[^>]+content=["']([^"']+)["'][^>]+name=["']description["']/i)?.[1]?.trim() ??
315
+ "";
316
+ const parts = [title && `title="${title}"`, desc && `desc="${desc.slice(0, 150)}"`].filter(Boolean);
317
+ return parts.length > 0 ? parts.join(" | ") : "(no meta)";
318
+ }
@@ -0,0 +1,43 @@
1
+ import type { LanguageModel, ToolSet } from "ai";
2
+ import type { McpSdkServerConfigWithInstance } from "@anthropic-ai/claude-agent-sdk";
3
+ import { z } from "zod";
4
+ import type { CodexReasoningEffort } from "../config/model-spec.js";
5
+ export interface AdaptedTools {
6
+ model: LanguageModel;
7
+ tools: ToolSet;
8
+ /** Cleanup function to shut down any HTTP MCP servers (Codex CLI strategy). */
9
+ cleanup?: () => Promise<void>;
10
+ }
11
+ /** Options forwarded to adapt strategies. */
12
+ export interface AdaptOptions {
13
+ sandboxEnabled?: boolean;
14
+ codexReasoningEffort?: CodexReasoningEffort;
15
+ /** Optional working directory for provider CLI execution. */
16
+ cwd?: string;
17
+ }
18
+ /** Extracted tool entry from an AI SDK ToolSet. */
19
+ interface UsableTool {
20
+ name: string;
21
+ description: string;
22
+ inputSchema: z.ZodObject<z.ZodRawShape>;
23
+ execute: (args: Record<string, unknown>) => Promise<unknown>;
24
+ }
25
+ /** Extract the usable tools from an AI SDK ToolSet (skips proprietary / incomplete tools). */
26
+ export declare function extractUsableTools(tools: ToolSet): UsableTool[];
27
+ /**
28
+ * Convert an AI SDK ToolSet into a Claude Code MCP server config.
29
+ * Each tool's `inputSchema` (Zod) is passed through directly,
30
+ * and `execute` is wrapped in the MCP `handler` envelope.
31
+ */
32
+ export declare function toolSetToMcpServer(tools: ToolSet, serverName?: string): McpSdkServerConfigWithInstance;
33
+ /** Start an HTTP MCP server on loopback with the given tools registered. */
34
+ export declare function startHttpMcpServer(usable: UsableTool[]): Promise<{
35
+ url: string;
36
+ cleanup: () => Promise<void>;
37
+ }>;
38
+ /**
39
+ * Adapt tools for MCP-based providers (Claude Code, Codex CLI).
40
+ * Non-MCP providers pass through unchanged.
41
+ */
42
+ export declare function adaptTools(provider: string, modelId: string, model: LanguageModel, tools: ToolSet, options?: AdaptOptions): Promise<AdaptedTools>;
43
+ export {};
@@ -0,0 +1,244 @@
1
+ import http from "node:http";
2
+ import { randomUUID } from "node:crypto";
3
+ import { createCustomMcpServer, createClaudeCode } from "ai-sdk-provider-claude-code";
4
+ import { createCodexCli } from "ai-sdk-provider-codex-cli";
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
7
+ import { logger } from "../logger.js";
8
+ import { CLAUDE_CODE_PROVIDER, CODEX_CLI_PROVIDER, CODEX_CLI_ENV } from "./providers.js";
9
+ /** Tool names that use Anthropic's proprietary format and should be skipped. */
10
+ const SKIP_TOOLS = new Set(["computer"]);
11
+ const MCP_SERVER_NAME = "verybot-tools";
12
+ const MCP_HTTP_PATH = "/mcp";
13
+ /** Force OAuth login by clearing API key from the SDK environment. */
14
+ const CLAUDE_CODE_ENV = { ANTHROPIC_API_KEY: undefined };
15
+ // ---------------------------------------------------------------------------
16
+ // Port pool — allocate from a fixed range and reclaim on cleanup
17
+ // ---------------------------------------------------------------------------
18
+ const MCP_PORT_RANGE_START = 19400;
19
+ const MCP_PORT_RANGE_END = 19999;
20
+ const availablePorts = new Set(Array.from({ length: MCP_PORT_RANGE_END - MCP_PORT_RANGE_START + 1 }, (_, i) => MCP_PORT_RANGE_START + i));
21
+ function allocatePort() {
22
+ const port = availablePorts.values().next().value;
23
+ if (port === undefined)
24
+ throw new Error(`No available MCP ports in range ${MCP_PORT_RANGE_START}-${MCP_PORT_RANGE_END}`);
25
+ availablePorts.delete(port);
26
+ return port;
27
+ }
28
+ function releasePort(port) {
29
+ availablePorts.add(port);
30
+ }
31
+ /** Extract the usable tools from an AI SDK ToolSet (skips proprietary / incomplete tools). */
32
+ export function extractUsableTools(tools) {
33
+ const result = [];
34
+ for (const [name, tool] of Object.entries(tools)) {
35
+ if (SKIP_TOOLS.has(name)) {
36
+ logger.info(`[mcp-adapter] skip "${name}" (proprietary format)`);
37
+ continue;
38
+ }
39
+ const { description, inputSchema, execute } = tool;
40
+ if (!execute || !inputSchema) {
41
+ logger.info(`[mcp-adapter] skip "${name}" — execute=${!!execute} inputSchema=${!!inputSchema}`);
42
+ continue;
43
+ }
44
+ logger.info(`[mcp-adapter] + "${name}"`);
45
+ result.push({ name, description: description ?? name, inputSchema, execute });
46
+ }
47
+ return result;
48
+ }
49
+ /** Serialize a tool result into a string for MCP text content. */
50
+ function formatResult(result) {
51
+ if (result === undefined || result === null)
52
+ return "";
53
+ if (typeof result === "string")
54
+ return result;
55
+ if (Buffer.isBuffer(result))
56
+ return `[binary ${result.length} bytes]`;
57
+ if (typeof result === "object" && "data" in result && Buffer.isBuffer(result.data)) {
58
+ return `[image ${(result.data).length} bytes]`;
59
+ }
60
+ return JSON.stringify(result);
61
+ }
62
+ /** Wrap an execute function in the MCP CallToolResult envelope. */
63
+ function wrapToolHandler(name, execute) {
64
+ return async (args) => {
65
+ try {
66
+ const result = await execute(args);
67
+ return { content: [{ type: "text", text: formatResult(result) }] };
68
+ }
69
+ catch (err) {
70
+ const message = err instanceof Error ? err.message : String(err);
71
+ logger.warn(`[mcp-adapter] Tool "${name}" error: ${message}`);
72
+ return {
73
+ content: [{ type: "text", text: `Error: ${message}` }],
74
+ isError: true,
75
+ };
76
+ }
77
+ };
78
+ }
79
+ // ---------------------------------------------------------------------------
80
+ // Claude Code in-process MCP server (unchanged behavior)
81
+ // ---------------------------------------------------------------------------
82
+ /**
83
+ * Convert an AI SDK ToolSet into a Claude Code MCP server config.
84
+ * Each tool's `inputSchema` (Zod) is passed through directly,
85
+ * and `execute` is wrapped in the MCP `handler` envelope.
86
+ */
87
+ export function toolSetToMcpServer(tools, serverName = MCP_SERVER_NAME) {
88
+ const usable = extractUsableTools(tools);
89
+ logger.info(`[mcp-adapter] Converting ${Object.keys(tools).length} AI SDK tools for server "${serverName}"`);
90
+ const mcpTools = {};
91
+ for (const { name, description, inputSchema, execute } of usable) {
92
+ mcpTools[name] = { description, inputSchema, handler: wrapToolHandler(name, execute) };
93
+ }
94
+ const toolCount = Object.keys(mcpTools).length;
95
+ logger.info(`[mcp-adapter] Registered ${toolCount} tools as MCP server "${serverName}"`);
96
+ return createCustomMcpServer({ name: serverName, tools: mcpTools });
97
+ }
98
+ // ---------------------------------------------------------------------------
99
+ // Strategy: Claude Code (in-process MCP)
100
+ // ---------------------------------------------------------------------------
101
+ function claudeCodeStrategy(modelId, _model, tools, options) {
102
+ const mcpServer = toolSetToMcpServer(tools);
103
+ const claudeCode = createClaudeCode({
104
+ defaultSettings: {
105
+ permissionMode: "bypassPermissions",
106
+ env: CLAUDE_CODE_ENV,
107
+ mcpServers: { [MCP_SERVER_NAME]: mcpServer },
108
+ ...(options.cwd ? { cwd: options.cwd } : {}),
109
+ ...(options.sandboxEnabled && { sandbox: { enabled: true } }),
110
+ verbose: logger.level === "debug",
111
+ logger: {
112
+ debug: (msg) => logger.debug(`[claude-code-sdk] ${msg}`),
113
+ info: (msg) => logger.info(`[claude-code-sdk] ${msg}`),
114
+ warn: (msg) => logger.warn(`[claude-code-sdk] ${msg}`),
115
+ error: (msg) => logger.error(`[claude-code-sdk] ${msg}`),
116
+ },
117
+ },
118
+ });
119
+ logger.info(`[mcp-adapter] Created claude-code provider with MCP server "${MCP_SERVER_NAME}" for model "${modelId}"`);
120
+ return {
121
+ model: claudeCode(modelId, { streamingInput: "always" }),
122
+ tools: {}, // tools are in the MCP server; pass empty set to AI SDK
123
+ };
124
+ }
125
+ // ---------------------------------------------------------------------------
126
+ // Strategy: Codex CLI (external HTTP MCP server)
127
+ // ---------------------------------------------------------------------------
128
+ /** Start an HTTP MCP server on loopback with the given tools registered. */
129
+ export async function startHttpMcpServer(usable) {
130
+ const mcpServer = new McpServer({ name: MCP_SERVER_NAME, version: "1.0.0" }, { capabilities: { tools: {} } });
131
+ for (const { name, description, inputSchema, execute } of usable) {
132
+ mcpServer.tool(name, description, inputSchema.shape, async (args) => {
133
+ return wrapToolHandler(name, execute)(args);
134
+ });
135
+ }
136
+ // Codex performs multiple MCP requests per run (initialize, list tools, calls).
137
+ // Streamable HTTP transport must therefore be stateful across requests.
138
+ const transport = new StreamableHTTPServerTransport({
139
+ sessionIdGenerator: () => randomUUID(),
140
+ });
141
+ await mcpServer.connect(transport);
142
+ const createHandler = () => http.createServer(async (req, res) => {
143
+ const rawUrl = req.url ?? "";
144
+ let pathname = rawUrl;
145
+ try {
146
+ pathname = new URL(rawUrl, "http://127.0.0.1").pathname;
147
+ }
148
+ catch {
149
+ // Keep raw value when URL parsing fails.
150
+ }
151
+ const normalizedPath = pathname.endsWith("/") && pathname.length > 1 ? pathname.slice(0, -1) : pathname;
152
+ if (normalizedPath !== MCP_HTTP_PATH) {
153
+ res.writeHead(404).end("Not Found");
154
+ return;
155
+ }
156
+ try {
157
+ await transport.handleRequest(req, res);
158
+ }
159
+ catch (err) {
160
+ logger.error(`[mcp-adapter] HTTP MCP request error: ${err instanceof Error ? err.message : err}`);
161
+ if (!res.headersSent) {
162
+ res.writeHead(500).end("Internal Server Error");
163
+ }
164
+ }
165
+ });
166
+ // Try up to MAX_PORT_RETRIES ports in case one is already in use externally
167
+ const MAX_PORT_RETRIES = 3;
168
+ let port = 0;
169
+ let httpServer;
170
+ for (let attempt = 0; attempt < MAX_PORT_RETRIES; attempt++) {
171
+ port = allocatePort();
172
+ httpServer = createHandler();
173
+ httpServer.unref(); // allow process to exit even if cleanup is missed
174
+ try {
175
+ await new Promise((resolve, reject) => {
176
+ httpServer.listen(port, "127.0.0.1", () => resolve());
177
+ httpServer.on("error", reject);
178
+ });
179
+ break;
180
+ }
181
+ catch (err) {
182
+ releasePort(port);
183
+ if (attempt === MAX_PORT_RETRIES - 1)
184
+ throw err;
185
+ logger.warn(`[mcp-adapter] Port ${port} in use, retrying with another port`);
186
+ }
187
+ }
188
+ const url = `http://127.0.0.1:${port}${MCP_HTTP_PATH}`;
189
+ logger.info(`[mcp-adapter] HTTP MCP server listening on ${url} (${usable.length} tools)`);
190
+ const cleanup = async () => {
191
+ logger.info(`[mcp-adapter] Shutting down HTTP MCP server on port ${port}`);
192
+ await mcpServer.close();
193
+ await new Promise((resolve) => httpServer.close(() => resolve()));
194
+ releasePort(port);
195
+ };
196
+ return { url, cleanup };
197
+ }
198
+ async function codexCliStrategy(modelId, _model, tools, options) {
199
+ const usable = extractUsableTools(tools);
200
+ logger.info(`[mcp-adapter] Setting up Codex CLI HTTP MCP server for model "${modelId}"`);
201
+ const { url, cleanup } = await startHttpMcpServer(usable);
202
+ // Match "bypass all permissions": disable Codex CLI approvals and sandbox.
203
+ const codexCli = createCodexCli({
204
+ defaultSettings: {
205
+ dangerouslyBypassApprovalsAndSandbox: true,
206
+ env: CODEX_CLI_ENV,
207
+ ...(options.cwd ? { cwd: options.cwd } : {}),
208
+ ...(options.codexReasoningEffort ? { reasoningEffort: options.codexReasoningEffort } : {}),
209
+ rmcpClient: true,
210
+ mcpServers: {
211
+ [MCP_SERVER_NAME]: { transport: "http", url },
212
+ },
213
+ verbose: logger.level === "debug",
214
+ logger: {
215
+ debug: (msg) => logger.debug(`[codex-cli-sdk] ${msg}`),
216
+ info: (msg) => logger.info(`[codex-cli-sdk] ${msg}`),
217
+ warn: (msg) => logger.warn(`[codex-cli-sdk] ${msg}`),
218
+ error: (msg) => logger.error(`[codex-cli-sdk] ${msg}`),
219
+ },
220
+ },
221
+ });
222
+ return {
223
+ model: codexCli(modelId),
224
+ tools: {}, // tools are in the MCP server; pass empty set to AI SDK
225
+ cleanup,
226
+ };
227
+ }
228
+ // ---------------------------------------------------------------------------
229
+ // Strategy dispatcher
230
+ // ---------------------------------------------------------------------------
231
+ const STRATEGIES = {
232
+ [CLAUDE_CODE_PROVIDER]: claudeCodeStrategy,
233
+ [CODEX_CLI_PROVIDER]: codexCliStrategy,
234
+ };
235
+ /**
236
+ * Adapt tools for MCP-based providers (Claude Code, Codex CLI).
237
+ * Non-MCP providers pass through unchanged.
238
+ */
239
+ export async function adaptTools(provider, modelId, model, tools, options = {}) {
240
+ const strategy = STRATEGIES[provider];
241
+ if (!strategy)
242
+ return { model, tools };
243
+ return strategy(modelId, model, tools, options);
244
+ }
@@ -0,0 +1,26 @@
1
+ import type { LanguageModel, ModelMessage } from "ai";
2
+ import type { MemoryStore } from "../memory/store.js";
3
+ import type { EmbeddingProvider } from "../memory/embedding.js";
4
+ import { extractFacts } from "../memory/extractor.js";
5
+ import type { SessionStateMap } from "./session-state.js";
6
+ /**
7
+ * Handles memory extraction from conversation sessions.
8
+ * Tracks per-session message counts and batches extraction.
9
+ */
10
+ export declare class MemoryExtractor {
11
+ private model;
12
+ private memoryStore;
13
+ private embeddingProvider;
14
+ constructor(model: LanguageModel, memoryStore: MemoryStore, embeddingProvider: EmbeddingProvider | null);
15
+ /** Update the model used for extraction (e.g. after config reload). */
16
+ setModel(model: LanguageModel): void;
17
+ /**
18
+ * Increment extraction counter and trigger extraction if threshold reached.
19
+ * Non-blocking: extraction runs in background.
20
+ */
21
+ trackAndMaybeExtract(sessionKey: string, sessions: SessionStateMap, messages: ModelMessage[], teamId?: string): void;
22
+ /** Extract and save facts from messages to memory store. */
23
+ extractAndSaveFacts(sessionKey: string, messages: Parameters<typeof extractFacts>[1], teamId?: string): Promise<void>;
24
+ /** Extract remaining facts from all active sessions (call before shutdown). */
25
+ flushAll(sessions: SessionStateMap): Promise<void>;
26
+ }