@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
|
+
import type { SkillInstallOption, SkillRequirements, SkillSecuritySummary } from '@/types'
|
|
2
3
|
|
|
3
4
|
export type SkillSourceFormat = 'openclaw' | 'plain'
|
|
4
5
|
|
|
@@ -8,6 +9,19 @@ type NormalizeSkillInput = {
|
|
|
8
9
|
filename?: unknown
|
|
9
10
|
content?: unknown
|
|
10
11
|
sourceUrl?: unknown
|
|
12
|
+
sourceFormat?: unknown
|
|
13
|
+
author?: unknown
|
|
14
|
+
tags?: unknown
|
|
15
|
+
version?: unknown
|
|
16
|
+
homepage?: unknown
|
|
17
|
+
primaryEnv?: unknown
|
|
18
|
+
skillKey?: unknown
|
|
19
|
+
always?: unknown
|
|
20
|
+
installOptions?: unknown
|
|
21
|
+
skillRequirements?: unknown
|
|
22
|
+
detectedEnvVars?: unknown
|
|
23
|
+
security?: unknown
|
|
24
|
+
frontmatter?: unknown
|
|
11
25
|
}
|
|
12
26
|
|
|
13
27
|
export type NormalizedSkill = {
|
|
@@ -17,6 +31,23 @@ export type NormalizedSkill = {
|
|
|
17
31
|
content: string
|
|
18
32
|
sourceUrl?: string
|
|
19
33
|
sourceFormat: SkillSourceFormat
|
|
34
|
+
author?: string
|
|
35
|
+
tags?: string[]
|
|
36
|
+
version?: string
|
|
37
|
+
homepage?: string
|
|
38
|
+
primaryEnv?: string | null
|
|
39
|
+
skillKey?: string | null
|
|
40
|
+
always?: boolean
|
|
41
|
+
installOptions?: SkillInstallOption[]
|
|
42
|
+
skillRequirements?: SkillRequirements
|
|
43
|
+
detectedEnvVars?: string[]
|
|
44
|
+
security?: SkillSecuritySummary | null
|
|
45
|
+
frontmatter?: Record<string, unknown> | null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type ParsedFrontmatter = {
|
|
49
|
+
frontmatter: Record<string, unknown>
|
|
50
|
+
body: string
|
|
20
51
|
}
|
|
21
52
|
|
|
22
53
|
function asTrimmedString(value: unknown): string | null {
|
|
@@ -25,8 +56,30 @@ function asTrimmedString(value: unknown): string | null {
|
|
|
25
56
|
return trimmed.length > 0 ? trimmed : null
|
|
26
57
|
}
|
|
27
58
|
|
|
59
|
+
function asStringArray(value: unknown): string[] | undefined {
|
|
60
|
+
if (!Array.isArray(value)) return undefined
|
|
61
|
+
const items = value
|
|
62
|
+
.map((item) => asTrimmedString(item))
|
|
63
|
+
.filter((item): item is string => Boolean(item))
|
|
64
|
+
return items.length ? items : undefined
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function asBoolean(value: unknown): boolean | undefined {
|
|
68
|
+
if (typeof value === 'boolean') return value
|
|
69
|
+
if (typeof value === 'string') {
|
|
70
|
+
if (value.trim().toLowerCase() === 'true') return true
|
|
71
|
+
if (value.trim().toLowerCase() === 'false') return false
|
|
72
|
+
}
|
|
73
|
+
return undefined
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function asObject(value: unknown): Record<string, unknown> | null {
|
|
77
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null
|
|
78
|
+
return value as Record<string, unknown>
|
|
79
|
+
}
|
|
80
|
+
|
|
28
81
|
function stripQuotes(value: string): string {
|
|
29
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith(
|
|
82
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\''))) {
|
|
30
83
|
return value.slice(1, -1)
|
|
31
84
|
}
|
|
32
85
|
return value
|
|
@@ -47,26 +100,6 @@ function sanitizeFilename(input: string): string {
|
|
|
47
100
|
return safe.toLowerCase().endsWith('.md') ? safe : `${safe}.md`
|
|
48
101
|
}
|
|
49
102
|
|
|
50
|
-
function parseFrontmatterBlock(content: string): { frontmatter: Record<string, string>; body: string } | null {
|
|
51
|
-
const match = content.match(/^\s*---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/)
|
|
52
|
-
if (!match) return null
|
|
53
|
-
const rawFrontmatter = match[1]
|
|
54
|
-
const body = match[2] || ''
|
|
55
|
-
|
|
56
|
-
const frontmatter: Record<string, string> = {}
|
|
57
|
-
for (const rawLine of rawFrontmatter.split(/\r?\n/)) {
|
|
58
|
-
const line = rawLine.trim()
|
|
59
|
-
if (!line || line.startsWith('#')) continue
|
|
60
|
-
const idx = line.indexOf(':')
|
|
61
|
-
if (idx === -1) continue
|
|
62
|
-
const key = line.slice(0, idx).trim().toLowerCase()
|
|
63
|
-
const value = stripQuotes(line.slice(idx + 1).trim())
|
|
64
|
-
if (key) frontmatter[key] = value
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return { frontmatter, body }
|
|
68
|
-
}
|
|
69
|
-
|
|
70
103
|
function deriveNameFromFilename(filename: string): string {
|
|
71
104
|
return filename
|
|
72
105
|
.replace(/\.md$/i, '')
|
|
@@ -89,15 +122,285 @@ function deriveFilenameFromUrl(url: string): string | null {
|
|
|
89
122
|
}
|
|
90
123
|
}
|
|
91
124
|
|
|
125
|
+
function parseInlineArray(value: string): unknown[] {
|
|
126
|
+
const inner = value.slice(1, -1).trim()
|
|
127
|
+
if (!inner) return []
|
|
128
|
+
return inner
|
|
129
|
+
.split(',')
|
|
130
|
+
.map((part) => parseScalar(part.trim()))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function parseScalar(value: string): unknown {
|
|
134
|
+
const trimmed = value.trim()
|
|
135
|
+
if (!trimmed) return ''
|
|
136
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith('\'') && trimmed.endsWith('\''))) {
|
|
137
|
+
return stripQuotes(trimmed)
|
|
138
|
+
}
|
|
139
|
+
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
|
|
140
|
+
return parseInlineArray(trimmed)
|
|
141
|
+
}
|
|
142
|
+
if (/^(true|false)$/i.test(trimmed)) return trimmed.toLowerCase() === 'true'
|
|
143
|
+
if (/^(null|~)$/i.test(trimmed)) return null
|
|
144
|
+
if (/^-?\d+(?:\.\d+)?$/.test(trimmed)) return Number(trimmed)
|
|
145
|
+
return trimmed
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function nextNestedContainer(lines: string[], start: number, currentIndent: number): Record<string, unknown> | unknown[] {
|
|
149
|
+
for (let index = start + 1; index < lines.length; index += 1) {
|
|
150
|
+
const raw = lines[index]
|
|
151
|
+
const trimmed = raw.trim()
|
|
152
|
+
if (!trimmed || trimmed.startsWith('#')) continue
|
|
153
|
+
const indent = raw.match(/^\s*/)?.[0]?.length || 0
|
|
154
|
+
if (indent <= currentIndent) break
|
|
155
|
+
return trimmed.startsWith('- ') ? [] : {}
|
|
156
|
+
}
|
|
157
|
+
return {}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function parseFrontmatterData(rawFrontmatter: string): Record<string, unknown> {
|
|
161
|
+
const lines = rawFrontmatter.split(/\r?\n/)
|
|
162
|
+
const root: Record<string, unknown> = {}
|
|
163
|
+
const stack: Array<{ indent: number; value: Record<string, unknown> | unknown[] }> = [{ indent: -1, value: root }]
|
|
164
|
+
|
|
165
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
166
|
+
const rawLine = lines[index]
|
|
167
|
+
const trimmed = rawLine.trim()
|
|
168
|
+
if (!trimmed || trimmed.startsWith('#')) continue
|
|
169
|
+
const indent = rawLine.match(/^\s*/)?.[0]?.length || 0
|
|
170
|
+
|
|
171
|
+
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
172
|
+
stack.pop()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const parent = stack[stack.length - 1]?.value
|
|
176
|
+
if (!parent) continue
|
|
177
|
+
|
|
178
|
+
if (trimmed.startsWith('- ')) {
|
|
179
|
+
if (!Array.isArray(parent)) continue
|
|
180
|
+
const rest = trimmed.slice(2).trim()
|
|
181
|
+
if (!rest) {
|
|
182
|
+
const container = nextNestedContainer(lines, index, indent)
|
|
183
|
+
parent.push(container)
|
|
184
|
+
stack.push({ indent, value: container })
|
|
185
|
+
continue
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const objectItemMatch = rest.match(/^([A-Za-z0-9_.-]+):\s*(.*)$/)
|
|
189
|
+
if (objectItemMatch) {
|
|
190
|
+
const [, key, rawValue] = objectItemMatch
|
|
191
|
+
const objectItem: Record<string, unknown> = {}
|
|
192
|
+
if (rawValue.trim()) {
|
|
193
|
+
objectItem[key] = parseScalar(rawValue)
|
|
194
|
+
parent.push(objectItem)
|
|
195
|
+
stack.push({ indent, value: objectItem })
|
|
196
|
+
} else {
|
|
197
|
+
const container = nextNestedContainer(lines, index, indent)
|
|
198
|
+
objectItem[key] = container
|
|
199
|
+
parent.push(objectItem)
|
|
200
|
+
stack.push({ indent, value: objectItem })
|
|
201
|
+
stack.push({ indent: indent + 1, value: container })
|
|
202
|
+
}
|
|
203
|
+
continue
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
parent.push(parseScalar(rest))
|
|
207
|
+
continue
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (Array.isArray(parent)) continue
|
|
211
|
+
|
|
212
|
+
const keyMatch = trimmed.match(/^([A-Za-z0-9_.-]+):\s*(.*)$/)
|
|
213
|
+
if (!keyMatch) continue
|
|
214
|
+
const [, key, rawValue] = keyMatch
|
|
215
|
+
if (rawValue.trim()) {
|
|
216
|
+
parent[key] = parseScalar(rawValue)
|
|
217
|
+
continue
|
|
218
|
+
}
|
|
219
|
+
const container = nextNestedContainer(lines, index, indent)
|
|
220
|
+
parent[key] = container
|
|
221
|
+
stack.push({ indent, value: container })
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return root
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function parseFrontmatterBlock(content: string): ParsedFrontmatter | null {
|
|
228
|
+
const match = content.match(/^\s*---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/)
|
|
229
|
+
if (!match) return null
|
|
230
|
+
const rawFrontmatter = match[1]
|
|
231
|
+
const body = match[2] || ''
|
|
232
|
+
return {
|
|
233
|
+
frontmatter: parseFrontmatterData(rawFrontmatter),
|
|
234
|
+
body,
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function normalizeInstallOptions(value: unknown): SkillInstallOption[] | undefined {
|
|
239
|
+
if (!Array.isArray(value)) return undefined
|
|
240
|
+
const normalized: SkillInstallOption[] = value.flatMap((entry) => {
|
|
241
|
+
const row = asObject(entry)
|
|
242
|
+
if (!row) return []
|
|
243
|
+
const kind = asTrimmedString(row.kind)
|
|
244
|
+
const label = asTrimmedString(row.label)
|
|
245
|
+
|| asTrimmedString(row.formula)
|
|
246
|
+
|| asTrimmedString(row.package)
|
|
247
|
+
|| asTrimmedString(row.url)
|
|
248
|
+
if (!kind || !label) return []
|
|
249
|
+
if (!['brew', 'node', 'go', 'uv', 'download'].includes(kind)) return []
|
|
250
|
+
return [{
|
|
251
|
+
kind: kind as SkillInstallOption['kind'],
|
|
252
|
+
label,
|
|
253
|
+
bins: asStringArray(row.bins),
|
|
254
|
+
} satisfies SkillInstallOption]
|
|
255
|
+
})
|
|
256
|
+
return normalized.length ? normalized : undefined
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function normalizeRequirements(value: unknown): SkillRequirements | undefined {
|
|
260
|
+
const source = asObject(value)
|
|
261
|
+
if (!source) return undefined
|
|
262
|
+
const requires = asObject(source.requires) || source
|
|
263
|
+
const anyBins = Array.isArray(requires.anyBins)
|
|
264
|
+
? requires.anyBins
|
|
265
|
+
.map((group) => asStringArray(group) || [])
|
|
266
|
+
.filter((group) => group.length > 0)
|
|
267
|
+
: undefined
|
|
268
|
+
const normalized: SkillRequirements = {
|
|
269
|
+
bins: asStringArray(requires.bins),
|
|
270
|
+
anyBins,
|
|
271
|
+
env: asStringArray(requires.env),
|
|
272
|
+
config: asStringArray(requires.config),
|
|
273
|
+
os: asStringArray(source.os ?? requires.os),
|
|
274
|
+
}
|
|
275
|
+
if (!normalized.bins && !normalized.anyBins && !normalized.env && !normalized.config && !normalized.os) {
|
|
276
|
+
return undefined
|
|
277
|
+
}
|
|
278
|
+
return normalized
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function pickRuntimeMetadata(frontmatter: Record<string, unknown>): Record<string, unknown> | null {
|
|
282
|
+
const metadata = asObject(frontmatter.metadata)
|
|
283
|
+
if (metadata) {
|
|
284
|
+
const scoped = asObject(metadata.openclaw)
|
|
285
|
+
|| asObject(metadata.clawdbot)
|
|
286
|
+
|| asObject(metadata.clawdis)
|
|
287
|
+
if (scoped) return scoped
|
|
288
|
+
}
|
|
289
|
+
return asObject(frontmatter.openclaw)
|
|
290
|
+
|| asObject(frontmatter.clawdbot)
|
|
291
|
+
|| asObject(frontmatter.clawdis)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function uniqueStrings(values: string[]): string[] {
|
|
295
|
+
return Array.from(new Set(values.map((value) => value.trim()).filter(Boolean)))
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function extractDetectedEnvVars(rawContent: string): string[] {
|
|
299
|
+
const detected = new Set<string>()
|
|
300
|
+
const patterns = [
|
|
301
|
+
/process\.env\.([A-Z][A-Z0-9_]+)/g,
|
|
302
|
+
/\$\{([A-Z][A-Z0-9_]+)\}/g,
|
|
303
|
+
/\bexport\s+([A-Z][A-Z0-9_]+)\b/g,
|
|
304
|
+
]
|
|
305
|
+
for (const pattern of patterns) {
|
|
306
|
+
let match: RegExpExecArray | null
|
|
307
|
+
while ((match = pattern.exec(rawContent)) !== null) {
|
|
308
|
+
detected.add(match[1])
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return [...detected].sort()
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function extractInstallCommands(rawContent: string): string[] {
|
|
315
|
+
const commands: string[] = []
|
|
316
|
+
for (const line of rawContent.split(/\r?\n/)) {
|
|
317
|
+
const trimmed = line.trim()
|
|
318
|
+
if (!trimmed) continue
|
|
319
|
+
if (
|
|
320
|
+
/(brew install|npm install|pnpm add|yarn add|go install|uv tool install|curl .*\|\s*(?:bash|sh)|wget .*\|\s*(?:bash|sh))/i.test(trimmed)
|
|
321
|
+
) {
|
|
322
|
+
commands.push(trimmed)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return uniqueStrings(commands).slice(0, 8)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function buildSkillSecuritySummary(params: {
|
|
329
|
+
rawContent: string
|
|
330
|
+
requirements?: SkillRequirements
|
|
331
|
+
installOptions?: SkillInstallOption[]
|
|
332
|
+
primaryEnv?: string | null
|
|
333
|
+
}): SkillSecuritySummary | null {
|
|
334
|
+
const detectedEnvVars = extractDetectedEnvVars(params.rawContent)
|
|
335
|
+
const declaredEnv = new Set<string>([
|
|
336
|
+
...(params.requirements?.env || []),
|
|
337
|
+
...(params.primaryEnv ? [params.primaryEnv] : []),
|
|
338
|
+
])
|
|
339
|
+
const missingDeclarations = detectedEnvVars.filter((name) => !declaredEnv.has(name))
|
|
340
|
+
const installCommands = extractInstallCommands(params.rawContent)
|
|
341
|
+
const notes: string[] = []
|
|
342
|
+
|
|
343
|
+
if (missingDeclarations.length) {
|
|
344
|
+
notes.push(`Detected env vars missing from frontmatter: ${missingDeclarations.join(', ')}`)
|
|
345
|
+
}
|
|
346
|
+
if (installCommands.length) {
|
|
347
|
+
notes.push('Skill content includes install instructions or executable bootstrap commands.')
|
|
348
|
+
}
|
|
349
|
+
if ((params.installOptions?.length || 0) > 0) {
|
|
350
|
+
notes.push('Skill declares install options that should be reviewed before enabling.')
|
|
351
|
+
}
|
|
352
|
+
if (/(curl .*\|\s*(?:bash|sh)|wget .*\|\s*(?:bash|sh)|sudo\s+)/i.test(params.rawContent)) {
|
|
353
|
+
notes.push('Skill content includes high-risk shell patterns.')
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (!notes.length && !detectedEnvVars.length && !installCommands.length) return null
|
|
357
|
+
|
|
358
|
+
const level: SkillSecuritySummary['level'] =
|
|
359
|
+
notes.some((note) => /high-risk|missing from frontmatter/i.test(note))
|
|
360
|
+
? 'high'
|
|
361
|
+
: installCommands.length || detectedEnvVars.length
|
|
362
|
+
? 'medium'
|
|
363
|
+
: 'low'
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
level,
|
|
367
|
+
notes,
|
|
368
|
+
detectedEnvVars,
|
|
369
|
+
missingDeclarations,
|
|
370
|
+
installCommands,
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
92
374
|
export function normalizeSkillPayload(input: NormalizeSkillInput): NormalizedSkill {
|
|
93
375
|
const rawContent = typeof input.content === 'string' ? input.content : ''
|
|
94
376
|
const parsed = parseFrontmatterBlock(rawContent)
|
|
377
|
+
const preservedFrontmatter = asObject(input.frontmatter)
|
|
378
|
+
const frontmatter = parsed?.frontmatter || preservedFrontmatter || null
|
|
379
|
+
const runtimeMeta = frontmatter ? pickRuntimeMetadata(frontmatter) : null
|
|
95
380
|
|
|
96
|
-
const frontmatterName = asTrimmedString(
|
|
97
|
-
const frontmatterDescription = asTrimmedString(
|
|
381
|
+
const frontmatterName = asTrimmedString(frontmatter?.name)
|
|
382
|
+
const frontmatterDescription = asTrimmedString(frontmatter?.description)
|
|
383
|
+
const frontmatterAuthor = asTrimmedString(frontmatter?.author)
|
|
384
|
+
const frontmatterTags = asStringArray(frontmatter?.tags)
|
|
385
|
+
const version = asTrimmedString(frontmatter?.version)
|
|
386
|
+
|| asTrimmedString(runtimeMeta?.version)
|
|
387
|
+
|| asTrimmedString(input.version)
|
|
388
|
+
const homepage = asTrimmedString(runtimeMeta?.homepage)
|
|
389
|
+
|| asTrimmedString(frontmatter?.homepage)
|
|
390
|
+
|| asTrimmedString(input.homepage)
|
|
391
|
+
const primaryEnv = asTrimmedString(runtimeMeta?.primaryEnv)
|
|
392
|
+
|| asTrimmedString(input.primaryEnv)
|
|
393
|
+
|| null
|
|
394
|
+
const skillKey = asTrimmedString(runtimeMeta?.skillKey)
|
|
395
|
+
|| asTrimmedString(input.skillKey)
|
|
396
|
+
|| null
|
|
397
|
+
const always = asBoolean(runtimeMeta?.always) ?? asBoolean(input.always)
|
|
398
|
+
const installOptions = normalizeInstallOptions(runtimeMeta?.install)
|
|
399
|
+
|| normalizeInstallOptions(input.installOptions)
|
|
400
|
+
const skillRequirements = normalizeRequirements(runtimeMeta)
|
|
401
|
+
|| normalizeRequirements(input.skillRequirements)
|
|
98
402
|
|
|
99
403
|
const sourceUrl = asTrimmedString(input.sourceUrl) || undefined
|
|
100
|
-
|
|
101
404
|
const initialFilename = asTrimmedString(input.filename)
|
|
102
405
|
|| (sourceUrl ? deriveFilenameFromUrl(sourceUrl) : null)
|
|
103
406
|
|| (frontmatterName ? `${slugify(frontmatterName)}.md` : null)
|
|
@@ -112,10 +415,41 @@ export function normalizeSkillPayload(input: NormalizeSkillInput): NormalizedSki
|
|
|
112
415
|
|| frontmatterDescription
|
|
113
416
|
|| ''
|
|
114
417
|
|
|
115
|
-
|
|
418
|
+
const author = asTrimmedString(input.author)
|
|
419
|
+
|| frontmatterAuthor
|
|
420
|
+
|| undefined
|
|
421
|
+
|
|
422
|
+
const tags = asStringArray(input.tags)
|
|
423
|
+
|| frontmatterTags
|
|
424
|
+
|| undefined
|
|
425
|
+
|
|
116
426
|
const normalizedContent = parsed ? parsed.body.trimStart() : rawContent
|
|
427
|
+
const detectedEnvVars = extractDetectedEnvVars(rawContent)
|
|
428
|
+
const preservedDetectedEnvVars = asStringArray(input.detectedEnvVars)
|
|
429
|
+
const generatedSecurity = buildSkillSecuritySummary({
|
|
430
|
+
rawContent,
|
|
431
|
+
requirements: skillRequirements,
|
|
432
|
+
installOptions,
|
|
433
|
+
primaryEnv,
|
|
434
|
+
})
|
|
435
|
+
const securityRecord = asObject(input.security)
|
|
436
|
+
const security = generatedSecurity || (securityRecord
|
|
437
|
+
? {
|
|
438
|
+
level: securityRecord.level === 'high' || securityRecord.level === 'medium' ? securityRecord.level : 'low',
|
|
439
|
+
notes: asStringArray(securityRecord.notes) || [],
|
|
440
|
+
detectedEnvVars: asStringArray(securityRecord.detectedEnvVars),
|
|
441
|
+
missingDeclarations: asStringArray(securityRecord.missingDeclarations),
|
|
442
|
+
installCommands: asStringArray(securityRecord.installCommands),
|
|
443
|
+
} satisfies SkillSecuritySummary
|
|
444
|
+
: null)
|
|
117
445
|
|
|
118
|
-
const sourceFormat: SkillSourceFormat = parsed && (
|
|
446
|
+
const sourceFormat: SkillSourceFormat = (parsed && (
|
|
447
|
+
frontmatterName !== null
|
|
448
|
+
|| frontmatterDescription !== null
|
|
449
|
+
|| runtimeMeta !== null
|
|
450
|
+
))
|
|
451
|
+
|| input.sourceFormat === 'openclaw'
|
|
452
|
+
|| preservedFrontmatter !== null
|
|
119
453
|
? 'openclaw'
|
|
120
454
|
: 'plain'
|
|
121
455
|
|
|
@@ -126,5 +460,17 @@ export function normalizeSkillPayload(input: NormalizeSkillInput): NormalizedSki
|
|
|
126
460
|
content: normalizedContent,
|
|
127
461
|
sourceUrl,
|
|
128
462
|
sourceFormat,
|
|
463
|
+
author,
|
|
464
|
+
tags,
|
|
465
|
+
version: version || undefined,
|
|
466
|
+
homepage: homepage || undefined,
|
|
467
|
+
primaryEnv,
|
|
468
|
+
skillKey,
|
|
469
|
+
always,
|
|
470
|
+
installOptions,
|
|
471
|
+
skillRequirements,
|
|
472
|
+
detectedEnvVars: detectedEnvVars.length ? detectedEnvVars : preservedDetectedEnvVars,
|
|
473
|
+
security,
|
|
474
|
+
frontmatter,
|
|
129
475
|
}
|
|
130
476
|
}
|