@swarmclawai/swarmclaw 0.7.2 → 0.7.4
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 +116 -50
- package/bin/package-manager.js +157 -0
- package/bin/package-manager.test.js +90 -0
- package/bin/server-cmd.js +38 -7
- package/bin/swarmclaw.js +54 -4
- package/bin/update-cmd.js +48 -10
- package/bin/update-cmd.test.js +55 -0
- package/package.json +8 -3
- package/scripts/postinstall.mjs +26 -0
- package/src/app/api/agents/[id]/route.ts +43 -0
- package/src/app/api/agents/[id]/thread/route.ts +39 -8
- package/src/app/api/agents/route.ts +35 -2
- package/src/app/api/auth/route.ts +77 -8
- package/src/app/api/chatrooms/[id]/chat/route.ts +22 -6
- package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/route.ts +6 -0
- package/src/app/api/chats/[id]/browser/route.ts +5 -1
- package/src/app/api/chats/[id]/chat/route.ts +7 -3
- package/src/app/api/chats/[id]/messages/route.ts +19 -13
- package/src/app/api/chats/[id]/route.ts +30 -0
- package/src/app/api/chats/[id]/stop/route.ts +6 -1
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/route.ts +23 -1
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
- package/src/app/api/connectors/doctor/route.ts +13 -0
- package/src/app/api/external-agents/[id]/heartbeat/route.ts +33 -0
- package/src/app/api/external-agents/[id]/route.ts +31 -0
- package/src/app/api/external-agents/register/route.ts +3 -0
- package/src/app/api/external-agents/route.ts +66 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/gateways/[id]/health/route.ts +28 -0
- package/src/app/api/gateways/[id]/route.ts +79 -0
- package/src/app/api/gateways/route.ts +57 -0
- package/src/app/api/memory/maintenance/route.ts +11 -1
- package/src/app/api/openclaw/agent-files/route.ts +27 -4
- package/src/app/api/openclaw/gateway/route.ts +10 -7
- package/src/app/api/openclaw/skills/route.ts +12 -4
- package/src/app/api/plugins/dependencies/route.ts +24 -0
- package/src/app/api/plugins/install/route.ts +15 -92
- package/src/app/api/plugins/route.ts +3 -26
- package/src/app/api/plugins/settings/route.ts +17 -12
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
- package/src/app/api/schedules/[id]/route.ts +38 -9
- package/src/app/api/schedules/route.ts +51 -28
- package/src/app/api/settings/route.ts +55 -17
- package/src/app/api/setup/doctor/route.ts +6 -4
- package/src/app/api/tasks/[id]/route.ts +16 -6
- package/src/app/api/tasks/bulk/route.ts +3 -3
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +135 -17
- package/src/cli/binary.test.js +142 -0
- package/src/cli/index.js +38 -11
- package/src/cli/index.test.js +195 -0
- package/src/cli/index.ts +21 -12
- package/src/cli/server-cmd.test.js +59 -0
- package/src/cli/spec.js +20 -2
- package/src/components/agents/agent-card.tsx +15 -12
- package/src/components/agents/agent-chat-list.tsx +101 -1
- package/src/components/agents/agent-list.tsx +46 -9
- package/src/components/agents/agent-sheet.tsx +456 -23
- package/src/components/agents/inspector-panel.tsx +110 -49
- package/src/components/agents/sandbox-env-panel.tsx +4 -1
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +970 -275
- package/src/components/chat/chat-area.tsx +70 -27
- package/src/components/chat/chat-card.tsx +6 -21
- package/src/components/chat/chat-header.tsx +263 -366
- package/src/components/chat/chat-list.tsx +62 -26
- package/src/components/chat/checkpoint-timeline.tsx +1 -1
- package/src/components/chat/message-list.tsx +145 -19
- package/src/components/chatrooms/chatroom-input.tsx +96 -33
- package/src/components/chatrooms/chatroom-list.tsx +141 -72
- package/src/components/chatrooms/chatroom-message.tsx +7 -6
- package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
- package/src/components/chatrooms/chatroom-view.tsx +422 -209
- package/src/components/chatrooms/reaction-picker.tsx +38 -33
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +217 -0
- package/src/components/gateways/gateway-sheet.tsx +567 -0
- package/src/components/home/home-view.tsx +128 -4
- package/src/components/input/chat-input.tsx +135 -86
- package/src/components/layout/app-layout.tsx +385 -194
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/memory/memory-browser.tsx +71 -6
- package/src/components/memory/memory-card.tsx +18 -0
- package/src/components/memory/memory-detail.tsx +58 -31
- package/src/components/memory/memory-sheet.tsx +32 -4
- package/src/components/plugins/plugin-list.tsx +15 -3
- package/src/components/plugins/plugin-sheet.tsx +118 -9
- package/src/components/projects/project-detail.tsx +189 -1
- package/src/components/providers/provider-list.tsx +158 -2
- package/src/components/providers/provider-sheet.tsx +81 -70
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/bottom-sheet.tsx +31 -15
- package/src/components/shared/command-palette.tsx +111 -24
- package/src/components/shared/confirm-dialog.tsx +45 -30
- package/src/components/shared/model-combobox.tsx +90 -8
- package/src/components/shared/settings/plugin-manager.tsx +20 -4
- package/src/components/shared/settings/section-capability-policy.tsx +105 -0
- package/src/components/shared/settings/section-heartbeat.tsx +88 -6
- package/src/components/shared/settings/section-orchestrator.tsx +6 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
- package/src/components/shared/settings/section-secrets.tsx +6 -6
- package/src/components/shared/settings/section-user-preferences.tsx +1 -1
- package/src/components/shared/settings/section-voice.tsx +5 -1
- package/src/components/shared/settings/section-web-search.tsx +10 -2
- package/src/components/shared/settings/settings-page.tsx +248 -47
- package/src/components/tasks/approvals-panel.tsx +211 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/usage/metrics-dashboard.tsx +74 -1
- package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +7 -7
- package/src/lib/auth.ts +17 -0
- package/src/lib/chat-streaming-state.test.ts +108 -0
- package/src/lib/chat-streaming-state.ts +108 -0
- package/src/lib/heartbeat-defaults.ts +48 -0
- package/src/lib/memory-presentation.ts +59 -0
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/provider-model-discovery-client.ts +29 -0
- package/src/lib/providers/index.ts +12 -5
- package/src/lib/runtime-loop.ts +105 -3
- package/src/lib/safe-storage.ts +6 -1
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/agent-runtime-config.test.ts +141 -0
- package/src/lib/server/agent-runtime-config.ts +277 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +264 -0
- package/src/lib/server/approvals.ts +483 -75
- package/src/lib/server/autonomy-runtime.test.ts +341 -0
- package/src/lib/server/browser-state.test.ts +118 -0
- package/src/lib/server/browser-state.ts +123 -0
- package/src/lib/server/build-llm.test.ts +44 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +219 -0
- package/src/lib/server/chat-execution.ts +402 -125
- package/src/lib/server/chatroom-health.test.ts +26 -0
- package/src/lib/server/chatroom-health.ts +2 -3
- package/src/lib/server/chatroom-helpers.test.ts +74 -2
- package/src/lib/server/chatroom-helpers.ts +144 -11
- package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
- package/src/lib/server/connectors/discord.ts +175 -11
- package/src/lib/server/connectors/doctor.test.ts +80 -0
- package/src/lib/server/connectors/doctor.ts +116 -0
- package/src/lib/server/connectors/manager.ts +994 -130
- package/src/lib/server/connectors/policy.test.ts +222 -0
- package/src/lib/server/connectors/policy.ts +452 -0
- package/src/lib/server/connectors/slack.ts +189 -10
- package/src/lib/server/connectors/telegram.ts +65 -15
- package/src/lib/server/connectors/thread-context.test.ts +44 -0
- package/src/lib/server/connectors/thread-context.ts +72 -0
- package/src/lib/server/connectors/types.ts +41 -11
- package/src/lib/server/daemon-state.ts +62 -3
- package/src/lib/server/data-dir.ts +13 -0
- package/src/lib/server/delegation-jobs.test.ts +140 -0
- package/src/lib/server/delegation-jobs.ts +248 -0
- package/src/lib/server/document-utils.test.ts +47 -0
- package/src/lib/server/document-utils.ts +397 -0
- package/src/lib/server/eval/agent-regression.test.ts +47 -0
- package/src/lib/server/eval/agent-regression.ts +1742 -0
- package/src/lib/server/eval/runner.ts +11 -1
- package/src/lib/server/eval/store.ts +2 -1
- package/src/lib/server/heartbeat-service.ts +23 -43
- package/src/lib/server/heartbeat-source.test.ts +22 -0
- package/src/lib/server/heartbeat-source.ts +7 -0
- package/src/lib/server/identity-continuity.test.ts +77 -0
- package/src/lib/server/identity-continuity.ts +127 -0
- package/src/lib/server/mailbox-utils.ts +347 -0
- package/src/lib/server/main-agent-loop.ts +31 -964
- package/src/lib/server/memory-db.ts +4 -6
- package/src/lib/server/memory-tiers.ts +40 -0
- package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
- package/src/lib/server/openclaw-agent-resolver.ts +128 -0
- package/src/lib/server/openclaw-exec-config.ts +6 -5
- package/src/lib/server/openclaw-gateway.ts +123 -36
- package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
- package/src/lib/server/openclaw-skills-normalize.ts +136 -0
- package/src/lib/server/openclaw-sync.ts +3 -2
- package/src/lib/server/orchestrator-lg.ts +18 -8
- package/src/lib/server/orchestrator.ts +5 -4
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +215 -0
- package/src/lib/server/plugins.ts +832 -69
- package/src/lib/server/provider-health.ts +33 -3
- package/src/lib/server/provider-model-discovery.ts +481 -0
- package/src/lib/server/queue.ts +4 -21
- package/src/lib/server/runtime-settings.test.ts +119 -0
- package/src/lib/server/runtime-settings.ts +12 -92
- package/src/lib/server/schedule-normalization.ts +187 -0
- package/src/lib/server/scheduler.ts +2 -0
- package/src/lib/server/session-archive-memory.test.ts +85 -0
- package/src/lib/server/session-archive-memory.ts +230 -0
- package/src/lib/server/session-mailbox.ts +8 -18
- package/src/lib/server/session-reset-policy.test.ts +99 -0
- package/src/lib/server/session-reset-policy.ts +311 -0
- package/src/lib/server/session-run-manager.ts +33 -80
- package/src/lib/server/session-tools/autonomy-tools.test.ts +128 -0
- package/src/lib/server/session-tools/calendar.ts +2 -12
- package/src/lib/server/session-tools/connector.ts +109 -8
- package/src/lib/server/session-tools/context.ts +14 -2
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +96 -34
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +406 -20
- package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
- package/src/lib/server/session-tools/discovery.ts +40 -12
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/email.ts +1 -3
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +98 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +243 -24
- package/src/lib/server/session-tools/http.ts +9 -3
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +1 -3
- package/src/lib/server/session-tools/index.ts +87 -2
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
- package/src/lib/server/session-tools/memory.ts +35 -3
- package/src/lib/server/session-tools/monitor.ts +162 -12
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +142 -4
- package/src/lib/server/session-tools/plugin-creator.ts +95 -25
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +1 -3
- package/src/lib/server/session-tools/sandbox.ts +51 -92
- package/src/lib/server/session-tools/schedule.ts +20 -10
- package/src/lib/server/session-tools/session-info.ts +58 -4
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +54 -17
- package/src/lib/server/session-tools/shell.ts +2 -2
- package/src/lib/server/session-tools/subagent.ts +195 -27
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +13 -10
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +947 -108
- package/src/lib/server/storage.ts +255 -10
- package/src/lib/server/stream-agent-chat.test.ts +61 -0
- package/src/lib/server/stream-agent-chat.ts +185 -25
- package/src/lib/server/structured-extract.test.ts +72 -0
- package/src/lib/server/structured-extract.ts +373 -0
- package/src/lib/server/task-mention.test.ts +16 -2
- package/src/lib/server/task-mention.ts +61 -11
- package/src/lib/server/tool-aliases.ts +80 -12
- package/src/lib/server/tool-capability-policy.ts +7 -1
- package/src/lib/server/tool-retry.ts +2 -0
- package/src/lib/server/watch-jobs.test.ts +173 -0
- package/src/lib/server/watch-jobs.ts +532 -0
- package/src/lib/server/ws-hub.ts +5 -3
- package/src/lib/setup-defaults.ts +352 -11
- package/src/lib/tool-definitions.ts +3 -4
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +62 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +43 -7
- package/src/stores/use-chat-store.ts +31 -2
- package/src/stores/use-chatroom-store.ts +153 -26
- package/src/types/index.ts +470 -44
- package/src/app/api/chats/[id]/main-loop/route.ts +0 -94
- package/src/components/chat/new-chat-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -17
- package/src/lib/server/session-run-manager.test.ts +0 -26
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { OpenClawSkillEntry, SkillInstallOption, SkillRequirements } from '@/types'
|
|
2
|
+
|
|
3
|
+
interface GatewayConfigCheck {
|
|
4
|
+
path?: string
|
|
5
|
+
satisfied?: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface GatewayInstallOption {
|
|
9
|
+
kind?: string
|
|
10
|
+
label?: string
|
|
11
|
+
bins?: string[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface GatewaySkillRequirements {
|
|
15
|
+
bins?: string[]
|
|
16
|
+
anyBins?: string[][]
|
|
17
|
+
env?: string[]
|
|
18
|
+
config?: string[]
|
|
19
|
+
os?: string[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface GatewaySkillEntry {
|
|
23
|
+
name?: string
|
|
24
|
+
description?: string
|
|
25
|
+
source?: string
|
|
26
|
+
eligible?: boolean
|
|
27
|
+
requirements?: GatewaySkillRequirements
|
|
28
|
+
missing?: GatewaySkillRequirements
|
|
29
|
+
disabled?: boolean
|
|
30
|
+
install?: GatewayInstallOption[]
|
|
31
|
+
configChecks?: GatewayConfigCheck[]
|
|
32
|
+
skillKey?: string
|
|
33
|
+
baseDir?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface GatewaySkillsStatusPayload {
|
|
37
|
+
skills?: GatewaySkillEntry[]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function uniq(values: Array<string | undefined | null>): string[] {
|
|
41
|
+
return [...new Set(values.map((value) => (value ?? '').trim()).filter(Boolean))]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeSource(source: string | undefined): OpenClawSkillEntry['source'] {
|
|
45
|
+
switch ((source ?? '').trim()) {
|
|
46
|
+
case 'openclaw-bundled':
|
|
47
|
+
case 'bundled':
|
|
48
|
+
return 'bundled'
|
|
49
|
+
case 'managed':
|
|
50
|
+
return 'managed'
|
|
51
|
+
case 'personal':
|
|
52
|
+
return 'personal'
|
|
53
|
+
case 'workspace':
|
|
54
|
+
return 'workspace'
|
|
55
|
+
default:
|
|
56
|
+
return 'workspace'
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizeInstallOptions(install: GatewayInstallOption[] | undefined): SkillInstallOption[] | undefined {
|
|
61
|
+
if (!Array.isArray(install) || !install.length) return undefined
|
|
62
|
+
const normalized = install
|
|
63
|
+
.map((entry) => {
|
|
64
|
+
const kind = (entry.kind ?? '').trim()
|
|
65
|
+
if (!kind || !entry.label?.trim()) return null
|
|
66
|
+
if (!['brew', 'node', 'go', 'uv', 'download'].includes(kind)) return null
|
|
67
|
+
return {
|
|
68
|
+
kind: kind as SkillInstallOption['kind'],
|
|
69
|
+
label: entry.label.trim(),
|
|
70
|
+
bins: Array.isArray(entry.bins) ? uniq(entry.bins) : undefined,
|
|
71
|
+
} satisfies SkillInstallOption
|
|
72
|
+
})
|
|
73
|
+
.filter((value): value is NonNullable<typeof value> => value !== null)
|
|
74
|
+
return normalized.length ? normalized : undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizeRequirements(input: GatewaySkillRequirements | undefined): SkillRequirements | undefined {
|
|
78
|
+
if (!input || typeof input !== 'object') return undefined
|
|
79
|
+
const bins = Array.isArray(input.bins) ? uniq(input.bins) : undefined
|
|
80
|
+
const anyBins = Array.isArray(input.anyBins)
|
|
81
|
+
? input.anyBins
|
|
82
|
+
.map((group) => Array.isArray(group) ? uniq(group) : [])
|
|
83
|
+
.filter((group) => group.length > 0)
|
|
84
|
+
: undefined
|
|
85
|
+
const env = Array.isArray(input.env) ? uniq(input.env) : undefined
|
|
86
|
+
const config = Array.isArray(input.config) ? uniq(input.config) : undefined
|
|
87
|
+
const os = Array.isArray(input.os) ? uniq(input.os) : undefined
|
|
88
|
+
if (!bins && !anyBins && !env && !config && !os) return undefined
|
|
89
|
+
return { bins, anyBins, env, config, os }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function flattenMissing(input: GatewaySkillRequirements | undefined): string[] | undefined {
|
|
93
|
+
if (!input || typeof input !== 'object') return undefined
|
|
94
|
+
const out: string[] = []
|
|
95
|
+
for (const value of Array.isArray(input.bins) ? uniq(input.bins) : []) out.push(value)
|
|
96
|
+
for (const group of Array.isArray(input.anyBins) ? input.anyBins : []) {
|
|
97
|
+
const normalized = Array.isArray(group) ? uniq(group) : []
|
|
98
|
+
if (normalized.length) out.push(`one of: ${normalized.join(' | ')}`)
|
|
99
|
+
}
|
|
100
|
+
for (const value of Array.isArray(input.env) ? uniq(input.env) : []) out.push(`env ${value}`)
|
|
101
|
+
for (const value of Array.isArray(input.config) ? uniq(input.config) : []) out.push(`config ${value}`)
|
|
102
|
+
for (const value of Array.isArray(input.os) ? uniq(input.os) : []) out.push(`os ${value}`)
|
|
103
|
+
return out.length ? out : undefined
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function normalizeOpenClawSkillsPayload(payload: unknown): OpenClawSkillEntry[] {
|
|
107
|
+
const rawSkills = Array.isArray(payload)
|
|
108
|
+
? payload as GatewaySkillEntry[]
|
|
109
|
+
: Array.isArray((payload as GatewaySkillsStatusPayload | null | undefined)?.skills)
|
|
110
|
+
? (payload as GatewaySkillsStatusPayload).skills!
|
|
111
|
+
: []
|
|
112
|
+
|
|
113
|
+
return rawSkills
|
|
114
|
+
.map((skill) => {
|
|
115
|
+
const name = skill.name?.trim()
|
|
116
|
+
if (!name) return null
|
|
117
|
+
return {
|
|
118
|
+
name,
|
|
119
|
+
description: skill.description?.trim() || undefined,
|
|
120
|
+
source: normalizeSource(skill.source),
|
|
121
|
+
eligible: skill.eligible === true,
|
|
122
|
+
missing: flattenMissing(skill.missing),
|
|
123
|
+
disabled: skill.disabled === true,
|
|
124
|
+
installOptions: normalizeInstallOptions(skill.install),
|
|
125
|
+
skillRequirements: normalizeRequirements(skill.requirements),
|
|
126
|
+
configChecks: Array.isArray(skill.configChecks)
|
|
127
|
+
? skill.configChecks
|
|
128
|
+
.filter((check) => check.path?.trim())
|
|
129
|
+
.map((check) => ({ key: check.path!.trim(), ok: check.satisfied === true }))
|
|
130
|
+
: undefined,
|
|
131
|
+
skillKey: skill.skillKey?.trim() || undefined,
|
|
132
|
+
baseDir: skill.baseDir?.trim() || undefined,
|
|
133
|
+
} satisfies OpenClawSkillEntry
|
|
134
|
+
})
|
|
135
|
+
.filter((skill): skill is NonNullable<typeof skill> => skill !== null)
|
|
136
|
+
}
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import crypto from 'node:crypto'
|
|
4
4
|
import { DATA_DIR } from './data-dir'
|
|
5
|
+
import { normalizeOpenClawAgentId } from '@/lib/openclaw-agent-id'
|
|
5
6
|
import { loadSettings, loadAgents, saveAgents, loadSchedules, saveSchedules, loadCredentials, decryptKey, encryptKey } from './storage'
|
|
6
7
|
import { getMemoryDb } from './memory-db'
|
|
7
8
|
import type { AppSettings, MemoryEntry, Schedule } from '@/types'
|
|
@@ -182,7 +183,7 @@ export function pushAgentToOpenClaw(agentId: string): { written: string[] } {
|
|
|
182
183
|
const agent = agents[agentId]
|
|
183
184
|
if (!agent) throw new Error(`Agent not found: ${agentId}`)
|
|
184
185
|
|
|
185
|
-
const agentDir = path.join(config.workspacePath, 'agents', agent.name
|
|
186
|
+
const agentDir = path.join(config.workspacePath, 'agents', normalizeOpenClawAgentId(agent.name))
|
|
186
187
|
ensureDir(agentDir)
|
|
187
188
|
|
|
188
189
|
const written: string[] = []
|
|
@@ -214,7 +215,7 @@ export function pullAgentFromOpenClaw(agentId: string): { updated: string[] } {
|
|
|
214
215
|
const agent = agents[agentId]
|
|
215
216
|
if (!agent) throw new Error(`Agent not found: ${agentId}`)
|
|
216
217
|
|
|
217
|
-
const agentDir = path.join(config.workspacePath, 'agents', agent.name
|
|
218
|
+
const agentDir = path.join(config.workspacePath, 'agents', normalizeOpenClawAgentId(agent.name))
|
|
218
219
|
const updated: string[] = []
|
|
219
220
|
|
|
220
221
|
const soulPath = path.join(agentDir, 'SOUL.md')
|
|
@@ -13,6 +13,7 @@ import { notify } from './ws-hub'
|
|
|
13
13
|
import { pushMainLoopEventToMainSessions } from './main-agent-loop'
|
|
14
14
|
import { buildCurrentDateTimePromptContext } from './prompt-runtime-context'
|
|
15
15
|
import { getPluginManager } from './plugins'
|
|
16
|
+
import './builtin-plugins'
|
|
16
17
|
import { genId } from '@/lib/id'
|
|
17
18
|
import { NON_LANGGRAPH_PROVIDER_IDS } from '@/lib/provider-sets'
|
|
18
19
|
import type { Agent, TaskComment, MessageToolEvent } from '@/types'
|
|
@@ -118,7 +119,7 @@ async function executeSubTaskViaCli(agent: Agent, task: string, parentSessionId:
|
|
|
118
119
|
messages: [],
|
|
119
120
|
createdAt: Date.now(),
|
|
120
121
|
lastActiveAt: Date.now(),
|
|
121
|
-
sessionType: '
|
|
122
|
+
sessionType: 'human' as const,
|
|
122
123
|
agentId: agent.id,
|
|
123
124
|
parentSessionId,
|
|
124
125
|
plugins: agent.plugins || agent.tools || [],
|
|
@@ -177,7 +178,11 @@ export async function executeLangGraphOrchestrator(
|
|
|
177
178
|
return `Agent "${agentName}" not found. Available: ${agents.map((a) => a.name).join(', ')}`
|
|
178
179
|
}
|
|
179
180
|
console.log(`[orchestrator-lg] Delegating to ${agent.name}: ${agentTask.slice(0, 80)}`)
|
|
180
|
-
getPluginManager().runHook(
|
|
181
|
+
getPluginManager().runHook(
|
|
182
|
+
'onAgentDelegation',
|
|
183
|
+
{ sourceAgentId: orchestrator.id, targetAgentId: agent.id, task: agentTask },
|
|
184
|
+
{ enabledIds: orchestrator.plugins || [] },
|
|
185
|
+
)
|
|
181
186
|
const result = await executeSubTaskViaCli(agent, agentTask, sessionId)
|
|
182
187
|
saveMessage(sessionId, 'assistant', `Delegated to ${agent.name}: ${agentTask.slice(0, 100)}`, [{
|
|
183
188
|
name: 'delegate_to_agent',
|
|
@@ -393,6 +398,7 @@ export async function executeLangGraphOrchestrator(
|
|
|
393
398
|
|
|
394
399
|
const checkpointSaver = getCheckpointSaver()
|
|
395
400
|
const isStrictMode = settings.capabilityPolicyMode === 'strict'
|
|
401
|
+
const approvalInterruptsEnabled = isStrictMode && settings.approvalsEnabled !== false
|
|
396
402
|
const allTools = [delegateTool, storeMemoryTool, searchMemoryTool, getSecretTool, commentOnTaskTool, createTaskTool, markCompleteTool]
|
|
397
403
|
const llmWithTools = llm.bindTools(allTools)
|
|
398
404
|
const toolNode = new ToolNode(allTools)
|
|
@@ -472,7 +478,7 @@ export async function executeLangGraphOrchestrator(
|
|
|
472
478
|
|
|
473
479
|
const compiledGraph = graph.compile({
|
|
474
480
|
checkpointer: checkpointSaver,
|
|
475
|
-
...(
|
|
481
|
+
...(approvalInterruptsEnabled ? { interruptBefore: ['tools'] } : {}),
|
|
476
482
|
})
|
|
477
483
|
|
|
478
484
|
// Export graph structure for introspection
|
|
@@ -534,7 +540,7 @@ export async function executeLangGraphOrchestrator(
|
|
|
534
540
|
}
|
|
535
541
|
|
|
536
542
|
// Check for interrupt (paused before tool execution in strict mode)
|
|
537
|
-
if (
|
|
543
|
+
if (approvalInterruptsEnabled && taskId) {
|
|
538
544
|
const state = await compiledGraph.getState({ configurable: { thread_id: threadId } })
|
|
539
545
|
const nextNodes = state?.next || []
|
|
540
546
|
if (nextNodes.includes('tools')) {
|
|
@@ -606,8 +612,7 @@ export async function executeLangGraphOrchestrator(
|
|
|
606
612
|
const completeMatch = finalResult.match(/ORCHESTRATION_COMPLETE:\s*([\s\S]+)/)
|
|
607
613
|
const summary = completeMatch ? completeMatch[1].trim() : finalResult
|
|
608
614
|
|
|
609
|
-
|
|
610
|
-
return `${summary}\n\n[MAIN_LOOP_META] {"status":"ok", "follow_up":false, "summary":${JSON.stringify(summary.slice(0, 300))}, "mission_task_id":${JSON.stringify(taskId || null)}}`
|
|
615
|
+
return summary
|
|
611
616
|
}
|
|
612
617
|
|
|
613
618
|
/**
|
|
@@ -628,7 +633,11 @@ export async function resumeLangGraphOrchestrator(
|
|
|
628
633
|
async ({ agentName, task: agentTask }) => {
|
|
629
634
|
const agent = agents.find((a) => a.name.toLowerCase() === agentName.toLowerCase())
|
|
630
635
|
if (!agent) return `Agent "${agentName}" not found. Available: ${agents.map((a) => a.name).join(', ')}`
|
|
631
|
-
getPluginManager().runHook(
|
|
636
|
+
getPluginManager().runHook(
|
|
637
|
+
'onAgentDelegation',
|
|
638
|
+
{ sourceAgentId: orchestrator.id, targetAgentId: agent.id, task: agentTask },
|
|
639
|
+
{ enabledIds: orchestrator.plugins || [] },
|
|
640
|
+
)
|
|
632
641
|
const result = await executeSubTaskViaCli(agent, agentTask, sessionId)
|
|
633
642
|
saveMessage(sessionId, 'assistant', `Delegated to ${agent.name}: ${agentTask.slice(0, 100)}`, [{
|
|
634
643
|
name: 'delegate_to_agent',
|
|
@@ -753,6 +762,7 @@ export async function resumeLangGraphOrchestrator(
|
|
|
753
762
|
const checkpointSaver = getCheckpointSaver()
|
|
754
763
|
const settings = loadSettings()
|
|
755
764
|
const isStrictMode = settings.capabilityPolicyMode === 'strict'
|
|
765
|
+
const approvalInterruptsEnabled = isStrictMode && settings.approvalsEnabled !== false
|
|
756
766
|
|
|
757
767
|
const allTools = [delegateTool, storeMemoryTool, searchMemoryTool, getSecretTool, commentOnTaskTool, createTaskTool, markCompleteTool]
|
|
758
768
|
const llmWithTools = llm.bindTools(allTools)
|
|
@@ -782,7 +792,7 @@ export async function resumeLangGraphOrchestrator(
|
|
|
782
792
|
.addEdge('router', 'agent')
|
|
783
793
|
.compile({
|
|
784
794
|
checkpointer: checkpointSaver,
|
|
785
|
-
...(
|
|
795
|
+
...(approvalInterruptsEnabled ? { interruptBefore: ['tools'] } : {}),
|
|
786
796
|
})
|
|
787
797
|
|
|
788
798
|
let finalResult = ''
|
|
@@ -42,7 +42,7 @@ export function createOrchestratorSession(
|
|
|
42
42
|
messages: [] as any[],
|
|
43
43
|
createdAt: Date.now(),
|
|
44
44
|
lastActiveAt: Date.now(),
|
|
45
|
-
sessionType: '
|
|
45
|
+
sessionType: 'human' as const,
|
|
46
46
|
agentId: orchestrator.id,
|
|
47
47
|
parentSessionId: parentSessionId || null,
|
|
48
48
|
plugins: Array.isArray(orchestrator.plugins) ? [...orchestrator.plugins] : (Array.isArray(orchestrator.tools) ? [...orchestrator.tools] : []),
|
|
@@ -86,6 +86,7 @@ async function executeOrchestratorLegacy(
|
|
|
86
86
|
sessionId: string,
|
|
87
87
|
taskId?: string,
|
|
88
88
|
): Promise<string> {
|
|
89
|
+
void taskId
|
|
89
90
|
const allAgents = loadAgents()
|
|
90
91
|
const sessions = loadSessions()
|
|
91
92
|
const session = sessions[sessionId]
|
|
@@ -241,7 +242,7 @@ async function executeOrchestratorLegacy(
|
|
|
241
242
|
|
|
242
243
|
if (cmd.done) {
|
|
243
244
|
result = cmd.summary || fullText
|
|
244
|
-
return
|
|
245
|
+
return result
|
|
245
246
|
}
|
|
246
247
|
}
|
|
247
248
|
|
|
@@ -256,7 +257,7 @@ async function executeOrchestratorLegacy(
|
|
|
256
257
|
result = `Loop stopped after reaching max turns (${maxTurns}).`
|
|
257
258
|
}
|
|
258
259
|
|
|
259
|
-
return
|
|
260
|
+
return result
|
|
260
261
|
}
|
|
261
262
|
|
|
262
263
|
async function executeSubTask(
|
|
@@ -288,7 +289,7 @@ async function executeSubTask(
|
|
|
288
289
|
messages: [] as any[],
|
|
289
290
|
createdAt: Date.now(),
|
|
290
291
|
lastActiveAt: Date.now(),
|
|
291
|
-
sessionType: '
|
|
292
|
+
sessionType: 'human' as const,
|
|
292
293
|
agentId: agent.id,
|
|
293
294
|
parentSessionId,
|
|
294
295
|
plugins: agent.plugins || agent.tools || [],
|
|
@@ -11,9 +11,33 @@ import path from 'path'
|
|
|
11
11
|
const UPLOAD_DIR = process.env.SWARMCLAW_UPLOAD_DIR || path.join(process.env.DATA_DIR || path.join(process.cwd(), 'data'), 'uploads')
|
|
12
12
|
if (!fs.existsSync(UPLOAD_DIR)) fs.mkdirSync(UPLOAD_DIR, { recursive: true })
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
function resolvePlaywrightCli() {
|
|
15
|
+
const candidates = [
|
|
16
|
+
path.join(process.cwd(), 'node_modules', '@playwright', 'mcp', 'cli.js'),
|
|
17
|
+
path.join(process.cwd(), '[project]', 'node_modules', '@playwright', 'mcp', 'cli.js'),
|
|
18
|
+
]
|
|
19
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) || null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function sanitizePlaywrightEnv(baseEnv) {
|
|
23
|
+
const env = { ...baseEnv }
|
|
24
|
+
for (const key of Object.keys(env)) {
|
|
25
|
+
if (!key.toUpperCase().startsWith('PLAYWRIGHT_MCP_')) continue
|
|
26
|
+
delete env[key]
|
|
27
|
+
}
|
|
28
|
+
return env
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const cliPath = resolvePlaywrightCli()
|
|
32
|
+
const child = cliPath
|
|
33
|
+
? spawn(process.execPath, [cliPath], {
|
|
34
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
35
|
+
env: sanitizePlaywrightEnv(process.env),
|
|
36
|
+
})
|
|
37
|
+
: spawn('npx', ['@playwright/mcp@latest'], {
|
|
38
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
39
|
+
env: sanitizePlaywrightEnv(process.env),
|
|
40
|
+
})
|
|
17
41
|
|
|
18
42
|
// Forward stdin → child
|
|
19
43
|
process.stdin.on('data', (chunk) => child.stdin.write(chunk))
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, it } from 'node:test'
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { getPluginManager, normalizeMarketplacePluginUrl, sanitizePluginFilename } from './plugins'
|
|
6
|
+
import { canonicalizePluginId, expandPluginIds, pluginIdMatches } from './tool-aliases'
|
|
7
|
+
import { DATA_DIR } from './data-dir'
|
|
8
|
+
|
|
9
|
+
let testPluginSeq = 0
|
|
10
|
+
|
|
11
|
+
function uniquePluginId(prefix: string): string {
|
|
12
|
+
testPluginSeq += 1
|
|
13
|
+
return `${prefix}_${Date.now()}_${testPluginSeq}`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('plugin id canonicalization', () => {
|
|
17
|
+
it('normalizes built-in aliases to canonical plugin families', () => {
|
|
18
|
+
assert.equal(canonicalizePluginId('session_info'), 'manage_sessions')
|
|
19
|
+
assert.equal(canonicalizePluginId('connectors'), 'manage_connectors')
|
|
20
|
+
assert.equal(canonicalizePluginId('subagent'), 'spawn_subagent')
|
|
21
|
+
assert.equal(canonicalizePluginId('http'), 'http_request')
|
|
22
|
+
assert.equal(canonicalizePluginId('human_loop'), 'ask_human')
|
|
23
|
+
assert.equal(canonicalizePluginId('dataframe'), 'table')
|
|
24
|
+
assert.equal(canonicalizePluginId('extract_structured'), 'extract')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('expands aliases to include the canonical family id', () => {
|
|
28
|
+
const expanded = expandPluginIds(['session_info', 'http', 'human_loop'])
|
|
29
|
+
assert.equal(expanded.includes('manage_sessions'), true)
|
|
30
|
+
assert.equal(expanded.includes('session_info'), true)
|
|
31
|
+
assert.equal(expanded.includes('http_request'), true)
|
|
32
|
+
assert.equal(expanded.includes('http'), true)
|
|
33
|
+
assert.equal(expanded.includes('ask_human'), true)
|
|
34
|
+
assert.equal(expanded.includes('human_loop'), true)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('does not expand a specific platform tool back into manage_platform', () => {
|
|
38
|
+
const expanded = expandPluginIds(['manage_schedules'])
|
|
39
|
+
assert.equal(expanded.includes('manage_schedules'), true)
|
|
40
|
+
assert.equal(expanded.includes('manage_platform'), false)
|
|
41
|
+
assert.equal(pluginIdMatches(['manage_platform'], 'manage_schedules'), true)
|
|
42
|
+
assert.equal(pluginIdMatches(['manage_schedules'], 'manage_platform'), false)
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('plugin install helpers', () => {
|
|
47
|
+
it('rewrites legacy marketplace URLs to the canonical raw source', () => {
|
|
48
|
+
const normalized = normalizeMarketplacePluginUrl('https://github.com/swarmclawai/plugins/blob/master/foo/bar.js')
|
|
49
|
+
assert.equal(normalized, 'https://raw.githubusercontent.com/swarmclawai/swarmforge/main/foo/bar.js')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('allows .js and .mjs plugin filenames and blocks traversal', () => {
|
|
53
|
+
assert.equal(sanitizePluginFilename('plugin.js'), 'plugin.js')
|
|
54
|
+
assert.equal(sanitizePluginFilename('plugin.mjs'), 'plugin.mjs')
|
|
55
|
+
assert.throws(() => sanitizePluginFilename('../plugin.js'), /Invalid filename/)
|
|
56
|
+
assert.throws(() => sanitizePluginFilename('plugin.ts'), /Filename must end/)
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe('plugin manager hook execution', () => {
|
|
61
|
+
it('applies beforeToolExec mutations only for explicitly enabled plugins', async () => {
|
|
62
|
+
const pluginId = uniquePluginId('before_tool_exec')
|
|
63
|
+
getPluginManager().registerBuiltin(pluginId, {
|
|
64
|
+
name: 'Before Tool Exec Test',
|
|
65
|
+
hooks: {
|
|
66
|
+
beforeToolExec: ({ input }) => ({ ...(input || {}), patched: true }),
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const withoutEnable = await getPluginManager().runBeforeToolExec(
|
|
71
|
+
{ toolName: 'shell', input: { original: true } },
|
|
72
|
+
{},
|
|
73
|
+
)
|
|
74
|
+
assert.deepEqual(withoutEnable, { original: true })
|
|
75
|
+
|
|
76
|
+
const withEnable = await getPluginManager().runBeforeToolExec(
|
|
77
|
+
{ toolName: 'shell', input: { original: true } },
|
|
78
|
+
{ enabledIds: [pluginId] },
|
|
79
|
+
)
|
|
80
|
+
assert.deepEqual(withEnable, { original: true, patched: true })
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('chains text transforms in plugin order', async () => {
|
|
84
|
+
const pluginA = uniquePluginId('transform_a')
|
|
85
|
+
const pluginB = uniquePluginId('transform_b')
|
|
86
|
+
getPluginManager().registerBuiltin(pluginA, {
|
|
87
|
+
name: 'Transform A',
|
|
88
|
+
hooks: {
|
|
89
|
+
transformOutboundMessage: ({ text }) => `${text} A`,
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
getPluginManager().registerBuiltin(pluginB, {
|
|
93
|
+
name: 'Transform B',
|
|
94
|
+
hooks: {
|
|
95
|
+
transformOutboundMessage: ({ text }) => `${text} B`,
|
|
96
|
+
},
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const transformed = await getPluginManager().transformText(
|
|
100
|
+
'transformOutboundMessage',
|
|
101
|
+
{
|
|
102
|
+
session: {
|
|
103
|
+
id: 's1',
|
|
104
|
+
name: 'Test Session',
|
|
105
|
+
cwd: process.cwd(),
|
|
106
|
+
user: 'tester',
|
|
107
|
+
provider: 'openai',
|
|
108
|
+
model: 'gpt-test',
|
|
109
|
+
claudeSessionId: null,
|
|
110
|
+
messages: [],
|
|
111
|
+
createdAt: Date.now(),
|
|
112
|
+
lastActiveAt: Date.now(),
|
|
113
|
+
plugins: [pluginA, pluginB],
|
|
114
|
+
},
|
|
115
|
+
text: 'base',
|
|
116
|
+
},
|
|
117
|
+
{ enabledIds: [pluginA, pluginB] },
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
assert.equal(transformed, 'base A B')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('does not run generic hooks unless scope is provided explicitly', async () => {
|
|
124
|
+
const pluginId = uniquePluginId('scoped_hook')
|
|
125
|
+
let callCount = 0
|
|
126
|
+
getPluginManager().registerBuiltin(pluginId, {
|
|
127
|
+
name: 'Scoped Hook Test',
|
|
128
|
+
hooks: {
|
|
129
|
+
afterChatTurn: () => {
|
|
130
|
+
callCount += 1
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
await getPluginManager().runHook(
|
|
136
|
+
'afterChatTurn',
|
|
137
|
+
{
|
|
138
|
+
session: {
|
|
139
|
+
id: 's2',
|
|
140
|
+
name: 'Scoped Hook Session',
|
|
141
|
+
cwd: process.cwd(),
|
|
142
|
+
user: 'tester',
|
|
143
|
+
provider: 'openai',
|
|
144
|
+
model: 'gpt-test',
|
|
145
|
+
claudeSessionId: null,
|
|
146
|
+
messages: [],
|
|
147
|
+
createdAt: Date.now(),
|
|
148
|
+
lastActiveAt: Date.now(),
|
|
149
|
+
},
|
|
150
|
+
message: 'hi',
|
|
151
|
+
response: 'hello',
|
|
152
|
+
source: 'chat',
|
|
153
|
+
internal: false,
|
|
154
|
+
},
|
|
155
|
+
{},
|
|
156
|
+
)
|
|
157
|
+
assert.equal(callCount, 0)
|
|
158
|
+
|
|
159
|
+
await getPluginManager().runHook(
|
|
160
|
+
'afterChatTurn',
|
|
161
|
+
{
|
|
162
|
+
session: {
|
|
163
|
+
id: 's3',
|
|
164
|
+
name: 'Scoped Hook Session Enabled',
|
|
165
|
+
cwd: process.cwd(),
|
|
166
|
+
user: 'tester',
|
|
167
|
+
provider: 'openai',
|
|
168
|
+
model: 'gpt-test',
|
|
169
|
+
claudeSessionId: null,
|
|
170
|
+
messages: [],
|
|
171
|
+
createdAt: Date.now(),
|
|
172
|
+
lastActiveAt: Date.now(),
|
|
173
|
+
plugins: [pluginId],
|
|
174
|
+
},
|
|
175
|
+
message: 'hi',
|
|
176
|
+
response: 'hello',
|
|
177
|
+
source: 'chat',
|
|
178
|
+
internal: false,
|
|
179
|
+
},
|
|
180
|
+
{ enabledIds: [pluginId] },
|
|
181
|
+
)
|
|
182
|
+
assert.equal(callCount, 1)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('stores dependency-aware plugins in managed workspaces', async () => {
|
|
186
|
+
const filename = `${uniquePluginId('workspace_plugin')}.js`
|
|
187
|
+
const manager = getPluginManager()
|
|
188
|
+
|
|
189
|
+
await manager.savePluginSource(
|
|
190
|
+
filename,
|
|
191
|
+
'module.exports = { name: "Workspace Plugin", tools: [] }',
|
|
192
|
+
{
|
|
193
|
+
packageJson: {
|
|
194
|
+
name: 'workspace-plugin',
|
|
195
|
+
dependencies: {
|
|
196
|
+
lodash: '^4.17.21',
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
packageManager: 'npm',
|
|
200
|
+
},
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
const meta = manager.listPlugins().find((plugin) => plugin.filename === filename)
|
|
204
|
+
assert.equal(meta?.isBuiltin, false)
|
|
205
|
+
assert.equal(meta?.hasDependencyManifest, true)
|
|
206
|
+
assert.equal(meta?.dependencyCount, 1)
|
|
207
|
+
assert.equal(meta?.packageManager, 'npm')
|
|
208
|
+
assert.equal(manager.readPluginSource(filename).includes('Workspace Plugin'), true)
|
|
209
|
+
|
|
210
|
+
const shimPath = path.join(DATA_DIR, 'plugins', filename)
|
|
211
|
+
assert.equal(fs.readFileSync(shimPath, 'utf8').includes('Auto-generated plugin workspace shim'), true)
|
|
212
|
+
|
|
213
|
+
assert.equal(manager.deletePlugin(filename), true)
|
|
214
|
+
})
|
|
215
|
+
})
|