@swarmclawai/swarmclaw 0.7.1 → 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.
Files changed (237) hide show
  1. package/README.md +155 -150
  2. package/package.json +1 -1
  3. package/src/app/api/agents/[id]/route.ts +26 -0
  4. package/src/app/api/agents/[id]/thread/route.ts +37 -9
  5. package/src/app/api/agents/route.ts +13 -2
  6. package/src/app/api/auth/route.ts +76 -7
  7. package/src/app/api/chatrooms/[id]/chat/route.ts +7 -2
  8. package/src/app/api/{sessions → chats}/[id]/browser/route.ts +5 -1
  9. package/src/app/api/{sessions → chats}/[id]/chat/route.ts +7 -3
  10. package/src/app/api/{sessions → chats}/[id]/checkpoints/route.ts +1 -1
  11. package/src/app/api/chats/[id]/main-loop/route.ts +13 -0
  12. package/src/app/api/{sessions → chats}/[id]/messages/route.ts +19 -13
  13. package/src/app/api/{sessions → chats}/[id]/restore/route.ts +1 -1
  14. package/src/app/api/{sessions → chats}/[id]/route.ts +22 -52
  15. package/src/app/api/{sessions → chats}/[id]/stop/route.ts +6 -1
  16. package/src/app/api/{sessions → chats}/route.ts +21 -7
  17. package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
  18. package/src/app/api/connectors/doctor/route.ts +13 -0
  19. package/src/app/api/files/open/route.ts +16 -14
  20. package/src/app/api/memory/maintenance/route.ts +11 -1
  21. package/src/app/api/openclaw/agent-files/route.ts +27 -4
  22. package/src/app/api/openclaw/skills/route.ts +11 -3
  23. package/src/app/api/plugins/dependencies/route.ts +24 -0
  24. package/src/app/api/plugins/install/route.ts +15 -92
  25. package/src/app/api/plugins/route.ts +6 -26
  26. package/src/app/api/plugins/settings/route.ts +40 -0
  27. package/src/app/api/plugins/ui/route.ts +1 -0
  28. package/src/app/api/settings/route.ts +49 -7
  29. package/src/app/api/tasks/[id]/route.ts +15 -6
  30. package/src/app/api/tasks/bulk/route.ts +2 -2
  31. package/src/app/api/tasks/route.ts +9 -4
  32. package/src/app/api/usage/route.ts +30 -0
  33. package/src/app/api/webhooks/[id]/route.ts +8 -1
  34. package/src/app/page.tsx +9 -2
  35. package/src/cli/index.js +39 -33
  36. package/src/cli/index.ts +43 -49
  37. package/src/cli/spec.js +29 -27
  38. package/src/components/agents/agent-card.tsx +16 -13
  39. package/src/components/agents/agent-chat-list.tsx +104 -4
  40. package/src/components/agents/agent-list.tsx +54 -22
  41. package/src/components/agents/agent-sheet.tsx +209 -18
  42. package/src/components/agents/cron-job-form.tsx +3 -3
  43. package/src/components/agents/inspector-panel.tsx +110 -50
  44. package/src/components/auth/access-key-gate.tsx +36 -97
  45. package/src/components/auth/setup-wizard.tsx +5 -38
  46. package/src/components/chat/chat-area.tsx +39 -27
  47. package/src/components/{sessions/session-card.tsx → chat/chat-card.tsx} +7 -23
  48. package/src/components/chat/chat-header.tsx +299 -314
  49. package/src/components/{sessions/session-list.tsx → chat/chat-list.tsx} +11 -14
  50. package/src/components/chat/chat-tool-toggles.tsx +26 -17
  51. package/src/components/chat/checkpoint-timeline.tsx +4 -4
  52. package/src/components/chat/message-bubble.tsx +4 -1
  53. package/src/components/chat/message-list.tsx +5 -3
  54. package/src/components/chat/session-debug-panel.tsx +1 -1
  55. package/src/components/chat/tool-request-banner.tsx +3 -3
  56. package/src/components/chatrooms/agent-hover-card.tsx +3 -3
  57. package/src/components/chatrooms/chatroom-tool-request-banner.tsx +2 -2
  58. package/src/components/chatrooms/chatroom-view.tsx +347 -205
  59. package/src/components/connectors/connector-list.tsx +265 -127
  60. package/src/components/connectors/connector-sheet.tsx +218 -1
  61. package/src/components/home/home-view.tsx +129 -5
  62. package/src/components/layout/app-layout.tsx +392 -182
  63. package/src/components/layout/mobile-header.tsx +26 -8
  64. package/src/components/plugins/plugin-list.tsx +487 -254
  65. package/src/components/plugins/plugin-sheet.tsx +236 -13
  66. package/src/components/projects/project-detail.tsx +183 -0
  67. package/src/components/settings/gateway-connection-panel.tsx +1 -1
  68. package/src/components/shared/agent-picker-list.tsx +2 -2
  69. package/src/components/shared/command-palette.tsx +111 -25
  70. package/src/components/shared/settings/plugin-manager.tsx +20 -4
  71. package/src/components/shared/settings/section-capability-policy.tsx +105 -0
  72. package/src/components/shared/settings/section-heartbeat.tsx +78 -1
  73. package/src/components/shared/settings/section-orchestrator.tsx +3 -3
  74. package/src/components/shared/settings/section-providers.tsx +1 -1
  75. package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
  76. package/src/components/shared/settings/section-secrets.tsx +6 -6
  77. package/src/components/shared/settings/section-user-preferences.tsx +1 -1
  78. package/src/components/shared/settings/section-voice.tsx +5 -1
  79. package/src/components/shared/settings/section-web-search.tsx +10 -2
  80. package/src/components/shared/settings/settings-page.tsx +244 -56
  81. package/src/components/tasks/approvals-panel.tsx +205 -18
  82. package/src/components/tasks/task-board.tsx +242 -46
  83. package/src/components/usage/metrics-dashboard.tsx +147 -1
  84. package/src/components/wallets/wallet-panel.tsx +17 -5
  85. package/src/components/webhooks/webhook-sheet.tsx +8 -8
  86. package/src/lib/auth.ts +17 -0
  87. package/src/lib/chat-streaming-state.test.ts +108 -0
  88. package/src/lib/chat-streaming-state.ts +108 -0
  89. package/src/lib/chat.ts +1 -1
  90. package/src/lib/{sessions.ts → chats.ts} +28 -18
  91. package/src/lib/openclaw-agent-id.test.ts +14 -0
  92. package/src/lib/openclaw-agent-id.ts +31 -0
  93. package/src/lib/providers/claude-cli.ts +1 -1
  94. package/src/lib/server/agent-assignment.test.ts +112 -0
  95. package/src/lib/server/agent-assignment.ts +169 -0
  96. package/src/lib/server/approval-connector-notify.test.ts +253 -0
  97. package/src/lib/server/approvals-auto-approve.test.ts +205 -0
  98. package/src/lib/server/approvals.ts +483 -75
  99. package/src/lib/server/autonomy-runtime.test.ts +341 -0
  100. package/src/lib/server/browser-state.test.ts +118 -0
  101. package/src/lib/server/browser-state.ts +123 -0
  102. package/src/lib/server/build-llm.test.ts +36 -0
  103. package/src/lib/server/build-llm.ts +11 -4
  104. package/src/lib/server/builtin-plugins.ts +34 -0
  105. package/src/lib/server/capability-router.ts +10 -8
  106. package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
  107. package/src/lib/server/chat-execution-tool-events.test.ts +134 -0
  108. package/src/lib/server/chat-execution.ts +285 -165
  109. package/src/lib/server/chatroom-health.test.ts +26 -0
  110. package/src/lib/server/chatroom-health.ts +2 -3
  111. package/src/lib/server/chatroom-helpers.test.ts +67 -2
  112. package/src/lib/server/chatroom-helpers.ts +48 -8
  113. package/src/lib/server/connectors/discord.ts +175 -11
  114. package/src/lib/server/connectors/doctor.test.ts +80 -0
  115. package/src/lib/server/connectors/doctor.ts +116 -0
  116. package/src/lib/server/connectors/manager.ts +948 -112
  117. package/src/lib/server/connectors/policy.test.ts +222 -0
  118. package/src/lib/server/connectors/policy.ts +452 -0
  119. package/src/lib/server/connectors/slack.ts +188 -9
  120. package/src/lib/server/connectors/telegram.ts +65 -15
  121. package/src/lib/server/connectors/thread-context.test.ts +44 -0
  122. package/src/lib/server/connectors/thread-context.ts +72 -0
  123. package/src/lib/server/connectors/types.ts +41 -11
  124. package/src/lib/server/cost.ts +34 -1
  125. package/src/lib/server/daemon-state.ts +61 -3
  126. package/src/lib/server/data-dir.ts +13 -0
  127. package/src/lib/server/delegation-jobs.test.ts +140 -0
  128. package/src/lib/server/delegation-jobs.ts +248 -0
  129. package/src/lib/server/document-utils.test.ts +47 -0
  130. package/src/lib/server/document-utils.ts +397 -0
  131. package/src/lib/server/heartbeat-service.ts +14 -40
  132. package/src/lib/server/heartbeat-source.test.ts +22 -0
  133. package/src/lib/server/heartbeat-source.ts +7 -0
  134. package/src/lib/server/identity-continuity.test.ts +77 -0
  135. package/src/lib/server/identity-continuity.ts +127 -0
  136. package/src/lib/server/mailbox-utils.ts +347 -0
  137. package/src/lib/server/main-agent-loop.ts +28 -1103
  138. package/src/lib/server/memory-db.ts +4 -6
  139. package/src/lib/server/memory-tiers.ts +40 -0
  140. package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
  141. package/src/lib/server/openclaw-agent-resolver.ts +128 -0
  142. package/src/lib/server/openclaw-exec-config.ts +5 -6
  143. package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
  144. package/src/lib/server/openclaw-skills-normalize.ts +136 -0
  145. package/src/lib/server/openclaw-sync.ts +3 -2
  146. package/src/lib/server/orchestrator-lg.ts +20 -9
  147. package/src/lib/server/orchestrator.ts +7 -7
  148. package/src/lib/server/playwright-proxy.mjs +27 -3
  149. package/src/lib/server/plugins.test.ts +207 -0
  150. package/src/lib/server/plugins.ts +927 -66
  151. package/src/lib/server/provider-health.ts +38 -6
  152. package/src/lib/server/queue.ts +13 -28
  153. package/src/lib/server/scheduler.ts +2 -0
  154. package/src/lib/server/session-archive-memory.test.ts +85 -0
  155. package/src/lib/server/session-archive-memory.ts +230 -0
  156. package/src/lib/server/session-mailbox.ts +8 -18
  157. package/src/lib/server/session-reset-policy.test.ts +99 -0
  158. package/src/lib/server/session-reset-policy.ts +311 -0
  159. package/src/lib/server/session-run-manager.ts +33 -82
  160. package/src/lib/server/session-tools/autonomy-tools.test.ts +105 -0
  161. package/src/lib/server/session-tools/calendar.ts +366 -0
  162. package/src/lib/server/session-tools/canvas.ts +1 -1
  163. package/src/lib/server/session-tools/chatroom.ts +4 -2
  164. package/src/lib/server/session-tools/connector.ts +114 -10
  165. package/src/lib/server/session-tools/context.ts +21 -5
  166. package/src/lib/server/session-tools/crawl.ts +447 -0
  167. package/src/lib/server/session-tools/crud.ts +74 -28
  168. package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
  169. package/src/lib/server/session-tools/delegate.ts +497 -24
  170. package/src/lib/server/session-tools/discovery.ts +24 -6
  171. package/src/lib/server/session-tools/document.ts +283 -0
  172. package/src/lib/server/session-tools/edit_file.ts +4 -2
  173. package/src/lib/server/session-tools/email.ts +320 -0
  174. package/src/lib/server/session-tools/extract.ts +137 -0
  175. package/src/lib/server/session-tools/file-normalize.test.ts +93 -0
  176. package/src/lib/server/session-tools/file-send.test.ts +84 -1
  177. package/src/lib/server/session-tools/file.ts +241 -25
  178. package/src/lib/server/session-tools/git.ts +1 -1
  179. package/src/lib/server/session-tools/http.ts +1 -1
  180. package/src/lib/server/session-tools/human-loop.ts +227 -0
  181. package/src/lib/server/session-tools/image-gen.ts +380 -0
  182. package/src/lib/server/session-tools/index.ts +130 -50
  183. package/src/lib/server/session-tools/mailbox.ts +276 -0
  184. package/src/lib/server/session-tools/memory.ts +172 -3
  185. package/src/lib/server/session-tools/monitor.ts +151 -8
  186. package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
  187. package/src/lib/server/session-tools/openclaw-nodes.ts +1 -1
  188. package/src/lib/server/session-tools/openclaw-workspace.ts +1 -1
  189. package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
  190. package/src/lib/server/session-tools/platform.ts +148 -7
  191. package/src/lib/server/session-tools/plugin-creator.ts +89 -26
  192. package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
  193. package/src/lib/server/session-tools/replicate.ts +301 -0
  194. package/src/lib/server/session-tools/sample-ui.ts +1 -1
  195. package/src/lib/server/session-tools/sandbox.ts +4 -2
  196. package/src/lib/server/session-tools/schedule.ts +24 -12
  197. package/src/lib/server/session-tools/session-info.ts +43 -7
  198. package/src/lib/server/session-tools/session-tools-wiring.test.ts +31 -17
  199. package/src/lib/server/session-tools/shell.ts +5 -2
  200. package/src/lib/server/session-tools/subagent.ts +194 -28
  201. package/src/lib/server/session-tools/table.ts +587 -0
  202. package/src/lib/server/session-tools/wallet.ts +42 -12
  203. package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
  204. package/src/lib/server/session-tools/web.ts +926 -91
  205. package/src/lib/server/storage.ts +255 -16
  206. package/src/lib/server/stream-agent-chat.ts +116 -268
  207. package/src/lib/server/structured-extract.test.ts +72 -0
  208. package/src/lib/server/structured-extract.ts +373 -0
  209. package/src/lib/server/task-mention.test.ts +16 -2
  210. package/src/lib/server/task-mention.ts +61 -10
  211. package/src/lib/server/tool-aliases.ts +66 -18
  212. package/src/lib/server/tool-capability-policy.test.ts +9 -9
  213. package/src/lib/server/tool-capability-policy.ts +38 -27
  214. package/src/lib/server/tool-retry.ts +2 -0
  215. package/src/lib/server/watch-jobs.test.ts +173 -0
  216. package/src/lib/server/watch-jobs.ts +532 -0
  217. package/src/lib/server/ws-hub.ts +5 -3
  218. package/src/lib/tool-definitions.ts +4 -0
  219. package/src/lib/validation/schemas.test.ts +26 -0
  220. package/src/lib/validation/schemas.ts +10 -1
  221. package/src/lib/ws-client.ts +14 -12
  222. package/src/proxy.ts +5 -5
  223. package/src/stores/use-app-store.ts +5 -11
  224. package/src/stores/use-chat-store.ts +38 -9
  225. package/src/types/index.ts +352 -47
  226. package/src/app/api/sessions/[id]/main-loop/route.ts +0 -94
  227. package/src/components/sessions/new-session-sheet.tsx +0 -253
  228. package/src/lib/server/main-session.ts +0 -24
  229. package/src/lib/server/session-run-manager.test.ts +0 -23
  230. /package/src/app/api/{sessions → chats}/[id]/clear/route.ts +0 -0
  231. /package/src/app/api/{sessions → chats}/[id]/deploy/route.ts +0 -0
  232. /package/src/app/api/{sessions → chats}/[id]/devserver/route.ts +0 -0
  233. /package/src/app/api/{sessions → chats}/[id]/edit-resend/route.ts +0 -0
  234. /package/src/app/api/{sessions → chats}/[id]/fork/route.ts +0 -0
  235. /package/src/app/api/{sessions → chats}/[id]/mailbox/route.ts +0 -0
  236. /package/src/app/api/{sessions → chats}/[id]/retry/route.ts +0 -0
  237. /package/src/app/api/{sessions → chats}/heartbeat/route.ts +0 -0
@@ -14,6 +14,7 @@ import {
14
14
  traverseLinkedMemoryGraph,
15
15
  type MemoryLookupLimits,
16
16
  } from './memory-graph'
17
+ import { isWorkingMemoryCategory } from './memory-tiers'
17
18
 
18
19
  import { DATA_DIR } from './data-dir'
19
20
 
@@ -1203,8 +1204,7 @@ function initDb() {
1203
1204
  if (seenCanonical.has(keyCanonical)) canonicalDuplicateCandidates++
1204
1205
  else seenCanonical.add(keyCanonical)
1205
1206
 
1206
- const category = String(row.category || '').toLowerCase()
1207
- const isWorkingLike = category === 'execution' || category === 'working' || category === 'scratch'
1207
+ const isWorkingLike = isWorkingMemoryCategory(row.category)
1208
1208
  if (isWorkingLike && (row.updatedAt || row.createdAt || 0) < cutoff) staleWorkingCandidates++
1209
1209
  }
1210
1210
 
@@ -1303,8 +1303,7 @@ function initDb() {
1303
1303
  if (pruneWorking && toDelete.size < deleteBudget) {
1304
1304
  for (const row of rows) {
1305
1305
  if (toDelete.has(row.id)) continue
1306
- const category = String(row.category || '').toLowerCase()
1307
- const isWorkingLike = category === 'execution' || category === 'working' || category === 'scratch'
1306
+ const isWorkingLike = isWorkingMemoryCategory(row.category)
1308
1307
  const updatedAt = row.updatedAt || row.createdAt || 0
1309
1308
  if (isWorkingLike && updatedAt < cutoff) toDelete.add(row.id)
1310
1309
  if (toDelete.size >= deleteBudget) break
@@ -1323,8 +1322,7 @@ function initDb() {
1323
1322
  const deletedSet = new Set(deleteIds)
1324
1323
  for (const row of rows) {
1325
1324
  if (!deletedSet.has(row.id)) continue
1326
- const category = String(row.category || '').toLowerCase()
1327
- const isWorkingLike = category === 'execution' || category === 'working' || category === 'scratch'
1325
+ const isWorkingLike = isWorkingMemoryCategory(row.category)
1328
1326
  if (isWorkingLike) pruned++
1329
1327
  else deduped++
1330
1328
  }
@@ -0,0 +1,40 @@
1
+ import type { MemoryEntry } from '@/types'
2
+
3
+ export type MemoryTier = 'working' | 'durable' | 'archive'
4
+
5
+ const WORKING_CATEGORIES = new Set(['execution', 'working', 'scratch', 'breadcrumb'])
6
+ const ARCHIVE_CATEGORIES = new Set(['session_archive'])
7
+
8
+ export function getMemoryTierForCategory(category: unknown): MemoryTier {
9
+ const normalized = typeof category === 'string' ? category.trim().toLowerCase() : ''
10
+ if (ARCHIVE_CATEGORIES.has(normalized)) return 'archive'
11
+ if (WORKING_CATEGORIES.has(normalized)) return 'working'
12
+ return 'durable'
13
+ }
14
+
15
+ export function getMemoryTier(entry: Pick<MemoryEntry, 'category' | 'metadata'>): MemoryTier {
16
+ const metadataTier = typeof entry.metadata?.tier === 'string' ? entry.metadata.tier.trim().toLowerCase() : ''
17
+ if (metadataTier === 'archive' || metadataTier === 'session_archive') return 'archive'
18
+ if (metadataTier === 'working') return 'working'
19
+ if (metadataTier === 'durable') return 'durable'
20
+ return getMemoryTierForCategory(entry.category)
21
+ }
22
+
23
+ export function partitionMemoriesByTier<T extends Pick<MemoryEntry, 'category' | 'metadata'>>(entries: T[]) {
24
+ const working: T[] = []
25
+ const durable: T[] = []
26
+ const archive: T[] = []
27
+
28
+ for (const entry of entries) {
29
+ const tier = getMemoryTier(entry)
30
+ if (tier === 'working') working.push(entry)
31
+ else if (tier === 'archive') archive.push(entry)
32
+ else durable.push(entry)
33
+ }
34
+
35
+ return { working, durable, archive }
36
+ }
37
+
38
+ export function isWorkingMemoryCategory(category: unknown): boolean {
39
+ return getMemoryTierForCategory(category) === 'working'
40
+ }
@@ -0,0 +1,70 @@
1
+ import assert from 'node:assert/strict'
2
+ import { test } from 'node:test'
3
+ import type { Agent } from '@/types'
4
+ import {
5
+ resolveOpenClawGatewayAgentIdFromList,
6
+ type OpenClawGatewayAgentSummary,
7
+ } from './openclaw-agent-resolver'
8
+
9
+ function makeOpenClawAgent(overrides: Partial<Agent> = {}): Agent {
10
+ const now = Date.now()
11
+ return {
12
+ id: 'f4535f26',
13
+ name: 'OpenClaw Ops',
14
+ description: '',
15
+ systemPrompt: '',
16
+ provider: 'openclaw',
17
+ model: 'openclaw-default',
18
+ createdAt: now,
19
+ updatedAt: now,
20
+ ...overrides,
21
+ }
22
+ }
23
+
24
+ test('resolveOpenClawGatewayAgentIdFromList matches a local OpenClaw agent by normalized name', () => {
25
+ const gatewayAgents: OpenClawGatewayAgentSummary[] = [
26
+ { id: 'main', name: 'Main' },
27
+ { id: 'openclaw-ops', name: 'OpenClaw Ops' },
28
+ ]
29
+ const resolved = resolveOpenClawGatewayAgentIdFromList({
30
+ agentRef: 'f4535f26',
31
+ gatewayAgents,
32
+ localAgent: makeOpenClawAgent(),
33
+ })
34
+ assert.equal(resolved, 'openclaw-ops')
35
+ })
36
+
37
+ test('resolveOpenClawGatewayAgentIdFromList preserves direct gateway ids', () => {
38
+ const gatewayAgents: OpenClawGatewayAgentSummary[] = [
39
+ { id: 'main', name: 'Main' },
40
+ ]
41
+ const resolved = resolveOpenClawGatewayAgentIdFromList({
42
+ agentRef: 'main',
43
+ gatewayAgents,
44
+ })
45
+ assert.equal(resolved, 'main')
46
+ })
47
+
48
+ test('resolveOpenClawGatewayAgentIdFromList can match identity names when display names differ', () => {
49
+ const gatewayAgents: OpenClawGatewayAgentSummary[] = [
50
+ { id: 'research-ops', identity: { name: 'Research Ops' } },
51
+ ]
52
+ const resolved = resolveOpenClawGatewayAgentIdFromList({
53
+ agentRef: 'agent-123',
54
+ gatewayAgents,
55
+ localAgent: makeOpenClawAgent({ id: 'agent-123', name: 'Research Ops' }),
56
+ })
57
+ assert.equal(resolved, 'research-ops')
58
+ })
59
+
60
+ test('single-agent gateway can back a local OpenClaw provider agent without an explicit name match', async () => {
61
+ const gatewayAgents: OpenClawGatewayAgentSummary[] = [
62
+ { id: 'main', name: 'Main' },
63
+ ]
64
+ const resolved = resolveOpenClawGatewayAgentIdFromList({
65
+ agentRef: 'f4535f26',
66
+ gatewayAgents,
67
+ localAgent: makeOpenClawAgent({ name: 'OpenClaw-2' }),
68
+ })
69
+ assert.equal(resolved, 'main')
70
+ })
@@ -0,0 +1,128 @@
1
+ import type { Agent } from '@/types'
2
+ import { normalizeOpenClawAgentId } from '@/lib/openclaw-agent-id'
3
+ import { ensureGatewayConnected, type OpenClawGateway } from './openclaw-gateway'
4
+ import { loadAgents } from './storage'
5
+
6
+ export interface OpenClawGatewayAgentSummary {
7
+ id: string
8
+ name?: string
9
+ identity?: {
10
+ name?: string
11
+ } | null
12
+ }
13
+
14
+ interface OpenClawGatewayAgentsList {
15
+ defaultId?: string
16
+ agents?: OpenClawGatewayAgentSummary[]
17
+ }
18
+
19
+ function addTextCandidate(target: Set<string>, value: string | undefined | null) {
20
+ const trimmed = (value ?? '').trim()
21
+ if (trimmed) {
22
+ target.add(trimmed.toLowerCase())
23
+ }
24
+ }
25
+
26
+ function addNormalizedCandidate(target: Set<string>, value: string | undefined | null) {
27
+ const trimmed = (value ?? '').trim()
28
+ if (trimmed) {
29
+ target.add(normalizeOpenClawAgentId(trimmed))
30
+ }
31
+ }
32
+
33
+ export function resolveOpenClawGatewayAgentIdFromList(params: {
34
+ agentRef: string
35
+ gatewayAgents: OpenClawGatewayAgentSummary[]
36
+ localAgent?: Agent | null
37
+ }): string | null {
38
+ const rawRef = params.agentRef.trim()
39
+ if (!rawRef) {
40
+ return null
41
+ }
42
+
43
+ const exactTextCandidates = new Set<string>()
44
+ const normalizedCandidates = new Set<string>()
45
+
46
+ addTextCandidate(exactTextCandidates, rawRef)
47
+ addNormalizedCandidate(normalizedCandidates, rawRef)
48
+
49
+ if (params.localAgent) {
50
+ addTextCandidate(exactTextCandidates, params.localAgent.id)
51
+ addTextCandidate(exactTextCandidates, params.localAgent.name)
52
+ addNormalizedCandidate(normalizedCandidates, params.localAgent.id)
53
+ addNormalizedCandidate(normalizedCandidates, params.localAgent.name)
54
+ }
55
+
56
+ for (const gatewayAgent of params.gatewayAgents) {
57
+ if (exactTextCandidates.has(gatewayAgent.id.trim().toLowerCase())) {
58
+ return gatewayAgent.id
59
+ }
60
+ }
61
+
62
+ for (const gatewayAgent of params.gatewayAgents) {
63
+ if (normalizedCandidates.has(normalizeOpenClawAgentId(gatewayAgent.id))) {
64
+ return gatewayAgent.id
65
+ }
66
+ }
67
+
68
+ for (const gatewayAgent of params.gatewayAgents) {
69
+ const labels = [gatewayAgent.name, gatewayAgent.identity?.name]
70
+ for (const label of labels) {
71
+ if (!label?.trim()) continue
72
+ if (exactTextCandidates.has(label.trim().toLowerCase())) {
73
+ return gatewayAgent.id
74
+ }
75
+ }
76
+ }
77
+
78
+ for (const gatewayAgent of params.gatewayAgents) {
79
+ const labels = [gatewayAgent.name, gatewayAgent.identity?.name]
80
+ for (const label of labels) {
81
+ if (!label?.trim()) continue
82
+ if (normalizedCandidates.has(normalizeOpenClawAgentId(label))) {
83
+ return gatewayAgent.id
84
+ }
85
+ }
86
+ }
87
+
88
+ if (params.localAgent && params.gatewayAgents.length === 1) {
89
+ return params.gatewayAgents[0].id
90
+ }
91
+
92
+ return null
93
+ }
94
+
95
+ export async function resolveOpenClawGatewayAgentId(
96
+ agentRef: string,
97
+ gatewayArg?: OpenClawGateway | null,
98
+ ): Promise<string> {
99
+ const trimmedRef = agentRef.trim()
100
+ if (!trimmedRef) {
101
+ throw new Error('Missing agentId')
102
+ }
103
+
104
+ const localAgents = loadAgents({ includeTrashed: true }) as Record<string, Agent>
105
+ const localAgent = localAgents[trimmedRef] || null
106
+ if (localAgent && localAgent.provider !== 'openclaw') {
107
+ throw new Error(`Agent "${localAgent.name}" is not an OpenClaw agent`)
108
+ }
109
+
110
+ const gateway = gatewayArg ?? await ensureGatewayConnected()
111
+ if (!gateway) {
112
+ throw new Error('OpenClaw gateway not connected')
113
+ }
114
+
115
+ const result = await gateway.rpc('agents.list', {}) as OpenClawGatewayAgentsList | undefined
116
+ const gatewayAgents = Array.isArray(result?.agents) ? result.agents : []
117
+ const resolved = resolveOpenClawGatewayAgentIdFromList({
118
+ agentRef: trimmedRef,
119
+ gatewayAgents,
120
+ localAgent,
121
+ })
122
+ if (resolved) {
123
+ return resolved
124
+ }
125
+
126
+ const label = localAgent?.name?.trim() || trimmedRef
127
+ throw new Error(`OpenClaw gateway agent not found for "${label}"`)
128
+ }
@@ -7,12 +7,12 @@ const DEFAULT_CONFIG: ExecApprovalConfig = {
7
7
  patterns: [],
8
8
  }
9
9
 
10
- /** Fetch exec approval config from gateway for a given agent */
11
- export async function getExecConfig(agentId: string): Promise<ExecApprovalSnapshot> {
10
+ /** Fetch the gateway's global exec approval config. */
11
+ export async function getExecConfig(_agentId?: string): Promise<ExecApprovalSnapshot> {
12
12
  const gw = await ensureGatewayConnected()
13
13
  if (!gw) throw new Error('Gateway not connected')
14
14
 
15
- const result = await gw.rpc('exec.approvals.get', { agentId }) as ExecApprovalSnapshot | undefined
15
+ const result = await gw.rpc('exec.approvals.get', {}) as ExecApprovalSnapshot | undefined
16
16
  if (!result) {
17
17
  return { path: '', exists: false, hash: '', file: { ...DEFAULT_CONFIG } }
18
18
  }
@@ -21,7 +21,7 @@ export async function getExecConfig(agentId: string): Promise<ExecApprovalSnapsh
21
21
 
22
22
  /** Save exec approval config with hash-based conflict retry (up to 3 attempts) */
23
23
  export async function setExecConfig(
24
- agentId: string,
24
+ _agentId: string,
25
25
  config: ExecApprovalConfig,
26
26
  baseHash: string,
27
27
  ): Promise<{ ok: boolean; hash: string }> {
@@ -32,7 +32,6 @@ export async function setExecConfig(
32
32
  for (let attempt = 0; attempt < 3; attempt++) {
33
33
  try {
34
34
  const result = await gw.rpc('exec.approvals.set', {
35
- agentId,
36
35
  file: config,
37
36
  baseHash: currentHash,
38
37
  }) as { hash?: string } | undefined
@@ -41,7 +40,7 @@ export async function setExecConfig(
41
40
  const msg = err instanceof Error ? err.message : String(err)
42
41
  if (msg.includes('conflict') && attempt < 2) {
43
42
  // Re-fetch to get fresh hash
44
- const fresh = await getExecConfig(agentId)
43
+ const fresh = await getExecConfig()
45
44
  currentHash = fresh.hash
46
45
  continue
47
46
  }
@@ -0,0 +1,56 @@
1
+ import assert from 'node:assert/strict'
2
+ import { test } from 'node:test'
3
+ import { normalizeOpenClawSkillsPayload } from './openclaw-skills-normalize'
4
+
5
+ test('normalizeOpenClawSkillsPayload maps gateway skill reports into UI entries', () => {
6
+ const normalized = normalizeOpenClawSkillsPayload({
7
+ workspaceDir: '/tmp/workspace',
8
+ skills: [
9
+ {
10
+ name: 'github',
11
+ description: 'GitHub operations',
12
+ source: 'openclaw-bundled',
13
+ eligible: true,
14
+ requirements: {
15
+ bins: ['gh'],
16
+ anyBins: [['git', 'jj']],
17
+ env: ['GH_TOKEN'],
18
+ },
19
+ missing: {
20
+ config: ['channels.github'],
21
+ },
22
+ install: [
23
+ { kind: 'brew', label: 'Install GitHub CLI', bins: ['gh'] },
24
+ ],
25
+ configChecks: [
26
+ { path: 'channels.github', satisfied: false },
27
+ ],
28
+ skillKey: 'github',
29
+ baseDir: '/tmp/github',
30
+ },
31
+ ],
32
+ })
33
+
34
+ assert.equal(normalized.length, 1)
35
+ assert.deepEqual(normalized[0], {
36
+ name: 'github',
37
+ description: 'GitHub operations',
38
+ source: 'bundled',
39
+ eligible: true,
40
+ missing: ['config channels.github'],
41
+ disabled: false,
42
+ installOptions: [
43
+ { kind: 'brew', label: 'Install GitHub CLI', bins: ['gh'] },
44
+ ],
45
+ skillRequirements: {
46
+ bins: ['gh'],
47
+ anyBins: [['git', 'jj']],
48
+ env: ['GH_TOKEN'],
49
+ config: undefined,
50
+ os: undefined,
51
+ },
52
+ configChecks: [{ key: 'channels.github', ok: false }],
53
+ skillKey: 'github',
54
+ baseDir: '/tmp/github',
55
+ })
56
+ })
@@ -0,0 +1,136 @@
1
+ import type { OpenClawSkillEntry, SkillInstallOption, SkillRequirements } from '@/types'
2
+
3
+ interface GatewayConfigCheck {
4
+ path?: string
5
+ satisfied?: boolean
6
+ }
7
+
8
+ interface GatewayInstallOption {
9
+ kind?: string
10
+ label?: string
11
+ bins?: string[]
12
+ }
13
+
14
+ interface GatewaySkillRequirements {
15
+ bins?: string[]
16
+ anyBins?: string[][]
17
+ env?: string[]
18
+ config?: string[]
19
+ os?: string[]
20
+ }
21
+
22
+ interface GatewaySkillEntry {
23
+ name?: string
24
+ description?: string
25
+ source?: string
26
+ eligible?: boolean
27
+ requirements?: GatewaySkillRequirements
28
+ missing?: GatewaySkillRequirements
29
+ disabled?: boolean
30
+ install?: GatewayInstallOption[]
31
+ configChecks?: GatewayConfigCheck[]
32
+ skillKey?: string
33
+ baseDir?: string
34
+ }
35
+
36
+ interface GatewaySkillsStatusPayload {
37
+ skills?: GatewaySkillEntry[]
38
+ }
39
+
40
+ function uniq(values: Array<string | undefined | null>): string[] {
41
+ return [...new Set(values.map((value) => (value ?? '').trim()).filter(Boolean))]
42
+ }
43
+
44
+ function normalizeSource(source: string | undefined): OpenClawSkillEntry['source'] {
45
+ switch ((source ?? '').trim()) {
46
+ case 'openclaw-bundled':
47
+ case 'bundled':
48
+ return 'bundled'
49
+ case 'managed':
50
+ return 'managed'
51
+ case 'personal':
52
+ return 'personal'
53
+ case 'workspace':
54
+ return 'workspace'
55
+ default:
56
+ return 'workspace'
57
+ }
58
+ }
59
+
60
+ function normalizeInstallOptions(install: GatewayInstallOption[] | undefined): SkillInstallOption[] | undefined {
61
+ if (!Array.isArray(install) || !install.length) return undefined
62
+ const normalized = install
63
+ .map((entry) => {
64
+ const kind = (entry.kind ?? '').trim()
65
+ if (!kind || !entry.label?.trim()) return null
66
+ if (!['brew', 'node', 'go', 'uv', 'download'].includes(kind)) return null
67
+ return {
68
+ kind: kind as SkillInstallOption['kind'],
69
+ label: entry.label.trim(),
70
+ bins: Array.isArray(entry.bins) ? uniq(entry.bins) : undefined,
71
+ } satisfies SkillInstallOption
72
+ })
73
+ .filter((value): value is NonNullable<typeof value> => value !== null)
74
+ return normalized.length ? normalized : undefined
75
+ }
76
+
77
+ function normalizeRequirements(input: GatewaySkillRequirements | undefined): SkillRequirements | undefined {
78
+ if (!input || typeof input !== 'object') return undefined
79
+ const bins = Array.isArray(input.bins) ? uniq(input.bins) : undefined
80
+ const anyBins = Array.isArray(input.anyBins)
81
+ ? input.anyBins
82
+ .map((group) => Array.isArray(group) ? uniq(group) : [])
83
+ .filter((group) => group.length > 0)
84
+ : undefined
85
+ const env = Array.isArray(input.env) ? uniq(input.env) : undefined
86
+ const config = Array.isArray(input.config) ? uniq(input.config) : undefined
87
+ const os = Array.isArray(input.os) ? uniq(input.os) : undefined
88
+ if (!bins && !anyBins && !env && !config && !os) return undefined
89
+ return { bins, anyBins, env, config, os }
90
+ }
91
+
92
+ function flattenMissing(input: GatewaySkillRequirements | undefined): string[] | undefined {
93
+ if (!input || typeof input !== 'object') return undefined
94
+ const out: string[] = []
95
+ for (const value of Array.isArray(input.bins) ? uniq(input.bins) : []) out.push(value)
96
+ for (const group of Array.isArray(input.anyBins) ? input.anyBins : []) {
97
+ const normalized = Array.isArray(group) ? uniq(group) : []
98
+ if (normalized.length) out.push(`one of: ${normalized.join(' | ')}`)
99
+ }
100
+ for (const value of Array.isArray(input.env) ? uniq(input.env) : []) out.push(`env ${value}`)
101
+ for (const value of Array.isArray(input.config) ? uniq(input.config) : []) out.push(`config ${value}`)
102
+ for (const value of Array.isArray(input.os) ? uniq(input.os) : []) out.push(`os ${value}`)
103
+ return out.length ? out : undefined
104
+ }
105
+
106
+ export function normalizeOpenClawSkillsPayload(payload: unknown): OpenClawSkillEntry[] {
107
+ const rawSkills = Array.isArray(payload)
108
+ ? payload as GatewaySkillEntry[]
109
+ : Array.isArray((payload as GatewaySkillsStatusPayload | null | undefined)?.skills)
110
+ ? (payload as GatewaySkillsStatusPayload).skills!
111
+ : []
112
+
113
+ return rawSkills
114
+ .map((skill) => {
115
+ const name = skill.name?.trim()
116
+ if (!name) return null
117
+ return {
118
+ name,
119
+ description: skill.description?.trim() || undefined,
120
+ source: normalizeSource(skill.source),
121
+ eligible: skill.eligible === true,
122
+ missing: flattenMissing(skill.missing),
123
+ disabled: skill.disabled === true,
124
+ installOptions: normalizeInstallOptions(skill.install),
125
+ skillRequirements: normalizeRequirements(skill.requirements),
126
+ configChecks: Array.isArray(skill.configChecks)
127
+ ? skill.configChecks
128
+ .filter((check) => check.path?.trim())
129
+ .map((check) => ({ key: check.path!.trim(), ok: check.satisfied === true }))
130
+ : undefined,
131
+ skillKey: skill.skillKey?.trim() || undefined,
132
+ baseDir: skill.baseDir?.trim() || undefined,
133
+ } satisfies OpenClawSkillEntry
134
+ })
135
+ .filter((skill): skill is NonNullable<typeof skill> => skill !== null)
136
+ }
@@ -2,6 +2,7 @@ import fs from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import crypto from 'node:crypto'
4
4
  import { DATA_DIR } from './data-dir'
5
+ import { normalizeOpenClawAgentId } from '@/lib/openclaw-agent-id'
5
6
  import { loadSettings, loadAgents, saveAgents, loadSchedules, saveSchedules, loadCredentials, decryptKey, encryptKey } from './storage'
6
7
  import { getMemoryDb } from './memory-db'
7
8
  import type { AppSettings, MemoryEntry, Schedule } from '@/types'
@@ -182,7 +183,7 @@ export function pushAgentToOpenClaw(agentId: string): { written: string[] } {
182
183
  const agent = agents[agentId]
183
184
  if (!agent) throw new Error(`Agent not found: ${agentId}`)
184
185
 
185
- const agentDir = path.join(config.workspacePath, 'agents', agent.name.toLowerCase().replace(/\s+/g, '-'))
186
+ const agentDir = path.join(config.workspacePath, 'agents', normalizeOpenClawAgentId(agent.name))
186
187
  ensureDir(agentDir)
187
188
 
188
189
  const written: string[] = []
@@ -214,7 +215,7 @@ export function pullAgentFromOpenClaw(agentId: string): { updated: string[] } {
214
215
  const agent = agents[agentId]
215
216
  if (!agent) throw new Error(`Agent not found: ${agentId}`)
216
217
 
217
- const agentDir = path.join(config.workspacePath, 'agents', agent.name.toLowerCase().replace(/\s+/g, '-'))
218
+ const agentDir = path.join(config.workspacePath, 'agents', normalizeOpenClawAgentId(agent.name))
218
219
  const updated: string[] = []
219
220
 
220
221
  const soulPath = path.join(agentDir, 'SOUL.md')
@@ -13,6 +13,7 @@ import { notify } from './ws-hub'
13
13
  import { pushMainLoopEventToMainSessions } from './main-agent-loop'
14
14
  import { buildCurrentDateTimePromptContext } from './prompt-runtime-context'
15
15
  import { getPluginManager } from './plugins'
16
+ import './builtin-plugins'
16
17
  import { genId } from '@/lib/id'
17
18
  import { NON_LANGGRAPH_PROVIDER_IDS } from '@/lib/provider-sets'
18
19
  import type { Agent, TaskComment, MessageToolEvent } from '@/types'
@@ -118,10 +119,10 @@ async function executeSubTaskViaCli(agent: Agent, task: string, parentSessionId:
118
119
  messages: [],
119
120
  createdAt: Date.now(),
120
121
  lastActiveAt: Date.now(),
121
- sessionType: 'orchestrated' as const,
122
+ sessionType: 'human' as const,
122
123
  agentId: agent.id,
123
124
  parentSessionId,
124
- tools: agent.tools || [],
125
+ plugins: agent.plugins || agent.tools || [],
125
126
  }
126
127
  ss(sessions)
127
128
 
@@ -156,9 +157,9 @@ export async function executeLangGraphOrchestrator(
156
157
  const agents = agentIds.map((id) => allAgents[id]).filter(Boolean) as Agent[]
157
158
  const agentListContext = agents.length
158
159
  ? '\n\nAvailable agents:\n' + agents.map((a) => {
159
- const tools = a.tools?.length ? ` [tools: ${a.tools.join(', ')}]` : ''
160
+ const plugins = (a.plugins || a.tools)?.length ? ` [plugins: ${(a.plugins || a.tools)!.join(', ')}]` : ''
160
161
  const skills = a.skills?.length ? ` [skills: ${a.skills.join(', ')}]` : ''
161
- return `- ${a.name}: ${a.description}${tools}${skills}`
162
+ return `- ${a.name}: ${a.description}${plugins}${skills}`
162
163
  }).join('\n')
163
164
  : '\n\n(No agents available for delegation.)'
164
165
 
@@ -177,7 +178,11 @@ export async function executeLangGraphOrchestrator(
177
178
  return `Agent "${agentName}" not found. Available: ${agents.map((a) => a.name).join(', ')}`
178
179
  }
179
180
  console.log(`[orchestrator-lg] Delegating to ${agent.name}: ${agentTask.slice(0, 80)}`)
180
- getPluginManager().runHook('onAgentDelegation', { sourceAgentId: orchestrator.id, targetAgentId: agent.id, task: agentTask })
181
+ getPluginManager().runHook(
182
+ 'onAgentDelegation',
183
+ { sourceAgentId: orchestrator.id, targetAgentId: agent.id, task: agentTask },
184
+ { enabledIds: orchestrator.plugins || [] },
185
+ )
181
186
  const result = await executeSubTaskViaCli(agent, agentTask, sessionId)
182
187
  saveMessage(sessionId, 'assistant', `Delegated to ${agent.name}: ${agentTask.slice(0, 100)}`, [{
183
188
  name: 'delegate_to_agent',
@@ -393,6 +398,7 @@ export async function executeLangGraphOrchestrator(
393
398
 
394
399
  const checkpointSaver = getCheckpointSaver()
395
400
  const isStrictMode = settings.capabilityPolicyMode === 'strict'
401
+ const approvalInterruptsEnabled = isStrictMode && settings.approvalsEnabled !== false
396
402
  const allTools = [delegateTool, storeMemoryTool, searchMemoryTool, getSecretTool, commentOnTaskTool, createTaskTool, markCompleteTool]
397
403
  const llmWithTools = llm.bindTools(allTools)
398
404
  const toolNode = new ToolNode(allTools)
@@ -472,7 +478,7 @@ export async function executeLangGraphOrchestrator(
472
478
 
473
479
  const compiledGraph = graph.compile({
474
480
  checkpointer: checkpointSaver,
475
- ...(isStrictMode ? { interruptBefore: ['tools'] } : {}),
481
+ ...(approvalInterruptsEnabled ? { interruptBefore: ['tools'] } : {}),
476
482
  })
477
483
 
478
484
  // Export graph structure for introspection
@@ -534,7 +540,7 @@ export async function executeLangGraphOrchestrator(
534
540
  }
535
541
 
536
542
  // Check for interrupt (paused before tool execution in strict mode)
537
- if (isStrictMode && taskId) {
543
+ if (approvalInterruptsEnabled && taskId) {
538
544
  const state = await compiledGraph.getState({ configurable: { thread_id: threadId } })
539
545
  const nextNodes = state?.next || []
540
546
  if (nextNodes.includes('tools')) {
@@ -628,7 +634,11 @@ export async function resumeLangGraphOrchestrator(
628
634
  async ({ agentName, task: agentTask }) => {
629
635
  const agent = agents.find((a) => a.name.toLowerCase() === agentName.toLowerCase())
630
636
  if (!agent) return `Agent "${agentName}" not found. Available: ${agents.map((a) => a.name).join(', ')}`
631
- getPluginManager().runHook('onAgentDelegation', { sourceAgentId: orchestrator.id, targetAgentId: agent.id, task: agentTask })
637
+ getPluginManager().runHook(
638
+ 'onAgentDelegation',
639
+ { sourceAgentId: orchestrator.id, targetAgentId: agent.id, task: agentTask },
640
+ { enabledIds: orchestrator.plugins || [] },
641
+ )
632
642
  const result = await executeSubTaskViaCli(agent, agentTask, sessionId)
633
643
  saveMessage(sessionId, 'assistant', `Delegated to ${agent.name}: ${agentTask.slice(0, 100)}`, [{
634
644
  name: 'delegate_to_agent',
@@ -753,6 +763,7 @@ export async function resumeLangGraphOrchestrator(
753
763
  const checkpointSaver = getCheckpointSaver()
754
764
  const settings = loadSettings()
755
765
  const isStrictMode = settings.capabilityPolicyMode === 'strict'
766
+ const approvalInterruptsEnabled = isStrictMode && settings.approvalsEnabled !== false
756
767
 
757
768
  const allTools = [delegateTool, storeMemoryTool, searchMemoryTool, getSecretTool, commentOnTaskTool, createTaskTool, markCompleteTool]
758
769
  const llmWithTools = llm.bindTools(allTools)
@@ -782,7 +793,7 @@ export async function resumeLangGraphOrchestrator(
782
793
  .addEdge('router', 'agent')
783
794
  .compile({
784
795
  checkpointer: checkpointSaver,
785
- ...(isStrictMode ? { interruptBefore: ['tools'] } : {}),
796
+ ...(approvalInterruptsEnabled ? { interruptBefore: ['tools'] } : {}),
786
797
  })
787
798
 
788
799
  let finalResult = ''