@swarmclawai/swarmclaw 0.2.0

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 (319) hide show
  1. package/README.md +577 -0
  2. package/bin/server-cmd.js +359 -0
  3. package/bin/swarmclaw.js +29 -0
  4. package/bin/swarmclaw.mjs +1504 -0
  5. package/next.config.ts +33 -0
  6. package/package.json +112 -0
  7. package/postcss.config.mjs +7 -0
  8. package/public/branding/swarmclaw-org-avatar.png +0 -0
  9. package/public/branding/swarmclaw-org-avatar.svg +58 -0
  10. package/public/file.svg +1 -0
  11. package/public/globe.svg +1 -0
  12. package/public/next.svg +1 -0
  13. package/public/screenshots/agents.png +0 -0
  14. package/public/screenshots/connectors.png +0 -0
  15. package/public/screenshots/dashboard.png +0 -0
  16. package/public/screenshots/new-session-openclaw.png +0 -0
  17. package/public/screenshots/providers.png +0 -0
  18. package/public/screenshots/schedules.png +0 -0
  19. package/public/screenshots/tasks.png +0 -0
  20. package/public/vercel.svg +1 -0
  21. package/public/window.svg +1 -0
  22. package/src/app/api/agents/[id]/route.ts +30 -0
  23. package/src/app/api/agents/[id]/thread/route.ts +66 -0
  24. package/src/app/api/agents/generate/route.ts +42 -0
  25. package/src/app/api/agents/route.ts +33 -0
  26. package/src/app/api/auth/route.ts +25 -0
  27. package/src/app/api/claude-skills/route.ts +42 -0
  28. package/src/app/api/clawhub/install/route.ts +39 -0
  29. package/src/app/api/clawhub/search/route.ts +11 -0
  30. package/src/app/api/connectors/[id]/route.ts +79 -0
  31. package/src/app/api/connectors/route.ts +60 -0
  32. package/src/app/api/credentials/[id]/route.ts +14 -0
  33. package/src/app/api/credentials/route.ts +31 -0
  34. package/src/app/api/daemon/health-check/route.ts +11 -0
  35. package/src/app/api/daemon/route.ts +22 -0
  36. package/src/app/api/dirs/pick/route.ts +60 -0
  37. package/src/app/api/dirs/route.ts +29 -0
  38. package/src/app/api/documents/[id]/route.ts +47 -0
  39. package/src/app/api/documents/route.ts +93 -0
  40. package/src/app/api/files/serve/route.ts +69 -0
  41. package/src/app/api/generate/info/route.ts +12 -0
  42. package/src/app/api/generate/route.ts +106 -0
  43. package/src/app/api/ip/route.ts +6 -0
  44. package/src/app/api/knowledge/[id]/route.ts +61 -0
  45. package/src/app/api/knowledge/route.ts +48 -0
  46. package/src/app/api/knowledge/upload/route.ts +86 -0
  47. package/src/app/api/logs/route.ts +65 -0
  48. package/src/app/api/mcp-servers/[id]/route.ts +32 -0
  49. package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
  50. package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
  51. package/src/app/api/mcp-servers/route.ts +27 -0
  52. package/src/app/api/memory/[id]/route.ts +126 -0
  53. package/src/app/api/memory/maintenance/route.ts +63 -0
  54. package/src/app/api/memory/route.ts +111 -0
  55. package/src/app/api/memory-images/[filename]/route.ts +36 -0
  56. package/src/app/api/orchestrator/run/route.ts +43 -0
  57. package/src/app/api/plugins/install/route.ts +58 -0
  58. package/src/app/api/plugins/marketplace/route.ts +33 -0
  59. package/src/app/api/plugins/route.ts +21 -0
  60. package/src/app/api/preview-server/route.ts +339 -0
  61. package/src/app/api/providers/[id]/models/route.ts +29 -0
  62. package/src/app/api/providers/[id]/route.ts +34 -0
  63. package/src/app/api/providers/configs/route.ts +7 -0
  64. package/src/app/api/providers/ollama/route.ts +30 -0
  65. package/src/app/api/providers/openclaw/health/route.ts +23 -0
  66. package/src/app/api/providers/route.ts +28 -0
  67. package/src/app/api/runs/[id]/route.ts +9 -0
  68. package/src/app/api/runs/route.ts +13 -0
  69. package/src/app/api/schedules/[id]/route.ts +28 -0
  70. package/src/app/api/schedules/[id]/run/route.ts +104 -0
  71. package/src/app/api/schedules/route.ts +78 -0
  72. package/src/app/api/secrets/[id]/route.ts +29 -0
  73. package/src/app/api/secrets/route.ts +42 -0
  74. package/src/app/api/sessions/[id]/browser/route.ts +13 -0
  75. package/src/app/api/sessions/[id]/chat/route.ts +96 -0
  76. package/src/app/api/sessions/[id]/clear/route.ts +19 -0
  77. package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
  78. package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
  79. package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
  80. package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
  81. package/src/app/api/sessions/[id]/messages/route.ts +9 -0
  82. package/src/app/api/sessions/[id]/retry/route.ts +28 -0
  83. package/src/app/api/sessions/[id]/route.ts +103 -0
  84. package/src/app/api/sessions/[id]/stop/route.ts +13 -0
  85. package/src/app/api/sessions/heartbeat/route.ts +26 -0
  86. package/src/app/api/sessions/route.ts +85 -0
  87. package/src/app/api/settings/route.ts +58 -0
  88. package/src/app/api/setup/check-provider/route.ts +326 -0
  89. package/src/app/api/setup/doctor/route.ts +250 -0
  90. package/src/app/api/skills/[id]/route.ts +40 -0
  91. package/src/app/api/skills/import/route.ts +69 -0
  92. package/src/app/api/skills/route.ts +28 -0
  93. package/src/app/api/tasks/[id]/route.ts +102 -0
  94. package/src/app/api/tasks/route.ts +115 -0
  95. package/src/app/api/tts/route.ts +40 -0
  96. package/src/app/api/upload/route.ts +18 -0
  97. package/src/app/api/uploads/[filename]/route.ts +59 -0
  98. package/src/app/api/usage/route.ts +35 -0
  99. package/src/app/api/version/route.ts +81 -0
  100. package/src/app/api/version/update/route.ts +95 -0
  101. package/src/app/api/webhooks/[id]/history/route.ts +13 -0
  102. package/src/app/api/webhooks/[id]/route.ts +204 -0
  103. package/src/app/api/webhooks/route.ts +37 -0
  104. package/src/app/favicon.ico +0 -0
  105. package/src/app/globals.css +370 -0
  106. package/src/app/layout.tsx +52 -0
  107. package/src/app/page.tsx +172 -0
  108. package/src/cli/index.js +1232 -0
  109. package/src/cli/index.test.js +281 -0
  110. package/src/cli/index.ts +1158 -0
  111. package/src/cli/spec.js +284 -0
  112. package/src/components/agents/agent-card.tsx +219 -0
  113. package/src/components/agents/agent-chat-list.tsx +165 -0
  114. package/src/components/agents/agent-list.tsx +110 -0
  115. package/src/components/agents/agent-sheet.tsx +1220 -0
  116. package/src/components/auth/access-key-gate.tsx +248 -0
  117. package/src/components/auth/setup-wizard.tsx +940 -0
  118. package/src/components/auth/user-picker.tsx +88 -0
  119. package/src/components/chat/chat-area.tsx +406 -0
  120. package/src/components/chat/chat-header.tsx +491 -0
  121. package/src/components/chat/chat-tool-toggles.tsx +161 -0
  122. package/src/components/chat/code-block.tsx +146 -0
  123. package/src/components/chat/dev-server-bar.tsx +39 -0
  124. package/src/components/chat/message-bubble.tsx +486 -0
  125. package/src/components/chat/message-list.tsx +299 -0
  126. package/src/components/chat/session-debug-panel.tsx +196 -0
  127. package/src/components/chat/streaming-bubble.tsx +85 -0
  128. package/src/components/chat/thinking-indicator.tsx +26 -0
  129. package/src/components/chat/tool-call-bubble.tsx +438 -0
  130. package/src/components/chat/tool-request-banner.tsx +103 -0
  131. package/src/components/connectors/connector-list.tsx +196 -0
  132. package/src/components/connectors/connector-sheet.tsx +804 -0
  133. package/src/components/input/chat-input.tsx +235 -0
  134. package/src/components/knowledge/knowledge-list.tsx +206 -0
  135. package/src/components/knowledge/knowledge-sheet.tsx +316 -0
  136. package/src/components/layout/app-layout.tsx +1016 -0
  137. package/src/components/layout/daemon-indicator.tsx +56 -0
  138. package/src/components/layout/mobile-header.tsx +31 -0
  139. package/src/components/layout/network-banner.tsx +17 -0
  140. package/src/components/layout/update-banner.tsx +130 -0
  141. package/src/components/logs/log-list.tsx +358 -0
  142. package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
  143. package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
  144. package/src/components/memory/memory-card.tsx +63 -0
  145. package/src/components/memory/memory-detail.tsx +339 -0
  146. package/src/components/memory/memory-list.tsx +198 -0
  147. package/src/components/memory/memory-sheet.tsx +70 -0
  148. package/src/components/plugins/plugin-list.tsx +60 -0
  149. package/src/components/plugins/plugin-sheet.tsx +311 -0
  150. package/src/components/providers/provider-list.tsx +96 -0
  151. package/src/components/providers/provider-sheet.tsx +542 -0
  152. package/src/components/runs/run-list.tsx +231 -0
  153. package/src/components/schedules/schedule-card.tsx +63 -0
  154. package/src/components/schedules/schedule-list.tsx +76 -0
  155. package/src/components/schedules/schedule-sheet.tsx +336 -0
  156. package/src/components/secrets/secret-sheet.tsx +180 -0
  157. package/src/components/secrets/secrets-list.tsx +91 -0
  158. package/src/components/sessions/new-session-sheet.tsx +478 -0
  159. package/src/components/sessions/session-card.tsx +144 -0
  160. package/src/components/sessions/session-list.tsx +202 -0
  161. package/src/components/shared/ai-gen-block.tsx +77 -0
  162. package/src/components/shared/avatar.tsx +48 -0
  163. package/src/components/shared/bottom-sheet.tsx +30 -0
  164. package/src/components/shared/confirm-dialog.tsx +47 -0
  165. package/src/components/shared/connector-platform-icon.tsx +113 -0
  166. package/src/components/shared/dir-browser.tsx +285 -0
  167. package/src/components/shared/dropdown.tsx +55 -0
  168. package/src/components/shared/icon-button.tsx +25 -0
  169. package/src/components/shared/settings/plugin-manager.tsx +207 -0
  170. package/src/components/shared/settings/section-capability-policy.tsx +93 -0
  171. package/src/components/shared/settings/section-embedding.tsx +99 -0
  172. package/src/components/shared/settings/section-heartbeat.tsx +168 -0
  173. package/src/components/shared/settings/section-memory.tsx +77 -0
  174. package/src/components/shared/settings/section-orchestrator.tsx +108 -0
  175. package/src/components/shared/settings/section-providers.tsx +181 -0
  176. package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
  177. package/src/components/shared/settings/section-secrets.tsx +132 -0
  178. package/src/components/shared/settings/section-user-preferences.tsx +24 -0
  179. package/src/components/shared/settings/section-voice.tsx +53 -0
  180. package/src/components/shared/settings/settings-sheet.tsx +88 -0
  181. package/src/components/shared/settings/types.ts +7 -0
  182. package/src/components/shared/settings/utils.ts +13 -0
  183. package/src/components/shared/settings-sheet.tsx +1 -0
  184. package/src/components/shared/skeleton.tsx +19 -0
  185. package/src/components/shared/usage-badge.tsx +28 -0
  186. package/src/components/skills/clawhub-browser.tsx +225 -0
  187. package/src/components/skills/skill-list.tsx +70 -0
  188. package/src/components/skills/skill-sheet.tsx +254 -0
  189. package/src/components/tasks/task-board.tsx +96 -0
  190. package/src/components/tasks/task-card.tsx +179 -0
  191. package/src/components/tasks/task-column.tsx +73 -0
  192. package/src/components/tasks/task-list.tsx +118 -0
  193. package/src/components/tasks/task-sheet.tsx +415 -0
  194. package/src/components/ui/avatar.tsx +109 -0
  195. package/src/components/ui/badge.tsx +48 -0
  196. package/src/components/ui/button.tsx +64 -0
  197. package/src/components/ui/card.tsx +92 -0
  198. package/src/components/ui/dialog.tsx +158 -0
  199. package/src/components/ui/dropdown-menu.tsx +257 -0
  200. package/src/components/ui/input.tsx +21 -0
  201. package/src/components/ui/scroll-area.tsx +58 -0
  202. package/src/components/ui/select.tsx +190 -0
  203. package/src/components/ui/separator.tsx +28 -0
  204. package/src/components/ui/sheet.tsx +143 -0
  205. package/src/components/ui/sonner.tsx +22 -0
  206. package/src/components/ui/textarea.tsx +18 -0
  207. package/src/components/ui/tooltip.tsx +56 -0
  208. package/src/components/usage/usage-list.tsx +105 -0
  209. package/src/components/webhooks/webhook-list.tsx +166 -0
  210. package/src/components/webhooks/webhook-sheet.tsx +402 -0
  211. package/src/hooks/use-auto-resize.ts +20 -0
  212. package/src/hooks/use-media-query.ts +21 -0
  213. package/src/hooks/use-speech-recognition.ts +83 -0
  214. package/src/instrumentation.ts +8 -0
  215. package/src/lib/agents.ts +13 -0
  216. package/src/lib/api-client.ts +100 -0
  217. package/src/lib/chat.ts +60 -0
  218. package/src/lib/memory.ts +42 -0
  219. package/src/lib/openclaw-endpoint.test.ts +48 -0
  220. package/src/lib/openclaw-endpoint.ts +67 -0
  221. package/src/lib/provider-config.ts +13 -0
  222. package/src/lib/providers/anthropic.ts +135 -0
  223. package/src/lib/providers/claude-cli.ts +202 -0
  224. package/src/lib/providers/codex-cli.ts +260 -0
  225. package/src/lib/providers/index.ts +351 -0
  226. package/src/lib/providers/ollama.ts +131 -0
  227. package/src/lib/providers/openai.ts +164 -0
  228. package/src/lib/providers/openclaw.ts +330 -0
  229. package/src/lib/providers/opencode-cli.ts +164 -0
  230. package/src/lib/runtime-loop.ts +15 -0
  231. package/src/lib/schedule-dedupe.test.ts +84 -0
  232. package/src/lib/schedule-dedupe.ts +174 -0
  233. package/src/lib/schedule-name.ts +62 -0
  234. package/src/lib/schedules.ts +16 -0
  235. package/src/lib/server/agent-registry.ts +70 -0
  236. package/src/lib/server/api-routes.test.ts +362 -0
  237. package/src/lib/server/autonomy-contract.ts +200 -0
  238. package/src/lib/server/build-llm.ts +155 -0
  239. package/src/lib/server/capability-router.test.ts +21 -0
  240. package/src/lib/server/capability-router.ts +172 -0
  241. package/src/lib/server/chat-execution.ts +894 -0
  242. package/src/lib/server/clawhub-client.test.ts +161 -0
  243. package/src/lib/server/clawhub-client.ts +26 -0
  244. package/src/lib/server/connectors/connector-routing.test.ts +243 -0
  245. package/src/lib/server/connectors/discord.ts +116 -0
  246. package/src/lib/server/connectors/googlechat.ts +66 -0
  247. package/src/lib/server/connectors/manager.ts +559 -0
  248. package/src/lib/server/connectors/matrix.ts +78 -0
  249. package/src/lib/server/connectors/media.ts +149 -0
  250. package/src/lib/server/connectors/openclaw.test.ts +375 -0
  251. package/src/lib/server/connectors/openclaw.ts +1132 -0
  252. package/src/lib/server/connectors/signal.ts +183 -0
  253. package/src/lib/server/connectors/slack.ts +258 -0
  254. package/src/lib/server/connectors/teams.ts +94 -0
  255. package/src/lib/server/connectors/telegram.ts +221 -0
  256. package/src/lib/server/connectors/types.ts +62 -0
  257. package/src/lib/server/connectors/whatsapp.ts +349 -0
  258. package/src/lib/server/context-manager.ts +232 -0
  259. package/src/lib/server/cost.ts +31 -0
  260. package/src/lib/server/daemon-state.ts +354 -0
  261. package/src/lib/server/data-dir.ts +3 -0
  262. package/src/lib/server/embeddings.ts +111 -0
  263. package/src/lib/server/execution-log.ts +257 -0
  264. package/src/lib/server/gateway/protocol.test.ts +54 -0
  265. package/src/lib/server/gateway/protocol.ts +114 -0
  266. package/src/lib/server/heartbeat-service.ts +366 -0
  267. package/src/lib/server/knowledge-db.test.ts +441 -0
  268. package/src/lib/server/logger.ts +47 -0
  269. package/src/lib/server/main-agent-loop.ts +1017 -0
  270. package/src/lib/server/mcp-client.test.ts +342 -0
  271. package/src/lib/server/mcp-client.ts +130 -0
  272. package/src/lib/server/memory-db.ts +1078 -0
  273. package/src/lib/server/memory-graph.test.ts +153 -0
  274. package/src/lib/server/memory-graph.ts +138 -0
  275. package/src/lib/server/openclaw-health.ts +245 -0
  276. package/src/lib/server/orchestrator-lg.ts +431 -0
  277. package/src/lib/server/orchestrator.ts +364 -0
  278. package/src/lib/server/playwright-proxy.mjs +70 -0
  279. package/src/lib/server/plugins.ts +229 -0
  280. package/src/lib/server/process-manager.ts +327 -0
  281. package/src/lib/server/provider-health.ts +113 -0
  282. package/src/lib/server/queue.ts +859 -0
  283. package/src/lib/server/runtime-settings.ts +119 -0
  284. package/src/lib/server/scheduler.ts +196 -0
  285. package/src/lib/server/session-mailbox.ts +129 -0
  286. package/src/lib/server/session-run-manager.ts +512 -0
  287. package/src/lib/server/session-tools/connector.ts +124 -0
  288. package/src/lib/server/session-tools/context-mgmt.ts +103 -0
  289. package/src/lib/server/session-tools/context.ts +114 -0
  290. package/src/lib/server/session-tools/crud.ts +673 -0
  291. package/src/lib/server/session-tools/delegate.ts +708 -0
  292. package/src/lib/server/session-tools/file.ts +264 -0
  293. package/src/lib/server/session-tools/index.ts +164 -0
  294. package/src/lib/server/session-tools/memory.ts +230 -0
  295. package/src/lib/server/session-tools/session-info.ts +422 -0
  296. package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
  297. package/src/lib/server/session-tools/shell.ts +171 -0
  298. package/src/lib/server/session-tools/web.ts +408 -0
  299. package/src/lib/server/session-tools.ts +9 -0
  300. package/src/lib/server/skills-normalize.ts +130 -0
  301. package/src/lib/server/storage-mcp.test.ts +161 -0
  302. package/src/lib/server/storage.ts +670 -0
  303. package/src/lib/server/stream-agent-chat.ts +571 -0
  304. package/src/lib/server/task-reports.ts +122 -0
  305. package/src/lib/server/task-result.ts +161 -0
  306. package/src/lib/server/task-validation.test.ts +27 -0
  307. package/src/lib/server/task-validation.ts +90 -0
  308. package/src/lib/server/tool-capability-policy.test.ts +58 -0
  309. package/src/lib/server/tool-capability-policy.ts +262 -0
  310. package/src/lib/sessions.ts +68 -0
  311. package/src/lib/tasks.ts +20 -0
  312. package/src/lib/tts.ts +42 -0
  313. package/src/lib/upload.ts +10 -0
  314. package/src/lib/utils.ts +6 -0
  315. package/src/proxy.ts +43 -0
  316. package/src/stores/use-app-store.ts +468 -0
  317. package/src/stores/use-chat-store.ts +323 -0
  318. package/src/types/index.ts +621 -0
  319. package/tsconfig.json +34 -0
@@ -0,0 +1,264 @@
1
+ import { z } from 'zod'
2
+ import { tool, type StructuredToolInterface } from '@langchain/core/tools'
3
+ import fs from 'fs'
4
+ import path from 'path'
5
+ import { UPLOAD_DIR } from '../storage'
6
+ import type { ToolBuildContext } from './context'
7
+ import { safePath, truncate, listDirRecursive, MAX_OUTPUT, MAX_FILE } from './context'
8
+
9
+ export function buildFileTools(bctx: ToolBuildContext): StructuredToolInterface[] {
10
+ const tools: StructuredToolInterface[] = []
11
+
12
+ const filesEnabled = bctx.hasTool('files')
13
+ const canReadFiles = filesEnabled || bctx.hasTool('read_file')
14
+ const canWriteFiles = filesEnabled || bctx.hasTool('write_file')
15
+ const canListFiles = filesEnabled || bctx.hasTool('list_files')
16
+ const canSendFiles = filesEnabled || bctx.hasTool('send_file')
17
+ const canCopyFiles = filesEnabled || bctx.hasTool('copy_file')
18
+ const canMoveFiles = filesEnabled || bctx.hasTool('move_file')
19
+ const canDeleteFiles = bctx.hasTool('delete_file')
20
+
21
+ if (canReadFiles) {
22
+ tools.push(
23
+ tool(
24
+ async ({ filePath }) => {
25
+ try {
26
+ const resolved = safePath(bctx.cwd, filePath)
27
+ const content = fs.readFileSync(resolved, 'utf-8')
28
+ return truncate(content, MAX_FILE)
29
+ } catch (err: any) {
30
+ return `Error reading file: ${err.message}`
31
+ }
32
+ },
33
+ {
34
+ name: 'read_file',
35
+ description: 'Read a file from the session working directory.',
36
+ schema: z.object({
37
+ filePath: z.string().describe('Relative path to the file'),
38
+ }),
39
+ },
40
+ ),
41
+ )
42
+ }
43
+
44
+ if (canWriteFiles) {
45
+ tools.push(
46
+ tool(
47
+ async ({ filePath, content }) => {
48
+ try {
49
+ const resolved = safePath(bctx.cwd, filePath)
50
+ fs.mkdirSync(path.dirname(resolved), { recursive: true })
51
+ fs.writeFileSync(resolved, content, 'utf-8')
52
+ return `File written: ${filePath} (${content.length} bytes)`
53
+ } catch (err: any) {
54
+ return `Error writing file: ${err.message}`
55
+ }
56
+ },
57
+ {
58
+ name: 'write_file',
59
+ description: 'Write content to a file in the session working directory. Creates directories if needed.',
60
+ schema: z.object({
61
+ filePath: z.string().describe('Relative path to the file'),
62
+ content: z.string().describe('The content to write'),
63
+ }),
64
+ },
65
+ ),
66
+ )
67
+ }
68
+
69
+ if (canListFiles) {
70
+ tools.push(
71
+ tool(
72
+ async ({ dirPath }) => {
73
+ try {
74
+ const resolved = safePath(bctx.cwd, dirPath || '.')
75
+ const tree = listDirRecursive(resolved, 0, 3)
76
+ return tree.length ? tree.join('\n') : '(empty directory)'
77
+ } catch (err: any) {
78
+ return `Error listing files: ${err.message}`
79
+ }
80
+ },
81
+ {
82
+ name: 'list_files',
83
+ description: 'List files in the session working directory recursively (max depth 3).',
84
+ schema: z.object({
85
+ dirPath: z.string().optional().describe('Relative path to list (defaults to working directory)'),
86
+ }),
87
+ },
88
+ ),
89
+ )
90
+ }
91
+
92
+ if (canCopyFiles) {
93
+ tools.push(
94
+ tool(
95
+ async ({ sourcePath, destinationPath, overwrite }) => {
96
+ try {
97
+ const source = safePath(bctx.cwd, sourcePath)
98
+ const destination = safePath(bctx.cwd, destinationPath)
99
+ if (!fs.existsSync(source)) return `Error: source file not found: ${sourcePath}`
100
+ const sourceStat = fs.statSync(source)
101
+ if (sourceStat.isDirectory()) return `Error: source must be a file (directories are not supported by copy_file).`
102
+ if (fs.existsSync(destination) && !overwrite) return `Error: destination already exists: ${destinationPath} (set overwrite=true to replace).`
103
+ fs.mkdirSync(path.dirname(destination), { recursive: true })
104
+ fs.copyFileSync(source, destination)
105
+ return `File copied: ${sourcePath} -> ${destinationPath}`
106
+ } catch (err: any) {
107
+ return `Error copying file: ${err.message}`
108
+ }
109
+ },
110
+ {
111
+ name: 'copy_file',
112
+ description: 'Copy a file to a new location in the working directory.',
113
+ schema: z.object({
114
+ sourcePath: z.string().describe('Source file path (relative to working directory)'),
115
+ destinationPath: z.string().describe('Destination file path (relative to working directory)'),
116
+ overwrite: z.boolean().optional().describe('Overwrite destination if it exists (default false)'),
117
+ }),
118
+ },
119
+ ),
120
+ )
121
+ }
122
+
123
+ if (canMoveFiles) {
124
+ tools.push(
125
+ tool(
126
+ async ({ sourcePath, destinationPath, overwrite }) => {
127
+ try {
128
+ const source = safePath(bctx.cwd, sourcePath)
129
+ const destination = safePath(bctx.cwd, destinationPath)
130
+ if (!fs.existsSync(source)) return `Error: source file not found: ${sourcePath}`
131
+ const sourceStat = fs.statSync(source)
132
+ if (sourceStat.isDirectory()) return `Error: source must be a file (directories are not supported by move_file).`
133
+ if (fs.existsSync(destination) && !overwrite) return `Error: destination already exists: ${destinationPath} (set overwrite=true to replace).`
134
+ fs.mkdirSync(path.dirname(destination), { recursive: true })
135
+ if (fs.existsSync(destination) && overwrite) fs.unlinkSync(destination)
136
+ fs.renameSync(source, destination)
137
+ return `File moved: ${sourcePath} -> ${destinationPath}`
138
+ } catch (err: any) {
139
+ return `Error moving file: ${err.message}`
140
+ }
141
+ },
142
+ {
143
+ name: 'move_file',
144
+ description: 'Move (rename) a file to a new location in the working directory.',
145
+ schema: z.object({
146
+ sourcePath: z.string().describe('Source file path (relative to working directory)'),
147
+ destinationPath: z.string().describe('Destination file path (relative to working directory)'),
148
+ overwrite: z.boolean().optional().describe('Overwrite destination if it exists (default false)'),
149
+ }),
150
+ },
151
+ ),
152
+ )
153
+ }
154
+
155
+ if (canDeleteFiles) {
156
+ tools.push(
157
+ tool(
158
+ async ({ filePath, recursive, force }) => {
159
+ try {
160
+ const resolved = safePath(bctx.cwd, filePath)
161
+ const root = path.resolve(bctx.cwd)
162
+ if (resolved === root) return 'Error: refusing to delete the session working directory root.'
163
+ if (!fs.existsSync(resolved)) {
164
+ return force ? `Path already absent: ${filePath}` : `Error: path not found: ${filePath}`
165
+ }
166
+ const stat = fs.statSync(resolved)
167
+ if (stat.isDirectory() && !recursive) {
168
+ return 'Error: target is a directory. Set recursive=true to delete directories.'
169
+ }
170
+ fs.rmSync(resolved, { recursive: !!recursive, force: !!force })
171
+ return `Deleted: ${filePath}`
172
+ } catch (err: any) {
173
+ return `Error deleting file: ${err.message}`
174
+ }
175
+ },
176
+ {
177
+ name: 'delete_file',
178
+ description: 'Delete a file or directory from the working directory. Disabled by default and must be explicitly enabled.',
179
+ schema: z.object({
180
+ filePath: z.string().describe('Path to delete (relative to working directory)'),
181
+ recursive: z.boolean().optional().describe('Required for deleting directories'),
182
+ force: z.boolean().optional().describe('Ignore missing paths and force deletion where possible'),
183
+ }),
184
+ },
185
+ ),
186
+ )
187
+ }
188
+
189
+ if (canSendFiles) {
190
+ tools.push(
191
+ tool(
192
+ async ({ filePath: rawPath }) => {
193
+ try {
194
+ // Resolve relative to cwd, but also allow absolute paths
195
+ const resolved = path.isAbsolute(rawPath) ? rawPath : path.resolve(bctx.cwd, rawPath)
196
+ if (!fs.existsSync(resolved)) return `Error: file not found: ${rawPath}`
197
+ const stat = fs.statSync(resolved)
198
+ if (stat.isDirectory()) return `Error: cannot send a directory. Send individual files instead.`
199
+ if (stat.size > 100 * 1024 * 1024) return `Error: file too large (${(stat.size / 1024 / 1024).toFixed(1)}MB). Max 100MB.`
200
+
201
+ const ext = path.extname(resolved).slice(1).toLowerCase()
202
+ const basename = path.basename(resolved)
203
+ const filename = `${Date.now()}-${basename}`
204
+ const dest = path.join(UPLOAD_DIR, filename)
205
+ fs.copyFileSync(resolved, dest)
206
+
207
+ const IMAGE_EXTS = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp', 'ico']
208
+ const VIDEO_EXTS = ['mp4', 'webm', 'mov', 'avi', 'mkv']
209
+
210
+ if (IMAGE_EXTS.includes(ext)) {
211
+ return `![${basename}](/api/uploads/${filename})`
212
+ } else if (VIDEO_EXTS.includes(ext)) {
213
+ return `![${basename}](/api/uploads/${filename})`
214
+ } else {
215
+ return `[Download ${basename}](/api/uploads/${filename})`
216
+ }
217
+ } catch (err: any) {
218
+ return `Error sending file: ${err.message}`
219
+ }
220
+ },
221
+ {
222
+ name: 'send_file',
223
+ description: 'Send a file to the user so they can view or download it in the chat. Works with images, videos, PDFs, documents, and any other file type. The file will appear inline for images/videos, or as a download link for other types.',
224
+ schema: z.object({
225
+ filePath: z.string().describe('Path to the file (relative to working directory, or absolute)'),
226
+ }),
227
+ },
228
+ ),
229
+ )
230
+ }
231
+
232
+ if (bctx.hasTool('edit_file')) {
233
+ tools.push(
234
+ tool(
235
+ async ({ filePath, oldText, newText }) => {
236
+ try {
237
+ const resolved = safePath(bctx.cwd, filePath)
238
+ if (!fs.existsSync(resolved)) return `Error: File not found: ${filePath}`
239
+ const content = fs.readFileSync(resolved, 'utf-8')
240
+ const count = content.split(oldText).length - 1
241
+ if (count === 0) return `Error: oldText not found in ${filePath}`
242
+ if (count > 1) return `Error: oldText found ${count} times in ${filePath}. Make it more specific.`
243
+ const updated = content.replace(oldText, newText)
244
+ fs.writeFileSync(resolved, updated, 'utf-8')
245
+ return `Successfully edited ${filePath}`
246
+ } catch (err: any) {
247
+ return `Error editing file: ${err.message}`
248
+ }
249
+ },
250
+ {
251
+ name: 'edit_file',
252
+ description: 'Search and replace text in a file. The oldText must match exactly once in the file.',
253
+ schema: z.object({
254
+ filePath: z.string().describe('Relative path to the file'),
255
+ oldText: z.string().describe('Exact text to find (must be unique in the file)'),
256
+ newText: z.string().describe('Text to replace it with'),
257
+ }),
258
+ },
259
+ ),
260
+ )
261
+ }
262
+
263
+ return tools
264
+ }
@@ -0,0 +1,164 @@
1
+ import { z } from 'zod'
2
+ import { tool, type StructuredToolInterface } from '@langchain/core/tools'
3
+ import { loadSettings, loadSessions, saveSessions, loadMcpServers } from '../storage'
4
+ import { loadRuntimeSettings } from '../runtime-settings'
5
+ import { log } from '../logger'
6
+ import { resolveSessionToolPolicy } from '../tool-capability-policy'
7
+ import type { ToolContext, SessionToolsResult, ToolBuildContext } from './context'
8
+ import { buildShellTools } from './shell'
9
+ import { buildFileTools } from './file'
10
+ import { buildDelegateTools } from './delegate'
11
+ import { buildWebTools, sweepOrphanedBrowsers, cleanupSessionBrowser, getActiveBrowserCount, hasActiveBrowser } from './web'
12
+ import { buildMemoryTools } from './memory'
13
+ import { buildCrudTools } from './crud'
14
+ import { buildSessionInfoTools } from './session-info'
15
+ import { buildConnectorTools } from './connector'
16
+ import { buildContextTools } from './context-mgmt'
17
+
18
+ export type { ToolContext, SessionToolsResult }
19
+ export { sweepOrphanedBrowsers, cleanupSessionBrowser, getActiveBrowserCount, hasActiveBrowser }
20
+
21
+ export async function buildSessionTools(cwd: string, enabledTools: string[], ctx?: ToolContext): Promise<SessionToolsResult> {
22
+ const tools: StructuredToolInterface[] = []
23
+ const cleanupFns: (() => Promise<void>)[] = []
24
+ const runtime = loadRuntimeSettings()
25
+ const commandTimeoutMs = runtime.shellCommandTimeoutMs
26
+ const claudeTimeoutMs = runtime.claudeCodeTimeoutMs
27
+ const cliProcessTimeoutMs = runtime.cliProcessTimeoutMs
28
+ const appSettings = loadSettings()
29
+ const toolPolicy = resolveSessionToolPolicy(enabledTools, appSettings)
30
+ const activeTools = toolPolicy.enabledTools
31
+ const hasTool = (toolName: string) => activeTools.includes(toolName)
32
+
33
+ if (toolPolicy.blockedTools.length > 0) {
34
+ log.info('session-tools', 'Capability policy blocked tool families', {
35
+ sessionId: ctx?.sessionId || null,
36
+ agentId: ctx?.agentId || null,
37
+ blockedTools: toolPolicy.blockedTools.map((entry) => `${entry.tool}:${entry.reason}`),
38
+ })
39
+ }
40
+
41
+ const resolveCurrentSession = (): any | null => {
42
+ if (!ctx?.sessionId) return null
43
+ const sessions = loadSessions()
44
+ return sessions[ctx.sessionId] || null
45
+ }
46
+
47
+ const readStoredDelegateResumeId = (key: 'claudeCode' | 'codex' | 'opencode'): string | null => {
48
+ const session = resolveCurrentSession()
49
+ if (!session?.delegateResumeIds || typeof session.delegateResumeIds !== 'object') return null
50
+ const raw = session.delegateResumeIds[key]
51
+ return typeof raw === 'string' && raw.trim() ? raw.trim() : null
52
+ }
53
+
54
+ const persistDelegateResumeId = (key: 'claudeCode' | 'codex' | 'opencode', resumeId: string | null | undefined): void => {
55
+ const normalized = typeof resumeId === 'string' ? resumeId.trim() : ''
56
+ if (!normalized || !ctx?.sessionId) return
57
+ const sessions = loadSessions()
58
+ const target = sessions[ctx.sessionId]
59
+ if (!target) return
60
+ const current = (target.delegateResumeIds && typeof target.delegateResumeIds === 'object')
61
+ ? target.delegateResumeIds
62
+ : {}
63
+ target.delegateResumeIds = {
64
+ ...current,
65
+ [key]: normalized,
66
+ }
67
+ target.updatedAt = Date.now()
68
+ sessions[ctx.sessionId] = target
69
+ saveSessions(sessions)
70
+ }
71
+
72
+ const bctx: ToolBuildContext = {
73
+ cwd,
74
+ ctx,
75
+ hasTool,
76
+ cleanupFns,
77
+ commandTimeoutMs,
78
+ claudeTimeoutMs,
79
+ cliProcessTimeoutMs,
80
+ persistDelegateResumeId,
81
+ readStoredDelegateResumeId,
82
+ resolveCurrentSession,
83
+ activeTools,
84
+ }
85
+
86
+ tools.push(
87
+ ...buildShellTools(bctx),
88
+ ...buildFileTools(bctx),
89
+ ...buildDelegateTools(bctx),
90
+ ...buildWebTools(bctx),
91
+ ...buildMemoryTools(bctx),
92
+ ...buildCrudTools(bctx),
93
+ ...buildSessionInfoTools(bctx),
94
+ ...buildConnectorTools(bctx),
95
+ ...buildContextTools(bctx),
96
+ )
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // MCP server tools — first-class injection (each MCP tool becomes its own LangChain tool)
100
+ // ---------------------------------------------------------------------------
101
+ const disabledMcpToolNames = new Set<string>(ctx?.mcpDisabledTools ?? [])
102
+
103
+ if (ctx?.mcpServerIds?.length) {
104
+ const mcpConnections: Array<{ client: any; transport: any }> = []
105
+ const allMcpServers = loadMcpServers()
106
+
107
+ for (const serverId of ctx.mcpServerIds) {
108
+ const config = allMcpServers[serverId]
109
+ if (!config) continue
110
+ try {
111
+ const { connectMcpServer, mcpToolsToLangChain } = await import('../mcp-client')
112
+ const conn = await connectMcpServer(config)
113
+ mcpConnections.push(conn)
114
+ const mcpLcTools = await mcpToolsToLangChain(conn.client, config.name)
115
+ for (const t of mcpLcTools) {
116
+ if (!disabledMcpToolNames.has(t.name)) {
117
+ tools.push(t)
118
+ }
119
+ }
120
+ } catch (err: any) {
121
+ log.warn('session-tools', `Failed to connect MCP server "${config.name}"`, { serverId, error: err.message })
122
+ }
123
+ }
124
+
125
+ // Register cleanup for all MCP connections
126
+ cleanupFns.push(async () => {
127
+ const { disconnectMcpServer } = await import('../mcp-client')
128
+ for (const conn of mcpConnections) {
129
+ await disconnectMcpServer(conn.client, conn.transport)
130
+ }
131
+ })
132
+ }
133
+
134
+ // request_tool_access: always available
135
+ tools.push(
136
+ tool(
137
+ async ({ toolId, reason }) => {
138
+ return JSON.stringify({
139
+ type: 'tool_request',
140
+ toolId,
141
+ reason,
142
+ message: `Tool access request sent to user for "${toolId}". Wait for the user to grant access before trying to use it.`,
143
+ })
144
+ },
145
+ {
146
+ name: 'request_tool_access',
147
+ description: 'Request access to a tool that is currently disabled. The user will be prompted to grant access. Use this when you need a tool from the disabled tools list.',
148
+ schema: z.object({
149
+ toolId: z.string().describe('The tool ID to request access for (e.g. manage_tasks, shell, claude_code)'),
150
+ reason: z.string().describe('Brief explanation of why you need this tool'),
151
+ }),
152
+ },
153
+ ),
154
+ )
155
+
156
+ return {
157
+ tools,
158
+ cleanup: async () => {
159
+ for (const fn of cleanupFns) {
160
+ try { await fn() } catch { /* ignore */ }
161
+ }
162
+ },
163
+ }
164
+ }
@@ -0,0 +1,230 @@
1
+ import { z } from 'zod'
2
+ import { tool, type StructuredToolInterface } from '@langchain/core/tools'
3
+ import fs from 'fs'
4
+ import crypto from 'crypto'
5
+ import { getMemoryDb, getMemoryLookupLimits, storeMemoryImageAsset } from '../memory-db'
6
+ import { loadSettings } from '../storage'
7
+ import type { ToolBuildContext } from './context'
8
+
9
+ export function buildMemoryTools(bctx: ToolBuildContext): StructuredToolInterface[] {
10
+ const tools: StructuredToolInterface[] = []
11
+ const { ctx, hasTool } = bctx
12
+
13
+ if (hasTool('memory')) {
14
+ const memDb = getMemoryDb()
15
+
16
+ tools.push(
17
+ tool(
18
+ async ({ action, key, value, category, query, scope, filePaths, references, project, imagePath, linkedMemoryIds, depth, linkedLimit, targetIds, tags }) => {
19
+ try {
20
+ const scopeMode = scope || 'auto'
21
+ const currentAgentId = ctx?.agentId || null
22
+ const canAccessMemory = (m: any) => !m?.agentId || m.agentId === currentAgentId
23
+ const filterScope = (rows: any[]) => {
24
+ if (scopeMode === 'shared') return rows.filter((m) => !m.agentId)
25
+ if (scopeMode === 'agent') return rows.filter((m) => currentAgentId && m.agentId === currentAgentId)
26
+ return rows.filter(canAccessMemory)
27
+ }
28
+
29
+ const limits = getMemoryLookupLimits(loadSettings())
30
+ const requestedDepth = typeof depth === 'number' ? depth : 0
31
+ const requestedLinkedLimit = typeof linkedLimit === 'number' ? linkedLimit : limits.maxLinkedExpansion
32
+ const effectiveDepth = Math.max(0, Math.min(requestedDepth, limits.maxDepth))
33
+ const effectiveLinkedLimit = Math.max(0, Math.min(requestedLinkedLimit, limits.maxLinkedExpansion))
34
+ const maxPerLookup = limits.maxPerLookup
35
+
36
+ const normalizedLegacyRefs = Array.isArray(filePaths)
37
+ ? filePaths.map((f: any) => ({
38
+ type: f.kind === 'project' ? 'project' : (f.kind === 'folder' ? 'folder' : 'file'),
39
+ path: f.path,
40
+ projectRoot: f.projectRoot,
41
+ projectName: f.projectName,
42
+ note: f.contextSnippet,
43
+ timestamp: typeof f.timestamp === 'number' ? f.timestamp : Date.now(),
44
+ }))
45
+ : []
46
+ const normalizedRefs = Array.isArray(references) ? references : []
47
+ if (project?.rootPath) {
48
+ normalizedRefs.push({
49
+ type: 'project',
50
+ path: project.rootPath,
51
+ projectRoot: project.rootPath,
52
+ projectName: project.name,
53
+ title: project.name,
54
+ note: project.note,
55
+ timestamp: Date.now(),
56
+ })
57
+ }
58
+ const mergedRefs = [...normalizedLegacyRefs, ...normalizedRefs]
59
+
60
+ const formatEntry = (m: any) => {
61
+ let line = `[${m.id}] (${m.agentId ? `agent:${m.agentId}` : 'shared'}) ${m.category}/${m.title}: ${m.content}`
62
+ if (m.references?.length) {
63
+ line += `\n refs: ${m.references.map((r: any) => {
64
+ const core = r.path || r.title || r.type
65
+ const projectMeta = r.projectName ? ` @${r.projectName}` : ''
66
+ const existsMeta = typeof r.exists === 'boolean' ? (r.exists ? ' (exists)' : ' (missing)') : ''
67
+ return `${r.type}:${core}${projectMeta}${existsMeta}`
68
+ }).join(', ')}`
69
+ } else if (m.filePaths?.length) {
70
+ line += `\n files: ${m.filePaths.map((f: any) => `${f.path}${f.contextSnippet ? ` (${f.contextSnippet})` : ''}`).join(', ')}`
71
+ }
72
+ if (m.image?.path || m.imagePath) line += `\n image: ${m.image?.path || m.imagePath}`
73
+ if (m.linkedMemoryIds?.length) line += `\n linked: ${m.linkedMemoryIds.join(', ')}`
74
+ return line
75
+ }
76
+
77
+ if (action === 'store') {
78
+ let storedImage: any = null
79
+ if (imagePath) {
80
+ if (!fs.existsSync(imagePath)) {
81
+ return `Error: image file not found: ${imagePath}`
82
+ }
83
+ try {
84
+ storedImage = await storeMemoryImageAsset(imagePath, crypto.randomBytes(6).toString('hex'))
85
+ } catch {
86
+ return `Error: failed to process image at ${imagePath}`
87
+ }
88
+ }
89
+
90
+ const entry = memDb.add({
91
+ agentId: scopeMode === 'shared' ? null : currentAgentId,
92
+ sessionId: ctx?.sessionId || null,
93
+ category: category || 'note',
94
+ title: key,
95
+ content: value || '',
96
+ references: mergedRefs as any,
97
+ filePaths: filePaths as any,
98
+ image: storedImage,
99
+ imagePath: storedImage?.path || undefined,
100
+ linkedMemoryIds,
101
+ })
102
+ const memoryScope = entry.agentId ? 'agent' : 'shared'
103
+ let result = `Stored ${memoryScope} memory "${key}" (id: ${entry.id})`
104
+ if (mergedRefs.length) result += ` with ${mergedRefs.length} reference(s)`
105
+ if (storedImage?.path) result += ` with image`
106
+ if (linkedMemoryIds?.length) result += ` linked to ${linkedMemoryIds.length} memor${linkedMemoryIds.length === 1 ? 'y' : 'ies'}`
107
+ return result
108
+ }
109
+ if (action === 'get') {
110
+ if (effectiveDepth > 0) {
111
+ const result = memDb.getWithLinked(key, effectiveDepth, maxPerLookup, effectiveLinkedLimit)
112
+ if (!result) return `Memory not found: ${key}`
113
+ const accessible = result.entries.filter(canAccessMemory)
114
+ if (!accessible.length) return 'Error: you do not have access to that memory.'
115
+ let output = accessible.map(formatEntry).join('\n---\n')
116
+ if (result.truncated) output += `\n\n[Results truncated at ${maxPerLookup} memories / ${effectiveLinkedLimit} linked expansions]`
117
+ return output
118
+ }
119
+ const found = memDb.get(key)
120
+ if (!found) return `Memory not found: ${key}`
121
+ if (!canAccessMemory(found)) return 'Error: you do not have access to that memory.'
122
+ return formatEntry(found)
123
+ }
124
+ if (action === 'search') {
125
+ if (effectiveDepth > 0) {
126
+ const result = memDb.searchWithLinked(query || key, undefined, effectiveDepth, maxPerLookup, effectiveLinkedLimit)
127
+ const accessible = filterScope(result.entries)
128
+ if (!accessible.length) return 'No memories found.'
129
+ let output = accessible.map(formatEntry).join('\n')
130
+ if (result.truncated) output += `\n\n[Results truncated at ${maxPerLookup} memories / ${effectiveLinkedLimit} linked expansions]`
131
+ return output
132
+ }
133
+ const results = filterScope(memDb.search(query || key))
134
+ if (!results.length) return 'No memories found.'
135
+ return results.slice(0, maxPerLookup).map(formatEntry).join('\n')
136
+ }
137
+ if (action === 'list') {
138
+ const results = filterScope(memDb.list(undefined, maxPerLookup))
139
+ if (!results.length) return 'No memories stored yet.'
140
+ return results.map(formatEntry).join('\n')
141
+ }
142
+ if (action === 'delete') {
143
+ const found = memDb.get(key)
144
+ if (!found) return `Memory not found: ${key}`
145
+ if (!canAccessMemory(found)) return 'Error: you do not have access to that memory.'
146
+ memDb.delete(key)
147
+ return `Deleted memory "${key}"`
148
+ }
149
+ if (action === 'link') {
150
+ if (!targetIds?.length) return 'Error: targetIds required for link action.'
151
+ const result = memDb.link(key, targetIds, true)
152
+ if (!result) return `Memory not found: ${key}`
153
+ return `Linked memory "${key}" to ${targetIds.length} memor${targetIds.length === 1 ? 'y' : 'ies'} (bidirectional): ${targetIds.join(', ')}`
154
+ }
155
+ if (action === 'unlink') {
156
+ if (!targetIds?.length) return 'Error: targetIds required for unlink action.'
157
+ const result = memDb.unlink(key, targetIds, true)
158
+ if (!result) return `Memory not found: ${key}`
159
+ return `Unlinked ${targetIds.length} memor${targetIds.length === 1 ? 'y' : 'ies'} from "${key}" (bidirectional)`
160
+ }
161
+ if (action === 'knowledge_store') {
162
+ const { addKnowledge } = await import('../memory-db')
163
+ if (!value) return 'Error: value (content) is required for knowledge_store'
164
+ const entry = addKnowledge({
165
+ title: key || 'Untitled',
166
+ content: value,
167
+ tags: tags,
168
+ createdByAgentId: ctx?.agentId || null,
169
+ createdBySessionId: ctx?.sessionId || null,
170
+ })
171
+ return `Knowledge stored: "${entry.title}" (id: ${entry.id})`
172
+ }
173
+ if (action === 'knowledge_search') {
174
+ const { searchKnowledge } = await import('../memory-db')
175
+ const results = searchKnowledge(query || key || '', tags, 10)
176
+ if (!results.length) return 'No knowledge entries found.'
177
+ return results.map(r => `[${r.id}] ${r.title}: ${r.content.slice(0, 200)}`).join('\n---\n')
178
+ }
179
+ return `Unknown action "${action}". Use: store, get, search, list, delete, link, unlink, knowledge_store, or knowledge_search.`
180
+ } catch (err: any) {
181
+ return `Error: ${err.message}`
182
+ }
183
+ },
184
+ {
185
+ name: 'memory_tool',
186
+ description: 'Store and retrieve long-term memories that persist across sessions. Memories can be shared or agent-scoped. Supports file references, image attachments, and linking memories together with depth traversal. Also supports a cross-agent knowledge base via "knowledge_store" and "knowledge_search". Use "store", "get", "search", "list", "delete", "link", "unlink", "knowledge_store", or "knowledge_search".',
187
+ schema: z.object({
188
+ action: z.enum(['store', 'get', 'search', 'list', 'delete', 'link', 'unlink', 'knowledge_store', 'knowledge_search']).describe('The action to perform'),
189
+ key: z.string().describe('For store: memory title. For get/delete/link/unlink: memory ID. For search: optional query fallback.'),
190
+ value: z.string().optional().describe('The memory content (for store action)'),
191
+ category: z.string().optional().describe('Category like "note", "fact", "preference", "project", "identity" (for store action, defaults to "note")'),
192
+ query: z.string().optional().describe('Search query (alternative to key for search action)'),
193
+ scope: z.enum(['auto', 'shared', 'agent']).optional().describe('Scope hint: auto (shared + own), shared, or agent'),
194
+ filePaths: z.array(z.object({
195
+ path: z.string().describe('File or folder path'),
196
+ contextSnippet: z.string().optional().describe('Brief context about this file reference'),
197
+ kind: z.enum(['file', 'folder', 'project']).optional().describe('Reference type for legacy filePaths compatibility'),
198
+ projectRoot: z.string().optional().describe('Optional project root path'),
199
+ projectName: z.string().optional().describe('Optional project display name'),
200
+ exists: z.boolean().optional().describe('Optional known existence state'),
201
+ timestamp: z.number().describe('When this file was referenced'),
202
+ })).optional().describe('File/folder references to attach to the memory (for store action)'),
203
+ references: z.array(z.object({
204
+ type: z.enum(['project', 'folder', 'file', 'task', 'session', 'url']),
205
+ path: z.string().optional(),
206
+ projectRoot: z.string().optional(),
207
+ projectName: z.string().optional(),
208
+ title: z.string().optional(),
209
+ note: z.string().optional(),
210
+ timestamp: z.number().optional(),
211
+ })).optional().describe('Structured references attached to the memory (preferred over filePaths).'),
212
+ project: z.object({
213
+ rootPath: z.string().describe('Project/workspace root path'),
214
+ name: z.string().optional().describe('Optional project display name'),
215
+ note: z.string().optional().describe('Optional note about the project context'),
216
+ }).optional().describe('Shortcut to add a project reference on store action.'),
217
+ imagePath: z.string().optional().describe('Path to an image file to attach (will be compressed and stored). For store action.'),
218
+ linkedMemoryIds: z.array(z.string()).optional().describe('IDs of other memories to link to (for store action)'),
219
+ depth: z.number().optional().describe('How deep to traverse linked memories (for get/search). Respects configured maxDepth limit. Default: 0 (no traversal).'),
220
+ linkedLimit: z.number().optional().describe('Max linked memories expanded during traversal. Respects configured server cap.'),
221
+ targetIds: z.array(z.string()).optional().describe('Memory IDs to link/unlink (for link/unlink actions)'),
222
+ tags: z.array(z.string()).optional().describe('Tags for categorizing knowledge entries'),
223
+ }),
224
+ },
225
+ ),
226
+ )
227
+ }
228
+
229
+ return tools
230
+ }