@swarmclawai/swarmclaw 1.2.4 → 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/README.md +14 -0
  2. package/bin/daemon-cmd.js +169 -0
  3. package/bin/server-cmd.js +3 -0
  4. package/bin/swarmclaw.js +11 -0
  5. package/package.json +17 -16
  6. package/src/app/api/agents/[id]/clone/route.ts +3 -32
  7. package/src/app/api/agents/[id]/route.ts +6 -158
  8. package/src/app/api/agents/[id]/status/route.ts +2 -3
  9. package/src/app/api/agents/[id]/thread/route.ts +4 -17
  10. package/src/app/api/agents/bulk/route.ts +5 -47
  11. package/src/app/api/agents/route.ts +5 -119
  12. package/src/app/api/agents/trash/route.ts +13 -24
  13. package/src/app/api/auth/route.ts +3 -9
  14. package/src/app/api/autonomy/estop/route.ts +5 -5
  15. package/src/app/api/chatrooms/[id]/chat/route.ts +11 -5
  16. package/src/app/api/chatrooms/[id]/route.ts +23 -2
  17. package/src/app/api/chatrooms/route.ts +13 -2
  18. package/src/app/api/chats/[id]/clear/route.ts +2 -13
  19. package/src/app/api/chats/[id]/deploy/route.ts +2 -3
  20. package/src/app/api/chats/[id]/edit-resend/route.ts +7 -13
  21. package/src/app/api/chats/[id]/mailbox/route.ts +6 -8
  22. package/src/app/api/chats/[id]/queue/route.ts +17 -64
  23. package/src/app/api/chats/[id]/retry/route.ts +4 -22
  24. package/src/app/api/chats/[id]/route.ts +10 -138
  25. package/src/app/api/chats/heartbeat/route.ts +2 -1
  26. package/src/app/api/chats/migrate-messages/route.ts +7 -0
  27. package/src/app/api/chats/route.ts +13 -134
  28. package/src/app/api/connectors/[id]/access/route.ts +12 -229
  29. package/src/app/api/connectors/[id]/doctor/route.ts +1 -1
  30. package/src/app/api/connectors/[id]/health/route.ts +12 -39
  31. package/src/app/api/connectors/[id]/route.ts +14 -122
  32. package/src/app/api/connectors/[id]/webhook/route.ts +1 -1
  33. package/src/app/api/connectors/doctor/route.ts +1 -1
  34. package/src/app/api/connectors/route.ts +12 -70
  35. package/src/app/api/credentials/[id]/route.ts +2 -4
  36. package/src/app/api/credentials/route.ts +10 -19
  37. package/src/app/api/daemon/health-check/route.ts +3 -4
  38. package/src/app/api/daemon/route.ts +10 -8
  39. package/src/app/api/documents/route.ts +11 -10
  40. package/src/app/api/external-agents/route.ts +3 -3
  41. package/src/app/api/gateways/[id]/health/route.ts +2 -3
  42. package/src/app/api/gateways/[id]/route.ts +7 -122
  43. package/src/app/api/gateways/route.ts +3 -103
  44. package/src/app/api/mcp-servers/[id]/tools/route.ts +5 -5
  45. package/src/app/api/openclaw/dashboard-url/route.ts +8 -16
  46. package/src/app/api/openclaw/directory/route.ts +2 -2
  47. package/src/app/api/openclaw/history/route.ts +3 -5
  48. package/src/app/api/providers/[id]/route.test.ts +49 -0
  49. package/src/app/api/providers/ollama/route.ts +6 -5
  50. package/src/app/api/schedules/[id]/route.ts +14 -108
  51. package/src/app/api/schedules/[id]/run/route.ts +6 -67
  52. package/src/app/api/schedules/route.ts +9 -51
  53. package/src/app/api/settings/route.ts +4 -3
  54. package/src/app/api/setup/check-provider/route.ts +15 -1
  55. package/src/app/api/setup/openclaw-device/route.ts +2 -2
  56. package/src/app/api/system/status/route.ts +2 -2
  57. package/src/app/api/tasks/[id]/route.ts +16 -202
  58. package/src/app/api/tasks/bulk/route.ts +5 -86
  59. package/src/app/api/tasks/metrics/route.ts +2 -1
  60. package/src/app/api/tasks/route.ts +11 -171
  61. package/src/app/api/upload/route.ts +1 -1
  62. package/src/app/api/uploads/[filename]/route.ts +1 -1
  63. package/src/app/api/uploads/route.ts +1 -1
  64. package/src/app/api/webhooks/[id]/history/route.ts +2 -2
  65. package/src/app/layout.tsx +9 -6
  66. package/src/app/protocols/page.tsx +71 -89
  67. package/src/app/tasks/page.tsx +32 -32
  68. package/src/cli/index.js +1 -0
  69. package/src/cli/spec.js +1 -0
  70. package/src/components/agents/agent-sheet.tsx +5 -5
  71. package/src/components/auth/setup-wizard/index.tsx +4 -4
  72. package/src/components/auth/setup-wizard/step-agents.tsx +1 -1
  73. package/src/components/auth/setup-wizard/step-connect.tsx +1 -1
  74. package/src/components/auth/setup-wizard/utils.ts +1 -1
  75. package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
  76. package/src/components/connectors/connector-list.tsx +26 -40
  77. package/src/components/connectors/connector-sheet.tsx +95 -149
  78. package/src/components/gateways/gateway-sheet.tsx +61 -110
  79. package/src/components/layout/live-query-sync.tsx +121 -0
  80. package/src/components/protocols/structured-session-launcher.tsx +24 -45
  81. package/src/components/providers/app-query-provider.tsx +17 -0
  82. package/src/components/providers/provider-list.tsx +60 -61
  83. package/src/components/providers/provider-sheet.tsx +74 -56
  84. package/src/components/skills/skill-list.tsx +5 -18
  85. package/src/components/skills/skill-sheet.tsx +21 -20
  86. package/src/components/skills/skills-workspace.tsx +48 -87
  87. package/src/components/tasks/task-card.tsx +20 -13
  88. package/src/components/tasks/task-column.tsx +22 -7
  89. package/src/components/tasks/task-list.tsx +8 -11
  90. package/src/components/tasks/task-sheet.tsx +111 -103
  91. package/src/features/agents/queries.ts +20 -0
  92. package/src/features/chatrooms/queries.ts +20 -0
  93. package/src/features/chats/queries.ts +27 -0
  94. package/src/features/connectors/queries.ts +145 -0
  95. package/src/features/credentials/queries.ts +37 -0
  96. package/src/features/extensions/queries.ts +26 -0
  97. package/src/features/external-agents/queries.ts +36 -0
  98. package/src/features/gateways/queries.ts +274 -0
  99. package/src/features/missions/queries.ts +23 -0
  100. package/src/features/projects/queries.ts +20 -0
  101. package/src/features/protocols/queries.ts +149 -0
  102. package/src/features/providers/queries.ts +142 -0
  103. package/src/features/settings/queries.ts +20 -0
  104. package/src/features/skills/queries.ts +182 -0
  105. package/src/features/tasks/queries.ts +189 -0
  106. package/src/hooks/use-ws.ts +3 -2
  107. package/src/lib/app/api-client.ts +2 -2
  108. package/src/lib/query/client.ts +17 -0
  109. package/src/lib/server/agents/agent-runtime-config.ts +1 -1
  110. package/src/lib/server/agents/agent-service.ts +429 -0
  111. package/src/lib/server/agents/agent-thread-session.ts +6 -5
  112. package/src/lib/server/agents/autonomy-contract.ts +1 -4
  113. package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
  114. package/src/lib/server/agents/delegation-advisory.ts +251 -0
  115. package/src/lib/server/agents/main-agent-loop.ts +98 -40
  116. package/src/lib/server/agents/subagent-runtime.ts +12 -0
  117. package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
  118. package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
  119. package/src/lib/server/build-llm.ts +7 -15
  120. package/src/lib/server/capability-router.test.ts +70 -1
  121. package/src/lib/server/capability-router.ts +24 -99
  122. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
  123. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
  124. package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
  125. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
  126. package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
  127. package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
  128. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
  129. package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
  130. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
  131. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
  132. package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
  133. package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
  134. package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
  135. package/src/lib/server/chat-execution/message-classifier.ts +74 -32
  136. package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
  137. package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
  138. package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
  139. package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
  140. package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
  141. package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
  142. package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
  143. package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
  144. package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
  145. package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
  146. package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
  147. package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
  148. package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
  149. package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
  150. package/src/lib/server/chats/chat-session-service.ts +410 -0
  151. package/src/lib/server/connectors/access.ts +1 -1
  152. package/src/lib/server/connectors/commands.ts +7 -6
  153. package/src/lib/server/connectors/connector-inbound.ts +14 -7
  154. package/src/lib/server/connectors/connector-outbound.ts +16 -11
  155. package/src/lib/server/connectors/connector-service.ts +453 -0
  156. package/src/lib/server/connectors/delivery.ts +17 -12
  157. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
  158. package/src/lib/server/connectors/media.ts +1 -1
  159. package/src/lib/server/connectors/response-media.ts +1 -1
  160. package/src/lib/server/connectors/session-consolidation.ts +11 -7
  161. package/src/lib/server/connectors/session.ts +9 -7
  162. package/src/lib/server/connectors/voice-note.ts +2 -1
  163. package/src/lib/server/context-manager.ts +20 -1
  164. package/src/lib/server/cost.ts +2 -3
  165. package/src/lib/server/credentials/credential-repository.ts +43 -4
  166. package/src/lib/server/credentials/credential-service.ts +112 -0
  167. package/src/lib/server/daemon/admin-metadata.ts +64 -0
  168. package/src/lib/server/daemon/controller.ts +577 -0
  169. package/src/lib/server/daemon/daemon-runtime.ts +352 -0
  170. package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
  171. package/src/lib/server/daemon/types.ts +101 -0
  172. package/src/lib/server/embeddings.ts +3 -9
  173. package/src/lib/server/eval/agent-regression.ts +3 -2
  174. package/src/lib/server/eval/runner.ts +2 -2
  175. package/src/lib/server/execution-brief.test.ts +167 -0
  176. package/src/lib/server/execution-brief.ts +295 -0
  177. package/src/lib/server/execution-engine/chat-turn.ts +9 -0
  178. package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
  179. package/src/lib/server/execution-engine/index.ts +35 -0
  180. package/src/lib/server/execution-engine/task-attempt.ts +303 -0
  181. package/src/lib/server/execution-engine/types.ts +33 -0
  182. package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
  183. package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
  184. package/src/lib/server/memory/session-archive-memory.ts +12 -10
  185. package/src/lib/server/messages/message-repository.ts +330 -0
  186. package/src/lib/server/missions/mission-service/core.ts +8 -6
  187. package/src/lib/server/openclaw/agent-resolver.ts +2 -3
  188. package/src/lib/server/openclaw/doctor.ts +1 -1
  189. package/src/lib/server/openclaw/gateway.test.ts +10 -1
  190. package/src/lib/server/openclaw/gateway.ts +5 -14
  191. package/src/lib/server/openclaw/health.ts +3 -11
  192. package/src/lib/server/openclaw/sync.ts +8 -6
  193. package/src/lib/server/persistence/storage-context.ts +3 -0
  194. package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
  195. package/src/lib/server/protocols/protocol-normalization.ts +1 -1
  196. package/src/lib/server/protocols/protocol-queries.ts +13 -7
  197. package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
  198. package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
  199. package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
  200. package/src/lib/server/protocols/protocol-swarm.ts +8 -8
  201. package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
  202. package/src/lib/server/protocols/protocol-templates.ts +4 -2
  203. package/src/lib/server/protocols/protocol-types.ts +10 -7
  204. package/src/lib/server/provider-endpoint.ts +7 -12
  205. package/src/lib/server/provider-model-discovery.ts +2 -11
  206. package/src/lib/server/query-expansion.ts +5 -6
  207. package/src/lib/server/run-context.test.ts +365 -0
  208. package/src/lib/server/run-context.ts +367 -0
  209. package/src/lib/server/runtime/heartbeat-service.ts +7 -5
  210. package/src/lib/server/runtime/queue/core.ts +61 -190
  211. package/src/lib/server/runtime/run-ledger.ts +8 -0
  212. package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
  213. package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
  214. package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
  215. package/src/lib/server/schedules/schedule-route-service.ts +230 -0
  216. package/src/lib/server/service-result.ts +16 -0
  217. package/src/lib/server/session-note.ts +2 -3
  218. package/src/lib/server/session-reset-policy.ts +4 -3
  219. package/src/lib/server/session-tools/connector.ts +9 -6
  220. package/src/lib/server/session-tools/context-mgmt.ts +58 -9
  221. package/src/lib/server/session-tools/crud.ts +162 -10
  222. package/src/lib/server/session-tools/delegate.ts +1 -1
  223. package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
  224. package/src/lib/server/session-tools/memory.ts +6 -4
  225. package/src/lib/server/session-tools/session-info.test.ts +56 -0
  226. package/src/lib/server/session-tools/session-info.ts +119 -12
  227. package/src/lib/server/session-tools/skill-runtime.ts +3 -1
  228. package/src/lib/server/session-tools/skills.ts +15 -15
  229. package/src/lib/server/session-tools/subagent.test.ts +115 -1
  230. package/src/lib/server/session-tools/subagent.ts +125 -7
  231. package/src/lib/server/session-tools/team-context.ts +4 -3
  232. package/src/lib/server/session-tools/wallet.ts +0 -58
  233. package/src/lib/server/sessions/session-lineage.ts +55 -0
  234. package/src/lib/server/sessions/session-repository.ts +2 -2
  235. package/src/lib/server/skills/learned-skills.ts +24 -23
  236. package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
  237. package/src/lib/server/skills/skill-repository.ts +136 -13
  238. package/src/lib/server/skills/skill-suggestions.ts +25 -28
  239. package/src/lib/server/storage-normalization.test.ts +44 -267
  240. package/src/lib/server/storage-normalization.ts +75 -0
  241. package/src/lib/server/storage.ts +19 -0
  242. package/src/lib/server/structured-extract.ts +3 -14
  243. package/src/lib/server/tasks/task-followups.ts +16 -11
  244. package/src/lib/server/tasks/task-result.test.ts +25 -29
  245. package/src/lib/server/tasks/task-result.ts +5 -9
  246. package/src/lib/server/tasks/task-route-service.ts +449 -0
  247. package/src/lib/server/text-normalization.ts +41 -0
  248. package/src/lib/server/tool-planning.ts +6 -42
  249. package/src/lib/server/upload-path.ts +5 -0
  250. package/src/lib/server/working-state/extraction.ts +614 -0
  251. package/src/lib/server/working-state/normalization.ts +866 -0
  252. package/src/lib/server/working-state/prompt.ts +60 -0
  253. package/src/lib/server/working-state/repository.ts +38 -0
  254. package/src/lib/server/working-state/service.test.ts +253 -0
  255. package/src/lib/server/working-state/service.ts +293 -0
  256. package/src/lib/validation/schemas.ts +1 -0
  257. package/src/lib/ws-client.ts +3 -3
  258. package/src/stores/slices/task-slice.ts +1 -4
  259. package/src/stores/use-chatroom-store.ts +2 -2
  260. package/src/types/index.ts +277 -12
@@ -0,0 +1,352 @@
1
+ import http from 'node:http'
2
+ import { URL } from 'node:url'
3
+
4
+ import { loadConnectors } from '@/lib/server/connectors/connector-repository'
5
+ import {
6
+ clearReconnectState,
7
+ getConnectorPresence,
8
+ getConnectorQR,
9
+ getConnectorStatus,
10
+ getReconnectState,
11
+ hasConnectorCredentials,
12
+ isConnectorAuthenticated,
13
+ listRunningConnectors,
14
+ repairConnector,
15
+ startConnector,
16
+ stopConnector,
17
+ } from '@/lib/server/connectors/manager'
18
+ import { log } from '@/lib/server/logger'
19
+ import {
20
+ getDaemonHealthSummary,
21
+ getDaemonStatus,
22
+ runDaemonHealthCheckNow,
23
+ startDaemon,
24
+ stopDaemon,
25
+ } from '@/lib/server/runtime/daemon-state'
26
+ import {
27
+ clearDaemonAdminMetadata,
28
+ readDaemonAdminMetadata,
29
+ writeDaemonAdminMetadata,
30
+ } from '@/lib/server/daemon/admin-metadata'
31
+ import {
32
+ loadDaemonStatusRecord,
33
+ patchDaemonStatusRecord,
34
+ } from '@/lib/server/daemon/daemon-status-repository'
35
+ import type {
36
+ DaemonAdminMetadata,
37
+ DaemonConnectorRuntimeState,
38
+ DaemonHealthSummaryPayload,
39
+ DaemonRunningConnectorInfo,
40
+ DaemonStatusPayload,
41
+ } from '@/lib/server/daemon/types'
42
+
43
+ const TAG = 'daemon-runtime'
44
+ const HEARTBEAT_FLUSH_INTERVAL_MS = 5_000
45
+
46
+ type AdminSnapshotResponse = {
47
+ status: DaemonStatusPayload
48
+ healthSummary: DaemonHealthSummaryPayload
49
+ }
50
+
51
+ function parseArgs(argv: string[]): { port: number; token: string } {
52
+ let port = Number.parseInt(process.env.SWARMCLAW_DAEMON_ADMIN_PORT || '', 10)
53
+ let token = (process.env.SWARMCLAW_DAEMON_ADMIN_TOKEN || '').trim()
54
+ for (let index = 0; index < argv.length; index += 1) {
55
+ const arg = argv[index]
56
+ if (arg === '--port' && index + 1 < argv.length) {
57
+ port = Number.parseInt(argv[index + 1] || '', 10)
58
+ index += 1
59
+ } else if (arg === '--token' && index + 1 < argv.length) {
60
+ token = (argv[index + 1] || '').trim()
61
+ index += 1
62
+ }
63
+ }
64
+ if (!Number.isFinite(port) || port <= 0) {
65
+ throw new Error('Missing daemon admin port.')
66
+ }
67
+ if (!token) {
68
+ throw new Error('Missing daemon admin token.')
69
+ }
70
+ return { port, token }
71
+ }
72
+
73
+ function sendJson(res: http.ServerResponse, statusCode: number, payload: unknown): void {
74
+ const body = JSON.stringify(payload)
75
+ res.writeHead(statusCode, {
76
+ 'content-type': 'application/json; charset=utf-8',
77
+ 'content-length': Buffer.byteLength(body),
78
+ })
79
+ res.end(body)
80
+ }
81
+
82
+ async function readJsonBody(req: http.IncomingMessage): Promise<Record<string, unknown>> {
83
+ const chunks: Buffer[] = []
84
+ for await (const chunk of req) {
85
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))
86
+ }
87
+ if (chunks.length === 0) return {}
88
+ return JSON.parse(Buffer.concat(chunks).toString('utf8')) as Record<string, unknown>
89
+ }
90
+
91
+ function buildSnapshot(): AdminSnapshotResponse {
92
+ return {
93
+ status: getDaemonStatus() as DaemonStatusPayload,
94
+ healthSummary: getDaemonHealthSummary() as DaemonHealthSummaryPayload,
95
+ }
96
+ }
97
+
98
+ function buildConnectorRuntimeSnapshot(): Record<string, DaemonConnectorRuntimeState> {
99
+ const connectors = loadConnectors()
100
+ const snapshot: Record<string, DaemonConnectorRuntimeState> = {}
101
+ for (const connector of Object.values(connectors)) {
102
+ const runtimeStatus = getConnectorStatus(connector.id)
103
+ const reconnectState = getReconnectState(connector.id)
104
+ snapshot[connector.id] = {
105
+ status: runtimeStatus === 'running'
106
+ ? 'running'
107
+ : connector.lastError
108
+ ? 'error'
109
+ : 'stopped',
110
+ authenticated: connector.platform === 'whatsapp' ? isConnectorAuthenticated(connector.id) : undefined,
111
+ hasCredentials: connector.platform === 'whatsapp' ? hasConnectorCredentials(connector.id) : undefined,
112
+ qrDataUrl: connector.platform === 'whatsapp' ? getConnectorQR(connector.id) : undefined,
113
+ reconnectAttempts: reconnectState?.attempts,
114
+ nextRetryAt: reconnectState?.nextRetryAt,
115
+ reconnectError: reconnectState?.error ?? null,
116
+ reconnectExhausted: reconnectState?.exhausted,
117
+ presence: runtimeStatus === 'running' ? getConnectorPresence(connector.id) : null,
118
+ }
119
+ }
120
+ return snapshot
121
+ }
122
+
123
+ async function buildConnectorActionSnapshot(
124
+ connectorId: string,
125
+ action: 'start' | 'stop' | 'repair',
126
+ ): Promise<DaemonConnectorRuntimeState | null> {
127
+ if (!loadConnectors()[connectorId]) return null
128
+ if (action === 'start') {
129
+ clearReconnectState(connectorId)
130
+ await startConnector(connectorId)
131
+ } else if (action === 'stop') {
132
+ await stopConnector(connectorId)
133
+ } else {
134
+ clearReconnectState(connectorId)
135
+ await repairConnector(connectorId)
136
+ }
137
+ return buildConnectorRuntimeSnapshot()[connectorId] || null
138
+ }
139
+
140
+ let shuttingDown = false
141
+ let heartbeatInterval: ReturnType<typeof setInterval> | null = null
142
+ let server: http.Server | null = null
143
+
144
+ function persistHeartbeat(): void {
145
+ const snapshot = buildSnapshot()
146
+ patchDaemonStatusRecord((current) => ({
147
+ ...current,
148
+ pid: process.pid,
149
+ desiredState: snapshot.status.running ? 'running' : current.desiredState,
150
+ manualStopRequested: current.manualStopRequested,
151
+ startedAt: current.startedAt || Date.now(),
152
+ stoppedAt: snapshot.status.running ? null : current.stoppedAt,
153
+ adminPort: readDaemonAdminMetadata()?.port ?? current.adminPort,
154
+ lastHeartbeatAt: Date.now(),
155
+ updatedAt: Date.now(),
156
+ lastError: null,
157
+ lastStatus: snapshot.status,
158
+ lastHealthSummary: snapshot.healthSummary,
159
+ }))
160
+ }
161
+
162
+ async function shutdown(source: string): Promise<void> {
163
+ if (shuttingDown) return
164
+ shuttingDown = true
165
+ if (heartbeatInterval) {
166
+ clearInterval(heartbeatInterval)
167
+ heartbeatInterval = null
168
+ }
169
+ try {
170
+ await stopDaemon({ source, manualStop: false })
171
+ } catch (err: unknown) {
172
+ log.warn(TAG, `Daemon runtime stop failed (${source})`, err)
173
+ }
174
+ const snapshot = buildSnapshot()
175
+ patchDaemonStatusRecord((current) => ({
176
+ ...current,
177
+ pid: null,
178
+ adminPort: null,
179
+ desiredState: 'stopped',
180
+ startedAt: current.startedAt,
181
+ stoppedAt: Date.now(),
182
+ lastHeartbeatAt: current.lastHeartbeatAt,
183
+ updatedAt: Date.now(),
184
+ lastStopSource: source,
185
+ lastStatus: {
186
+ ...snapshot.status,
187
+ running: false,
188
+ schedulerActive: false,
189
+ },
190
+ lastHealthSummary: {
191
+ ...snapshot.healthSummary,
192
+ ok: false,
193
+ components: {
194
+ ...snapshot.healthSummary.components,
195
+ daemon: { status: 'stopped' },
196
+ },
197
+ },
198
+ }))
199
+ clearDaemonAdminMetadata()
200
+ await new Promise<void>((resolve) => {
201
+ if (!server) {
202
+ resolve()
203
+ return
204
+ }
205
+ server.close(() => resolve())
206
+ })
207
+ }
208
+
209
+ async function main(): Promise<void> {
210
+ const { port, token } = parseArgs(process.argv.slice(2))
211
+ const metadata: DaemonAdminMetadata = {
212
+ pid: process.pid,
213
+ port,
214
+ token,
215
+ launchedAt: Date.now(),
216
+ source: loadDaemonStatusRecord().lastLaunchSource,
217
+ }
218
+ writeDaemonAdminMetadata(metadata)
219
+
220
+ const started = startDaemon({ source: 'daemon-runtime:boot', manualStart: true })
221
+ const snapshot = buildSnapshot()
222
+ if (!started && !snapshot.status.running) {
223
+ patchDaemonStatusRecord((current) => ({
224
+ ...current,
225
+ pid: null,
226
+ adminPort: null,
227
+ desiredState: 'stopped',
228
+ stoppedAt: Date.now(),
229
+ updatedAt: Date.now(),
230
+ lastError: 'Daemon runtime could not acquire execution lease.',
231
+ lastStatus: {
232
+ ...snapshot.status,
233
+ running: false,
234
+ schedulerActive: false,
235
+ },
236
+ lastHealthSummary: {
237
+ ...snapshot.healthSummary,
238
+ ok: false,
239
+ components: {
240
+ ...snapshot.healthSummary.components,
241
+ daemon: { status: 'stopped' },
242
+ },
243
+ },
244
+ }))
245
+ clearDaemonAdminMetadata()
246
+ process.exitCode = 1
247
+ return
248
+ }
249
+
250
+ persistHeartbeat()
251
+ heartbeatInterval = setInterval(() => {
252
+ persistHeartbeat()
253
+ }, HEARTBEAT_FLUSH_INTERVAL_MS)
254
+
255
+ server = http.createServer(async (req, res) => {
256
+ try {
257
+ const auth = req.headers.authorization || ''
258
+ if (auth !== `Bearer ${token}`) {
259
+ sendJson(res, 401, { error: 'Unauthorized' })
260
+ return
261
+ }
262
+ const url = new URL(req.url || '/', `http://127.0.0.1:${port}`)
263
+ if (req.method === 'GET' && url.pathname === '/status') {
264
+ sendJson(res, 200, buildSnapshot())
265
+ return
266
+ }
267
+ if (req.method === 'POST' && url.pathname === '/health-check') {
268
+ await runDaemonHealthCheckNow()
269
+ persistHeartbeat()
270
+ sendJson(res, 200, buildSnapshot())
271
+ return
272
+ }
273
+ if (req.method === 'POST' && url.pathname === '/stop') {
274
+ sendJson(res, 200, { ok: true })
275
+ void shutdown('daemon-admin:stop').finally(() => process.exit(0))
276
+ return
277
+ }
278
+ if (req.method === 'GET' && url.pathname === '/connectors') {
279
+ sendJson(res, 200, { connectors: buildConnectorRuntimeSnapshot() })
280
+ return
281
+ }
282
+ if (req.method === 'GET' && url.pathname === '/connectors/running') {
283
+ const platform = url.searchParams.get('platform') || undefined
284
+ sendJson(res, 200, { connectors: listRunningConnectors(platform) as DaemonRunningConnectorInfo[] })
285
+ return
286
+ }
287
+ const connectorMatch = url.pathname.match(/^\/connectors\/([^/]+)$/)
288
+ if (req.method === 'GET' && connectorMatch) {
289
+ const connectorId = decodeURIComponent(connectorMatch[1] || '')
290
+ sendJson(res, 200, { connector: buildConnectorRuntimeSnapshot()[connectorId] || null })
291
+ return
292
+ }
293
+ const actionMatch = url.pathname.match(/^\/connectors\/([^/]+)\/actions$/)
294
+ if (req.method === 'POST' && actionMatch) {
295
+ const connectorId = decodeURIComponent(actionMatch[1] || '')
296
+ const body = await readJsonBody(req)
297
+ const action = typeof body.action === 'string' ? body.action.trim().toLowerCase() : ''
298
+ if (action !== 'start' && action !== 'stop' && action !== 'repair') {
299
+ sendJson(res, 400, { error: 'Invalid connector action.' })
300
+ return
301
+ }
302
+ const connector = await buildConnectorActionSnapshot(connectorId, action)
303
+ sendJson(res, connector ? 200 : 404, { connector })
304
+ return
305
+ }
306
+ sendJson(res, 404, { error: 'Not found' })
307
+ } catch (err: unknown) {
308
+ sendJson(res, 500, { error: err instanceof Error ? err.message : 'Daemon admin failure' })
309
+ }
310
+ })
311
+
312
+ await new Promise<void>((resolve) => {
313
+ server!.listen(port, '127.0.0.1', () => resolve())
314
+ })
315
+
316
+ for (const signal of ['SIGINT', 'SIGTERM'] as const) {
317
+ process.on(signal, () => {
318
+ void shutdown(`signal:${signal}`).finally(() => process.exit(0))
319
+ })
320
+ }
321
+ process.on('uncaughtException', (err: Error) => {
322
+ patchDaemonStatusRecord((current) => ({
323
+ ...current,
324
+ lastError: err.message,
325
+ updatedAt: Date.now(),
326
+ }))
327
+ void shutdown('uncaughtException').finally(() => process.exit(1))
328
+ })
329
+ process.on('unhandledRejection', (reason: unknown) => {
330
+ patchDaemonStatusRecord((current) => ({
331
+ ...current,
332
+ lastError: reason instanceof Error ? reason.message : String(reason),
333
+ updatedAt: Date.now(),
334
+ }))
335
+ void shutdown('unhandledRejection').finally(() => process.exit(1))
336
+ })
337
+ }
338
+
339
+ void main().catch((err: unknown) => {
340
+ patchDaemonStatusRecord((current) => ({
341
+ ...current,
342
+ pid: null,
343
+ adminPort: null,
344
+ desiredState: 'stopped',
345
+ stoppedAt: Date.now(),
346
+ updatedAt: Date.now(),
347
+ lastError: err instanceof Error ? err.message : 'Daemon runtime failed to start',
348
+ }))
349
+ clearDaemonAdminMetadata()
350
+ log.error(TAG, 'Fatal daemon runtime error', err)
351
+ process.exit(1)
352
+ })
@@ -0,0 +1,63 @@
1
+ import {
2
+ loadStoredItem,
3
+ patchStoredItem,
4
+ upsertStoredItem,
5
+ } from '@/lib/server/storage'
6
+ import type {
7
+ DaemonHealthSummaryPayload,
8
+ DaemonStatusPayload,
9
+ PersistedDaemonStatusRecord,
10
+ } from '@/lib/server/daemon/types'
11
+
12
+ const DAEMON_STATUS_ID = 'primary'
13
+
14
+ function now(): number {
15
+ return Date.now()
16
+ }
17
+
18
+ function normalizeStatusPayload(value: unknown): DaemonStatusPayload | null {
19
+ return value && typeof value === 'object' ? value as DaemonStatusPayload : null
20
+ }
21
+
22
+ function normalizeHealthSummary(value: unknown): DaemonHealthSummaryPayload | null {
23
+ return value && typeof value === 'object' ? value as DaemonHealthSummaryPayload : null
24
+ }
25
+
26
+ function normalizeRecord(value: unknown): PersistedDaemonStatusRecord {
27
+ const record = value && typeof value === 'object' ? value as Partial<PersistedDaemonStatusRecord> : {}
28
+ return {
29
+ pid: typeof record.pid === 'number' && Number.isFinite(record.pid) ? Math.trunc(record.pid) : null,
30
+ adminPort: typeof record.adminPort === 'number' && Number.isFinite(record.adminPort) ? Math.trunc(record.adminPort) : null,
31
+ desiredState: record.desiredState === 'running' ? 'running' : 'stopped',
32
+ manualStopRequested: record.manualStopRequested === true,
33
+ startedAt: typeof record.startedAt === 'number' && Number.isFinite(record.startedAt) ? Math.trunc(record.startedAt) : null,
34
+ stoppedAt: typeof record.stoppedAt === 'number' && Number.isFinite(record.stoppedAt) ? Math.trunc(record.stoppedAt) : null,
35
+ lastHeartbeatAt: typeof record.lastHeartbeatAt === 'number' && Number.isFinite(record.lastHeartbeatAt) ? Math.trunc(record.lastHeartbeatAt) : null,
36
+ updatedAt: typeof record.updatedAt === 'number' && Number.isFinite(record.updatedAt) ? Math.trunc(record.updatedAt) : now(),
37
+ lastLaunchSource: typeof record.lastLaunchSource === 'string' ? record.lastLaunchSource : null,
38
+ lastStopSource: typeof record.lastStopSource === 'string' ? record.lastStopSource : null,
39
+ lastError: typeof record.lastError === 'string' ? record.lastError : null,
40
+ lastStatus: normalizeStatusPayload(record.lastStatus),
41
+ lastHealthSummary: normalizeHealthSummary(record.lastHealthSummary),
42
+ }
43
+ }
44
+
45
+ export function loadDaemonStatusRecord(): PersistedDaemonStatusRecord {
46
+ return normalizeRecord(loadStoredItem('daemon_status', DAEMON_STATUS_ID))
47
+ }
48
+
49
+ export function saveDaemonStatusRecord(record: PersistedDaemonStatusRecord | Record<string, unknown>): PersistedDaemonStatusRecord {
50
+ const normalized = normalizeRecord(record)
51
+ upsertStoredItem('daemon_status', DAEMON_STATUS_ID, normalized)
52
+ return normalized
53
+ }
54
+
55
+ export function patchDaemonStatusRecord(
56
+ updater: (current: PersistedDaemonStatusRecord) => PersistedDaemonStatusRecord | Record<string, unknown>,
57
+ ): PersistedDaemonStatusRecord {
58
+ const next = patchStoredItem<PersistedDaemonStatusRecord>('daemon_status', DAEMON_STATUS_ID, (current) => {
59
+ const normalized = normalizeRecord(current)
60
+ return normalizeRecord(updater(normalized))
61
+ })
62
+ return normalizeRecord(next)
63
+ }
@@ -0,0 +1,101 @@
1
+ import type { EstopState } from '@/types'
2
+
3
+ export interface DaemonStatusPayload {
4
+ running: boolean
5
+ schedulerActive: boolean
6
+ autostartEnabled: boolean
7
+ backgroundServicesEnabled: boolean
8
+ reducedMode: boolean
9
+ manualStopRequested: boolean
10
+ estop: EstopState
11
+ queueLength: number
12
+ lastProcessed: number | null
13
+ nextScheduled: number | null
14
+ heartbeat: Record<string, unknown> | null
15
+ health: {
16
+ monitorActive: boolean
17
+ connectorMonitorActive: boolean
18
+ staleSessions: number
19
+ connectorsInBackoff: number
20
+ connectorsExhausted: number
21
+ checkIntervalSec: number
22
+ connectorCheckIntervalSec: number
23
+ integrity: {
24
+ enabled: boolean
25
+ lastCheckedAt: number | null
26
+ lastDriftCount: number
27
+ }
28
+ }
29
+ webhookRetry: {
30
+ pendingRetries: number
31
+ deadLettered: number
32
+ }
33
+ guards: {
34
+ healthCheckRunning: boolean
35
+ connectorHealthCheckRunning: boolean
36
+ shuttingDown: boolean
37
+ providerCircuitBreakers: number
38
+ }
39
+ }
40
+
41
+ export interface DaemonHealthSummaryPayload {
42
+ ok: boolean
43
+ uptime: number
44
+ components: {
45
+ daemon: { status: 'healthy' | 'stopped' | 'degraded' }
46
+ connectors: { healthy: number; errored: number; total: number }
47
+ providers: { healthy: number; cooldown: number; total: number }
48
+ gateways: { healthy: number; degraded: number; total: number }
49
+ }
50
+ estop: boolean
51
+ nextScheduledTask: number | null
52
+ }
53
+
54
+ export interface PersistedDaemonStatusRecord {
55
+ pid: number | null
56
+ adminPort: number | null
57
+ desiredState: 'running' | 'stopped'
58
+ manualStopRequested: boolean
59
+ startedAt: number | null
60
+ stoppedAt: number | null
61
+ lastHeartbeatAt: number | null
62
+ updatedAt: number
63
+ lastLaunchSource: string | null
64
+ lastStopSource: string | null
65
+ lastError: string | null
66
+ lastStatus: DaemonStatusPayload | null
67
+ lastHealthSummary: DaemonHealthSummaryPayload | null
68
+ }
69
+
70
+ export interface DaemonAdminMetadata {
71
+ pid: number
72
+ port: number
73
+ token: string
74
+ launchedAt: number
75
+ source: string | null
76
+ }
77
+
78
+ export interface DaemonConnectorRuntimeState {
79
+ status: 'running' | 'stopped' | 'error'
80
+ authenticated?: boolean
81
+ hasCredentials?: boolean
82
+ qrDataUrl?: string | null
83
+ reconnectAttempts?: number
84
+ nextRetryAt?: number
85
+ reconnectError?: string | null
86
+ reconnectExhausted?: boolean
87
+ presence?: {
88
+ lastMessageAt: number | null
89
+ channelId: string | null
90
+ } | null
91
+ }
92
+
93
+ export interface DaemonRunningConnectorInfo {
94
+ id: string
95
+ name: string
96
+ platform: string
97
+ agentId: string | null
98
+ supportsSend: boolean
99
+ configuredTargets: string[]
100
+ recentChannelId: string | null
101
+ }
@@ -1,4 +1,5 @@
1
- import { loadSettings, loadCredentials, decryptKey } from './storage'
1
+ import { resolveCredentialSecret } from './credentials/credential-service'
2
+ import { loadSettings } from './settings/settings-repository'
2
3
  import { hmrSingleton } from '@/lib/shared-utils'
3
4
  import { log } from '@/lib/server/logger'
4
5
 
@@ -47,14 +48,7 @@ export async function getEmbedding(text: string): Promise<number[] | null> {
47
48
 
48
49
  const model = settings.embeddingModel || 'text-embedding-3-small'
49
50
 
50
- let apiKey: string | null = null
51
- if (settings.embeddingCredentialId) {
52
- const creds = loadCredentials()
53
- const cred = creds[settings.embeddingCredentialId]
54
- if (cred?.encryptedKey) {
55
- try { apiKey = decryptKey(cred.encryptedKey) } catch { /* ignore */ }
56
- }
57
- }
51
+ const apiKey = resolveCredentialSecret(settings.embeddingCredentialId)
58
52
 
59
53
  try {
60
54
  if (provider === 'local') {
@@ -7,7 +7,8 @@ import { genId } from '@/lib/id'
7
7
  import type { ApprovalRequest, MessageToolEvent, Session } from '@/types'
8
8
  import { dedup } from '@/lib/shared-utils'
9
9
  import { submitDecision } from '../approvals'
10
- import { executeSessionChatTurn, type ExecuteChatTurnResult } from '@/lib/server/chat-execution/chat-execution'
10
+ import type { ExecuteChatTurnResult } from '@/lib/server/chat-execution/chat-execution'
11
+ import { executeExecutionChatTurn } from '@/lib/server/execution-engine/chat-turn'
11
12
  import { WORKSPACE_DIR } from '../data-dir'
12
13
  import { getExtensionManager } from '../extensions'
13
14
  import { sendMailboxEnvelope, listMailbox } from '@/lib/server/chatrooms/session-mailbox'
@@ -1009,7 +1010,7 @@ async function runTurn(ctx: ScenarioContext, message: string): Promise<ExecuteCh
1009
1010
  let result: ExecuteChatTurnResult
1010
1011
  try {
1011
1012
  result = await Promise.race([
1012
- executeSessionChatTurn({
1013
+ executeExecutionChatTurn({
1013
1014
  sessionId: ctx.sessionId,
1014
1015
  message,
1015
1016
  internal: true,
@@ -6,7 +6,7 @@ import { getScenario, EVAL_SCENARIOS } from './scenarios'
6
6
  import { scoreCriteria } from './scorer'
7
7
  import { saveEvalRun } from './store'
8
8
  import { loadSessions, saveSessions, loadAgents, loadCredentials, decryptKey } from '../storage'
9
- import { executeSessionChatTurn } from '@/lib/server/chat-execution/chat-execution'
9
+ import { executeExecutionChatTurn } from '@/lib/server/execution-engine/chat-turn'
10
10
  import { WORKSPACE_DIR } from '../data-dir'
11
11
  import type { Session } from '@/types'
12
12
  import { errorMessage } from '@/lib/shared-utils'
@@ -64,7 +64,7 @@ export async function runEvalScenario(scenarioId: string, agentId: string): Prom
64
64
  saveSessions(sessions)
65
65
 
66
66
  try {
67
- const result = await executeSessionChatTurn({
67
+ const result = await executeExecutionChatTurn({
68
68
  sessionId,
69
69
  message: scenario.userMessage,
70
70
  internal: true,