@swarmclawai/swarmclaw 1.2.4 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) 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 +23 -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/providers/index.test.ts +108 -0
  109. package/src/lib/providers/index.ts +38 -15
  110. package/src/lib/query/client.ts +17 -0
  111. package/src/lib/server/agents/agent-runtime-config.ts +1 -1
  112. package/src/lib/server/agents/agent-service.ts +429 -0
  113. package/src/lib/server/agents/agent-thread-session.ts +6 -5
  114. package/src/lib/server/agents/autonomy-contract.ts +1 -4
  115. package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
  116. package/src/lib/server/agents/delegation-advisory.ts +251 -0
  117. package/src/lib/server/agents/main-agent-loop.ts +98 -40
  118. package/src/lib/server/agents/subagent-runtime.ts +12 -0
  119. package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
  120. package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
  121. package/src/lib/server/build-llm.ts +7 -15
  122. package/src/lib/server/capability-router.test.ts +70 -1
  123. package/src/lib/server/capability-router.ts +24 -99
  124. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
  125. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
  126. package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
  127. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
  128. package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
  129. package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
  130. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
  131. package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
  132. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
  133. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
  134. package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
  135. package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
  136. package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
  137. package/src/lib/server/chat-execution/message-classifier.ts +74 -32
  138. package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
  139. package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
  140. package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
  141. package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
  142. package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
  143. package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
  144. package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
  145. package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
  146. package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
  147. package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
  148. package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
  149. package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
  150. package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
  151. package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
  152. package/src/lib/server/chats/chat-session-service.ts +410 -0
  153. package/src/lib/server/connectors/access.ts +1 -1
  154. package/src/lib/server/connectors/commands.ts +7 -6
  155. package/src/lib/server/connectors/connector-inbound.ts +14 -7
  156. package/src/lib/server/connectors/connector-outbound.ts +16 -11
  157. package/src/lib/server/connectors/connector-service.ts +453 -0
  158. package/src/lib/server/connectors/delivery.ts +17 -12
  159. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
  160. package/src/lib/server/connectors/media.ts +1 -1
  161. package/src/lib/server/connectors/response-media.ts +1 -1
  162. package/src/lib/server/connectors/session-consolidation.ts +11 -7
  163. package/src/lib/server/connectors/session.ts +9 -7
  164. package/src/lib/server/connectors/voice-note.ts +2 -1
  165. package/src/lib/server/context-manager.ts +20 -1
  166. package/src/lib/server/cost.ts +2 -3
  167. package/src/lib/server/credentials/credential-repository.ts +43 -4
  168. package/src/lib/server/credentials/credential-service.ts +112 -0
  169. package/src/lib/server/daemon/admin-metadata.ts +64 -0
  170. package/src/lib/server/daemon/controller.ts +577 -0
  171. package/src/lib/server/daemon/daemon-runtime.ts +352 -0
  172. package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
  173. package/src/lib/server/daemon/types.ts +101 -0
  174. package/src/lib/server/embeddings.ts +3 -9
  175. package/src/lib/server/eval/agent-regression.ts +3 -2
  176. package/src/lib/server/eval/runner.ts +2 -2
  177. package/src/lib/server/execution-brief.test.ts +167 -0
  178. package/src/lib/server/execution-brief.ts +295 -0
  179. package/src/lib/server/execution-engine/chat-turn.ts +9 -0
  180. package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
  181. package/src/lib/server/execution-engine/index.ts +35 -0
  182. package/src/lib/server/execution-engine/task-attempt.ts +303 -0
  183. package/src/lib/server/execution-engine/types.ts +33 -0
  184. package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
  185. package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
  186. package/src/lib/server/memory/session-archive-memory.ts +12 -10
  187. package/src/lib/server/messages/message-repository.ts +330 -0
  188. package/src/lib/server/missions/mission-service/core.ts +8 -6
  189. package/src/lib/server/openclaw/agent-resolver.ts +2 -3
  190. package/src/lib/server/openclaw/doctor.ts +1 -1
  191. package/src/lib/server/openclaw/gateway.test.ts +10 -1
  192. package/src/lib/server/openclaw/gateway.ts +5 -14
  193. package/src/lib/server/openclaw/health.ts +3 -11
  194. package/src/lib/server/openclaw/sync.ts +8 -6
  195. package/src/lib/server/persistence/storage-context.ts +3 -0
  196. package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
  197. package/src/lib/server/protocols/protocol-normalization.ts +1 -1
  198. package/src/lib/server/protocols/protocol-queries.ts +13 -7
  199. package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
  200. package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
  201. package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
  202. package/src/lib/server/protocols/protocol-swarm.ts +8 -8
  203. package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
  204. package/src/lib/server/protocols/protocol-templates.ts +4 -2
  205. package/src/lib/server/protocols/protocol-types.ts +10 -7
  206. package/src/lib/server/provider-endpoint.ts +7 -12
  207. package/src/lib/server/provider-model-discovery.ts +2 -11
  208. package/src/lib/server/query-expansion.ts +5 -6
  209. package/src/lib/server/run-context.test.ts +365 -0
  210. package/src/lib/server/run-context.ts +367 -0
  211. package/src/lib/server/runtime/heartbeat-service.ts +7 -5
  212. package/src/lib/server/runtime/queue/core.ts +61 -190
  213. package/src/lib/server/runtime/run-ledger.ts +8 -0
  214. package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
  215. package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
  216. package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
  217. package/src/lib/server/schedules/schedule-route-service.ts +230 -0
  218. package/src/lib/server/service-result.ts +16 -0
  219. package/src/lib/server/session-note.ts +2 -3
  220. package/src/lib/server/session-reset-policy.ts +4 -3
  221. package/src/lib/server/session-tools/connector.ts +9 -6
  222. package/src/lib/server/session-tools/context-mgmt.ts +58 -9
  223. package/src/lib/server/session-tools/crud.ts +162 -10
  224. package/src/lib/server/session-tools/delegate.ts +1 -1
  225. package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
  226. package/src/lib/server/session-tools/memory.ts +6 -4
  227. package/src/lib/server/session-tools/session-info.test.ts +56 -0
  228. package/src/lib/server/session-tools/session-info.ts +119 -12
  229. package/src/lib/server/session-tools/skill-runtime.ts +3 -1
  230. package/src/lib/server/session-tools/skills.ts +15 -15
  231. package/src/lib/server/session-tools/subagent.test.ts +115 -1
  232. package/src/lib/server/session-tools/subagent.ts +125 -7
  233. package/src/lib/server/session-tools/team-context.ts +4 -3
  234. package/src/lib/server/session-tools/wallet.ts +0 -58
  235. package/src/lib/server/sessions/session-lineage.ts +55 -0
  236. package/src/lib/server/sessions/session-repository.ts +2 -2
  237. package/src/lib/server/skills/learned-skills.ts +24 -23
  238. package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
  239. package/src/lib/server/skills/skill-repository.ts +136 -13
  240. package/src/lib/server/skills/skill-suggestions.ts +25 -28
  241. package/src/lib/server/storage-normalization.test.ts +44 -267
  242. package/src/lib/server/storage-normalization.ts +75 -0
  243. package/src/lib/server/storage.ts +19 -0
  244. package/src/lib/server/structured-extract.ts +3 -14
  245. package/src/lib/server/tasks/task-followups.ts +16 -11
  246. package/src/lib/server/tasks/task-result.test.ts +25 -29
  247. package/src/lib/server/tasks/task-result.ts +5 -9
  248. package/src/lib/server/tasks/task-route-service.ts +449 -0
  249. package/src/lib/server/text-normalization.ts +41 -0
  250. package/src/lib/server/tool-planning.ts +6 -42
  251. package/src/lib/server/upload-path.ts +5 -0
  252. package/src/lib/server/working-state/extraction.ts +614 -0
  253. package/src/lib/server/working-state/normalization.ts +866 -0
  254. package/src/lib/server/working-state/prompt.ts +60 -0
  255. package/src/lib/server/working-state/repository.ts +38 -0
  256. package/src/lib/server/working-state/service.test.ts +253 -0
  257. package/src/lib/server/working-state/service.ts +293 -0
  258. package/src/lib/validation/schemas.ts +1 -0
  259. package/src/lib/ws-client.ts +3 -3
  260. package/src/stores/slices/task-slice.ts +1 -4
  261. package/src/stores/use-chatroom-store.ts +2 -2
  262. package/src/types/index.ts +277 -12
@@ -0,0 +1,330 @@
1
+ import type { Message, Session } from '@/types'
2
+ import { perf } from '@/lib/server/runtime/perf'
3
+ import { log } from '@/lib/server/logger'
4
+ import { getDb, withTransaction, loadSession, patchSession } from '@/lib/server/storage'
5
+ import { notify } from '@/lib/server/ws-hub'
6
+
7
+ const TAG = 'message-repo'
8
+ const MAX_SUMMARY_TEXT = 280
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Prepared statements — lazily created so the db is fully initialised first
12
+ // ---------------------------------------------------------------------------
13
+
14
+ type Stmts = ReturnType<typeof buildStatements>
15
+ let _stmts: Stmts | null = null
16
+
17
+ function stmts(): Stmts {
18
+ if (!_stmts) _stmts = buildStatements()
19
+ return _stmts
20
+ }
21
+
22
+ function buildStatements() {
23
+ const db = getDb()
24
+ return {
25
+ selectAll: db.prepare(
26
+ 'SELECT data FROM session_messages WHERE session_id = ? ORDER BY seq ASC',
27
+ ),
28
+ selectCount: db.prepare(
29
+ 'SELECT COUNT(*) as count FROM session_messages WHERE session_id = ?',
30
+ ),
31
+ selectLast: db.prepare(
32
+ 'SELECT data FROM session_messages WHERE session_id = ? ORDER BY seq DESC LIMIT 1',
33
+ ),
34
+ selectRecent: db.prepare(
35
+ 'SELECT data FROM session_messages WHERE session_id = ? ORDER BY seq DESC LIMIT ?',
36
+ ),
37
+ selectMaxSeq: db.prepare(
38
+ 'SELECT MAX(seq) as maxSeq FROM session_messages WHERE session_id = ?',
39
+ ),
40
+ insert: db.prepare(
41
+ 'INSERT INTO session_messages (session_id, seq, data) VALUES (?, ?, ?)',
42
+ ),
43
+ update: db.prepare(
44
+ 'UPDATE session_messages SET data = ? WHERE session_id = ? AND seq = ?',
45
+ ),
46
+ deleteAfter: db.prepare(
47
+ 'DELETE FROM session_messages WHERE session_id = ? AND seq > ?',
48
+ ),
49
+ deleteAll: db.prepare(
50
+ 'DELETE FROM session_messages WHERE session_id = ?',
51
+ ),
52
+ }
53
+ }
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Internal helpers
57
+ // ---------------------------------------------------------------------------
58
+
59
+ function nextSeq(sessionId: string): number {
60
+ const row = stmts().selectMaxSeq.get(sessionId) as { maxSeq: number | null } | undefined
61
+ return (row?.maxSeq ?? -1) + 1
62
+ }
63
+
64
+ function rowCount(sessionId: string): number {
65
+ return (stmts().selectCount.get(sessionId) as { count: number }).count
66
+ }
67
+
68
+ function parseMsg(raw: string): Message | null {
69
+ try {
70
+ return JSON.parse(raw) as Message
71
+ } catch {
72
+ return null
73
+ }
74
+ }
75
+
76
+ function summarizeForMeta(message: Message): Message {
77
+ return {
78
+ role: message.role,
79
+ text: typeof message.text === 'string' ? message.text.slice(0, MAX_SUMMARY_TEXT) : '',
80
+ time: message.time,
81
+ kind: message.kind,
82
+ source: message.source,
83
+ suppressed: message.suppressed,
84
+ streaming: message.streaming,
85
+ bookmarked: message.bookmarked,
86
+ }
87
+ }
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // Session metadata sync — keeps messageCount / lastMessageSummary on the blob
91
+ // ---------------------------------------------------------------------------
92
+
93
+ function syncSessionMeta(sessionId: string): void {
94
+ const count = rowCount(sessionId)
95
+ const lastRow = stmts().selectLast.get(sessionId) as { data: string } | undefined
96
+ const lastMsg = lastRow ? parseMsg(lastRow.data) : null
97
+
98
+ patchSession(sessionId, (current) => {
99
+ if (!current) return null
100
+ current.messageCount = count
101
+ current.lastMessageSummary = lastMsg ? summarizeForMeta(lastMsg) : null
102
+ if (lastMsg?.role === 'assistant' && typeof lastMsg.time === 'number') {
103
+ current.lastAssistantAt = lastMsg.time
104
+ }
105
+ current.lastActiveAt = Date.now()
106
+ return current
107
+ })
108
+ }
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // Lazy migration — copies blob messages → table on first access
112
+ // ---------------------------------------------------------------------------
113
+
114
+ function ensureMigrated(sessionId: string): void {
115
+ if (rowCount(sessionId) > 0) return
116
+ lazyMigrateSession(sessionId)
117
+ }
118
+
119
+ function lazyMigrateSession(sessionId: string): Message[] | null {
120
+ const session = loadSession(sessionId)
121
+ if (!session || !Array.isArray(session.messages) || session.messages.length === 0) {
122
+ return null
123
+ }
124
+
125
+ const messages = session.messages
126
+ log.info(TAG, `Lazy-migrating ${messages.length} messages for session ${sessionId}`)
127
+
128
+ withTransaction(() => {
129
+ // Double-check inside transaction to prevent duplicate migration
130
+ if (rowCount(sessionId) > 0) return
131
+
132
+ const ins = stmts().insert
133
+ for (let i = 0; i < messages.length; i++) {
134
+ ins.run(sessionId, i, JSON.stringify(messages[i]))
135
+ }
136
+
137
+ // Compute metadata on the blob (keep messages intact for backward compat)
138
+ const lastMsg = messages[messages.length - 1]
139
+ let lastAssistantAt: number | null = null
140
+ for (let i = messages.length - 1; i >= 0; i--) {
141
+ if (messages[i].role === 'assistant' && typeof messages[i].time === 'number') {
142
+ lastAssistantAt = messages[i].time
143
+ break
144
+ }
145
+ }
146
+
147
+ patchSession(sessionId, (current) => {
148
+ if (!current) return null
149
+ current.messageCount = messages.length
150
+ current.lastMessageSummary = lastMsg ? summarizeForMeta(lastMsg) : null
151
+ if (lastAssistantAt !== null && typeof current.lastAssistantAt !== 'number') {
152
+ current.lastAssistantAt = lastAssistantAt
153
+ }
154
+ return current
155
+ })
156
+ })
157
+
158
+ return messages
159
+ }
160
+
161
+ // ---------------------------------------------------------------------------
162
+ // Public API
163
+ // ---------------------------------------------------------------------------
164
+
165
+ /** Return all messages for a session, ordered by sequence. */
166
+ export function getMessages(sessionId: string): Message[] {
167
+ return perf.measureSync('message-repo', 'getMessages', () => {
168
+ const rows = stmts().selectAll.all(sessionId) as Array<{ data: string }>
169
+
170
+ // Lazy migration: populate table from session blob on first read
171
+ if (rows.length === 0) {
172
+ const migrated = lazyMigrateSession(sessionId)
173
+ if (migrated) return migrated
174
+ }
175
+
176
+ const out: Message[] = []
177
+ for (const row of rows) {
178
+ const m = parseMsg(row.data)
179
+ if (m) out.push(m)
180
+ }
181
+ return out
182
+ }, { sessionId })
183
+ }
184
+
185
+ /** Return message count (from table, with blob fallback pre-migration). */
186
+ export function getMessageCount(sessionId: string): number {
187
+ return perf.measureSync('message-repo', 'getMessageCount', () => {
188
+ const count = rowCount(sessionId)
189
+ if (count > 0) return count
190
+ // Pre-migration fallback
191
+ const session = loadSession(sessionId)
192
+ return Array.isArray(session?.messages) ? session.messages.length : 0
193
+ }, { sessionId })
194
+ }
195
+
196
+ /** Return the last message (from table, with blob fallback). */
197
+ export function getLastMessage(sessionId: string): Message | null {
198
+ return perf.measureSync('message-repo', 'getLastMessage', () => {
199
+ const row = stmts().selectLast.get(sessionId) as { data: string } | undefined
200
+ if (row) return parseMsg(row.data)
201
+ const session = loadSession(sessionId)
202
+ const msgs = session?.messages
203
+ return Array.isArray(msgs) && msgs.length > 0 ? msgs[msgs.length - 1] : null
204
+ }, { sessionId })
205
+ }
206
+
207
+ /** Return the last N messages in chronological order. */
208
+ export function getRecentMessages(sessionId: string, n: number): Message[] {
209
+ return perf.measureSync('message-repo', 'getRecentMessages', () => {
210
+ const rows = stmts().selectRecent.all(sessionId, n) as Array<{ data: string }>
211
+ if (rows.length > 0) {
212
+ const out: Message[] = []
213
+ for (const row of rows) {
214
+ const m = parseMsg(row.data)
215
+ if (m) out.push(m)
216
+ }
217
+ return out.reverse() // DESC → ASC
218
+ }
219
+ // Pre-migration fallback
220
+ const session = loadSession(sessionId)
221
+ return Array.isArray(session?.messages) ? session.messages.slice(-n) : []
222
+ }, { sessionId, n })
223
+ }
224
+
225
+ /** Append a single message. Returns the assigned sequence number. */
226
+ export function appendMessage(sessionId: string, message: Message): number {
227
+ return perf.measureSync('message-repo', 'appendMessage', () => {
228
+ ensureMigrated(sessionId)
229
+ const seq = nextSeq(sessionId)
230
+ stmts().insert.run(sessionId, seq, JSON.stringify(message))
231
+ syncSessionMeta(sessionId)
232
+ notify('messages', 'append', sessionId)
233
+ return seq
234
+ }, { sessionId })
235
+ }
236
+
237
+ /** Append multiple messages in a single transaction. */
238
+ export function appendMessages(sessionId: string, messages: Message[]): void {
239
+ if (!messages.length) return
240
+ perf.measureSync('message-repo', 'appendMessages', () => {
241
+ ensureMigrated(sessionId)
242
+ withTransaction(() => {
243
+ let seq = nextSeq(sessionId)
244
+ const ins = stmts().insert
245
+ for (const msg of messages) {
246
+ ins.run(sessionId, seq++, JSON.stringify(msg))
247
+ }
248
+ })
249
+ syncSessionMeta(sessionId)
250
+ notify('messages', 'append', sessionId)
251
+ }, { sessionId, count: messages.length })
252
+ }
253
+
254
+ /** Replace the message at a given sequence number (e.g. replace-last-assistant). */
255
+ export function replaceMessageAt(sessionId: string, seq: number, message: Message): void {
256
+ perf.measureSync('message-repo', 'replaceMessageAt', () => {
257
+ stmts().update.run(JSON.stringify(message), sessionId, seq)
258
+ syncSessionMeta(sessionId)
259
+ notify('messages', 'update', sessionId)
260
+ }, { sessionId, seq })
261
+ }
262
+
263
+ /** Delete all messages with seq > the given value (edit-and-resend). */
264
+ export function truncateAfter(sessionId: string, seq: number): void {
265
+ perf.measureSync('message-repo', 'truncateAfter', () => {
266
+ stmts().deleteAfter.run(sessionId, seq)
267
+ syncSessionMeta(sessionId)
268
+ notify('messages', 'truncate', sessionId)
269
+ }, { sessionId, seq })
270
+ }
271
+
272
+ /** Remove all messages for a session. */
273
+ export function clearMessages(sessionId: string): void {
274
+ perf.measureSync('message-repo', 'clearMessages', () => {
275
+ stmts().deleteAll.run(sessionId)
276
+ syncSessionMeta(sessionId)
277
+ notify('messages', 'clear', sessionId)
278
+ }, { sessionId })
279
+ }
280
+
281
+ /** Replace the entire message list (used after in-memory prune operations). */
282
+ export function replaceAllMessages(sessionId: string, messages: Message[]): void {
283
+ perf.measureSync('message-repo', 'replaceAllMessages', () => {
284
+ withTransaction(() => {
285
+ stmts().deleteAll.run(sessionId)
286
+ const ins = stmts().insert
287
+ for (let i = 0; i < messages.length; i++) {
288
+ ins.run(sessionId, i, JSON.stringify(messages[i]))
289
+ }
290
+ })
291
+ syncSessionMeta(sessionId)
292
+ notify('messages', 'replace', sessionId)
293
+ }, { sessionId, count: messages.length })
294
+ }
295
+
296
+ /** Cleanup: delete all rows for a session (called on session delete). */
297
+ export function deleteSessionMessages(sessionId: string): void {
298
+ stmts().deleteAll.run(sessionId)
299
+ }
300
+
301
+ // ---------------------------------------------------------------------------
302
+ // Bulk migration (for CLI / admin endpoint)
303
+ // ---------------------------------------------------------------------------
304
+
305
+ export function migrateAllSessions(): { migrated: number; skipped: number; total: number } {
306
+ const db = getDb()
307
+ const rows = db.prepare('SELECT id, data FROM sessions').all() as Array<{ id: string; data: string }>
308
+ let migrated = 0
309
+ let skipped = 0
310
+
311
+ for (const row of rows) {
312
+ try {
313
+ const session = JSON.parse(row.data) as Session
314
+ if (!Array.isArray(session.messages) || session.messages.length === 0) {
315
+ skipped++
316
+ continue
317
+ }
318
+ if (rowCount(row.id) > 0) {
319
+ skipped++
320
+ continue
321
+ }
322
+ lazyMigrateSession(row.id)
323
+ migrated++
324
+ } catch {
325
+ skipped++
326
+ }
327
+ }
328
+
329
+ return { migrated, skipped, total: rows.length }
330
+ }
@@ -18,6 +18,7 @@ import type {
18
18
  SessionQueuedTurn,
19
19
  SessionRunRecord,
20
20
  } from '@/types'
21
+ import { getMessages } from '@/lib/server/messages/message-repository'
21
22
  import { loadApprovals } from '@/lib/server/approvals/approval-repository'
22
23
  import { loadDelegationJob } from '@/lib/server/agents/delegation-job-repository'
23
24
  import { logActivity } from '@/lib/server/activity/activity-log'
@@ -49,6 +50,8 @@ import { errorMessage, hmrSingleton } from '@/lib/shared-utils'
49
50
  import { getSessionQueueSnapshot, listRuns } from '@/lib/server/runtime/session-run-manager'
50
51
  import { loadTask, loadTasks, patchTask } from '@/lib/server/tasks/task-repository'
51
52
  import { notify } from '@/lib/server/ws-hub'
53
+ import { buildExecutionBrief, buildExecutionBriefContextBlock } from '@/lib/server/execution-brief'
54
+ import { cleanText } from '@/lib/server/text-normalization'
52
55
 
53
56
  const TAG = 'mission-service'
54
57
 
@@ -56,10 +59,6 @@ function now(): number {
56
59
  return Date.now()
57
60
  }
58
61
 
59
- function cleanText(value: unknown, max = 320): string {
60
- return typeof value === 'string' ? value.replace(/\s+/g, ' ').trim().slice(0, max) : ''
61
- }
62
-
63
62
  function uniqueStrings(values: unknown, maxItems: number, maxChars = 180): string[] {
64
63
  const source = Array.isArray(values) ? values : []
65
64
  const out: string[] = []
@@ -1571,7 +1570,7 @@ export async function resolveMissionForTurn(params: {
1571
1570
  sessionId: params.session.id,
1572
1571
  agentId: params.session.agentId || null,
1573
1572
  message: params.message,
1574
- recentMessages: Array.isArray(params.session.messages) ? params.session.messages : [],
1573
+ recentMessages: getMessages(params.session.id),
1575
1574
  currentMission: currentMission ? buildMissionSummary(currentMission) : null,
1576
1575
  session: params.session,
1577
1576
  }, params.generateText ? { generateText: params.generateText } : undefined)
@@ -2254,7 +2253,10 @@ export function buildMissionContextBlock(mission: Mission | null | undefined): s
2254
2253
  export function buildMissionHeartbeatPrompt(session: Session, fallbackPrompt: string): string | null {
2255
2254
  const mission = getMissionForSession(session)
2256
2255
  if (!mission || isMissionTerminal(mission.status)) return null
2257
- const contextBlock = buildMissionContextBlock(mission)
2256
+ const contextBlock = buildExecutionBriefContextBlock(buildExecutionBrief({
2257
+ session,
2258
+ mission,
2259
+ }))
2258
2260
  return [
2259
2261
  'MAIN_AGENT_HEARTBEAT_TICK',
2260
2262
  `Time: ${new Date().toISOString()}`,
@@ -1,7 +1,7 @@
1
1
  import type { Agent } from '@/types'
2
2
  import { normalizeOpenClawAgentId } from '@/lib/openclaw/openclaw-agent-id'
3
+ import { loadAgent } from '@/lib/server/agents/agent-repository'
3
4
  import { ensureGatewayConnected, type OpenClawGateway } from './gateway'
4
- import { loadAgents } from '../storage'
5
5
 
6
6
  export interface OpenClawGatewayAgentSummary {
7
7
  id: string
@@ -101,8 +101,7 @@ export async function resolveOpenClawGatewayAgentId(
101
101
  throw new Error('Missing agentId')
102
102
  }
103
103
 
104
- const localAgents = loadAgents({ includeTrashed: true }) as Record<string, Agent>
105
- const localAgent = localAgents[trimmedRef] || null
104
+ const localAgent = loadAgent(trimmedRef, { includeTrashed: true }) as Agent | null
106
105
  if (localAgent && localAgent.provider !== 'openclaw') {
107
106
  throw new Error(`Agent "${localAgent.name}" is not an OpenClaw agent`)
108
107
  }
@@ -2,7 +2,7 @@ import { execFile } from 'child_process'
2
2
  import { promisify } from 'util'
3
3
  import * as path from 'path'
4
4
  import * as os from 'os'
5
- import { loadSettings } from '../storage'
5
+ import { loadSettings } from '../settings/settings-repository'
6
6
 
7
7
  const execFileAsync = promisify(execFile)
8
8
 
@@ -1,8 +1,9 @@
1
1
  import assert from 'node:assert/strict'
2
- import { afterEach, test } from 'node:test'
2
+ import { afterEach, beforeEach, test } from 'node:test'
3
3
 
4
4
  import type { Agent, GatewayProfile } from '@/types'
5
5
  import {
6
+ deleteAgent,
6
7
  encryptKey,
7
8
  loadAgents,
8
9
  loadCredentials,
@@ -24,6 +25,14 @@ const originalCredentials = loadCredentials()
24
25
  const originalGateways = loadGatewayProfiles()
25
26
  const originalAgents = loadAgents({ includeTrashed: true })
26
27
 
28
+ beforeEach(() => {
29
+ saveCredentials({})
30
+ saveGatewayProfiles({})
31
+ for (const agentId of Object.keys(loadAgents({ includeTrashed: true }))) {
32
+ deleteAgent(agentId)
33
+ }
34
+ })
35
+
27
36
  afterEach(() => {
28
37
  disconnectGateway()
29
38
  saveCredentials(originalCredentials)
@@ -2,7 +2,8 @@ import { WebSocket } from 'ws'
2
2
  import { randomUUID } from 'crypto'
3
3
  import { wsConnect } from '@/lib/providers/openclaw'
4
4
  import { deriveOpenClawWsUrl } from '@/lib/openclaw/openclaw-endpoint'
5
- import { loadAgents, loadCredentials, decryptKey } from '../storage'
5
+ import { loadAgent, loadAgents } from '@/lib/server/agents/agent-repository'
6
+ import { resolveCredentialSecret } from '@/lib/server/credentials/credential-service'
6
7
  import { notify, notifyWithPayload } from '../ws-hub'
7
8
  import { getGatewayProfile, getGatewayProfiles, resolvePrimaryAgentRoute } from '@/lib/server/agents/agent-runtime-config'
8
9
  import { isAgentDisabled } from '@/lib/server/agents/agent-availability'
@@ -80,16 +81,7 @@ function normalizeWsUrl(raw: string): string {
80
81
  }
81
82
 
82
83
  function resolveTokenForCredential(credentialId?: string | null): string | undefined {
83
- const id = typeof credentialId === 'string' && credentialId.trim() ? credentialId.trim() : ''
84
- if (!id) return undefined
85
- const creds = loadCredentials()
86
- const cred = creds[id]
87
- if (!cred?.encryptedKey) return undefined
88
- try {
89
- return decryptKey(cred.encryptedKey)
90
- } catch {
91
- return undefined
92
- }
84
+ return resolveCredentialSecret(credentialId) || undefined
93
85
  }
94
86
 
95
87
  export function resolveGatewayConfig(target?: {
@@ -110,8 +102,7 @@ export function resolveGatewayConfig(target?: {
110
102
 
111
103
  const agentId = typeof target?.agentId === 'string' ? target.agentId.trim() : ''
112
104
  if (agentId) {
113
- const agents = loadAgents({ includeTrashed: true })
114
- const agent = agents[agentId] as Agent | undefined
105
+ const agent = loadAgent(agentId, { includeTrashed: true }) as Agent | null
115
106
  const config = agent ? buildGatewayConfigFromAgent(agent, { allowDisabled: true, allowTrashed: true }) : null
116
107
  if (config) return config
117
108
  }
@@ -236,7 +227,7 @@ export class OpenClawGateway {
236
227
  }
237
228
 
238
229
  private rejectAllPending(reason: string) {
239
- for (const [id, p] of this.pending) {
230
+ for (const p of this.pending.values()) {
240
231
  clearTimeout(p.timer)
241
232
  p.reject(new Error(reason))
242
233
  }
@@ -1,6 +1,7 @@
1
1
  import { deriveOpenClawWsUrl, normalizeOpenClawEndpoint } from '@/lib/openclaw/openclaw-endpoint'
2
2
  import { wsConnect } from '@/lib/providers/openclaw'
3
- import { decryptKey, loadCredentials, loadGatewayProfiles, saveGatewayProfiles } from '../storage'
3
+ import { resolveCredentialSecret } from '@/lib/server/credentials/credential-service'
4
+ import { loadGatewayProfiles, saveGatewayProfiles } from '@/lib/server/gateways/gateway-profile-repository'
4
5
  import { notify } from '../ws-hub'
5
6
  import type { GatewayProfile } from '@/types'
6
7
 
@@ -60,16 +61,7 @@ function getErrorMessage(err: unknown): string | undefined {
60
61
  }
61
62
 
62
63
  function resolveCredentialToken(credentialId?: string | null): string | null {
63
- const id = normalizeToken(credentialId)
64
- if (!id) return null
65
- const credentials = loadCredentials()
66
- const credential = credentials[id]
67
- if (!credential?.encryptedKey) return null
68
- try {
69
- return decryptKey(credential.encryptedKey)
70
- } catch {
71
- return null
72
- }
64
+ return resolveCredentialSecret(normalizeToken(credentialId))
73
65
  }
74
66
 
75
67
  function extractModels(payload: unknown): string[] {
@@ -4,8 +4,11 @@ import path from 'node:path'
4
4
  import crypto from 'node:crypto'
5
5
  import { DATA_DIR } from '../data-dir'
6
6
  import { normalizeOpenClawAgentId } from '@/lib/openclaw/openclaw-agent-id'
7
- import { loadSettings, loadAgents, upsertAgent, loadSchedules, upsertSchedules, loadCredentials, decryptKey, encryptKey } from '../storage'
7
+ import { loadAgents, upsertAgent } from '@/lib/server/agents/agent-repository'
8
+ import { decryptKey, encryptKey, loadCredentials, saveCredentials } from '@/lib/server/credentials/credential-repository'
8
9
  import { getMemoryDb } from '@/lib/server/memory/memory-db'
10
+ import { loadSchedules, upsertSchedules } from '@/lib/server/schedules/schedule-repository'
11
+ import { loadSettings } from '@/lib/server/settings/settings-repository'
9
12
  import type { AppSettings, MemoryEntry, Schedule } from '@/types'
10
13
 
11
14
  export interface OpenClawSyncConfig {
@@ -289,7 +292,7 @@ export function pullSchedulesFromOpenClaw(): { imported: number } {
289
292
  if (existingNames.has(key)) continue
290
293
 
291
294
  const id = crypto.randomUUID()
292
- const schedule = {
295
+ const schedule: Schedule = {
293
296
  id,
294
297
  name: job.name,
295
298
  agentId: job.agentId || '',
@@ -299,8 +302,8 @@ export function pullSchedulesFromOpenClaw(): { imported: number } {
299
302
  status: 'active',
300
303
  createdAt: Date.now(),
301
304
  }
302
- schedules[id] = schedule as any
303
- importedEntries.push([id, schedule as any])
305
+ schedules[id] = schedule
306
+ importedEntries.push([id, schedule])
304
307
  existingNames.add(key)
305
308
  imported++
306
309
  }
@@ -324,8 +327,7 @@ export async function pullCredentialsFromOpenClaw(): Promise<{ imported: number
324
327
  return { imported: 0 }
325
328
  }
326
329
 
327
- const { loadCredentials: loadCreds, saveCredentials } = await import('../storage')
328
- const creds = loadCreds()
330
+ const creds = loadCredentials()
329
331
  const existingProviders = new Set(Object.values(creds).map((c: Record<string, unknown>) => c.provider))
330
332
  let imported = 0
331
333
 
@@ -4,6 +4,7 @@ import { agentRepository } from '@/lib/server/agents/agent-repository'
4
4
  import { approvalRepository } from '@/lib/server/approvals/approval-repository'
5
5
  import { chatroomRepository } from '@/lib/server/chatrooms/chatroom-repository'
6
6
  import { connectorRepository } from '@/lib/server/connectors/connector-repository'
7
+ import * as messageRepository from '@/lib/server/messages/message-repository'
7
8
  import { missionEventRepository, missionRepository } from '@/lib/server/missions/mission-repository'
8
9
  import { projectRepository } from '@/lib/server/projects/project-repository'
9
10
  import { scheduleRepository } from '@/lib/server/schedules/schedule-repository'
@@ -17,6 +18,7 @@ export interface StorageTxContext {
17
18
  approvals: typeof approvalRepository
18
19
  chatrooms: typeof chatroomRepository
19
20
  connectors: typeof connectorRepository
21
+ messages: typeof messageRepository
20
22
  missions: typeof missionRepository
21
23
  missionEvents: typeof missionEventRepository
22
24
  projects: typeof projectRepository
@@ -34,6 +36,7 @@ export function createStorageTxContext(): StorageTxContext {
34
36
  approvals: approvalRepository,
35
37
  chatrooms: chatroomRepository,
36
38
  connectors: connectorRepository,
39
+ messages: messageRepository,
37
40
  missions: missionRepository,
38
41
  missionEvents: missionEventRepository,
39
42
  projects: projectRepository,
@@ -29,14 +29,19 @@ import {
29
29
  import { streamAgentChat } from '@/lib/server/chat-execution/stream-agent-chat'
30
30
  import { shouldSuppressHiddenControlText, stripHiddenControlTokens } from '@/lib/server/agents/assistant-control'
31
31
  import { resolvePrimaryAgentRoute } from '@/lib/server/agents/agent-runtime-config'
32
+ import { getAgent, getAgents } from '@/lib/server/agents/agent-repository'
32
33
  import { buildLLM } from '@/lib/server/build-llm'
33
34
  import {
34
- loadAgents,
35
- loadChatrooms,
36
- patchProtocolRun,
35
+ loadChatroom,
36
+ patchChatroom,
37
37
  upsertChatroom,
38
+ } from '@/lib/server/chatrooms/chatroom-repository'
39
+ import {
40
+ loadProtocolRunEventsByRunId,
41
+ patchProtocolRun,
42
+ upsertProtocolRun,
38
43
  upsertProtocolRunEvent,
39
- } from '@/lib/server/storage'
44
+ } from '@/lib/server/protocols/protocol-run-repository'
40
45
  import { notify } from '@/lib/server/ws-hub'
41
46
  import { errorMessage } from '@/lib/shared-utils'
42
47
  import { AGENT_TURN_TIMEOUT_MS, cleanText, now } from '@/lib/server/protocols/protocol-types'
@@ -76,22 +81,24 @@ export function appendProtocolEvent(runId: string, event: Omit<ProtocolRunEvent,
76
81
  }
77
82
 
78
83
  export function listEvents(runId: string): ProtocolRunEvent[] {
79
- const { loadProtocolRunEventsByRunId } = require('@/lib/server/storage') as typeof import('@/lib/server/storage')
80
84
  return loadProtocolRunEventsByRunId(runId)
81
85
  }
82
86
 
83
87
  export function appendTranscriptMessage(chatroomId: string, message: Omit<ChatroomMessage, 'id' | 'time'>, deps?: ProtocolRunDeps): ChatroomMessage | null {
84
- const chatrooms = loadChatrooms()
85
- const chatroom = chatrooms[chatroomId]
86
- if (!chatroom) return null
87
88
  const nextMessage: ChatroomMessage = {
88
89
  ...message,
89
90
  id: genId(),
90
91
  time: now(deps),
91
92
  }
92
- chatroom.messages = Array.isArray(chatroom.messages) ? [...chatroom.messages, nextMessage] : [nextMessage]
93
- chatroom.updatedAt = nextMessage.time
94
- upsertChatroom(chatroomId, chatroom)
93
+ const updated = patchChatroom(chatroomId, (current) => {
94
+ if (!current) return null
95
+ return {
96
+ ...current,
97
+ messages: Array.isArray(current.messages) ? [...current.messages, nextMessage] : [nextMessage],
98
+ updatedAt: nextMessage.time,
99
+ }
100
+ })
101
+ if (!updated) return null
95
102
  notify(`chatroom:${chatroomId}`)
96
103
  return nextMessage
97
104
  }
@@ -184,8 +191,7 @@ export async function defaultExecuteAgentTurn(params: {
184
191
  agentId: string
185
192
  prompt: string
186
193
  }): Promise<ProtocolAgentTurnResult> {
187
- const agents = loadAgents() as Record<string, Agent>
188
- const agent = agents[params.agentId]
194
+ const agent = getAgent(params.agentId) as Agent | null
189
195
  if (!agent) throw new Error(`Agent not found: ${params.agentId}`)
190
196
  let run = params.run
191
197
  if (!run.transcriptChatroomId) {
@@ -201,8 +207,12 @@ export async function defaultExecuteAgentTurn(params: {
201
207
  updatedAt: Date.now(),
202
208
  })
203
209
  }
204
- const chatroom = loadChatrooms()[run.transcriptChatroomId!]
210
+ const chatroom = loadChatroom(run.transcriptChatroomId!)
205
211
  if (!chatroom) throw new Error(`Structured session transcript room not found: ${run.transcriptChatroomId}`)
212
+ const agents = {
213
+ ...getAgents(chatroom.agentIds),
214
+ [agent.id]: agent,
215
+ } as Record<string, Agent>
206
216
 
207
217
  const route = resolvePrimaryAgentRoute(agent)
208
218
  const apiKey = resolveApiKey(route?.credentialId || agent.credentialId)
@@ -369,8 +379,7 @@ export function createTranscriptRoom(input: {
369
379
  participantAgentIds: string[]
370
380
  parentChatroomId?: string | null
371
381
  }, deps?: ProtocolRunDeps): Chatroom {
372
- const chatrooms = loadChatrooms()
373
- const parentChatroom = input.parentChatroomId ? chatrooms[input.parentChatroomId] || null : null
382
+ const parentChatroom = input.parentChatroomId ? loadChatroom(input.parentChatroomId) : null
374
383
  const room: Chatroom = {
375
384
  id: genId(),
376
385
  name: transcriptRoomName(input.title, parentChatroom),
@@ -404,7 +413,6 @@ export function createArtifact(run: ProtocolRun, phase: ProtocolPhaseDefinition,
404
413
 
405
414
  export function persistRun(run: ProtocolRun): ProtocolRun {
406
415
  const normalized = normalizeProtocolRun(run)
407
- const { upsertProtocolRun } = require('@/lib/server/storage') as typeof import('@/lib/server/storage')
408
416
  upsertProtocolRun(normalized.id, normalized)
409
417
  notify('protocol_runs')
410
418
  notify(`protocol_run:${normalized.id}`)