@swarmclawai/swarmclaw 0.7.8 → 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 -15
- 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 +22 -2
- package/src/app/api/clawhub/install/route.ts +28 -8
- package/src/app/api/connectors/[id]/route.ts +26 -1
- 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/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/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 +73 -24
- package/src/components/agents/inspector-panel.tsx +41 -0
- package/src/components/canvas/canvas-panel.tsx +236 -65
- package/src/components/chat/chat-card.tsx +36 -13
- package/src/components/chat/chat-header.tsx +44 -16
- package/src/components/chat/chat-list.tsx +28 -4
- package/src/components/chat/checkpoint-timeline.tsx +50 -34
- 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/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/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 +7 -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 +191 -95
- package/src/components/tasks/task-board.tsx +273 -2
- package/src/components/tasks/task-card.tsx +38 -9
- 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 +11 -0
- package/src/lib/server/capability-router.ts +26 -1
- 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 +353 -72
- package/src/lib/server/clawhub-client.test.ts +14 -8
- package/src/lib/server/connectors/manager.test.ts +1147 -0
- package/src/lib/server/connectors/manager.ts +362 -63
- 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 +1 -1
- 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 +189 -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 +15 -10
- 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/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 +2 -2
- package/src/lib/server/plugins-advanced.test.ts +351 -0
- package/src/lib/server/plugins.ts +205 -5
- package/src/lib/server/queue-advanced.test.ts +528 -0
- package/src/lib/server/queue-followups.test.ts +262 -0
- package/src/lib/server/queue-reconcile.test.ts +128 -0
- package/src/lib/server/queue.ts +293 -61
- 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 +52 -4
- package/src/lib/server/session-tools/canvas.ts +14 -12
- package/src/lib/server/session-tools/connector.test.ts +138 -0
- package/src/lib/server/session-tools/connector.ts +348 -61
- package/src/lib/server/session-tools/context.ts +12 -3
- package/src/lib/server/session-tools/crud.ts +221 -10
- package/src/lib/server/session-tools/delegate-fallback.test.ts +103 -0
- package/src/lib/server/session-tools/delegate.ts +64 -8
- 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/memory.test.ts +93 -0
- package/src/lib/server/session-tools/memory.ts +546 -79
- package/src/lib/server/session-tools/normalize-tool-args.ts +1 -1
- 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 +162 -1
- package/src/lib/server/session-tools/web.ts +468 -64
- 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 +419 -9
- package/src/lib/server/stream-agent-chat.ts +887 -83
- package/src/lib/server/system-events.ts +1 -1
- package/src/lib/server/tool-capability-policy-advanced.test.ts +502 -0
- 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.ts +4 -2
- 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-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 +210 -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,6 +117,116 @@ 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().
|
|
@@ -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
|
|
@@ -835,9 +981,11 @@ class PluginManager {
|
|
|
835
981
|
author: p.author || 'SwarmClaw',
|
|
836
982
|
version: p.version || '1.0.0',
|
|
837
983
|
source: 'local',
|
|
984
|
+
sourceLabel: 'builtin',
|
|
985
|
+
installSource: 'builtin',
|
|
838
986
|
openclaw: p.openclaw === true,
|
|
839
987
|
},
|
|
840
|
-
hooks: p.hooks
|
|
988
|
+
hooks: buildPluginHooks(id, p.name, p.hooks, p.tools),
|
|
841
989
|
tools: p.tools || [],
|
|
842
990
|
ui: p.ui,
|
|
843
991
|
providers: p.providers,
|
|
@@ -855,7 +1003,7 @@ class PluginManager {
|
|
|
855
1003
|
|
|
856
1004
|
let dynamicRequire: NodeRequire | null = null
|
|
857
1005
|
try {
|
|
858
|
-
dynamicRequire = createRequire(
|
|
1006
|
+
dynamicRequire = createRequire(path.join(process.cwd(), 'package.json'))
|
|
859
1007
|
} catch (err: unknown) {
|
|
860
1008
|
log.warn('plugins', 'createRequire failed; external plugins disabled', {
|
|
861
1009
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -886,10 +1034,13 @@ class PluginManager {
|
|
|
886
1034
|
enabled: true,
|
|
887
1035
|
author: plugin.author,
|
|
888
1036
|
version: plugin.version || '0.0.1',
|
|
889
|
-
source: explicitConfig
|
|
1037
|
+
source: inferStoredPluginSource(explicitConfig),
|
|
1038
|
+
sourceLabel: inferStoredPublisherSource(explicitConfig),
|
|
1039
|
+
installSource: inferStoredInstallSource(explicitConfig),
|
|
1040
|
+
sourceUrl: explicitConfig?.sourceUrl,
|
|
890
1041
|
openclaw: plugin.openclaw === true,
|
|
891
1042
|
},
|
|
892
|
-
hooks: plugin.hooks
|
|
1043
|
+
hooks: buildPluginHooks(file, plugin.name, plugin.hooks, plugin.tools),
|
|
893
1044
|
tools: plugin.tools || [],
|
|
894
1045
|
ui: plugin.ui,
|
|
895
1046
|
providers: plugin.providers,
|
|
@@ -1146,6 +1297,44 @@ class PluginManager {
|
|
|
1146
1297
|
return lines
|
|
1147
1298
|
}
|
|
1148
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
|
+
|
|
1149
1338
|
/** Collect all settings fields declared by enabled plugins */
|
|
1150
1339
|
collectSettingsFields(enabledPlugins: string[]): Array<{ pluginId: string; pluginName: string; fields: import('@/types').PluginSettingsField[] }> {
|
|
1151
1340
|
this.load()
|
|
@@ -1323,6 +1512,11 @@ class PluginManager {
|
|
|
1323
1512
|
return true
|
|
1324
1513
|
}
|
|
1325
1514
|
|
|
1515
|
+
isExplicitlyDisabled(filename: string): boolean {
|
|
1516
|
+
const explicit = this.readConfigEntry(filename)
|
|
1517
|
+
return explicit?.enabled === false
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1326
1520
|
listPlugins(): PluginMeta[] {
|
|
1327
1521
|
try {
|
|
1328
1522
|
this.load()
|
|
@@ -1363,6 +1557,9 @@ class PluginManager {
|
|
|
1363
1557
|
author: p.author || 'SwarmClaw',
|
|
1364
1558
|
version: (p as { version?: string }).version || loaded?.meta.version || '1.0.0',
|
|
1365
1559
|
source: loaded?.meta.source || 'local',
|
|
1560
|
+
sourceLabel: 'builtin',
|
|
1561
|
+
installSource: 'builtin',
|
|
1562
|
+
sourceUrl: loaded?.meta.sourceUrl,
|
|
1366
1563
|
openclaw: p.openclaw === true,
|
|
1367
1564
|
failureCount: failure?.count,
|
|
1368
1565
|
lastFailureAt: failure?.lastFailedAt,
|
|
@@ -1391,7 +1588,10 @@ class PluginManager {
|
|
|
1391
1588
|
isBuiltin: false,
|
|
1392
1589
|
author: loaded?.meta.author,
|
|
1393
1590
|
version: loaded?.meta.version || '0.0.1',
|
|
1394
|
-
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,
|
|
1395
1595
|
openclaw: loaded?.meta.openclaw,
|
|
1396
1596
|
createdByAgentId: explicitCfg?.createdByAgentId || null,
|
|
1397
1597
|
failureCount: failure?.count,
|