@swarmclawai/swarmclaw 0.7.7 → 0.8.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 +12 -14
- package/next.config.ts +13 -2
- package/package.json +4 -2
- package/src/app/api/agents/[id]/thread/route.ts +9 -0
- package/src/app/api/agents/route.ts +4 -0
- package/src/app/api/agents/thread-route.test.ts +133 -0
- package/src/app/api/approvals/route.test.ts +148 -0
- package/src/app/api/canvas/[sessionId]/route.ts +3 -1
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -2
- package/src/app/api/chats/[id]/devserver/route.ts +48 -7
- package/src/app/api/chats/[id]/messages/route.ts +42 -18
- package/src/app/api/chats/[id]/route.ts +1 -1
- package/src/app/api/chats/[id]/stop/route.ts +5 -4
- package/src/app/api/chats/route.ts +23 -2
- package/src/app/api/clawhub/install/route.ts +28 -8
- package/src/app/api/connectors/[id]/route.ts +46 -3
- package/src/app/api/connectors/route.ts +12 -8
- package/src/app/api/external-agents/route.test.ts +165 -0
- package/src/app/api/gateways/[id]/health/route.ts +27 -12
- package/src/app/api/gateways/[id]/route.ts +2 -0
- package/src/app/api/gateways/health-route.test.ts +135 -0
- package/src/app/api/gateways/route.ts +2 -0
- package/src/app/api/mcp-servers/route.test.ts +130 -0
- package/src/app/api/openclaw/deploy/route.ts +38 -5
- package/src/app/api/plugins/install/route.ts +46 -6
- package/src/app/api/plugins/marketplace/route.ts +48 -15
- package/src/app/api/preview-server/route.ts +26 -11
- package/src/app/api/projects/[id]/route.ts +6 -2
- package/src/app/api/projects/route.ts +4 -3
- package/src/app/api/schedules/[id]/run/route.ts +4 -0
- package/src/app/api/schedules/route.test.ts +86 -0
- package/src/app/api/schedules/route.ts +6 -1
- package/src/app/api/secrets/[id]/route.ts +1 -0
- package/src/app/api/secrets/route.ts +2 -1
- package/src/app/api/settings/route.ts +2 -0
- package/src/app/api/setup/check-provider/route.test.ts +19 -0
- package/src/app/api/setup/check-provider/route.ts +40 -10
- package/src/app/api/skills/[id]/route.ts +12 -0
- package/src/app/api/skills/import/route.ts +14 -12
- package/src/app/api/skills/route.ts +13 -1
- package/src/app/api/tasks/[id]/route.ts +10 -1
- package/src/app/api/tasks/import/github/route.test.ts +65 -0
- package/src/app/api/tasks/import/github/route.ts +337 -0
- package/src/app/api/wallets/[id]/approve/route.ts +17 -3
- package/src/app/api/wallets/[id]/route.ts +79 -33
- package/src/app/api/wallets/[id]/send/route.ts +19 -33
- package/src/app/api/wallets/route.ts +78 -61
- package/src/app/api/webhooks/[id]/route.ts +33 -6
- package/src/app/api/webhooks/route.test.ts +272 -0
- package/src/cli/index.js +1 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-card.tsx +9 -2
- package/src/components/agents/agent-chat-list.tsx +18 -2
- package/src/components/agents/agent-list.tsx +1 -0
- package/src/components/agents/agent-sheet.tsx +257 -38
- package/src/components/agents/inspector-panel.tsx +41 -0
- package/src/components/canvas/canvas-panel.tsx +236 -65
- package/src/components/chat/chat-area.tsx +36 -19
- package/src/components/chat/chat-card.tsx +36 -13
- package/src/components/chat/chat-header.tsx +48 -16
- package/src/components/chat/chat-list.tsx +28 -4
- package/src/components/chat/checkpoint-timeline.tsx +50 -34
- package/src/components/chat/delegation-banner.test.ts +14 -1
- package/src/components/chat/delegation-banner.tsx +1 -1
- package/src/components/chat/message-bubble.tsx +208 -145
- package/src/components/chat/message-list.tsx +48 -19
- package/src/components/chatrooms/chatroom-message.tsx +2 -2
- package/src/components/chatrooms/chatroom-sheet.tsx +16 -2
- package/src/components/connectors/connector-health.tsx +1 -1
- package/src/components/connectors/connector-list.tsx +7 -2
- package/src/components/connectors/connector-sheet.tsx +337 -148
- package/src/components/gateways/gateway-sheet.tsx +2 -2
- package/src/components/layout/app-layout.tsx +40 -23
- package/src/components/mcp-servers/mcp-server-list.tsx +26 -5
- package/src/components/mcp-servers/mcp-server-sheet.tsx +19 -2
- package/src/components/openclaw/openclaw-deploy-panel.tsx +269 -21
- package/src/components/plugins/plugin-list.tsx +45 -9
- package/src/components/plugins/plugin-sheet.tsx +55 -7
- package/src/components/projects/project-detail.tsx +217 -0
- package/src/components/projects/project-sheet.tsx +176 -4
- package/src/components/providers/provider-list.tsx +2 -1
- package/src/components/providers/provider-sheet.tsx +21 -2
- package/src/components/schedules/schedule-card.tsx +25 -1
- package/src/components/schedules/schedule-sheet.tsx +44 -2
- package/src/components/secrets/secret-sheet.tsx +21 -2
- package/src/components/shared/agent-switch-dialog.tsx +12 -1
- package/src/components/shared/bottom-sheet.tsx +13 -3
- package/src/components/shared/command-palette.tsx +8 -1
- package/src/components/shared/confirm-dialog.tsx +19 -4
- package/src/components/shared/connector-platform-icon.test.ts +28 -0
- package/src/components/shared/connector-platform-icon.tsx +39 -6
- package/src/components/shared/settings/plugin-manager.tsx +29 -6
- package/src/components/shared/settings/section-capability-policy.tsx +45 -3
- package/src/components/shared/settings/section-voice.tsx +11 -3
- package/src/components/skills/skill-list.tsx +25 -0
- package/src/components/skills/skill-sheet.tsx +84 -12
- package/src/components/tasks/approvals-panel.tsx +289 -34
- package/src/components/tasks/task-board.tsx +410 -25
- package/src/components/tasks/task-card.tsx +66 -8
- package/src/components/tasks/task-sheet.tsx +16 -4
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/wallets/wallet-approval-dialog.tsx +4 -2
- package/src/components/wallets/wallet-panel.tsx +435 -90
- package/src/components/wallets/wallet-section.tsx +198 -48
- package/src/components/webhooks/webhook-sheet.tsx +22 -2
- package/src/lib/approval-display.ts +20 -0
- package/src/lib/canvas-content.ts +198 -0
- package/src/lib/chat-artifact-summary.ts +165 -0
- package/src/lib/chat-display.test.ts +91 -0
- package/src/lib/chat-display.ts +58 -0
- package/src/lib/chat-streaming-state.test.ts +47 -1
- package/src/lib/chat-streaming-state.ts +42 -0
- package/src/lib/ollama-model.ts +10 -0
- package/src/lib/openclaw-endpoint.test.ts +8 -0
- package/src/lib/openclaw-endpoint.ts +6 -1
- package/src/lib/plugin-install-cors.ts +46 -0
- package/src/lib/plugin-sources.test.ts +43 -0
- package/src/lib/plugin-sources.ts +77 -0
- package/src/lib/providers/ollama.ts +16 -6
- package/src/lib/providers/openclaw.test.ts +54 -0
- package/src/lib/providers/openclaw.ts +127 -11
- package/src/lib/schedule-dedupe-advanced.test.ts +1335 -0
- package/src/lib/schedule-dedupe.test.ts +66 -1
- package/src/lib/schedule-dedupe.ts +169 -12
- package/src/lib/schedule-origin.test.ts +20 -0
- package/src/lib/schedule-origin.ts +15 -0
- package/src/lib/server/__fixtures__/fake-mcp-stdio-server.mjs +27 -0
- package/src/lib/server/agent-availability.ts +16 -0
- package/src/lib/server/agent-runtime-config.ts +12 -4
- package/src/lib/server/agent-thread-session.test.ts +51 -0
- package/src/lib/server/agent-thread-session.ts +7 -0
- package/src/lib/server/approval-match.ts +205 -0
- package/src/lib/server/approvals-auto-approve.test.ts +538 -1
- package/src/lib/server/approvals.ts +214 -1
- package/src/lib/server/assistant-control.test.ts +29 -0
- package/src/lib/server/assistant-control.ts +23 -0
- package/src/lib/server/build-llm.test.ts +79 -0
- package/src/lib/server/build-llm.ts +14 -4
- package/src/lib/server/canvas-content.test.ts +32 -0
- package/src/lib/server/canvas-content.ts +6 -0
- package/src/lib/server/capability-router.test.ts +33 -0
- package/src/lib/server/capability-router.ts +80 -19
- package/src/lib/server/chat-execution-advanced.test.ts +651 -0
- package/src/lib/server/chat-execution-disabled.test.ts +94 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +157 -0
- package/src/lib/server/chat-execution.ts +378 -73
- package/src/lib/server/clawhub-client.test.ts +14 -8
- package/src/lib/server/connectors/manager-reconnect.test.ts +47 -0
- package/src/lib/server/connectors/manager.test.ts +1147 -0
- package/src/lib/server/connectors/manager.ts +461 -137
- package/src/lib/server/connectors/pairing.ts +26 -5
- package/src/lib/server/connectors/types.ts +2 -0
- package/src/lib/server/connectors/whatsapp.test.ts +134 -0
- package/src/lib/server/connectors/whatsapp.ts +271 -47
- package/src/lib/server/context-manager.ts +6 -1
- package/src/lib/server/daemon-state.ts +84 -47
- package/src/lib/server/data-dir.test.ts +37 -0
- package/src/lib/server/data-dir.ts +20 -1
- package/src/lib/server/delegation-jobs-advanced.test.ts +513 -0
- package/src/lib/server/devserver-launch.test.ts +60 -0
- package/src/lib/server/devserver-launch.ts +85 -0
- package/src/lib/server/elevenlabs.test.ts +247 -1
- package/src/lib/server/elevenlabs.ts +147 -43
- package/src/lib/server/ethereum.ts +590 -0
- package/src/lib/server/eval/agent-regression-advanced.test.ts +302 -0
- package/src/lib/server/eval/agent-regression.test.ts +18 -1
- package/src/lib/server/eval/agent-regression.ts +383 -11
- package/src/lib/server/evm-swap.ts +475 -0
- package/src/lib/server/execution-log.ts +1 -0
- package/src/lib/server/heartbeat-service-timer.test.ts +173 -0
- package/src/lib/server/heartbeat-service.ts +20 -11
- package/src/lib/server/heartbeat-wake.test.ts +112 -0
- package/src/lib/server/heartbeat-wake.ts +338 -57
- package/src/lib/server/main-agent-loop-advanced.test.ts +538 -0
- package/src/lib/server/main-agent-loop.test.ts +260 -0
- package/src/lib/server/main-agent-loop.ts +559 -14
- package/src/lib/server/mcp-client.test.ts +16 -0
- package/src/lib/server/mcp-client.ts +25 -0
- package/src/lib/server/memory-integration.test.ts +719 -0
- package/src/lib/server/memory-policy.test.ts +43 -0
- package/src/lib/server/memory-policy.ts +132 -0
- package/src/lib/server/memory-tiers.test.ts +60 -0
- package/src/lib/server/memory-tiers.ts +16 -0
- package/src/lib/server/ollama-runtime.ts +58 -0
- package/src/lib/server/openclaw-deploy.test.ts +109 -1
- package/src/lib/server/openclaw-deploy.ts +557 -81
- package/src/lib/server/openclaw-gateway.test.ts +131 -0
- package/src/lib/server/openclaw-gateway.ts +10 -4
- package/src/lib/server/openclaw-health.test.ts +35 -0
- package/src/lib/server/openclaw-health.ts +215 -47
- package/src/lib/server/orchestrator-lg.ts +3 -2
- package/src/lib/server/orchestrator.ts +2 -0
- package/src/lib/server/plugins-advanced.test.ts +351 -0
- package/src/lib/server/plugins.ts +211 -6
- package/src/lib/server/project-context.ts +162 -0
- package/src/lib/server/project-utils.ts +150 -0
- package/src/lib/server/queue-advanced.test.ts +528 -0
- package/src/lib/server/queue-followups.test.ts +409 -2
- package/src/lib/server/queue-reconcile.test.ts +128 -0
- package/src/lib/server/queue.ts +527 -68
- package/src/lib/server/scheduler.ts +29 -1
- package/src/lib/server/session-note.test.ts +36 -0
- package/src/lib/server/session-note.ts +42 -0
- package/src/lib/server/session-run-manager.ts +83 -4
- package/src/lib/server/session-tools/canvas.ts +14 -12
- package/src/lib/server/session-tools/connector-inputs.test.ts +37 -0
- package/src/lib/server/session-tools/connector.test.ts +138 -0
- package/src/lib/server/session-tools/connector.ts +366 -54
- package/src/lib/server/session-tools/context.ts +17 -3
- package/src/lib/server/session-tools/crud.ts +484 -84
- package/src/lib/server/session-tools/delegate-fallback.test.ts +103 -0
- package/src/lib/server/session-tools/delegate-resume.test.ts +50 -0
- package/src/lib/server/session-tools/delegate.ts +102 -10
- package/src/lib/server/session-tools/discovery-approvals.test.ts +142 -0
- package/src/lib/server/session-tools/discovery.ts +80 -12
- package/src/lib/server/session-tools/file-normalize.test.ts +36 -0
- package/src/lib/server/session-tools/file.ts +43 -4
- package/src/lib/server/session-tools/human-loop.ts +35 -5
- package/src/lib/server/session-tools/index.ts +44 -9
- package/src/lib/server/session-tools/manage-connectors.test.ts +139 -0
- package/src/lib/server/session-tools/manage-schedules-advanced.test.ts +564 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +283 -0
- package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +852 -0
- package/src/lib/server/session-tools/manage-tasks.test.ts +114 -0
- package/src/lib/server/session-tools/memory.test.ts +93 -0
- package/src/lib/server/session-tools/memory.ts +554 -75
- package/src/lib/server/session-tools/normalize-tool-args.ts +1 -1
- package/src/lib/server/session-tools/platform-access.test.ts +58 -0
- package/src/lib/server/session-tools/platform.ts +60 -19
- package/src/lib/server/session-tools/plugin-creator.ts +57 -1
- package/src/lib/server/session-tools/primitive-tools.test.ts +6 -0
- package/src/lib/server/session-tools/schedule.ts +6 -1
- package/src/lib/server/session-tools/shell-normalize.test.ts +25 -1
- package/src/lib/server/session-tools/shell.ts +22 -3
- package/src/lib/server/session-tools/wallet-tool.test.ts +254 -0
- package/src/lib/server/session-tools/wallet.ts +1374 -139
- package/src/lib/server/session-tools/web-inputs.test.ts +178 -0
- package/src/lib/server/session-tools/web.ts +621 -70
- package/src/lib/server/skill-discovery.ts +128 -0
- package/src/lib/server/skill-eligibility.test.ts +84 -0
- package/src/lib/server/skill-eligibility.ts +95 -0
- package/src/lib/server/skill-prompt-budget.test.ts +102 -0
- package/src/lib/server/skill-prompt-budget.ts +125 -0
- package/src/lib/server/skills-normalize.test.ts +54 -0
- package/src/lib/server/skills-normalize.ts +372 -26
- package/src/lib/server/solana.ts +214 -29
- package/src/lib/server/storage.ts +65 -36
- package/src/lib/server/stream-agent-chat.test.ts +437 -2
- package/src/lib/server/stream-agent-chat.ts +957 -79
- package/src/lib/server/system-events.ts +1 -1
- package/src/lib/server/tool-aliases.ts +2 -0
- package/src/lib/server/tool-capability-policy-advanced.test.ts +502 -0
- package/src/lib/server/tool-capability-policy.test.ts +24 -0
- package/src/lib/server/tool-capability-policy.ts +29 -1
- package/src/lib/server/tool-loop-detection.test.ts +105 -0
- package/src/lib/server/tool-loop-detection.ts +260 -0
- package/src/lib/server/tool-planning.test.ts +44 -0
- package/src/lib/server/tool-planning.ts +271 -0
- package/src/lib/server/wallet-execution.test.ts +198 -0
- package/src/lib/server/wallet-portfolio.test.ts +98 -0
- package/src/lib/server/wallet-portfolio.ts +724 -0
- package/src/lib/server/wallet-service.test.ts +57 -0
- package/src/lib/server/wallet-service.ts +213 -0
- package/src/lib/server/watch-jobs-advanced.test.ts +594 -0
- package/src/lib/server/watch-jobs.ts +17 -2
- package/src/lib/server/workspace-context.ts +111 -0
- package/src/lib/skill-save-payload.test.ts +39 -0
- package/src/lib/skill-save-payload.ts +37 -0
- package/src/lib/tasks.ts +28 -0
- package/src/lib/tool-definitions.ts +2 -1
- package/src/lib/tool-event-summary.test.ts +30 -0
- package/src/lib/tool-event-summary.ts +37 -0
- package/src/lib/validation/schemas.ts +1 -0
- package/src/lib/wallet-transactions.test.ts +75 -0
- package/src/lib/wallet-transactions.ts +43 -0
- package/src/lib/wallet.test.ts +17 -0
- package/src/lib/wallet.ts +183 -0
- package/src/proxy.test.ts +31 -0
- package/src/proxy.ts +34 -2
- package/src/stores/use-chat-store.ts +15 -1
- package/src/types/index.ts +249 -14
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import {
|
|
4
|
+
canonicalizePluginId,
|
|
5
|
+
expandPluginIds,
|
|
6
|
+
getPluginAliases,
|
|
7
|
+
normalizePluginId,
|
|
8
|
+
pluginIdMatches,
|
|
9
|
+
} from './tool-aliases'
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// normalizePluginId
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
describe('normalizePluginId', () => {
|
|
15
|
+
it('converts uppercase to lowercase', () => {
|
|
16
|
+
assert.equal(normalizePluginId('WEB_SEARCH'), 'web_search')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('trims leading and trailing whitespace', () => {
|
|
20
|
+
assert.equal(normalizePluginId(' shell '), 'shell')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('handles combined upper + whitespace', () => {
|
|
24
|
+
assert.equal(normalizePluginId(' WEB_SEARCH '), 'web_search')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('returns empty string for empty input', () => {
|
|
28
|
+
assert.equal(normalizePluginId(''), '')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('returns already normalized value unchanged', () => {
|
|
32
|
+
assert.equal(normalizePluginId('files'), 'files')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('returns empty string for non-string input (number)', () => {
|
|
36
|
+
assert.equal(normalizePluginId(42), '')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('returns empty string for null', () => {
|
|
40
|
+
assert.equal(normalizePluginId(null), '')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('returns empty string for undefined', () => {
|
|
44
|
+
assert.equal(normalizePluginId(undefined), '')
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// canonicalizePluginId
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
describe('canonicalizePluginId', () => {
|
|
52
|
+
it('resolves web_search → web', () => {
|
|
53
|
+
assert.equal(canonicalizePluginId('web_search'), 'web')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('resolves web_fetch → web', () => {
|
|
57
|
+
assert.equal(canonicalizePluginId('web_fetch'), 'web')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('keeps web (already canonical)', () => {
|
|
61
|
+
assert.equal(canonicalizePluginId('web'), 'web')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('resolves execute_command → shell', () => {
|
|
65
|
+
assert.equal(canonicalizePluginId('execute_command'), 'shell')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('resolves memory_tool → memory', () => {
|
|
69
|
+
assert.equal(canonicalizePluginId('memory_tool'), 'memory')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('keeps files (already canonical)', () => {
|
|
73
|
+
assert.equal(canonicalizePluginId('files'), 'files')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('returns unknown plugin as-is', () => {
|
|
77
|
+
assert.equal(canonicalizePluginId('totally_unknown'), 'totally_unknown')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('resolves delegate_to_claude_code → delegate', () => {
|
|
81
|
+
assert.equal(canonicalizePluginId('delegate_to_claude_code'), 'delegate')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('resolves claude_code → delegate', () => {
|
|
85
|
+
assert.equal(canonicalizePluginId('claude_code'), 'delegate')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('resolves process_tool → shell', () => {
|
|
89
|
+
assert.equal(canonicalizePluginId('process_tool'), 'shell')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('resolves openclaw_browser → browser', () => {
|
|
93
|
+
assert.equal(canonicalizePluginId('openclaw_browser'), 'browser')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('returns raw string (preserving case) for empty normalized result', () => {
|
|
97
|
+
// non-string input → normalizePluginId returns ''
|
|
98
|
+
assert.equal(canonicalizePluginId(123), '')
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// expandPluginIds
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
describe('expandPluginIds', () => {
|
|
106
|
+
it('shell implies process', () => {
|
|
107
|
+
const result = expandPluginIds(['shell'])
|
|
108
|
+
assert.ok(result.includes('shell'))
|
|
109
|
+
assert.ok(result.includes('process'))
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('manage_platform expands to 10 sub-plugins', () => {
|
|
113
|
+
const result = expandPluginIds(['manage_platform'])
|
|
114
|
+
const expected = [
|
|
115
|
+
'manage_platform',
|
|
116
|
+
'manage_agents',
|
|
117
|
+
'manage_projects',
|
|
118
|
+
'manage_tasks',
|
|
119
|
+
'manage_schedules',
|
|
120
|
+
'manage_skills',
|
|
121
|
+
'manage_documents',
|
|
122
|
+
'manage_webhooks',
|
|
123
|
+
'manage_connectors',
|
|
124
|
+
'manage_sessions',
|
|
125
|
+
'manage_secrets',
|
|
126
|
+
]
|
|
127
|
+
for (const e of expected) {
|
|
128
|
+
assert.ok(result.includes(e), `expected ${e} in expansion`)
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('web expands to include web_search and web_fetch', () => {
|
|
133
|
+
const result = expandPluginIds(['web'])
|
|
134
|
+
assert.ok(result.includes('web'))
|
|
135
|
+
assert.ok(result.includes('web_search'))
|
|
136
|
+
assert.ok(result.includes('web_fetch'))
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('removes duplicates after expansion', () => {
|
|
140
|
+
const result = expandPluginIds(['web', 'web_search', 'web_fetch'])
|
|
141
|
+
const unique = new Set(result)
|
|
142
|
+
assert.equal(result.length, unique.size)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('returns empty array for empty input', () => {
|
|
146
|
+
assert.deepEqual(expandPluginIds([]), [])
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('keeps unknown plugin as-is', () => {
|
|
150
|
+
const result = expandPluginIds(['my_custom_plugin'])
|
|
151
|
+
assert.ok(result.includes('my_custom_plugin'))
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('deduplicates overlapping expansions from multiple inputs', () => {
|
|
155
|
+
const result = expandPluginIds(['web', 'web_search'])
|
|
156
|
+
const counts = result.reduce<Record<string, number>>((acc, id) => {
|
|
157
|
+
acc[id] = (acc[id] || 0) + 1
|
|
158
|
+
return acc
|
|
159
|
+
}, {})
|
|
160
|
+
for (const [id, count] of Object.entries(counts)) {
|
|
161
|
+
assert.equal(count, 1, `${id} appears ${count} times`)
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('returns empty array for null', () => {
|
|
166
|
+
assert.deepEqual(expandPluginIds(null), [])
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('returns empty array for undefined', () => {
|
|
170
|
+
assert.deepEqual(expandPluginIds(undefined), [])
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('shell also expands aliases (execute_command, process_tool)', () => {
|
|
174
|
+
const result = expandPluginIds(['shell'])
|
|
175
|
+
assert.ok(result.includes('execute_command'))
|
|
176
|
+
assert.ok(result.includes('process_tool'))
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('manage_platform + shell has no duplicates', () => {
|
|
180
|
+
const result = expandPluginIds(['manage_platform', 'shell'])
|
|
181
|
+
const unique = new Set(result)
|
|
182
|
+
assert.equal(result.length, unique.size)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('handles same plugin requested multiple times', () => {
|
|
186
|
+
const result = expandPluginIds(['web', 'web', 'web'])
|
|
187
|
+
const webCount = result.filter((id) => id === 'web').length
|
|
188
|
+
assert.equal(webCount, 1)
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// getPluginAliases
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
describe('getPluginAliases', () => {
|
|
196
|
+
it('web returns [web, web_search, web_fetch]', () => {
|
|
197
|
+
const result = getPluginAliases('web')
|
|
198
|
+
assert.ok(result.includes('web'))
|
|
199
|
+
assert.ok(result.includes('web_search'))
|
|
200
|
+
assert.ok(result.includes('web_fetch'))
|
|
201
|
+
assert.equal(result.length, 3)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('web_search returns the same group as web', () => {
|
|
205
|
+
const fromWeb = getPluginAliases('web').sort()
|
|
206
|
+
const fromAlias = getPluginAliases('web_search').sort()
|
|
207
|
+
assert.deepEqual(fromWeb, fromAlias)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('unknown plugin returns array with just the input', () => {
|
|
211
|
+
assert.deepEqual(getPluginAliases('unknown_thing'), ['unknown_thing'])
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('shell includes execute_command and process_tool', () => {
|
|
215
|
+
const result = getPluginAliases('shell')
|
|
216
|
+
assert.ok(result.includes('shell'))
|
|
217
|
+
assert.ok(result.includes('execute_command'))
|
|
218
|
+
assert.ok(result.includes('process_tool'))
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('returns empty array for empty string', () => {
|
|
222
|
+
assert.deepEqual(getPluginAliases(''), [])
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('returns empty array for null', () => {
|
|
226
|
+
assert.deepEqual(getPluginAliases(null), [])
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
it('delegate group includes all delegate variants', () => {
|
|
230
|
+
const result = getPluginAliases('delegate')
|
|
231
|
+
assert.ok(result.includes('claude_code'))
|
|
232
|
+
assert.ok(result.includes('delegate_to_claude_code'))
|
|
233
|
+
assert.ok(result.includes('codex_cli'))
|
|
234
|
+
assert.ok(result.includes('delegate_to_codex_cli'))
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
// pluginIdMatches
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
describe('pluginIdMatches', () => {
|
|
242
|
+
it('web enabled, web_search matches (alias)', () => {
|
|
243
|
+
assert.equal(pluginIdMatches(['web'], 'web_search'), true)
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
it('web_search enabled, web matches (reverse alias)', () => {
|
|
247
|
+
assert.equal(pluginIdMatches(['web_search'], 'web'), true)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('files enabled, shell does not match (different families)', () => {
|
|
251
|
+
assert.equal(pluginIdMatches(['files'], 'shell'), false)
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
it('manage_platform enabled, manage_tasks matches (implication)', () => {
|
|
255
|
+
assert.equal(pluginIdMatches(['manage_platform'], 'manage_tasks'), true)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it('empty enabled list, nothing matches', () => {
|
|
259
|
+
assert.equal(pluginIdMatches([], 'web'), false)
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it('case insensitive match', () => {
|
|
263
|
+
assert.equal(pluginIdMatches(['WEB'], 'web_search'), true)
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('shell enabled, process matches (implication)', () => {
|
|
267
|
+
assert.equal(pluginIdMatches(['shell'], 'process'), true)
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('manage_platform enabled, manage_secrets matches', () => {
|
|
271
|
+
assert.equal(pluginIdMatches(['manage_platform'], 'manage_secrets'), true)
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it('null enabled list returns false', () => {
|
|
275
|
+
assert.equal(pluginIdMatches(null, 'web'), false)
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
it('undefined enabled list returns false', () => {
|
|
279
|
+
assert.equal(pluginIdMatches(undefined, 'web'), false)
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
// Complex expansion scenarios
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
describe('complex expansion scenarios', () => {
|
|
287
|
+
it('shell + web + memory fully expands', () => {
|
|
288
|
+
const result = expandPluginIds(['shell', 'web', 'memory'])
|
|
289
|
+
// shell family
|
|
290
|
+
assert.ok(result.includes('shell'))
|
|
291
|
+
assert.ok(result.includes('execute_command'))
|
|
292
|
+
assert.ok(result.includes('process_tool'))
|
|
293
|
+
assert.ok(result.includes('process'))
|
|
294
|
+
// web family
|
|
295
|
+
assert.ok(result.includes('web'))
|
|
296
|
+
assert.ok(result.includes('web_search'))
|
|
297
|
+
assert.ok(result.includes('web_fetch'))
|
|
298
|
+
// memory family
|
|
299
|
+
assert.ok(result.includes('memory'))
|
|
300
|
+
assert.ok(result.includes('memory_tool'))
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it('large plugin list (50+ items) all expanded correctly', () => {
|
|
304
|
+
const ids = Array.from({ length: 50 }, (_, i) => `custom_plugin_${i}`)
|
|
305
|
+
ids.push('shell', 'web')
|
|
306
|
+
const result = expandPluginIds(ids)
|
|
307
|
+
// All custom ones present
|
|
308
|
+
for (let i = 0; i < 50; i++) {
|
|
309
|
+
assert.ok(result.includes(`custom_plugin_${i}`))
|
|
310
|
+
}
|
|
311
|
+
// Shell expansion present
|
|
312
|
+
assert.ok(result.includes('process'))
|
|
313
|
+
// Web expansion present
|
|
314
|
+
assert.ok(result.includes('web_fetch'))
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
it('alias chains do not cause infinite loops', () => {
|
|
318
|
+
// delegate has many aliases; expansion should terminate
|
|
319
|
+
const result = expandPluginIds(['delegate'])
|
|
320
|
+
assert.ok(result.includes('delegate'))
|
|
321
|
+
assert.ok(result.includes('claude_code'))
|
|
322
|
+
assert.ok(result.includes('delegate_to_claude_code'))
|
|
323
|
+
// Just confirm it returned without hanging
|
|
324
|
+
assert.ok(result.length > 0)
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
it('connector aliases expand correctly', () => {
|
|
328
|
+
const result = expandPluginIds(['manage_connectors'])
|
|
329
|
+
assert.ok(result.includes('manage_connectors'))
|
|
330
|
+
assert.ok(result.includes('connectors'))
|
|
331
|
+
assert.ok(result.includes('connector_message_tool'))
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
it('sandbox aliases expand', () => {
|
|
335
|
+
const result = expandPluginIds(['sandbox'])
|
|
336
|
+
assert.ok(result.includes('sandbox'))
|
|
337
|
+
assert.ok(result.includes('sandbox_exec'))
|
|
338
|
+
assert.ok(result.includes('sandbox_list_runtimes'))
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
it('files expands to include read_file, write_file, etc.', () => {
|
|
342
|
+
const result = expandPluginIds(['files'])
|
|
343
|
+
assert.ok(result.includes('read_file'))
|
|
344
|
+
assert.ok(result.includes('write_file'))
|
|
345
|
+
assert.ok(result.includes('list_files'))
|
|
346
|
+
assert.ok(result.includes('copy_file'))
|
|
347
|
+
assert.ok(result.includes('move_file'))
|
|
348
|
+
assert.ok(result.includes('delete_file'))
|
|
349
|
+
assert.ok(result.includes('send_file'))
|
|
350
|
+
})
|
|
351
|
+
})
|
|
@@ -4,6 +4,13 @@ import crypto from 'crypto'
|
|
|
4
4
|
import { createRequire } from 'module'
|
|
5
5
|
import { spawn } from 'child_process'
|
|
6
6
|
import type { Plugin, PluginHooks, PluginMeta, PluginToolDef, PluginUIExtension, PluginProviderExtension, PluginConnectorExtension, Session, PluginPackageManager, PluginDependencyInstallStatus } from '@/types'
|
|
7
|
+
import {
|
|
8
|
+
inferPluginInstallSourceFromUrl,
|
|
9
|
+
inferPluginPublisherSourceFromUrl,
|
|
10
|
+
isMarketplaceInstallSource,
|
|
11
|
+
normalizePluginInstallSource,
|
|
12
|
+
normalizePluginPublisherSource,
|
|
13
|
+
} from '@/lib/plugin-sources'
|
|
7
14
|
import { DATA_DIR } from './data-dir'
|
|
8
15
|
import { canonicalizePluginId, expandPluginIds, getPluginAliases } from './tool-aliases'
|
|
9
16
|
import { log } from './logger'
|
|
@@ -34,6 +41,9 @@ interface PluginFailureRecord {
|
|
|
34
41
|
interface PluginConfigEntry {
|
|
35
42
|
enabled?: boolean
|
|
36
43
|
createdByAgentId?: string
|
|
44
|
+
source?: PluginMeta['source']
|
|
45
|
+
sourceLabel?: PluginMeta['sourceLabel']
|
|
46
|
+
installSource?: PluginMeta['installSource']
|
|
37
47
|
sourceUrl?: string
|
|
38
48
|
sourceHash?: string
|
|
39
49
|
installedAt?: number
|
|
@@ -97,6 +107,8 @@ type HookRegistrar = {
|
|
|
97
107
|
type HookContext<K extends keyof PluginHooks> =
|
|
98
108
|
PluginHooks[K] extends ((ctx: infer C) => unknown) | undefined ? C : never
|
|
99
109
|
|
|
110
|
+
type ApprovalGuidanceHook = NonNullable<PluginHooks['getApprovalGuidance']>
|
|
111
|
+
|
|
100
112
|
/** Legacy OpenClaw format: activate(ctx)/deactivate() */
|
|
101
113
|
interface OpenClawLegacyPlugin {
|
|
102
114
|
name: string
|
|
@@ -105,13 +117,123 @@ interface OpenClawLegacyPlugin {
|
|
|
105
117
|
deactivate?: () => void
|
|
106
118
|
}
|
|
107
119
|
|
|
120
|
+
function trimString(value: unknown): string {
|
|
121
|
+
return typeof value === 'string' ? value.trim() : ''
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function normalizeApprovalGuidanceLines(
|
|
125
|
+
value: string | string[] | null | undefined,
|
|
126
|
+
): string[] {
|
|
127
|
+
if (typeof value === 'string') {
|
|
128
|
+
const trimmed = value.trim()
|
|
129
|
+
return trimmed ? [trimmed] : []
|
|
130
|
+
}
|
|
131
|
+
if (!Array.isArray(value)) return []
|
|
132
|
+
return value
|
|
133
|
+
.map((line) => (typeof line === 'string' ? line.trim() : ''))
|
|
134
|
+
.filter(Boolean)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function dedupeApprovalGuidanceLines(lines: string[]): string[] {
|
|
138
|
+
return Array.from(new Set(lines.map((line) => line.trim()).filter(Boolean)))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function formatApprovalToolLabel(toolNames: string[]): string {
|
|
142
|
+
const uniqueNames = Array.from(new Set(toolNames.map((name) => name.trim()).filter(Boolean)))
|
|
143
|
+
if (uniqueNames.length === 0) return 'its tools'
|
|
144
|
+
if (uniqueNames.length === 1) return `\`${uniqueNames[0]}\``
|
|
145
|
+
if (uniqueNames.length === 2) return `\`${uniqueNames[0]}\` and \`${uniqueNames[1]}\``
|
|
146
|
+
return `${uniqueNames.slice(0, -1).map((name) => `\`${name}\``).join(', ')}, and \`${uniqueNames.at(-1)}\``
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function buildDefaultPluginApprovalGuidance(params: {
|
|
150
|
+
pluginId: string
|
|
151
|
+
pluginName: string
|
|
152
|
+
tools: PluginToolDef[]
|
|
153
|
+
}): ApprovalGuidanceHook {
|
|
154
|
+
const toolNames = params.tools
|
|
155
|
+
.map((tool) => (typeof tool?.name === 'string' ? tool.name.trim() : ''))
|
|
156
|
+
.filter(Boolean)
|
|
157
|
+
const toolLabel = formatApprovalToolLabel(toolNames)
|
|
158
|
+
const matchIds = new Set(
|
|
159
|
+
dedupeApprovalGuidanceLines([
|
|
160
|
+
params.pluginId,
|
|
161
|
+
...toolNames,
|
|
162
|
+
...expandPluginIds([params.pluginId]),
|
|
163
|
+
...toolNames.flatMap((toolName) => expandPluginIds([toolName])),
|
|
164
|
+
]).map((value) => canonicalizePluginId(value) || value.toLowerCase()),
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return ({ approval, phase, approved }) => {
|
|
168
|
+
if (approval.category !== 'tool_access') return null
|
|
169
|
+
const requestedIds = [
|
|
170
|
+
trimString(approval.data.pluginId),
|
|
171
|
+
trimString(approval.data.toolId),
|
|
172
|
+
trimString(approval.data.toolName),
|
|
173
|
+
].filter(Boolean)
|
|
174
|
+
const matchesPlugin = requestedIds.some((value) => {
|
|
175
|
+
const candidates = [value, ...expandPluginIds([value])]
|
|
176
|
+
return candidates.some((candidate) => matchIds.has(canonicalizePluginId(candidate) || candidate.toLowerCase()))
|
|
177
|
+
})
|
|
178
|
+
if (!matchesPlugin) return null
|
|
179
|
+
|
|
180
|
+
if (phase === 'connector_reminder') {
|
|
181
|
+
return `Approving this lets the agent use ${toolLabel} from ${params.pluginName}.`
|
|
182
|
+
}
|
|
183
|
+
if (approved === true) {
|
|
184
|
+
return [
|
|
185
|
+
`Access to ${params.pluginName} is approved. Continue with ${toolLabel} on the next turn.`,
|
|
186
|
+
'Do not request the same access again in prose once it has been approved.',
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
if (approved === false) {
|
|
190
|
+
return `Do not request access to ${params.pluginName} again unless the task or required capability materially changes.`
|
|
191
|
+
}
|
|
192
|
+
return [
|
|
193
|
+
`If access to ${params.pluginName} is granted, continue with ${toolLabel} on the next turn.`,
|
|
194
|
+
'Do not ask for the same access again in prose while this approval is pending.',
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function composeApprovalGuidance(
|
|
200
|
+
defaultHook: ApprovalGuidanceHook,
|
|
201
|
+
customHook?: PluginHooks['getApprovalGuidance'],
|
|
202
|
+
): ApprovalGuidanceHook {
|
|
203
|
+
return (ctx) => {
|
|
204
|
+
const combined = dedupeApprovalGuidanceLines([
|
|
205
|
+
...normalizeApprovalGuidanceLines(defaultHook(ctx)),
|
|
206
|
+
...normalizeApprovalGuidanceLines(customHook?.(ctx)),
|
|
207
|
+
])
|
|
208
|
+
return combined.length > 0 ? combined : null
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function buildPluginHooks(
|
|
213
|
+
pluginId: string,
|
|
214
|
+
pluginName: string,
|
|
215
|
+
hooks: PluginHooks | undefined,
|
|
216
|
+
tools: PluginToolDef[] | undefined,
|
|
217
|
+
): PluginHooks {
|
|
218
|
+
const nextHooks: PluginHooks = { ...(hooks || {}) }
|
|
219
|
+
nextHooks.getApprovalGuidance = composeApprovalGuidance(
|
|
220
|
+
buildDefaultPluginApprovalGuidance({
|
|
221
|
+
pluginId,
|
|
222
|
+
pluginName,
|
|
223
|
+
tools: tools || [],
|
|
224
|
+
}),
|
|
225
|
+
hooks?.getApprovalGuidance,
|
|
226
|
+
)
|
|
227
|
+
return nextHooks
|
|
228
|
+
}
|
|
229
|
+
|
|
108
230
|
/**
|
|
109
231
|
* Real OpenClaw plugin format: function export `(api) => {}` or object with `register(api)`.
|
|
110
232
|
* Supports api.registerHook(), api.registerTool(), api.registerCommand(), api.registerService().
|
|
111
233
|
*/
|
|
112
234
|
interface OpenClawPluginApi {
|
|
113
235
|
registerHook: (event: string, handler: (...args: unknown[]) => unknown, meta?: { name?: string; description?: string }) => void
|
|
114
|
-
registerTool: (def: PluginToolDef | { name: string; description?: string; parameters?: Record<string, unknown>; execute: (...args: unknown[]) => unknown }) => void
|
|
236
|
+
registerTool: (def: PluginToolDef | { name: string; description?: string; parameters?: Record<string, unknown>; planning?: PluginToolDef['planning']; execute: (...args: unknown[]) => unknown }) => void
|
|
115
237
|
registerCommand: (def: { name: string; description?: string; handler: (...args: unknown[]) => unknown }) => void
|
|
116
238
|
registerService: (def: { id: string; start: () => void; stop?: () => void }) => void
|
|
117
239
|
registerProvider: (def: Record<string, unknown>) => void
|
|
@@ -215,6 +337,30 @@ function toRawPluginUrl(url: string): string {
|
|
|
215
337
|
return url
|
|
216
338
|
}
|
|
217
339
|
|
|
340
|
+
function inferStoredPluginSource(config: PluginConfigEntry | null | undefined): PluginMeta['source'] {
|
|
341
|
+
if (config?.source === 'local' || config?.source === 'manual' || config?.source === 'marketplace') {
|
|
342
|
+
return config.source
|
|
343
|
+
}
|
|
344
|
+
if (config?.sourceUrl) {
|
|
345
|
+
const installSource = normalizePluginInstallSource(config?.installSource)
|
|
346
|
+
|| inferPluginInstallSourceFromUrl(config.sourceUrl)
|
|
347
|
+
return isMarketplaceInstallSource(installSource) ? 'marketplace' : 'manual'
|
|
348
|
+
}
|
|
349
|
+
return 'local'
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function inferStoredPublisherSource(config: PluginConfigEntry | null | undefined): NonNullable<PluginMeta['sourceLabel']> {
|
|
353
|
+
return normalizePluginPublisherSource(config?.sourceLabel)
|
|
354
|
+
|| inferPluginPublisherSourceFromUrl(config?.sourceUrl)
|
|
355
|
+
|| (config?.sourceUrl ? 'manual' : 'local')
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function inferStoredInstallSource(config: PluginConfigEntry | null | undefined): NonNullable<PluginMeta['installSource']> {
|
|
359
|
+
return normalizePluginInstallSource(config?.installSource)
|
|
360
|
+
|| inferPluginInstallSourceFromUrl(config?.sourceUrl)
|
|
361
|
+
|| (config?.sourceUrl ? 'manual' : 'local')
|
|
362
|
+
}
|
|
363
|
+
|
|
218
364
|
export function normalizeMarketplacePluginUrl(url: string): string {
|
|
219
365
|
const trimmed = typeof url === 'string' ? url.trim() : ''
|
|
220
366
|
if (!trimmed) return trimmed
|
|
@@ -295,6 +441,7 @@ function coerceTools(rawTools: unknown): PluginToolDef[] {
|
|
|
295
441
|
name,
|
|
296
442
|
description: typeof rawTool.description === 'string' ? rawTool.description : `Plugin tool: ${name}`,
|
|
297
443
|
parameters: isRecord(rawTool.parameters) ? rawTool.parameters : { type: 'object', properties: {} },
|
|
444
|
+
planning: isRecord(rawTool.planning) ? rawTool.planning as PluginToolDef['planning'] : undefined,
|
|
298
445
|
execute: execute as PluginToolDef['execute'],
|
|
299
446
|
})
|
|
300
447
|
}
|
|
@@ -320,6 +467,7 @@ function coerceTools(rawTools: unknown): PluginToolDef[] {
|
|
|
320
467
|
name,
|
|
321
468
|
description: typeof rawTool.description === 'string' ? rawTool.description : `Plugin tool: ${name}`,
|
|
322
469
|
parameters: isRecord(rawTool.parameters) ? rawTool.parameters : { type: 'object', properties: {} },
|
|
470
|
+
planning: isRecord(rawTool.planning) ? rawTool.planning as PluginToolDef['planning'] : undefined,
|
|
323
471
|
execute: rawTool.execute as PluginToolDef['execute'],
|
|
324
472
|
})
|
|
325
473
|
}
|
|
@@ -397,6 +545,9 @@ function normalizePlugin(mod: unknown): Plugin | null {
|
|
|
397
545
|
name: def.name,
|
|
398
546
|
description: def.description || `Plugin tool: ${def.name}`,
|
|
399
547
|
parameters: (def.parameters || { type: 'object', properties: {} }) as Record<string, unknown>,
|
|
548
|
+
planning: isRecord((def as Record<string, unknown>).planning)
|
|
549
|
+
? (def as PluginToolDef).planning
|
|
550
|
+
: undefined,
|
|
400
551
|
execute: def.execute as PluginToolDef['execute'],
|
|
401
552
|
})
|
|
402
553
|
}
|
|
@@ -830,9 +981,11 @@ class PluginManager {
|
|
|
830
981
|
author: p.author || 'SwarmClaw',
|
|
831
982
|
version: p.version || '1.0.0',
|
|
832
983
|
source: 'local',
|
|
984
|
+
sourceLabel: 'builtin',
|
|
985
|
+
installSource: 'builtin',
|
|
833
986
|
openclaw: p.openclaw === true,
|
|
834
987
|
},
|
|
835
|
-
hooks: p.hooks
|
|
988
|
+
hooks: buildPluginHooks(id, p.name, p.hooks, p.tools),
|
|
836
989
|
tools: p.tools || [],
|
|
837
990
|
ui: p.ui,
|
|
838
991
|
providers: p.providers,
|
|
@@ -850,7 +1003,7 @@ class PluginManager {
|
|
|
850
1003
|
|
|
851
1004
|
let dynamicRequire: NodeRequire | null = null
|
|
852
1005
|
try {
|
|
853
|
-
dynamicRequire = createRequire(
|
|
1006
|
+
dynamicRequire = createRequire(path.join(process.cwd(), 'package.json'))
|
|
854
1007
|
} catch (err: unknown) {
|
|
855
1008
|
log.warn('plugins', 'createRequire failed; external plugins disabled', {
|
|
856
1009
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -881,10 +1034,13 @@ class PluginManager {
|
|
|
881
1034
|
enabled: true,
|
|
882
1035
|
author: plugin.author,
|
|
883
1036
|
version: plugin.version || '0.0.1',
|
|
884
|
-
source: explicitConfig
|
|
1037
|
+
source: inferStoredPluginSource(explicitConfig),
|
|
1038
|
+
sourceLabel: inferStoredPublisherSource(explicitConfig),
|
|
1039
|
+
installSource: inferStoredInstallSource(explicitConfig),
|
|
1040
|
+
sourceUrl: explicitConfig?.sourceUrl,
|
|
885
1041
|
openclaw: plugin.openclaw === true,
|
|
886
1042
|
},
|
|
887
|
-
hooks: plugin.hooks
|
|
1043
|
+
hooks: buildPluginHooks(file, plugin.name, plugin.hooks, plugin.tools),
|
|
888
1044
|
tools: plugin.tools || [],
|
|
889
1045
|
ui: plugin.ui,
|
|
890
1046
|
providers: plugin.providers,
|
|
@@ -1141,6 +1297,44 @@ class PluginManager {
|
|
|
1141
1297
|
return lines
|
|
1142
1298
|
}
|
|
1143
1299
|
|
|
1300
|
+
/** Collect approval guidance from all enabled plugins for a specific approval event */
|
|
1301
|
+
collectApprovalGuidance(
|
|
1302
|
+
enabledPlugins: string[],
|
|
1303
|
+
ctx: {
|
|
1304
|
+
approval: import('@/types').ApprovalRequest
|
|
1305
|
+
phase: 'request' | 'resume' | 'connector_reminder'
|
|
1306
|
+
approved?: boolean
|
|
1307
|
+
},
|
|
1308
|
+
): string[] {
|
|
1309
|
+
this.load()
|
|
1310
|
+
const enabledSet = new Set(expandPluginIds(enabledPlugins))
|
|
1311
|
+
const lines: string[] = []
|
|
1312
|
+
|
|
1313
|
+
for (const [id, p] of this.plugins.entries()) {
|
|
1314
|
+
if (!enabledSet.has(id)) continue
|
|
1315
|
+
const hook = p.hooks.getApprovalGuidance
|
|
1316
|
+
if (!hook) continue
|
|
1317
|
+
try {
|
|
1318
|
+
const result = hook(ctx)
|
|
1319
|
+
if (result === null || result === undefined) continue
|
|
1320
|
+
if (typeof result === 'string' && result.trim()) {
|
|
1321
|
+
lines.push(result)
|
|
1322
|
+
} else if (Array.isArray(result)) {
|
|
1323
|
+
for (const line of result) {
|
|
1324
|
+
if (typeof line === 'string' && line.trim()) lines.push(line)
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
} catch (err: unknown) {
|
|
1328
|
+
log.error('plugins', 'getApprovalGuidance hook failed', {
|
|
1329
|
+
pluginId: id,
|
|
1330
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1331
|
+
})
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
return lines
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1144
1338
|
/** Collect all settings fields declared by enabled plugins */
|
|
1145
1339
|
collectSettingsFields(enabledPlugins: string[]): Array<{ pluginId: string; pluginName: string; fields: import('@/types').PluginSettingsField[] }> {
|
|
1146
1340
|
this.load()
|
|
@@ -1318,6 +1512,11 @@ class PluginManager {
|
|
|
1318
1512
|
return true
|
|
1319
1513
|
}
|
|
1320
1514
|
|
|
1515
|
+
isExplicitlyDisabled(filename: string): boolean {
|
|
1516
|
+
const explicit = this.readConfigEntry(filename)
|
|
1517
|
+
return explicit?.enabled === false
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1321
1520
|
listPlugins(): PluginMeta[] {
|
|
1322
1521
|
try {
|
|
1323
1522
|
this.load()
|
|
@@ -1358,6 +1557,9 @@ class PluginManager {
|
|
|
1358
1557
|
author: p.author || 'SwarmClaw',
|
|
1359
1558
|
version: (p as { version?: string }).version || loaded?.meta.version || '1.0.0',
|
|
1360
1559
|
source: loaded?.meta.source || 'local',
|
|
1560
|
+
sourceLabel: 'builtin',
|
|
1561
|
+
installSource: 'builtin',
|
|
1562
|
+
sourceUrl: loaded?.meta.sourceUrl,
|
|
1361
1563
|
openclaw: p.openclaw === true,
|
|
1362
1564
|
failureCount: failure?.count,
|
|
1363
1565
|
lastFailureAt: failure?.lastFailedAt,
|
|
@@ -1386,7 +1588,10 @@ class PluginManager {
|
|
|
1386
1588
|
isBuiltin: false,
|
|
1387
1589
|
author: loaded?.meta.author,
|
|
1388
1590
|
version: loaded?.meta.version || '0.0.1',
|
|
1389
|
-
source: loaded?.meta.source || (explicitCfg
|
|
1591
|
+
source: loaded?.meta.source || inferStoredPluginSource(explicitCfg),
|
|
1592
|
+
sourceLabel: loaded?.meta.sourceLabel || inferStoredPublisherSource(explicitCfg),
|
|
1593
|
+
installSource: loaded?.meta.installSource || inferStoredInstallSource(explicitCfg),
|
|
1594
|
+
sourceUrl: loaded?.meta.sourceUrl || explicitCfg?.sourceUrl,
|
|
1390
1595
|
openclaw: loaded?.meta.openclaw,
|
|
1391
1596
|
createdByAgentId: explicitCfg?.createdByAgentId || null,
|
|
1392
1597
|
failureCount: failure?.count,
|