@swarmclawai/swarmclaw 0.7.2 → 0.7.3
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 +81 -22
- package/package.json +1 -1
- package/src/app/api/agents/[id]/route.ts +26 -0
- package/src/app/api/agents/[id]/thread/route.ts +36 -7
- package/src/app/api/agents/route.ts +12 -1
- package/src/app/api/auth/route.ts +76 -7
- package/src/app/api/chatrooms/[id]/chat/route.ts +7 -2
- 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]/main-loop/route.ts +7 -88
- package/src/app/api/chats/[id]/messages/route.ts +19 -13
- package/src/app/api/chats/[id]/route.ts +18 -0
- package/src/app/api/chats/[id]/stop/route.ts +6 -1
- package/src/app/api/chats/route.ts +16 -0
- 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/files/open/route.ts +16 -14
- 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/skills/route.ts +11 -3
- 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/settings/route.ts +49 -7
- package/src/app/api/tasks/[id]/route.ts +15 -6
- package/src/app/api/tasks/bulk/route.ts +2 -2
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +9 -2
- package/src/cli/index.js +4 -0
- package/src/cli/index.ts +3 -10
- 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 +207 -16
- package/src/components/agents/inspector-panel.tsx +108 -48
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/chat/chat-area.tsx +29 -13
- package/src/components/chat/chat-card.tsx +4 -20
- package/src/components/chat/chat-header.tsx +255 -353
- package/src/components/chat/chat-list.tsx +7 -9
- package/src/components/chat/checkpoint-timeline.tsx +1 -1
- package/src/components/chat/message-list.tsx +3 -1
- package/src/components/chatrooms/chatroom-view.tsx +347 -205
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +217 -0
- package/src/components/home/home-view.tsx +128 -4
- package/src/components/layout/app-layout.tsx +383 -194
- package/src/components/layout/mobile-header.tsx +26 -8
- 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 +183 -0
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/command-palette.tsx +111 -24
- 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 +77 -0
- package/src/components/shared/settings/section-orchestrator.tsx +3 -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 +245 -46
- package/src/components/tasks/approvals-panel.tsx +205 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/usage/metrics-dashboard.tsx +74 -1
- 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/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +205 -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 +36 -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 +134 -0
- package/src/lib/server/chat-execution.ts +250 -61
- 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 +67 -2
- package/src/lib/server/chatroom-helpers.ts +45 -5
- 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 +946 -110
- 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 +188 -9
- 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 +59 -1
- 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/heartbeat-service.ts +13 -39
- 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 +27 -967
- 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 +5 -6
- 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 +17 -6
- package/src/lib/server/orchestrator.ts +2 -2
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +207 -0
- package/src/lib/server/plugins.ts +822 -69
- package/src/lib/server/provider-health.ts +33 -3
- package/src/lib/server/queue.ts +3 -20
- 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 +105 -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 +70 -32
- 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.ts +22 -4
- 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 +93 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +237 -24
- 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 +56 -1
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/memory.ts +35 -3
- package/src/lib/server/session-tools/monitor.ts +150 -7
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- 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 +86 -23
- 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/schedule.ts +20 -10
- package/src/lib/server/session-tools/session-info.ts +36 -3
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +31 -17
- package/src/lib/server/session-tools/subagent.ts +193 -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 +896 -100
- package/src/lib/server/storage.ts +226 -7
- package/src/lib/server/stream-agent-chat.ts +46 -21
- 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 -10
- package/src/lib/server/tool-aliases.ts +44 -7
- package/src/lib/server/tool-capability-policy.ts +6 -0
- 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/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +7 -0
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +0 -6
- package/src/stores/use-chat-store.ts +31 -2
- package/src/types/index.ts +287 -44
- 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,142 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { normalizePlatformActionArgs } from './platform'
|
|
4
|
+
|
|
5
|
+
describe('normalizePlatformActionArgs', () => {
|
|
6
|
+
it('packs top-level create fields into data', () => {
|
|
7
|
+
const out = normalizePlatformActionArgs({
|
|
8
|
+
resource: 'tasks',
|
|
9
|
+
action: 'create',
|
|
10
|
+
title: 'Write docs',
|
|
11
|
+
agentId: 'default',
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
assert.equal(out.resource, 'tasks')
|
|
15
|
+
assert.equal(out.action, 'create')
|
|
16
|
+
assert.deepEqual(JSON.parse(String(out.data)), {
|
|
17
|
+
title: 'Write docs',
|
|
18
|
+
agentId: 'default',
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('merges object data with top-level overrides', () => {
|
|
23
|
+
const out = normalizePlatformActionArgs({
|
|
24
|
+
resource: 'tasks',
|
|
25
|
+
action: 'create',
|
|
26
|
+
data: { title: 'Old title', agentId: 'coder' },
|
|
27
|
+
title: 'New title',
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
assert.deepEqual(JSON.parse(String(out.data)), {
|
|
31
|
+
title: 'New title',
|
|
32
|
+
agentId: 'coder',
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('normalizes legacy resources envelope with parameters payload', () => {
|
|
37
|
+
const out = normalizePlatformActionArgs({
|
|
38
|
+
input: JSON.stringify({
|
|
39
|
+
resources: [
|
|
40
|
+
{
|
|
41
|
+
resource: 'tasks',
|
|
42
|
+
action: 'create',
|
|
43
|
+
parameters: {
|
|
44
|
+
title: 'Legacy task',
|
|
45
|
+
assigned_agent: 'default',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
}),
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
assert.equal(out.resource, 'tasks')
|
|
53
|
+
assert.equal(out.action, 'create')
|
|
54
|
+
assert.deepEqual(JSON.parse(String(out.data)), {
|
|
55
|
+
title: 'Legacy task',
|
|
56
|
+
assigned_agent: 'default',
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('normalizes singular resource names and resource payload objects', () => {
|
|
61
|
+
const out = normalizePlatformActionArgs({
|
|
62
|
+
input: JSON.stringify({
|
|
63
|
+
resource: 'task',
|
|
64
|
+
action: 'create',
|
|
65
|
+
task: {
|
|
66
|
+
title: 'Legacy singular task',
|
|
67
|
+
assigned_to: 'default',
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
assert.equal(out.resource, 'tasks')
|
|
73
|
+
assert.equal(out.action, 'create')
|
|
74
|
+
assert.deepEqual(JSON.parse(String(out.data)), {
|
|
75
|
+
title: 'Legacy singular task',
|
|
76
|
+
assigned_to: 'default',
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('normalizes legacy backlog task resource names to tasks', () => {
|
|
81
|
+
const out = normalizePlatformActionArgs({
|
|
82
|
+
input: JSON.stringify({
|
|
83
|
+
resource: 'backlog_task',
|
|
84
|
+
action: 'create',
|
|
85
|
+
backlog_task: {
|
|
86
|
+
title: 'Legacy backlog task',
|
|
87
|
+
description: 'Keep the intended task payload',
|
|
88
|
+
},
|
|
89
|
+
}),
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
assert.equal(out.resource, 'tasks')
|
|
93
|
+
assert.equal(out.action, 'create')
|
|
94
|
+
assert.deepEqual(JSON.parse(String(out.data)), {
|
|
95
|
+
title: 'Legacy backlog task',
|
|
96
|
+
description: 'Keep the intended task payload',
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('normalizes resources entries that use type instead of resource', () => {
|
|
101
|
+
const out = normalizePlatformActionArgs({
|
|
102
|
+
input: JSON.stringify({
|
|
103
|
+
action: 'create',
|
|
104
|
+
resources: [
|
|
105
|
+
{
|
|
106
|
+
type: 'task',
|
|
107
|
+
parameters: {
|
|
108
|
+
title: 'Typed task resource',
|
|
109
|
+
description: 'Created through a typed resources envelope',
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
}),
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
assert.equal(out.resource, 'tasks')
|
|
117
|
+
assert.equal(out.action, 'create')
|
|
118
|
+
assert.deepEqual(JSON.parse(String(out.data)), {
|
|
119
|
+
title: 'Typed task resource',
|
|
120
|
+
description: 'Created through a typed resources envelope',
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('infers schedules resource from create_schedule style actions', () => {
|
|
125
|
+
const out = normalizePlatformActionArgs({
|
|
126
|
+
input: JSON.stringify({
|
|
127
|
+
action: 'create_schedule',
|
|
128
|
+
data: {
|
|
129
|
+
name: 'Surgery check-in',
|
|
130
|
+
scheduleType: 'once',
|
|
131
|
+
},
|
|
132
|
+
}),
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
assert.equal(out.resource, 'schedules')
|
|
136
|
+
assert.equal(out.action, 'create')
|
|
137
|
+
assert.deepEqual(JSON.parse(String(out.data)), {
|
|
138
|
+
name: 'Surgery check-in',
|
|
139
|
+
scheduleType: 'once',
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
})
|
|
@@ -6,12 +6,150 @@ import type { Plugin, PluginHooks } from '@/types'
|
|
|
6
6
|
import { getPluginManager } from '../plugins'
|
|
7
7
|
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
8
8
|
|
|
9
|
+
function parsePlatformData(value: unknown): Record<string, unknown> | null {
|
|
10
|
+
if (!value) return null
|
|
11
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
12
|
+
return value as Record<string, unknown>
|
|
13
|
+
}
|
|
14
|
+
if (typeof value !== 'string') return null
|
|
15
|
+
const trimmed = value.trim()
|
|
16
|
+
if (!trimmed) return null
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(trimmed)
|
|
19
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
20
|
+
return parsed as Record<string, unknown>
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
// Preserve non-JSON data strings as-is in the caller.
|
|
24
|
+
}
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function firstPlatformResource(value: unknown): Record<string, unknown> | null {
|
|
29
|
+
if (!Array.isArray(value)) return null
|
|
30
|
+
const first = value.find((entry) => entry && typeof entry === 'object' && !Array.isArray(entry))
|
|
31
|
+
return first && typeof first === 'object' && !Array.isArray(first)
|
|
32
|
+
? first as Record<string, unknown>
|
|
33
|
+
: null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function normalizePlatformResourceName(value: unknown): string | undefined {
|
|
37
|
+
if (typeof value !== 'string') return undefined
|
|
38
|
+
const normalized = value.trim().toLowerCase()
|
|
39
|
+
if (!normalized) return undefined
|
|
40
|
+
const singularMap: Record<string, string> = {
|
|
41
|
+
agent: 'agents',
|
|
42
|
+
task: 'tasks',
|
|
43
|
+
backlog_task: 'tasks',
|
|
44
|
+
'backlog-task': 'tasks',
|
|
45
|
+
backlogtask: 'tasks',
|
|
46
|
+
task_backlog: 'tasks',
|
|
47
|
+
'task-backlog': 'tasks',
|
|
48
|
+
work_item: 'tasks',
|
|
49
|
+
'work-item': 'tasks',
|
|
50
|
+
schedule: 'schedules',
|
|
51
|
+
skill: 'skills',
|
|
52
|
+
document: 'documents',
|
|
53
|
+
secret: 'secrets',
|
|
54
|
+
connector: 'connectors',
|
|
55
|
+
session: 'sessions',
|
|
56
|
+
}
|
|
57
|
+
return singularMap[normalized] || normalized
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function inferPlatformResourceFromAction(value: unknown): { resource?: string; action?: string } {
|
|
61
|
+
if (typeof value !== 'string') return {}
|
|
62
|
+
const normalized = value.trim().toLowerCase().replace(/-/g, '_')
|
|
63
|
+
if (!normalized) return {}
|
|
64
|
+
const match = normalized.match(/^(list|get|create|update|delete)_([a-z_]+)$/)
|
|
65
|
+
if (!match) return {}
|
|
66
|
+
const [, action, rawResource] = match
|
|
67
|
+
const resource = normalizePlatformResourceName(rawResource)
|
|
68
|
+
if (!resource) return {}
|
|
69
|
+
return { resource, action }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function extractPlatformFields(value: Record<string, unknown>): Record<string, unknown> {
|
|
73
|
+
const fields: Record<string, unknown> = {}
|
|
74
|
+
for (const [key, fieldValue] of Object.entries(value)) {
|
|
75
|
+
if (fieldValue === undefined || fieldValue === null) continue
|
|
76
|
+
if (['input', 'args', 'arguments', 'payload', 'resources', 'parameters', 'resource', 'type', 'action', 'id'].includes(key)) continue
|
|
77
|
+
fields[key] = fieldValue
|
|
78
|
+
}
|
|
79
|
+
return fields
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function normalizePlatformActionArgs(rawArgs: Record<string, unknown>): Record<string, unknown> {
|
|
83
|
+
const normalized = normalizeToolInputArgs(rawArgs)
|
|
84
|
+
const resourceEntry = firstPlatformResource(normalized.resources)
|
|
85
|
+
const { resource, action, id, data, ...rest } = normalized
|
|
86
|
+
const payload: Record<string, unknown> = {}
|
|
87
|
+
const resourceValue = resource ?? resourceEntry?.resource ?? resourceEntry?.type
|
|
88
|
+
const rawResourceName = typeof resourceValue === 'string'
|
|
89
|
+
? String(resourceValue).trim()
|
|
90
|
+
: undefined
|
|
91
|
+
|
|
92
|
+
const rawAction = action ?? resourceEntry?.action
|
|
93
|
+
const inferredFromAction = resourceValue === undefined
|
|
94
|
+
? inferPlatformResourceFromAction(rawAction)
|
|
95
|
+
: {}
|
|
96
|
+
const effectiveResource = normalizePlatformResourceName(resourceValue) ?? inferredFromAction.resource
|
|
97
|
+
const effectiveAction = inferredFromAction.action && resourceValue === undefined
|
|
98
|
+
? inferredFromAction.action
|
|
99
|
+
: rawAction
|
|
100
|
+
const effectiveId = id ?? resourceEntry?.id
|
|
101
|
+
|
|
102
|
+
if (effectiveResource !== undefined) payload.resource = effectiveResource
|
|
103
|
+
if (effectiveAction !== undefined) payload.action = effectiveAction
|
|
104
|
+
if (effectiveId !== undefined) payload.id = effectiveId
|
|
105
|
+
|
|
106
|
+
const directFields = extractPlatformFields(rest)
|
|
107
|
+
const resourcePayloadCandidates = effectiveResource
|
|
108
|
+
? uniqueStrings([
|
|
109
|
+
rawResourceName,
|
|
110
|
+
effectiveResource,
|
|
111
|
+
effectiveResource.replace(/s$/, ''),
|
|
112
|
+
])
|
|
113
|
+
: []
|
|
114
|
+
const directResourcePayload = resourcePayloadCandidates
|
|
115
|
+
.map((candidate) => parsePlatformData(normalized[candidate]))
|
|
116
|
+
.find(Boolean)
|
|
117
|
+
|| null
|
|
118
|
+
if (effectiveResource) {
|
|
119
|
+
for (const candidate of resourcePayloadCandidates) delete directFields[candidate]
|
|
120
|
+
}
|
|
121
|
+
const parameterFields = {
|
|
122
|
+
...(parsePlatformData(resourceEntry?.parameters) || {}),
|
|
123
|
+
...(parsePlatformData(resourceEntry?.params) || {}),
|
|
124
|
+
...(parsePlatformData(normalized.parameters) || {}),
|
|
125
|
+
...(directResourcePayload || {}),
|
|
126
|
+
}
|
|
127
|
+
const parsedData = parsePlatformData(data)
|
|
128
|
+
const mergedData = {
|
|
129
|
+
...(parsedData || {}),
|
|
130
|
+
...parameterFields,
|
|
131
|
+
...directFields,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (Object.keys(mergedData).length > 0) {
|
|
135
|
+
payload.data = JSON.stringify(mergedData)
|
|
136
|
+
} else if (typeof data === 'string' && data.trim()) {
|
|
137
|
+
payload.data = data
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return payload
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function uniqueStrings(values: Array<string | undefined>): string[] {
|
|
144
|
+
return [...new Set(values.filter((value): value is string => Boolean(value)))]
|
|
145
|
+
}
|
|
146
|
+
|
|
9
147
|
/**
|
|
10
148
|
* Unified Platform Execution Logic
|
|
11
149
|
*/
|
|
12
150
|
async function executePlatformAction(args: any, bctx: any) {
|
|
13
|
-
const normalized =
|
|
14
|
-
const { resource, action, id, data
|
|
151
|
+
const normalized = normalizePlatformActionArgs((args ?? {}) as Record<string, unknown>)
|
|
152
|
+
const { resource, action, id, data } = normalized
|
|
15
153
|
|
|
16
154
|
// We reuse the existing CRUD tool logic but expose it via a single tool
|
|
17
155
|
const crudTools = buildCrudTools({
|
|
@@ -36,7 +174,7 @@ async function executePlatformAction(args: any, bctx: any) {
|
|
|
36
174
|
}
|
|
37
175
|
|
|
38
176
|
// Forward to the specific CRUD tool implementation
|
|
39
|
-
return targetTool.invoke({ action, id, data
|
|
177
|
+
return targetTool.invoke({ action, id, data })
|
|
40
178
|
}
|
|
41
179
|
|
|
42
180
|
/**
|
|
@@ -52,7 +190,7 @@ const PlatformPlugin: Plugin = {
|
|
|
52
190
|
tools: [
|
|
53
191
|
{
|
|
54
192
|
name: 'manage_platform',
|
|
55
|
-
description: 'Unified tool for managing all SwarmClaw resources.',
|
|
193
|
+
description: 'Unified tool for managing all SwarmClaw resources. For create/update, pass resource + action, then either put fields inside data, pass them as top-level fields, or use a single resources[0].parameters envelope.',
|
|
56
194
|
parameters: {
|
|
57
195
|
type: 'object',
|
|
58
196
|
properties: {
|
|
@@ -27,6 +27,8 @@ async function executePluginCreatorAction(args: Record<string, unknown>, ctxOrBc
|
|
|
27
27
|
const action = normalized.action as string | undefined
|
|
28
28
|
const filename = (normalized.filename ?? normalized.fileName) as string | undefined
|
|
29
29
|
const code = (normalized.code ?? normalized.content) as string | undefined
|
|
30
|
+
const packageJson = normalized.packageJson ?? normalized.package_json ?? normalized.manifest
|
|
31
|
+
const packageManager = typeof normalized.packageManager === 'string' ? normalized.packageManager : undefined
|
|
30
32
|
const approved = normalized.approved as boolean | undefined
|
|
31
33
|
|
|
32
34
|
try {
|
|
@@ -40,15 +42,23 @@ async function executePluginCreatorAction(args: Record<string, unknown>, ctxOrBc
|
|
|
40
42
|
|
|
41
43
|
// REQUIRE USER APPROVAL
|
|
42
44
|
if (approved !== true) {
|
|
43
|
-
const {
|
|
44
|
-
|
|
45
|
+
const { requestApprovalMaybeAutoApprove } = await import('../approvals')
|
|
46
|
+
const approval = await requestApprovalMaybeAutoApprove({
|
|
45
47
|
category: 'plugin_scaffold',
|
|
46
48
|
title: `Scaffold Plugin: ${filename}`,
|
|
47
49
|
description: `Create new plugin file with ${code.length} chars of code.`,
|
|
48
|
-
data: { filename, code, createdByAgentId: pctx.agentId || null },
|
|
50
|
+
data: { filename, code, packageJson, packageManager, createdByAgentId: pctx.agentId || null },
|
|
49
51
|
agentId: pctx.agentId,
|
|
50
52
|
sessionId: pctx.sessionId,
|
|
51
53
|
})
|
|
54
|
+
if (approval.status === 'approved') {
|
|
55
|
+
return JSON.stringify({
|
|
56
|
+
type: 'plugin_scaffold_request',
|
|
57
|
+
filename,
|
|
58
|
+
autoApproved: true,
|
|
59
|
+
message: `Plugin "${filename}" was auto-approved and scaffolded. It is now available in this chat.`,
|
|
60
|
+
})
|
|
61
|
+
}
|
|
52
62
|
return JSON.stringify({
|
|
53
63
|
type: 'plugin_scaffold_request',
|
|
54
64
|
filename,
|
|
@@ -56,12 +66,13 @@ async function executePluginCreatorAction(args: Record<string, unknown>, ctxOrBc
|
|
|
56
66
|
})
|
|
57
67
|
}
|
|
58
68
|
|
|
59
|
-
const filePath = path.join(PLUGINS_DIR, filename)
|
|
60
|
-
fs.writeFileSync(filePath, code, 'utf8')
|
|
61
|
-
|
|
62
|
-
// Reload the plugin manager so the new plugin is discovered
|
|
63
69
|
const manager = getPluginManager()
|
|
64
|
-
manager.
|
|
70
|
+
await manager.savePluginSource(filename, code, {
|
|
71
|
+
packageJson,
|
|
72
|
+
packageManager,
|
|
73
|
+
installDependencies: packageJson !== undefined,
|
|
74
|
+
})
|
|
75
|
+
const filePath = path.join(PLUGINS_DIR, filename)
|
|
65
76
|
|
|
66
77
|
// Auto-enable the plugin for the agent that created it
|
|
67
78
|
if (pctx.agentId && pctx.sessionId) {
|
|
@@ -82,10 +93,53 @@ async function executePluginCreatorAction(args: Record<string, unknown>, ctxOrBc
|
|
|
82
93
|
return `Plugin saved to ${filePath} and PluginManager reloaded. It is now enabled for this chat.`
|
|
83
94
|
}
|
|
84
95
|
|
|
96
|
+
if (action === 'install_dependencies') {
|
|
97
|
+
if (!filename) return 'Error: filename is required for install_dependencies.'
|
|
98
|
+
|
|
99
|
+
if (approved !== true) {
|
|
100
|
+
const { requestApprovalMaybeAutoApprove } = await import('../approvals')
|
|
101
|
+
const approval = await requestApprovalMaybeAutoApprove({
|
|
102
|
+
category: 'plugin_install',
|
|
103
|
+
title: `Install Plugin Dependencies: ${filename}`,
|
|
104
|
+
description: `Install package dependencies for plugin ${filename}${packageManager ? ` using ${packageManager}` : ''}.`,
|
|
105
|
+
data: { filename, packageJson, packageManager, createdByAgentId: pctx.agentId || null },
|
|
106
|
+
agentId: pctx.agentId,
|
|
107
|
+
sessionId: pctx.sessionId,
|
|
108
|
+
})
|
|
109
|
+
if (approval.status === 'approved') {
|
|
110
|
+
return JSON.stringify({
|
|
111
|
+
type: 'plugin_install_request',
|
|
112
|
+
filename,
|
|
113
|
+
autoApproved: true,
|
|
114
|
+
message: `Dependencies for "${filename}" were auto-approved and are being installed.`,
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
return JSON.stringify({
|
|
118
|
+
type: 'plugin_install_request',
|
|
119
|
+
filename,
|
|
120
|
+
message: `I've requested approval to install dependencies for "${filename}". Once approved, the plugin manager will run the selected package manager automatically.`,
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const manager = getPluginManager()
|
|
125
|
+
if (packageJson !== undefined) {
|
|
126
|
+
const source = manager.readPluginSource(filename)
|
|
127
|
+
await manager.savePluginSource(filename, source, {
|
|
128
|
+
packageJson,
|
|
129
|
+
packageManager,
|
|
130
|
+
installDependencies: false,
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
const result = await manager.installPluginDependencies(filename, {
|
|
134
|
+
packageManager: packageManager as import('@/types').PluginPackageManager | undefined,
|
|
135
|
+
})
|
|
136
|
+
return `Dependencies installed for ${filename} using ${result.packageManager || packageManager || 'npm'}.`
|
|
137
|
+
}
|
|
138
|
+
|
|
85
139
|
if (action === 'get_spec') {
|
|
86
140
|
return `
|
|
87
141
|
SwarmClaw Plugin Specification:
|
|
88
|
-
A plugin is a
|
|
142
|
+
A plugin is a JavaScript module (.js or .mjs) that can be dual-compatible with both SwarmClaw and OpenClaw platforms.
|
|
89
143
|
|
|
90
144
|
\`\`\`js
|
|
91
145
|
module.exports = {
|
|
@@ -100,10 +154,11 @@ module.exports = {
|
|
|
100
154
|
hooks: {
|
|
101
155
|
beforeAgentStart: async ({ session, message }) => {},
|
|
102
156
|
afterAgentComplete: async ({ session, response }) => {},
|
|
103
|
-
beforeToolExec: async ({
|
|
104
|
-
afterToolExec: async ({ session, toolName,
|
|
157
|
+
beforeToolExec: async ({ toolName, input }) => input,
|
|
158
|
+
afterToolExec: async ({ session, toolName, input, output }) => {},
|
|
105
159
|
transformInboundMessage: async ({ session, text }) => { return text; },
|
|
106
160
|
transformOutboundMessage: async ({ session, text }) => { return text; },
|
|
161
|
+
afterChatTurn: async ({ session, message, response, source, internal }) => {},
|
|
107
162
|
},
|
|
108
163
|
|
|
109
164
|
tools: [
|
|
@@ -140,27 +195,31 @@ module.exports = {
|
|
|
140
195
|
\`\`\`
|
|
141
196
|
|
|
142
197
|
Key rules:
|
|
143
|
-
- Export
|
|
144
|
-
- SwarmClaw checks
|
|
198
|
+
- Export SwarmClaw hooks/tools. Add register(api) too if you want OpenClaw compatibility.
|
|
199
|
+
- SwarmClaw checks hooks/tools first; OpenClaw checks register()
|
|
145
200
|
- Tools must have name, description, parameters (JSON Schema), and execute function
|
|
146
201
|
- Hooks are optional — only include the ones you need
|
|
202
|
+
- If your plugin needs npm/pnpm/yarn/bun packages, include a packageJson object during scaffold or call install_dependencies later.
|
|
203
|
+
- Dependency installs are run by the plugin manager inside a per-plugin workspace using the selected package manager with scripts disabled.
|
|
204
|
+
- Plugin settings are declared through ui.settingsFields and stored per plugin ID
|
|
147
205
|
- Keep plugins focused: one clear purpose per plugin
|
|
148
206
|
`
|
|
149
207
|
}
|
|
150
208
|
|
|
151
209
|
if (action === 'read') {
|
|
152
210
|
if (!filename) return 'Error: filename required.'
|
|
153
|
-
|
|
154
|
-
if (!fs.existsSync(filePath)) return `File not found: ${filename}`
|
|
155
|
-
return fs.readFileSync(filePath, 'utf8')
|
|
211
|
+
return getPluginManager().readPluginSource(filename)
|
|
156
212
|
}
|
|
157
213
|
|
|
158
214
|
if (action === 'edit') {
|
|
159
215
|
if (!filename || !code) return 'Error: filename and code are required for edit.'
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
216
|
+
const manager = getPluginManager()
|
|
217
|
+
try {
|
|
218
|
+
manager.readPluginSource(filename)
|
|
219
|
+
} catch {
|
|
220
|
+
return `File not found: ${filename}. Use scaffold to create new plugins.`
|
|
221
|
+
}
|
|
222
|
+
await manager.savePluginSource(filename, code)
|
|
164
223
|
return `Updated ${filename} and reloaded plugin manager.`
|
|
165
224
|
}
|
|
166
225
|
|
|
@@ -175,7 +234,7 @@ Key rules:
|
|
|
175
234
|
return `File not found: ${filename}`
|
|
176
235
|
}
|
|
177
236
|
|
|
178
|
-
return `Unknown action "${action}". Valid actions: get_spec, scaffold, read, edit, delete`
|
|
237
|
+
return `Unknown action "${action}". Valid actions: get_spec, scaffold, read, edit, delete, install_dependencies`
|
|
179
238
|
} catch (err: unknown) {
|
|
180
239
|
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
181
240
|
}
|
|
@@ -195,9 +254,11 @@ const PluginCreatorPlugin: Plugin = {
|
|
|
195
254
|
parameters: {
|
|
196
255
|
type: 'object',
|
|
197
256
|
properties: {
|
|
198
|
-
action: { type: 'string', enum: ['get_spec', 'scaffold', 'read', 'edit', 'delete'], description: 'get_spec: learn format. scaffold: create (needs approval). read: view code. edit: update existing. delete: remove.' },
|
|
257
|
+
action: { type: 'string', enum: ['get_spec', 'scaffold', 'read', 'edit', 'delete', 'install_dependencies'], description: 'get_spec: learn format. scaffold: create (needs approval). read: view code. edit: update existing. delete: remove. install_dependencies: write/read package.json and install runtime deps.' },
|
|
199
258
|
filename: { type: 'string', description: 'Plugin filename, e.g. my-plugin.js. Required for scaffold and delete.' },
|
|
200
259
|
code: { type: 'string', description: 'The raw JavaScript code for the plugin. Required for scaffold.' },
|
|
260
|
+
packageJson: { type: 'object', description: 'Optional package.json object for dependency-aware plugins. Use with scaffold or install_dependencies.' },
|
|
261
|
+
packageManager: { type: 'string', enum: ['npm', 'pnpm', 'yarn', 'bun'], description: 'Optional package manager to use for dependency installs.' },
|
|
201
262
|
approved: { type: 'boolean', description: 'Internal flag — do NOT set this. The approval system handles it automatically.' }
|
|
202
263
|
},
|
|
203
264
|
required: ['action']
|
|
@@ -228,9 +289,11 @@ export function buildPluginCreatorTools(bctx: ToolBuildContext): StructuredToolI
|
|
|
228
289
|
name: 'plugin_creator_tool',
|
|
229
290
|
description: PluginCreatorPlugin.tools![0].description,
|
|
230
291
|
schema: z.object({
|
|
231
|
-
action: z.enum(['get_spec', 'scaffold', 'read', 'edit', 'delete']),
|
|
292
|
+
action: z.enum(['get_spec', 'scaffold', 'read', 'edit', 'delete', 'install_dependencies']),
|
|
232
293
|
filename: z.string().optional(),
|
|
233
294
|
code: z.string().optional(),
|
|
295
|
+
packageJson: z.record(z.string(), z.any()).optional(),
|
|
296
|
+
packageManager: z.enum(['npm', 'pnpm', 'yarn', 'bun']).optional(),
|
|
234
297
|
approved: z.boolean().optional()
|
|
235
298
|
})
|
|
236
299
|
}
|