@swarmclawai/swarmclaw 1.3.6 → 1.4.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 (96) hide show
  1. package/README.md +32 -1
  2. package/package.json +9 -3
  3. package/src/.env.local +4 -0
  4. package/src/app/api/.well-known/agent-card/route.ts +46 -0
  5. package/src/app/api/a2a/route.ts +56 -0
  6. package/src/app/api/a2a/tasks/[taskId]/status/route.ts +49 -0
  7. package/src/app/api/chats/[id]/deploy/route.ts +2 -2
  8. package/src/app/api/openclaw/sync/route.ts +1 -1
  9. package/src/app/api/swarmfeed/channels/route.ts +14 -0
  10. package/src/app/api/swarmfeed/posts/route.ts +60 -0
  11. package/src/app/api/swarmfeed/route.ts +37 -0
  12. package/src/app/protocols/builder/[templateId]/page.tsx +93 -0
  13. package/src/app/protocols/page.tsx +16 -7
  14. package/src/app/swarmfeed/page.tsx +7 -0
  15. package/src/cli/index.js +19 -0
  16. package/src/cli/spec.js +8 -0
  17. package/src/components/agents/agent-avatar.tsx +2 -5
  18. package/src/components/agents/agent-sheet.tsx +10 -0
  19. package/src/components/auth/access-key-gate.tsx +25 -0
  20. package/src/components/layout/sidebar-rail.tsx +52 -0
  21. package/src/components/protocols/builder/edge-editor.tsx +43 -0
  22. package/src/components/protocols/builder/edge-types/branch-edge.tsx +33 -0
  23. package/src/components/protocols/builder/edge-types/default-edge.tsx +18 -0
  24. package/src/components/protocols/builder/edge-types/index.ts +3 -0
  25. package/src/components/protocols/builder/edge-types/loop-edge.tsx +19 -0
  26. package/src/components/protocols/builder/node-inspector.tsx +227 -0
  27. package/src/components/protocols/builder/node-palette.tsx +97 -0
  28. package/src/components/protocols/builder/node-types/branch-node.tsx +34 -0
  29. package/src/components/protocols/builder/node-types/complete-node.tsx +17 -0
  30. package/src/components/protocols/builder/node-types/for-each-node.tsx +21 -0
  31. package/src/components/protocols/builder/node-types/index.ts +9 -0
  32. package/src/components/protocols/builder/node-types/join-node.tsx +18 -0
  33. package/src/components/protocols/builder/node-types/loop-node.tsx +22 -0
  34. package/src/components/protocols/builder/node-types/parallel-node.tsx +31 -0
  35. package/src/components/protocols/builder/node-types/phase-node.tsx +52 -0
  36. package/src/components/protocols/builder/node-types/subflow-node.tsx +23 -0
  37. package/src/components/protocols/builder/node-types/swarm-node.tsx +26 -0
  38. package/src/components/protocols/builder/protocol-builder-canvas.tsx +184 -0
  39. package/src/components/protocols/builder/run-overlay.tsx +29 -0
  40. package/src/components/protocols/builder/template-gallery.tsx +53 -0
  41. package/src/components/protocols/builder/validation-panel.tsx +57 -0
  42. package/src/components/skills/skills-workspace.tsx +1 -9
  43. package/src/features/protocols/builder/hooks/index.ts +2 -0
  44. package/src/features/protocols/builder/hooks/use-canvas-validation.ts +14 -0
  45. package/src/features/protocols/builder/hooks/use-run-overlay.ts +39 -0
  46. package/src/features/protocols/builder/hooks/use-template-sync.ts +45 -0
  47. package/src/features/protocols/builder/protocol-builder-store.ts +233 -0
  48. package/src/features/protocols/builder/utils/node-position-layout.ts +41 -0
  49. package/src/features/protocols/builder/utils/nodes-to-template.test.ts +179 -0
  50. package/src/features/protocols/builder/utils/nodes-to-template.ts +49 -0
  51. package/src/features/protocols/builder/utils/template-to-nodes.test.ts +314 -0
  52. package/src/features/protocols/builder/utils/template-to-nodes.ts +169 -0
  53. package/src/features/protocols/builder/validators/dag-validator.test.ts +150 -0
  54. package/src/features/protocols/builder/validators/dag-validator.ts +119 -0
  55. package/src/features/swarmfeed/agent-social-settings.tsx +277 -0
  56. package/src/features/swarmfeed/compose-post.tsx +139 -0
  57. package/src/features/swarmfeed/feed-page.tsx +136 -0
  58. package/src/features/swarmfeed/post-card.tsx +114 -0
  59. package/src/features/swarmfeed/queries.ts +28 -0
  60. package/src/lib/a2a/agent-card.ts +61 -0
  61. package/src/lib/a2a/auth.ts +54 -0
  62. package/src/lib/a2a/client.ts +133 -0
  63. package/src/lib/a2a/discovery.ts +116 -0
  64. package/src/lib/a2a/handlers.ts +176 -0
  65. package/src/lib/a2a/json-rpc-router.ts +38 -0
  66. package/src/lib/a2a/types.ts +95 -0
  67. package/src/lib/app/navigation.ts +1 -0
  68. package/src/lib/app/view-constants.ts +9 -1
  69. package/src/lib/providers/anthropic.ts +111 -107
  70. package/src/lib/providers/openai.ts +146 -142
  71. package/src/lib/server/agents/main-agent-loop.test.ts +94 -0
  72. package/src/lib/server/agents/main-agent-loop.ts +377 -41
  73. package/src/lib/server/chat-execution/chat-execution.ts +12 -7
  74. package/src/lib/server/extensions.ts +11 -0
  75. package/src/lib/server/openclaw/sync.ts +4 -4
  76. package/src/lib/server/protocols/protocol-a2a-delegate.ts +135 -0
  77. package/src/lib/server/protocols/protocol-normalization.ts +1 -0
  78. package/src/lib/server/protocols/protocol-step-helpers.test.ts +1 -1
  79. package/src/lib/server/protocols/protocol-step-helpers.ts +1 -0
  80. package/src/lib/server/protocols/protocol-step-processors.ts +2 -0
  81. package/src/lib/server/protocols/protocol-types.ts +1 -0
  82. package/src/lib/server/session-tools/delegate.ts +151 -77
  83. package/src/lib/server/storage-auth.ts +10 -2
  84. package/src/lib/server/storage-normalization.ts +11 -0
  85. package/src/lib/server/storage.ts +100 -0
  86. package/src/lib/server/working-state/service.test.ts +2 -3
  87. package/src/lib/server/working-state/service.ts +37 -6
  88. package/src/lib/swarmfeed-client.ts +157 -0
  89. package/src/lib/validation/schemas.ts +1 -1
  90. package/src/stores/slices/data-slice.ts +3 -0
  91. package/src/stores/use-approval-store.ts +4 -1
  92. package/src/types/agent.ts +31 -1
  93. package/src/types/index.ts +1 -0
  94. package/src/types/protocol.ts +19 -0
  95. package/src/types/session.ts +1 -1
  96. package/src/types/swarmfeed.ts +30 -0
@@ -874,6 +874,17 @@ class ExtensionManager {
874
874
  try {
875
875
  const parsed = JSON.parse(fs.readFileSync(EXTENSION_FAILURES, 'utf8')) as Record<string, ExtensionFailureRecord>
876
876
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return {}
877
+ // Prune records older than 7 days
878
+ const maxAgeMs = 7 * 24 * 60 * 60 * 1000
879
+ const now = Date.now()
880
+ let pruned = false
881
+ for (const key of Object.keys(parsed)) {
882
+ if (now - (parsed[key].lastFailedAt || 0) > maxAgeMs) {
883
+ delete parsed[key]
884
+ pruned = true
885
+ }
886
+ }
887
+ if (pruned) this.writeFailureState(parsed)
877
888
  return parsed
878
889
  } catch {
879
890
  return {}
@@ -381,7 +381,7 @@ export function syncExtensionsFromOpenClaw(): { imported: number } {
381
381
  const openclawExtensionDir = path.join(config.workspacePath, 'plugins')
382
382
  if (!fs.existsSync(openclawExtensionDir)) return { imported: 0 }
383
383
 
384
- const localExtensionDir = path.join(DATA_DIR, 'plugins')
384
+ const localExtensionDir = path.join(DATA_DIR, 'extensions')
385
385
  ensureDir(localExtensionDir)
386
386
 
387
387
  const files = fs.readdirSync(openclawExtensionDir).filter((f) => f.endsWith('.js'))
@@ -432,7 +432,7 @@ export function setSharedDeviceToken(token: string): void {
432
432
 
433
433
  // --- Unified Sync Entry Point ---
434
434
 
435
- export type SyncType = 'memory' | 'workspace' | 'schedules' | 'credentials' | 'plugins'
435
+ export type SyncType = 'memory' | 'workspace' | 'schedules' | 'credentials' | 'extensions'
436
436
 
437
437
  export interface SyncResult {
438
438
  type: SyncType
@@ -467,7 +467,7 @@ export async function runSync(params: {
467
467
  case 'credentials':
468
468
  results.push({ type, action: 'push', result: pushCredentialsToOpenClaw() })
469
469
  break
470
- case 'plugins':
470
+ case 'extensions':
471
471
  // Extensions only pull from OpenClaw
472
472
  break
473
473
  }
@@ -492,7 +492,7 @@ export async function runSync(params: {
492
492
  case 'credentials':
493
493
  results.push({ type, action: 'pull', result: await pullCredentialsFromOpenClaw() })
494
494
  break
495
- case 'plugins':
495
+ case 'extensions':
496
496
  results.push({ type, action: 'pull', result: syncExtensionsFromOpenClaw() })
497
497
  break
498
498
  }
@@ -0,0 +1,135 @@
1
+ import { genId } from '@/lib/id'
2
+ import { log } from '@/lib/server/logger'
3
+ import { errorMessage } from '@/lib/shared-utils'
4
+ import { upsertTask } from '@/lib/server/tasks/task-repository'
5
+ import { notify } from '@/lib/server/ws-hub'
6
+ import { callA2AAgent } from '@/lib/a2a/client'
7
+ import { loadExternalAgents } from '@/lib/server/storage'
8
+ import { appendProtocolEvent, persistRun } from '@/lib/server/protocols/protocol-agent-turn'
9
+ import { now } from '@/lib/server/protocols/protocol-types'
10
+ import type { ProtocolRunDeps } from '@/lib/server/protocols/protocol-types'
11
+ import type { ProtocolPhaseDefinition, ProtocolRun, ProtocolRunPhaseState } from '@/types/protocol'
12
+ import type { BoardTask } from '@/types/task'
13
+
14
+ const TAG = 'protocol-a2a-delegate'
15
+
16
+ /**
17
+ * Process an a2a_delegate phase: call a remote A2A agent and wait for the result.
18
+ *
19
+ * Follows the same pattern as processDispatchDelegationPhase:
20
+ * 1. Create a BoardTask for tracking (with protocolRunId so wakeProtocolRunFromTaskCompletion fires)
21
+ * 2. Call the remote agent via HTTP
22
+ * 3. Set protocol run to 'waiting'
23
+ * 4. When the HTTP call completes, update the task → wake machinery resumes the run
24
+ */
25
+ export function processA2ADelegatePhase(
26
+ run: ProtocolRun,
27
+ phase: ProtocolPhaseDefinition,
28
+ deps?: ProtocolRunDeps,
29
+ ): ProtocolRun {
30
+ const config = phase.a2aDelegateConfig
31
+ if (!config?.taskName || !config?.taskMessage) {
32
+ appendProtocolEvent(run.id, {
33
+ type: 'failed',
34
+ phaseId: phase.id,
35
+ summary: `a2a_delegate phase "${phase.label}" missing a2aDelegateConfig`,
36
+ }, deps)
37
+ return persistRun({
38
+ ...run,
39
+ status: 'failed',
40
+ lastError: `a2a_delegate phase "${phase.label}" missing a2aDelegateConfig`,
41
+ endedAt: run.endedAt || now(deps),
42
+ updatedAt: now(deps),
43
+ })
44
+ }
45
+
46
+ // Resolve target URL
47
+ let targetUrl = config.targetUrl
48
+ if (!targetUrl && config.targetExternalAgentId) {
49
+ const externalAgents = loadExternalAgents()
50
+ const ea = externalAgents[config.targetExternalAgentId]
51
+ if (ea?.endpoint) {
52
+ targetUrl = ea.endpoint
53
+ }
54
+ }
55
+
56
+ if (!targetUrl) {
57
+ appendProtocolEvent(run.id, {
58
+ type: 'failed',
59
+ phaseId: phase.id,
60
+ summary: `a2a_delegate phase "${phase.label}" — no target URL resolved`,
61
+ }, deps)
62
+ return persistRun({
63
+ ...run,
64
+ status: 'failed',
65
+ lastError: `a2a_delegate phase "${phase.label}" — could not resolve target A2A agent URL`,
66
+ endedAt: run.endedAt || now(deps),
67
+ updatedAt: now(deps),
68
+ })
69
+ }
70
+
71
+ // Create a BoardTask for tracking
72
+ const taskId = genId()
73
+ const taskData: BoardTask = {
74
+ id: taskId,
75
+ title: `A2A: ${config.taskName}`,
76
+ description: config.taskMessage,
77
+ status: 'queued',
78
+ agentId: run.facilitatorAgentId || run.participantAgentIds?.[0] || '',
79
+ protocolRunId: run.id,
80
+ sourceType: 'delegation',
81
+ externalSource: { source: 'a2a', id: taskId },
82
+ queuedAt: now(deps),
83
+ createdAt: now(deps),
84
+ updatedAt: now(deps),
85
+ }
86
+ upsertTask(taskId, taskData)
87
+ notify('tasks')
88
+
89
+ appendProtocolEvent(run.id, {
90
+ type: 'delegation_dispatched',
91
+ summary: `Dispatched A2A delegation to ${targetUrl}: ${config.taskName}`,
92
+ phaseId: phase.id,
93
+ taskId,
94
+ }, deps)
95
+
96
+ log.info(TAG, `Calling remote A2A agent at ${targetUrl}`, { taskName: config.taskName, taskId })
97
+
98
+ // Fire the HTTP call asynchronously — when it completes, update the task
99
+ // The existing wakeProtocolRunFromTaskCompletion machinery will resume the run
100
+ const resolvedUrl = targetUrl
101
+ callA2AAgent(resolvedUrl, 'executeTask', {
102
+ taskId,
103
+ taskName: config.taskName,
104
+ message: config.taskMessage,
105
+ }, {
106
+ timeout: config.timeoutMs ?? 300_000,
107
+ credentialId: config.credentialId,
108
+ }).then(result => {
109
+ const resultStr = typeof result === 'string' ? result : JSON.stringify(result)
110
+ upsertTask(taskId, { ...taskData, status: 'completed', result: resultStr, updatedAt: Date.now(), completedAt: Date.now() })
111
+ notify('tasks')
112
+ log.info(TAG, `A2A delegation completed for task ${taskId}`)
113
+ // Dynamic import to break circular dependency (protocol-step-processors → protocol-a2a-delegate → protocol-run-lifecycle → protocol-step-processors)
114
+ import('@/lib/server/protocols/protocol-run-lifecycle').then(m => m.wakeProtocolRunFromTaskCompletion(taskId))
115
+ }).catch(err => {
116
+ log.error(TAG, `A2A delegation failed for task ${taskId}: ${errorMessage(err)}`)
117
+ if (config.onFailure === 'advance_with_warning') {
118
+ upsertTask(taskId, { ...taskData, status: 'completed', result: `A2A delegation failed: ${errorMessage(err)}`, error: errorMessage(err), updatedAt: Date.now(), completedAt: Date.now() })
119
+ } else {
120
+ upsertTask(taskId, { ...taskData, status: 'failed', error: errorMessage(err), updatedAt: Date.now() })
121
+ }
122
+ notify('tasks')
123
+ import('@/lib/server/protocols/protocol-run-lifecycle').then(m => m.wakeProtocolRunFromTaskCompletion(taskId))
124
+ })
125
+
126
+ const createdTaskIds = [...(run.createdTaskIds || []), taskId]
127
+ return persistRun({
128
+ ...run,
129
+ status: 'waiting',
130
+ waitingReason: `Waiting for A2A delegation: ${config.taskName}`,
131
+ createdTaskIds,
132
+ phaseState: { ...(run.phaseState || { phaseId: phase.id }), dispatchedTaskId: taskId } as ProtocolRunPhaseState,
133
+ updatedAt: now(deps),
134
+ })
135
+ }
@@ -171,6 +171,7 @@ function isDiscussionStepKindLocal(kind: string): boolean {
171
171
  'wait',
172
172
  'dispatch_task',
173
173
  'dispatch_delegation',
174
+ 'a2a_delegate',
174
175
  ].includes(kind)
175
176
  }
176
177
 
@@ -48,7 +48,7 @@ describe('protocol-step-helpers', () => {
48
48
  const kinds = [
49
49
  'present', 'collect_independent_inputs', 'round_robin',
50
50
  'compare', 'decide', 'summarize', 'emit_tasks', 'wait',
51
- 'dispatch_task', 'dispatch_delegation',
51
+ 'dispatch_task', 'dispatch_delegation', 'a2a_delegate',
52
52
  ]
53
53
  for (const kind of kinds) {
54
54
  const step = { id: `step-${kind}`, kind, label: kind } as never
@@ -58,6 +58,7 @@ export function phaseFromStep(step: ProtocolStepDefinition): ProtocolPhaseDefini
58
58
  completionCriteria: step.completionCriteria || null,
59
59
  taskConfig: step.taskConfig || null,
60
60
  delegationConfig: step.delegationConfig || null,
61
+ a2aDelegateConfig: step.a2aDelegateConfig || null,
61
62
  }
62
63
  }
63
64
 
@@ -23,6 +23,7 @@ import type * as ProtocolRunLifecycle from '@/lib/server/protocols/protocol-run-
23
23
  import { processForEachStep } from '@/lib/server/protocols/protocol-foreach'
24
24
  import { processSubflowStep } from '@/lib/server/protocols/protocol-subflow'
25
25
  import { processSwarmStep } from '@/lib/server/protocols/protocol-swarm'
26
+ import { processA2ADelegatePhase } from '@/lib/server/protocols/protocol-a2a-delegate'
26
27
  import { findRunStep } from '@/lib/server/protocols/protocol-normalization'
27
28
  import {
28
29
  appendProtocolEvent,
@@ -708,6 +709,7 @@ export async function stepProtocolRun(run: ProtocolRun, deps?: ProtocolRunDeps):
708
709
  if (phase.kind === 'emit_tasks') return processEmitTasksPhase(started, phase, deps)
709
710
  if (phase.kind === 'dispatch_task') return processDispatchTaskPhase(started, phase, deps)
710
711
  if (phase.kind === 'dispatch_delegation') return processDispatchDelegationPhase(started, phase, deps)
712
+ if (phase.kind === 'a2a_delegate') return processA2ADelegatePhase(started, phase, deps)
711
713
  return processWaitPhase(started, phase, deps)
712
714
  }
713
715
  if (step.kind === 'branch') return processBranchStep(run, step, deps)
@@ -150,5 +150,6 @@ export function isDiscussionStepKind(kind: ProtocolStepDefinition['kind'] | Prot
150
150
  'wait',
151
151
  'dispatch_task',
152
152
  'dispatch_delegation',
153
+ 'a2a_delegate',
153
154
  ].includes(kind)
154
155
  }