@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
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { log } from '@/lib/server/logger'
|
|
2
|
+
import { hmrSingleton } from '@/lib/shared-utils'
|
|
3
|
+
import { logActivity } from '@/lib/server/activity/activity-log'
|
|
4
|
+
import type { Connector, InboundMessage } from '@/types/connector'
|
|
5
|
+
import type { PlatformConnector, ConnectorInstance } from '@/lib/server/connectors/types'
|
|
6
|
+
import { createBoardTaskFromAssignment, updateBoardTaskFromEvent, findBoardTaskBySwarmdockId } from './swarmdock-tasks'
|
|
7
|
+
import { shouldAutoBid, submitAutoBid } from './swarmdock-bidding'
|
|
8
|
+
import type { TaskSubmitInput } from '@swarmdock/shared'
|
|
9
|
+
|
|
10
|
+
const TAG = 'swarmdock'
|
|
11
|
+
|
|
12
|
+
// SDK types inlined until @swarmdock/sdk is built and linked
|
|
13
|
+
interface SwarmDockTask {
|
|
14
|
+
id: string
|
|
15
|
+
requesterId: string
|
|
16
|
+
assigneeId: string | null
|
|
17
|
+
title: string
|
|
18
|
+
description: string
|
|
19
|
+
skillRequirements: string[]
|
|
20
|
+
budgetMax: string
|
|
21
|
+
status: string
|
|
22
|
+
deadline: string | null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface SwarmDockSSEEvent {
|
|
26
|
+
type: string
|
|
27
|
+
data: Record<string, unknown>
|
|
28
|
+
timestamp: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface SwarmDockConfig {
|
|
32
|
+
apiUrl: string
|
|
33
|
+
walletAddress: string
|
|
34
|
+
agentDescription: string
|
|
35
|
+
skills: string
|
|
36
|
+
autoDiscover: boolean
|
|
37
|
+
maxBudget: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseConfig(connector: Connector): SwarmDockConfig {
|
|
41
|
+
const c = connector.config || {}
|
|
42
|
+
return {
|
|
43
|
+
apiUrl: c.apiUrl || 'https://api.swarmdock.ai',
|
|
44
|
+
walletAddress: c.walletAddress || '',
|
|
45
|
+
agentDescription: c.agentDescription || connector.name || '',
|
|
46
|
+
skills: c.skills || '',
|
|
47
|
+
autoDiscover: c.autoDiscover === 'true',
|
|
48
|
+
maxBudget: c.maxBudget || '0',
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function buildTaskPrompt(task: SwarmDockTask): string {
|
|
53
|
+
const lines: string[] = [
|
|
54
|
+
`# SwarmDock Task: ${task.title}`,
|
|
55
|
+
'',
|
|
56
|
+
task.description,
|
|
57
|
+
'',
|
|
58
|
+
`**Required Skills:** ${task.skillRequirements.join(', ')}`,
|
|
59
|
+
`**Budget:** ${formatUsdc(task.budgetMax)}`,
|
|
60
|
+
]
|
|
61
|
+
if (task.deadline) lines.push(`**Deadline:** ${task.deadline}`)
|
|
62
|
+
lines.push('', 'Complete this task and provide your deliverables. Your response will be submitted as the task result on the SwarmDock marketplace.')
|
|
63
|
+
return lines.join('\n')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function formatUsdc(microUnits: string): string {
|
|
67
|
+
const cents = BigInt(microUnits)
|
|
68
|
+
const dollars = Number(cents) / 1_000_000
|
|
69
|
+
return `$${dollars.toFixed(2)} USDC`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function submitSwarmdockTaskResult(
|
|
73
|
+
client: { tasks: { submit: (taskId: string, input: TaskSubmitInput) => Promise<unknown> } },
|
|
74
|
+
swarmdockTaskId: string,
|
|
75
|
+
text: string,
|
|
76
|
+
): Promise<void> {
|
|
77
|
+
const payload: TaskSubmitInput = {
|
|
78
|
+
artifacts: [{ type: 'text/markdown', content: text }],
|
|
79
|
+
files: [],
|
|
80
|
+
}
|
|
81
|
+
await client.tasks.submit(swarmdockTaskId, payload)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Runtime state: maps SwarmDock task IDs → SwarmClaw BoardTask IDs */
|
|
85
|
+
const taskIdMap = hmrSingleton('__swarmclaw_swarmdock_task_map__', () => new Map<string, string>())
|
|
86
|
+
|
|
87
|
+
const swarmdock: PlatformConnector = {
|
|
88
|
+
async start(connector, _botToken, onMessage): Promise<ConnectorInstance> {
|
|
89
|
+
const config = parseConfig(connector)
|
|
90
|
+
const connectorId = connector.id
|
|
91
|
+
const agentId = connector.agentId || ''
|
|
92
|
+
const privateKey = _botToken || ''
|
|
93
|
+
|
|
94
|
+
if (!privateKey) throw new Error('SwarmDock connector requires an Ed25519 private key credential')
|
|
95
|
+
if (!config.walletAddress) throw new Error('SwarmDock connector requires a Base L2 wallet address in config')
|
|
96
|
+
|
|
97
|
+
// Dynamic import of the SDK (must be built and linked first)
|
|
98
|
+
let SwarmDockClient: typeof import('@swarmdock/sdk').SwarmDockClient
|
|
99
|
+
try {
|
|
100
|
+
const sdk = await import('@swarmdock/sdk')
|
|
101
|
+
SwarmDockClient = sdk.SwarmDockClient
|
|
102
|
+
} catch {
|
|
103
|
+
throw new Error('SwarmDock SDK (@swarmdock/sdk) is not installed. Run: npm install @swarmdock/sdk')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const client = new SwarmDockClient({
|
|
107
|
+
baseUrl: config.apiUrl,
|
|
108
|
+
privateKey,
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// Register agent on SwarmDock (Ed25519 challenge-response)
|
|
112
|
+
const skillList = config.skills
|
|
113
|
+
.split(',')
|
|
114
|
+
.map((s) => s.trim())
|
|
115
|
+
.filter(Boolean)
|
|
116
|
+
.map((skillId) => ({
|
|
117
|
+
skillId,
|
|
118
|
+
skillName: skillId.replace(/-/g, ' '),
|
|
119
|
+
description: `${skillId} capability`,
|
|
120
|
+
category: skillId,
|
|
121
|
+
basePrice: '1000000', // $1.00 default
|
|
122
|
+
}))
|
|
123
|
+
|
|
124
|
+
log.info(TAG, `Registering agent "${connector.name}" on SwarmDock at ${config.apiUrl}`)
|
|
125
|
+
const registration = await client.register({
|
|
126
|
+
displayName: connector.name,
|
|
127
|
+
description: config.agentDescription,
|
|
128
|
+
framework: 'swarmclaw',
|
|
129
|
+
walletAddress: config.walletAddress,
|
|
130
|
+
skills: skillList,
|
|
131
|
+
})
|
|
132
|
+
log.info(TAG, `Registered as ${registration.agent.did} (trust level ${registration.agent.trustLevel})`)
|
|
133
|
+
|
|
134
|
+
logActivity({
|
|
135
|
+
entityType: 'connector',
|
|
136
|
+
entityId: connectorId,
|
|
137
|
+
action: 'swarmdock-registered',
|
|
138
|
+
actor: 'system',
|
|
139
|
+
summary: `Agent "${connector.name}" registered on SwarmDock as ${registration.agent.did}`,
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// Set up SSE event stream
|
|
143
|
+
let alive = true
|
|
144
|
+
|
|
145
|
+
const handleSSEEvent = async (event: SwarmDockSSEEvent) => {
|
|
146
|
+
if (!alive) return
|
|
147
|
+
try {
|
|
148
|
+
switch (event.type) {
|
|
149
|
+
case 'task.created': {
|
|
150
|
+
if (!config.autoDiscover) break
|
|
151
|
+
const task = event.data as unknown as SwarmDockTask
|
|
152
|
+
if (shouldAutoBid(task, config)) {
|
|
153
|
+
await submitAutoBid(client, task.id, config)
|
|
154
|
+
logActivity({
|
|
155
|
+
entityType: 'connector',
|
|
156
|
+
entityId: connectorId,
|
|
157
|
+
action: 'swarmdock-bid',
|
|
158
|
+
actor: 'system',
|
|
159
|
+
summary: `Auto-bid on SwarmDock task: "${task.title}"`,
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
break
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
case 'task.assigned': {
|
|
166
|
+
const task = event.data as unknown as SwarmDockTask
|
|
167
|
+
if (!task.assigneeId) break
|
|
168
|
+
|
|
169
|
+
// Signal work started on SwarmDock
|
|
170
|
+
try { await client.tasks.start(task.id) } catch {}
|
|
171
|
+
|
|
172
|
+
// Create a BoardTask in SwarmClaw
|
|
173
|
+
const boardTaskId = await createBoardTaskFromAssignment(task, agentId, connectorId, config.apiUrl)
|
|
174
|
+
taskIdMap.set(task.id, boardTaskId)
|
|
175
|
+
|
|
176
|
+
// Dispatch as inbound message to the assigned agent
|
|
177
|
+
const inbound: InboundMessage = {
|
|
178
|
+
platform: 'swarmdock',
|
|
179
|
+
channelId: `swarmdock-task:${task.id}`,
|
|
180
|
+
channelName: `SwarmDock: ${task.title}`,
|
|
181
|
+
senderId: task.requesterId,
|
|
182
|
+
senderName: `SwarmDock Requester`,
|
|
183
|
+
text: buildTaskPrompt(task),
|
|
184
|
+
messageId: task.id,
|
|
185
|
+
}
|
|
186
|
+
await onMessage(inbound)
|
|
187
|
+
break
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
case 'task.completed':
|
|
191
|
+
case 'task.cancelled':
|
|
192
|
+
case 'task.failed': {
|
|
193
|
+
const taskId = (event.data as Record<string, string>).taskId
|
|
194
|
+
if (taskId) await updateBoardTaskFromEvent(taskId, event.type)
|
|
195
|
+
break
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
case 'payment.released': {
|
|
199
|
+
const data = event.data as Record<string, string>
|
|
200
|
+
logActivity({
|
|
201
|
+
entityType: 'connector',
|
|
202
|
+
entityId: connectorId,
|
|
203
|
+
action: 'swarmdock-payment',
|
|
204
|
+
actor: 'system',
|
|
205
|
+
summary: `Payment received: ${formatUsdc(data.amount || '0')} for task ${data.taskId}`,
|
|
206
|
+
})
|
|
207
|
+
break
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
} catch (err) {
|
|
211
|
+
log.error(TAG, `Error handling SSE event ${event.type}: ${err instanceof Error ? err.message : String(err)}`)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
client.events.subscribe(handleSSEEvent as (event: unknown) => void)
|
|
216
|
+
|
|
217
|
+
// Token refresh heartbeat (23h, token lasts 24h)
|
|
218
|
+
const heartbeatInterval = setInterval(async () => {
|
|
219
|
+
try {
|
|
220
|
+
await client.heartbeat()
|
|
221
|
+
log.debug(TAG, 'SwarmDock token refreshed')
|
|
222
|
+
} catch (err) {
|
|
223
|
+
log.error(TAG, `SwarmDock heartbeat failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
224
|
+
}
|
|
225
|
+
}, 23 * 60 * 60 * 1000)
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
connector,
|
|
229
|
+
|
|
230
|
+
stop: async () => {
|
|
231
|
+
alive = false
|
|
232
|
+
clearInterval(heartbeatInterval)
|
|
233
|
+
client.events.unsubscribe()
|
|
234
|
+
log.info(TAG, 'SwarmDock connector stopped')
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
sendMessage: async (channelId: string, text: string) => {
|
|
238
|
+
// channelId format: "swarmdock-task:{taskId}"
|
|
239
|
+
const swarmdockTaskId = channelId.replace('swarmdock-task:', '')
|
|
240
|
+
if (!swarmdockTaskId) return
|
|
241
|
+
|
|
242
|
+
await submitSwarmdockTaskResult(client, swarmdockTaskId, text)
|
|
243
|
+
log.info(TAG, `Submitted results for SwarmDock task ${swarmdockTaskId}`)
|
|
244
|
+
|
|
245
|
+
if (findBoardTaskBySwarmdockId(swarmdockTaskId)) {
|
|
246
|
+
await updateBoardTaskFromEvent(swarmdockTaskId, 'task.submitted')
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return { messageId: swarmdockTaskId }
|
|
250
|
+
},
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export default swarmdock
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { log } from '@/lib/server/logger'
|
|
2
2
|
import type { PlatformConnector, ConnectorInstance, InboundMessage } from './types'
|
|
3
3
|
import { resolveConnectorIngressReply } from './ingress-delivery'
|
|
4
|
+
import { errorMessage } from '@/lib/shared-utils'
|
|
4
5
|
|
|
5
6
|
const TAG = 'teams'
|
|
6
7
|
|
|
@@ -51,8 +52,8 @@ const teams: PlatformConnector = {
|
|
|
51
52
|
const reply = await resolveConnectorIngressReply(onMessage, inbound)
|
|
52
53
|
if (!reply) return
|
|
53
54
|
await context.sendActivity(reply.visibleText)
|
|
54
|
-
} catch (err:
|
|
55
|
-
log.error(TAG, 'Error handling message:', err
|
|
55
|
+
} catch (err: unknown) {
|
|
56
|
+
log.error(TAG, 'Error handling message:', errorMessage(err))
|
|
56
57
|
try {
|
|
57
58
|
await context.sendActivity('Sorry, I encountered an error processing your message.')
|
|
58
59
|
} catch { /* ignore */ }
|
|
@@ -127,8 +127,8 @@ const telegram: PlatformConnector = {
|
|
|
127
127
|
mimeType: m.mimeType,
|
|
128
128
|
})
|
|
129
129
|
if (stored) media.push(stored)
|
|
130
|
-
} catch (err:
|
|
131
|
-
log.warn(TAG, `Failed to fetch media ${m.fileId}:`,
|
|
130
|
+
} catch (err: unknown) {
|
|
131
|
+
log.warn(TAG, `Failed to fetch media ${m.fileId}:`, errorMessage(err))
|
|
132
132
|
media.push({
|
|
133
133
|
type: m.type,
|
|
134
134
|
fileName: m.fileName,
|
|
@@ -177,8 +177,8 @@ const telegram: PlatformConnector = {
|
|
|
177
177
|
return String(sent.message_id)
|
|
178
178
|
},
|
|
179
179
|
})
|
|
180
|
-
} catch (err:
|
|
181
|
-
log.error(TAG, 'Error handling message:', err
|
|
180
|
+
} catch (err: unknown) {
|
|
181
|
+
log.error(TAG, 'Error handling message:', errorMessage(err))
|
|
182
182
|
try {
|
|
183
183
|
await ctx.reply('Sorry, I encountered an error processing your message.')
|
|
184
184
|
} catch { /* ignore */ }
|
|
@@ -714,8 +714,8 @@ const whatsapp: PlatformConnector = {
|
|
|
714
714
|
fileName: mediaCandidate.payload?.fileName || undefined,
|
|
715
715
|
})
|
|
716
716
|
media.push(saved)
|
|
717
|
-
} catch (err:
|
|
718
|
-
log.error(TAG, `Failed to decode media: ${
|
|
717
|
+
} catch (err: unknown) {
|
|
718
|
+
log.error(TAG, `Failed to decode media: ${errorMessage(err)}`)
|
|
719
719
|
media.push({
|
|
720
720
|
type: mediaCandidate.kind,
|
|
721
721
|
fileName: mediaCandidate.payload?.fileName || undefined,
|
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
} from '@/lib/server/daemon/types'
|
|
26
26
|
import { DATA_DIR } from '@/lib/server/data-dir'
|
|
27
27
|
import { loadEstopState } from '@/lib/server/runtime/estop'
|
|
28
|
+
import { getDaemonStatus } from '@/lib/server/runtime/daemon-state/core'
|
|
28
29
|
import { daemonAutostartEnvEnabled } from '@/lib/server/runtime/daemon-policy'
|
|
29
30
|
import {
|
|
30
31
|
releaseRuntimeLock,
|
|
@@ -344,6 +345,12 @@ export async function ensureDaemonProcessRunning(
|
|
|
344
345
|
source: string,
|
|
345
346
|
opts?: { manualStart?: boolean },
|
|
346
347
|
): Promise<boolean> {
|
|
348
|
+
// In dev mode, the daemon may already be running in-process (same Next.js server)
|
|
349
|
+
// without a daemon-admin.json file. Check in-process state first to avoid spawning
|
|
350
|
+
// a subprocess that fails to acquire the already-held lease.
|
|
351
|
+
const inProcessStatus = getDaemonStatus()
|
|
352
|
+
if (inProcessStatus.running) return false
|
|
353
|
+
|
|
347
354
|
const manualStart = opts?.manualStart === true
|
|
348
355
|
const record = loadDaemonStatusRecord()
|
|
349
356
|
if (loadEstopState().level !== 'none') return false
|
|
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict'
|
|
|
2
2
|
import { test } from 'node:test'
|
|
3
3
|
|
|
4
4
|
import { buildExecutionBrief, buildExecutionBriefContextBlock, serializeExecutionBriefForDelegation } from './execution-brief'
|
|
5
|
-
import type {
|
|
5
|
+
import type { Session, SessionWorkingState } from '@/types'
|
|
6
6
|
|
|
7
7
|
test('buildExecutionBrief prefers working state and folds in mission and run-context fallback data', () => {
|
|
8
8
|
const session = {
|
|
@@ -31,25 +31,8 @@ test('buildExecutionBrief prefers working state and folds in mission and run-con
|
|
|
31
31
|
},
|
|
32
32
|
} satisfies Partial<Session> as Session
|
|
33
33
|
|
|
34
|
-
const mission = {
|
|
35
|
-
id: 'm1',
|
|
36
|
-
source: 'chat',
|
|
37
|
-
objective: 'Ship the release fix',
|
|
38
|
-
status: 'active',
|
|
39
|
-
phase: 'executing',
|
|
40
|
-
currentStep: 'Verify staging auth',
|
|
41
|
-
successCriteria: ['Restore staging deploys'],
|
|
42
|
-
waitState: {
|
|
43
|
-
kind: 'approval',
|
|
44
|
-
reason: 'Waiting for deploy approval.',
|
|
45
|
-
},
|
|
46
|
-
createdAt: 1,
|
|
47
|
-
updatedAt: 1,
|
|
48
|
-
} satisfies Partial<Mission> as Mission
|
|
49
|
-
|
|
50
34
|
const workingState = {
|
|
51
35
|
sessionId: 's1',
|
|
52
|
-
missionId: 'm1',
|
|
53
36
|
objective: 'Ship the release fix safely',
|
|
54
37
|
summary: 'Auth mismatch isolated to staging.',
|
|
55
38
|
constraints: ['Do not change the API'],
|
|
@@ -79,7 +62,7 @@ test('buildExecutionBrief prefers working state and folds in mission and run-con
|
|
|
79
62
|
updatedAt: 1,
|
|
80
63
|
} satisfies SessionWorkingState
|
|
81
64
|
|
|
82
|
-
const brief = buildExecutionBrief({ session,
|
|
65
|
+
const brief = buildExecutionBrief({ session, workingState })
|
|
83
66
|
|
|
84
67
|
assert.equal(brief.objective, 'Ship the release fix safely')
|
|
85
68
|
assert.equal(brief.summary, 'Auth mismatch isolated to staging.')
|
|
@@ -87,11 +70,9 @@ test('buildExecutionBrief prefers working state and folds in mission and run-con
|
|
|
87
70
|
assert.equal(brief.nextAction, 'Request deploy approval')
|
|
88
71
|
assert.equal(brief.plan[0]?.text, 'Request deploy approval')
|
|
89
72
|
assert.equal(brief.blockers[0], 'Deploy approval is pending. | next: Request deploy approval')
|
|
90
|
-
assert.ok(brief.blockers.some((entry) => /waiting for deploy approval/i.test(entry)))
|
|
91
73
|
assert.ok(brief.facts.some((entry) => /auth mismatch isolated to staging/i.test(entry)))
|
|
92
74
|
assert.ok(brief.artifacts.some((entry) => /deploy\.log/i.test(entry)))
|
|
93
75
|
assert.equal(brief.parentContext, 'Parent asked for a contained fix.')
|
|
94
|
-
assert.equal(brief.missionPhase, 'executing')
|
|
95
76
|
})
|
|
96
77
|
|
|
97
78
|
test('buildExecutionBriefContextBlock renders a single canonical state block', () => {
|
|
@@ -139,7 +120,6 @@ test('buildExecutionBriefContextBlock renders a single canonical state block', (
|
|
|
139
120
|
test('serializeExecutionBriefForDelegation creates a bounded handoff summary', () => {
|
|
140
121
|
const text = serializeExecutionBriefForDelegation({
|
|
141
122
|
sessionId: 's3',
|
|
142
|
-
missionId: 'm3',
|
|
143
123
|
objective: 'Repair the deployment pipeline',
|
|
144
124
|
summary: 'The regression is isolated to the release job.',
|
|
145
125
|
status: 'blocked',
|
|
@@ -153,9 +133,6 @@ test('serializeExecutionBriefForDelegation creates a bounded handoff summary', (
|
|
|
153
133
|
artifacts: ['/tmp/project/release.log'],
|
|
154
134
|
constraints: ['Keep the current release shape.'],
|
|
155
135
|
successCriteria: ['Production deploy completes'],
|
|
156
|
-
missionStatus: 'active',
|
|
157
|
-
missionPhase: 'executing',
|
|
158
|
-
waitState: null,
|
|
159
136
|
evidenceRefs: [],
|
|
160
137
|
parentContext: 'Parent is waiting on a concise status update.',
|
|
161
138
|
})
|
|
@@ -2,7 +2,6 @@ import type {
|
|
|
2
2
|
EvidenceRef,
|
|
3
3
|
ExecutionBrief,
|
|
4
4
|
ExecutionBriefPlanStep,
|
|
5
|
-
Mission,
|
|
6
5
|
Session,
|
|
7
6
|
SessionWorkingState,
|
|
8
7
|
WorkingPlanStep,
|
|
@@ -75,14 +74,8 @@ function dedupePlan(steps: ExecutionBriefPlanStep[]): ExecutionBriefPlanStep[] {
|
|
|
75
74
|
return out
|
|
76
75
|
}
|
|
77
76
|
|
|
78
|
-
function inferStatus(
|
|
77
|
+
function inferStatus(workingState: SessionWorkingState | null): WorkingStateStatus {
|
|
79
78
|
if (workingState?.status) return workingState.status
|
|
80
|
-
if (!mission) return 'idle'
|
|
81
|
-
if (mission.status === 'completed') return 'completed'
|
|
82
|
-
if (mission.status === 'waiting') return 'waiting'
|
|
83
|
-
if (mission.status === 'failed' || mission.phase === 'failed') return 'blocked'
|
|
84
|
-
if (mission.waitState) return 'waiting'
|
|
85
|
-
if (mission.phase === 'executing' || mission.phase === 'dispatching' || mission.phase === 'verifying') return 'progress'
|
|
86
79
|
return 'idle'
|
|
87
80
|
}
|
|
88
81
|
|
|
@@ -121,15 +114,14 @@ function buildFacts(workingState: SessionWorkingState | null, session: Session |
|
|
|
121
114
|
return uniqueStrings([...activeFacts, ...runContextFacts], MAX_FACTS, 240)
|
|
122
115
|
}
|
|
123
116
|
|
|
124
|
-
function buildBlockers(workingState: SessionWorkingState | null,
|
|
117
|
+
function buildBlockers(workingState: SessionWorkingState | null, session: Session | null): string[] {
|
|
125
118
|
const activeBlockers = workingState
|
|
126
119
|
? workingState.blockers
|
|
127
120
|
.filter((blocker) => blocker.status === 'active')
|
|
128
121
|
.map((blocker) => blocker.nextAction ? `${blocker.summary} | next: ${blocker.nextAction}` : blocker.summary)
|
|
129
122
|
: []
|
|
130
|
-
const missionBlockers = mission?.waitState?.reason ? [cleanText(mission.waitState.reason, 280)] : []
|
|
131
123
|
const runContextBlockers = session?.runContext ? ensureRunContext(session.runContext).blockers : []
|
|
132
|
-
return uniqueStrings([...activeBlockers, ...
|
|
124
|
+
return uniqueStrings([...activeBlockers, ...runContextBlockers], MAX_BLOCKERS, 280)
|
|
133
125
|
}
|
|
134
126
|
|
|
135
127
|
function buildArtifacts(workingState: SessionWorkingState | null): string[] {
|
|
@@ -147,8 +139,8 @@ function buildConstraints(workingState: SessionWorkingState | null, session: Ses
|
|
|
147
139
|
return uniqueStrings([...(workingConstraints || []), ...(runContext?.constraints || [])], 10, 220)
|
|
148
140
|
}
|
|
149
141
|
|
|
150
|
-
function buildSuccessCriteria(workingState: SessionWorkingState | null
|
|
151
|
-
return uniqueStrings([...(workingState?.successCriteria || [])
|
|
142
|
+
function buildSuccessCriteria(workingState: SessionWorkingState | null): string[] {
|
|
143
|
+
return uniqueStrings([...(workingState?.successCriteria || [])], 10, 220)
|
|
152
144
|
}
|
|
153
145
|
|
|
154
146
|
function buildEvidenceRefs(workingState: SessionWorkingState | null): EvidenceRef[] {
|
|
@@ -161,49 +153,40 @@ function buildEvidenceRefs(workingState: SessionWorkingState | null): EvidenceRe
|
|
|
161
153
|
export function buildExecutionBrief(params: {
|
|
162
154
|
sessionId?: string | null
|
|
163
155
|
session?: Session | null
|
|
164
|
-
mission?:
|
|
156
|
+
mission?: null
|
|
165
157
|
workingState?: SessionWorkingState | null
|
|
166
158
|
}): ExecutionBrief {
|
|
167
159
|
const session = params.session
|
|
168
160
|
|| (params.sessionId ? getSession(params.sessionId) || null : null)
|
|
169
|
-
const mission = params.mission || null
|
|
170
161
|
const workingState = params.workingState
|
|
171
|
-
|| (session?.id ? loadSessionWorkingState(session.id
|
|
162
|
+
|| (session?.id ? loadSessionWorkingState(session.id) : null)
|
|
172
163
|
const runContext = session?.runContext ? ensureRunContext(session.runContext) : null
|
|
173
164
|
const plan = buildPlan(workingState, session)
|
|
174
165
|
const nextAction = cleanText(
|
|
175
166
|
workingState?.nextAction
|
|
176
|
-
|| mission?.currentStep
|
|
177
167
|
|| plan.find((step) => step.status === 'active')?.text,
|
|
178
168
|
240,
|
|
179
169
|
) || null
|
|
180
170
|
|
|
181
171
|
return {
|
|
182
172
|
sessionId: session?.id || params.sessionId || null,
|
|
183
|
-
missionId: mission?.id || workingState?.missionId || null,
|
|
184
173
|
objective: cleanMultiline(
|
|
185
174
|
workingState?.objective
|
|
186
|
-
|| mission?.objective
|
|
187
175
|
|| runContext?.objective,
|
|
188
176
|
900,
|
|
189
177
|
) || null,
|
|
190
178
|
summary: cleanMultiline(
|
|
191
|
-
workingState?.summary
|
|
192
|
-
|| mission?.verifierSummary
|
|
193
|
-
|| mission?.plannerSummary,
|
|
179
|
+
workingState?.summary,
|
|
194
180
|
700,
|
|
195
181
|
) || null,
|
|
196
|
-
status: inferStatus(
|
|
182
|
+
status: inferStatus(workingState),
|
|
197
183
|
nextAction,
|
|
198
184
|
plan,
|
|
199
|
-
blockers: buildBlockers(workingState,
|
|
185
|
+
blockers: buildBlockers(workingState, session),
|
|
200
186
|
facts: buildFacts(workingState, session),
|
|
201
187
|
artifacts: buildArtifacts(workingState),
|
|
202
188
|
constraints: buildConstraints(workingState, session),
|
|
203
|
-
successCriteria: buildSuccessCriteria(workingState
|
|
204
|
-
missionStatus: mission?.status || null,
|
|
205
|
-
missionPhase: mission?.phase || null,
|
|
206
|
-
waitState: mission?.waitState || null,
|
|
189
|
+
successCriteria: buildSuccessCriteria(workingState),
|
|
207
190
|
evidenceRefs: buildEvidenceRefs(workingState),
|
|
208
191
|
parentContext: cleanMultiline(runContext?.parentContext, 900) || null,
|
|
209
192
|
}
|
|
@@ -238,10 +221,7 @@ export function buildExecutionBriefContextBlock(
|
|
|
238
221
|
|| brief.artifacts.length > 0
|
|
239
222
|
|| brief.constraints.length > 0
|
|
240
223
|
|| brief.successCriteria.length > 0
|
|
241
|
-
|| brief.evidenceRefs.length > 0
|
|
242
|
-
|| brief.missionStatus
|
|
243
|
-
|| brief.missionPhase
|
|
244
|
-
|| brief.waitState?.reason,
|
|
224
|
+
|| brief.evidenceRefs.length > 0,
|
|
245
225
|
)
|
|
246
226
|
if (!hasContent && brief.status === 'idle') return ''
|
|
247
227
|
const sections = [
|
|
@@ -250,9 +230,6 @@ export function buildExecutionBriefContextBlock(
|
|
|
250
230
|
brief.objective ? `Objective: ${brief.objective}` : '',
|
|
251
231
|
brief.summary ? `Summary: ${brief.summary}` : '',
|
|
252
232
|
`Status: ${brief.status}`,
|
|
253
|
-
brief.missionStatus ? `Mission status: ${brief.missionStatus}` : '',
|
|
254
|
-
brief.missionPhase ? `Mission phase: ${brief.missionPhase}` : '',
|
|
255
|
-
brief.waitState?.reason ? `Waiting reason: ${cleanText(brief.waitState.reason, 280)}` : '',
|
|
256
233
|
brief.nextAction ? `Next action: ${brief.nextAction}` : '',
|
|
257
234
|
brief.successCriteria.length > 0 ? `Success criteria: ${brief.successCriteria.join(' | ')}` : '',
|
|
258
235
|
brief.constraints.length > 0 ? `Constraints: ${brief.constraints.join(' | ')}` : '',
|
|
@@ -238,7 +238,6 @@ export function enqueueTaskAttemptExecution(
|
|
|
238
238
|
const run: SessionRunRecord = {
|
|
239
239
|
id: executionId,
|
|
240
240
|
sessionId: input.sessionId,
|
|
241
|
-
missionId: input.task.missionId || null,
|
|
242
241
|
kind: 'task_attempt',
|
|
243
242
|
ownerType: 'task',
|
|
244
243
|
ownerId: input.task.id,
|
|
@@ -4,6 +4,7 @@ import { genId } from '@/lib/id'
|
|
|
4
4
|
import { normalizeOpenClawEndpoint } from '@/lib/openclaw/openclaw-endpoint'
|
|
5
5
|
import { listAgents, saveAgentMany } from '@/lib/server/agents/agent-repository'
|
|
6
6
|
import { getGatewayProfiles } from '@/lib/server/agents/agent-runtime-config'
|
|
7
|
+
import { deleteCredentialRecord } from '@/lib/server/credentials/credential-service'
|
|
7
8
|
import {
|
|
8
9
|
loadGatewayProfile,
|
|
9
10
|
loadGatewayProfiles,
|
|
@@ -161,7 +162,9 @@ export function updateGatewayProfile(id: string, input: Record<string, unknown>)
|
|
|
161
162
|
|
|
162
163
|
export function deleteGatewayProfileAndDetachAgents(id: string): boolean {
|
|
163
164
|
const gateways = loadGatewayProfiles()
|
|
164
|
-
|
|
165
|
+
const deleted = gateways[id]
|
|
166
|
+
if (!deleted) return false
|
|
167
|
+
const orphanCredentialId = deleted.credentialId || null
|
|
165
168
|
delete gateways[id]
|
|
166
169
|
saveGatewayProfiles(gateways)
|
|
167
170
|
|
|
@@ -195,6 +198,21 @@ export function deleteGatewayProfileAndDetachAgents(id: string): boolean {
|
|
|
195
198
|
}
|
|
196
199
|
|
|
197
200
|
if (changed.length > 0) saveAgentMany(changed)
|
|
201
|
+
|
|
202
|
+
// Clean up orphaned credential if no other gateway or agent references it
|
|
203
|
+
if (orphanCredentialId) {
|
|
204
|
+
const stillReferencedByGateway = Object.values(gateways).some(
|
|
205
|
+
(gw) => gw && gw.credentialId === orphanCredentialId,
|
|
206
|
+
)
|
|
207
|
+
const stillReferencedByAgent = !stillReferencedByGateway && Object.values(agents).some(
|
|
208
|
+
(a) => a.credentialId === orphanCredentialId
|
|
209
|
+
|| (Array.isArray(a.fallbackCredentialIds) && a.fallbackCredentialIds.includes(orphanCredentialId)),
|
|
210
|
+
)
|
|
211
|
+
if (!stillReferencedByGateway && !stillReferencedByAgent) {
|
|
212
|
+
deleteCredentialRecord(orphanCredentialId)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
198
216
|
notify('gateways')
|
|
199
217
|
return true
|
|
200
218
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import test from 'node:test'
|
|
3
|
+
import { runWithTempDataDir } from '@/lib/server/test-utils/run-with-temp-data-dir'
|
|
4
|
+
|
|
5
|
+
test('appendMessage notifies both generic and per-session message topics', () => {
|
|
6
|
+
const output = runWithTempDataDir<{
|
|
7
|
+
genericTopics: string[]
|
|
8
|
+
sessionTopics: string[]
|
|
9
|
+
}>(`
|
|
10
|
+
const { WebSocket } = await import('ws')
|
|
11
|
+
const storageMod = await import('@/lib/server/storage')
|
|
12
|
+
const repoMod = await import('@/lib/server/messages/message-repository')
|
|
13
|
+
const storage = storageMod.default || storageMod
|
|
14
|
+
const repo = repoMod.default || repoMod
|
|
15
|
+
|
|
16
|
+
storage.saveSessions({
|
|
17
|
+
'sess-notify': {
|
|
18
|
+
id: 'sess-notify',
|
|
19
|
+
name: 'Notify Session',
|
|
20
|
+
cwd: process.env.WORKSPACE_DIR,
|
|
21
|
+
user: 'tester',
|
|
22
|
+
provider: 'openai',
|
|
23
|
+
model: 'gpt-5',
|
|
24
|
+
claudeSessionId: null,
|
|
25
|
+
codexThreadId: null,
|
|
26
|
+
opencodeSessionId: null,
|
|
27
|
+
delegateResumeIds: { claudeCode: null, codex: null, opencode: null, gemini: null },
|
|
28
|
+
messages: [],
|
|
29
|
+
createdAt: Date.now(),
|
|
30
|
+
lastActiveAt: Date.now(),
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const genericPayloads = []
|
|
35
|
+
const sessionPayloads = []
|
|
36
|
+
globalThis.__swarmclaw_ws__ = {
|
|
37
|
+
wss: null,
|
|
38
|
+
clients: new Set([
|
|
39
|
+
{
|
|
40
|
+
ws: {
|
|
41
|
+
readyState: WebSocket.OPEN,
|
|
42
|
+
send(payload) { genericPayloads.push(JSON.parse(payload)) },
|
|
43
|
+
},
|
|
44
|
+
topics: new Set(['messages']),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
ws: {
|
|
48
|
+
readyState: WebSocket.OPEN,
|
|
49
|
+
send(payload) { sessionPayloads.push(JSON.parse(payload)) },
|
|
50
|
+
},
|
|
51
|
+
topics: new Set(['messages:sess-notify']),
|
|
52
|
+
},
|
|
53
|
+
]),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
repo.appendMessage('sess-notify', {
|
|
57
|
+
role: 'user',
|
|
58
|
+
text: 'hello',
|
|
59
|
+
time: 1,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
console.log(JSON.stringify({
|
|
63
|
+
genericTopics: genericPayloads.map((payload) => payload.topic),
|
|
64
|
+
sessionTopics: sessionPayloads.map((payload) => payload.topic),
|
|
65
|
+
}))
|
|
66
|
+
`, { prefix: 'swarmclaw-message-repo-notify-' })
|
|
67
|
+
|
|
68
|
+
assert.deepEqual(output.genericTopics, ['messages'])
|
|
69
|
+
assert.deepEqual(output.sessionTopics, ['messages:sess-notify'])
|
|
70
|
+
})
|