@swarmclawai/swarmclaw 0.6.7 → 0.7.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 +82 -39
- package/next.config.ts +31 -6
- package/package.json +3 -2
- package/src/app/api/agents/[id]/thread/route.ts +1 -0
- package/src/app/api/agents/route.ts +19 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/eval/run/route.ts +37 -0
- package/src/app/api/eval/scenarios/route.ts +24 -0
- package/src/app/api/eval/suite/route.ts +29 -0
- package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
- package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
- package/src/app/api/memory/graph/route.ts +46 -0
- package/src/app/api/memory/route.ts +36 -5
- package/src/app/api/notifications/route.ts +3 -0
- package/src/app/api/plugins/install/route.ts +57 -5
- package/src/app/api/plugins/marketplace/route.ts +73 -22
- package/src/app/api/plugins/route.ts +61 -1
- package/src/app/api/plugins/ui/route.ts +34 -0
- package/src/app/api/sessions/[id]/checkpoints/route.ts +31 -0
- package/src/app/api/sessions/[id]/restore/route.ts +36 -0
- package/src/app/api/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- package/src/app/api/souls/[id]/route.ts +65 -0
- package/src/app/api/souls/route.ts +70 -0
- package/src/app/api/tasks/[id]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +16 -3
- package/src/app/api/tasks/route.ts +10 -2
- package/src/app/api/usage/route.ts +9 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +37 -0
- package/src/components/activity/activity-feed.tsx +9 -2
- package/src/components/agents/agent-avatar.tsx +5 -1
- package/src/components/agents/agent-card.tsx +55 -9
- package/src/components/agents/agent-sheet.tsx +112 -34
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/agents/soul-library-picker.tsx +84 -13
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- package/src/components/chat/activity-moment.tsx +2 -0
- package/src/components/chat/chat-area.tsx +11 -0
- package/src/components/chat/chat-header.tsx +69 -25
- package/src/components/chat/chat-tool-toggles.tsx +2 -2
- package/src/components/chat/checkpoint-timeline.tsx +112 -0
- package/src/components/chat/code-block.tsx +3 -1
- package/src/components/chat/exec-approval-card.tsx +8 -1
- package/src/components/chat/message-bubble.tsx +164 -4
- package/src/components/chat/message-list.tsx +46 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/session-debug-panel.tsx +106 -84
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/task-approval-card.tsx +78 -0
- package/src/components/chat/thinking-indicator.tsx +48 -12
- package/src/components/chat/tool-call-bubble.tsx +3 -0
- package/src/components/chat/tool-request-banner.tsx +39 -20
- package/src/components/chatrooms/chatroom-list.tsx +11 -4
- package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
- package/src/components/connectors/connector-list.tsx +33 -11
- package/src/components/connectors/connector-sheet.tsx +37 -7
- package/src/components/home/home-view.tsx +54 -24
- package/src/components/input/chat-input.tsx +22 -1
- package/src/components/knowledge/knowledge-list.tsx +17 -18
- package/src/components/knowledge/knowledge-sheet.tsx +9 -5
- package/src/components/layout/app-layout.tsx +87 -19
- package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
- package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
- package/src/components/memory/memory-browser.tsx +73 -45
- package/src/components/memory/memory-graph-view.tsx +203 -0
- package/src/components/memory/memory-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +214 -60
- package/src/components/plugins/plugin-sheet.tsx +119 -24
- package/src/components/projects/project-list.tsx +17 -9
- package/src/components/providers/provider-list.tsx +21 -6
- package/src/components/providers/provider-sheet.tsx +42 -25
- package/src/components/runs/run-list.tsx +17 -13
- package/src/components/schedules/schedule-card.tsx +10 -3
- package/src/components/schedules/schedule-list.tsx +2 -2
- package/src/components/schedules/schedule-sheet.tsx +28 -9
- package/src/components/secrets/secret-sheet.tsx +7 -2
- package/src/components/secrets/secrets-list.tsx +18 -5
- package/src/components/sessions/new-session-sheet.tsx +183 -376
- package/src/components/sessions/session-card.tsx +10 -2
- package/src/components/settings/gateway-connection-panel.tsx +9 -8
- package/src/components/shared/command-palette.tsx +13 -5
- package/src/components/shared/empty-state.tsx +20 -8
- package/src/components/shared/hint-tip.tsx +31 -0
- package/src/components/shared/notification-center.tsx +134 -86
- package/src/components/shared/profile-sheet.tsx +4 -0
- package/src/components/shared/settings/plugin-manager.tsx +360 -135
- package/src/components/shared/settings/section-capability-policy.tsx +3 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +149 -4
- package/src/components/skills/clawhub-browser.tsx +1 -0
- package/src/components/skills/skill-list.tsx +31 -12
- package/src/components/skills/skill-sheet.tsx +20 -7
- package/src/components/tasks/approvals-panel.tsx +224 -0
- package/src/components/tasks/task-board.tsx +20 -12
- package/src/components/tasks/task-card.tsx +21 -7
- package/src/components/tasks/task-column.tsx +4 -3
- package/src/components/tasks/task-list.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +130 -1
- package/src/components/ui/dialog.tsx +1 -0
- package/src/components/ui/sheet.tsx +1 -0
- package/src/components/usage/metrics-dashboard.tsx +72 -48
- package/src/components/wallets/wallet-panel.tsx +65 -41
- package/src/components/wallets/wallet-section.tsx +9 -3
- package/src/components/webhooks/webhook-list.tsx +21 -12
- package/src/components/webhooks/webhook-sheet.tsx +13 -3
- package/src/lib/approval-display.test.ts +45 -0
- package/src/lib/approval-display.ts +62 -0
- package/src/lib/clipboard.ts +38 -0
- package/src/lib/memory.ts +8 -0
- package/src/lib/providers/claude-cli.ts +5 -3
- package/src/lib/providers/index.ts +67 -21
- package/src/lib/runtime-loop.ts +3 -2
- package/src/lib/server/approvals.ts +150 -0
- package/src/lib/server/chat-execution.ts +319 -74
- package/src/lib/server/chatroom-helpers.ts +63 -5
- package/src/lib/server/chatroom-orchestration.ts +74 -0
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/context-manager.ts +132 -50
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +112 -1
- package/src/lib/server/data-dir.ts +18 -1
- package/src/lib/server/eval/runner.ts +126 -0
- package/src/lib/server/eval/scenarios.ts +218 -0
- package/src/lib/server/eval/scorer.ts +96 -0
- package/src/lib/server/eval/store.ts +37 -0
- package/src/lib/server/eval/types.ts +48 -0
- package/src/lib/server/execution-log.ts +12 -8
- package/src/lib/server/guardian.ts +34 -0
- package/src/lib/server/heartbeat-service.ts +53 -1
- package/src/lib/server/integrity-monitor.ts +208 -0
- package/src/lib/server/langgraph-checkpoint.ts +10 -0
- package/src/lib/server/link-understanding.ts +55 -0
- package/src/lib/server/llm-response-cache.test.ts +102 -0
- package/src/lib/server/llm-response-cache.ts +227 -0
- package/src/lib/server/main-agent-loop.ts +115 -16
- package/src/lib/server/main-session.ts +6 -3
- package/src/lib/server/mcp-conformance.test.ts +18 -0
- package/src/lib/server/mcp-conformance.ts +233 -0
- package/src/lib/server/memory-db.ts +193 -19
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/mmr.ts +73 -0
- package/src/lib/server/orchestrator-lg.ts +7 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +662 -132
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/query-expansion.ts +57 -0
- package/src/lib/server/queue.ts +280 -11
- package/src/lib/server/runtime-settings.ts +9 -0
- package/src/lib/server/session-run-manager.test.ts +23 -0
- package/src/lib/server/session-run-manager.ts +32 -2
- package/src/lib/server/session-tools/canvas.ts +85 -50
- package/src/lib/server/session-tools/chatroom.ts +130 -127
- package/src/lib/server/session-tools/connector.ts +233 -454
- package/src/lib/server/session-tools/context-mgmt.ts +87 -105
- package/src/lib/server/session-tools/crud.ts +84 -7
- package/src/lib/server/session-tools/delegate.ts +351 -752
- package/src/lib/server/session-tools/discovery.ts +198 -0
- package/src/lib/server/session-tools/edit_file.ts +82 -0
- package/src/lib/server/session-tools/file-send.test.ts +39 -0
- package/src/lib/server/session-tools/file.ts +257 -425
- package/src/lib/server/session-tools/git.ts +87 -47
- package/src/lib/server/session-tools/http.ts +95 -33
- package/src/lib/server/session-tools/index.ts +217 -138
- package/src/lib/server/session-tools/memory.ts +154 -239
- package/src/lib/server/session-tools/monitor.ts +126 -0
- package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
- package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
- package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
- package/src/lib/server/session-tools/platform.ts +86 -0
- package/src/lib/server/session-tools/plugin-creator.ts +239 -0
- package/src/lib/server/session-tools/sample-ui.ts +97 -0
- package/src/lib/server/session-tools/sandbox.ts +175 -148
- package/src/lib/server/session-tools/schedule.ts +78 -0
- package/src/lib/server/session-tools/session-info.ts +104 -410
- package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
- package/src/lib/server/session-tools/shell.ts +171 -143
- package/src/lib/server/session-tools/subagent.ts +77 -77
- package/src/lib/server/session-tools/wallet.ts +182 -106
- package/src/lib/server/session-tools/web.ts +181 -327
- package/src/lib/server/storage.ts +36 -0
- package/src/lib/server/stream-agent-chat.ts +348 -242
- package/src/lib/server/task-quality-gate.test.ts +44 -0
- package/src/lib/server/task-quality-gate.ts +67 -0
- package/src/lib/server/task-validation.test.ts +78 -0
- package/src/lib/server/task-validation.ts +67 -2
- package/src/lib/server/tool-aliases.ts +68 -0
- package/src/lib/server/tool-capability-policy.ts +24 -5
- package/src/lib/server/tool-retry.ts +62 -0
- package/src/lib/server/transcript-repair.ts +72 -0
- package/src/lib/setup-defaults.ts +1 -0
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +24 -23
- package/src/lib/validation/schemas.ts +13 -0
- package/src/lib/view-routes.ts +2 -23
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +155 -10
|
@@ -1,112 +1,95 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
3
3
|
import type { ToolBuildContext } from './context'
|
|
4
|
+
import type { Plugin, PluginHooks } from '@/types'
|
|
5
|
+
import { getPluginManager } from '../plugins'
|
|
6
|
+
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Core OpenClaw Nodes Execution Logic
|
|
10
|
+
*/
|
|
11
|
+
async function executeNodesAction(args: any) {
|
|
12
|
+
const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
|
|
13
|
+
const action = normalized.action as string | undefined
|
|
14
|
+
const nodeId = (normalized.nodeId ?? normalized.node_id) as string | undefined
|
|
15
|
+
const message = normalized.message as string | undefined
|
|
16
|
+
const params = normalized.params as Record<string, unknown> | undefined
|
|
17
|
+
try {
|
|
18
|
+
const { listRunningConnectors, getRunningInstance } = await import('../connectors/manager')
|
|
19
|
+
const openclawConnectors = listRunningConnectors('openclaw')
|
|
20
|
+
if (!openclawConnectors.length) {
|
|
21
|
+
return JSON.stringify({
|
|
22
|
+
status: 'not_connected',
|
|
23
|
+
message: 'No running OpenClaw connector found.',
|
|
24
|
+
hint: 'Start an OpenClaw connector in the Connectors panel, then retry.',
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
const inst = getRunningInstance(openclawConnectors[0].id)
|
|
28
|
+
if (!inst) {
|
|
29
|
+
return JSON.stringify({
|
|
30
|
+
status: 'not_connected',
|
|
31
|
+
message: 'OpenClaw connector instance not accessible.',
|
|
32
|
+
connectorId: openclawConnectors[0].id,
|
|
33
|
+
})
|
|
34
|
+
}
|
|
7
35
|
|
|
8
|
-
|
|
36
|
+
if (action === 'list') {
|
|
37
|
+
return JSON.stringify({ status: 'nodes.list not supported on gateway yet', connectorId: openclawConnectors[0].id })
|
|
38
|
+
}
|
|
39
|
+
if (action === 'notify') {
|
|
40
|
+
return JSON.stringify({ status: 'nodes.notify not supported on gateway yet', nodeId, message })
|
|
41
|
+
}
|
|
42
|
+
if (action === 'invoke') {
|
|
43
|
+
return JSON.stringify({ status: 'nodes.invoke not supported on gateway yet', nodeId, invokeAction: params?.action })
|
|
44
|
+
}
|
|
9
45
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const openclawConnectors = listRunningConnectors('openclaw')
|
|
16
|
-
if (!openclawConnectors.length) {
|
|
17
|
-
return JSON.stringify({ error: 'No running OpenClaw connector found.' })
|
|
18
|
-
}
|
|
19
|
-
const { getRunningInstance } = await import('../connectors/manager')
|
|
20
|
-
const inst = getRunningInstance(openclawConnectors[0].id)
|
|
21
|
-
if (!inst) return JSON.stringify({ error: 'OpenClaw connector instance not accessible.' })
|
|
46
|
+
return JSON.stringify({ status: 'error', error: `Unknown nodes action "${action}".` })
|
|
47
|
+
} catch (err: any) {
|
|
48
|
+
return JSON.stringify({ error: err.message })
|
|
49
|
+
}
|
|
50
|
+
}
|
|
22
51
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Register as a Built-in Plugin
|
|
54
|
+
*/
|
|
55
|
+
const NodesPlugin: Plugin = {
|
|
56
|
+
name: 'OpenClaw Nodes',
|
|
57
|
+
description: 'Integrate with mobile apps and IoT devices via the OpenClaw gateway.',
|
|
58
|
+
hooks: {} as PluginHooks,
|
|
59
|
+
tools: [
|
|
60
|
+
{
|
|
61
|
+
name: 'openclaw_nodes',
|
|
62
|
+
description: 'Interact with connected OpenClaw nodes/devices.',
|
|
63
|
+
parameters: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties: {
|
|
66
|
+
action: { type: 'string', enum: ['list', 'notify', 'invoke'] },
|
|
67
|
+
nodeId: { type: 'string' },
|
|
68
|
+
message: { type: 'string' },
|
|
69
|
+
params: { type: 'object' }
|
|
70
|
+
},
|
|
71
|
+
required: ['action']
|
|
34
72
|
},
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
},
|
|
40
|
-
),
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
tools.push(
|
|
44
|
-
tool(
|
|
45
|
-
async ({ nodeId, action, params }) => {
|
|
46
|
-
try {
|
|
47
|
-
const { listRunningConnectors, getRunningInstance } = await import('../connectors/manager')
|
|
48
|
-
const openclawConnectors = listRunningConnectors('openclaw')
|
|
49
|
-
if (!openclawConnectors.length) {
|
|
50
|
-
return JSON.stringify({ error: 'No running OpenClaw connector found.' })
|
|
51
|
-
}
|
|
52
|
-
const inst = getRunningInstance(openclawConnectors[0].id)
|
|
53
|
-
if (!inst) return JSON.stringify({ error: 'OpenClaw connector instance not accessible.' })
|
|
73
|
+
execute: async (args) => executeNodesAction(args)
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
54
77
|
|
|
55
|
-
|
|
56
|
-
status: 'openclaw_node_invoke requires nodes.invoke RPC support on the gateway',
|
|
57
|
-
nodeId,
|
|
58
|
-
action,
|
|
59
|
-
params: params || null,
|
|
60
|
-
connectorId: openclawConnectors[0].id,
|
|
61
|
-
})
|
|
62
|
-
} catch (err: unknown) {
|
|
63
|
-
return JSON.stringify({ error: err instanceof Error ? err.message : String(err) })
|
|
64
|
-
}
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
name: 'openclaw_node_invoke',
|
|
68
|
-
description: 'Invoke an action on a connected node/IoT device through the OpenClaw gateway.',
|
|
69
|
-
schema: z.object({
|
|
70
|
-
nodeId: z.string().describe('Target node ID'),
|
|
71
|
-
action: z.string().describe('Action to invoke on the node'),
|
|
72
|
-
params: z.record(z.string(), z.unknown()).optional().describe('Optional parameters for the action'),
|
|
73
|
-
}),
|
|
74
|
-
},
|
|
75
|
-
),
|
|
76
|
-
)
|
|
78
|
+
getPluginManager().registerBuiltin('openclaw_nodes', NodesPlugin)
|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Legacy Bridge
|
|
82
|
+
*/
|
|
83
|
+
export function buildOpenClawNodeTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
84
|
+
if (!bctx.hasTool('openclaw_nodes')) return []
|
|
85
|
+
return [
|
|
79
86
|
tool(
|
|
80
|
-
async (
|
|
81
|
-
try {
|
|
82
|
-
const { listRunningConnectors, getRunningInstance } = await import('../connectors/manager')
|
|
83
|
-
const openclawConnectors = listRunningConnectors('openclaw')
|
|
84
|
-
if (!openclawConnectors.length) {
|
|
85
|
-
return JSON.stringify({ error: 'No running OpenClaw connector found.' })
|
|
86
|
-
}
|
|
87
|
-
const inst = getRunningInstance(openclawConnectors[0].id)
|
|
88
|
-
if (!inst) return JSON.stringify({ error: 'OpenClaw connector instance not accessible.' })
|
|
89
|
-
|
|
90
|
-
return JSON.stringify({
|
|
91
|
-
status: 'openclaw_node_notify requires nodes.notify RPC support on the gateway',
|
|
92
|
-
nodeId,
|
|
93
|
-
message,
|
|
94
|
-
connectorId: openclawConnectors[0].id,
|
|
95
|
-
})
|
|
96
|
-
} catch (err: unknown) {
|
|
97
|
-
return JSON.stringify({ error: err instanceof Error ? err.message : String(err) })
|
|
98
|
-
}
|
|
99
|
-
},
|
|
87
|
+
async (args) => executeNodesAction(args),
|
|
100
88
|
{
|
|
101
|
-
name: '
|
|
102
|
-
description:
|
|
103
|
-
schema: z.object({
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
},
|
|
108
|
-
),
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
return tools
|
|
89
|
+
name: 'openclaw_nodes',
|
|
90
|
+
description: NodesPlugin.tools![0].description,
|
|
91
|
+
schema: z.object({}).passthrough()
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
]
|
|
112
95
|
}
|
|
@@ -6,7 +6,10 @@ import * as path from 'path'
|
|
|
6
6
|
import * as os from 'os'
|
|
7
7
|
import { loadSettings } from '../storage'
|
|
8
8
|
import type { ToolBuildContext } from './context'
|
|
9
|
-
import { MAX_OUTPUT } from './context'
|
|
9
|
+
import { MAX_OUTPUT, truncate } from './context'
|
|
10
|
+
import type { Plugin, PluginHooks } from '@/types'
|
|
11
|
+
import { getPluginManager } from '../plugins'
|
|
12
|
+
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
10
13
|
|
|
11
14
|
const execFileAsync = promisify(execFile)
|
|
12
15
|
|
|
@@ -28,105 +31,112 @@ async function gitInWorkspace(args: string[], timeoutMs = 15_000): Promise<{ std
|
|
|
28
31
|
})
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Core OpenClaw Workspace Execution Logic
|
|
36
|
+
*/
|
|
37
|
+
async function executeWorkspaceAction(args: any) {
|
|
38
|
+
const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
|
|
39
|
+
const message = normalized.message as string | undefined
|
|
40
|
+
const commitHash = normalized.commitHash as string | undefined
|
|
41
|
+
const limit = normalized.limit as number | undefined
|
|
42
|
+
const action = typeof normalized.action === 'string' && normalized.action.trim() ? normalized.action.trim() : 'history'
|
|
43
|
+
try {
|
|
44
|
+
const workspace = resolveWorkspacePath()
|
|
45
|
+
const inGitRepo = async (): Promise<boolean> => {
|
|
46
|
+
try {
|
|
47
|
+
await execFileAsync('git', ['rev-parse', '--is-inside-work-tree'], { cwd: workspace, timeout: 5000 })
|
|
48
|
+
return true
|
|
49
|
+
} catch {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
}
|
|
33
53
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
54
|
+
if (action === 'backup') {
|
|
55
|
+
if (!(await inGitRepo())) {
|
|
56
|
+
return JSON.stringify({ ok: false, error: `Workspace is not a git repo: ${workspace}` })
|
|
57
|
+
}
|
|
58
|
+
const label = message || new Date().toISOString().replace(/[:.]/g, '-')
|
|
59
|
+
await gitInWorkspace(['add', '-A'])
|
|
60
|
+
const status = await gitInWorkspace(['status', '--porcelain'])
|
|
61
|
+
if (!status.stdout.trim()) return JSON.stringify({ ok: true, message: 'Clean.' })
|
|
62
|
+
await gitInWorkspace(['commit', '-m', `backup: ${label}`])
|
|
63
|
+
const { stdout } = await gitInWorkspace(['rev-parse', 'HEAD'])
|
|
64
|
+
return JSON.stringify({ ok: true, commitHash: stdout.trim() })
|
|
65
|
+
}
|
|
44
66
|
|
|
45
|
-
|
|
46
|
-
|
|
67
|
+
if (action === 'rollback') {
|
|
68
|
+
if (!(await inGitRepo())) {
|
|
69
|
+
return JSON.stringify({ ok: false, error: `Workspace is not a git repo: ${workspace}` })
|
|
70
|
+
}
|
|
71
|
+
let target = commitHash
|
|
72
|
+
if (!target) {
|
|
73
|
+
const { stdout } = await gitInWorkspace(['log', '--oneline', '--format=%H %s', '-50'])
|
|
74
|
+
const lines = stdout.trim().split('\n').filter(Boolean)
|
|
75
|
+
const stable = lines.find(l => !/^(rollback|daily|auto|backup:)/i.test(l.substring(41)))
|
|
76
|
+
if (!stable) return JSON.stringify({ ok: false, error: 'No stable commit.' })
|
|
77
|
+
target = stable.substring(0, 40)
|
|
78
|
+
}
|
|
79
|
+
await gitInWorkspace(['reset', '--hard', target])
|
|
80
|
+
return JSON.stringify({ ok: true, rolledBackTo: target })
|
|
81
|
+
}
|
|
47
82
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
83
|
+
if (action === 'history') {
|
|
84
|
+
if (!(await inGitRepo())) {
|
|
85
|
+
return JSON.stringify({ ok: false, error: `Workspace is not a git repo: ${workspace}` })
|
|
86
|
+
}
|
|
87
|
+
const count = Math.max(1, Math.min(limit ?? 20, 100))
|
|
88
|
+
const { stdout } = await gitInWorkspace(['log', '--oneline', `--format=%H %s`, `-${count}`])
|
|
89
|
+
const commits = stdout.trim().split('\n').filter(Boolean).map(l => ({ hash: l.substring(0, 40), message: l.substring(41) }))
|
|
90
|
+
return JSON.stringify({ commits })
|
|
91
|
+
}
|
|
53
92
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return JSON.stringify({ ok: false, error: execErr.stderr || execErr.message || String(err) })
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
name: 'openclaw_workspace_backup',
|
|
64
|
-
description: 'Create a git backup of the OpenClaw workspace. Stages all changes and commits.',
|
|
65
|
-
schema: z.object({
|
|
66
|
-
message: z.string().optional().describe('Optional backup message (defaults to timestamp)'),
|
|
67
|
-
}),
|
|
68
|
-
},
|
|
69
|
-
),
|
|
70
|
-
tool(
|
|
71
|
-
async ({ commitHash }) => {
|
|
72
|
-
try {
|
|
73
|
-
if (!commitHash) {
|
|
74
|
-
// Find first stable commit (skip auto-generated ones)
|
|
75
|
-
const { stdout } = await gitInWorkspace(['log', '--oneline', '--format=%H %s', '-50'])
|
|
76
|
-
const lines = stdout.trim().split('\n').filter(Boolean)
|
|
77
|
-
const autoPattern = /^(rollback|daily-backup|auto-backup|guardian-auto|backup:)/i
|
|
78
|
-
const stable = lines.find((line) => {
|
|
79
|
-
const msg = line.substring(41) // after hash + space
|
|
80
|
-
return !autoPattern.test(msg)
|
|
81
|
-
})
|
|
82
|
-
if (!stable) {
|
|
83
|
-
return JSON.stringify({ ok: false, error: 'No stable commit found to roll back to.' })
|
|
84
|
-
}
|
|
85
|
-
commitHash = stable.substring(0, 40)
|
|
86
|
-
}
|
|
93
|
+
return `Unknown action "${action}".`
|
|
94
|
+
} catch (err: any) {
|
|
95
|
+
return JSON.stringify({ ok: false, error: err.stderr || err.message })
|
|
96
|
+
}
|
|
97
|
+
}
|
|
87
98
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Register as a Built-in Plugin
|
|
101
|
+
*/
|
|
102
|
+
const WorkspacePlugin: Plugin = {
|
|
103
|
+
name: 'OpenClaw Workspace',
|
|
104
|
+
description: 'Manage OpenClaw workspace versioning: backup, rollback, and history.',
|
|
105
|
+
hooks: {} as PluginHooks,
|
|
106
|
+
tools: [
|
|
107
|
+
{
|
|
108
|
+
name: 'openclaw_workspace',
|
|
109
|
+
description: 'Versioning tools for the OpenClaw workspace.',
|
|
110
|
+
parameters: {
|
|
111
|
+
type: 'object',
|
|
112
|
+
properties: {
|
|
113
|
+
action: { type: 'string', enum: ['backup', 'rollback', 'history'] },
|
|
114
|
+
message: { type: 'string' },
|
|
115
|
+
commitHash: { type: 'string' },
|
|
116
|
+
limit: { type: 'number' }
|
|
117
|
+
},
|
|
118
|
+
required: ['action']
|
|
95
119
|
},
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
120
|
+
execute: async (args) => executeWorkspaceAction(args)
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
getPluginManager().registerBuiltin('openclaw_workspace', WorkspacePlugin)
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Legacy Bridge
|
|
129
|
+
*/
|
|
130
|
+
export function buildOpenClawWorkspaceTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
131
|
+
if (!bctx.hasTool('openclaw_workspace')) return []
|
|
132
|
+
return [
|
|
104
133
|
tool(
|
|
105
|
-
async (
|
|
106
|
-
try {
|
|
107
|
-
const count = Math.max(1, Math.min(limit ?? 20, 100))
|
|
108
|
-
const { stdout } = await gitInWorkspace(['log', '--oneline', `--format=%H %s`, `-${count}`])
|
|
109
|
-
const commits = stdout
|
|
110
|
-
.trim()
|
|
111
|
-
.split('\n')
|
|
112
|
-
.filter(Boolean)
|
|
113
|
-
.map((line) => ({
|
|
114
|
-
hash: line.substring(0, 40),
|
|
115
|
-
message: line.substring(41),
|
|
116
|
-
}))
|
|
117
|
-
return JSON.stringify({ commits })
|
|
118
|
-
} catch (err: unknown) {
|
|
119
|
-
const execErr = err as { stderr?: string; message?: string }
|
|
120
|
-
return JSON.stringify({ ok: false, error: execErr.stderr || execErr.message || String(err) })
|
|
121
|
-
}
|
|
122
|
-
},
|
|
134
|
+
async (args) => executeWorkspaceAction(args),
|
|
123
135
|
{
|
|
124
|
-
name: '
|
|
125
|
-
description:
|
|
126
|
-
schema: z.object({
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
},
|
|
130
|
-
),
|
|
136
|
+
name: 'openclaw_workspace',
|
|
137
|
+
description: WorkspacePlugin.tools![0].description,
|
|
138
|
+
schema: z.object({}).passthrough()
|
|
139
|
+
}
|
|
140
|
+
)
|
|
131
141
|
]
|
|
132
142
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
3
|
+
import { buildCrudTools } from './crud'
|
|
4
|
+
import type { ToolBuildContext } from './context'
|
|
5
|
+
import type { Plugin, PluginHooks } from '@/types'
|
|
6
|
+
import { getPluginManager } from '../plugins'
|
|
7
|
+
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Unified Platform Execution Logic
|
|
11
|
+
*/
|
|
12
|
+
async function executePlatformAction(args: any, bctx: any) {
|
|
13
|
+
const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
|
|
14
|
+
const { resource, action, id, data, ...rest } = normalized
|
|
15
|
+
|
|
16
|
+
// We reuse the existing CRUD tool logic but expose it via a single tool
|
|
17
|
+
const crudTools = buildCrudTools({
|
|
18
|
+
...bctx,
|
|
19
|
+
hasTool: (id: string) => [
|
|
20
|
+
'manage_agents',
|
|
21
|
+
'manage_tasks',
|
|
22
|
+
'manage_schedules',
|
|
23
|
+
'manage_skills',
|
|
24
|
+
'manage_documents',
|
|
25
|
+
'manage_secrets',
|
|
26
|
+
'manage_connectors',
|
|
27
|
+
'manage_sessions'
|
|
28
|
+
].includes(id)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const targetToolName = `manage_${resource}`
|
|
32
|
+
const targetTool = crudTools.find(t => t.name === targetToolName)
|
|
33
|
+
|
|
34
|
+
if (!targetTool) {
|
|
35
|
+
return `Error: Unknown resource type "${resource}". Valid resources: agents, tasks, schedules, skills, documents, secrets, connectors, sessions.`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Forward to the specific CRUD tool implementation
|
|
39
|
+
return targetTool.invoke({ action, id, data, ...rest })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Register as a Built-in Plugin
|
|
44
|
+
*/
|
|
45
|
+
const PlatformPlugin: Plugin = {
|
|
46
|
+
name: 'Core Platform',
|
|
47
|
+
description: 'Unified management of agents, tasks, schedules, skills, documents, and secrets.',
|
|
48
|
+
hooks: {} as PluginHooks,
|
|
49
|
+
tools: [
|
|
50
|
+
{
|
|
51
|
+
name: 'manage_platform',
|
|
52
|
+
description: 'Unified tool for managing all SwarmClaw resources.',
|
|
53
|
+
parameters: {
|
|
54
|
+
type: 'object',
|
|
55
|
+
properties: {
|
|
56
|
+
resource: { type: 'string', enum: ['agents', 'tasks', 'schedules', 'skills', 'documents', 'secrets', 'connectors', 'sessions'] },
|
|
57
|
+
action: { type: 'string', enum: ['list', 'get', 'create', 'update', 'delete'] },
|
|
58
|
+
id: { type: 'string' },
|
|
59
|
+
data: { type: 'string' }
|
|
60
|
+
},
|
|
61
|
+
required: ['resource', 'action']
|
|
62
|
+
},
|
|
63
|
+
execute: async (args, context) => executePlatformAction(args, { ...context.session, ctx: context.session })
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getPluginManager().registerBuiltin('manage_platform', PlatformPlugin)
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Legacy Bridge
|
|
72
|
+
*/
|
|
73
|
+
export function buildPlatformTools(bctx: ToolBuildContext): StructuredToolInterface[] {
|
|
74
|
+
if (!bctx.hasTool('manage_platform')) return []
|
|
75
|
+
|
|
76
|
+
return [
|
|
77
|
+
tool(
|
|
78
|
+
async (args) => executePlatformAction(args, bctx),
|
|
79
|
+
{
|
|
80
|
+
name: 'manage_platform',
|
|
81
|
+
description: PlatformPlugin.tools![0].description,
|
|
82
|
+
schema: z.object({}).passthrough()
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
]
|
|
86
|
+
}
|