@swarmclawai/swarmclaw 1.9.4 → 1.9.6

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.
@@ -54,22 +54,60 @@ export function importConfig(manifest: PortableManifest): ImportResult {
54
54
  idMap,
55
55
  }
56
56
 
57
- // --- Skills first (agents may reference them) ---
57
+ // --- Projects first (agents, skills, schedules, and goals may reference them) ---
58
+ if (manifest.projects && manifest.projects.length) {
59
+ const existingProjects = loadProjects() as Record<string, Project>
60
+ const existingProjectNames = new Set(Object.values(existingProjects).map((p) => p.name))
61
+ for (const portable of manifest.projects) {
62
+ const name = deduplicateName(portable.name, existingProjectNames)
63
+ const id = genId()
64
+ idMap[portable.originalId] = id
65
+ existingProjectNames.add(name)
66
+ const now = Date.now()
67
+ const project: Project = {
68
+ id,
69
+ name,
70
+ description: portable.description ?? '',
71
+ color: portable.color,
72
+ objective: portable.objective,
73
+ audience: portable.audience,
74
+ priorities: portable.priorities,
75
+ openObjectives: portable.openObjectives,
76
+ capabilityHints: portable.capabilityHints,
77
+ credentialRequirements: portable.credentialRequirements,
78
+ successMetrics: portable.successMetrics,
79
+ heartbeatPrompt: portable.heartbeatPrompt,
80
+ heartbeatIntervalSec: portable.heartbeatIntervalSec,
81
+ createdAt: now,
82
+ updatedAt: now,
83
+ }
84
+ existingProjects[id] = project
85
+ result.projects.created++
86
+ result.projects.names.push(name)
87
+ }
88
+ saveProjects(existingProjects)
89
+ }
90
+
91
+ // --- Skills (agents may reference them) ---
58
92
  const existingSkills = loadSkills()
59
93
  const existingSkillNames = new Set(Object.values(existingSkills).map((s) => s.name))
94
+ const pendingSkillAgentLinks: Array<{ skillId: string; originalAgentIds: string[] }> = []
60
95
  for (const portable of manifest.skills) {
61
96
  const name = deduplicateName(portable.name, existingSkillNames)
62
97
  const id = genId()
63
98
  idMap[portable.originalId] = id
64
99
  existingSkillNames.add(name)
100
+ const originalProjectId = portable.originalProjectId ?? (portable as { projectId?: string | null }).projectId ?? null
65
101
  const skill: Skill = {
66
102
  id,
67
103
  name,
68
104
  filename: `${name.toLowerCase().replace(/[^a-z0-9]+/g, '-')}.md`,
69
105
  content: portable.content,
106
+ projectId: originalProjectId ? idMap[originalProjectId] || originalProjectId : undefined,
70
107
  description: portable.description,
71
108
  tags: portable.tags,
72
109
  scope: portable.scope || 'global',
110
+ agentIds: portable.originalAgentIds ? [] : undefined,
73
111
  author: portable.author,
74
112
  version: portable.version,
75
113
  primaryEnv: portable.primaryEnv,
@@ -80,47 +118,51 @@ export function importConfig(manifest: PortableManifest): ImportResult {
80
118
  updatedAt: Date.now(),
81
119
  }
82
120
  saveSkill(id, skill)
121
+ if (portable.originalAgentIds?.length) {
122
+ pendingSkillAgentLinks.push({ skillId: id, originalAgentIds: portable.originalAgentIds })
123
+ }
83
124
  result.skills.created++
84
125
  result.skills.names.push(name)
85
126
  }
86
127
 
87
- // --- Projects (agents and goals may reference them) ---
88
- if (manifest.projects && manifest.projects.length) {
89
- const existingProjects = loadProjects() as Record<string, Project>
90
- const existingProjectNames = new Set(Object.values(existingProjects).map((p) => p.name))
91
- for (const portable of manifest.projects) {
92
- const name = deduplicateName(portable.name, existingProjectNames)
128
+ // --- MCP Servers (agents may reference them) ---
129
+ if (manifest.mcpServers && manifest.mcpServers.length) {
130
+ const existingMcp = loadMcpServers() as Record<string, McpServerConfig>
131
+ const existingMcpNames = new Set(Object.values(existingMcp).map((s) => s.name))
132
+ for (const portable of manifest.mcpServers) {
133
+ const name = deduplicateName(portable.name, existingMcpNames)
93
134
  const id = genId()
94
135
  idMap[portable.originalId] = id
95
- existingProjectNames.add(name)
96
- const now = Date.now()
97
- const project: Project = {
98
- id,
99
- name,
100
- description: portable.description ?? '',
101
- color: portable.color,
102
- objective: portable.objective,
103
- audience: portable.audience,
104
- priorities: portable.priorities,
105
- openObjectives: portable.openObjectives,
106
- capabilityHints: portable.capabilityHints,
107
- credentialRequirements: portable.credentialRequirements,
108
- successMetrics: portable.successMetrics,
109
- heartbeatPrompt: portable.heartbeatPrompt,
110
- heartbeatIntervalSec: portable.heartbeatIntervalSec,
111
- createdAt: now,
112
- updatedAt: now,
136
+ existingMcpNames.add(name)
137
+ const env: Record<string, string> = {}
138
+ for (const key of portable.envKeys || []) env[key] = ''
139
+ const headers: Record<string, string> = {}
140
+ for (const key of portable.headerKeys || []) headers[key] = ''
141
+ existingMcp[id] = {
142
+ id, name,
143
+ transport: portable.transport,
144
+ command: portable.command,
145
+ args: portable.args,
146
+ cwd: portable.cwd,
147
+ url: portable.url,
148
+ env: Object.keys(env).length ? env : undefined,
149
+ headers: Object.keys(headers).length ? headers : undefined,
150
+ createdAt: Date.now(),
151
+ updatedAt: Date.now(),
152
+ } as McpServerConfig
153
+ result.mcpServers.created++
154
+ result.mcpServers.names.push(name)
155
+ if ((portable.envKeys?.length || 0) + (portable.headerKeys?.length || 0) > 0) {
156
+ result.mcpServers.needsCredentials.push(name)
113
157
  }
114
- existingProjects[id] = project
115
- result.projects.created++
116
- result.projects.names.push(name)
117
158
  }
118
- saveProjects(existingProjects)
159
+ saveMcpServers(existingMcp)
119
160
  }
120
161
 
121
162
  // --- Agents ---
122
163
  const existingAgents = loadAgents()
123
164
  const existingAgentNames = new Set(Object.values(existingAgents).map((a) => a.name))
165
+ const pendingAgentGoalLinks: Array<{ agentId: string; originalGoalId: string }> = []
124
166
  for (const portable of manifest.agents) {
125
167
  const name = deduplicateName(portable.name, existingAgentNames)
126
168
  const id = genId()
@@ -128,13 +170,17 @@ export function importConfig(manifest: PortableManifest): ImportResult {
128
170
  idMap[portable.originalId] = id
129
171
  existingAgentNames.add(name)
130
172
  const remappedSkillIds = (portable.skillIds || []).map((sid) => idMap[sid] || sid)
173
+ const remappedMcpServerIds = (portable.mcpServerIds || []).map((sid) => idMap[sid] || sid)
131
174
  const remappedProjectId = portable.projectId && idMap[portable.projectId] ? idMap[portable.projectId] : portable.projectId
175
+ const originalGoalId = portable.goalId || null
132
176
  const agent: Agent = {
133
177
  ...(portable as Omit<PortableAgent, 'originalId'>),
134
178
  id,
135
179
  name,
136
180
  skillIds: remappedSkillIds,
181
+ mcpServerIds: remappedMcpServerIds,
137
182
  projectId: remappedProjectId,
183
+ goalId: originalGoalId && idMap[originalGoalId] ? idMap[originalGoalId] : originalGoalId,
138
184
  threadSessionId: null,
139
185
  lastUsedAt: undefined,
140
186
  totalCost: undefined,
@@ -148,9 +194,23 @@ export function importConfig(manifest: PortableManifest): ImportResult {
148
194
  existingAgents[id] = agent
149
195
  result.agents.created++
150
196
  result.agents.names.push(name)
197
+ if (originalGoalId) pendingAgentGoalLinks.push({ agentId: id, originalGoalId })
151
198
  }
152
199
  saveAgents(existingAgents)
153
200
 
201
+ if (pendingSkillAgentLinks.length) {
202
+ const skills = loadSkills()
203
+ for (const pending of pendingSkillAgentLinks) {
204
+ const skill = skills[pending.skillId]
205
+ if (!skill) continue
206
+ skill.agentIds = pending.originalAgentIds
207
+ .map((agentId) => idMap[agentId])
208
+ .filter((agentId): agentId is string => Boolean(agentId))
209
+ skill.updatedAt = Date.now()
210
+ saveSkill(pending.skillId, skill)
211
+ }
212
+ }
213
+
154
214
  // --- Schedules (need agent ID mapping) ---
155
215
  const existingSchedules = loadSchedules()
156
216
  const existingScheduleNames = new Set(Object.values(existingSchedules).map((s) => s.name))
@@ -163,9 +223,21 @@ export function importConfig(manifest: PortableManifest): ImportResult {
163
223
  existingScheduleNames.add(name)
164
224
  const schedule: Schedule = {
165
225
  id, name, agentId: newAgentId,
226
+ projectId: portable.projectId ? idMap[portable.projectId] || portable.projectId : undefined,
166
227
  taskPrompt: portable.taskPrompt,
167
228
  taskMode: portable.taskMode,
168
229
  message: portable.message,
230
+ protocolTemplateId: portable.protocolTemplateId,
231
+ protocolParticipantAgentIds: (portable.protocolParticipantAgentIds || [])
232
+ .map((agentId) => idMap[agentId])
233
+ .filter((agentId): agentId is string => Boolean(agentId)),
234
+ protocolFacilitatorAgentId: portable.protocolFacilitatorAgentId
235
+ ? idMap[portable.protocolFacilitatorAgentId] || null
236
+ : null,
237
+ protocolObserverAgentIds: (portable.protocolObserverAgentIds || [])
238
+ .map((agentId) => idMap[agentId])
239
+ .filter((agentId): agentId is string => Boolean(agentId)),
240
+ protocolConfig: portable.protocolConfig,
169
241
  description: portable.description,
170
242
  scheduleType: portable.scheduleType,
171
243
  frequency: portable.frequency,
@@ -184,41 +256,48 @@ export function importConfig(manifest: PortableManifest): ImportResult {
184
256
  result.schedules.names.push(name)
185
257
  }
186
258
 
187
- // --- MCP Servers ---
188
- if (manifest.mcpServers && manifest.mcpServers.length) {
189
- const existingMcp = loadMcpServers() as Record<string, McpServerConfig>
190
- const existingMcpNames = new Set(Object.values(existingMcp).map((s) => s.name))
191
- for (const portable of manifest.mcpServers) {
192
- const name = deduplicateName(portable.name, existingMcpNames)
259
+ // --- Chatrooms ---
260
+ if (manifest.chatrooms && manifest.chatrooms.length) {
261
+ const existingChatrooms = loadChatrooms()
262
+ const existingChatroomNames = new Set(Object.values(existingChatrooms).map((c) => c.name))
263
+ for (const portable of manifest.chatrooms) {
264
+ const name = deduplicateName(portable.name, existingChatroomNames)
193
265
  const id = genId()
194
266
  idMap[portable.originalId] = id
195
- existingMcpNames.add(name)
196
- const env: Record<string, string> = {}
197
- for (const key of portable.envKeys || []) env[key] = ''
198
- const headers: Record<string, string> = {}
199
- for (const key of portable.headerKeys || []) headers[key] = ''
200
- existingMcp[id] = {
267
+ existingChatroomNames.add(name)
268
+ const now = Date.now()
269
+ const remappedAgentIds = portable.originalAgentIds
270
+ .map((aid) => idMap[aid])
271
+ .filter((aid): aid is string => Boolean(aid))
272
+ const remappedRules = (portable.routingRules || []).map((r, idx) => ({
273
+ id: `route-${idx + 1}`,
274
+ type: r.type,
275
+ pattern: r.pattern,
276
+ keywords: r.keywords,
277
+ agentId: idMap[r.originalAgentId] || r.originalAgentId,
278
+ priority: r.priority,
279
+ }))
280
+ const chatroom: Chatroom = {
201
281
  id, name,
202
- transport: portable.transport,
203
- command: portable.command,
204
- args: portable.args,
205
- cwd: portable.cwd,
206
- url: portable.url,
207
- env: Object.keys(env).length ? env : undefined,
208
- headers: Object.keys(headers).length ? headers : undefined,
209
- createdAt: Date.now(),
210
- updatedAt: Date.now(),
211
- } as McpServerConfig
212
- result.mcpServers.created++
213
- result.mcpServers.names.push(name)
214
- if ((portable.envKeys?.length || 0) + (portable.headerKeys?.length || 0) > 0) {
215
- result.mcpServers.needsCredentials.push(name)
282
+ description: portable.description,
283
+ agentIds: remappedAgentIds,
284
+ messages: [],
285
+ chatMode: portable.chatMode,
286
+ autoAddress: portable.autoAddress,
287
+ routingGuidance: portable.routingGuidance,
288
+ routingRules: remappedRules,
289
+ temporary: portable.temporary,
290
+ topic: portable.topic,
291
+ createdAt: now,
292
+ updatedAt: now,
216
293
  }
294
+ upsertChatroom(id, chatroom)
295
+ result.chatrooms.created++
296
+ result.chatrooms.names.push(name)
217
297
  }
218
- saveMcpServers(existingMcp)
219
298
  }
220
299
 
221
- // --- Connectors ---
300
+ // --- Connectors (after chatrooms so room-bound connectors can remap) ---
222
301
  if (manifest.connectors && manifest.connectors.length) {
223
302
  const existingConnectors = loadConnectors()
224
303
  const existingConnectorNames = new Set(Object.values(existingConnectors).map((c) => c.name))
@@ -256,47 +335,6 @@ export function importConfig(manifest: PortableManifest): ImportResult {
256
335
  }
257
336
  }
258
337
 
259
- // --- Chatrooms ---
260
- if (manifest.chatrooms && manifest.chatrooms.length) {
261
- const existingChatrooms = loadChatrooms()
262
- const existingChatroomNames = new Set(Object.values(existingChatrooms).map((c) => c.name))
263
- for (const portable of manifest.chatrooms) {
264
- const name = deduplicateName(portable.name, existingChatroomNames)
265
- const id = genId()
266
- idMap[portable.originalId] = id
267
- existingChatroomNames.add(name)
268
- const now = Date.now()
269
- const remappedAgentIds = portable.originalAgentIds
270
- .map((aid) => idMap[aid])
271
- .filter((aid): aid is string => Boolean(aid))
272
- const remappedRules = (portable.routingRules || []).map((r, idx) => ({
273
- id: `route-${idx + 1}`,
274
- type: r.type,
275
- pattern: r.pattern,
276
- keywords: r.keywords,
277
- agentId: idMap[r.originalAgentId] || r.originalAgentId,
278
- priority: r.priority,
279
- }))
280
- const chatroom: Chatroom = {
281
- id, name,
282
- description: portable.description,
283
- agentIds: remappedAgentIds,
284
- messages: [],
285
- chatMode: portable.chatMode,
286
- autoAddress: portable.autoAddress,
287
- routingGuidance: portable.routingGuidance,
288
- routingRules: remappedRules,
289
- temporary: portable.temporary,
290
- topic: portable.topic,
291
- createdAt: now,
292
- updatedAt: now,
293
- }
294
- upsertChatroom(id, chatroom)
295
- result.chatrooms.created++
296
- result.chatrooms.names.push(name)
297
- }
298
- }
299
-
300
338
  // --- Goals (after projects + agents so refs can be remapped) ---
301
339
  if (manifest.goals && manifest.goals.length) {
302
340
  // Two-pass to handle parent goal refs.
@@ -336,6 +374,18 @@ export function importConfig(manifest: PortableManifest): ImportResult {
336
374
  }
337
375
  }
338
376
 
377
+ if (pendingAgentGoalLinks.length) {
378
+ const agents = loadAgents()
379
+ for (const pending of pendingAgentGoalLinks) {
380
+ const remappedGoalId = idMap[pending.originalGoalId]
381
+ const agent = agents[pending.agentId]
382
+ if (!agent || !remappedGoalId) continue
383
+ agent.goalId = remappedGoalId
384
+ agent.updatedAt = Date.now()
385
+ }
386
+ saveAgents(agents)
387
+ }
388
+
339
389
  logActivity({
340
390
  entityType: 'system',
341
391
  entityId: 'portability',
@@ -619,6 +619,8 @@ const PortableSkillSchema = z.object({
619
619
  originalId: z.string().min(1),
620
620
  name: z.string().min(1),
621
621
  content: z.string(),
622
+ originalProjectId: z.string().nullable().optional(),
623
+ originalAgentIds: z.array(z.string()).optional(),
622
624
  }).passthrough()
623
625
 
624
626
  const PortableScheduleSchema = z.object({
@@ -627,13 +629,64 @@ const PortableScheduleSchema = z.object({
627
629
  name: z.string().min(1),
628
630
  }).passthrough()
629
631
 
632
+ const PortableConnectorSchema = z.object({
633
+ originalId: z.string().min(1),
634
+ name: z.string().min(1),
635
+ platform: z.string().min(1),
636
+ }).passthrough()
637
+
638
+ const PortableChatroomSchema = z.object({
639
+ originalId: z.string().min(1),
640
+ originalAgentIds: z.array(z.string()),
641
+ name: z.string().min(1),
642
+ }).passthrough()
643
+
644
+ const PortableMcpServerSchema = z.object({
645
+ originalId: z.string().min(1),
646
+ name: z.string().min(1),
647
+ transport: z.string().min(1),
648
+ }).passthrough()
649
+
650
+ const PortableProjectSchema = z.object({
651
+ originalId: z.string().min(1),
652
+ name: z.string().min(1),
653
+ }).passthrough()
654
+
655
+ const PortableGoalSchema = z.object({
656
+ originalId: z.string().min(1),
657
+ title: z.string().min(1),
658
+ level: z.string().min(1),
659
+ objective: z.string().min(1),
660
+ status: z.string().min(1),
661
+ }).passthrough()
662
+
663
+ const PortableExtensionRefSchema = z.object({
664
+ name: z.string().min(1),
665
+ }).passthrough()
666
+
667
+ const PortableManifestScopeSchema = z.discriminatedUnion('kind', [
668
+ z.object({ kind: z.literal('all') }).passthrough(),
669
+ z.object({
670
+ kind: z.literal('project'),
671
+ originalProjectId: z.string().min(1),
672
+ projectName: z.string().min(1),
673
+ }).passthrough(),
674
+ ])
675
+
630
676
  export const PortableManifestSchema = z.object({
631
677
  formatVersion: z.number().int().nonnegative(),
632
678
  exportedAt: z.string().optional(),
679
+ scope: PortableManifestScopeSchema.optional(),
633
680
  agents: z.array(PortableAgentSchema),
634
681
  skills: z.array(PortableSkillSchema),
635
682
  schedules: z.array(PortableScheduleSchema),
636
- })
683
+ connectors: z.array(PortableConnectorSchema).optional(),
684
+ chatrooms: z.array(PortableChatroomSchema).optional(),
685
+ mcpServers: z.array(PortableMcpServerSchema).optional(),
686
+ projects: z.array(PortableProjectSchema).optional(),
687
+ goals: z.array(PortableGoalSchema).optional(),
688
+ extensions: z.array(PortableExtensionRefSchema).optional(),
689
+ }).passthrough()
637
690
 
638
691
  /** Format ZodError into a 400-friendly payload */
639
692
  export function formatZodError(err: z.ZodError) {