@swarmclawai/swarmclaw 1.2.4 → 1.2.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.
- package/README.md +14 -0
- package/bin/daemon-cmd.js +169 -0
- package/bin/server-cmd.js +3 -0
- package/bin/swarmclaw.js +11 -0
- package/package.json +17 -16
- package/src/app/api/agents/[id]/clone/route.ts +3 -32
- package/src/app/api/agents/[id]/route.ts +6 -158
- package/src/app/api/agents/[id]/status/route.ts +2 -3
- package/src/app/api/agents/[id]/thread/route.ts +4 -17
- package/src/app/api/agents/bulk/route.ts +5 -47
- package/src/app/api/agents/route.ts +5 -119
- package/src/app/api/agents/trash/route.ts +13 -24
- package/src/app/api/auth/route.ts +3 -9
- package/src/app/api/autonomy/estop/route.ts +5 -5
- package/src/app/api/chatrooms/[id]/chat/route.ts +11 -5
- package/src/app/api/chatrooms/[id]/route.ts +23 -2
- package/src/app/api/chatrooms/route.ts +13 -2
- package/src/app/api/chats/[id]/clear/route.ts +2 -13
- package/src/app/api/chats/[id]/deploy/route.ts +2 -3
- package/src/app/api/chats/[id]/edit-resend/route.ts +7 -13
- package/src/app/api/chats/[id]/mailbox/route.ts +6 -8
- package/src/app/api/chats/[id]/queue/route.ts +17 -64
- package/src/app/api/chats/[id]/retry/route.ts +4 -22
- package/src/app/api/chats/[id]/route.ts +10 -138
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/migrate-messages/route.ts +7 -0
- package/src/app/api/chats/route.ts +13 -134
- package/src/app/api/connectors/[id]/access/route.ts +12 -229
- package/src/app/api/connectors/[id]/doctor/route.ts +1 -1
- package/src/app/api/connectors/[id]/health/route.ts +12 -39
- package/src/app/api/connectors/[id]/route.ts +14 -122
- package/src/app/api/connectors/[id]/webhook/route.ts +1 -1
- package/src/app/api/connectors/doctor/route.ts +1 -1
- package/src/app/api/connectors/route.ts +12 -70
- package/src/app/api/credentials/[id]/route.ts +2 -4
- package/src/app/api/credentials/route.ts +10 -19
- package/src/app/api/daemon/health-check/route.ts +3 -4
- package/src/app/api/daemon/route.ts +10 -8
- package/src/app/api/documents/route.ts +11 -10
- package/src/app/api/external-agents/route.ts +3 -3
- package/src/app/api/gateways/[id]/health/route.ts +2 -3
- package/src/app/api/gateways/[id]/route.ts +7 -122
- package/src/app/api/gateways/route.ts +3 -103
- package/src/app/api/mcp-servers/[id]/tools/route.ts +5 -5
- package/src/app/api/openclaw/dashboard-url/route.ts +8 -16
- package/src/app/api/openclaw/directory/route.ts +2 -2
- package/src/app/api/openclaw/history/route.ts +3 -5
- package/src/app/api/providers/[id]/route.test.ts +49 -0
- package/src/app/api/providers/ollama/route.ts +6 -5
- package/src/app/api/schedules/[id]/route.ts +14 -108
- package/src/app/api/schedules/[id]/run/route.ts +6 -67
- package/src/app/api/schedules/route.ts +9 -51
- package/src/app/api/settings/route.ts +4 -3
- package/src/app/api/setup/check-provider/route.ts +23 -1
- package/src/app/api/setup/openclaw-device/route.ts +2 -2
- package/src/app/api/system/status/route.ts +2 -2
- package/src/app/api/tasks/[id]/route.ts +16 -202
- package/src/app/api/tasks/bulk/route.ts +5 -86
- package/src/app/api/tasks/metrics/route.ts +2 -1
- package/src/app/api/tasks/route.ts +11 -171
- package/src/app/api/upload/route.ts +1 -1
- package/src/app/api/uploads/[filename]/route.ts +1 -1
- package/src/app/api/uploads/route.ts +1 -1
- package/src/app/api/webhooks/[id]/history/route.ts +2 -2
- package/src/app/layout.tsx +9 -6
- package/src/app/protocols/page.tsx +71 -89
- package/src/app/tasks/page.tsx +32 -32
- package/src/cli/index.js +1 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-sheet.tsx +5 -5
- package/src/components/auth/setup-wizard/index.tsx +4 -4
- package/src/components/auth/setup-wizard/step-agents.tsx +1 -1
- package/src/components/auth/setup-wizard/step-connect.tsx +1 -1
- package/src/components/auth/setup-wizard/utils.ts +1 -1
- package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
- package/src/components/connectors/connector-list.tsx +26 -40
- package/src/components/connectors/connector-sheet.tsx +95 -149
- package/src/components/gateways/gateway-sheet.tsx +61 -110
- package/src/components/layout/live-query-sync.tsx +121 -0
- package/src/components/protocols/structured-session-launcher.tsx +24 -45
- package/src/components/providers/app-query-provider.tsx +17 -0
- package/src/components/providers/provider-list.tsx +60 -61
- package/src/components/providers/provider-sheet.tsx +74 -56
- package/src/components/skills/skill-list.tsx +5 -18
- package/src/components/skills/skill-sheet.tsx +21 -20
- package/src/components/skills/skills-workspace.tsx +48 -87
- package/src/components/tasks/task-card.tsx +20 -13
- package/src/components/tasks/task-column.tsx +22 -7
- package/src/components/tasks/task-list.tsx +8 -11
- package/src/components/tasks/task-sheet.tsx +111 -103
- package/src/features/agents/queries.ts +20 -0
- package/src/features/chatrooms/queries.ts +20 -0
- package/src/features/chats/queries.ts +27 -0
- package/src/features/connectors/queries.ts +145 -0
- package/src/features/credentials/queries.ts +37 -0
- package/src/features/extensions/queries.ts +26 -0
- package/src/features/external-agents/queries.ts +36 -0
- package/src/features/gateways/queries.ts +274 -0
- package/src/features/missions/queries.ts +23 -0
- package/src/features/projects/queries.ts +20 -0
- package/src/features/protocols/queries.ts +149 -0
- package/src/features/providers/queries.ts +142 -0
- package/src/features/settings/queries.ts +20 -0
- package/src/features/skills/queries.ts +182 -0
- package/src/features/tasks/queries.ts +189 -0
- package/src/hooks/use-ws.ts +3 -2
- package/src/lib/app/api-client.ts +2 -2
- package/src/lib/providers/index.test.ts +108 -0
- package/src/lib/providers/index.ts +38 -15
- package/src/lib/query/client.ts +17 -0
- package/src/lib/server/agents/agent-runtime-config.ts +1 -1
- package/src/lib/server/agents/agent-service.ts +429 -0
- package/src/lib/server/agents/agent-thread-session.ts +6 -5
- package/src/lib/server/agents/autonomy-contract.ts +1 -4
- package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
- package/src/lib/server/agents/delegation-advisory.ts +251 -0
- package/src/lib/server/agents/main-agent-loop.ts +98 -40
- package/src/lib/server/agents/subagent-runtime.ts +12 -0
- package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
- package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
- package/src/lib/server/build-llm.ts +7 -15
- package/src/lib/server/capability-router.test.ts +70 -1
- package/src/lib/server/capability-router.ts +24 -99
- package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
- package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
- package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
- package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
- package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
- package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
- package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
- package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
- package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
- package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
- package/src/lib/server/chat-execution/message-classifier.ts +74 -32
- package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
- package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
- package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
- package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
- package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
- package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
- package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
- package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
- package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
- package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
- package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
- package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
- package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
- package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
- package/src/lib/server/chats/chat-session-service.ts +410 -0
- package/src/lib/server/connectors/access.ts +1 -1
- package/src/lib/server/connectors/commands.ts +7 -6
- package/src/lib/server/connectors/connector-inbound.ts +14 -7
- package/src/lib/server/connectors/connector-outbound.ts +16 -11
- package/src/lib/server/connectors/connector-service.ts +453 -0
- package/src/lib/server/connectors/delivery.ts +17 -12
- package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
- package/src/lib/server/connectors/media.ts +1 -1
- package/src/lib/server/connectors/response-media.ts +1 -1
- package/src/lib/server/connectors/session-consolidation.ts +11 -7
- package/src/lib/server/connectors/session.ts +9 -7
- package/src/lib/server/connectors/voice-note.ts +2 -1
- package/src/lib/server/context-manager.ts +20 -1
- package/src/lib/server/cost.ts +2 -3
- package/src/lib/server/credentials/credential-repository.ts +43 -4
- package/src/lib/server/credentials/credential-service.ts +112 -0
- package/src/lib/server/daemon/admin-metadata.ts +64 -0
- package/src/lib/server/daemon/controller.ts +577 -0
- package/src/lib/server/daemon/daemon-runtime.ts +352 -0
- package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
- package/src/lib/server/daemon/types.ts +101 -0
- package/src/lib/server/embeddings.ts +3 -9
- package/src/lib/server/eval/agent-regression.ts +3 -2
- package/src/lib/server/eval/runner.ts +2 -2
- package/src/lib/server/execution-brief.test.ts +167 -0
- package/src/lib/server/execution-brief.ts +295 -0
- package/src/lib/server/execution-engine/chat-turn.ts +9 -0
- package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
- package/src/lib/server/execution-engine/index.ts +35 -0
- package/src/lib/server/execution-engine/task-attempt.ts +303 -0
- package/src/lib/server/execution-engine/types.ts +33 -0
- package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
- package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
- package/src/lib/server/memory/session-archive-memory.ts +12 -10
- package/src/lib/server/messages/message-repository.ts +330 -0
- package/src/lib/server/missions/mission-service/core.ts +8 -6
- package/src/lib/server/openclaw/agent-resolver.ts +2 -3
- package/src/lib/server/openclaw/doctor.ts +1 -1
- package/src/lib/server/openclaw/gateway.test.ts +10 -1
- package/src/lib/server/openclaw/gateway.ts +5 -14
- package/src/lib/server/openclaw/health.ts +3 -11
- package/src/lib/server/openclaw/sync.ts +8 -6
- package/src/lib/server/persistence/storage-context.ts +3 -0
- package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
- package/src/lib/server/protocols/protocol-normalization.ts +1 -1
- package/src/lib/server/protocols/protocol-queries.ts +13 -7
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
- package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
- package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
- package/src/lib/server/protocols/protocol-swarm.ts +8 -8
- package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
- package/src/lib/server/protocols/protocol-templates.ts +4 -2
- package/src/lib/server/protocols/protocol-types.ts +10 -7
- package/src/lib/server/provider-endpoint.ts +7 -12
- package/src/lib/server/provider-model-discovery.ts +2 -11
- package/src/lib/server/query-expansion.ts +5 -6
- package/src/lib/server/run-context.test.ts +365 -0
- package/src/lib/server/run-context.ts +367 -0
- package/src/lib/server/runtime/heartbeat-service.ts +7 -5
- package/src/lib/server/runtime/queue/core.ts +61 -190
- package/src/lib/server/runtime/run-ledger.ts +8 -0
- package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
- package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
- package/src/lib/server/schedules/schedule-route-service.ts +230 -0
- package/src/lib/server/service-result.ts +16 -0
- package/src/lib/server/session-note.ts +2 -3
- package/src/lib/server/session-reset-policy.ts +4 -3
- package/src/lib/server/session-tools/connector.ts +9 -6
- package/src/lib/server/session-tools/context-mgmt.ts +58 -9
- package/src/lib/server/session-tools/crud.ts +162 -10
- package/src/lib/server/session-tools/delegate.ts +1 -1
- package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
- package/src/lib/server/session-tools/memory.ts +6 -4
- package/src/lib/server/session-tools/session-info.test.ts +56 -0
- package/src/lib/server/session-tools/session-info.ts +119 -12
- package/src/lib/server/session-tools/skill-runtime.ts +3 -1
- package/src/lib/server/session-tools/skills.ts +15 -15
- package/src/lib/server/session-tools/subagent.test.ts +115 -1
- package/src/lib/server/session-tools/subagent.ts +125 -7
- package/src/lib/server/session-tools/team-context.ts +4 -3
- package/src/lib/server/session-tools/wallet.ts +0 -58
- package/src/lib/server/sessions/session-lineage.ts +55 -0
- package/src/lib/server/sessions/session-repository.ts +2 -2
- package/src/lib/server/skills/learned-skills.ts +24 -23
- package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
- package/src/lib/server/skills/skill-repository.ts +136 -13
- package/src/lib/server/skills/skill-suggestions.ts +25 -28
- package/src/lib/server/storage-normalization.test.ts +44 -267
- package/src/lib/server/storage-normalization.ts +75 -0
- package/src/lib/server/storage.ts +19 -0
- package/src/lib/server/structured-extract.ts +3 -14
- package/src/lib/server/tasks/task-followups.ts +16 -11
- package/src/lib/server/tasks/task-result.test.ts +25 -29
- package/src/lib/server/tasks/task-result.ts +5 -9
- package/src/lib/server/tasks/task-route-service.ts +449 -0
- package/src/lib/server/text-normalization.ts +41 -0
- package/src/lib/server/tool-planning.ts +6 -42
- package/src/lib/server/upload-path.ts +5 -0
- package/src/lib/server/working-state/extraction.ts +614 -0
- package/src/lib/server/working-state/normalization.ts +866 -0
- package/src/lib/server/working-state/prompt.ts +60 -0
- package/src/lib/server/working-state/repository.ts +38 -0
- package/src/lib/server/working-state/service.test.ts +253 -0
- package/src/lib/server/working-state/service.ts +293 -0
- package/src/lib/validation/schemas.ts +1 -0
- package/src/lib/ws-client.ts +3 -3
- package/src/stores/slices/task-slice.ts +1 -4
- package/src/stores/use-chatroom-store.ts +2 -2
- package/src/types/index.ts +277 -12
|
@@ -111,4 +111,156 @@ describe('manage_tasks tool', () => {
|
|
|
111
111
|
assert.equal(output.created.codexResumeId, 'codex-thread-1')
|
|
112
112
|
assert.deepEqual(output.created.blockedBy, ['task-source'])
|
|
113
113
|
})
|
|
114
|
+
|
|
115
|
+
it('auto-assigns unowned tasks to a better-fit teammate when delegation is enabled', () => {
|
|
116
|
+
const output = runWithTempDataDir(`
|
|
117
|
+
const storageMod = await import('./src/lib/server/storage')
|
|
118
|
+
const crudMod = await import('./src/lib/server/session-tools/crud')
|
|
119
|
+
const storage = storageMod.default || storageMod
|
|
120
|
+
const crud = crudMod.default || crudMod
|
|
121
|
+
|
|
122
|
+
const now = Date.now()
|
|
123
|
+
storage.saveAgents({
|
|
124
|
+
ceo: {
|
|
125
|
+
id: 'ceo',
|
|
126
|
+
name: 'CEO',
|
|
127
|
+
role: 'coordinator',
|
|
128
|
+
description: 'Directs specialist workers',
|
|
129
|
+
systemPrompt: '',
|
|
130
|
+
provider: 'openai',
|
|
131
|
+
model: 'gpt-test',
|
|
132
|
+
capabilities: ['coordination', 'delegation', 'operations'],
|
|
133
|
+
delegationEnabled: true,
|
|
134
|
+
delegationTargetMode: 'all',
|
|
135
|
+
delegationTargetAgentIds: [],
|
|
136
|
+
createdAt: now,
|
|
137
|
+
updatedAt: now,
|
|
138
|
+
},
|
|
139
|
+
builder: {
|
|
140
|
+
id: 'builder',
|
|
141
|
+
name: 'Builder',
|
|
142
|
+
role: 'worker',
|
|
143
|
+
description: 'Builds and debugs software',
|
|
144
|
+
systemPrompt: '',
|
|
145
|
+
provider: 'openai',
|
|
146
|
+
model: 'gpt-test',
|
|
147
|
+
capabilities: ['coding', 'implementation', 'debugging'],
|
|
148
|
+
createdAt: now,
|
|
149
|
+
updatedAt: now,
|
|
150
|
+
},
|
|
151
|
+
researcher: {
|
|
152
|
+
id: 'researcher',
|
|
153
|
+
name: 'Researcher',
|
|
154
|
+
role: 'worker',
|
|
155
|
+
description: 'Research and synthesis specialist',
|
|
156
|
+
systemPrompt: '',
|
|
157
|
+
provider: 'openai',
|
|
158
|
+
model: 'gpt-test',
|
|
159
|
+
capabilities: ['research', 'analysis', 'summarization'],
|
|
160
|
+
createdAt: now,
|
|
161
|
+
updatedAt: now,
|
|
162
|
+
},
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
const tools = crud.buildCrudTools({
|
|
166
|
+
cwd: process.env.WORKSPACE_DIR,
|
|
167
|
+
ctx: { sessionId: 'session-auto-assign', agentId: 'ceo', delegationEnabled: true, delegationTargetMode: 'all', delegationTargetAgentIds: [] },
|
|
168
|
+
hasExtension: (name) => name === 'manage_tasks',
|
|
169
|
+
})
|
|
170
|
+
const tool = tools.find((entry) => entry.name === 'manage_tasks')
|
|
171
|
+
const raw = await tool.invoke({
|
|
172
|
+
action: 'create',
|
|
173
|
+
title: 'Implement API integration',
|
|
174
|
+
description: 'Build the API client and fix the failing tests.',
|
|
175
|
+
requiredCapabilities: ['coding'],
|
|
176
|
+
status: 'backlog',
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
const response = JSON.parse(raw)
|
|
180
|
+
const stored = storage.loadTasks()[response.id]
|
|
181
|
+
console.log(JSON.stringify({ response, stored }))
|
|
182
|
+
`)
|
|
183
|
+
|
|
184
|
+
assert.equal(output.response.agentId, 'builder')
|
|
185
|
+
assert.equal(output.stored.agentId, 'builder')
|
|
186
|
+
assert.equal(output.response.delegationAdvisory.autoAssigned, true)
|
|
187
|
+
assert.equal(output.response.delegationAdvisory.recommendedAgentId, 'builder')
|
|
188
|
+
assert.deepEqual(output.response.delegationAdvisory.requiredCapabilities, ['coding'])
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('keeps an explicit assignee but returns delegation advisory when another teammate is a better fit', () => {
|
|
192
|
+
const output = runWithTempDataDir(`
|
|
193
|
+
const storageMod = await import('./src/lib/server/storage')
|
|
194
|
+
const crudMod = await import('./src/lib/server/session-tools/crud')
|
|
195
|
+
const storage = storageMod.default || storageMod
|
|
196
|
+
const crud = crudMod.default || crudMod
|
|
197
|
+
|
|
198
|
+
const now = Date.now()
|
|
199
|
+
storage.saveAgents({
|
|
200
|
+
ceo: {
|
|
201
|
+
id: 'ceo',
|
|
202
|
+
name: 'CEO',
|
|
203
|
+
role: 'coordinator',
|
|
204
|
+
description: 'Directs specialist workers',
|
|
205
|
+
systemPrompt: '',
|
|
206
|
+
provider: 'openai',
|
|
207
|
+
model: 'gpt-test',
|
|
208
|
+
capabilities: ['coordination', 'delegation', 'operations'],
|
|
209
|
+
delegationEnabled: true,
|
|
210
|
+
delegationTargetMode: 'all',
|
|
211
|
+
delegationTargetAgentIds: [],
|
|
212
|
+
createdAt: now,
|
|
213
|
+
updatedAt: now,
|
|
214
|
+
},
|
|
215
|
+
builder: {
|
|
216
|
+
id: 'builder',
|
|
217
|
+
name: 'Builder',
|
|
218
|
+
role: 'worker',
|
|
219
|
+
description: 'Builds and debugs software',
|
|
220
|
+
systemPrompt: '',
|
|
221
|
+
provider: 'openai',
|
|
222
|
+
model: 'gpt-test',
|
|
223
|
+
capabilities: ['coding', 'implementation', 'debugging'],
|
|
224
|
+
createdAt: now,
|
|
225
|
+
updatedAt: now,
|
|
226
|
+
},
|
|
227
|
+
researcher: {
|
|
228
|
+
id: 'researcher',
|
|
229
|
+
name: 'Researcher',
|
|
230
|
+
role: 'worker',
|
|
231
|
+
description: 'Research and synthesis specialist',
|
|
232
|
+
systemPrompt: '',
|
|
233
|
+
provider: 'openai',
|
|
234
|
+
model: 'gpt-test',
|
|
235
|
+
capabilities: ['research', 'analysis', 'summarization'],
|
|
236
|
+
createdAt: now,
|
|
237
|
+
updatedAt: now,
|
|
238
|
+
},
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
const tools = crud.buildCrudTools({
|
|
242
|
+
cwd: process.env.WORKSPACE_DIR,
|
|
243
|
+
ctx: { sessionId: 'session-explicit-assign', agentId: 'ceo', delegationEnabled: true, delegationTargetMode: 'all', delegationTargetAgentIds: [] },
|
|
244
|
+
hasExtension: (name) => name === 'manage_tasks',
|
|
245
|
+
})
|
|
246
|
+
const tool = tools.find((entry) => entry.name === 'manage_tasks')
|
|
247
|
+
const raw = await tool.invoke({
|
|
248
|
+
action: 'create',
|
|
249
|
+
title: 'Implement API integration',
|
|
250
|
+
description: 'Build the API client and fix the failing tests.',
|
|
251
|
+
agentId: 'researcher',
|
|
252
|
+
requiredCapabilities: ['coding'],
|
|
253
|
+
status: 'backlog',
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
const response = JSON.parse(raw)
|
|
257
|
+
const stored = storage.loadTasks()[response.id]
|
|
258
|
+
console.log(JSON.stringify({ response, stored }))
|
|
259
|
+
`)
|
|
260
|
+
|
|
261
|
+
assert.equal(output.response.agentId, 'researcher')
|
|
262
|
+
assert.equal(output.stored.agentId, 'researcher')
|
|
263
|
+
assert.equal(output.response.delegationAdvisory.autoAssigned, false)
|
|
264
|
+
assert.equal(output.response.delegationAdvisory.recommendedAgentId, 'builder')
|
|
265
|
+
})
|
|
114
266
|
})
|
|
@@ -15,6 +15,7 @@ import { expandQuery } from '../query-expansion'
|
|
|
15
15
|
import type { FileReference, MemoryEntry, MemoryImage, MemoryReference, Extension, ExtensionHooks, Session } from '@/types'
|
|
16
16
|
import type { ToolBuildContext } from './context'
|
|
17
17
|
import { registerNativeCapability } from '../native-capabilities'
|
|
18
|
+
import { getMessages } from '@/lib/server/messages/message-repository'
|
|
18
19
|
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
19
20
|
import { getMemoryTier, partitionMemoriesByTier, shouldHideFromDurableRecall } from '@/lib/server/memory/memory-tiers'
|
|
20
21
|
import { syncSessionArchiveMemory } from '@/lib/server/memory/session-archive-memory'
|
|
@@ -88,13 +89,14 @@ function isSessionContext(ctx: MemoryActionContext | null | undefined): ctx is S
|
|
|
88
89
|
return !!ctx
|
|
89
90
|
&& typeof ctx.id === 'string'
|
|
90
91
|
&& typeof ctx.name === 'string'
|
|
91
|
-
&&
|
|
92
|
+
&& typeof ctx.provider === 'string'
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
function latestUserFactFromSession(session: Session | null): string {
|
|
95
|
-
if (!session
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
if (!session) return ''
|
|
97
|
+
const messages = getMessages(session.id)
|
|
98
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
99
|
+
const message = messages[index]
|
|
98
100
|
if (message?.role !== 'user') continue
|
|
99
101
|
const text = typeof message.text === 'string' ? message.text.replace(/\s+/g, ' ').trim() : ''
|
|
100
102
|
if (text) return text
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { describe, it } from 'node:test'
|
|
3
|
+
|
|
4
|
+
import { buildSessionIdentityPayload } from '@/lib/server/session-tools/session-info'
|
|
5
|
+
|
|
6
|
+
describe('buildSessionIdentityPayload', () => {
|
|
7
|
+
it('includes harness, project, provider, and delegation context for identity queries', () => {
|
|
8
|
+
const payload = buildSessionIdentityPayload({
|
|
9
|
+
context: { sessionId: 'session-child', agentId: 'agent-1' },
|
|
10
|
+
currentSession: {
|
|
11
|
+
id: 'session-child',
|
|
12
|
+
name: 'Builder Chat',
|
|
13
|
+
cwd: '/workspace/projects/northstar',
|
|
14
|
+
provider: 'openai',
|
|
15
|
+
model: 'gpt-5',
|
|
16
|
+
user: 'system',
|
|
17
|
+
createdAt: 1,
|
|
18
|
+
lastActiveAt: 1,
|
|
19
|
+
claudeSessionId: null,
|
|
20
|
+
parentSessionId: 'session-parent',
|
|
21
|
+
sessionType: 'human',
|
|
22
|
+
agentId: 'agent-1',
|
|
23
|
+
messages: [],
|
|
24
|
+
} as never,
|
|
25
|
+
currentAgent: {
|
|
26
|
+
name: 'Builder',
|
|
27
|
+
delegationTargetMode: 'selected',
|
|
28
|
+
delegationTargetAgentIds: ['qa-1', 'ops-1'],
|
|
29
|
+
} as never,
|
|
30
|
+
activeProjectContext: {
|
|
31
|
+
projectId: 'project-1',
|
|
32
|
+
project: { name: 'Northstar' },
|
|
33
|
+
} as never,
|
|
34
|
+
enabledExtensions: ['files', 'manage_sessions', 'codex_cli'],
|
|
35
|
+
toolPolicy: {
|
|
36
|
+
mode: 'balanced',
|
|
37
|
+
requestedExtensions: ['files', 'manage_sessions', 'codex_cli', 'manage_secrets'],
|
|
38
|
+
enabledExtensions: ['files', 'manage_sessions', 'codex_cli'],
|
|
39
|
+
blockedExtensions: [{ tool: 'manage_secrets', reason: 'blocked by policy', source: 'policy' }],
|
|
40
|
+
},
|
|
41
|
+
rootSessionId: 'session-root',
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
assert.equal(payload.promptMode, 'minimal')
|
|
45
|
+
assert.equal(payload.projectId, 'project-1')
|
|
46
|
+
assert.equal(payload.projectName, 'Northstar')
|
|
47
|
+
assert.equal(payload.provider, 'openai')
|
|
48
|
+
assert.equal(payload.model, 'gpt-5')
|
|
49
|
+
assert.equal(payload.rootSessionId, 'session-root')
|
|
50
|
+
assert.deepEqual(payload.enabledExtensions, ['files', 'manage_sessions', 'codex_cli'])
|
|
51
|
+
assert.deepEqual(payload.blockedExtensions, [{ tool: 'manage_secrets', reason: 'blocked by policy', source: 'policy' }])
|
|
52
|
+
assert.equal(payload.delegationEnabled, true)
|
|
53
|
+
assert.equal(payload.delegationTargetMode, 'selected')
|
|
54
|
+
assert.deepEqual(payload.delegationTargetAgentIds, ['qa-1', 'ops-1'])
|
|
55
|
+
})
|
|
56
|
+
})
|
|
@@ -3,10 +3,19 @@ import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
|
3
3
|
import { genId } from '@/lib/id'
|
|
4
4
|
import { loadSessions, saveSessions, loadAgents } from '../storage'
|
|
5
5
|
import type { ToolBuildContext } from './context'
|
|
6
|
-
import type {
|
|
6
|
+
import type { ActiveProjectContext } from '@/lib/server/project-context'
|
|
7
|
+
import type { SessionToolPolicyDecision } from '@/lib/server/tool-capability-policy'
|
|
8
|
+
import type { Agent, Extension, ExtensionHooks, Session } from '@/types'
|
|
7
9
|
import { registerNativeCapability } from '../native-capabilities'
|
|
8
10
|
import { normalizeToolInputArgs } from './normalize-tool-args'
|
|
9
|
-
import { getEnabledCapabilitySelection } from '@/lib/capability-selection'
|
|
11
|
+
import { getEnabledCapabilityIds, getEnabledCapabilitySelection } from '@/lib/capability-selection'
|
|
12
|
+
import { getExtensionManager } from '@/lib/server/extensions'
|
|
13
|
+
import { canonicalizeExtensionId, expandExtensionIds } from '@/lib/server/tool-aliases'
|
|
14
|
+
import { resolvePromptMode } from '@/lib/server/chat-execution/prompt-mode'
|
|
15
|
+
import { resolveActiveProjectContext } from '@/lib/server/project-context'
|
|
16
|
+
import { resolveSessionLineageIds } from '@/lib/server/sessions/session-lineage'
|
|
17
|
+
import { loadSettings } from '@/lib/server/settings/settings-repository'
|
|
18
|
+
import { resolveSessionToolPolicy } from '@/lib/server/tool-capability-policy'
|
|
10
19
|
|
|
11
20
|
/**
|
|
12
21
|
* Core Session Info Execution Logic
|
|
@@ -15,17 +24,111 @@ async function executeWhoAmI(context: { sessionId?: string; agentId?: string })
|
|
|
15
24
|
try {
|
|
16
25
|
const sessions = loadSessions()
|
|
17
26
|
const current = context.sessionId ? sessions[context.sessionId] : null
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
const agents = loadAgents()
|
|
28
|
+
const agentRecord = (context.agentId ? agents[context.agentId] : null) || (current?.agentId ? agents[current.agentId] : null) || null
|
|
29
|
+
const { toolPolicy, enabledExtensions } = resolveSessionIdentityAccess(current)
|
|
30
|
+
const activeProjectContext = resolveActiveProjectContext(current || { agentId: context.agentId || null, cwd: null, projectId: null })
|
|
31
|
+
const { rootSessionId } = resolveSessionLineageIds(current || { id: context.sessionId || '', parentSessionId: null })
|
|
32
|
+
return JSON.stringify(buildSessionIdentityPayload({
|
|
33
|
+
context,
|
|
34
|
+
currentSession: current,
|
|
35
|
+
currentAgent: agentRecord || null,
|
|
36
|
+
activeProjectContext,
|
|
37
|
+
enabledExtensions,
|
|
38
|
+
toolPolicy,
|
|
39
|
+
rootSessionId,
|
|
40
|
+
}))
|
|
26
41
|
} catch (err: any) { return `Error: ${err.message}` }
|
|
27
42
|
}
|
|
28
43
|
|
|
44
|
+
function normalizeRuntimeExtensionId(extensionId: string): string {
|
|
45
|
+
const normalized = extensionId.trim().toLowerCase()
|
|
46
|
+
if (!normalized) return ''
|
|
47
|
+
if (normalized === 'delegate_to_claude_code' || normalized === 'claude_code') return 'claude_code'
|
|
48
|
+
if (normalized === 'delegate_to_codex_cli' || normalized === 'codex_cli') return 'codex_cli'
|
|
49
|
+
if (normalized === 'delegate_to_opencode_cli' || normalized === 'opencode_cli') return 'opencode_cli'
|
|
50
|
+
if (normalized === 'delegate_to_gemini_cli' || normalized === 'gemini_cli') return 'gemini_cli'
|
|
51
|
+
if (['session_info', 'sessions_tool', 'whoami_tool', 'search_history_tool'].includes(normalized)) return 'manage_sessions'
|
|
52
|
+
return canonicalizeExtensionId(normalized)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function canonicalizeEnabledExtensions(enabledExtensions: string[]): string[] {
|
|
56
|
+
const seen = new Set<string>()
|
|
57
|
+
const values: string[] = []
|
|
58
|
+
for (const extensionId of enabledExtensions) {
|
|
59
|
+
const normalized = normalizeRuntimeExtensionId(extensionId)
|
|
60
|
+
if (!normalized || seen.has(normalized)) continue
|
|
61
|
+
seen.add(normalized)
|
|
62
|
+
values.push(normalized)
|
|
63
|
+
}
|
|
64
|
+
return values
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function resolveSessionIdentityAccess(current: Session | null): {
|
|
68
|
+
toolPolicy: SessionToolPolicyDecision
|
|
69
|
+
enabledExtensions: string[]
|
|
70
|
+
} {
|
|
71
|
+
const rawExtensions = getEnabledCapabilityIds(current)
|
|
72
|
+
const hasShellCapability = rawExtensions.some((toolId) => ['shell', 'execute_command'].includes(String(toolId)))
|
|
73
|
+
const extensionManager = getExtensionManager()
|
|
74
|
+
const requestedExtensions = expandExtensionIds([
|
|
75
|
+
...rawExtensions,
|
|
76
|
+
...(hasShellCapability ? ['process'] : []),
|
|
77
|
+
]).filter((id) => !extensionManager.isExplicitlyDisabled(id))
|
|
78
|
+
const toolPolicy = resolveSessionToolPolicy(requestedExtensions, loadSettings())
|
|
79
|
+
const blockedExtensionIds = new Set(expandExtensionIds(toolPolicy.blockedExtensions.map((entry) => entry.tool)))
|
|
80
|
+
const enabledExtensions = canonicalizeEnabledExtensions(
|
|
81
|
+
expandExtensionIds(toolPolicy.enabledExtensions)
|
|
82
|
+
.filter((id) => !blockedExtensionIds.has(id))
|
|
83
|
+
.filter((id) => !extensionManager.isExplicitlyDisabled(id)),
|
|
84
|
+
)
|
|
85
|
+
return { toolPolicy, enabledExtensions }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function buildSessionIdentityPayload(params: {
|
|
89
|
+
context: { sessionId?: string; agentId?: string }
|
|
90
|
+
currentSession: Session | null
|
|
91
|
+
currentAgent?: Agent | null
|
|
92
|
+
activeProjectContext?: ActiveProjectContext | null
|
|
93
|
+
enabledExtensions?: string[]
|
|
94
|
+
toolPolicy?: SessionToolPolicyDecision | null
|
|
95
|
+
rootSessionId?: string | null
|
|
96
|
+
}): Record<string, unknown> {
|
|
97
|
+
const current = params.currentSession
|
|
98
|
+
const currentAgent = params.currentAgent || null
|
|
99
|
+
const activeProjectContext = params.activeProjectContext || null
|
|
100
|
+
const enabledExtensions = Array.isArray(params.enabledExtensions) ? params.enabledExtensions : []
|
|
101
|
+
const toolPolicy = params.toolPolicy || null
|
|
102
|
+
const delegationEnabled = enabledExtensions.some((extensionId) => ['delegate', 'spawn_subagent', 'claude_code', 'codex_cli', 'opencode_cli', 'gemini_cli'].includes(extensionId))
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
sessionId: params.context.sessionId || undefined,
|
|
106
|
+
sessionName: current?.name || undefined,
|
|
107
|
+
sessionType: current?.sessionType || undefined,
|
|
108
|
+
sessionKind: current?.parentSessionId ? 'delegated_child' : 'root_chat',
|
|
109
|
+
promptMode: current ? resolvePromptMode(current) : undefined,
|
|
110
|
+
user: current?.user || undefined,
|
|
111
|
+
agentId: params.context.agentId || current?.agentId || undefined,
|
|
112
|
+
agentName: typeof currentAgent?.name === 'string' ? currentAgent.name : undefined,
|
|
113
|
+
parentSessionId: current?.parentSessionId || undefined,
|
|
114
|
+
rootSessionId: params.rootSessionId || current?.id || undefined,
|
|
115
|
+
cwd: current?.cwd || undefined,
|
|
116
|
+
projectId: activeProjectContext?.projectId || undefined,
|
|
117
|
+
projectName: activeProjectContext?.project?.name || undefined,
|
|
118
|
+
provider: current?.provider || undefined,
|
|
119
|
+
model: current?.model || undefined,
|
|
120
|
+
enabledExtensions,
|
|
121
|
+
blockedExtensions: toolPolicy?.blockedExtensions || [],
|
|
122
|
+
delegationEnabled,
|
|
123
|
+
delegationTargetMode: delegationEnabled
|
|
124
|
+
? (currentAgent?.delegationTargetMode === 'selected' ? 'selected' : 'all')
|
|
125
|
+
: undefined,
|
|
126
|
+
delegationTargetAgentIds: delegationEnabled && Array.isArray(currentAgent?.delegationTargetAgentIds)
|
|
127
|
+
? currentAgent.delegationTargetAgentIds
|
|
128
|
+
: [],
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
29
132
|
function inferSessionsAction(
|
|
30
133
|
normalized: Record<string, unknown>,
|
|
31
134
|
context: { sessionId?: string; agentId?: string },
|
|
@@ -133,8 +236,12 @@ const SessionInfoExtension: Extension = {
|
|
|
133
236
|
name: 'Core Session Info',
|
|
134
237
|
description: 'Identify current session context and manage other agent sessions.',
|
|
135
238
|
hooks: {
|
|
136
|
-
getCapabilityDescription: () => 'I can manage chat sessions (`sessions_tool`) —
|
|
137
|
-
getOperatingGuidance: () =>
|
|
239
|
+
getCapabilityDescription: () => 'I can manage chat sessions (`sessions_tool`) — inspect live harness/session context with action `identity`, look up past session messages with `history`, spawn sessions, and coordinate work.',
|
|
240
|
+
getOperatingGuidance: () => [
|
|
241
|
+
'Use `sessions_tool` action `identity` when you need live session, project, lineage, or enabled-tool context.',
|
|
242
|
+
'Use `sessions_tool` action `history` only when you need earlier messages from this same session that are not already visible in the current thread.',
|
|
243
|
+
'Inspect existing chats before creating duplicates.',
|
|
244
|
+
],
|
|
138
245
|
} as ExtensionHooks,
|
|
139
246
|
tools: [
|
|
140
247
|
{
|
|
@@ -2,7 +2,9 @@ import { z } from 'zod'
|
|
|
2
2
|
import { tool, type StructuredToolInterface } from '@langchain/core/tools'
|
|
3
3
|
import type { Agent, Session, SessionSkillRuntimeState } from '@/types'
|
|
4
4
|
import { errorMessage } from '@/lib/shared-utils'
|
|
5
|
-
import { loadAgent
|
|
5
|
+
import { loadAgent } from '@/lib/server/agents/agent-repository'
|
|
6
|
+
import { loadSkills } from '@/lib/server/skills/skill-repository'
|
|
7
|
+
import { patchSession } from '@/lib/server/sessions/session-repository'
|
|
6
8
|
import {
|
|
7
9
|
findResolvedSkill,
|
|
8
10
|
recommendRuntimeSkillsForTask,
|
|
@@ -4,11 +4,15 @@ import { dedup, errorMessage } from '@/lib/shared-utils'
|
|
|
4
4
|
import { requestApproval } from '@/lib/server/approvals'
|
|
5
5
|
import {
|
|
6
6
|
loadAgent,
|
|
7
|
-
loadApprovals,
|
|
8
|
-
loadSkills,
|
|
9
7
|
patchAgent,
|
|
10
|
-
|
|
11
|
-
} from '@/lib/server/
|
|
8
|
+
} from '@/lib/server/agents/agent-repository'
|
|
9
|
+
import { loadApproval, loadApprovals } from '@/lib/server/approvals/approval-repository'
|
|
10
|
+
import {
|
|
11
|
+
deleteSkill,
|
|
12
|
+
loadSkill,
|
|
13
|
+
loadSkills,
|
|
14
|
+
saveSkill,
|
|
15
|
+
} from '@/lib/server/skills/skill-repository'
|
|
12
16
|
import { fetchSkillContent, searchClawHub } from '@/lib/server/skills/clawhub-client'
|
|
13
17
|
import { clearDiscoveredSkillsCache } from '@/lib/server/skills/skill-discovery'
|
|
14
18
|
import {
|
|
@@ -133,11 +137,10 @@ function upsertStoredSkill(input: {
|
|
|
133
137
|
existingId?: string
|
|
134
138
|
body: Record<string, unknown>
|
|
135
139
|
}): Skill {
|
|
136
|
-
const skills = loadSkills()
|
|
137
140
|
const normalized = normalizeSkillPayload(input.body)
|
|
138
141
|
const now = Date.now()
|
|
139
142
|
const id = input.existingId || genId()
|
|
140
|
-
const previous = input.existingId ?
|
|
143
|
+
const previous = input.existingId ? loadSkill(input.existingId) : null
|
|
141
144
|
|
|
142
145
|
const next: Skill = {
|
|
143
146
|
id,
|
|
@@ -171,8 +174,7 @@ function upsertStoredSkill(input: {
|
|
|
171
174
|
updatedAt: now,
|
|
172
175
|
}
|
|
173
176
|
|
|
174
|
-
|
|
175
|
-
saveSkills(skills)
|
|
177
|
+
saveSkill(id, next)
|
|
176
178
|
clearDiscoveredSkillsCache()
|
|
177
179
|
return next
|
|
178
180
|
}
|
|
@@ -241,7 +243,7 @@ function ensureApprovedInstall(approvalId: string | null | undefined): ApprovalR
|
|
|
241
243
|
if (!normalized) {
|
|
242
244
|
throw new Error('This install requires approval. Call manage_skills install first to create the approval request, then retry with approvalId after approval.')
|
|
243
245
|
}
|
|
244
|
-
const approval =
|
|
246
|
+
const approval = loadApproval(normalized)
|
|
245
247
|
if (!approval) throw new Error(`Approval "${normalized}" not found.`)
|
|
246
248
|
if (approval.status !== 'approved') {
|
|
247
249
|
throw new Error(`Approval "${normalized}" is not approved yet.`)
|
|
@@ -251,7 +253,7 @@ function ensureApprovedInstall(approvalId: string | null | undefined): ApprovalR
|
|
|
251
253
|
|
|
252
254
|
async function materializeResolvedSkill(skill: ResolvedRuntimeSkill): Promise<Skill> {
|
|
253
255
|
const skills = loadSkills()
|
|
254
|
-
const existing = skill.storageId ?
|
|
256
|
+
const existing = skill.storageId ? loadSkill(skill.storageId) : null
|
|
255
257
|
if (existing) return existing
|
|
256
258
|
const duplicate = Object.values(skills).find((entry) =>
|
|
257
259
|
normalizeKey(entry.skillKey || entry.name) === normalizeKey(skill.skillKey || skill.name),
|
|
@@ -366,7 +368,7 @@ export async function executeManageSkillsAction(
|
|
|
366
368
|
case 'update': {
|
|
367
369
|
const skillId = typeof normalized.id === 'string' ? normalized.id.trim() : ''
|
|
368
370
|
if (!skillId) return 'Error: "id" is required for update action.'
|
|
369
|
-
const existing =
|
|
371
|
+
const existing = loadSkill(skillId)
|
|
370
372
|
if (!existing) return `Not found: skills "${skillId}"`
|
|
371
373
|
const updated = upsertStoredSkill({
|
|
372
374
|
existingId: skillId,
|
|
@@ -377,10 +379,8 @@ export async function executeManageSkillsAction(
|
|
|
377
379
|
case 'delete': {
|
|
378
380
|
const skillId = typeof normalized.id === 'string' ? normalized.id.trim() : ''
|
|
379
381
|
if (!skillId) return 'Error: "id" is required for delete action.'
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
delete skills[skillId]
|
|
383
|
-
saveSkills(skills)
|
|
382
|
+
if (!loadSkill(skillId)) return `Not found: skills "${skillId}"`
|
|
383
|
+
deleteSkill(skillId)
|
|
384
384
|
return JSON.stringify({ deleted: skillId })
|
|
385
385
|
}
|
|
386
386
|
case 'status': {
|
|
@@ -1,8 +1,41 @@
|
|
|
1
1
|
import assert from 'node:assert/strict'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import os from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { spawnSync } from 'node:child_process'
|
|
2
6
|
import { describe, it } from 'node:test'
|
|
3
7
|
|
|
4
8
|
import { buildSessionTools } from './index'
|
|
5
9
|
|
|
10
|
+
const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), '../../../..')
|
|
11
|
+
|
|
12
|
+
function runWithTempDataDir(script: string) {
|
|
13
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-subagent-tool-'))
|
|
14
|
+
try {
|
|
15
|
+
const result = spawnSync(process.execPath, ['--import', 'tsx', '--input-type=module', '--eval', script], {
|
|
16
|
+
cwd: repoRoot,
|
|
17
|
+
env: {
|
|
18
|
+
...process.env,
|
|
19
|
+
DATA_DIR: path.join(tempDir, 'data'),
|
|
20
|
+
WORKSPACE_DIR: path.join(tempDir, 'workspace'),
|
|
21
|
+
SWARMCLAW_BUILD_MODE: '1',
|
|
22
|
+
},
|
|
23
|
+
encoding: 'utf-8',
|
|
24
|
+
timeout: 30_000,
|
|
25
|
+
})
|
|
26
|
+
assert.equal(result.status, 0, result.stderr || result.stdout || 'subprocess failed')
|
|
27
|
+
const lines = (result.stdout || '')
|
|
28
|
+
.trim()
|
|
29
|
+
.split('\n')
|
|
30
|
+
.map((line) => line.trim())
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
const jsonLine = [...lines].reverse().find((line) => line.startsWith('{'))
|
|
33
|
+
return JSON.parse(jsonLine || '{}')
|
|
34
|
+
} finally {
|
|
35
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
6
39
|
describe('spawn_subagent runtime access', () => {
|
|
7
40
|
it('hides spawn_subagent unless delegation is enabled', async () => {
|
|
8
41
|
const built = await buildSessionTools(process.cwd(), ['spawn_subagent'], {
|
|
@@ -42,9 +75,90 @@ describe('spawn_subagent runtime access', () => {
|
|
|
42
75
|
message: 'hello',
|
|
43
76
|
})
|
|
44
77
|
|
|
45
|
-
assert.match(String(raw), /
|
|
78
|
+
assert.match(String(raw), /allowed delegation list/i)
|
|
46
79
|
} finally {
|
|
47
80
|
await built.cleanup()
|
|
48
81
|
}
|
|
49
82
|
})
|
|
83
|
+
|
|
84
|
+
it('resolves best_fit start requests to the best allowed delegate', () => {
|
|
85
|
+
const output = runWithTempDataDir(`
|
|
86
|
+
const storageMod = await import('./src/lib/server/storage')
|
|
87
|
+
const toolsMod = await import('./src/lib/server/session-tools')
|
|
88
|
+
const storage = storageMod.default || storageMod
|
|
89
|
+
const toolsApi = toolsMod.default || toolsMod
|
|
90
|
+
|
|
91
|
+
const now = Date.now()
|
|
92
|
+
storage.saveAgents({
|
|
93
|
+
ceo: {
|
|
94
|
+
id: 'ceo',
|
|
95
|
+
name: 'CEO',
|
|
96
|
+
role: 'coordinator',
|
|
97
|
+
description: 'Executes through specialists',
|
|
98
|
+
systemPrompt: '',
|
|
99
|
+
provider: 'openai',
|
|
100
|
+
model: 'gpt-test',
|
|
101
|
+
capabilities: ['coordination', 'delegation', 'operations'],
|
|
102
|
+
delegationEnabled: true,
|
|
103
|
+
delegationTargetMode: 'selected',
|
|
104
|
+
delegationTargetAgentIds: ['builder', 'writer'],
|
|
105
|
+
createdAt: now,
|
|
106
|
+
updatedAt: now,
|
|
107
|
+
},
|
|
108
|
+
builder: {
|
|
109
|
+
id: 'builder',
|
|
110
|
+
name: 'Builder',
|
|
111
|
+
role: 'worker',
|
|
112
|
+
description: 'Builds product changes',
|
|
113
|
+
systemPrompt: '',
|
|
114
|
+
provider: 'openai',
|
|
115
|
+
model: 'gpt-test',
|
|
116
|
+
capabilities: ['coding', 'implementation', 'debugging'],
|
|
117
|
+
createdAt: now,
|
|
118
|
+
updatedAt: now,
|
|
119
|
+
},
|
|
120
|
+
writer: {
|
|
121
|
+
id: 'writer',
|
|
122
|
+
name: 'Writer',
|
|
123
|
+
role: 'worker',
|
|
124
|
+
description: 'Writes and edits content',
|
|
125
|
+
systemPrompt: '',
|
|
126
|
+
provider: 'openai',
|
|
127
|
+
model: 'gpt-test',
|
|
128
|
+
capabilities: ['writing', 'editing'],
|
|
129
|
+
createdAt: now,
|
|
130
|
+
updatedAt: now,
|
|
131
|
+
},
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const built = await toolsApi.buildSessionTools(process.env.WORKSPACE_DIR, ['spawn_subagent'], {
|
|
135
|
+
sessionId: 'session-best-fit',
|
|
136
|
+
agentId: 'ceo',
|
|
137
|
+
delegationEnabled: true,
|
|
138
|
+
delegationTargetMode: 'selected',
|
|
139
|
+
delegationTargetAgentIds: ['builder', 'writer'],
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const tool = built.tools.find((entry) => entry.name === 'spawn_subagent')
|
|
144
|
+
const raw = await tool.invoke({
|
|
145
|
+
action: 'start',
|
|
146
|
+
selectionMode: 'best_fit',
|
|
147
|
+
message: 'Implement the API change and fix the failing tests.',
|
|
148
|
+
workType: 'coding',
|
|
149
|
+
requiredCapabilities: ['coding', 'debugging'],
|
|
150
|
+
background: true,
|
|
151
|
+
})
|
|
152
|
+
console.log(JSON.stringify(JSON.parse(String(raw))))
|
|
153
|
+
} finally {
|
|
154
|
+
await built.cleanup()
|
|
155
|
+
}
|
|
156
|
+
process.exit(0)
|
|
157
|
+
`)
|
|
158
|
+
|
|
159
|
+
assert.equal(output.selectionMode, 'best_fit')
|
|
160
|
+
assert.equal(output.agentId, 'builder')
|
|
161
|
+
assert.equal(output.workType, 'coding')
|
|
162
|
+
assert.deepEqual(output.requiredCapabilities, ['coding', 'debugging'])
|
|
163
|
+
})
|
|
50
164
|
})
|