@swarmclawai/swarmclaw 0.6.7 → 0.7.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 (203) hide show
  1. package/README.md +82 -39
  2. package/next.config.ts +31 -6
  3. package/package.json +3 -2
  4. package/src/app/api/agents/[id]/thread/route.ts +1 -0
  5. package/src/app/api/agents/route.ts +19 -5
  6. package/src/app/api/approvals/route.ts +22 -0
  7. package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
  8. package/src/app/api/clawhub/install/route.ts +2 -2
  9. package/src/app/api/eval/run/route.ts +37 -0
  10. package/src/app/api/eval/scenarios/route.ts +24 -0
  11. package/src/app/api/eval/suite/route.ts +29 -0
  12. package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
  13. package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
  14. package/src/app/api/memory/graph/route.ts +46 -0
  15. package/src/app/api/memory/route.ts +36 -5
  16. package/src/app/api/notifications/route.ts +3 -0
  17. package/src/app/api/plugins/install/route.ts +57 -5
  18. package/src/app/api/plugins/marketplace/route.ts +73 -22
  19. package/src/app/api/plugins/route.ts +61 -1
  20. package/src/app/api/plugins/ui/route.ts +34 -0
  21. package/src/app/api/sessions/[id]/checkpoints/route.ts +31 -0
  22. package/src/app/api/sessions/[id]/restore/route.ts +36 -0
  23. package/src/app/api/settings/route.ts +62 -0
  24. package/src/app/api/setup/doctor/route.ts +22 -5
  25. package/src/app/api/souls/[id]/route.ts +65 -0
  26. package/src/app/api/souls/route.ts +70 -0
  27. package/src/app/api/tasks/[id]/approve/route.ts +4 -3
  28. package/src/app/api/tasks/[id]/route.ts +16 -3
  29. package/src/app/api/tasks/route.ts +10 -2
  30. package/src/app/api/usage/route.ts +9 -2
  31. package/src/app/globals.css +27 -0
  32. package/src/app/page.tsx +10 -5
  33. package/src/cli/index.js +37 -0
  34. package/src/components/activity/activity-feed.tsx +9 -2
  35. package/src/components/agents/agent-avatar.tsx +5 -1
  36. package/src/components/agents/agent-card.tsx +55 -9
  37. package/src/components/agents/agent-sheet.tsx +112 -34
  38. package/src/components/agents/inspector-panel.tsx +1 -1
  39. package/src/components/agents/soul-library-picker.tsx +84 -13
  40. package/src/components/auth/access-key-gate.tsx +63 -54
  41. package/src/components/auth/user-picker.tsx +37 -32
  42. package/src/components/chat/activity-moment.tsx +2 -0
  43. package/src/components/chat/chat-area.tsx +11 -0
  44. package/src/components/chat/chat-header.tsx +69 -25
  45. package/src/components/chat/chat-tool-toggles.tsx +2 -2
  46. package/src/components/chat/checkpoint-timeline.tsx +112 -0
  47. package/src/components/chat/code-block.tsx +3 -1
  48. package/src/components/chat/exec-approval-card.tsx +8 -1
  49. package/src/components/chat/message-bubble.tsx +164 -4
  50. package/src/components/chat/message-list.tsx +46 -4
  51. package/src/components/chat/session-approval-card.tsx +80 -0
  52. package/src/components/chat/session-debug-panel.tsx +106 -84
  53. package/src/components/chat/streaming-bubble.tsx +6 -5
  54. package/src/components/chat/task-approval-card.tsx +78 -0
  55. package/src/components/chat/thinking-indicator.tsx +48 -12
  56. package/src/components/chat/tool-call-bubble.tsx +3 -0
  57. package/src/components/chat/tool-request-banner.tsx +39 -20
  58. package/src/components/chatrooms/chatroom-list.tsx +11 -4
  59. package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
  60. package/src/components/connectors/connector-list.tsx +33 -11
  61. package/src/components/connectors/connector-sheet.tsx +37 -7
  62. package/src/components/home/home-view.tsx +54 -24
  63. package/src/components/input/chat-input.tsx +22 -1
  64. package/src/components/knowledge/knowledge-list.tsx +17 -18
  65. package/src/components/knowledge/knowledge-sheet.tsx +9 -5
  66. package/src/components/layout/app-layout.tsx +87 -19
  67. package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
  68. package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
  69. package/src/components/memory/memory-browser.tsx +73 -45
  70. package/src/components/memory/memory-graph-view.tsx +203 -0
  71. package/src/components/memory/memory-list.tsx +20 -13
  72. package/src/components/plugins/plugin-list.tsx +214 -60
  73. package/src/components/plugins/plugin-sheet.tsx +119 -24
  74. package/src/components/projects/project-list.tsx +17 -9
  75. package/src/components/providers/provider-list.tsx +21 -6
  76. package/src/components/providers/provider-sheet.tsx +42 -25
  77. package/src/components/runs/run-list.tsx +17 -13
  78. package/src/components/schedules/schedule-card.tsx +10 -3
  79. package/src/components/schedules/schedule-list.tsx +2 -2
  80. package/src/components/schedules/schedule-sheet.tsx +28 -9
  81. package/src/components/secrets/secret-sheet.tsx +7 -2
  82. package/src/components/secrets/secrets-list.tsx +18 -5
  83. package/src/components/sessions/new-session-sheet.tsx +183 -376
  84. package/src/components/sessions/session-card.tsx +10 -2
  85. package/src/components/settings/gateway-connection-panel.tsx +9 -8
  86. package/src/components/shared/command-palette.tsx +13 -5
  87. package/src/components/shared/empty-state.tsx +20 -8
  88. package/src/components/shared/hint-tip.tsx +31 -0
  89. package/src/components/shared/notification-center.tsx +134 -86
  90. package/src/components/shared/profile-sheet.tsx +4 -0
  91. package/src/components/shared/settings/plugin-manager.tsx +360 -135
  92. package/src/components/shared/settings/section-capability-policy.tsx +3 -3
  93. package/src/components/shared/settings/section-runtime-loop.tsx +149 -4
  94. package/src/components/skills/clawhub-browser.tsx +1 -0
  95. package/src/components/skills/skill-list.tsx +31 -12
  96. package/src/components/skills/skill-sheet.tsx +20 -7
  97. package/src/components/tasks/approvals-panel.tsx +224 -0
  98. package/src/components/tasks/task-board.tsx +20 -12
  99. package/src/components/tasks/task-card.tsx +21 -7
  100. package/src/components/tasks/task-column.tsx +4 -3
  101. package/src/components/tasks/task-list.tsx +1 -1
  102. package/src/components/tasks/task-sheet.tsx +130 -1
  103. package/src/components/ui/dialog.tsx +1 -0
  104. package/src/components/ui/sheet.tsx +1 -0
  105. package/src/components/usage/metrics-dashboard.tsx +72 -48
  106. package/src/components/wallets/wallet-panel.tsx +65 -41
  107. package/src/components/wallets/wallet-section.tsx +9 -3
  108. package/src/components/webhooks/webhook-list.tsx +21 -12
  109. package/src/components/webhooks/webhook-sheet.tsx +13 -3
  110. package/src/lib/approval-display.test.ts +45 -0
  111. package/src/lib/approval-display.ts +62 -0
  112. package/src/lib/clipboard.ts +38 -0
  113. package/src/lib/memory.ts +8 -0
  114. package/src/lib/providers/claude-cli.ts +5 -3
  115. package/src/lib/providers/index.ts +67 -21
  116. package/src/lib/runtime-loop.ts +3 -2
  117. package/src/lib/server/approvals.ts +150 -0
  118. package/src/lib/server/chat-execution.ts +319 -74
  119. package/src/lib/server/chatroom-helpers.ts +63 -5
  120. package/src/lib/server/chatroom-orchestration.ts +74 -0
  121. package/src/lib/server/clawhub-client.ts +82 -6
  122. package/src/lib/server/connectors/manager.ts +27 -1
  123. package/src/lib/server/context-manager.ts +132 -50
  124. package/src/lib/server/cost.test.ts +73 -0
  125. package/src/lib/server/cost.ts +165 -34
  126. package/src/lib/server/daemon-state.ts +112 -1
  127. package/src/lib/server/data-dir.ts +18 -1
  128. package/src/lib/server/eval/runner.ts +126 -0
  129. package/src/lib/server/eval/scenarios.ts +218 -0
  130. package/src/lib/server/eval/scorer.ts +96 -0
  131. package/src/lib/server/eval/store.ts +37 -0
  132. package/src/lib/server/eval/types.ts +48 -0
  133. package/src/lib/server/execution-log.ts +12 -8
  134. package/src/lib/server/guardian.ts +34 -0
  135. package/src/lib/server/heartbeat-service.ts +53 -1
  136. package/src/lib/server/integrity-monitor.ts +208 -0
  137. package/src/lib/server/langgraph-checkpoint.ts +10 -0
  138. package/src/lib/server/link-understanding.ts +55 -0
  139. package/src/lib/server/llm-response-cache.test.ts +102 -0
  140. package/src/lib/server/llm-response-cache.ts +227 -0
  141. package/src/lib/server/main-agent-loop.ts +115 -16
  142. package/src/lib/server/main-session.ts +6 -3
  143. package/src/lib/server/mcp-conformance.test.ts +18 -0
  144. package/src/lib/server/mcp-conformance.ts +233 -0
  145. package/src/lib/server/memory-db.ts +193 -19
  146. package/src/lib/server/memory-retrieval.test.ts +56 -0
  147. package/src/lib/server/mmr.ts +73 -0
  148. package/src/lib/server/orchestrator-lg.ts +7 -1
  149. package/src/lib/server/orchestrator.ts +4 -3
  150. package/src/lib/server/plugins.ts +662 -132
  151. package/src/lib/server/process-manager.ts +18 -0
  152. package/src/lib/server/query-expansion.ts +57 -0
  153. package/src/lib/server/queue.ts +280 -11
  154. package/src/lib/server/runtime-settings.ts +9 -0
  155. package/src/lib/server/session-run-manager.test.ts +23 -0
  156. package/src/lib/server/session-run-manager.ts +32 -2
  157. package/src/lib/server/session-tools/canvas.ts +85 -50
  158. package/src/lib/server/session-tools/chatroom.ts +130 -127
  159. package/src/lib/server/session-tools/connector.ts +233 -454
  160. package/src/lib/server/session-tools/context-mgmt.ts +87 -105
  161. package/src/lib/server/session-tools/crud.ts +84 -7
  162. package/src/lib/server/session-tools/delegate.ts +351 -752
  163. package/src/lib/server/session-tools/discovery.ts +198 -0
  164. package/src/lib/server/session-tools/edit_file.ts +82 -0
  165. package/src/lib/server/session-tools/file-send.test.ts +39 -0
  166. package/src/lib/server/session-tools/file.ts +257 -425
  167. package/src/lib/server/session-tools/git.ts +87 -47
  168. package/src/lib/server/session-tools/http.ts +95 -33
  169. package/src/lib/server/session-tools/index.ts +217 -138
  170. package/src/lib/server/session-tools/memory.ts +154 -239
  171. package/src/lib/server/session-tools/monitor.ts +126 -0
  172. package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
  173. package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
  174. package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
  175. package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
  176. package/src/lib/server/session-tools/platform.ts +86 -0
  177. package/src/lib/server/session-tools/plugin-creator.ts +239 -0
  178. package/src/lib/server/session-tools/sample-ui.ts +97 -0
  179. package/src/lib/server/session-tools/sandbox.ts +175 -148
  180. package/src/lib/server/session-tools/schedule.ts +78 -0
  181. package/src/lib/server/session-tools/session-info.ts +104 -410
  182. package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
  183. package/src/lib/server/session-tools/shell.ts +171 -143
  184. package/src/lib/server/session-tools/subagent.ts +77 -77
  185. package/src/lib/server/session-tools/wallet.ts +182 -106
  186. package/src/lib/server/session-tools/web.ts +181 -327
  187. package/src/lib/server/storage.ts +36 -0
  188. package/src/lib/server/stream-agent-chat.ts +348 -242
  189. package/src/lib/server/task-quality-gate.test.ts +44 -0
  190. package/src/lib/server/task-quality-gate.ts +67 -0
  191. package/src/lib/server/task-validation.test.ts +78 -0
  192. package/src/lib/server/task-validation.ts +67 -2
  193. package/src/lib/server/tool-aliases.ts +68 -0
  194. package/src/lib/server/tool-capability-policy.ts +24 -5
  195. package/src/lib/server/tool-retry.ts +62 -0
  196. package/src/lib/server/transcript-repair.ts +72 -0
  197. package/src/lib/setup-defaults.ts +1 -0
  198. package/src/lib/tasks.ts +7 -1
  199. package/src/lib/tool-definitions.ts +24 -23
  200. package/src/lib/validation/schemas.ts +13 -0
  201. package/src/lib/view-routes.ts +2 -23
  202. package/src/stores/use-app-store.ts +23 -1
  203. package/src/types/index.ts +155 -10
@@ -1,255 +1,170 @@
1
1
  import { z } from 'zod'
2
- import { tool, type StructuredToolInterface } from '@langchain/core/tools'
2
+ import { tool } from '@langchain/core/tools'
3
3
  import fs from 'fs'
4
4
  import { genId } from '@/lib/id'
5
- import { getMemoryDb, getMemoryLookupLimits, storeMemoryImageAsset } from '../memory-db'
5
+ import {
6
+ filterMemoriesByScope,
7
+ getMemoryDb,
8
+ getMemoryLookupLimits,
9
+ normalizeMemoryScopeMode,
10
+ storeMemoryImageAsset,
11
+ } from '../memory-db'
6
12
  import { loadSettings } from '../storage'
13
+ import { expandQuery } from '../query-expansion'
14
+ import type { MemoryEntry, Plugin, PluginHooks } from '@/types'
7
15
  import type { ToolBuildContext } from './context'
16
+ import { getPluginManager } from '../plugins'
17
+ import { normalizeToolInputArgs } from './normalize-tool-args'
8
18
 
9
- export function buildMemoryTools(bctx: ToolBuildContext): StructuredToolInterface[] {
10
- const tools: StructuredToolInterface[] = []
11
- const { ctx, hasTool } = bctx
19
+ /**
20
+ * Advanced Database-Backed Memory logic.
21
+ */
22
+ async function executeMemoryAction(input: any, ctx: any) {
23
+ const normalized = normalizeToolInputArgs((input ?? {}) as Record<string, unknown>)
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
+ const n = normalized as Record<string, any>
26
+ const {
27
+ action, key, value, query, scope, rerank,
28
+ scopeSessionId, projectRoot, filePaths, references, project,
29
+ linkedMemoryIds, depth, linkedLimit, targetIds,
30
+ tags, pinned, sharedWith
31
+ } = n
32
+ const category = typeof n.category === 'string' ? n.category : 'note'
33
+ const imagePath = typeof n.imagePath === 'string' ? n.imagePath : undefined
34
+
35
+ const memDb = getMemoryDb()
36
+ const currentAgentId = ctx?.agentId || null
37
+ const rawScope = typeof scope === 'string' ? scope : 'auto'
38
+ const scopeMode = normalizeMemoryScopeMode(rawScope === 'shared' ? 'global' : rawScope)
39
+ const rerankMode = rerank === 'semantic' || rerank === 'lexical' ? rerank : 'balanced'
40
+
41
+ const scopeFilter = {
42
+ mode: scopeMode,
43
+ agentId: currentAgentId,
44
+ sessionId: (typeof scopeSessionId === 'string' && scopeSessionId.trim()) ? scopeSessionId.trim() : (ctx?.sessionId || null),
45
+ projectRoot: (typeof projectRoot === 'string' && projectRoot.trim()) ? projectRoot.trim() : ((project && typeof project === 'object' && 'rootPath' in project && typeof (project as Record<string, unknown>).rootPath === 'string') ? (project as Record<string, unknown>).rootPath as string : null),
46
+ }
47
+
48
+ const filterScope = (rows: MemoryEntry[]) => filterMemoriesByScope(rows, scopeFilter)
49
+ const canReadMemory = (m: MemoryEntry) => filterScope([m]).length > 0
50
+ const canMutateMemory = (m: MemoryEntry) => !m?.agentId || m.agentId === currentAgentId
12
51
 
13
- if (hasTool('memory')) {
14
- const memDb = getMemoryDb()
52
+ const limits = getMemoryLookupLimits(loadSettings())
53
+ const maxPerLookup = limits.maxPerLookup
15
54
 
16
- tools.push(
17
- tool(
18
- async (input) => {
19
- const { action, key, value, category, query, scope, filePaths, references, project, imagePath, linkedMemoryIds, depth, linkedLimit, targetIds, tags, pinned, sharedWith } = input as Record<string, any>
20
- try {
21
- const scopeMode = scope || 'auto'
22
- const currentAgentId = ctx?.agentId || null
23
- const canAccessMemory = (m: any) => !m?.agentId || m.agentId === currentAgentId
24
- const filterScope = (rows: any[]) => {
25
- if (scopeMode === 'shared') return rows.filter((m) => !m.agentId)
26
- if (scopeMode === 'agent') return rows.filter((m) => currentAgentId && m.agentId === currentAgentId)
27
- return rows.filter(canAccessMemory)
28
- }
55
+ const formatEntry = (m: any) => {
56
+ let line = `[${m.id}] (${m.agentId ? `agent:${m.agentId}` : 'shared'}) ${m.category}/${m.title}: ${m.content}`
57
+ if (m.reinforcementCount) line += ` (reinforced ×${m.reinforcementCount})`
58
+ if (m.references?.length) {
59
+ line += `\n refs: ${m.references.map((r: any) => `${r.type}:${r.path || r.title || r.type}`).join(', ')}`
60
+ }
61
+ if (m.imagePath) line += `\n image: ${m.imagePath}`
62
+ if (m.linkedMemoryIds?.length) line += `\n linked: ${m.linkedMemoryIds.join(', ')}`
63
+ return line
64
+ }
29
65
 
30
- const limits = getMemoryLookupLimits(loadSettings())
31
- const requestedDepth = typeof depth === 'number' ? depth : 0
32
- const requestedLinkedLimit = typeof linkedLimit === 'number' ? linkedLimit : limits.maxLinkedExpansion
33
- const effectiveDepth = Math.max(0, Math.min(requestedDepth, limits.maxDepth))
34
- const effectiveLinkedLimit = Math.max(0, Math.min(requestedLinkedLimit, limits.maxLinkedExpansion))
35
- const maxPerLookup = limits.maxPerLookup
66
+ if (action === 'store') {
67
+ let storedImage: any = null
68
+ if (imagePath && fs.existsSync(imagePath)) {
69
+ storedImage = await storeMemoryImageAsset(imagePath, genId(6))
70
+ }
71
+ const entry = memDb.add({
72
+ agentId: scopeMode === 'global' ? null : currentAgentId,
73
+ sessionId: ctx?.sessionId || null,
74
+ category: category || 'note',
75
+ title: key,
76
+ content: value || '',
77
+ references: Array.isArray(references) ? references : [],
78
+ filePaths: filePaths as any,
79
+ imagePath: storedImage?.path || undefined,
80
+ linkedMemoryIds,
81
+ pinned: pinned === true,
82
+ sharedWith: Array.isArray(sharedWith) ? sharedWith : undefined,
83
+ })
84
+ return `Stored memory "${key}" (id: ${entry.id})`
85
+ }
36
86
 
37
- const normalizedLegacyRefs = Array.isArray(filePaths)
38
- ? filePaths.map((f: any) => ({
39
- type: f.kind === 'project' ? 'project' : (f.kind === 'folder' ? 'folder' : 'file'),
40
- path: f.path,
41
- projectRoot: f.projectRoot,
42
- projectName: f.projectName,
43
- note: f.contextSnippet,
44
- timestamp: typeof f.timestamp === 'number' ? f.timestamp : Date.now(),
45
- }))
46
- : []
47
- const normalizedRefs = Array.isArray(references) ? references : []
48
- if (project?.rootPath) {
49
- normalizedRefs.push({
50
- type: 'project',
51
- path: project.rootPath,
52
- projectRoot: project.rootPath,
53
- projectName: project.name,
54
- title: project.name,
55
- note: project.note,
56
- timestamp: Date.now(),
57
- })
58
- }
59
- const mergedRefs = [...normalizedLegacyRefs, ...normalizedRefs]
87
+ if (action === 'get') {
88
+ const found = memDb.get(key)
89
+ if (!found || !canReadMemory(found)) return `Memory not found or access denied: ${key}`
90
+ return formatEntry(found)
91
+ }
60
92
 
61
- const formatEntry = (m: any) => {
62
- let line = `[${m.id}] (${m.agentId ? `agent:${m.agentId}` : 'shared'}) ${m.category}/${m.title}: ${m.content}`
63
- if (m.reinforcementCount) line += ` (reinforced ×${m.reinforcementCount})`
64
- if (m.references?.length) {
65
- line += `\n refs: ${m.references.map((r: any) => {
66
- const core = r.path || r.title || r.type
67
- const projectMeta = r.projectName ? ` @${r.projectName}` : ''
68
- const existsMeta = typeof r.exists === 'boolean' ? (r.exists ? ' (exists)' : ' (missing)') : ''
69
- return `${r.type}:${core}${projectMeta}${existsMeta}`
70
- }).join(', ')}`
71
- } else if (m.filePaths?.length) {
72
- line += `\n files: ${m.filePaths.map((f: any) => `${f.path}${f.contextSnippet ? ` (${f.contextSnippet})` : ''}`).join(', ')}`
73
- }
74
- if (m.image?.path || m.imagePath) line += `\n image: ${m.image?.path || m.imagePath}`
75
- if (m.linkedMemoryIds?.length) line += `\n linked: ${m.linkedMemoryIds.join(', ')}`
76
- return line
77
- }
93
+ if (action === 'search') {
94
+ const queries = query ? await expandQuery(query) : [key || '']
95
+ const allResults: MemoryEntry[] = []
96
+ const seenIds = new Set<string>()
97
+ for (const q of queries) {
98
+ const results = memDb.search(q, currentAgentId || undefined, { scope: scopeFilter, rerankMode })
99
+ for (const r of results) {
100
+ if (!seenIds.has(r.id)) {
101
+ seenIds.add(r.id); allResults.push(r)
102
+ }
103
+ }
104
+ }
105
+ if (!allResults.length) return 'No memories found.'
106
+ return allResults.slice(0, maxPerLookup).map(formatEntry).join('\n')
107
+ }
78
108
 
79
- if (action === 'store') {
80
- let storedImage: any = null
81
- if (imagePath) {
82
- if (!fs.existsSync(imagePath)) {
83
- return `Error: image file not found: ${imagePath}`
84
- }
85
- try {
86
- storedImage = await storeMemoryImageAsset(imagePath, genId(6))
87
- } catch {
88
- return `Error: failed to process image at ${imagePath}`
89
- }
90
- }
109
+ if (action === 'list') {
110
+ const results = filterScope(memDb.list(undefined, maxPerLookup))
111
+ return results.length ? results.map(formatEntry).join('\n') : 'No memories stored yet.'
112
+ }
91
113
 
92
- const entry = memDb.add({
93
- agentId: scopeMode === 'shared' ? null : currentAgentId,
94
- sessionId: ctx?.sessionId || null,
95
- category: category || 'note',
96
- title: key,
97
- content: value || '',
98
- references: mergedRefs as any,
99
- filePaths: filePaths as any,
100
- image: storedImage,
101
- imagePath: storedImage?.path || undefined,
102
- linkedMemoryIds,
103
- pinned: pinned === true,
104
- sharedWith: Array.isArray(sharedWith) ? sharedWith : undefined,
105
- })
106
- const memoryScope = entry.agentId ? 'agent' : 'shared'
107
- let result = `Stored ${memoryScope} memory "${key}" (id: ${entry.id})`
108
- if (mergedRefs.length) result += ` with ${mergedRefs.length} reference(s)`
109
- if (storedImage?.path) result += ` with image`
110
- if (linkedMemoryIds?.length) result += ` linked to ${linkedMemoryIds.length} memor${linkedMemoryIds.length === 1 ? 'y' : 'ies'}`
111
- return result
112
- }
113
- if (action === 'get') {
114
- if (effectiveDepth > 0) {
115
- const result = memDb.getWithLinked(key, effectiveDepth, maxPerLookup, effectiveLinkedLimit)
116
- if (!result) return `Memory not found: ${key}`
117
- const accessible = result.entries.filter(canAccessMemory)
118
- if (!accessible.length) return 'Error: you do not have access to that memory.'
119
- let output = accessible.map(formatEntry).join('\n---\n')
120
- if (result.truncated) output += `\n\n[Results truncated at ${maxPerLookup} memories / ${effectiveLinkedLimit} linked expansions]`
121
- return output
122
- }
123
- const found = memDb.get(key)
124
- if (!found) return `Memory not found: ${key}`
125
- if (!canAccessMemory(found)) return 'Error: you do not have access to that memory.'
126
- return formatEntry(found)
127
- }
128
- if (action === 'search') {
129
- if (effectiveDepth > 0) {
130
- const result = memDb.searchWithLinked(query || key, undefined, effectiveDepth, maxPerLookup, effectiveLinkedLimit)
131
- const accessible = filterScope(result.entries)
132
- if (!accessible.length) return 'No memories found.'
133
- let output = accessible.map(formatEntry).join('\n')
134
- if (result.truncated) output += `\n\n[Results truncated at ${maxPerLookup} memories / ${effectiveLinkedLimit} linked expansions]`
135
- return output
136
- }
137
- const results = filterScope(memDb.search(query || key))
138
- if (!results.length) return 'No memories found.'
139
- return results.slice(0, maxPerLookup).map(formatEntry).join('\n')
140
- }
141
- if (action === 'list') {
142
- const results = filterScope(memDb.list(undefined, maxPerLookup))
143
- if (!results.length) return 'No memories stored yet.'
144
- return results.map(formatEntry).join('\n')
145
- }
146
- if (action === 'delete') {
147
- const found = memDb.get(key)
148
- if (!found) return `Memory not found: ${key}`
149
- if (!canAccessMemory(found)) return 'Error: you do not have access to that memory.'
150
- memDb.delete(key)
151
- return `Deleted memory "${key}"`
152
- }
153
- if (action === 'link') {
154
- if (!targetIds?.length) return 'Error: targetIds required for link action.'
155
- const result = memDb.link(key, targetIds, true)
156
- if (!result) return `Memory not found: ${key}`
157
- return `Linked memory "${key}" to ${targetIds.length} memor${targetIds.length === 1 ? 'y' : 'ies'} (bidirectional): ${targetIds.join(', ')}`
158
- }
159
- if (action === 'unlink') {
160
- if (!targetIds?.length) return 'Error: targetIds required for unlink action.'
161
- const result = memDb.unlink(key, targetIds, true)
162
- if (!result) return `Memory not found: ${key}`
163
- return `Unlinked ${targetIds.length} memor${targetIds.length === 1 ? 'y' : 'ies'} from "${key}" (bidirectional)`
164
- }
165
- if (action === 'knowledge_store') {
166
- const { addKnowledge } = await import('../memory-db')
167
- if (!value) return 'Error: value (content) is required for knowledge_store'
168
- const source = (input as Record<string, unknown>).source as string | undefined
169
- const sourceUrl = (input as Record<string, unknown>).sourceUrl as string | undefined
170
- const entry = addKnowledge({
171
- title: key || 'Untitled',
172
- content: value,
173
- tags: tags,
174
- createdByAgentId: ctx?.agentId || null,
175
- createdBySessionId: ctx?.sessionId || null,
176
- source: source || undefined,
177
- sourceUrl: sourceUrl || undefined,
178
- })
179
- return `Knowledge stored: "${entry.title}" (id: ${entry.id})`
180
- }
181
- if (action === 'knowledge_search') {
182
- const { searchKnowledge } = await import('../memory-db')
183
- const results = searchKnowledge(query || key || '', tags, 10)
184
- if (!results.length) return 'No knowledge entries found.'
185
- return results.map(r => {
186
- const meta = r.metadata as Record<string, unknown> | undefined
187
- const src = meta?.source as string | undefined
188
- const srcUrl = meta?.sourceUrl as string | undefined
189
- let line = `[${r.id}] ${r.title}: ${r.content.slice(0, 200)}`
190
- if (src && srcUrl) {
191
- line += ` [${src}](${srcUrl})`
192
- } else if (src) {
193
- line += ` (source: ${src})`
194
- } else if (srcUrl) {
195
- line += ` (${srcUrl})`
196
- }
197
- return line
198
- }).join('\n---\n')
199
- }
200
- return `Unknown action "${action}". Use: store, get, search, list, delete, link, unlink, knowledge_store, or knowledge_search.`
201
- } catch (err: unknown) {
202
- return `Error: ${err instanceof Error ? err.message : String(err)}`
203
- }
204
- },
205
- {
206
- name: 'memory_tool',
207
- description: 'My long-term memory — things I remember across conversations. I can store personal notes, recall past context, and build up knowledge over time. Memories can be private to me or shared with other agents. I can also attach files, link related memories, and contribute to a shared knowledge base. Actions: store, get, search, list, delete, link, unlink, knowledge_store, knowledge_search.',
208
- schema: z.object({
209
- action: z.enum(['store', 'get', 'search', 'list', 'delete', 'link', 'unlink', 'knowledge_store', 'knowledge_search']).describe('The action to perform'),
210
- key: z.string().describe('For store: memory title. For get/delete/link/unlink: memory ID. For search: optional query fallback.'),
211
- value: z.string().optional().describe('The memory content (for store action)'),
212
- category: z.string().optional().describe('Category like "note", "fact", "preference", "project", "identity" (for store action, defaults to "note")'),
213
- query: z.string().optional().describe('Search query (alternative to key for search action)'),
214
- scope: z.enum(['auto', 'shared', 'agent']).optional().describe('Scope hint: auto (shared + own), shared, or agent'),
215
- filePaths: z.array(z.object({
216
- path: z.string().describe('File or folder path'),
217
- contextSnippet: z.string().optional().describe('Brief context about this file reference'),
218
- kind: z.enum(['file', 'folder', 'project']).optional().describe('Reference type for legacy filePaths compatibility'),
219
- projectRoot: z.string().optional().describe('Optional project root path'),
220
- projectName: z.string().optional().describe('Optional project display name'),
221
- exists: z.boolean().optional().describe('Optional known existence state'),
222
- timestamp: z.number().describe('When this file was referenced'),
223
- })).optional().describe('File/folder references to attach to the memory (for store action)'),
224
- references: z.array(z.object({
225
- type: z.enum(['project', 'folder', 'file', 'task', 'session', 'url']),
226
- path: z.string().optional(),
227
- projectRoot: z.string().optional(),
228
- projectName: z.string().optional(),
229
- title: z.string().optional(),
230
- note: z.string().optional(),
231
- timestamp: z.number().optional(),
232
- })).optional().describe('Structured references attached to the memory (preferred over filePaths).'),
233
- project: z.object({
234
- rootPath: z.string().describe('Project/workspace root path'),
235
- name: z.string().optional().describe('Optional project display name'),
236
- note: z.string().optional().describe('Optional note about the project context'),
237
- }).optional().describe('Shortcut to add a project reference on store action.'),
238
- imagePath: z.string().optional().describe('Path to an image file to attach (will be compressed and stored). For store action.'),
239
- linkedMemoryIds: z.array(z.string()).optional().describe('IDs of other memories to link to (for store action)'),
240
- depth: z.number().optional().describe('How deep to traverse linked memories (for get/search). Respects configured maxDepth limit. Default: 0 (no traversal).'),
241
- linkedLimit: z.number().optional().describe('Max linked memories expanded during traversal. Respects configured server cap.'),
242
- targetIds: z.array(z.string()).optional().describe('Memory IDs to link/unlink (for link/unlink actions)'),
243
- tags: z.array(z.string()).optional().describe('Tags for categorizing knowledge entries'),
244
- source: z.string().optional().describe("Source of the knowledge, e.g. 'user', 'web', 'document'"),
245
- sourceUrl: z.string().optional().describe('URL where the knowledge was sourced from'),
246
- pinned: z.boolean().optional().describe('Mark memory as pinned (always preloaded in agent context). For store action.'),
247
- sharedWith: z.array(z.string()).optional().describe('Agent IDs to share this memory with (for store action). They can read it in their context.'),
248
- }),
249
- },
250
- ),
251
- )
114
+ if (action === 'delete') {
115
+ const found = memDb.get(key)
116
+ if (!found || !canMutateMemory(found)) return 'Memory not found or access denied.'
117
+ memDb.delete(key)
118
+ return `Deleted memory "${key}"`
252
119
  }
253
120
 
254
- return tools
121
+ return `Unknown action "${action}".`
122
+ }
123
+
124
+ /**
125
+ * Register as a Built-in Plugin
126
+ */
127
+ const MemoryPlugin: Plugin = {
128
+ name: 'Core Memory',
129
+ description: 'Advanced database-backed long-term memory with semantic search and graph linking.',
130
+ hooks: {} as PluginHooks,
131
+ tools: [
132
+ {
133
+ name: 'memory_tool',
134
+ description: 'Advanced long-term memory system. Use to store and recall facts across all conversations.',
135
+ parameters: {
136
+ type: 'object',
137
+ properties: {
138
+ action: { type: 'string', enum: ['store', 'get', 'search', 'list', 'delete'] },
139
+ key: { type: 'string' },
140
+ value: { type: 'string' },
141
+ category: { type: 'string' },
142
+ query: { type: 'string' },
143
+ scope: { type: 'string', enum: ['auto', 'all', 'global', 'shared', 'agent', 'session', 'project'] },
144
+ },
145
+ required: ['action']
146
+ },
147
+ execute: async (args, context) => {
148
+ return executeMemoryAction(args, context.session)
149
+ }
150
+ }
151
+ ]
152
+ }
153
+
154
+ // Auto-register when imported
155
+ getPluginManager().registerBuiltin('memory', MemoryPlugin)
156
+
157
+ export function buildMemoryTools(bctx: ToolBuildContext) {
158
+ if (!bctx.hasTool('memory')) return []
159
+
160
+ return [
161
+ tool(
162
+ async (args) => executeMemoryAction(args, bctx.ctx),
163
+ {
164
+ name: 'memory_tool',
165
+ description: MemoryPlugin.tools![0].description,
166
+ schema: z.object({}).passthrough()
167
+ }
168
+ )
169
+ ]
255
170
  }
@@ -0,0 +1,126 @@
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 * as os from 'os'
6
+ import type { ToolBuildContext } from './context'
7
+ import { getPluginManager } from '../plugins'
8
+ import type { Plugin, PluginHooks } from '@/types'
9
+ import { safePath, truncate } from './context'
10
+ import { normalizeToolInputArgs } from './normalize-tool-args'
11
+
12
+ /**
13
+ * Unified Monitoring Logic
14
+ */
15
+ async function executeMonitorAction(args: any, bctx: { cwd: string }) {
16
+ const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
17
+ const action = normalized.action as string | undefined
18
+ const target = (normalized.target ?? normalized.url ?? normalized.path) as string | undefined
19
+ const limit = normalized.limit as number | undefined
20
+ const threshold = normalized.threshold as number | undefined
21
+
22
+ try {
23
+ switch (action) {
24
+ case 'sys_info': {
25
+ const freeMem = os.freemem()
26
+ const totalMem = os.totalmem()
27
+ const load = os.loadavg()
28
+ const uptime = os.uptime()
29
+ return JSON.stringify({
30
+ platform: os.platform(),
31
+ arch: os.arch(),
32
+ cpus: os.cpus().length,
33
+ memory: {
34
+ free: `${Math.round(freeMem / 1024 / 1024)}MB`,
35
+ total: `${Math.round(totalMem / 1024 / 1024)}MB`,
36
+ usage: `${Math.round(((totalMem - freeMem) / totalMem) * 100)}%`
37
+ },
38
+ loadAvg: load,
39
+ uptime: `${Math.round(uptime / 3600)} hours`
40
+ }, null, 2)
41
+ }
42
+
43
+ case 'watch_log': {
44
+ const resolved = safePath(bctx.cwd, target!)
45
+ if (!fs.existsSync(resolved)) return `Error: File not found ${target}`
46
+
47
+ const stats = fs.statSync(resolved)
48
+ const size = stats.size
49
+ const bufferSize = Math.min(size, 5000) // Read last 5KB
50
+ const fd = fs.openSync(resolved, 'r')
51
+ const buffer = Buffer.alloc(bufferSize)
52
+ fs.readSync(fd, buffer, 0, bufferSize, size - bufferSize)
53
+ fs.closeSync(fd)
54
+
55
+ return truncate(buffer.toString('utf8'), 2000)
56
+ }
57
+
58
+ case 'ping': {
59
+ const url = target?.startsWith('http') ? target : `http://${target}`
60
+ const start = Date.now()
61
+ try {
62
+ const res = await fetch(url, { signal: AbortSignal.timeout(5000) })
63
+ const latency = Date.now() - start
64
+ return JSON.stringify({
65
+ status: res.status,
66
+ ok: res.ok,
67
+ latency: `${latency}ms`,
68
+ url
69
+ }, null, 2)
70
+ } catch (err: any) {
71
+ return JSON.stringify({
72
+ status: 'error',
73
+ error: err.message,
74
+ url
75
+ }, null, 2)
76
+ }
77
+ }
78
+
79
+ default:
80
+ return `Error: Unknown action "${action}"`
81
+ }
82
+ } catch (err: any) {
83
+ return `Error: ${err.message}`
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Register as a Built-in Plugin
89
+ */
90
+ const MonitorPlugin: Plugin = {
91
+ name: 'Core Monitor',
92
+ description: 'System observability: check resource usage, watch logs, and ping endpoints.',
93
+ hooks: {} as PluginHooks,
94
+ tools: [
95
+ {
96
+ name: 'monitor_tool',
97
+ description: 'Observe system health, log activity, or endpoint availability.',
98
+ parameters: {
99
+ type: 'object',
100
+ properties: {
101
+ action: { type: 'string', enum: ['sys_info', 'watch_log', 'ping'] },
102
+ target: { type: 'string', description: 'Log file path (for watch_log) or URL (for ping)' },
103
+ limit: { type: 'number', description: 'Number of lines or bytes to retrieve' }
104
+ },
105
+ required: ['action']
106
+ },
107
+ execute: async (args, context) => executeMonitorAction(args, { cwd: context.session.cwd || process.cwd() })
108
+ }
109
+ ]
110
+ }
111
+
112
+ getPluginManager().registerBuiltin('monitor', MonitorPlugin)
113
+
114
+ export function buildMonitorTools(bctx: ToolBuildContext): StructuredToolInterface[] {
115
+ if (!bctx.hasTool('monitor')) return []
116
+ return [
117
+ tool(
118
+ async (args) => executeMonitorAction(args, { cwd: bctx.cwd }),
119
+ {
120
+ name: 'monitor_tool',
121
+ description: MonitorPlugin.tools![0].description,
122
+ schema: z.object({}).passthrough()
123
+ }
124
+ )
125
+ ]
126
+ }
@@ -0,0 +1,61 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { normalizeToolInputArgs } from './normalize-tool-args'
4
+
5
+ describe('normalizeToolInputArgs', () => {
6
+ it('keeps top-level args as-is when there is no wrapper', () => {
7
+ const out = normalizeToolInputArgs({ action: 'list', path: '/tmp' })
8
+ assert.equal(out.action, 'list')
9
+ assert.equal(out.path, '/tmp')
10
+ })
11
+
12
+ it('merges nested input object payloads', () => {
13
+ const out = normalizeToolInputArgs({
14
+ input: {
15
+ action: 'execute',
16
+ execute_command: 'pwd',
17
+ },
18
+ })
19
+ assert.equal(out.action, 'execute')
20
+ assert.equal(out.execute_command, 'pwd')
21
+ })
22
+
23
+ it('merges stringified input payloads', () => {
24
+ const out = normalizeToolInputArgs({
25
+ input: JSON.stringify({
26
+ action: 'list',
27
+ path: '/Users/dev/project',
28
+ }),
29
+ })
30
+ assert.equal(out.action, 'list')
31
+ assert.equal(out.path, '/Users/dev/project')
32
+ })
33
+
34
+ it('supports args/payload wrapper aliases', () => {
35
+ const out = normalizeToolInputArgs({
36
+ args: { action: 'read', filePath: 'README.md' },
37
+ payload: { limit: 10 },
38
+ })
39
+ assert.equal(out.action, 'read')
40
+ assert.equal(out.filePath, 'README.md')
41
+ assert.equal(out.limit, 10)
42
+ })
43
+
44
+ it('keeps explicit top-level values over nested wrappers', () => {
45
+ const out = normalizeToolInputArgs({
46
+ action: 'top-level',
47
+ input: { action: 'nested' },
48
+ })
49
+ assert.equal(out.action, 'top-level')
50
+ })
51
+
52
+ it('preserves falsey top-level values', () => {
53
+ const out = normalizeToolInputArgs({
54
+ input: { limit: 5, approved: true },
55
+ limit: 0,
56
+ approved: false,
57
+ })
58
+ assert.equal(out.limit, 0)
59
+ assert.equal(out.approved, false)
60
+ })
61
+ })
@@ -0,0 +1,48 @@
1
+ export type ToolArgsRecord = Record<string, unknown>
2
+
3
+ function parseRecordCandidate(value: unknown): ToolArgsRecord | null {
4
+ if (!value) return null
5
+ if (typeof value === 'object' && !Array.isArray(value)) {
6
+ return value as ToolArgsRecord
7
+ }
8
+ if (typeof value !== 'string') return null
9
+ const trimmed = value.trim()
10
+ if (!trimmed || (!trimmed.startsWith('{') && !trimmed.startsWith('['))) return null
11
+ try {
12
+ const parsed = JSON.parse(trimmed)
13
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
14
+ return parsed as ToolArgsRecord
15
+ }
16
+ } catch {
17
+ // ignore non-JSON strings
18
+ }
19
+ return null
20
+ }
21
+
22
+ /**
23
+ * Normalize common wrapper payloads used by older tool callers.
24
+ *
25
+ * Supports payloads nested under `input`, `args`, `arguments`, or `payload`
26
+ * as either objects or JSON strings.
27
+ */
28
+ export function normalizeToolInputArgs(rawArgs: ToolArgsRecord): ToolArgsRecord {
29
+ const nestedSources: Array<ToolArgsRecord | null> = [
30
+ parseRecordCandidate(rawArgs.input),
31
+ parseRecordCandidate(rawArgs.args),
32
+ parseRecordCandidate(rawArgs.arguments),
33
+ parseRecordCandidate(rawArgs.payload),
34
+ ]
35
+
36
+ const normalized: ToolArgsRecord = {}
37
+ for (const nested of nestedSources) {
38
+ if (!nested) continue
39
+ Object.assign(normalized, nested)
40
+ }
41
+
42
+ for (const [key, value] of Object.entries(rawArgs)) {
43
+ if (value === undefined || value === null) continue
44
+ normalized[key] = value
45
+ }
46
+
47
+ return normalized
48
+ }