@swarmclawai/swarmclaw 0.6.8 → 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 (166) hide show
  1. package/README.md +70 -45
  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 +18 -5
  6. package/src/app/api/approvals/route.ts +22 -0
  7. package/src/app/api/clawhub/install/route.ts +2 -2
  8. package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
  9. package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
  10. package/src/app/api/memory/route.ts +36 -5
  11. package/src/app/api/notifications/route.ts +3 -0
  12. package/src/app/api/plugins/install/route.ts +57 -5
  13. package/src/app/api/plugins/marketplace/route.ts +73 -22
  14. package/src/app/api/plugins/route.ts +61 -1
  15. package/src/app/api/plugins/ui/route.ts +34 -0
  16. package/src/app/api/settings/route.ts +62 -0
  17. package/src/app/api/setup/doctor/route.ts +22 -5
  18. package/src/app/api/tasks/[id]/approve/route.ts +4 -3
  19. package/src/app/api/tasks/[id]/route.ts +11 -3
  20. package/src/app/api/tasks/route.ts +8 -2
  21. package/src/app/globals.css +27 -0
  22. package/src/app/page.tsx +10 -5
  23. package/src/cli/index.js +13 -0
  24. package/src/components/activity/activity-feed.tsx +9 -2
  25. package/src/components/agents/agent-avatar.tsx +5 -1
  26. package/src/components/agents/agent-card.tsx +55 -9
  27. package/src/components/agents/agent-sheet.tsx +86 -29
  28. package/src/components/agents/inspector-panel.tsx +1 -1
  29. package/src/components/auth/access-key-gate.tsx +63 -54
  30. package/src/components/auth/user-picker.tsx +37 -32
  31. package/src/components/chat/chat-area.tsx +11 -0
  32. package/src/components/chat/chat-header.tsx +69 -25
  33. package/src/components/chat/chat-tool-toggles.tsx +2 -2
  34. package/src/components/chat/code-block.tsx +3 -1
  35. package/src/components/chat/exec-approval-card.tsx +8 -1
  36. package/src/components/chat/message-bubble.tsx +164 -4
  37. package/src/components/chat/message-list.tsx +30 -4
  38. package/src/components/chat/session-approval-card.tsx +80 -0
  39. package/src/components/chat/streaming-bubble.tsx +6 -5
  40. package/src/components/chat/thinking-indicator.tsx +48 -12
  41. package/src/components/chat/tool-request-banner.tsx +39 -20
  42. package/src/components/chatrooms/chatroom-list.tsx +11 -4
  43. package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
  44. package/src/components/connectors/connector-list.tsx +33 -11
  45. package/src/components/connectors/connector-sheet.tsx +29 -6
  46. package/src/components/home/home-view.tsx +20 -14
  47. package/src/components/input/chat-input.tsx +22 -1
  48. package/src/components/knowledge/knowledge-list.tsx +17 -18
  49. package/src/components/knowledge/knowledge-sheet.tsx +9 -5
  50. package/src/components/layout/app-layout.tsx +73 -21
  51. package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
  52. package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
  53. package/src/components/memory/memory-list.tsx +20 -13
  54. package/src/components/plugins/plugin-list.tsx +213 -59
  55. package/src/components/plugins/plugin-sheet.tsx +119 -24
  56. package/src/components/projects/project-list.tsx +17 -9
  57. package/src/components/providers/provider-list.tsx +21 -6
  58. package/src/components/providers/provider-sheet.tsx +42 -25
  59. package/src/components/runs/run-list.tsx +17 -13
  60. package/src/components/schedules/schedule-card.tsx +10 -3
  61. package/src/components/schedules/schedule-list.tsx +2 -2
  62. package/src/components/schedules/schedule-sheet.tsx +19 -7
  63. package/src/components/secrets/secret-sheet.tsx +7 -2
  64. package/src/components/secrets/secrets-list.tsx +18 -5
  65. package/src/components/sessions/new-session-sheet.tsx +183 -376
  66. package/src/components/sessions/session-card.tsx +10 -2
  67. package/src/components/settings/gateway-connection-panel.tsx +9 -8
  68. package/src/components/shared/command-palette.tsx +13 -5
  69. package/src/components/shared/empty-state.tsx +20 -8
  70. package/src/components/shared/notification-center.tsx +134 -86
  71. package/src/components/shared/profile-sheet.tsx +4 -0
  72. package/src/components/shared/settings/plugin-manager.tsx +360 -135
  73. package/src/components/shared/settings/section-capability-policy.tsx +3 -3
  74. package/src/components/shared/settings/section-runtime-loop.tsx +144 -0
  75. package/src/components/skills/clawhub-browser.tsx +1 -0
  76. package/src/components/skills/skill-list.tsx +31 -12
  77. package/src/components/skills/skill-sheet.tsx +20 -7
  78. package/src/components/tasks/approvals-panel.tsx +170 -66
  79. package/src/components/tasks/task-board.tsx +20 -12
  80. package/src/components/tasks/task-card.tsx +21 -7
  81. package/src/components/tasks/task-column.tsx +4 -3
  82. package/src/components/tasks/task-list.tsx +1 -1
  83. package/src/components/tasks/task-sheet.tsx +130 -1
  84. package/src/components/ui/dialog.tsx +1 -0
  85. package/src/components/ui/sheet.tsx +1 -0
  86. package/src/components/usage/metrics-dashboard.tsx +66 -64
  87. package/src/components/wallets/wallet-panel.tsx +65 -41
  88. package/src/components/wallets/wallet-section.tsx +9 -3
  89. package/src/components/webhooks/webhook-list.tsx +21 -12
  90. package/src/components/webhooks/webhook-sheet.tsx +13 -3
  91. package/src/lib/approval-display.test.ts +45 -0
  92. package/src/lib/approval-display.ts +62 -0
  93. package/src/lib/clipboard.ts +38 -0
  94. package/src/lib/memory.ts +8 -0
  95. package/src/lib/providers/claude-cli.ts +5 -3
  96. package/src/lib/providers/index.ts +67 -21
  97. package/src/lib/runtime-loop.ts +3 -2
  98. package/src/lib/server/approvals.ts +150 -0
  99. package/src/lib/server/chat-execution.ts +223 -62
  100. package/src/lib/server/clawhub-client.ts +82 -6
  101. package/src/lib/server/connectors/manager.ts +27 -1
  102. package/src/lib/server/cost.test.ts +73 -0
  103. package/src/lib/server/cost.ts +165 -34
  104. package/src/lib/server/daemon-state.ts +42 -0
  105. package/src/lib/server/data-dir.ts +18 -1
  106. package/src/lib/server/integrity-monitor.ts +208 -0
  107. package/src/lib/server/llm-response-cache.test.ts +102 -0
  108. package/src/lib/server/llm-response-cache.ts +227 -0
  109. package/src/lib/server/main-agent-loop.ts +1 -1
  110. package/src/lib/server/main-session.ts +6 -3
  111. package/src/lib/server/mcp-conformance.test.ts +18 -0
  112. package/src/lib/server/mcp-conformance.ts +233 -0
  113. package/src/lib/server/memory-db.ts +180 -17
  114. package/src/lib/server/memory-retrieval.test.ts +56 -0
  115. package/src/lib/server/orchestrator-lg.ts +4 -1
  116. package/src/lib/server/orchestrator.ts +4 -3
  117. package/src/lib/server/plugins.ts +650 -142
  118. package/src/lib/server/process-manager.ts +18 -0
  119. package/src/lib/server/queue.ts +253 -11
  120. package/src/lib/server/runtime-settings.ts +9 -0
  121. package/src/lib/server/session-run-manager.test.ts +23 -0
  122. package/src/lib/server/session-run-manager.ts +11 -1
  123. package/src/lib/server/session-tools/canvas.ts +85 -50
  124. package/src/lib/server/session-tools/chatroom.ts +130 -127
  125. package/src/lib/server/session-tools/connector.ts +233 -454
  126. package/src/lib/server/session-tools/context-mgmt.ts +87 -105
  127. package/src/lib/server/session-tools/crud.ts +84 -7
  128. package/src/lib/server/session-tools/delegate.ts +351 -752
  129. package/src/lib/server/session-tools/discovery.ts +198 -0
  130. package/src/lib/server/session-tools/edit_file.ts +82 -0
  131. package/src/lib/server/session-tools/file-send.test.ts +39 -0
  132. package/src/lib/server/session-tools/file.ts +257 -425
  133. package/src/lib/server/session-tools/git.ts +87 -47
  134. package/src/lib/server/session-tools/http.ts +85 -33
  135. package/src/lib/server/session-tools/index.ts +205 -160
  136. package/src/lib/server/session-tools/memory.ts +152 -265
  137. package/src/lib/server/session-tools/monitor.ts +126 -0
  138. package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
  139. package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
  140. package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
  141. package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
  142. package/src/lib/server/session-tools/platform.ts +86 -0
  143. package/src/lib/server/session-tools/plugin-creator.ts +239 -0
  144. package/src/lib/server/session-tools/sample-ui.ts +97 -0
  145. package/src/lib/server/session-tools/sandbox.ts +175 -148
  146. package/src/lib/server/session-tools/schedule.ts +66 -31
  147. package/src/lib/server/session-tools/session-info.ts +104 -410
  148. package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
  149. package/src/lib/server/session-tools/shell.ts +171 -143
  150. package/src/lib/server/session-tools/subagent.ts +77 -77
  151. package/src/lib/server/session-tools/wallet.ts +182 -106
  152. package/src/lib/server/session-tools/web.ts +179 -349
  153. package/src/lib/server/storage.ts +24 -0
  154. package/src/lib/server/stream-agent-chat.ts +301 -244
  155. package/src/lib/server/task-quality-gate.test.ts +44 -0
  156. package/src/lib/server/task-quality-gate.ts +67 -0
  157. package/src/lib/server/task-validation.test.ts +78 -0
  158. package/src/lib/server/task-validation.ts +67 -2
  159. package/src/lib/server/tool-aliases.ts +68 -0
  160. package/src/lib/server/tool-capability-policy.ts +23 -5
  161. package/src/lib/tasks.ts +7 -1
  162. package/src/lib/tool-definitions.ts +23 -23
  163. package/src/lib/validation/schemas.ts +12 -0
  164. package/src/lib/view-routes.ts +2 -24
  165. package/src/stores/use-app-store.ts +23 -1
  166. package/src/types/index.ts +121 -7
@@ -5,18 +5,17 @@ import { loadSettings, loadSessions, saveSessions, loadMcpServers } from '../sto
5
5
  import { loadRuntimeSettings } from '../runtime-settings'
6
6
  import { log } from '../logger'
7
7
  import { resolveSessionToolPolicy } from '../tool-capability-policy'
8
+ import { expandToolIds } from '../tool-aliases'
8
9
  import type { ToolContext, SessionToolsResult, ToolBuildContext } from './context'
10
+
11
+ // Import all tool modules to trigger their builtin registration
9
12
  import { buildShellTools } from './shell'
10
13
  import { buildFileTools } from './file'
14
+ import { buildEditFileTools } from './edit_file'
11
15
  import { buildDelegateTools } from './delegate'
12
16
  import { buildWebTools, sweepOrphanedBrowsers, cleanupSessionBrowser, getActiveBrowserCount, hasActiveBrowser } from './web'
13
17
  import { buildMemoryTools } from './memory'
14
- import { buildCrudTools } from './crud'
15
- import { buildSessionInfoTools } from './session-info'
16
- import { buildConnectorTools } from './connector'
17
- import { buildContextTools } from './context-mgmt'
18
18
  import { buildSandboxTools } from './sandbox'
19
- import { buildOpenClawNodeTools } from './openclaw-nodes'
20
19
  import { buildChatroomTools } from './chatroom'
21
20
  import { buildSubagentTools } from './subagent'
22
21
  import { buildCanvasTools } from './canvas'
@@ -25,6 +24,17 @@ import { buildGitTools } from './git'
25
24
  import { buildWalletTools } from './wallet'
26
25
  import { buildOpenClawWorkspaceTools } from './openclaw-workspace'
27
26
  import { buildScheduleTools } from './schedule'
27
+ import { buildPlatformTools } from './platform'
28
+ import { buildSessionInfoTools } from './session-info'
29
+ import { buildOpenClawNodeTools } from './openclaw-nodes'
30
+ import { buildContextTools } from './context-mgmt'
31
+ import { buildConnectorTools } from './connector'
32
+ import { buildDiscoveryTools } from './discovery'
33
+ import { buildMonitorTools } from './monitor'
34
+ import { buildSampleUITools } from './sample-ui'
35
+ import { buildPluginCreatorTools } from './plugin-creator'
36
+ import { normalizeToolInputArgs } from './normalize-tool-args'
37
+
28
38
  import { getPluginManager } from '../plugins'
29
39
  import { jsonSchemaToZod } from '../mcp-client'
30
40
 
@@ -34,143 +44,142 @@ export { sweepOrphanedBrowsers, cleanupSessionBrowser, getActiveBrowserCount, ha
34
44
  export async function buildSessionTools(cwd: string, enabledTools: string[], ctx?: ToolContext): Promise<SessionToolsResult> {
35
45
  const tools: StructuredToolInterface[] = []
36
46
  const cleanupFns: (() => Promise<void>)[] = []
37
- const runtime = loadRuntimeSettings()
38
- const commandTimeoutMs = runtime.shellCommandTimeoutMs
39
- const claudeTimeoutMs = runtime.claudeCodeTimeoutMs
40
- const cliProcessTimeoutMs = runtime.cliProcessTimeoutMs
41
- const appSettings = loadSettings()
42
- const toolPolicy = resolveSessionToolPolicy(enabledTools, appSettings)
43
- const activeTools = toolPolicy.enabledTools.includes('shell') && !toolPolicy.enabledTools.includes('process')
44
- ? [...toolPolicy.enabledTools, 'process']
45
- : toolPolicy.enabledTools
46
- const hasTool = (toolName: string) => activeTools.includes(toolName)
47
-
48
- if (toolPolicy.blockedTools.length > 0) {
49
- log.info('session-tools', 'Capability policy blocked tool families', {
50
- sessionId: ctx?.sessionId || null,
51
- agentId: ctx?.agentId || null,
52
- blockedTools: toolPolicy.blockedTools.map((entry) => `${entry.tool}:${entry.reason}`),
53
- })
54
- }
55
-
56
- const resolveCurrentSession = (): any | null => {
57
- if (!ctx?.sessionId) return null
58
- const sessions = loadSessions()
59
- return sessions[ctx.sessionId] || null
60
- }
47
+
48
+ try {
49
+ const runtime = loadRuntimeSettings()
50
+ const commandTimeoutMs = runtime.shellCommandTimeoutMs
51
+ const claudeTimeoutMs = runtime.claudeCodeTimeoutMs
52
+ const cliProcessTimeoutMs = runtime.cliProcessTimeoutMs
53
+ const appSettings = loadSettings()
54
+ const toolPolicy = resolveSessionToolPolicy(enabledTools, appSettings)
55
+ const expandedEnabledTools = expandToolIds(toolPolicy.enabledTools)
56
+ const expandedBlockedTools = expandToolIds(toolPolicy.blockedTools.map((entry) => entry.tool))
57
+ const blockedToolSet = new Set(expandedBlockedTools)
58
+ const filteredEnabledTools = expandedEnabledTools.filter((toolId) => !blockedToolSet.has(toolId))
59
+ const activeTools = filteredEnabledTools.includes('shell')
60
+ && !filteredEnabledTools.includes('process')
61
+ && !blockedToolSet.has('process')
62
+ ? [...filteredEnabledTools, 'process']
63
+ : filteredEnabledTools
64
+ const activeToolSet = new Set(activeTools)
65
+ const hasTool = (toolName: string) => activeToolSet.has(toolName)
61
66
 
62
- const readStoredDelegateResumeId = (key: 'claudeCode' | 'codex' | 'opencode'): string | null => {
63
- const session = resolveCurrentSession()
64
- if (!session?.delegateResumeIds || typeof session.delegateResumeIds !== 'object') return null
65
- const raw = session.delegateResumeIds[key]
66
- return typeof raw === 'string' && raw.trim() ? raw.trim() : null
67
- }
67
+ if (toolPolicy.blockedTools.length > 0) {
68
+ log.info('session-tools', 'Capability policy blocked tool families', {
69
+ sessionId: ctx?.sessionId || null,
70
+ agentId: ctx?.agentId || null,
71
+ blockedTools: toolPolicy.blockedTools.map((entry) => `${entry.tool}:${entry.reason}`),
72
+ })
73
+ }
68
74
 
69
- const persistDelegateResumeId = (key: 'claudeCode' | 'codex' | 'opencode', resumeId: string | null | undefined): void => {
70
- const normalized = typeof resumeId === 'string' ? resumeId.trim() : ''
71
- if (!normalized || !ctx?.sessionId) return
72
- const sessions = loadSessions()
73
- const target = sessions[ctx.sessionId]
74
- if (!target) return
75
- const current = (target.delegateResumeIds && typeof target.delegateResumeIds === 'object')
76
- ? target.delegateResumeIds
77
- : {}
78
- target.delegateResumeIds = {
79
- ...current,
80
- [key]: normalized,
75
+ const resolveCurrentSession = (): Session | null => {
76
+ if (!ctx?.sessionId) return null
77
+ const sessions = loadSessions()
78
+ return sessions[ctx.sessionId] || null
81
79
  }
82
- target.updatedAt = Date.now()
83
- sessions[ctx.sessionId] = target
84
- saveSessions(sessions)
85
- }
86
80
 
87
- const bctx: ToolBuildContext = {
88
- cwd,
89
- ctx,
90
- hasTool,
91
- cleanupFns,
92
- commandTimeoutMs,
93
- claudeTimeoutMs,
94
- cliProcessTimeoutMs,
95
- persistDelegateResumeId,
96
- readStoredDelegateResumeId,
97
- resolveCurrentSession,
98
- activeTools,
99
- }
81
+ const readStoredDelegateResumeId = (key: 'claudeCode' | 'codex' | 'opencode'): string | null => {
82
+ const session = resolveCurrentSession()
83
+ if (!session?.delegateResumeIds || typeof session.delegateResumeIds !== 'object') return null
84
+ const raw = session.delegateResumeIds[key]
85
+ return typeof raw === 'string' && raw.trim() ? raw.trim() : null
86
+ }
100
87
 
101
- tools.push(
102
- ...buildShellTools(bctx),
103
- ...buildFileTools(bctx),
104
- ...buildDelegateTools(bctx),
105
- ...buildWebTools(bctx),
106
- ...buildMemoryTools(bctx),
107
- ...buildCrudTools(bctx),
108
- ...buildSessionInfoTools(bctx),
109
- ...buildConnectorTools(bctx),
110
- ...buildContextTools(bctx),
111
- ...buildSandboxTools(bctx),
112
- ...buildOpenClawNodeTools(bctx),
113
- ...buildChatroomTools(bctx),
114
- ...buildSubagentTools(bctx),
115
- ...buildCanvasTools(bctx),
116
- ...buildHttpTools(bctx),
117
- ...buildGitTools(bctx),
118
- ...buildWalletTools(bctx),
119
- ...buildOpenClawWorkspaceTools(bctx),
120
- ...buildScheduleTools(bctx),
121
- )
88
+ const persistDelegateResumeId = (key: 'claudeCode' | 'codex' | 'opencode', resumeId: string | null | undefined): void => {
89
+ const normalized = typeof resumeId === 'string' ? resumeId.trim() : ''
90
+ if (!normalized || !ctx?.sessionId) return
91
+ const sessions = loadSessions()
92
+ const target = sessions[ctx.sessionId]
93
+ if (!target) return
94
+ const current = (target.delegateResumeIds && typeof target.delegateResumeIds === 'object')
95
+ ? target.delegateResumeIds
96
+ : {}
97
+ target.delegateResumeIds = {
98
+ ...current,
99
+ [key]: normalized,
100
+ }
101
+ target.updatedAt = Date.now()
102
+ sessions[ctx.sessionId] = target
103
+ saveSessions(sessions)
104
+ }
122
105
 
123
- // ---------------------------------------------------------------------------
124
- // MCP server tools — first-class injection (each MCP tool becomes its own LangChain tool)
125
- // ---------------------------------------------------------------------------
126
- const disabledMcpToolNames = new Set<string>(ctx?.mcpDisabledTools ?? [])
106
+ const bctx: ToolBuildContext = {
107
+ cwd,
108
+ ctx,
109
+ hasTool,
110
+ cleanupFns,
111
+ commandTimeoutMs,
112
+ claudeTimeoutMs,
113
+ cliProcessTimeoutMs,
114
+ persistDelegateResumeId,
115
+ readStoredDelegateResumeId,
116
+ resolveCurrentSession,
117
+ activeTools,
118
+ }
127
119
 
128
- if (ctx?.mcpServerIds?.length) {
129
- const mcpConnections: Array<{ client: any; transport: any }> = []
130
- const allMcpServers = loadMcpServers()
120
+ // 1. Build Native Bridge Tools (Legacy enablement)
121
+ tools.push(
122
+ ...buildShellTools(bctx),
123
+ ...buildFileTools(bctx),
124
+ ...buildEditFileTools(bctx),
125
+ ...buildDelegateTools(bctx),
126
+ ...buildWebTools(bctx),
127
+ ...buildMemoryTools(bctx),
128
+ ...buildPlatformTools(bctx),
129
+ ...buildSandboxTools(bctx),
130
+ ...buildChatroomTools(bctx),
131
+ ...buildSubagentTools(bctx),
132
+ ...buildCanvasTools(bctx),
133
+ ...buildHttpTools(bctx),
134
+ ...buildGitTools(bctx),
135
+ ...buildWalletTools(bctx),
136
+ ...buildOpenClawWorkspaceTools(bctx),
137
+ ...buildScheduleTools(bctx),
138
+ ...buildSessionInfoTools(bctx),
139
+ ...buildOpenClawNodeTools(bctx),
140
+ ...buildContextTools(bctx),
141
+ ...buildConnectorTools(bctx),
142
+ ...buildDiscoveryTools(bctx),
143
+ ...buildMonitorTools(bctx),
144
+ ...buildSampleUITools(bctx),
145
+ ...buildPluginCreatorTools(bctx),
146
+ )
131
147
 
132
- for (const serverId of ctx.mcpServerIds) {
133
- const config = allMcpServers[serverId]
134
- if (!config) continue
135
- try {
136
- const { connectMcpServer, mcpToolsToLangChain } = await import('../mcp-client')
137
- const conn = await connectMcpServer(config)
138
- mcpConnections.push(conn)
139
- const mcpLcTools = await mcpToolsToLangChain(conn.client, config.name)
140
- for (const t of mcpLcTools) {
141
- if (!disabledMcpToolNames.has(t.name)) {
142
- tools.push(t)
143
- }
148
+ // 2. Build Plugin Tools (Built-in + External)
149
+ try {
150
+ const pluginManager = getPluginManager()
151
+ const pluginTools = pluginManager.getTools(activeTools)
152
+ const existingNames = new Set(tools.map((t) => t.name))
153
+
154
+ for (const entry of pluginTools) {
155
+ const pt = entry.tool
156
+ if (existingNames.has(pt.name)) {
157
+ log.warn('session-tools', 'Skipping plugin tool due to duplicate name', {
158
+ toolName: pt.name,
159
+ pluginId: entry.pluginId,
160
+ })
161
+ continue
144
162
  }
145
- } catch (err: any) {
146
- log.warn('session-tools', `Failed to connect MCP server "${config.name}"`, { serverId, error: err.message })
147
- }
148
- }
163
+ existingNames.add(pt.name)
149
164
 
150
- // Register cleanup for all MCP connections
151
- cleanupFns.push(async () => {
152
- const { disconnectMcpServer } = await import('../mcp-client')
153
- for (const conn of mcpConnections) {
154
- await disconnectMcpServer(conn.client, conn.transport)
155
- }
156
- })
157
- }
158
-
159
- // ---------------------------------------------------------------------------
160
- // Plugin tools — native tools provided by SwarmClaw plugins
161
- // ---------------------------------------------------------------------------
162
- try {
163
- const pluginTools = getPluginManager().getPluginTools()
164
- for (const pt of pluginTools) {
165
- if (!disabledMcpToolNames.has(pt.name)) {
166
165
  tools.push(
167
166
  tool(
168
167
  async (args) => {
169
- const res = await pt.execute(args as Record<string, unknown>, {
170
- session: { id: ctx?.sessionId || 'unknown', agentId: ctx?.agentId } as Session,
171
- message: '',
172
- })
173
- return typeof res === 'string' ? res : JSON.stringify(res)
168
+ if (!pluginManager.isEnabled(entry.pluginId)) {
169
+ throw new Error(`Plugin "${entry.pluginId}" is disabled`)
170
+ }
171
+ try {
172
+ const normalizedArgs = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
173
+ const res = await pt.execute(normalizedArgs, {
174
+ session: { ...ctx, cwd } as any,
175
+ message: '',
176
+ })
177
+ pluginManager.recordExternalToolSuccess(entry.pluginId)
178
+ return typeof res === 'string' ? res : JSON.stringify(res)
179
+ } catch (err: unknown) {
180
+ pluginManager.recordExternalToolFailure(entry.pluginId, pt.name, err)
181
+ throw err
182
+ }
174
183
  },
175
184
  {
176
185
  name: pt.name,
@@ -180,39 +189,75 @@ export async function buildSessionTools(cwd: string, enabledTools: string[], ctx
180
189
  )
181
190
  )
182
191
  }
192
+ } catch (err: unknown) {
193
+ log.error('session-tools', 'Failed to load plugin tools', { error: err instanceof Error ? err.message : String(err) })
183
194
  }
184
- } catch (err: unknown) {
185
- log.error('session-tools', 'Failed to load plugin tools', { error: err instanceof Error ? err.message : String(err) })
186
- }
187
-
188
- // request_tool_access: always available
189
- tools.push(
190
- tool(
191
- async ({ toolId, reason }) => {
192
- return JSON.stringify({
193
- type: 'tool_request',
194
- toolId,
195
- reason,
196
- message: `Tool access request sent to user for "${toolId}". The user will be prompted to grant access — once granted, a follow-up message will arrive and you should immediately proceed with the original task using the newly available tool.`,
197
- })
198
- },
199
- {
200
- name: 'request_tool_access',
201
- description: 'Ask the user for access to a tool I don\'t currently have. They\'ll get a prompt to grant it, and once they do, I\'ll automatically continue where I left off. I should end my current response after calling this — no need to ask the user to confirm, it happens on its own.',
202
- schema: z.object({
203
- toolId: z.string().describe('The tool ID to request access for (e.g. manage_tasks, shell, claude_code)'),
204
- reason: z.string().describe('Brief explanation of why you need this tool'),
205
- }),
206
- },
207
- ),
208
- )
209
195
 
210
- return {
211
- tools,
212
- cleanup: async () => {
213
- for (const fn of cleanupFns) {
214
- try { await fn() } catch { /* ignore */ }
196
+ // 3. MCP server tools
197
+ const disabledMcpToolNames = new Set<string>(ctx?.mcpDisabledTools ?? [])
198
+ if (ctx?.mcpServerIds?.length) {
199
+ const mcpConnections: Array<{ client: any; transport: any }> = []
200
+ const allMcpServers = loadMcpServers()
201
+ for (const serverId of ctx.mcpServerIds) {
202
+ const config = allMcpServers[serverId]
203
+ if (!config) continue
204
+ try {
205
+ const { connectMcpServer, mcpToolsToLangChain } = await import('../mcp-client')
206
+ const conn = await connectMcpServer(config)
207
+ mcpConnections.push(conn)
208
+ const mcpLcTools = await mcpToolsToLangChain(conn.client, config.name)
209
+ for (const t of mcpLcTools) {
210
+ if (!disabledMcpToolNames.has(t.name)) {
211
+ tools.push(t)
212
+ }
213
+ }
214
+ } catch (err: any) {
215
+ log.warn('session-tools', `Failed to connect MCP server "${config.name}"`, { serverId, error: err.message })
216
+ }
215
217
  }
216
- },
218
+ cleanupFns.push(async () => {
219
+ const { disconnectMcpServer } = await import('../mcp-client')
220
+ for (const conn of mcpConnections) {
221
+ await disconnectMcpServer(conn.client, conn.transport)
222
+ }
223
+ })
224
+ }
225
+
226
+ // 4. Always available: request_tool_access
227
+ tools.push(
228
+ tool(
229
+ async (args) => {
230
+ const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
231
+ const toolId = normalized.toolId as string | undefined
232
+ const reason = normalized.reason as string | undefined
233
+ return JSON.stringify({
234
+ type: 'tool_request',
235
+ toolId,
236
+ reason,
237
+ message: `Tool access request sent to user for "${toolId}". The user will be prompted to grant access — once granted, a follow-up message will arrive and you should immediately proceed with the original task using the newly available tool.`,
238
+ })
239
+ },
240
+ {
241
+ name: 'request_tool_access',
242
+ description: 'Ask the user for access to a plugin I don\'t currently have.',
243
+ schema: z.object({
244
+ toolId: z.string().describe('The plugin ID to request access for'),
245
+ reason: z.string().describe('Brief explanation of why you need this plugin'),
246
+ }),
247
+ },
248
+ ),
249
+ )
250
+
251
+ return {
252
+ tools,
253
+ cleanup: async () => {
254
+ for (const fn of cleanupFns) {
255
+ try { await fn() } catch { /* ignore */ }
256
+ }
257
+ },
258
+ }
259
+ } catch (err: any) {
260
+ console.error('[session-tools] buildSessionTools critical failure:', err.message)
261
+ throw err
217
262
  }
218
263
  }