@swarmclawai/swarmclaw 1.2.6 → 1.2.9
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 +54 -23
- package/next.config.ts +1 -0
- package/package.json +4 -3
- package/scripts/easy-setup.mjs +1 -1
- package/scripts/postinstall.mjs +1 -1
- package/skills/swarmclaw.md +115 -0
- package/skills/tools/browser.md +131 -0
- package/skills/tools/execute.md +98 -0
- package/skills/tools/files.md +98 -0
- package/skills/tools/memory.md +104 -0
- package/skills/tools/platform.md +144 -0
- package/skills/tools/skills.md +83 -0
- package/src/app/agents/[id]/page.tsx +1 -18
- package/src/app/api/agents/thread-route.test.ts +0 -1
- package/src/app/api/approvals/route.test.ts +6 -22
- package/src/app/api/chats/[id]/messages/route.ts +23 -19
- package/src/app/api/chats/messages-route.test.ts +105 -51
- package/src/app/api/connectors/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/test/route.ts +3 -2
- package/src/app/api/openclaw/deploy/route.ts +2 -0
- package/src/app/api/portability/export/route.ts +8 -0
- package/src/app/api/portability/import/route.test.ts +80 -0
- package/src/app/api/portability/import/route.ts +28 -0
- package/src/app/api/settings/route.ts +0 -2
- package/src/app/api/setup/doctor/route.ts +4 -4
- package/src/app/api/wallets/[id]/route.ts +15 -157
- package/src/app/api/wallets/generate/route.ts +22 -0
- package/src/app/api/wallets/route.test.ts +147 -0
- package/src/app/api/wallets/route.ts +13 -95
- package/src/app/autonomy/page.tsx +2 -57
- package/src/app/protocols/page.tsx +2 -21
- package/src/app/settings/page.tsx +0 -9
- package/src/app/wallets/page.tsx +105 -5
- package/src/cli/index.js +21 -33
- package/src/cli/spec.js +19 -30
- package/src/components/agents/agent-chat-list.tsx +23 -1
- package/src/components/agents/agent-sheet.tsx +2 -40
- package/src/components/agents/inspector-panel.tsx +165 -131
- package/src/components/chat/chat-area.tsx +38 -9
- package/src/components/chat/chat-card.tsx +0 -31
- package/src/components/chat/message-bubble.tsx +1 -108
- package/src/components/chat/message-list.tsx +33 -19
- package/src/components/connectors/connector-sheet.tsx +25 -1
- package/src/components/gateways/gateway-sheet.tsx +5 -2
- package/src/components/layout/sidebar-rail.tsx +6 -10
- package/src/components/projects/project-detail.tsx +3 -35
- package/src/components/projects/tabs/overview-tab.tsx +3 -59
- package/src/components/projects/tabs/work-tab.tsx +7 -77
- package/src/components/protocols/structured-session-launcher.tsx +1 -22
- package/src/components/shared/connector-platform-icon.tsx +1 -0
- package/src/components/tasks/task-card.tsx +4 -34
- package/src/components/tasks/task-sheet.tsx +6 -36
- package/src/components/wallets/wallet-list.tsx +150 -0
- package/src/lib/agent-execute-defaults.test.ts +24 -0
- package/src/lib/agent-execute-defaults.ts +62 -0
- package/src/lib/app/navigation.test.ts +0 -13
- package/src/lib/app/navigation.ts +2 -7
- package/src/lib/app/view-constants.ts +14 -19
- package/src/lib/chat/queued-message-queue.test.ts +134 -1
- package/src/lib/chat/queued-message-queue.ts +77 -2
- package/src/lib/server/agents/agent-service.ts +5 -0
- package/src/lib/server/agents/agent-thread-session.ts +0 -1
- package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
- package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
- package/src/lib/server/agents/delegation-jobs.ts +0 -25
- package/src/lib/server/agents/main-agent-loop.ts +1 -49
- package/src/lib/server/agents/subagent-runtime.ts +0 -1
- package/src/lib/server/approval-match.ts +0 -85
- package/src/lib/server/approvals.test.ts +6 -6
- package/src/lib/server/approvals.ts +0 -6
- package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
- package/src/lib/server/builtin-extensions.ts +1 -2
- package/src/lib/server/capability-router.test.ts +0 -2
- package/src/lib/server/chat-execution/chat-execution-advanced.test.ts +1 -1
- package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +15 -14
- package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
- package/src/lib/server/chat-execution/chat-execution-utils.ts +2 -4
- package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +81 -64
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -0
- package/src/lib/server/chat-execution/continuation-evaluator.ts +8 -0
- package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
- package/src/lib/server/chat-execution/memory-mutation-tools.ts +1 -1
- package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
- package/src/lib/server/chat-execution/message-classifier.ts +11 -16
- package/src/lib/server/chat-execution/prompt-builder.test.ts +27 -0
- package/src/lib/server/chat-execution/prompt-builder.ts +14 -31
- package/src/lib/server/chat-execution/prompt-mode.test.ts +24 -0
- package/src/lib/server/chat-execution/prompt-mode.ts +5 -1
- package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
- package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
- package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
- package/src/lib/server/chat-execution/stream-agent-chat.test.ts +13 -126
- package/src/lib/server/chat-execution/stream-agent-chat.ts +46 -21
- package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
- package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
- package/src/lib/server/chatrooms/chatroom-routing.test.ts +4 -0
- package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
- package/src/lib/server/chats/chat-session-service.ts +3 -5
- package/src/lib/server/connectors/connector-inbound.ts +0 -1
- package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
- package/src/lib/server/connectors/connector-service.ts +39 -9
- package/src/lib/server/connectors/discord.ts +2 -2
- package/src/lib/server/connectors/matrix.ts +3 -2
- package/src/lib/server/connectors/signal.ts +5 -4
- package/src/lib/server/connectors/slack.ts +10 -9
- package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
- package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
- package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
- package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
- package/src/lib/server/connectors/swarmdock-tasks.ts +119 -0
- package/src/lib/server/connectors/swarmdock.ts +255 -0
- package/src/lib/server/connectors/teams.ts +3 -2
- package/src/lib/server/connectors/telegram.ts +4 -4
- package/src/lib/server/connectors/whatsapp.ts +2 -2
- package/src/lib/server/daemon/controller.ts +7 -0
- package/src/lib/server/execution-brief.test.ts +2 -25
- package/src/lib/server/execution-brief.ts +12 -35
- package/src/lib/server/execution-engine/task-attempt.ts +0 -1
- package/src/lib/server/gateways/gateway-profile-service.ts +19 -1
- package/src/lib/server/messages/message-repository.test.ts +70 -0
- package/src/lib/server/messages/message-repository.ts +11 -6
- package/src/lib/server/openclaw/deploy.ts +32 -2
- package/src/lib/server/persistence/storage-context.ts +0 -5
- package/src/lib/server/plugins-advanced.test.ts +1 -2
- package/src/lib/server/portability/export.ts +109 -0
- package/src/lib/server/portability/import.ts +159 -0
- package/src/lib/server/protocols/protocol-normalization.ts +0 -4
- package/src/lib/server/protocols/protocol-queries.ts +0 -6
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
- package/src/lib/server/protocols/protocol-service.ts +0 -1
- package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
- package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
- package/src/lib/server/protocols/protocol-swarm.ts +0 -2
- package/src/lib/server/protocols/protocol-types.ts +0 -2
- package/src/lib/server/provider-health.ts +1 -10
- package/src/lib/server/runtime/daemon-state/core.ts +0 -9
- package/src/lib/server/runtime/daemon-state.test.ts +0 -35
- package/src/lib/server/runtime/heartbeat-service.ts +3 -23
- package/src/lib/server/runtime/process-manager.ts +13 -9
- package/src/lib/server/runtime/queue/core.ts +11 -33
- package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
- package/src/lib/server/runtime/scheduler.ts +0 -13
- package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
- package/src/lib/server/runtime/session-run-manager/queries.ts +15 -1
- package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
- package/src/lib/server/runtime/session-run-manager.test.ts +58 -28
- package/src/lib/server/sandbox/session-runtime.test.ts +18 -1
- package/src/lib/server/sandbox/session-runtime.ts +40 -28
- package/src/lib/server/session-tools/autonomy-tools.test.ts +7 -9
- package/src/lib/server/session-tools/context.ts +1 -1
- package/src/lib/server/session-tools/credential-env.ts +109 -0
- package/src/lib/server/session-tools/crud.ts +3 -17
- package/src/lib/server/session-tools/delegate.ts +0 -4
- package/src/lib/server/session-tools/edit_file.ts +3 -2
- package/src/lib/server/session-tools/execute.test.ts +58 -0
- package/src/lib/server/session-tools/execute.ts +334 -0
- package/src/lib/server/session-tools/files-tool.ts +635 -0
- package/src/lib/server/session-tools/index.ts +14 -8
- package/src/lib/server/session-tools/memory-tool.ts +242 -0
- package/src/lib/server/session-tools/memory.ts +1 -1
- package/src/lib/server/session-tools/openclaw-nodes.ts +3 -2
- package/src/lib/server/session-tools/openclaw-workspace.ts +3 -2
- package/src/lib/server/session-tools/platform-tool.ts +617 -0
- package/src/lib/server/session-tools/session-info.ts +3 -2
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +3 -4
- package/src/lib/server/session-tools/shell.ts +7 -122
- package/src/lib/server/session-tools/skills-tool.ts +396 -0
- package/src/lib/server/session-tools/team-context.ts +0 -3
- package/src/lib/server/session-tools/web.ts +2 -2
- package/src/lib/server/storage-normalization.ts +10 -0
- package/src/lib/server/storage.ts +18 -45
- package/src/lib/server/tasks/task-checkout.ts +59 -0
- package/src/lib/server/tasks/task-lifecycle.ts +2 -0
- package/src/lib/server/tasks/task-route-service.ts +4 -26
- package/src/lib/server/tasks/task-service.ts +0 -7
- package/src/lib/server/tool-aliases.ts +2 -2
- package/src/lib/server/tool-capability-policy-advanced.test.ts +13 -6
- package/src/lib/server/tool-capability-policy.test.ts +2 -1
- package/src/lib/server/tool-capability-policy.ts +60 -35
- package/src/lib/server/tool-planning.ts +11 -12
- package/src/lib/server/universal-tool-access.ts +0 -1
- package/src/lib/server/wallets/wallet-crypto.ts +33 -0
- package/src/lib/server/wallets/wallet-repository.ts +24 -0
- package/src/lib/server/wallets/wallet-service.ts +119 -0
- package/src/lib/server/working-state/extraction.ts +8 -42
- package/src/lib/server/working-state/normalization.ts +10 -103
- package/src/lib/server/working-state/service.ts +12 -21
- package/src/lib/setup-defaults.ts +5 -0
- package/src/lib/strip-internal-metadata.test.ts +1 -1
- package/src/lib/strip-internal-metadata.ts +1 -1
- package/src/lib/tool-definitions.ts +1 -1
- package/src/lib/validation/schemas.test.ts +16 -0
- package/src/lib/validation/schemas.ts +49 -2
- package/src/stores/slices/data-slice.ts +5 -1
- package/src/stores/slices/ui-slice.ts +0 -4
- package/src/stores/use-chat-store.test.ts +231 -0
- package/src/stores/use-chat-store.ts +62 -13
- package/src/types/agent.ts +264 -0
- package/src/types/app-settings.ts +173 -0
- package/src/types/approval.ts +25 -0
- package/src/types/connector.ts +188 -0
- package/src/types/extension.ts +386 -0
- package/src/types/index.ts +16 -3555
- package/src/types/message.ts +56 -0
- package/src/types/misc.ts +737 -0
- package/src/types/protocol.ts +420 -0
- package/src/types/provider.ts +52 -0
- package/src/types/run.ts +180 -0
- package/src/types/schedule.ts +59 -0
- package/src/types/session.ts +215 -0
- package/src/types/skill.ts +157 -0
- package/src/types/swarmdock.ts +29 -0
- package/src/types/task.ts +144 -0
- package/src/types/working-state.ts +204 -0
- package/src/views/settings/section-heartbeat.tsx +2 -2
- package/src/views/settings/section-runtime-loop.tsx +0 -14
- package/src/app/api/canvas/[sessionId]/route.ts +0 -35
- package/src/app/api/missions/[id]/actions/route.ts +0 -31
- package/src/app/api/missions/[id]/events/route.ts +0 -14
- package/src/app/api/missions/[id]/route.ts +0 -10
- package/src/app/api/missions/route.test.ts +0 -244
- package/src/app/api/missions/route.ts +0 -57
- package/src/app/api/wallets/[id]/approve/route.ts +0 -79
- package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
- package/src/app/api/wallets/[id]/send/route.ts +0 -113
- package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
- package/src/app/missions/[id]/page.tsx +0 -3
- package/src/app/missions/page.tsx +0 -685
- package/src/components/canvas/canvas-panel.tsx +0 -267
- package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
- package/src/components/wallets/wallet-panel.tsx +0 -1010
- package/src/components/wallets/wallet-section.tsx +0 -260
- package/src/features/missions/queries.ts +0 -23
- package/src/lib/canvas-content.test.ts +0 -360
- package/src/lib/canvas-content.ts +0 -198
- package/src/lib/server/canvas-content.test.ts +0 -32
- package/src/lib/server/canvas-content.ts +0 -6
- package/src/lib/server/ethereum.ts +0 -591
- package/src/lib/server/evm-swap.ts +0 -476
- package/src/lib/server/missions/mission-intent.test.ts +0 -63
- package/src/lib/server/missions/mission-intent.ts +0 -569
- package/src/lib/server/missions/mission-repository.ts +0 -74
- package/src/lib/server/missions/mission-service/actions.ts +0 -6
- package/src/lib/server/missions/mission-service/bindings.ts +0 -9
- package/src/lib/server/missions/mission-service/context.ts +0 -4
- package/src/lib/server/missions/mission-service/core.ts +0 -2271
- package/src/lib/server/missions/mission-service/queries.ts +0 -12
- package/src/lib/server/missions/mission-service/recovery.ts +0 -5
- package/src/lib/server/missions/mission-service/ticks.ts +0 -9
- package/src/lib/server/missions/mission-service.test.ts +0 -888
- package/src/lib/server/missions/mission-service.ts +0 -6
- package/src/lib/server/session-tools/canvas.ts +0 -105
- package/src/lib/server/session-tools/sandbox.ts +0 -281
- package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
- package/src/lib/server/session-tools/wallet.ts +0 -1287
- package/src/lib/server/solana.ts +0 -327
- package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
- package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
- package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
- package/src/lib/server/wallet/wallet-service.test.ts +0 -81
- package/src/lib/server/wallet/wallet-service.ts +0 -225
- package/src/lib/wallet/wallet-transactions.test.ts +0 -75
- package/src/lib/wallet/wallet-transactions.ts +0 -43
- package/src/lib/wallet/wallet.test.ts +0 -333
- package/src/lib/wallet/wallet.ts +0 -183
- package/src/views/settings/section-wallets.tsx +0 -35
|
@@ -1,888 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
import { describe, it } from 'node:test'
|
|
3
|
-
import { runWithTempDataDir } from '@/lib/server/test-utils/run-with-temp-data-dir'
|
|
4
|
-
|
|
5
|
-
describe('mission-service', () => {
|
|
6
|
-
it('creates and then completes a mission from model-backed turn decisions', () => {
|
|
7
|
-
const output = runWithTempDataDir<{
|
|
8
|
-
missionId: string | null
|
|
9
|
-
sessionMissionId: string | null
|
|
10
|
-
missionStatus: string | null
|
|
11
|
-
missionPhase: string | null
|
|
12
|
-
eventTypes: string[]
|
|
13
|
-
}>(`
|
|
14
|
-
const storageMod = await import('@/lib/server/storage')
|
|
15
|
-
const missionMod = await import('@/lib/server/missions/mission-service')
|
|
16
|
-
const storage = storageMod.default || storageMod['module.exports'] || storageMod
|
|
17
|
-
const missions = missionMod.default || missionMod['module.exports'] || missionMod
|
|
18
|
-
|
|
19
|
-
storage.saveSessions({
|
|
20
|
-
sessionA: {
|
|
21
|
-
id: 'sessionA',
|
|
22
|
-
name: 'Release Chat',
|
|
23
|
-
cwd: process.env.WORKSPACE_DIR,
|
|
24
|
-
user: 'tester',
|
|
25
|
-
provider: 'ollama',
|
|
26
|
-
model: 'test-model',
|
|
27
|
-
claudeSessionId: null,
|
|
28
|
-
messages: [
|
|
29
|
-
{ role: 'user', text: 'Can you prep the release and update the docs?', time: 1 },
|
|
30
|
-
],
|
|
31
|
-
createdAt: 1,
|
|
32
|
-
lastActiveAt: 1,
|
|
33
|
-
agentId: 'agentA',
|
|
34
|
-
},
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
storage.saveAgents({
|
|
38
|
-
agentA: {
|
|
39
|
-
id: 'agentA',
|
|
40
|
-
name: 'Agent A',
|
|
41
|
-
provider: 'ollama',
|
|
42
|
-
model: 'test-model',
|
|
43
|
-
systemPrompt: 'test',
|
|
44
|
-
},
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
const session = storage.loadSessions().sessionA
|
|
48
|
-
const mission = await missions.resolveMissionForTurn({
|
|
49
|
-
session,
|
|
50
|
-
message: 'Please prep the release and update the docs.',
|
|
51
|
-
source: 'chat',
|
|
52
|
-
internal: false,
|
|
53
|
-
runId: 'run-1',
|
|
54
|
-
generateText: async () => JSON.stringify({
|
|
55
|
-
action: 'create_new',
|
|
56
|
-
confidence: 0.96,
|
|
57
|
-
objective: 'Prepare the next release',
|
|
58
|
-
successCriteria: ['README updated', 'release validated'],
|
|
59
|
-
currentStep: 'Audit release requirements',
|
|
60
|
-
plannerSummary: 'Track the release prep as a durable mission.',
|
|
61
|
-
}),
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
const updated = await missions.applyMissionOutcomeForTurn({
|
|
65
|
-
session,
|
|
66
|
-
missionId: mission?.id || '',
|
|
67
|
-
source: 'chat',
|
|
68
|
-
runId: 'run-1',
|
|
69
|
-
message: 'Please prep the release and update the docs.',
|
|
70
|
-
assistantText: 'I updated the release checklist and verified the remaining steps are complete.',
|
|
71
|
-
toolEvents: [],
|
|
72
|
-
generateText: async () => JSON.stringify({
|
|
73
|
-
verdict: 'completed',
|
|
74
|
-
confidence: 0.88,
|
|
75
|
-
phase: 'completed',
|
|
76
|
-
verifierSummary: 'The release prep work is complete.',
|
|
77
|
-
}),
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
const persistedSession = storage.loadSessions().sessionA
|
|
81
|
-
const events = missions.listMissionEventsForMission(mission?.id || '')
|
|
82
|
-
|
|
83
|
-
console.log(JSON.stringify({
|
|
84
|
-
missionId: mission?.id || null,
|
|
85
|
-
sessionMissionId: persistedSession.missionId || null,
|
|
86
|
-
missionStatus: updated?.status || null,
|
|
87
|
-
missionPhase: updated?.phase || null,
|
|
88
|
-
eventTypes: events.map((event) => event.type),
|
|
89
|
-
}))
|
|
90
|
-
`, { prefix: 'swarmclaw-mission-service-' })
|
|
91
|
-
|
|
92
|
-
assert.ok(output.missionId)
|
|
93
|
-
assert.equal(output.sessionMissionId, output.missionId)
|
|
94
|
-
assert.equal(output.missionStatus, 'completed')
|
|
95
|
-
assert.equal(output.missionPhase, 'completed')
|
|
96
|
-
assert.deepEqual(output.eventTypes, ['created', 'run_result', 'completed'])
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
it('reuses the current mission and records waiting state when verification says to pause', () => {
|
|
100
|
-
const output = runWithTempDataDir<{
|
|
101
|
-
missionId: string | null
|
|
102
|
-
sameMission: boolean
|
|
103
|
-
status: string | null
|
|
104
|
-
waitKind: string | null
|
|
105
|
-
waitReason: string | null
|
|
106
|
-
}>(`
|
|
107
|
-
const storageMod = await import('@/lib/server/storage')
|
|
108
|
-
const missionMod = await import('@/lib/server/missions/mission-service')
|
|
109
|
-
const storage = storageMod.default || storageMod['module.exports'] || storageMod
|
|
110
|
-
const missions = missionMod.default || missionMod['module.exports'] || missionMod
|
|
111
|
-
|
|
112
|
-
storage.saveSessions({
|
|
113
|
-
sessionA: {
|
|
114
|
-
id: 'sessionA',
|
|
115
|
-
name: 'Main Chat',
|
|
116
|
-
cwd: process.env.WORKSPACE_DIR,
|
|
117
|
-
user: 'tester',
|
|
118
|
-
provider: 'ollama',
|
|
119
|
-
model: 'test-model',
|
|
120
|
-
claudeSessionId: null,
|
|
121
|
-
messages: [],
|
|
122
|
-
createdAt: 1,
|
|
123
|
-
lastActiveAt: 1,
|
|
124
|
-
agentId: 'agentA',
|
|
125
|
-
},
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
storage.saveAgents({
|
|
129
|
-
agentA: {
|
|
130
|
-
id: 'agentA',
|
|
131
|
-
name: 'Agent A',
|
|
132
|
-
provider: 'ollama',
|
|
133
|
-
model: 'test-model',
|
|
134
|
-
systemPrompt: 'test',
|
|
135
|
-
},
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
const session = storage.loadSessions().sessionA
|
|
139
|
-
const created = await missions.resolveMissionForTurn({
|
|
140
|
-
session,
|
|
141
|
-
message: 'Build the release dashboard and keep iterating on it.',
|
|
142
|
-
source: 'chat',
|
|
143
|
-
internal: false,
|
|
144
|
-
runId: 'run-1',
|
|
145
|
-
generateText: async () => JSON.stringify({
|
|
146
|
-
action: 'create_new',
|
|
147
|
-
confidence: 0.95,
|
|
148
|
-
objective: 'Build the release dashboard',
|
|
149
|
-
successCriteria: ['dashboard exists'],
|
|
150
|
-
currentStep: 'Create the first dashboard draft',
|
|
151
|
-
}),
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
const attached = await missions.resolveMissionForTurn({
|
|
155
|
-
session: storage.loadSessions().sessionA,
|
|
156
|
-
message: 'Now add a blocker summary section too.',
|
|
157
|
-
source: 'chat',
|
|
158
|
-
internal: false,
|
|
159
|
-
runId: 'run-2',
|
|
160
|
-
generateText: async () => JSON.stringify({
|
|
161
|
-
action: 'attach_current',
|
|
162
|
-
confidence: 0.89,
|
|
163
|
-
currentStep: 'Add a blocker summary section',
|
|
164
|
-
}),
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
const updated = await missions.applyMissionOutcomeForTurn({
|
|
168
|
-
session: storage.loadSessions().sessionA,
|
|
169
|
-
missionId: attached?.id || '',
|
|
170
|
-
source: 'chat',
|
|
171
|
-
runId: 'run-2',
|
|
172
|
-
message: 'Now add a blocker summary section too.',
|
|
173
|
-
assistantText: 'I need a design approval before I can finish the dashboard changes.',
|
|
174
|
-
toolEvents: [],
|
|
175
|
-
generateText: async () => JSON.stringify({
|
|
176
|
-
verdict: 'waiting',
|
|
177
|
-
confidence: 0.83,
|
|
178
|
-
phase: 'waiting',
|
|
179
|
-
currentStep: 'Wait for design approval',
|
|
180
|
-
verifierSummary: 'The dashboard mission is waiting on a design approval.',
|
|
181
|
-
waitKind: 'approval',
|
|
182
|
-
waitReason: 'Design approval is still pending.',
|
|
183
|
-
}),
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
console.log(JSON.stringify({
|
|
187
|
-
missionId: created?.id || null,
|
|
188
|
-
sameMission: created?.id === attached?.id,
|
|
189
|
-
status: updated?.status || null,
|
|
190
|
-
waitKind: updated?.waitState?.kind || null,
|
|
191
|
-
waitReason: updated?.waitState?.reason || null,
|
|
192
|
-
}))
|
|
193
|
-
`, { prefix: 'swarmclaw-mission-service-' })
|
|
194
|
-
|
|
195
|
-
assert.ok(output.missionId)
|
|
196
|
-
assert.equal(output.sameMission, true)
|
|
197
|
-
assert.equal(output.status, 'waiting')
|
|
198
|
-
assert.equal(output.waitKind, 'approval')
|
|
199
|
-
assert.equal(output.waitReason, 'Design approval is still pending.')
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
it('leaves unrelated one-shot turns missionless when classification says none', () => {
|
|
203
|
-
const output = runWithTempDataDir<{
|
|
204
|
-
resolvedMissionId: string | null
|
|
205
|
-
sessionMissionId: string | null
|
|
206
|
-
}>(`
|
|
207
|
-
const storageMod = await import('@/lib/server/storage')
|
|
208
|
-
const missionMod = await import('@/lib/server/missions/mission-service')
|
|
209
|
-
const storage = storageMod.default || storageMod['module.exports'] || storageMod
|
|
210
|
-
const missions = missionMod.default || missionMod['module.exports'] || missionMod
|
|
211
|
-
|
|
212
|
-
storage.saveSessions({
|
|
213
|
-
sessionA: {
|
|
214
|
-
id: 'sessionA',
|
|
215
|
-
name: 'Main Chat',
|
|
216
|
-
cwd: process.env.WORKSPACE_DIR,
|
|
217
|
-
user: 'tester',
|
|
218
|
-
provider: 'ollama',
|
|
219
|
-
model: 'test-model',
|
|
220
|
-
claudeSessionId: null,
|
|
221
|
-
messages: [],
|
|
222
|
-
createdAt: 1,
|
|
223
|
-
lastActiveAt: 1,
|
|
224
|
-
agentId: 'agentA',
|
|
225
|
-
missionId: 'missionA',
|
|
226
|
-
},
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
storage.saveAgents({
|
|
230
|
-
agentA: {
|
|
231
|
-
id: 'agentA',
|
|
232
|
-
name: 'Agent A',
|
|
233
|
-
provider: 'ollama',
|
|
234
|
-
model: 'test-model',
|
|
235
|
-
systemPrompt: 'test',
|
|
236
|
-
},
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
storage.saveMissions({
|
|
240
|
-
missionA: {
|
|
241
|
-
id: 'missionA',
|
|
242
|
-
source: 'chat',
|
|
243
|
-
sourceRef: { kind: 'chat', sessionId: 'sessionA' },
|
|
244
|
-
objective: 'Long-running dashboard mission',
|
|
245
|
-
status: 'active',
|
|
246
|
-
phase: 'planning',
|
|
247
|
-
sessionId: 'sessionA',
|
|
248
|
-
agentId: 'agentA',
|
|
249
|
-
taskIds: [],
|
|
250
|
-
childMissionIds: [],
|
|
251
|
-
dependencyMissionIds: [],
|
|
252
|
-
dependencyTaskIds: [],
|
|
253
|
-
currentStep: 'Wait for the next dashboard task',
|
|
254
|
-
createdAt: 1,
|
|
255
|
-
updatedAt: 1,
|
|
256
|
-
},
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
const session = storage.loadSessions().sessionA
|
|
260
|
-
const resolved = await missions.resolveMissionForTurn({
|
|
261
|
-
session,
|
|
262
|
-
message: 'Thanks',
|
|
263
|
-
source: 'chat',
|
|
264
|
-
internal: false,
|
|
265
|
-
runId: 'run-1',
|
|
266
|
-
generateText: async () => JSON.stringify({
|
|
267
|
-
action: 'none',
|
|
268
|
-
confidence: 0.98,
|
|
269
|
-
}),
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
const persistedSession = storage.loadSessions().sessionA
|
|
273
|
-
console.log(JSON.stringify({
|
|
274
|
-
resolvedMissionId: resolved?.id || null,
|
|
275
|
-
sessionMissionId: persistedSession.missionId || null,
|
|
276
|
-
}))
|
|
277
|
-
`, { prefix: 'swarmclaw-mission-service-' })
|
|
278
|
-
|
|
279
|
-
assert.equal(output.resolvedMissionId, null)
|
|
280
|
-
assert.equal(output.sessionMissionId, 'missionA')
|
|
281
|
-
})
|
|
282
|
-
|
|
283
|
-
it('dispatches linked backlog tasks from a mission tick', () => {
|
|
284
|
-
const output = runWithTempDataDir<{
|
|
285
|
-
missionId: string | null
|
|
286
|
-
missionPhase: string | null
|
|
287
|
-
taskStatus: string | null
|
|
288
|
-
plannerDecision: string | null
|
|
289
|
-
}>(`
|
|
290
|
-
const storageMod = await import('@/lib/server/storage')
|
|
291
|
-
const missionMod = await import('@/lib/server/missions/mission-service')
|
|
292
|
-
const queueMod = await import('@/lib/server/runtime/queue')
|
|
293
|
-
const storage = storageMod.default || storageMod['module.exports'] || storageMod
|
|
294
|
-
const missions = missionMod.default || missionMod['module.exports'] || missionMod
|
|
295
|
-
const queue = queueMod.default || queueMod['module.exports'] || queueMod
|
|
296
|
-
|
|
297
|
-
storage.saveAgents({
|
|
298
|
-
agentA: {
|
|
299
|
-
id: 'agentA',
|
|
300
|
-
name: 'Agent A',
|
|
301
|
-
provider: 'ollama',
|
|
302
|
-
model: 'test-model',
|
|
303
|
-
systemPrompt: 'test',
|
|
304
|
-
},
|
|
305
|
-
})
|
|
306
|
-
|
|
307
|
-
storage.saveTasks({
|
|
308
|
-
taskA: {
|
|
309
|
-
id: 'taskA',
|
|
310
|
-
title: 'Generate release notes',
|
|
311
|
-
description: 'Create the release notes artifact.',
|
|
312
|
-
status: 'backlog',
|
|
313
|
-
agentId: 'agentA',
|
|
314
|
-
createdAt: 1,
|
|
315
|
-
updatedAt: 1,
|
|
316
|
-
},
|
|
317
|
-
})
|
|
318
|
-
|
|
319
|
-
const mission = missions.ensureMissionForTask(storage.loadTasks().taskA, { source: 'task' })
|
|
320
|
-
const updated = await missions.runMissionTick(mission?.id || '', 'test', {
|
|
321
|
-
generateText: async () => JSON.stringify({
|
|
322
|
-
decision: 'dispatch_task',
|
|
323
|
-
confidence: 0.96,
|
|
324
|
-
summary: 'Queue the linked release notes task.',
|
|
325
|
-
taskId: 'taskA',
|
|
326
|
-
}),
|
|
327
|
-
})
|
|
328
|
-
const task = storage.loadTasks().taskA
|
|
329
|
-
|
|
330
|
-
console.log(JSON.stringify({
|
|
331
|
-
missionId: mission?.id || null,
|
|
332
|
-
missionPhase: updated?.phase || null,
|
|
333
|
-
taskStatus: task?.status || null,
|
|
334
|
-
plannerDecision: updated?.plannerState?.lastDecision || null,
|
|
335
|
-
}))
|
|
336
|
-
`, { prefix: 'swarmclaw-mission-service-' })
|
|
337
|
-
|
|
338
|
-
assert.ok(output.missionId)
|
|
339
|
-
assert.equal(output.missionPhase, 'dispatching')
|
|
340
|
-
assert.equal(output.taskStatus, 'queued')
|
|
341
|
-
assert.equal(output.plannerDecision, 'dispatch_task')
|
|
342
|
-
})
|
|
343
|
-
|
|
344
|
-
it('marks task-backed missions completed when all linked tasks are complete', () => {
|
|
345
|
-
const output = runWithTempDataDir<{
|
|
346
|
-
missionStatus: string | null
|
|
347
|
-
missionPhase: string | null
|
|
348
|
-
lastVerdict: string | null
|
|
349
|
-
eventTypes: string[]
|
|
350
|
-
}>(`
|
|
351
|
-
const storageMod = await import('@/lib/server/storage')
|
|
352
|
-
const missionMod = await import('@/lib/server/missions/mission-service')
|
|
353
|
-
const storage = storageMod.default || storageMod['module.exports'] || storageMod
|
|
354
|
-
const missions = missionMod.default || missionMod['module.exports'] || missionMod
|
|
355
|
-
|
|
356
|
-
storage.saveAgents({
|
|
357
|
-
agentA: {
|
|
358
|
-
id: 'agentA',
|
|
359
|
-
name: 'Agent A',
|
|
360
|
-
provider: 'ollama',
|
|
361
|
-
model: 'test-model',
|
|
362
|
-
systemPrompt: 'test',
|
|
363
|
-
},
|
|
364
|
-
})
|
|
365
|
-
|
|
366
|
-
storage.saveTasks({
|
|
367
|
-
taskA: {
|
|
368
|
-
id: 'taskA',
|
|
369
|
-
title: 'Verify the release checklist',
|
|
370
|
-
description: 'Confirm the release checklist is complete.',
|
|
371
|
-
status: 'completed',
|
|
372
|
-
agentId: 'agentA',
|
|
373
|
-
createdAt: 1,
|
|
374
|
-
updatedAt: 2,
|
|
375
|
-
completedAt: 2,
|
|
376
|
-
},
|
|
377
|
-
})
|
|
378
|
-
|
|
379
|
-
const mission = missions.ensureMissionForTask(storage.loadTasks().taskA, { source: 'task' })
|
|
380
|
-
const updated = await missions.runMissionTick(mission?.id || '', 'test')
|
|
381
|
-
const events = missions.listMissionEventsForMission(mission?.id || '')
|
|
382
|
-
|
|
383
|
-
console.log(JSON.stringify({
|
|
384
|
-
missionStatus: updated?.status || null,
|
|
385
|
-
missionPhase: updated?.phase || null,
|
|
386
|
-
lastVerdict: updated?.verificationState?.lastVerdict || null,
|
|
387
|
-
eventTypes: events.map((event) => event.type),
|
|
388
|
-
}))
|
|
389
|
-
`, { prefix: 'swarmclaw-mission-service-' })
|
|
390
|
-
|
|
391
|
-
assert.equal(output.missionStatus, 'completed')
|
|
392
|
-
assert.equal(output.missionPhase, 'completed')
|
|
393
|
-
assert.equal(output.lastVerdict, 'completed')
|
|
394
|
-
assert.deepEqual(output.eventTypes, ['created', 'task_linked', 'planner_decision', 'verifier_decision', 'completed'])
|
|
395
|
-
})
|
|
396
|
-
|
|
397
|
-
it('uses the structured planner to queue a mission follow-up turn', () => {
|
|
398
|
-
const output = runWithTempDataDir<{
|
|
399
|
-
missionPhase: string | null
|
|
400
|
-
plannerDecision: string | null
|
|
401
|
-
queuedCount: number
|
|
402
|
-
queuedMissionId: string | null
|
|
403
|
-
queuedText: string | null
|
|
404
|
-
}>(`
|
|
405
|
-
const storageMod = await import('@/lib/server/storage')
|
|
406
|
-
const missionMod = await import('@/lib/server/missions/mission-service')
|
|
407
|
-
const runsMod = await import('@/lib/server/runtime/session-run-manager')
|
|
408
|
-
const storage = storageMod.default || storageMod['module.exports'] || storageMod
|
|
409
|
-
const missions = missionMod.default || missionMod['module.exports'] || missionMod
|
|
410
|
-
const runs = runsMod.default || runsMod['module.exports'] || runsMod
|
|
411
|
-
|
|
412
|
-
storage.saveAgents({
|
|
413
|
-
agentA: {
|
|
414
|
-
id: 'agentA',
|
|
415
|
-
name: 'Agent A',
|
|
416
|
-
provider: 'ollama',
|
|
417
|
-
model: 'test-model',
|
|
418
|
-
systemPrompt: 'test',
|
|
419
|
-
},
|
|
420
|
-
})
|
|
421
|
-
|
|
422
|
-
storage.saveSessions({
|
|
423
|
-
sessionA: {
|
|
424
|
-
id: 'sessionA',
|
|
425
|
-
name: 'Mission Chat',
|
|
426
|
-
cwd: process.env.WORKSPACE_DIR,
|
|
427
|
-
user: 'tester',
|
|
428
|
-
provider: 'ollama',
|
|
429
|
-
model: 'test-model',
|
|
430
|
-
claudeSessionId: null,
|
|
431
|
-
messages: [],
|
|
432
|
-
createdAt: 1,
|
|
433
|
-
lastActiveAt: 1,
|
|
434
|
-
agentId: 'agentA',
|
|
435
|
-
},
|
|
436
|
-
})
|
|
437
|
-
|
|
438
|
-
storage.saveMissions({
|
|
439
|
-
missionA: {
|
|
440
|
-
id: 'missionA',
|
|
441
|
-
source: 'schedule',
|
|
442
|
-
sourceRef: { kind: 'schedule', scheduleId: 'sch-1', recurring: true },
|
|
443
|
-
objective: 'Prepare the release handoff',
|
|
444
|
-
status: 'active',
|
|
445
|
-
phase: 'planning',
|
|
446
|
-
sessionId: 'sessionA',
|
|
447
|
-
agentId: 'agentA',
|
|
448
|
-
taskIds: [],
|
|
449
|
-
childMissionIds: [],
|
|
450
|
-
dependencyMissionIds: [],
|
|
451
|
-
dependencyTaskIds: [],
|
|
452
|
-
currentStep: 'Summarize the remaining blockers',
|
|
453
|
-
plannerSummary: 'Use the structured planner.',
|
|
454
|
-
verifierSummary: null,
|
|
455
|
-
blockerSummary: null,
|
|
456
|
-
waitState: null,
|
|
457
|
-
verificationState: { candidate: false },
|
|
458
|
-
createdAt: 1,
|
|
459
|
-
updatedAt: 1,
|
|
460
|
-
},
|
|
461
|
-
})
|
|
462
|
-
|
|
463
|
-
const updated = await missions.runMissionTick('missionA', 'test', {
|
|
464
|
-
generateText: async () => JSON.stringify({
|
|
465
|
-
decision: 'dispatch_session_turn',
|
|
466
|
-
confidence: 0.91,
|
|
467
|
-
summary: 'Queue the next durable follow-up turn.',
|
|
468
|
-
currentStep: 'Summarize the remaining blockers',
|
|
469
|
-
sessionMessage: 'Continue the mission and summarize the remaining release blockers.',
|
|
470
|
-
}),
|
|
471
|
-
})
|
|
472
|
-
const runList = runs.listRuns({ limit: 20 }).filter((run) => run.missionId === 'missionA')
|
|
473
|
-
|
|
474
|
-
console.log(JSON.stringify({
|
|
475
|
-
missionPhase: updated?.phase || null,
|
|
476
|
-
plannerDecision: updated?.plannerState?.lastDecision || null,
|
|
477
|
-
queuedCount: runList.filter((run) => run.status === 'queued' || run.status === 'running').length,
|
|
478
|
-
queuedMissionId: runList[0]?.missionId || null,
|
|
479
|
-
queuedText: runList[0]?.messagePreview || null,
|
|
480
|
-
}))
|
|
481
|
-
`, { prefix: 'swarmclaw-mission-service-' })
|
|
482
|
-
|
|
483
|
-
assert.equal(output.missionPhase, 'dispatching')
|
|
484
|
-
assert.equal(output.plannerDecision, 'dispatch_session_turn')
|
|
485
|
-
assert.equal(output.queuedCount, 1)
|
|
486
|
-
assert.equal(output.queuedMissionId, 'missionA')
|
|
487
|
-
assert.match(output.queuedText || '', /summarize the remaining release blockers/i)
|
|
488
|
-
})
|
|
489
|
-
|
|
490
|
-
it('requests mission ticks when an approval resolves', () => {
|
|
491
|
-
const output = runWithTempDataDir<{
|
|
492
|
-
resumedCount: number
|
|
493
|
-
missionStatus: string | null
|
|
494
|
-
missionPhase: string | null
|
|
495
|
-
eventTypes: string[]
|
|
496
|
-
}>(`
|
|
497
|
-
const storageMod = await import('@/lib/server/storage')
|
|
498
|
-
const missionMod = await import('@/lib/server/missions/mission-service')
|
|
499
|
-
const storage = storageMod.default || storageMod['module.exports'] || storageMod
|
|
500
|
-
const missions = missionMod.default || missionMod['module.exports'] || missionMod
|
|
501
|
-
|
|
502
|
-
storage.upsertApproval('approvalA', {
|
|
503
|
-
id: 'approvalA',
|
|
504
|
-
category: 'human_loop',
|
|
505
|
-
title: 'Resume release mission',
|
|
506
|
-
description: '',
|
|
507
|
-
data: {},
|
|
508
|
-
status: 'approved',
|
|
509
|
-
createdAt: 1,
|
|
510
|
-
updatedAt: 2,
|
|
511
|
-
sessionId: 'sessionA',
|
|
512
|
-
})
|
|
513
|
-
|
|
514
|
-
storage.saveMissions({
|
|
515
|
-
missionA: {
|
|
516
|
-
id: 'missionA',
|
|
517
|
-
source: 'chat',
|
|
518
|
-
sourceRef: { kind: 'chat', sessionId: 'sessionA' },
|
|
519
|
-
objective: 'Resume the release mission',
|
|
520
|
-
status: 'waiting',
|
|
521
|
-
phase: 'waiting',
|
|
522
|
-
sessionId: 'sessionA',
|
|
523
|
-
waitState: { kind: 'approval', reason: 'Waiting on approval.', approvalId: 'approvalA' },
|
|
524
|
-
taskIds: [],
|
|
525
|
-
createdAt: 1,
|
|
526
|
-
updatedAt: 1,
|
|
527
|
-
},
|
|
528
|
-
})
|
|
529
|
-
|
|
530
|
-
const resumed = missions.requestMissionTicksForApprovalDecision({
|
|
531
|
-
approvalId: 'approvalA',
|
|
532
|
-
status: 'approved',
|
|
533
|
-
sessionId: 'sessionA',
|
|
534
|
-
})
|
|
535
|
-
const mission = missions.loadMissionById('missionA')
|
|
536
|
-
const events = missions.listMissionEventsForMission('missionA')
|
|
537
|
-
|
|
538
|
-
console.log(JSON.stringify({
|
|
539
|
-
resumedCount: resumed.length,
|
|
540
|
-
missionStatus: mission?.status || null,
|
|
541
|
-
missionPhase: mission?.phase || null,
|
|
542
|
-
eventTypes: events.map((event) => event.type),
|
|
543
|
-
}))
|
|
544
|
-
`, { prefix: 'swarmclaw-mission-service-' })
|
|
545
|
-
|
|
546
|
-
assert.equal(output.resumedCount, 1)
|
|
547
|
-
assert.equal(output.missionStatus, 'active')
|
|
548
|
-
assert.equal(output.missionPhase, 'planning')
|
|
549
|
-
assert.ok(output.eventTypes.includes('source_triggered'))
|
|
550
|
-
})
|
|
551
|
-
|
|
552
|
-
it('requests mission ticks when a human reply arrives', () => {
|
|
553
|
-
const output = runWithTempDataDir<{
|
|
554
|
-
resumedCount: number
|
|
555
|
-
missionStatus: string | null
|
|
556
|
-
missionPhase: string | null
|
|
557
|
-
}>(`
|
|
558
|
-
const storageMod = await import('@/lib/server/storage')
|
|
559
|
-
const missionMod = await import('@/lib/server/missions/mission-service')
|
|
560
|
-
const storage = storageMod.default || storageMod['module.exports'] || storageMod
|
|
561
|
-
const missions = missionMod.default || missionMod['module.exports'] || missionMod
|
|
562
|
-
|
|
563
|
-
storage.saveMissions({
|
|
564
|
-
missionA: {
|
|
565
|
-
id: 'missionA',
|
|
566
|
-
source: 'connector',
|
|
567
|
-
sourceRef: { kind: 'connector', sessionId: 'sessionA', connectorId: 'con-1', channelId: 'chan-1' },
|
|
568
|
-
objective: 'Wait for the operator reply',
|
|
569
|
-
status: 'waiting',
|
|
570
|
-
phase: 'waiting',
|
|
571
|
-
sessionId: 'sessionA',
|
|
572
|
-
waitState: { kind: 'human_reply', reason: 'Waiting for operator reply.' },
|
|
573
|
-
taskIds: [],
|
|
574
|
-
createdAt: 1,
|
|
575
|
-
updatedAt: 1,
|
|
576
|
-
},
|
|
577
|
-
})
|
|
578
|
-
|
|
579
|
-
const resumed = missions.requestMissionTicksForHumanReply({
|
|
580
|
-
sessionId: 'sessionA',
|
|
581
|
-
correlationId: 'corr-1',
|
|
582
|
-
payload: 'The operator replied with the final answer.',
|
|
583
|
-
})
|
|
584
|
-
const mission = missions.loadMissionById('missionA')
|
|
585
|
-
|
|
586
|
-
console.log(JSON.stringify({
|
|
587
|
-
resumedCount: resumed.length,
|
|
588
|
-
missionStatus: mission?.status || null,
|
|
589
|
-
missionPhase: mission?.phase || null,
|
|
590
|
-
}))
|
|
591
|
-
`, { prefix: 'swarmclaw-mission-service-' })
|
|
592
|
-
|
|
593
|
-
assert.equal(output.resumedCount, 1)
|
|
594
|
-
assert.equal(output.missionStatus, 'active')
|
|
595
|
-
assert.equal(output.missionPhase, 'planning')
|
|
596
|
-
})
|
|
597
|
-
|
|
598
|
-
it('does not wake non-human waiting missions on unrelated replies', () => {
|
|
599
|
-
const output = runWithTempDataDir<{
|
|
600
|
-
resumedCount: number
|
|
601
|
-
missionStatus: string | null
|
|
602
|
-
missionPhase: string | null
|
|
603
|
-
eventTypes: string[]
|
|
604
|
-
}>(`
|
|
605
|
-
const storageMod = await import('@/lib/server/storage')
|
|
606
|
-
const missionMod = await import('@/lib/server/missions/mission-service')
|
|
607
|
-
const storage = storageMod.default || storageMod['module.exports'] || storageMod
|
|
608
|
-
const missions = missionMod.default || missionMod['module.exports'] || missionMod
|
|
609
|
-
|
|
610
|
-
storage.saveMissions({
|
|
611
|
-
missionA: {
|
|
612
|
-
id: 'missionA',
|
|
613
|
-
source: 'chat',
|
|
614
|
-
sourceRef: { kind: 'chat', sessionId: 'sessionA' },
|
|
615
|
-
objective: 'Wait for approval before shipping',
|
|
616
|
-
status: 'waiting',
|
|
617
|
-
phase: 'waiting',
|
|
618
|
-
sessionId: 'sessionA',
|
|
619
|
-
waitState: { kind: 'approval', reason: 'Waiting for approval.' },
|
|
620
|
-
taskIds: [],
|
|
621
|
-
createdAt: 1,
|
|
622
|
-
updatedAt: 1,
|
|
623
|
-
},
|
|
624
|
-
})
|
|
625
|
-
|
|
626
|
-
const resumed = missions.requestMissionTicksForHumanReply({
|
|
627
|
-
sessionId: 'sessionA',
|
|
628
|
-
correlationId: 'corr-1',
|
|
629
|
-
payload: 'Here is an unrelated follow-up.',
|
|
630
|
-
})
|
|
631
|
-
const mission = missions.loadMissionById('missionA')
|
|
632
|
-
const events = missions.listMissionEventsForMission('missionA')
|
|
633
|
-
|
|
634
|
-
console.log(JSON.stringify({
|
|
635
|
-
resumedCount: resumed.length,
|
|
636
|
-
missionStatus: mission?.status || null,
|
|
637
|
-
missionPhase: mission?.phase || null,
|
|
638
|
-
eventTypes: events.map((event) => event.type),
|
|
639
|
-
}))
|
|
640
|
-
`, { prefix: 'swarmclaw-mission-service-' })
|
|
641
|
-
|
|
642
|
-
assert.equal(output.resumedCount, 0)
|
|
643
|
-
assert.equal(output.missionStatus, 'waiting')
|
|
644
|
-
assert.equal(output.missionPhase, 'waiting')
|
|
645
|
-
assert.equal(output.eventTypes.includes('source_triggered'), false)
|
|
646
|
-
})
|
|
647
|
-
|
|
648
|
-
it('closes mission outcomes that only wait for a human reply when mission human loop is disabled', () => {
|
|
649
|
-
const output = runWithTempDataDir<{
|
|
650
|
-
missionStatus: string | null
|
|
651
|
-
missionPhase: string | null
|
|
652
|
-
verifierSummary: string | null
|
|
653
|
-
eventTypes: string[]
|
|
654
|
-
}>(`
|
|
655
|
-
const storageMod = await import('@/lib/server/storage')
|
|
656
|
-
const missionMod = await import('@/lib/server/missions/mission-service')
|
|
657
|
-
const storage = storageMod.default || storageMod['module.exports'] || storageMod
|
|
658
|
-
const missions = missionMod.default || missionMod['module.exports'] || missionMod
|
|
659
|
-
|
|
660
|
-
storage.saveSettings({ missionHumanLoopEnabled: false })
|
|
661
|
-
storage.saveSessions({
|
|
662
|
-
sessionA: {
|
|
663
|
-
id: 'sessionA',
|
|
664
|
-
name: 'Mission Chat',
|
|
665
|
-
cwd: process.env.WORKSPACE_DIR,
|
|
666
|
-
user: 'tester',
|
|
667
|
-
provider: 'ollama',
|
|
668
|
-
model: 'test-model',
|
|
669
|
-
claudeSessionId: null,
|
|
670
|
-
messages: [],
|
|
671
|
-
createdAt: 1,
|
|
672
|
-
lastActiveAt: 1,
|
|
673
|
-
agentId: 'agentA',
|
|
674
|
-
},
|
|
675
|
-
})
|
|
676
|
-
storage.saveAgents({
|
|
677
|
-
agentA: {
|
|
678
|
-
id: 'agentA',
|
|
679
|
-
name: 'Agent A',
|
|
680
|
-
provider: 'ollama',
|
|
681
|
-
model: 'test-model',
|
|
682
|
-
systemPrompt: 'test',
|
|
683
|
-
},
|
|
684
|
-
})
|
|
685
|
-
storage.saveMissions({
|
|
686
|
-
missionA: {
|
|
687
|
-
id: 'missionA',
|
|
688
|
-
source: 'chat',
|
|
689
|
-
sourceRef: { kind: 'chat', sessionId: 'sessionA' },
|
|
690
|
-
objective: 'Create one small file',
|
|
691
|
-
status: 'active',
|
|
692
|
-
phase: 'executing',
|
|
693
|
-
sessionId: 'sessionA',
|
|
694
|
-
agentId: 'agentA',
|
|
695
|
-
taskIds: [],
|
|
696
|
-
childMissionIds: [],
|
|
697
|
-
dependencyMissionIds: [],
|
|
698
|
-
dependencyTaskIds: [],
|
|
699
|
-
currentStep: 'Write the file',
|
|
700
|
-
createdAt: 1,
|
|
701
|
-
updatedAt: 1,
|
|
702
|
-
},
|
|
703
|
-
})
|
|
704
|
-
|
|
705
|
-
const updated = await missions.applyMissionOutcomeForTurn({
|
|
706
|
-
session: storage.loadSessions().sessionA,
|
|
707
|
-
missionId: 'missionA',
|
|
708
|
-
source: 'chat',
|
|
709
|
-
runId: 'run-1',
|
|
710
|
-
message: 'Create mission.txt with start in it.',
|
|
711
|
-
assistantText: 'Done. Let me know what you want next.',
|
|
712
|
-
toolEvents: [],
|
|
713
|
-
generateText: async () => JSON.stringify({
|
|
714
|
-
verdict: 'waiting',
|
|
715
|
-
confidence: 0.91,
|
|
716
|
-
phase: 'waiting',
|
|
717
|
-
waitKind: 'human_reply',
|
|
718
|
-
waitReason: 'Waiting for the user to say what to do next.',
|
|
719
|
-
verifierSummary: 'The file is done and the mission is waiting for the next instruction.',
|
|
720
|
-
}),
|
|
721
|
-
})
|
|
722
|
-
const events = missions.listMissionEventsForMission('missionA')
|
|
723
|
-
|
|
724
|
-
console.log(JSON.stringify({
|
|
725
|
-
missionStatus: updated?.status || null,
|
|
726
|
-
missionPhase: updated?.phase || null,
|
|
727
|
-
verifierSummary: updated?.verifierSummary || null,
|
|
728
|
-
eventTypes: events.map((event) => event.type),
|
|
729
|
-
}))
|
|
730
|
-
`, { prefix: 'swarmclaw-mission-service-' })
|
|
731
|
-
|
|
732
|
-
assert.equal(output.missionStatus, 'completed')
|
|
733
|
-
assert.equal(output.missionPhase, 'completed')
|
|
734
|
-
assert.match(String(output.verifierSummary || ''), /human-loop waits are disabled/i)
|
|
735
|
-
assert.ok(output.eventTypes.includes('completed'))
|
|
736
|
-
assert.equal(output.eventTypes.includes('waiting'), false)
|
|
737
|
-
})
|
|
738
|
-
|
|
739
|
-
it('does not leave planner ticks waiting for a human reply when mission human loop is disabled', () => {
|
|
740
|
-
const output = runWithTempDataDir<{
|
|
741
|
-
missionStatus: string | null
|
|
742
|
-
missionPhase: string | null
|
|
743
|
-
plannerDecision: string | null
|
|
744
|
-
lastVerdict: string | null
|
|
745
|
-
}>(`
|
|
746
|
-
const storageMod = await import('@/lib/server/storage')
|
|
747
|
-
const missionMod = await import('@/lib/server/missions/mission-service')
|
|
748
|
-
const storage = storageMod.default || storageMod['module.exports'] || storageMod
|
|
749
|
-
const missions = missionMod.default || missionMod['module.exports'] || missionMod
|
|
750
|
-
|
|
751
|
-
storage.saveSettings({ missionHumanLoopEnabled: false })
|
|
752
|
-
storage.saveMissions({
|
|
753
|
-
missionA: {
|
|
754
|
-
id: 'missionA',
|
|
755
|
-
source: 'manual',
|
|
756
|
-
sourceRef: { kind: 'manual' },
|
|
757
|
-
objective: 'Finish the small file task',
|
|
758
|
-
status: 'active',
|
|
759
|
-
phase: 'planning',
|
|
760
|
-
taskIds: [],
|
|
761
|
-
childMissionIds: [],
|
|
762
|
-
dependencyMissionIds: [],
|
|
763
|
-
dependencyTaskIds: [],
|
|
764
|
-
currentStep: 'Close out the task',
|
|
765
|
-
createdAt: 1,
|
|
766
|
-
updatedAt: 1,
|
|
767
|
-
},
|
|
768
|
-
})
|
|
769
|
-
|
|
770
|
-
const updated = await missions.runMissionTick('missionA', 'test', {
|
|
771
|
-
generateText: async () => JSON.stringify({
|
|
772
|
-
decision: 'wait',
|
|
773
|
-
confidence: 0.88,
|
|
774
|
-
summary: 'Waiting for the user to say what to do next.',
|
|
775
|
-
waitKind: 'human_reply',
|
|
776
|
-
waitReason: 'Waiting for the next instruction.',
|
|
777
|
-
}),
|
|
778
|
-
})
|
|
779
|
-
|
|
780
|
-
console.log(JSON.stringify({
|
|
781
|
-
missionStatus: updated?.status || null,
|
|
782
|
-
missionPhase: updated?.phase || null,
|
|
783
|
-
plannerDecision: updated?.plannerState?.lastDecision || null,
|
|
784
|
-
lastVerdict: updated?.verificationState?.lastVerdict || null,
|
|
785
|
-
}))
|
|
786
|
-
`, { prefix: 'swarmclaw-mission-service-' })
|
|
787
|
-
|
|
788
|
-
assert.equal(output.missionStatus, 'completed')
|
|
789
|
-
assert.equal(output.missionPhase, 'completed')
|
|
790
|
-
assert.equal(output.plannerDecision, 'verify_now')
|
|
791
|
-
assert.equal(output.lastVerdict, 'completed')
|
|
792
|
-
})
|
|
793
|
-
|
|
794
|
-
it('requests mission ticks when provider recovery clears a provider wait', () => {
|
|
795
|
-
const output = runWithTempDataDir<{
|
|
796
|
-
resumedCount: number
|
|
797
|
-
missionStatus: string | null
|
|
798
|
-
missionPhase: string | null
|
|
799
|
-
}>(`
|
|
800
|
-
const missionMod = await import('@/lib/server/missions/mission-service')
|
|
801
|
-
const storageMod = await import('@/lib/server/storage')
|
|
802
|
-
const missions = missionMod.default || missionMod['module.exports'] || missionMod
|
|
803
|
-
const storage = storageMod.default || storageMod['module.exports'] || storageMod
|
|
804
|
-
|
|
805
|
-
storage.saveMissions({
|
|
806
|
-
missionA: {
|
|
807
|
-
id: 'missionA',
|
|
808
|
-
source: 'chat',
|
|
809
|
-
sourceRef: { kind: 'chat', sessionId: 'sessionA' },
|
|
810
|
-
objective: 'Wait for provider recovery',
|
|
811
|
-
status: 'waiting',
|
|
812
|
-
phase: 'waiting',
|
|
813
|
-
sessionId: 'sessionA',
|
|
814
|
-
waitState: { kind: 'provider', reason: 'Provider connection failed.', providerKey: 'ollama' },
|
|
815
|
-
taskIds: [],
|
|
816
|
-
createdAt: 1,
|
|
817
|
-
updatedAt: 1,
|
|
818
|
-
},
|
|
819
|
-
})
|
|
820
|
-
|
|
821
|
-
const resumed = missions.requestMissionTicksForProviderRecovery('ollama')
|
|
822
|
-
const mission = missions.loadMissionById('missionA')
|
|
823
|
-
|
|
824
|
-
console.log(JSON.stringify({
|
|
825
|
-
resumedCount: resumed.length,
|
|
826
|
-
missionStatus: mission?.status || null,
|
|
827
|
-
missionPhase: mission?.phase || null,
|
|
828
|
-
}))
|
|
829
|
-
`, { prefix: 'swarmclaw-mission-service-' })
|
|
830
|
-
|
|
831
|
-
assert.equal(output.resumedCount, 1)
|
|
832
|
-
assert.equal(output.missionStatus, 'active')
|
|
833
|
-
assert.equal(output.missionPhase, 'planning')
|
|
834
|
-
})
|
|
835
|
-
|
|
836
|
-
it('reconciles stale executing missions on startup', () => {
|
|
837
|
-
const output = runWithTempDataDir<{
|
|
838
|
-
beforeStatus: string | null
|
|
839
|
-
beforePhase: string | null
|
|
840
|
-
missionStatus: string | null
|
|
841
|
-
missionPhase: string | null
|
|
842
|
-
eventTypes: string[]
|
|
843
|
-
}>(`
|
|
844
|
-
const storageMod = await import('@/lib/server/storage')
|
|
845
|
-
const missionMod = await import('@/lib/server/missions/mission-service')
|
|
846
|
-
const storage = storageMod.default || storageMod['module.exports'] || storageMod
|
|
847
|
-
const missions = missionMod.default || missionMod['module.exports'] || missionMod
|
|
848
|
-
|
|
849
|
-
storage.saveMissions({
|
|
850
|
-
missionA: {
|
|
851
|
-
id: 'missionA',
|
|
852
|
-
source: 'chat',
|
|
853
|
-
sourceRef: { kind: 'chat', sessionId: 'sessionA' },
|
|
854
|
-
objective: 'Recover after restart',
|
|
855
|
-
status: 'active',
|
|
856
|
-
phase: 'executing',
|
|
857
|
-
sessionId: 'sessionA',
|
|
858
|
-
taskIds: [],
|
|
859
|
-
controllerState: {
|
|
860
|
-
activeRunId: 'run-stale',
|
|
861
|
-
currentTaskId: 'task-stale',
|
|
862
|
-
},
|
|
863
|
-
createdAt: 1,
|
|
864
|
-
updatedAt: 1,
|
|
865
|
-
},
|
|
866
|
-
})
|
|
867
|
-
|
|
868
|
-
const before = missions.loadMissionById('missionA')
|
|
869
|
-
missions.runMissionControllerStartupRecovery()
|
|
870
|
-
const mission = missions.loadMissionById('missionA')
|
|
871
|
-
const events = missions.listMissionEventsForMission('missionA')
|
|
872
|
-
|
|
873
|
-
console.log(JSON.stringify({
|
|
874
|
-
beforeStatus: before?.status || null,
|
|
875
|
-
beforePhase: before?.phase || null,
|
|
876
|
-
missionStatus: mission?.status || null,
|
|
877
|
-
missionPhase: mission?.phase || null,
|
|
878
|
-
eventTypes: events.map((event) => event.type),
|
|
879
|
-
}))
|
|
880
|
-
`, { prefix: 'swarmclaw-mission-service-' })
|
|
881
|
-
|
|
882
|
-
assert.equal(output.beforeStatus, 'active')
|
|
883
|
-
assert.equal(output.beforePhase, 'executing')
|
|
884
|
-
assert.equal(output.missionStatus, 'active')
|
|
885
|
-
assert.equal(output.missionPhase, 'planning')
|
|
886
|
-
assert.ok(output.eventTypes.includes('interrupted'))
|
|
887
|
-
})
|
|
888
|
-
})
|