@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.
Files changed (203) hide show
  1. package/README.md +82 -39
  2. package/next.config.ts +31 -6
  3. package/package.json +3 -2
  4. package/src/app/api/agents/[id]/thread/route.ts +1 -0
  5. package/src/app/api/agents/route.ts +19 -5
  6. package/src/app/api/approvals/route.ts +22 -0
  7. package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
  8. package/src/app/api/clawhub/install/route.ts +2 -2
  9. package/src/app/api/eval/run/route.ts +37 -0
  10. package/src/app/api/eval/scenarios/route.ts +24 -0
  11. package/src/app/api/eval/suite/route.ts +29 -0
  12. package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
  13. package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
  14. package/src/app/api/memory/graph/route.ts +46 -0
  15. package/src/app/api/memory/route.ts +36 -5
  16. package/src/app/api/notifications/route.ts +3 -0
  17. package/src/app/api/plugins/install/route.ts +57 -5
  18. package/src/app/api/plugins/marketplace/route.ts +73 -22
  19. package/src/app/api/plugins/route.ts +61 -1
  20. package/src/app/api/plugins/ui/route.ts +34 -0
  21. package/src/app/api/sessions/[id]/checkpoints/route.ts +31 -0
  22. package/src/app/api/sessions/[id]/restore/route.ts +36 -0
  23. package/src/app/api/settings/route.ts +62 -0
  24. package/src/app/api/setup/doctor/route.ts +22 -5
  25. package/src/app/api/souls/[id]/route.ts +65 -0
  26. package/src/app/api/souls/route.ts +70 -0
  27. package/src/app/api/tasks/[id]/approve/route.ts +4 -3
  28. package/src/app/api/tasks/[id]/route.ts +16 -3
  29. package/src/app/api/tasks/route.ts +10 -2
  30. package/src/app/api/usage/route.ts +9 -2
  31. package/src/app/globals.css +27 -0
  32. package/src/app/page.tsx +10 -5
  33. package/src/cli/index.js +37 -0
  34. package/src/components/activity/activity-feed.tsx +9 -2
  35. package/src/components/agents/agent-avatar.tsx +5 -1
  36. package/src/components/agents/agent-card.tsx +55 -9
  37. package/src/components/agents/agent-sheet.tsx +112 -34
  38. package/src/components/agents/inspector-panel.tsx +1 -1
  39. package/src/components/agents/soul-library-picker.tsx +84 -13
  40. package/src/components/auth/access-key-gate.tsx +63 -54
  41. package/src/components/auth/user-picker.tsx +37 -32
  42. package/src/components/chat/activity-moment.tsx +2 -0
  43. package/src/components/chat/chat-area.tsx +11 -0
  44. package/src/components/chat/chat-header.tsx +69 -25
  45. package/src/components/chat/chat-tool-toggles.tsx +2 -2
  46. package/src/components/chat/checkpoint-timeline.tsx +112 -0
  47. package/src/components/chat/code-block.tsx +3 -1
  48. package/src/components/chat/exec-approval-card.tsx +8 -1
  49. package/src/components/chat/message-bubble.tsx +164 -4
  50. package/src/components/chat/message-list.tsx +46 -4
  51. package/src/components/chat/session-approval-card.tsx +80 -0
  52. package/src/components/chat/session-debug-panel.tsx +106 -84
  53. package/src/components/chat/streaming-bubble.tsx +6 -5
  54. package/src/components/chat/task-approval-card.tsx +78 -0
  55. package/src/components/chat/thinking-indicator.tsx +48 -12
  56. package/src/components/chat/tool-call-bubble.tsx +3 -0
  57. package/src/components/chat/tool-request-banner.tsx +39 -20
  58. package/src/components/chatrooms/chatroom-list.tsx +11 -4
  59. package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
  60. package/src/components/connectors/connector-list.tsx +33 -11
  61. package/src/components/connectors/connector-sheet.tsx +37 -7
  62. package/src/components/home/home-view.tsx +54 -24
  63. package/src/components/input/chat-input.tsx +22 -1
  64. package/src/components/knowledge/knowledge-list.tsx +17 -18
  65. package/src/components/knowledge/knowledge-sheet.tsx +9 -5
  66. package/src/components/layout/app-layout.tsx +87 -19
  67. package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
  68. package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
  69. package/src/components/memory/memory-browser.tsx +73 -45
  70. package/src/components/memory/memory-graph-view.tsx +203 -0
  71. package/src/components/memory/memory-list.tsx +20 -13
  72. package/src/components/plugins/plugin-list.tsx +214 -60
  73. package/src/components/plugins/plugin-sheet.tsx +119 -24
  74. package/src/components/projects/project-list.tsx +17 -9
  75. package/src/components/providers/provider-list.tsx +21 -6
  76. package/src/components/providers/provider-sheet.tsx +42 -25
  77. package/src/components/runs/run-list.tsx +17 -13
  78. package/src/components/schedules/schedule-card.tsx +10 -3
  79. package/src/components/schedules/schedule-list.tsx +2 -2
  80. package/src/components/schedules/schedule-sheet.tsx +28 -9
  81. package/src/components/secrets/secret-sheet.tsx +7 -2
  82. package/src/components/secrets/secrets-list.tsx +18 -5
  83. package/src/components/sessions/new-session-sheet.tsx +183 -376
  84. package/src/components/sessions/session-card.tsx +10 -2
  85. package/src/components/settings/gateway-connection-panel.tsx +9 -8
  86. package/src/components/shared/command-palette.tsx +13 -5
  87. package/src/components/shared/empty-state.tsx +20 -8
  88. package/src/components/shared/hint-tip.tsx +31 -0
  89. package/src/components/shared/notification-center.tsx +134 -86
  90. package/src/components/shared/profile-sheet.tsx +4 -0
  91. package/src/components/shared/settings/plugin-manager.tsx +360 -135
  92. package/src/components/shared/settings/section-capability-policy.tsx +3 -3
  93. package/src/components/shared/settings/section-runtime-loop.tsx +149 -4
  94. package/src/components/skills/clawhub-browser.tsx +1 -0
  95. package/src/components/skills/skill-list.tsx +31 -12
  96. package/src/components/skills/skill-sheet.tsx +20 -7
  97. package/src/components/tasks/approvals-panel.tsx +224 -0
  98. package/src/components/tasks/task-board.tsx +20 -12
  99. package/src/components/tasks/task-card.tsx +21 -7
  100. package/src/components/tasks/task-column.tsx +4 -3
  101. package/src/components/tasks/task-list.tsx +1 -1
  102. package/src/components/tasks/task-sheet.tsx +130 -1
  103. package/src/components/ui/dialog.tsx +1 -0
  104. package/src/components/ui/sheet.tsx +1 -0
  105. package/src/components/usage/metrics-dashboard.tsx +72 -48
  106. package/src/components/wallets/wallet-panel.tsx +65 -41
  107. package/src/components/wallets/wallet-section.tsx +9 -3
  108. package/src/components/webhooks/webhook-list.tsx +21 -12
  109. package/src/components/webhooks/webhook-sheet.tsx +13 -3
  110. package/src/lib/approval-display.test.ts +45 -0
  111. package/src/lib/approval-display.ts +62 -0
  112. package/src/lib/clipboard.ts +38 -0
  113. package/src/lib/memory.ts +8 -0
  114. package/src/lib/providers/claude-cli.ts +5 -3
  115. package/src/lib/providers/index.ts +67 -21
  116. package/src/lib/runtime-loop.ts +3 -2
  117. package/src/lib/server/approvals.ts +150 -0
  118. package/src/lib/server/chat-execution.ts +319 -74
  119. package/src/lib/server/chatroom-helpers.ts +63 -5
  120. package/src/lib/server/chatroom-orchestration.ts +74 -0
  121. package/src/lib/server/clawhub-client.ts +82 -6
  122. package/src/lib/server/connectors/manager.ts +27 -1
  123. package/src/lib/server/context-manager.ts +132 -50
  124. package/src/lib/server/cost.test.ts +73 -0
  125. package/src/lib/server/cost.ts +165 -34
  126. package/src/lib/server/daemon-state.ts +112 -1
  127. package/src/lib/server/data-dir.ts +18 -1
  128. package/src/lib/server/eval/runner.ts +126 -0
  129. package/src/lib/server/eval/scenarios.ts +218 -0
  130. package/src/lib/server/eval/scorer.ts +96 -0
  131. package/src/lib/server/eval/store.ts +37 -0
  132. package/src/lib/server/eval/types.ts +48 -0
  133. package/src/lib/server/execution-log.ts +12 -8
  134. package/src/lib/server/guardian.ts +34 -0
  135. package/src/lib/server/heartbeat-service.ts +53 -1
  136. package/src/lib/server/integrity-monitor.ts +208 -0
  137. package/src/lib/server/langgraph-checkpoint.ts +10 -0
  138. package/src/lib/server/link-understanding.ts +55 -0
  139. package/src/lib/server/llm-response-cache.test.ts +102 -0
  140. package/src/lib/server/llm-response-cache.ts +227 -0
  141. package/src/lib/server/main-agent-loop.ts +115 -16
  142. package/src/lib/server/main-session.ts +6 -3
  143. package/src/lib/server/mcp-conformance.test.ts +18 -0
  144. package/src/lib/server/mcp-conformance.ts +233 -0
  145. package/src/lib/server/memory-db.ts +193 -19
  146. package/src/lib/server/memory-retrieval.test.ts +56 -0
  147. package/src/lib/server/mmr.ts +73 -0
  148. package/src/lib/server/orchestrator-lg.ts +7 -1
  149. package/src/lib/server/orchestrator.ts +4 -3
  150. package/src/lib/server/plugins.ts +662 -132
  151. package/src/lib/server/process-manager.ts +18 -0
  152. package/src/lib/server/query-expansion.ts +57 -0
  153. package/src/lib/server/queue.ts +280 -11
  154. package/src/lib/server/runtime-settings.ts +9 -0
  155. package/src/lib/server/session-run-manager.test.ts +23 -0
  156. package/src/lib/server/session-run-manager.ts +32 -2
  157. package/src/lib/server/session-tools/canvas.ts +85 -50
  158. package/src/lib/server/session-tools/chatroom.ts +130 -127
  159. package/src/lib/server/session-tools/connector.ts +233 -454
  160. package/src/lib/server/session-tools/context-mgmt.ts +87 -105
  161. package/src/lib/server/session-tools/crud.ts +84 -7
  162. package/src/lib/server/session-tools/delegate.ts +351 -752
  163. package/src/lib/server/session-tools/discovery.ts +198 -0
  164. package/src/lib/server/session-tools/edit_file.ts +82 -0
  165. package/src/lib/server/session-tools/file-send.test.ts +39 -0
  166. package/src/lib/server/session-tools/file.ts +257 -425
  167. package/src/lib/server/session-tools/git.ts +87 -47
  168. package/src/lib/server/session-tools/http.ts +95 -33
  169. package/src/lib/server/session-tools/index.ts +217 -138
  170. package/src/lib/server/session-tools/memory.ts +154 -239
  171. package/src/lib/server/session-tools/monitor.ts +126 -0
  172. package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
  173. package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
  174. package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
  175. package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
  176. package/src/lib/server/session-tools/platform.ts +86 -0
  177. package/src/lib/server/session-tools/plugin-creator.ts +239 -0
  178. package/src/lib/server/session-tools/sample-ui.ts +97 -0
  179. package/src/lib/server/session-tools/sandbox.ts +175 -148
  180. package/src/lib/server/session-tools/schedule.ts +78 -0
  181. package/src/lib/server/session-tools/session-info.ts +104 -410
  182. package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
  183. package/src/lib/server/session-tools/shell.ts +171 -143
  184. package/src/lib/server/session-tools/subagent.ts +77 -77
  185. package/src/lib/server/session-tools/wallet.ts +182 -106
  186. package/src/lib/server/session-tools/web.ts +181 -327
  187. package/src/lib/server/storage.ts +36 -0
  188. package/src/lib/server/stream-agent-chat.ts +348 -242
  189. package/src/lib/server/task-quality-gate.test.ts +44 -0
  190. package/src/lib/server/task-quality-gate.ts +67 -0
  191. package/src/lib/server/task-validation.test.ts +78 -0
  192. package/src/lib/server/task-validation.ts +67 -2
  193. package/src/lib/server/tool-aliases.ts +68 -0
  194. package/src/lib/server/tool-capability-policy.ts +24 -5
  195. package/src/lib/server/tool-retry.ts +62 -0
  196. package/src/lib/server/transcript-repair.ts +72 -0
  197. package/src/lib/setup-defaults.ts +1 -0
  198. package/src/lib/tasks.ts +7 -1
  199. package/src/lib/tool-definitions.ts +24 -23
  200. package/src/lib/validation/schemas.ts +13 -0
  201. package/src/lib/view-routes.ts +2 -23
  202. package/src/stores/use-app-store.ts +23 -1
  203. 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
- export function buildOpenClawNodeTools(bctx: ToolBuildContext): StructuredToolInterface[] {
6
- if (!bctx.hasTool('openclaw_nodes')) return []
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
- const tools: StructuredToolInterface[] = []
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
- tools.push(
11
- tool(
12
- async () => {
13
- try {
14
- const { listRunningConnectors } = await import('../connectors/manager')
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
- // Proxy through RPC — use sendMessage as a workaround to invoke RPC
24
- // We need direct RPC access, so check if the instance exposes it
25
- // For now, return a helpful message about the integration
26
- return JSON.stringify({
27
- status: 'openclaw_nodes_list requires nodes.list RPC support on the gateway',
28
- connectorId: openclawConnectors[0].id,
29
- note: 'This feature requires the OpenClaw gateway to support nodes.* RPCs.',
30
- })
31
- } catch (err: unknown) {
32
- return JSON.stringify({ error: err instanceof Error ? err.message : String(err) })
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
- name: 'openclaw_nodes_list',
37
- description: 'List connected nodes/IoT devices through the OpenClaw gateway. Requires a running OpenClaw connector with nodes.* RPC support.',
38
- schema: z.object({}),
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
- return JSON.stringify({
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
- tools.push(
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 ({ nodeId, message }) => {
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: 'openclaw_node_notify',
102
- description: 'Send a notification to a connected node/IoT device through the OpenClaw gateway.',
103
- schema: z.object({
104
- nodeId: z.string().describe('Target node ID'),
105
- message: z.string().describe('Notification message'),
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
- export function buildOpenClawWorkspaceTools(bctx: ToolBuildContext): StructuredToolInterface[] {
32
- if (!bctx.hasTool('openclaw_workspace')) return []
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
- return [
35
- tool(
36
- async ({ message }) => {
37
- try {
38
- const workspace = resolveWorkspacePath()
39
- // Verify it's a git repo
40
- await execFileAsync('git', ['rev-parse', '--is-inside-work-tree'], {
41
- cwd: workspace,
42
- timeout: 5000,
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
- const label = message || new Date().toISOString().replace(/[:.]/g, '-')
46
- await gitInWorkspace(['add', '-A'])
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
- // Check if there's anything to commit
49
- const status = await gitInWorkspace(['status', '--porcelain'])
50
- if (!status.stdout.trim()) {
51
- return JSON.stringify({ ok: true, commitHash: null, message: 'Nothing to commit — workspace is clean.' })
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
- await gitInWorkspace(['commit', '-m', `backup: ${label}`])
55
- const { stdout } = await gitInWorkspace(['rev-parse', 'HEAD'])
56
- return JSON.stringify({ ok: true, commitHash: stdout.trim() })
57
- } catch (err: unknown) {
58
- const execErr = err as { stderr?: string; message?: string }
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
- const { stdout: prevHead } = await gitInWorkspace(['rev-parse', 'HEAD'])
89
- await gitInWorkspace(['reset', '--hard', commitHash])
90
- return JSON.stringify({ ok: true, rolledBackTo: commitHash, previousHead: prevHead.trim() })
91
- } catch (err: unknown) {
92
- const execErr = err as { stderr?: string; message?: string }
93
- return JSON.stringify({ ok: false, error: execErr.stderr || execErr.message || String(err) })
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
- name: 'openclaw_workspace_rollback',
98
- description: 'Roll back the OpenClaw workspace to a specific commit, or automatically find the last stable (non-auto-generated) commit.',
99
- schema: z.object({
100
- commitHash: z.string().optional().describe('Target commit hash (if omitted, uses the last stable commit)'),
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 ({ limit }) => {
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: 'openclaw_workspace_history',
125
- description: 'List recent git commits in the OpenClaw workspace.',
126
- schema: z.object({
127
- limit: z.number().optional().describe('Number of commits to return (default 20, max 100)'),
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
+ }