@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.
- package/README.md +82 -39
- package/next.config.ts +31 -6
- package/package.json +3 -2
- package/src/app/api/agents/[id]/thread/route.ts +1 -0
- package/src/app/api/agents/route.ts +19 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/eval/run/route.ts +37 -0
- package/src/app/api/eval/scenarios/route.ts +24 -0
- package/src/app/api/eval/suite/route.ts +29 -0
- package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
- package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
- package/src/app/api/memory/graph/route.ts +46 -0
- package/src/app/api/memory/route.ts +36 -5
- package/src/app/api/notifications/route.ts +3 -0
- package/src/app/api/plugins/install/route.ts +57 -5
- package/src/app/api/plugins/marketplace/route.ts +73 -22
- package/src/app/api/plugins/route.ts +61 -1
- package/src/app/api/plugins/ui/route.ts +34 -0
- package/src/app/api/sessions/[id]/checkpoints/route.ts +31 -0
- package/src/app/api/sessions/[id]/restore/route.ts +36 -0
- package/src/app/api/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- package/src/app/api/souls/[id]/route.ts +65 -0
- package/src/app/api/souls/route.ts +70 -0
- package/src/app/api/tasks/[id]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +16 -3
- package/src/app/api/tasks/route.ts +10 -2
- package/src/app/api/usage/route.ts +9 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +37 -0
- package/src/components/activity/activity-feed.tsx +9 -2
- package/src/components/agents/agent-avatar.tsx +5 -1
- package/src/components/agents/agent-card.tsx +55 -9
- package/src/components/agents/agent-sheet.tsx +112 -34
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/agents/soul-library-picker.tsx +84 -13
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- package/src/components/chat/activity-moment.tsx +2 -0
- package/src/components/chat/chat-area.tsx +11 -0
- package/src/components/chat/chat-header.tsx +69 -25
- package/src/components/chat/chat-tool-toggles.tsx +2 -2
- package/src/components/chat/checkpoint-timeline.tsx +112 -0
- package/src/components/chat/code-block.tsx +3 -1
- package/src/components/chat/exec-approval-card.tsx +8 -1
- package/src/components/chat/message-bubble.tsx +164 -4
- package/src/components/chat/message-list.tsx +46 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/session-debug-panel.tsx +106 -84
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/task-approval-card.tsx +78 -0
- package/src/components/chat/thinking-indicator.tsx +48 -12
- package/src/components/chat/tool-call-bubble.tsx +3 -0
- package/src/components/chat/tool-request-banner.tsx +39 -20
- package/src/components/chatrooms/chatroom-list.tsx +11 -4
- package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
- package/src/components/connectors/connector-list.tsx +33 -11
- package/src/components/connectors/connector-sheet.tsx +37 -7
- package/src/components/home/home-view.tsx +54 -24
- package/src/components/input/chat-input.tsx +22 -1
- package/src/components/knowledge/knowledge-list.tsx +17 -18
- package/src/components/knowledge/knowledge-sheet.tsx +9 -5
- package/src/components/layout/app-layout.tsx +87 -19
- package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
- package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
- package/src/components/memory/memory-browser.tsx +73 -45
- package/src/components/memory/memory-graph-view.tsx +203 -0
- package/src/components/memory/memory-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +214 -60
- package/src/components/plugins/plugin-sheet.tsx +119 -24
- package/src/components/projects/project-list.tsx +17 -9
- package/src/components/providers/provider-list.tsx +21 -6
- package/src/components/providers/provider-sheet.tsx +42 -25
- package/src/components/runs/run-list.tsx +17 -13
- package/src/components/schedules/schedule-card.tsx +10 -3
- package/src/components/schedules/schedule-list.tsx +2 -2
- package/src/components/schedules/schedule-sheet.tsx +28 -9
- package/src/components/secrets/secret-sheet.tsx +7 -2
- package/src/components/secrets/secrets-list.tsx +18 -5
- package/src/components/sessions/new-session-sheet.tsx +183 -376
- package/src/components/sessions/session-card.tsx +10 -2
- package/src/components/settings/gateway-connection-panel.tsx +9 -8
- package/src/components/shared/command-palette.tsx +13 -5
- package/src/components/shared/empty-state.tsx +20 -8
- package/src/components/shared/hint-tip.tsx +31 -0
- package/src/components/shared/notification-center.tsx +134 -86
- package/src/components/shared/profile-sheet.tsx +4 -0
- package/src/components/shared/settings/plugin-manager.tsx +360 -135
- package/src/components/shared/settings/section-capability-policy.tsx +3 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +149 -4
- package/src/components/skills/clawhub-browser.tsx +1 -0
- package/src/components/skills/skill-list.tsx +31 -12
- package/src/components/skills/skill-sheet.tsx +20 -7
- package/src/components/tasks/approvals-panel.tsx +224 -0
- package/src/components/tasks/task-board.tsx +20 -12
- package/src/components/tasks/task-card.tsx +21 -7
- package/src/components/tasks/task-column.tsx +4 -3
- package/src/components/tasks/task-list.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +130 -1
- package/src/components/ui/dialog.tsx +1 -0
- package/src/components/ui/sheet.tsx +1 -0
- package/src/components/usage/metrics-dashboard.tsx +72 -48
- package/src/components/wallets/wallet-panel.tsx +65 -41
- package/src/components/wallets/wallet-section.tsx +9 -3
- package/src/components/webhooks/webhook-list.tsx +21 -12
- package/src/components/webhooks/webhook-sheet.tsx +13 -3
- package/src/lib/approval-display.test.ts +45 -0
- package/src/lib/approval-display.ts +62 -0
- package/src/lib/clipboard.ts +38 -0
- package/src/lib/memory.ts +8 -0
- package/src/lib/providers/claude-cli.ts +5 -3
- package/src/lib/providers/index.ts +67 -21
- package/src/lib/runtime-loop.ts +3 -2
- package/src/lib/server/approvals.ts +150 -0
- package/src/lib/server/chat-execution.ts +319 -74
- package/src/lib/server/chatroom-helpers.ts +63 -5
- package/src/lib/server/chatroom-orchestration.ts +74 -0
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/context-manager.ts +132 -50
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +112 -1
- package/src/lib/server/data-dir.ts +18 -1
- package/src/lib/server/eval/runner.ts +126 -0
- package/src/lib/server/eval/scenarios.ts +218 -0
- package/src/lib/server/eval/scorer.ts +96 -0
- package/src/lib/server/eval/store.ts +37 -0
- package/src/lib/server/eval/types.ts +48 -0
- package/src/lib/server/execution-log.ts +12 -8
- package/src/lib/server/guardian.ts +34 -0
- package/src/lib/server/heartbeat-service.ts +53 -1
- package/src/lib/server/integrity-monitor.ts +208 -0
- package/src/lib/server/langgraph-checkpoint.ts +10 -0
- package/src/lib/server/link-understanding.ts +55 -0
- package/src/lib/server/llm-response-cache.test.ts +102 -0
- package/src/lib/server/llm-response-cache.ts +227 -0
- package/src/lib/server/main-agent-loop.ts +115 -16
- package/src/lib/server/main-session.ts +6 -3
- package/src/lib/server/mcp-conformance.test.ts +18 -0
- package/src/lib/server/mcp-conformance.ts +233 -0
- package/src/lib/server/memory-db.ts +193 -19
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/mmr.ts +73 -0
- package/src/lib/server/orchestrator-lg.ts +7 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +662 -132
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/query-expansion.ts +57 -0
- package/src/lib/server/queue.ts +280 -11
- package/src/lib/server/runtime-settings.ts +9 -0
- package/src/lib/server/session-run-manager.test.ts +23 -0
- package/src/lib/server/session-run-manager.ts +32 -2
- package/src/lib/server/session-tools/canvas.ts +85 -50
- package/src/lib/server/session-tools/chatroom.ts +130 -127
- package/src/lib/server/session-tools/connector.ts +233 -454
- package/src/lib/server/session-tools/context-mgmt.ts +87 -105
- package/src/lib/server/session-tools/crud.ts +84 -7
- package/src/lib/server/session-tools/delegate.ts +351 -752
- package/src/lib/server/session-tools/discovery.ts +198 -0
- package/src/lib/server/session-tools/edit_file.ts +82 -0
- package/src/lib/server/session-tools/file-send.test.ts +39 -0
- package/src/lib/server/session-tools/file.ts +257 -425
- package/src/lib/server/session-tools/git.ts +87 -47
- package/src/lib/server/session-tools/http.ts +95 -33
- package/src/lib/server/session-tools/index.ts +217 -138
- package/src/lib/server/session-tools/memory.ts +154 -239
- package/src/lib/server/session-tools/monitor.ts +126 -0
- package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
- package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
- package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
- package/src/lib/server/session-tools/platform.ts +86 -0
- package/src/lib/server/session-tools/plugin-creator.ts +239 -0
- package/src/lib/server/session-tools/sample-ui.ts +97 -0
- package/src/lib/server/session-tools/sandbox.ts +175 -148
- package/src/lib/server/session-tools/schedule.ts +78 -0
- package/src/lib/server/session-tools/session-info.ts +104 -410
- package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
- package/src/lib/server/session-tools/shell.ts +171 -143
- package/src/lib/server/session-tools/subagent.ts +77 -77
- package/src/lib/server/session-tools/wallet.ts +182 -106
- package/src/lib/server/session-tools/web.ts +181 -327
- package/src/lib/server/storage.ts +36 -0
- package/src/lib/server/stream-agent-chat.ts +348 -242
- package/src/lib/server/task-quality-gate.test.ts +44 -0
- package/src/lib/server/task-quality-gate.ts +67 -0
- package/src/lib/server/task-validation.test.ts +78 -0
- package/src/lib/server/task-validation.ts +67 -2
- package/src/lib/server/tool-aliases.ts +68 -0
- package/src/lib/server/tool-capability-policy.ts +24 -5
- package/src/lib/server/tool-retry.ts +62 -0
- package/src/lib/server/transcript-repair.ts +72 -0
- package/src/lib/setup-defaults.ts +1 -0
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +24 -23
- package/src/lib/validation/schemas.ts +13 -0
- package/src/lib/view-routes.ts +2 -23
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +155 -10
|
@@ -4,453 +4,285 @@ import fs from 'fs'
|
|
|
4
4
|
import path from 'path'
|
|
5
5
|
import { UPLOAD_DIR } from '../storage'
|
|
6
6
|
import type { ToolBuildContext } from './context'
|
|
7
|
-
import { safePath, truncate, listDirRecursive,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
import { safePath, truncate, listDirRecursive, MAX_FILE } from './context'
|
|
8
|
+
import type { Plugin, PluginHooks } from '@/types'
|
|
9
|
+
import { getPluginManager } from '../plugins'
|
|
10
|
+
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
11
|
+
|
|
12
|
+
function resolveFileToolPath(cwd: string, target: string): string {
|
|
13
|
+
try {
|
|
14
|
+
return safePath(cwd, target)
|
|
15
|
+
} catch (err: unknown) {
|
|
16
|
+
if (!path.isAbsolute(target)) throw err
|
|
17
|
+
return safePath(process.cwd(), target)
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
tools.push(
|
|
34
|
-
tool(
|
|
35
|
-
async ({ filePath }) => {
|
|
36
|
-
try {
|
|
37
|
-
const resolved = safePath(bctx.cwd, filePath)
|
|
38
|
-
const content = fs.readFileSync(resolved, 'utf-8')
|
|
39
|
-
return truncate(content, MAX_FILE)
|
|
40
|
-
} catch (err: unknown) {
|
|
41
|
-
return `Error reading file: ${err instanceof Error ? err.message : String(err)}`
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
name: 'read_file',
|
|
46
|
-
description: 'Read a file from the session working directory.',
|
|
47
|
-
schema: z.object({
|
|
48
|
-
filePath: z.string().describe('Relative path to the file'),
|
|
49
|
-
}),
|
|
50
|
-
},
|
|
51
|
-
),
|
|
52
|
-
)
|
|
21
|
+
/**
|
|
22
|
+
* Unified File Execution Logic
|
|
23
|
+
*/
|
|
24
|
+
async function executeFileAction(args: Record<string, unknown>, bctx: { cwd: string }) {
|
|
25
|
+
const normalized = normalizeToolInputArgs(args)
|
|
26
|
+
// Normalize filePath/content for single-file mode
|
|
27
|
+
const files = normalized.files as Array<Record<string, unknown>> | undefined
|
|
28
|
+
let action = normalized.action as string | undefined
|
|
29
|
+
const encoding = normalized.encoding as string | undefined
|
|
30
|
+
|
|
31
|
+
// Resiliency: check if action is buried in the files array
|
|
32
|
+
if (!action && Array.isArray(files) && files.length > 0) {
|
|
33
|
+
action = files[0].action as string
|
|
53
34
|
}
|
|
54
35
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
36
|
+
const filePath = (normalized.filePath || normalized.path) as string | undefined
|
|
37
|
+
const content = normalized.content as string | undefined
|
|
38
|
+
const dirPath = (normalized.dirPath || normalized.directory || normalized.path) as string | undefined
|
|
39
|
+
const sourcePath = (normalized.sourcePath || normalized.source || normalized.from) as string | undefined
|
|
40
|
+
const destinationPath = (normalized.destinationPath || normalized.destination || normalized.to) as string | undefined
|
|
41
|
+
const overwrite = !!normalized.overwrite
|
|
42
|
+
const recursive = !!normalized.recursive
|
|
43
|
+
const force = !!normalized.force
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
switch (action) {
|
|
47
|
+
case 'read': {
|
|
48
|
+
const target = filePath || (files?.[0]?.path as string | undefined)
|
|
49
|
+
if (!target) return 'Error: no filePath or path provided.'
|
|
50
|
+
const resolved = resolveFileToolPath(bctx.cwd, target)
|
|
51
|
+
return truncate(fs.readFileSync(resolved, 'utf-8'), MAX_FILE)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
case 'write': {
|
|
55
|
+
// Handle bulk files if provided
|
|
56
|
+
const filesToWrite: Array<Record<string, unknown>> = Array.isArray(files) ? files : [{ path: filePath, content }]
|
|
57
|
+
const results: string[] = []
|
|
58
|
+
|
|
59
|
+
for (const file of filesToWrite) {
|
|
60
|
+
const targetPath = (file.path || file.filePath) as string | undefined
|
|
61
|
+
if (!targetPath) continue
|
|
62
|
+
const fileContent = (file.content ?? '') as string
|
|
63
|
+
|
|
64
|
+
const resolved = resolveFileToolPath(bctx.cwd, targetPath)
|
|
65
|
+
fs.mkdirSync(path.dirname(resolved), { recursive: true })
|
|
66
|
+
|
|
67
|
+
if (encoding === 'base64' && typeof fileContent === 'string') {
|
|
68
|
+
const buf = Buffer.from(fileContent, 'base64')
|
|
69
|
+
fs.writeFileSync(resolved, buf)
|
|
70
|
+
results.push(`Written ${targetPath} (${buf.length} bytes, binary)`)
|
|
71
|
+
} else {
|
|
72
|
+
fs.writeFileSync(resolved, fileContent, 'utf-8')
|
|
73
|
+
results.push(`Written ${targetPath} (${fileContent.length} bytes)`)
|
|
71
74
|
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
}
|
|
76
|
+
return results.join('\n') || 'Error: no files to write.'
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
case 'list': {
|
|
80
|
+
const resolved = resolveFileToolPath(bctx.cwd, dirPath || filePath || '.')
|
|
81
|
+
const tree = listDirRecursive(resolved, 0, 3)
|
|
82
|
+
return tree.length ? tree.join('\n') : '(empty directory)'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
case 'copy': {
|
|
86
|
+
if (!sourcePath) return 'Error: sourcePath is required for copy action.'
|
|
87
|
+
if (!destinationPath) return 'Error: destinationPath is required for copy action.'
|
|
88
|
+
const src = resolveFileToolPath(bctx.cwd, sourcePath)
|
|
89
|
+
const dest = resolveFileToolPath(bctx.cwd, destinationPath)
|
|
90
|
+
if (fs.existsSync(dest) && !overwrite) return `Error: destination exists`
|
|
91
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
|
92
|
+
fs.copyFileSync(src, dest)
|
|
93
|
+
return `Copied ${sourcePath} to ${destinationPath}`
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
case 'move': {
|
|
97
|
+
if (!sourcePath) return 'Error: sourcePath is required for move action.'
|
|
98
|
+
if (!destinationPath) return 'Error: destinationPath is required for move action.'
|
|
99
|
+
const src = resolveFileToolPath(bctx.cwd, sourcePath)
|
|
100
|
+
const dest = resolveFileToolPath(bctx.cwd, destinationPath)
|
|
101
|
+
if (fs.existsSync(dest) && !overwrite) return `Error: destination exists`
|
|
102
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
|
103
|
+
if (fs.existsSync(dest) && overwrite) fs.unlinkSync(dest)
|
|
104
|
+
fs.renameSync(src, dest)
|
|
105
|
+
return `Moved ${sourcePath} to ${destinationPath}`
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
case 'delete': {
|
|
109
|
+
const target = filePath || (files?.[0]?.path as string | undefined)
|
|
110
|
+
if (!target) return 'Error: no filePath or path provided.'
|
|
111
|
+
const resolved = resolveFileToolPath(bctx.cwd, target)
|
|
112
|
+
if (resolved === path.resolve(bctx.cwd) || resolved === path.resolve(process.cwd())) return 'Error: cannot delete root'
|
|
113
|
+
fs.rmSync(resolved, { recursive: !!recursive, force: !!force })
|
|
114
|
+
return `Deleted ${target}`
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
default:
|
|
118
|
+
return `Error: Unknown action "${action}"`
|
|
119
|
+
}
|
|
120
|
+
} catch (err: unknown) {
|
|
121
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
84
122
|
}
|
|
123
|
+
}
|
|
85
124
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const tree = listDirRecursive(resolved, 0, 3)
|
|
93
|
-
return tree.length ? tree.join('\n') : '(empty directory)'
|
|
94
|
-
} catch (err: unknown) {
|
|
95
|
-
return `Error listing files: ${err instanceof Error ? err.message : String(err)}`
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
name: 'list_files',
|
|
100
|
-
description: 'List files in the session working directory recursively (max depth 3).',
|
|
101
|
-
schema: z.object({
|
|
102
|
-
dirPath: z.string().optional().describe('Relative path to list (defaults to working directory)'),
|
|
103
|
-
}),
|
|
104
|
-
},
|
|
105
|
-
),
|
|
106
|
-
)
|
|
125
|
+
function collectSendFilePaths(payload: unknown, into: string[]): void {
|
|
126
|
+
if (!payload) return
|
|
127
|
+
if (typeof payload === 'string') {
|
|
128
|
+
const trimmed = payload.trim()
|
|
129
|
+
if (trimmed) into.push(trimmed)
|
|
130
|
+
return
|
|
107
131
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
tool(
|
|
112
|
-
async ({ sourcePath, destinationPath, overwrite }) => {
|
|
113
|
-
try {
|
|
114
|
-
const source = safePath(bctx.cwd, sourcePath)
|
|
115
|
-
const destination = safePath(bctx.cwd, destinationPath)
|
|
116
|
-
if (!fs.existsSync(source)) return `Error: source file not found: ${sourcePath}`
|
|
117
|
-
const sourceStat = fs.statSync(source)
|
|
118
|
-
if (sourceStat.isDirectory()) return `Error: source must be a file (directories are not supported by copy_file).`
|
|
119
|
-
if (fs.existsSync(destination) && !overwrite) return `Error: destination already exists: ${destinationPath} (set overwrite=true to replace).`
|
|
120
|
-
fs.mkdirSync(path.dirname(destination), { recursive: true })
|
|
121
|
-
fs.copyFileSync(source, destination)
|
|
122
|
-
return `File copied: ${sourcePath} -> ${destinationPath}`
|
|
123
|
-
} catch (err: unknown) {
|
|
124
|
-
return `Error copying file: ${err instanceof Error ? err.message : String(err)}`
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
name: 'copy_file',
|
|
129
|
-
description: 'Copy a file to a new location in the working directory.',
|
|
130
|
-
schema: z.object({
|
|
131
|
-
sourcePath: z.string().describe('Source file path (relative to working directory)'),
|
|
132
|
-
destinationPath: z.string().describe('Destination file path (relative to working directory)'),
|
|
133
|
-
overwrite: z.boolean().optional().describe('Overwrite destination if it exists (default false)'),
|
|
134
|
-
}),
|
|
135
|
-
},
|
|
136
|
-
),
|
|
137
|
-
)
|
|
132
|
+
if (Array.isArray(payload)) {
|
|
133
|
+
for (const item of payload) collectSendFilePaths(item, into)
|
|
134
|
+
return
|
|
138
135
|
}
|
|
136
|
+
if (typeof payload !== 'object') return
|
|
137
|
+
const record = payload as Record<string, unknown>
|
|
138
|
+
if (typeof record.filePath === 'string') into.push(record.filePath)
|
|
139
|
+
if (typeof record.path === 'string') into.push(record.path)
|
|
140
|
+
if (record.files !== undefined) collectSendFilePaths(record.files, into)
|
|
141
|
+
}
|
|
139
142
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
return `Error moving file: ${err instanceof Error ? err.message : String(err)}`
|
|
157
|
-
}
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
name: 'move_file',
|
|
161
|
-
description: 'Move (rename) a file to a new location in the working directory.',
|
|
162
|
-
schema: z.object({
|
|
163
|
-
sourcePath: z.string().describe('Source file path (relative to working directory)'),
|
|
164
|
-
destinationPath: z.string().describe('Destination file path (relative to working directory)'),
|
|
165
|
-
overwrite: z.boolean().optional().describe('Overwrite destination if it exists (default false)'),
|
|
166
|
-
}),
|
|
167
|
-
},
|
|
168
|
-
),
|
|
169
|
-
)
|
|
143
|
+
export function normalizeSendFilePaths(args: Record<string, unknown>): string[] {
|
|
144
|
+
const candidates: string[] = []
|
|
145
|
+
collectSendFilePaths(args.filePath, candidates)
|
|
146
|
+
collectSendFilePaths(args.path, candidates)
|
|
147
|
+
collectSendFilePaths(args.files, candidates)
|
|
148
|
+
|
|
149
|
+
const nestedInput = args.input
|
|
150
|
+
if (typeof nestedInput === 'string') {
|
|
151
|
+
try {
|
|
152
|
+
const parsed = JSON.parse(nestedInput)
|
|
153
|
+
collectSendFilePaths(parsed, candidates)
|
|
154
|
+
} catch {
|
|
155
|
+
// ignore non-JSON input strings
|
|
156
|
+
}
|
|
157
|
+
} else if (nestedInput && typeof nestedInput === 'object') {
|
|
158
|
+
collectSendFilePaths(nestedInput, candidates)
|
|
170
159
|
}
|
|
171
160
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
const resolved = safePath(bctx.cwd, filePath)
|
|
178
|
-
const root = path.resolve(bctx.cwd)
|
|
179
|
-
if (resolved === root) return 'Error: refusing to delete the session working directory root.'
|
|
180
|
-
if (!fs.existsSync(resolved)) {
|
|
181
|
-
return force ? `Path already absent: ${filePath}` : `Error: path not found: ${filePath}`
|
|
182
|
-
}
|
|
183
|
-
const stat = fs.statSync(resolved)
|
|
184
|
-
if (stat.isDirectory() && !recursive) {
|
|
185
|
-
return 'Error: target is a directory. Set recursive=true to delete directories.'
|
|
186
|
-
}
|
|
187
|
-
fs.rmSync(resolved, { recursive: !!recursive, force: !!force })
|
|
188
|
-
return `Deleted: ${filePath}`
|
|
189
|
-
} catch (err: unknown) {
|
|
190
|
-
return `Error deleting file: ${err instanceof Error ? err.message : String(err)}`
|
|
191
|
-
}
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
name: 'delete_file',
|
|
195
|
-
description: 'Delete a file or directory from the working directory. Disabled by default and must be explicitly enabled.',
|
|
196
|
-
schema: z.object({
|
|
197
|
-
filePath: z.string().describe('Path to delete (relative to working directory)'),
|
|
198
|
-
recursive: z.boolean().optional().describe('Required for deleting directories'),
|
|
199
|
-
force: z.boolean().optional().describe('Ignore missing paths and force deletion where possible'),
|
|
200
|
-
}),
|
|
201
|
-
},
|
|
202
|
-
),
|
|
203
|
-
)
|
|
161
|
+
const deduped = new Set<string>()
|
|
162
|
+
for (const candidate of candidates) {
|
|
163
|
+
const trimmed = candidate.trim()
|
|
164
|
+
if (trimmed) deduped.add(trimmed)
|
|
204
165
|
}
|
|
166
|
+
return [...deduped]
|
|
167
|
+
}
|
|
205
168
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
pruneRecentSendFileCache(now)
|
|
213
|
-
// Resolve relative to cwd, but also allow absolute paths
|
|
214
|
-
const resolved = path.isAbsolute(rawPath) ? rawPath : path.resolve(bctx.cwd, rawPath)
|
|
215
|
-
if (!fs.existsSync(resolved)) return `Error: file not found: ${rawPath}`
|
|
216
|
-
const stat = fs.statSync(resolved)
|
|
217
|
-
if (stat.isDirectory()) return `Error: cannot send a directory. Send individual files instead.`
|
|
218
|
-
if (stat.size > 100 * 1024 * 1024) return `Error: file too large (${(stat.size / 1024 / 1024).toFixed(1)}MB). Max 100MB.`
|
|
219
|
-
|
|
220
|
-
const sessionId = bctx.ctx?.sessionId || 'no-session'
|
|
221
|
-
const dedupeKey = `${sessionId}|${resolved}`
|
|
222
|
-
const cached = recentSendFileResults.get(dedupeKey)
|
|
223
|
-
if (cached && now - cached.at <= SEND_FILE_DEDUPE_TTL_MS && fs.existsSync(cached.uploadPath)) {
|
|
224
|
-
return cached.output
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const ext = path.extname(resolved).slice(1).toLowerCase()
|
|
228
|
-
const basename = path.basename(resolved)
|
|
229
|
-
const filename = `${Date.now()}-${basename}`
|
|
230
|
-
const dest = path.join(UPLOAD_DIR, filename)
|
|
231
|
-
fs.copyFileSync(resolved, dest)
|
|
169
|
+
async function executeSendFile(args: Record<string, unknown>, bctx: { cwd: string }) {
|
|
170
|
+
try {
|
|
171
|
+
const paths = normalizeSendFilePaths(args)
|
|
172
|
+
if (paths.length === 0) {
|
|
173
|
+
return 'Error: filePath/path is required (or provide files[] / input.files[]).'
|
|
174
|
+
}
|
|
232
175
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
176
|
+
const links: string[] = []
|
|
177
|
+
const errors: string[] = []
|
|
178
|
+
for (const rawPath of paths) {
|
|
179
|
+
const resolved = path.isAbsolute(rawPath) ? rawPath : path.resolve(bctx.cwd, rawPath)
|
|
180
|
+
if (!fs.existsSync(resolved)) {
|
|
181
|
+
errors.push(`file not found: ${rawPath}`)
|
|
182
|
+
continue
|
|
183
|
+
}
|
|
184
|
+
const basename = path.basename(resolved)
|
|
185
|
+
const filename = `${Date.now()}-${basename}`
|
|
186
|
+
const dest = path.join(UPLOAD_DIR, filename)
|
|
187
|
+
fs.copyFileSync(resolved, dest)
|
|
188
|
+
links.push(`[Download ${basename}](/api/uploads/${filename})`)
|
|
189
|
+
}
|
|
236
190
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
} catch (err: unknown) {
|
|
243
|
-
return `Error sending file: ${err instanceof Error ? err.message : String(err)}`
|
|
244
|
-
}
|
|
245
|
-
},
|
|
246
|
-
{
|
|
247
|
-
name: 'send_file',
|
|
248
|
-
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.',
|
|
249
|
-
schema: z.object({
|
|
250
|
-
filePath: z.string().describe('Path to the file (relative to working directory, or absolute)'),
|
|
251
|
-
}),
|
|
252
|
-
},
|
|
253
|
-
),
|
|
254
|
-
)
|
|
191
|
+
if (links.length === 0) return `Error: ${errors[0] || 'file not found'}`
|
|
192
|
+
if (errors.length === 0) return links.join('\n')
|
|
193
|
+
return `${links.join('\n')}\n\nSkipped: ${errors.join('; ')}`
|
|
194
|
+
} catch (err: unknown) {
|
|
195
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
255
196
|
}
|
|
197
|
+
}
|
|
256
198
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
fs.mkdirSync(path.dirname(resolved), { recursive: true })
|
|
290
|
-
fs.writeFileSync(resolved, fullHtml, 'utf-8')
|
|
291
|
-
return `HTML document created: ${outName} (${fullHtml.length} bytes)`
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const { chromium } = await import('playwright')
|
|
295
|
-
const browser = await chromium.launch({ headless: true })
|
|
296
|
-
try {
|
|
297
|
-
const page = await browser.newPage()
|
|
298
|
-
await page.setContent(fullHtml, { waitUntil: 'networkidle' })
|
|
299
|
-
|
|
300
|
-
if (fmt === 'pdf') {
|
|
301
|
-
const outName = filename || `${defaultBase}.pdf`
|
|
302
|
-
const resolved = safePath(bctx.cwd, outName)
|
|
303
|
-
fs.mkdirSync(path.dirname(resolved), { recursive: true })
|
|
304
|
-
await page.pdf({ path: resolved, format: 'A4', margin: { top: '40px', bottom: '40px', left: '40px', right: '40px' }, printBackground: true })
|
|
305
|
-
return `PDF created: ${outName}`
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// png or jpg screenshot
|
|
309
|
-
const ext = fmt === 'jpg' ? 'jpeg' : 'png'
|
|
310
|
-
const outName = filename || `${defaultBase}.${fmt}`
|
|
311
|
-
const resolved = safePath(bctx.cwd, outName)
|
|
312
|
-
fs.mkdirSync(path.dirname(resolved), { recursive: true })
|
|
313
|
-
await page.screenshot({ path: resolved, type: ext, fullPage: true })
|
|
314
|
-
const size = fs.statSync(resolved).size
|
|
315
|
-
return `Image created: ${outName} (${(size / 1024).toFixed(1)} KB)`
|
|
316
|
-
} finally {
|
|
317
|
-
await browser.close()
|
|
318
|
-
}
|
|
319
|
-
} catch (err: unknown) {
|
|
320
|
-
return `Error creating document: ${err instanceof Error ? err.message : String(err)}`
|
|
321
|
-
}
|
|
199
|
+
/**
|
|
200
|
+
* Register as a Built-in Plugin
|
|
201
|
+
*/
|
|
202
|
+
const FilePlugin: Plugin = {
|
|
203
|
+
name: 'Core Files',
|
|
204
|
+
description: 'Complete file management: read, write, list, move, copy, delete, and send.',
|
|
205
|
+
hooks: {} as PluginHooks,
|
|
206
|
+
tools: [
|
|
207
|
+
{
|
|
208
|
+
name: 'files',
|
|
209
|
+
description: 'Unified file management tool. Actions: read, write, list, copy, move, delete. Supports bulk writes via "files" array.',
|
|
210
|
+
parameters: {
|
|
211
|
+
type: 'object',
|
|
212
|
+
properties: {
|
|
213
|
+
action: { type: 'string', enum: ['read', 'write', 'list', 'copy', 'move', 'delete'] },
|
|
214
|
+
filePath: { type: 'string' },
|
|
215
|
+
path: { type: 'string', description: 'Alias for filePath' },
|
|
216
|
+
content: { type: 'string' },
|
|
217
|
+
files: {
|
|
218
|
+
type: 'array',
|
|
219
|
+
items: {
|
|
220
|
+
type: 'object',
|
|
221
|
+
properties: { path: { type: 'string' }, content: { type: 'string' } }
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
encoding: { type: 'string', enum: ['utf-8', 'base64'] },
|
|
225
|
+
dirPath: { type: 'string' },
|
|
226
|
+
sourcePath: { type: 'string' },
|
|
227
|
+
destinationPath: { type: 'string' },
|
|
228
|
+
overwrite: { type: 'boolean' },
|
|
229
|
+
recursive: { type: 'boolean' },
|
|
230
|
+
force: { type: 'boolean' },
|
|
322
231
|
},
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
232
|
+
required: ['action']
|
|
233
|
+
},
|
|
234
|
+
execute: async (args, context) => executeFileAction(args, { cwd: context.session.cwd || process.cwd() })
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: 'send_file',
|
|
238
|
+
description: 'Send a file to the user in chat.',
|
|
239
|
+
parameters: {
|
|
240
|
+
type: 'object',
|
|
241
|
+
properties: {
|
|
242
|
+
filePath: { type: 'string' },
|
|
243
|
+
path: { type: 'string', description: 'Alias for filePath' },
|
|
244
|
+
files: {
|
|
245
|
+
type: 'array',
|
|
246
|
+
items: {
|
|
247
|
+
anyOf: [
|
|
248
|
+
{ type: 'string' },
|
|
249
|
+
{ type: 'object', properties: { filePath: { type: 'string' }, path: { type: 'string' } } },
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
input: { type: 'object', additionalProperties: true },
|
|
332
254
|
},
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
async ({ data, headers, sheetName, filename, format }) => {
|
|
340
|
-
try {
|
|
341
|
-
const fmt = format || 'xlsx'
|
|
342
|
-
let rows: Record<string, unknown>[]
|
|
343
|
-
try {
|
|
344
|
-
rows = JSON.parse(data)
|
|
345
|
-
if (!Array.isArray(rows)) return 'Error: data must be a JSON array of objects'
|
|
346
|
-
} catch {
|
|
347
|
-
return 'Error: data is not valid JSON. Pass a JSON array of objects, e.g. [{"name":"Alice","age":30}]'
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
if (!rows.length) return 'Error: data array is empty'
|
|
351
|
-
|
|
352
|
-
// Resolve column headers: explicit headers, or keys from first row
|
|
353
|
-
const cols = headers?.length
|
|
354
|
-
? headers
|
|
355
|
-
: Object.keys(rows[0] && typeof rows[0] === 'object' ? rows[0] : {})
|
|
356
|
-
if (!cols.length) return 'Error: could not determine column headers. Pass headers or use objects with keys.'
|
|
357
|
-
|
|
358
|
-
const defaultBase = (sheetName || 'spreadsheet').replace(/[^a-zA-Z0-9_-]/g, '_')
|
|
359
|
-
|
|
360
|
-
if (fmt === 'csv') {
|
|
361
|
-
const escapeCsv = (val: unknown): string => {
|
|
362
|
-
const s = val == null ? '' : String(val)
|
|
363
|
-
return s.includes(',') || s.includes('"') || s.includes('\n')
|
|
364
|
-
? `"${s.replace(/"/g, '""')}"`
|
|
365
|
-
: s
|
|
366
|
-
}
|
|
367
|
-
const lines = [cols.map(escapeCsv).join(',')]
|
|
368
|
-
for (const row of rows) {
|
|
369
|
-
const r = Array.isArray(row) ? row : cols.map((c) => (row as Record<string, unknown>)[c])
|
|
370
|
-
lines.push(r.map(escapeCsv).join(','))
|
|
371
|
-
}
|
|
372
|
-
const outName = filename || `${defaultBase}.csv`
|
|
373
|
-
const resolved = safePath(bctx.cwd, outName)
|
|
374
|
-
fs.mkdirSync(path.dirname(resolved), { recursive: true })
|
|
375
|
-
fs.writeFileSync(resolved, lines.join('\n'), 'utf-8')
|
|
376
|
-
return `CSV created: ${outName} (${rows.length} rows, ${cols.length} columns)`
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// xlsx via exceljs
|
|
380
|
-
const ExcelJS = await import('exceljs')
|
|
381
|
-
const workbook = new ExcelJS.default.Workbook()
|
|
382
|
-
const sheet = workbook.addWorksheet(sheetName || 'Sheet1')
|
|
383
|
-
|
|
384
|
-
sheet.columns = cols.map((c) => ({ header: c, key: c, width: Math.max(12, c.length + 4) }))
|
|
385
|
-
// Style header row
|
|
386
|
-
sheet.getRow(1).font = { bold: true }
|
|
387
|
-
sheet.getRow(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF3F4F6' } }
|
|
388
|
-
|
|
389
|
-
for (const row of rows) {
|
|
390
|
-
if (Array.isArray(row)) {
|
|
391
|
-
const obj: Record<string, unknown> = {}
|
|
392
|
-
cols.forEach((c, i) => { obj[c] = row[i] })
|
|
393
|
-
sheet.addRow(obj)
|
|
394
|
-
} else {
|
|
395
|
-
sheet.addRow(row)
|
|
396
|
-
}
|
|
397
|
-
}
|
|
255
|
+
required: []
|
|
256
|
+
},
|
|
257
|
+
execute: async (args, context) => executeSendFile(args, { cwd: context.session.cwd || process.cwd() })
|
|
258
|
+
}
|
|
259
|
+
]
|
|
260
|
+
}
|
|
398
261
|
|
|
399
|
-
|
|
400
|
-
const resolved = safePath(bctx.cwd, outName)
|
|
401
|
-
fs.mkdirSync(path.dirname(resolved), { recursive: true })
|
|
402
|
-
await workbook.xlsx.writeFile(resolved)
|
|
403
|
-
const size = fs.statSync(resolved).size
|
|
404
|
-
return `Excel spreadsheet created: ${outName} (${rows.length} rows, ${cols.length} columns, ${(size / 1024).toFixed(1)} KB)`
|
|
405
|
-
} catch (err: unknown) {
|
|
406
|
-
return `Error creating spreadsheet: ${err instanceof Error ? err.message : String(err)}`
|
|
407
|
-
}
|
|
408
|
-
},
|
|
409
|
-
{
|
|
410
|
-
name: 'create_spreadsheet',
|
|
411
|
-
description: 'Create an Excel (.xlsx) or CSV file from structured data. Pass data as a JSON array of objects. Use this for tables, reports, data exports, and any tabular data the user requests. After creating, use send_file to deliver it to the user.',
|
|
412
|
-
schema: z.object({
|
|
413
|
-
data: z.string().describe('JSON array of objects, e.g. [{"name":"Alice","score":95},{"name":"Bob","score":87}]'),
|
|
414
|
-
headers: z.array(z.string()).optional().describe('Column headers in display order. If omitted, keys from the first object are used.'),
|
|
415
|
-
sheetName: z.string().optional().describe('Worksheet name (default "Sheet1")'),
|
|
416
|
-
filename: z.string().optional().describe('Output filename (defaults to sheetName-based name with extension)'),
|
|
417
|
-
format: z.enum(['xlsx', 'csv']).optional().describe('Output format: "xlsx" (default) for Excel, "csv" for plain CSV.'),
|
|
418
|
-
}),
|
|
419
|
-
},
|
|
420
|
-
),
|
|
421
|
-
)
|
|
422
|
-
}
|
|
262
|
+
getPluginManager().registerBuiltin('files', FilePlugin)
|
|
423
263
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
filePath: z.string().describe('Relative path to the file'),
|
|
447
|
-
oldText: z.string().describe('Exact text to find (must be unique in the file)'),
|
|
448
|
-
newText: z.string().describe('Text to replace it with'),
|
|
449
|
-
}),
|
|
450
|
-
},
|
|
451
|
-
),
|
|
264
|
+
/**
|
|
265
|
+
* Legacy Bridge
|
|
266
|
+
*/
|
|
267
|
+
export function buildFileTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
268
|
+
if (!bctx.hasTool('files')) return []
|
|
269
|
+
|
|
270
|
+
return [
|
|
271
|
+
tool(
|
|
272
|
+
async (args) => executeFileAction(args, { cwd: bctx.cwd }),
|
|
273
|
+
{
|
|
274
|
+
name: 'files',
|
|
275
|
+
description: FilePlugin.tools![0].description,
|
|
276
|
+
schema: z.object({}).passthrough()
|
|
277
|
+
}
|
|
278
|
+
),
|
|
279
|
+
tool(
|
|
280
|
+
async (args) => executeSendFile(args, { cwd: bctx.cwd }),
|
|
281
|
+
{
|
|
282
|
+
name: 'send_file',
|
|
283
|
+
description: FilePlugin.tools![1].description,
|
|
284
|
+
schema: z.object({}).passthrough()
|
|
285
|
+
}
|
|
452
286
|
)
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
return tools
|
|
287
|
+
]
|
|
456
288
|
}
|