@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
@@ -0,0 +1,198 @@
1
+ import { z } from 'zod'
2
+ import { tool, type StructuredToolInterface } from '@langchain/core/tools'
3
+ import type { ToolBuildContext } from './context'
4
+ import { getPluginManager } from '../plugins'
5
+ import type { Plugin, PluginHooks, ClawHubSkill } from '@/types'
6
+ import { searchClawHub } from '../clawhub-client'
7
+ import { normalizeToolInputArgs } from './normalize-tool-args'
8
+ import { toolIdMatches } from '../tool-aliases'
9
+ import { loadSessions } from '../storage'
10
+
11
+ /**
12
+ * Unified Discovery Logic
13
+ */
14
+ async function executeDiscoveryAction(args: Record<string, unknown>, bctx?: ToolBuildContext) {
15
+ const normalized = normalizeToolInputArgs(args)
16
+ const action = normalized.action
17
+ const approved = normalized.approved
18
+ const pluginId = typeof normalized.pluginId === 'string'
19
+ ? normalized.pluginId.trim()
20
+ : typeof normalized.plugin_id === 'string'
21
+ ? normalized.plugin_id.trim()
22
+ : undefined
23
+ const url = typeof normalized.url === 'string' ? normalized.url.trim() : undefined
24
+ const reason = normalized.reason as string | undefined
25
+ const manager = getPluginManager()
26
+ const q = typeof normalized.query === 'string' ? normalized.query : ''
27
+
28
+ console.log('[discovery] Executing action:', action, { query: q, pluginId })
29
+
30
+ try {
31
+ switch (action) {
32
+ case 'list':
33
+ case 'discover': {
34
+ const plugins = manager.listPlugins()
35
+ return JSON.stringify(plugins.map(p => ({
36
+ id: p.filename,
37
+ name: p.name,
38
+ description: p.description,
39
+ enabled: p.enabled,
40
+ isBuiltin: !p.filename.endsWith('.js') && !p.filename.endsWith('.mjs')
41
+ })), null, 2)
42
+ }
43
+ case 'search_marketplace': {
44
+ const results: Record<string, unknown>[] = []
45
+
46
+ try {
47
+ console.log('[discovery] Searching ClawHub...')
48
+ const hubResults = await searchClawHub(q)
49
+ if (hubResults && hubResults.skills) {
50
+ results.push(...hubResults.skills.map((s) => ({
51
+ id: s.id,
52
+ name: s.name,
53
+ description: s.description,
54
+ author: s.author,
55
+ source: 'clawhub',
56
+ url: (s as ClawHubSkill & { rawUrl?: string }).rawUrl ?? s.url
57
+ })))
58
+ }
59
+ } catch (err: unknown) {
60
+ console.error('[discovery] ClawHub search failed:', err instanceof Error ? err.message : String(err))
61
+ }
62
+
63
+ try {
64
+ console.log('[discovery] Searching SwarmClaw registry...')
65
+ const scRes = await fetch('https://swarmclaw.ai/registry/plugins.json', { signal: AbortSignal.timeout(5000) })
66
+ if (scRes.ok) {
67
+ const scPlugins = await scRes.json()
68
+ const filtered = (scPlugins as Record<string, unknown>[]).filter((p: Record<string, unknown>) =>
69
+ !q || (String(p.name || '')).toLowerCase().includes(q.toLowerCase()) || (String(p.description || '')).toLowerCase().includes(q.toLowerCase())
70
+ )
71
+ results.push(...filtered.map(p => ({ ...p, source: 'swarmclaw' })))
72
+ }
73
+ } catch (err: unknown) {
74
+ console.error('[discovery] SC Registry search failed:', err instanceof Error ? err.message : String(err))
75
+ }
76
+
77
+ if (results.length === 0) {
78
+ return 'No marketplace plugins found for your query.'
79
+ }
80
+
81
+ return JSON.stringify(results, null, 2)
82
+ }
83
+ case 'request_access': {
84
+ if (!pluginId) {
85
+ return JSON.stringify({ error: 'pluginId is required for request_access. Use "discover" first to find the plugin filename.' })
86
+ }
87
+ // Check if the agent already has access via alias expansion (e.g. manage_platform includes manage_secrets)
88
+ if (bctx?.ctx?.sessionId) {
89
+ const allSessions = loadSessions()
90
+ const currentSession = allSessions[bctx.ctx.sessionId]
91
+ if (currentSession && toolIdMatches(currentSession.tools, pluginId)) {
92
+ return JSON.stringify({
93
+ alreadyGranted: true,
94
+ pluginId,
95
+ message: `You already have access to "${pluginId}" — proceed to use it directly.`,
96
+ })
97
+ }
98
+ }
99
+ const { requestApproval } = await import('../approvals')
100
+ requestApproval({
101
+ category: 'tool_access',
102
+ title: `Enable Plugin: ${pluginId}`,
103
+ description: reason || `Agent is requesting access to the "${pluginId}" plugin.`,
104
+ data: { toolId: pluginId, pluginId, requestedBy: bctx?.ctx?.agentId || 'unknown', reason: reason || '' },
105
+ agentId: bctx?.ctx?.agentId,
106
+ sessionId: bctx?.ctx?.sessionId,
107
+ })
108
+ return JSON.stringify({
109
+ type: 'plugin_request',
110
+ pluginId,
111
+ toolId: pluginId,
112
+ reason,
113
+ message: `Plugin access request sent to user for "${pluginId}". Once granted, I'll automatically continue.`,
114
+ })
115
+ }
116
+ case 'install_request': {
117
+ if (!url) {
118
+ return JSON.stringify({ error: 'url is required for install_request.' })
119
+ }
120
+ if (approved !== true) {
121
+ const { requestApproval } = await import('../approvals')
122
+ requestApproval({
123
+ category: 'plugin_install',
124
+ title: `Install Plugin${pluginId ? `: ${pluginId}` : ' from URL'}`,
125
+ description: reason || `Agent wants to install a plugin${url ? ` from ${url}` : ''}.`,
126
+ data: { url, pluginId: pluginId || '', requestedBy: bctx?.ctx?.agentId || 'unknown', reason: reason || '' },
127
+ agentId: bctx?.ctx?.agentId,
128
+ sessionId: bctx?.ctx?.sessionId,
129
+ })
130
+ return JSON.stringify({
131
+ type: 'plugin_install_request',
132
+ url,
133
+ pluginId,
134
+ reason,
135
+ message: `I'm requesting to install a new plugin from ${url}. This will add new capabilities to the platform.`
136
+ })
137
+ }
138
+
139
+ return `Installation approved. Please go to the Plugins manager and install from: ${url}`
140
+ }
141
+ default:
142
+ return `Error: Unknown action "${action}"`
143
+ }
144
+ } catch (err: unknown) {
145
+ const msg = err instanceof Error ? err.message : String(err)
146
+ console.error('[discovery] executeDiscoveryAction failed:', msg)
147
+ return `Error: ${msg}`
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Register as a Built-in Plugin
153
+ */
154
+ const DiscoveryPlugin: Plugin = {
155
+ name: 'Core Discovery',
156
+ description: 'Discover available plugins, search marketplaces, request access, or suggest new installs.',
157
+ hooks: {} as PluginHooks,
158
+ tools: [
159
+ {
160
+ name: 'manage_capabilities',
161
+ description: 'Search for available plugins locally or in external marketplaces.',
162
+ parameters: {
163
+ type: 'object',
164
+ properties: {
165
+ action: { type: 'string', enum: ['discover', 'search_marketplace', 'request_access', 'install_request'] },
166
+ query: { type: 'string', description: 'Search term for marketplace' },
167
+ pluginId: { type: 'string', description: 'The ID or filename of the plugin' },
168
+ url: { type: 'string', description: 'URL for new plugin install request' },
169
+ reason: { type: 'string', description: 'Why you need this capability' }
170
+ },
171
+ required: ['action', 'reason']
172
+ },
173
+ execute: async (args) => executeDiscoveryAction(args)
174
+ }
175
+ ]
176
+ }
177
+
178
+ getPluginManager().registerBuiltin('discovery', DiscoveryPlugin)
179
+
180
+ export function buildDiscoveryTools(bctx: ToolBuildContext): StructuredToolInterface[] {
181
+ // Always allow agents to discover what they can do
182
+ return [
183
+ tool(
184
+ async (args) => executeDiscoveryAction(args, bctx),
185
+ {
186
+ name: 'manage_capabilities',
187
+ description: DiscoveryPlugin.tools![0].description,
188
+ schema: z.object({
189
+ action: z.enum(['discover', 'search_marketplace', 'request_access', 'install_request']).describe('The discovery action to perform'),
190
+ query: z.string().optional().describe('The search query for marketplace actions'),
191
+ pluginId: z.string().optional(),
192
+ url: z.string().optional(),
193
+ reason: z.string().describe('Why you need to perform this discovery action')
194
+ })
195
+ }
196
+ )
197
+ ]
198
+ }
@@ -0,0 +1,82 @@
1
+ import { z } from 'zod'
2
+ import { tool, type StructuredToolInterface } from '@langchain/core/tools'
3
+ import fs from 'fs'
4
+ import type { ToolBuildContext } from './context'
5
+ import { safePath } from './context'
6
+ import type { Plugin, PluginHooks } from '@/types'
7
+ import { getPluginManager } from '../plugins'
8
+ import { normalizeToolInputArgs } from './normalize-tool-args'
9
+
10
+ /**
11
+ * Core Edit File Execution Logic (Surgical Search and Replace)
12
+ */
13
+ async function executeEditFile(args: { filePath: string; oldString: string; newString: string }, context: { cwd: string }) {
14
+ const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
15
+ const filePath = (normalized.filePath ?? normalized.path) as string
16
+ const oldString = normalized.oldString as string
17
+ const newString = normalized.newString as string
18
+ try {
19
+ const resolved = safePath(context.cwd, filePath)
20
+ if (!fs.existsSync(resolved)) return `Error: File not found: ${filePath}`
21
+
22
+ const content = fs.readFileSync(resolved, 'utf-8')
23
+ const count = content.split(oldString).length - 1
24
+
25
+ if (count === 0) {
26
+ return `Error: Exact match for 'oldString' not found in ${filePath}. Use 'files' with action='read' to check content.`
27
+ }
28
+ if (count > 1) {
29
+ return `Error: Multiple matches (${count}) found for 'oldString'. Please provide more context to ensure a surgical replacement.`
30
+ }
31
+
32
+ const updated = content.replace(oldString, newString)
33
+ fs.writeFileSync(resolved, updated, 'utf-8')
34
+ return `Successfully updated ${filePath} (1 replacement made).`
35
+ } catch (err: any) {
36
+ return `Error: ${err.message}`
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Register as a Built-in Plugin
42
+ */
43
+ const EditFilePlugin: Plugin = {
44
+ name: 'Core Edit File',
45
+ description: 'Surgical search-and-replace within existing files.',
46
+ hooks: {} as PluginHooks,
47
+ tools: [
48
+ {
49
+ name: 'edit_file',
50
+ description: 'Surgically replace a specific string within a file.',
51
+ parameters: {
52
+ type: 'object',
53
+ properties: {
54
+ filePath: { type: 'string' },
55
+ oldString: { type: 'string', description: 'The exact literal text to replace' },
56
+ newString: { type: 'string', description: 'The replacement text' }
57
+ },
58
+ required: ['filePath', 'oldString', 'newString']
59
+ },
60
+ execute: async (args, context) => executeEditFile(args as any, { cwd: context.session.cwd || process.cwd() })
61
+ }
62
+ ]
63
+ }
64
+
65
+ getPluginManager().registerBuiltin('edit_file', EditFilePlugin)
66
+
67
+ /**
68
+ * Legacy Bridge
69
+ */
70
+ export function buildEditFileTools(bctx: ToolBuildContext): StructuredToolInterface[] {
71
+ if (!bctx.hasTool('edit_file')) return []
72
+ return [
73
+ tool(
74
+ async (args) => executeEditFile(args as any, { cwd: bctx.cwd }),
75
+ {
76
+ name: 'edit_file',
77
+ description: EditFilePlugin.tools![0].description,
78
+ schema: z.object({}).passthrough()
79
+ }
80
+ )
81
+ ]
82
+ }
@@ -0,0 +1,39 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { normalizeSendFilePaths } from './file'
4
+
5
+ describe('normalizeSendFilePaths', () => {
6
+ it('reads top-level filePath', () => {
7
+ const out = normalizeSendFilePaths({ filePath: 'foo.png' })
8
+ assert.deepEqual(out, ['foo.png'])
9
+ })
10
+
11
+ it('reads nested input.files string array payload', () => {
12
+ const out = normalizeSendFilePaths({
13
+ input: {
14
+ files: ['a.png', 'b.png'],
15
+ },
16
+ })
17
+ assert.deepEqual(out, ['a.png', 'b.png'])
18
+ })
19
+
20
+ it('reads stringified input payload', () => {
21
+ const out = normalizeSendFilePaths({
22
+ input: JSON.stringify({
23
+ files: ['a.png'],
24
+ }),
25
+ })
26
+ assert.deepEqual(out, ['a.png'])
27
+ })
28
+
29
+ it('reads files object entries with path/filePath and dedupes', () => {
30
+ const out = normalizeSendFilePaths({
31
+ files: [
32
+ { path: 'a.png' },
33
+ { filePath: 'b.png' },
34
+ { path: 'a.png' },
35
+ ],
36
+ })
37
+ assert.deepEqual(out, ['a.png', 'b.png'])
38
+ })
39
+ })