@swarmclawai/swarmclaw 0.7.1 → 0.7.2

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 (119) hide show
  1. package/README.md +85 -139
  2. package/package.json +1 -1
  3. package/src/app/api/agents/[id]/thread/route.ts +1 -2
  4. package/src/app/api/agents/route.ts +1 -1
  5. package/src/app/api/{sessions → chats}/[id]/checkpoints/route.ts +1 -1
  6. package/src/app/api/{sessions → chats}/[id]/main-loop/route.ts +2 -2
  7. package/src/app/api/{sessions → chats}/[id]/restore/route.ts +1 -1
  8. package/src/app/api/{sessions → chats}/[id]/route.ts +4 -52
  9. package/src/app/api/{sessions → chats}/route.ts +5 -7
  10. package/src/app/api/plugins/route.ts +3 -0
  11. package/src/app/api/plugins/settings/route.ts +35 -0
  12. package/src/app/api/usage/route.ts +30 -0
  13. package/src/cli/index.js +35 -33
  14. package/src/cli/index.ts +40 -39
  15. package/src/cli/spec.js +29 -27
  16. package/src/components/agents/agent-card.tsx +1 -1
  17. package/src/components/agents/agent-chat-list.tsx +3 -3
  18. package/src/components/agents/agent-list.tsx +8 -13
  19. package/src/components/agents/agent-sheet.tsx +2 -2
  20. package/src/components/agents/cron-job-form.tsx +3 -3
  21. package/src/components/agents/inspector-panel.tsx +2 -2
  22. package/src/components/auth/setup-wizard.tsx +5 -38
  23. package/src/components/chat/chat-area.tsx +10 -14
  24. package/src/components/{sessions/session-card.tsx → chat/chat-card.tsx} +3 -3
  25. package/src/components/chat/chat-header.tsx +156 -73
  26. package/src/components/{sessions/session-list.tsx → chat/chat-list.tsx} +4 -5
  27. package/src/components/chat/chat-tool-toggles.tsx +26 -17
  28. package/src/components/chat/checkpoint-timeline.tsx +4 -4
  29. package/src/components/chat/message-bubble.tsx +4 -1
  30. package/src/components/chat/message-list.tsx +2 -2
  31. package/src/components/{sessions/new-session-sheet.tsx → chat/new-chat-sheet.tsx} +6 -6
  32. package/src/components/chat/session-debug-panel.tsx +1 -1
  33. package/src/components/chat/tool-request-banner.tsx +3 -3
  34. package/src/components/chatrooms/agent-hover-card.tsx +3 -3
  35. package/src/components/chatrooms/chatroom-tool-request-banner.tsx +2 -2
  36. package/src/components/connectors/connector-sheet.tsx +1 -1
  37. package/src/components/home/home-view.tsx +1 -1
  38. package/src/components/layout/app-layout.tsx +23 -2
  39. package/src/components/plugins/plugin-list.tsx +475 -254
  40. package/src/components/plugins/plugin-sheet.tsx +124 -10
  41. package/src/components/settings/gateway-connection-panel.tsx +1 -1
  42. package/src/components/shared/command-palette.tsx +0 -1
  43. package/src/components/shared/settings/section-heartbeat.tsx +1 -1
  44. package/src/components/shared/settings/section-providers.tsx +1 -1
  45. package/src/components/shared/settings/settings-page.tsx +1 -12
  46. package/src/components/usage/metrics-dashboard.tsx +73 -0
  47. package/src/components/webhooks/webhook-sheet.tsx +1 -1
  48. package/src/lib/chat.ts +1 -1
  49. package/src/lib/{sessions.ts → chats.ts} +28 -18
  50. package/src/lib/providers/claude-cli.ts +1 -1
  51. package/src/lib/server/approvals.ts +4 -4
  52. package/src/lib/server/capability-router.ts +10 -8
  53. package/src/lib/server/chat-execution.ts +36 -105
  54. package/src/lib/server/chatroom-helpers.ts +3 -3
  55. package/src/lib/server/connectors/manager.ts +4 -4
  56. package/src/lib/server/cost.ts +34 -1
  57. package/src/lib/server/daemon-state.ts +2 -2
  58. package/src/lib/server/heartbeat-service.ts +1 -1
  59. package/src/lib/server/main-agent-loop.ts +25 -160
  60. package/src/lib/server/main-session.ts +6 -13
  61. package/src/lib/server/orchestrator-lg.ts +3 -3
  62. package/src/lib/server/orchestrator.ts +5 -5
  63. package/src/lib/server/plugins.ts +112 -4
  64. package/src/lib/server/provider-health.ts +5 -3
  65. package/src/lib/server/queue.ts +12 -10
  66. package/src/lib/server/session-run-manager.test.ts +9 -6
  67. package/src/lib/server/session-run-manager.ts +1 -3
  68. package/src/lib/server/session-tools/calendar.ts +376 -0
  69. package/src/lib/server/session-tools/canvas.ts +1 -1
  70. package/src/lib/server/session-tools/chatroom.ts +4 -2
  71. package/src/lib/server/session-tools/connector.ts +5 -2
  72. package/src/lib/server/session-tools/context.ts +7 -3
  73. package/src/lib/server/session-tools/crud.ts +14 -6
  74. package/src/lib/server/session-tools/delegate.ts +95 -8
  75. package/src/lib/server/session-tools/discovery.ts +2 -2
  76. package/src/lib/server/session-tools/edit_file.ts +4 -2
  77. package/src/lib/server/session-tools/email.ts +322 -0
  78. package/src/lib/server/session-tools/file.ts +5 -2
  79. package/src/lib/server/session-tools/git.ts +1 -1
  80. package/src/lib/server/session-tools/http.ts +1 -1
  81. package/src/lib/server/session-tools/image-gen.ts +382 -0
  82. package/src/lib/server/session-tools/index.ts +74 -49
  83. package/src/lib/server/session-tools/memory.ts +139 -2
  84. package/src/lib/server/session-tools/monitor.ts +1 -1
  85. package/src/lib/server/session-tools/openclaw-nodes.ts +1 -1
  86. package/src/lib/server/session-tools/openclaw-workspace.ts +1 -1
  87. package/src/lib/server/session-tools/platform.ts +6 -3
  88. package/src/lib/server/session-tools/plugin-creator.ts +3 -3
  89. package/src/lib/server/session-tools/replicate.ts +303 -0
  90. package/src/lib/server/session-tools/sample-ui.ts +1 -1
  91. package/src/lib/server/session-tools/sandbox.ts +4 -2
  92. package/src/lib/server/session-tools/schedule.ts +4 -2
  93. package/src/lib/server/session-tools/session-info.ts +7 -4
  94. package/src/lib/server/session-tools/shell.ts +5 -2
  95. package/src/lib/server/session-tools/subagent.ts +2 -2
  96. package/src/lib/server/session-tools/wallet.ts +29 -2
  97. package/src/lib/server/session-tools/web.ts +44 -5
  98. package/src/lib/server/storage.ts +29 -9
  99. package/src/lib/server/stream-agent-chat.ts +72 -249
  100. package/src/lib/server/tool-aliases.ts +26 -15
  101. package/src/lib/server/tool-capability-policy.test.ts +9 -9
  102. package/src/lib/server/tool-capability-policy.ts +32 -27
  103. package/src/lib/tool-definitions.ts +4 -0
  104. package/src/lib/validation/schemas.ts +3 -1
  105. package/src/stores/use-app-store.ts +5 -5
  106. package/src/stores/use-chat-store.ts +7 -7
  107. package/src/types/index.ts +65 -3
  108. /package/src/app/api/{sessions → chats}/[id]/browser/route.ts +0 -0
  109. /package/src/app/api/{sessions → chats}/[id]/chat/route.ts +0 -0
  110. /package/src/app/api/{sessions → chats}/[id]/clear/route.ts +0 -0
  111. /package/src/app/api/{sessions → chats}/[id]/deploy/route.ts +0 -0
  112. /package/src/app/api/{sessions → chats}/[id]/devserver/route.ts +0 -0
  113. /package/src/app/api/{sessions → chats}/[id]/edit-resend/route.ts +0 -0
  114. /package/src/app/api/{sessions → chats}/[id]/fork/route.ts +0 -0
  115. /package/src/app/api/{sessions → chats}/[id]/mailbox/route.ts +0 -0
  116. /package/src/app/api/{sessions → chats}/[id]/messages/route.ts +0 -0
  117. /package/src/app/api/{sessions → chats}/[id]/retry/route.ts +0 -0
  118. /package/src/app/api/{sessions → chats}/[id]/stop/route.ts +0 -0
  119. /package/src/app/api/{sessions → chats}/heartbeat/route.ts +0 -0
@@ -135,7 +135,34 @@ async function executeWalletAction(args: any, context: { agentId?: string | null
135
135
  const WalletPlugin: Plugin = {
136
136
  name: 'Core Wallet',
137
137
  description: 'Manage agent crypto wallet: check balance, send SOL, view transactions.',
138
- hooks: {} as PluginHooks,
138
+ hooks: {
139
+ getAgentContext: async (ctx) => {
140
+ const agentId = ctx.session.agentId
141
+ if (!agentId) return null
142
+ const { loadWallets } = await import('../storage')
143
+ const wallets = loadWallets() as Record<string, { agentId: string; publicKey: string; chain: string; spendingLimitLamports?: number; dailyLimitLamports?: number; requireApproval: boolean }>
144
+ const wallet = Object.values(wallets).find((w) => w.agentId === agentId)
145
+ if (!wallet) return null
146
+ const { getBalance, lamportsToSol } = await import('../solana')
147
+ let balanceSol = 0
148
+ try {
149
+ const lamports = await getBalance(wallet.publicKey)
150
+ balanceSol = lamportsToSol(lamports)
151
+ } catch { /* RPC failure — show 0 */ }
152
+ const perTxLimit = lamportsToSol(wallet.spendingLimitLamports ?? 100_000_000)
153
+ const dailyLimit = lamportsToSol(wallet.dailyLimitLamports ?? 1_000_000_000)
154
+ return [
155
+ '## Your Wallet',
156
+ `You own a ${wallet.chain} wallet — this is your wallet, your funds, your address. Speak about it in the first person ("my wallet", "my balance", "I have X SOL"). You can spend from it freely within your limits${wallet.requireApproval ? ', though the user needs to approve each send before it goes through' : ''}.`,
157
+ `- Address: ${wallet.publicKey}`,
158
+ `- Balance: ${balanceSol} SOL`,
159
+ `- Per-transaction limit: ${perTxLimit} SOL`,
160
+ `- Daily limit: ${dailyLimit} SOL`,
161
+ 'Use the `wallet_tool` to check your balance, send SOL, or view your transaction history.',
162
+ ].join('\n')
163
+ },
164
+ getCapabilityDescription: () => 'I have my own crypto wallet (`wallet_tool`) — I can check my balance, send SOL, and review my transaction history.',
165
+ } as PluginHooks,
139
166
  ui: {
140
167
  sidebarItems: [
141
168
  {
@@ -179,7 +206,7 @@ getPluginManager().registerBuiltin('wallet', WalletPlugin)
179
206
  * Legacy Bridge
180
207
  */
181
208
  export function buildWalletTools(bctx: ToolBuildContext): StructuredToolInterface[] {
182
- if (!bctx.hasTool('wallet')) return []
209
+ if (!bctx.hasPlugin('wallet')) return []
183
210
  return [
184
211
  tool(
185
212
  async (args) => executeWalletAction(args, { agentId: bctx.ctx?.agentId }),
@@ -133,7 +133,9 @@ async function executeWebAction(args: Record<string, unknown>, bctx: any) {
133
133
  const WebPlugin: Plugin = {
134
134
  name: 'Core Web',
135
135
  description: 'Search the web and fetch content from URLs.',
136
- hooks: {} as PluginHooks,
136
+ hooks: {
137
+ getCapabilityDescription: () => 'I can search the web (`web_search`) for research, fact-checking, and discovery.',
138
+ } as PluginHooks,
137
139
  tools: [
138
140
  {
139
141
  name: 'web',
@@ -162,7 +164,7 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
162
164
  const tools: StructuredToolInterface[] = []
163
165
  const { cwd, ctx, cleanupFns } = bctx
164
166
 
165
- if (bctx.hasTool('web')) {
167
+ if (bctx.hasPlugin('web')) {
166
168
  tools.push(
167
169
  tool(
168
170
  async (args) => executeWebAction(args, bctx),
@@ -176,7 +178,7 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
176
178
  }
177
179
 
178
180
  // Browser tool (kept as direct injection for now due to complexity)
179
- if (bctx.hasTool('browser')) {
181
+ if (bctx.hasPlugin('browser')) {
180
182
  const sessionKey = ctx?.sessionId || `anon-${Date.now()}`
181
183
  let mcpClient: any = null
182
184
  let mcpServer: any = null
@@ -309,9 +311,46 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
309
311
  const { action, ...rest } = params
310
312
  const mcpTool = MCP_TOOL_MAP[action]
311
313
  if (!mcpTool) return `Unknown browser action: "${action}"`
314
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
312
315
  const args: Record<string, any> = {}
313
316
  for (const [k, v] of Object.entries(rest)) { if (v !== undefined && v !== null && v !== '') args[k] = v }
314
- const result = await callMcpTool(mcpTool, args, { saveTo: params.saveTo })
317
+
318
+ // If screenshot includes a url, navigate first then capture
319
+ if (action === 'screenshot' && args.url) {
320
+ const navUrl = args.url
321
+ delete args.url
322
+ await callMcpTool('browser_navigate', { url: navUrl })
323
+ try { await dismissCookieBanners(callMcpTool) } catch { /* ignore */ }
324
+ }
325
+
326
+ // Wait for the page to finish rendering before capturing
327
+ if (action === 'screenshot') {
328
+ try {
329
+ await callMcpTool('browser_evaluate', {
330
+ expression: `await new Promise(resolve => {
331
+ if (document.readyState === 'complete') {
332
+ setTimeout(resolve, 1500);
333
+ } else {
334
+ window.addEventListener('load', () => setTimeout(resolve, 1500), { once: true });
335
+ setTimeout(resolve, 5000);
336
+ }
337
+ })`,
338
+ })
339
+ } catch { /* page may not support evaluate — fall back to a flat delay */
340
+ await new Promise((r) => setTimeout(r, 2000))
341
+ }
342
+ }
343
+
344
+ let result = await callMcpTool(mcpTool, args, { saveTo: params.saveTo })
345
+
346
+ // Playwright throws ERR_ABORTED on server-side redirects (e.g. Wikipedia Special:Random).
347
+ // The browser follows the redirect fine — the original navigation just gets "aborted".
348
+ // Recover by taking a snapshot of the page the browser actually landed on.
349
+ if (action === 'navigate' && result.includes('ERR_ABORTED')) {
350
+ await new Promise((r) => setTimeout(r, 1000))
351
+ result = await callMcpTool('browser_snapshot', {})
352
+ }
353
+
315
354
  if (action === 'navigate') { try { await dismissCookieBanners(callMcpTool) } catch { /* ignore */ } }
316
355
  return result
317
356
  } catch (err: unknown) { return `Error: ${err instanceof Error ? err.message : String(err)}` }
@@ -332,7 +371,7 @@ export function buildWebTools(bctx: ToolBuildContext): StructuredToolInterface[]
332
371
 
333
372
  // openclaw_browser CLI passthrough
334
373
  const openclawPath = findBinaryOnPath('openclaw') || findBinaryOnPath('clawdbot')
335
- if (openclawPath && (bctx.hasTool('browser') || bctx.hasTool('openclaw_browser'))) {
374
+ if (openclawPath && (bctx.hasPlugin('browser') || bctx.hasPlugin('openclaw_browser'))) {
336
375
  tools.push(
337
376
  tool(
338
377
  async (rawArgs) => {
@@ -6,7 +6,6 @@ import Database from 'better-sqlite3'
6
6
 
7
7
  import { DATA_DIR, WORKSPACE_DIR } from './data-dir'
8
8
  import type { Message } from '@/types'
9
- import { ensureMainSessionFlag } from './main-session'
10
9
  export const UPLOAD_DIR = path.join(DATA_DIR, 'uploads')
11
10
 
12
11
  // --- LRU Cache ---
@@ -421,7 +420,7 @@ You have opinions about good agent design. You suggest creative approaches, warn
421
420
 
422
421
  Be concise but not curt. Warmth doesn't require verbosity. When someone asks "how do I...?", give them the direct steps. Offer to do things rather than just explaining — if someone wants an agent created, create it. Use your tools when actions speak louder than words. If you don't know something, say so honestly.`,
423
422
  isOrchestrator: false,
424
- tools: defaultStarterTools,
423
+ plugins: defaultStarterTools,
425
424
  heartbeatEnabled: true,
426
425
  platformAssignScope: 'all',
427
426
  skillIds: [],
@@ -435,10 +434,11 @@ Be concise but not curt. Warmth doesn't require verbosity. When someone asks "ho
435
434
  if (row?.data) {
436
435
  try {
437
436
  const existing = JSON.parse(row.data) as Record<string, unknown>
438
- const existingTools = Array.isArray(existing.tools) ? existing.tools : []
439
- const mergedTools = Array.from(new Set([...existingTools, ...defaultStarterTools])).filter((t) => t !== 'delete_file')
440
- if (JSON.stringify(existingTools) !== JSON.stringify(mergedTools)) {
441
- existing.tools = mergedTools
437
+ const existingPlugins = Array.isArray(existing.plugins) ? existing.plugins : Array.isArray(existing.tools) ? existing.tools : []
438
+ const mergedPlugins = Array.from(new Set([...existingPlugins, ...defaultStarterTools])).filter((t) => t !== 'delete_file')
439
+ if (JSON.stringify(existingPlugins) !== JSON.stringify(mergedPlugins)) {
440
+ existing.plugins = mergedPlugins
441
+ delete existing.tools
442
442
  existing.updatedAt = Date.now()
443
443
  db.prepare('UPDATE agents SET data = ? WHERE id = ?').run(JSON.stringify(existing), 'default')
444
444
  }
@@ -516,15 +516,19 @@ export function loadSessions(): Record<string, any> {
516
516
  changed = true
517
517
  }
518
518
 
519
- const beforeMainFlag = session.mainSession === true
520
- ensureMainSessionFlag(session)
521
- if (!beforeMainFlag && session.mainSession === true) changed = true
522
519
 
523
520
  const agentId = typeof session.agentId === 'string' ? session.agentId.trim() : ''
524
521
  if (agentId && !Object.prototype.hasOwnProperty.call(agents, agentId)) {
525
522
  session.agentId = null
526
523
  changed = true
527
524
  }
525
+
526
+ // Migrate tools → plugins
527
+ if (Array.isArray(session.tools) && !Array.isArray(session.plugins)) {
528
+ session.plugins = session.tools
529
+ delete session.tools
530
+ changed = true
531
+ }
528
532
  }
529
533
 
530
534
  if (changed) saveCollection('sessions', sessions)
@@ -596,8 +600,24 @@ export function decryptKey(encrypted: string): string {
596
600
  }
597
601
 
598
602
  // --- Agents ---
603
+
604
+ /** Migrate legacy `tools` field → `plugins` on agent objects. */
605
+ function migrateAgentPlugins(agents: Record<string, Record<string, unknown>>): boolean {
606
+ let changed = false
607
+ for (const agent of Object.values(agents)) {
608
+ if (!agent || typeof agent !== 'object') continue
609
+ if (Array.isArray(agent.tools) && !Array.isArray(agent.plugins)) {
610
+ agent.plugins = agent.tools
611
+ delete agent.tools
612
+ changed = true
613
+ }
614
+ }
615
+ return changed
616
+ }
617
+
599
618
  export function loadAgents(opts?: { includeTrashed?: boolean }): Record<string, any> {
600
619
  const all = loadCollection('agents')
620
+ if (migrateAgentPlugins(all)) saveCollection('agents', all)
601
621
  if (opts?.includeTrashed) return all
602
622
  const result: Record<string, any> = {}
603
623
  for (const [id, agent] of Object.entries(all)) {