@swarmclawai/swarmclaw 1.2.0 → 1.2.2

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 (241) hide show
  1. package/README.md +19 -0
  2. package/package.json +5 -2
  3. package/skills/coding-agent/SKILL.md +111 -0
  4. package/skills/github/SKILL.md +140 -0
  5. package/skills/nano-banana-pro/SKILL.md +62 -0
  6. package/skills/nano-banana-pro/scripts/generate_image.py +235 -0
  7. package/skills/nano-pdf/SKILL.md +53 -0
  8. package/skills/openai-image-gen/SKILL.md +78 -0
  9. package/skills/openai-image-gen/scripts/gen.py +328 -0
  10. package/skills/resourceful-problem-solving/SKILL.md +49 -0
  11. package/skills/skill-creator/SKILL.md +147 -0
  12. package/skills/skill-creator/scripts/init_skill.py +378 -0
  13. package/skills/skill-creator/scripts/quick_validate.py +159 -0
  14. package/skills/summarize/SKILL.md +77 -0
  15. package/src/app/api/auth/route.ts +20 -5
  16. package/src/app/api/chats/[id]/deploy/route.ts +11 -6
  17. package/src/app/api/chats/[id]/devserver/route.ts +17 -20
  18. package/src/app/api/chats/[id]/messages/route.ts +15 -11
  19. package/src/app/api/chats/[id]/route.ts +9 -10
  20. package/src/app/api/chats/[id]/stop/route.ts +5 -7
  21. package/src/app/api/chats/messages-route.test.ts +8 -6
  22. package/src/app/api/chats/route.ts +9 -10
  23. package/src/app/api/credentials/[id]/route.ts +4 -1
  24. package/src/app/api/extensions/marketplace/route.ts +5 -2
  25. package/src/app/api/ip/route.ts +2 -2
  26. package/src/app/api/memory/maintenance/route.ts +5 -2
  27. package/src/app/api/preview-server/route.ts +15 -12
  28. package/src/app/api/projects/[id]/route.ts +7 -46
  29. package/src/app/api/system/status/route.ts +11 -0
  30. package/src/app/api/upload/route.ts +4 -1
  31. package/src/cli/index.js +7 -0
  32. package/src/cli/spec.js +1 -0
  33. package/src/components/agents/agent-files-editor.tsx +44 -32
  34. package/src/components/agents/personality-builder.tsx +13 -7
  35. package/src/components/agents/trash-list.tsx +1 -1
  36. package/src/components/chat/chat-area.tsx +45 -23
  37. package/src/components/chat/message-bubble.test.ts +35 -0
  38. package/src/components/chat/message-bubble.tsx +20 -9
  39. package/src/components/chat/message-list.tsx +62 -42
  40. package/src/components/chat/swarm-status-card.tsx +10 -3
  41. package/src/components/input/chat-input.tsx +34 -14
  42. package/src/components/layout/daemon-indicator.tsx +7 -8
  43. package/src/components/layout/update-banner.tsx +8 -13
  44. package/src/components/logs/log-list.tsx +1 -1
  45. package/src/components/memory/memory-card.tsx +3 -1
  46. package/src/components/org-chart/org-chart-view.tsx +4 -0
  47. package/src/components/projects/project-list.tsx +4 -2
  48. package/src/components/projects/tabs/overview-tab.tsx +3 -2
  49. package/src/components/secrets/secret-sheet.tsx +1 -1
  50. package/src/components/secrets/secrets-list.tsx +1 -1
  51. package/src/components/shared/agent-switch-dialog.tsx +12 -6
  52. package/src/components/shared/dir-browser.tsx +22 -18
  53. package/src/components/skills/skill-sheet.tsx +2 -3
  54. package/src/components/tasks/task-list.tsx +1 -1
  55. package/src/components/tasks/task-sheet.tsx +1 -1
  56. package/src/hooks/use-openclaw-gateway.ts +46 -27
  57. package/src/instrumentation.ts +10 -7
  58. package/src/lib/chat/assistant-render-id.ts +3 -0
  59. package/src/lib/chat/chat-streaming-state.test.ts +42 -3
  60. package/src/lib/chat/chat-streaming-state.ts +20 -8
  61. package/src/lib/chat/chat.ts +18 -2
  62. package/src/lib/chat/queued-message-queue.test.ts +23 -1
  63. package/src/lib/chat/queued-message-queue.ts +11 -2
  64. package/src/lib/providers/anthropic.ts +6 -3
  65. package/src/lib/providers/claude-cli.ts +9 -3
  66. package/src/lib/providers/cli-utils.test.ts +124 -0
  67. package/src/lib/providers/cli-utils.ts +15 -0
  68. package/src/lib/providers/codex-cli.ts +9 -3
  69. package/src/lib/providers/gemini-cli.ts +6 -2
  70. package/src/lib/providers/index.ts +4 -1
  71. package/src/lib/providers/ollama.ts +5 -2
  72. package/src/lib/providers/openai.ts +8 -5
  73. package/src/lib/providers/opencode-cli.ts +6 -2
  74. package/src/lib/server/activity/activity-log.ts +21 -0
  75. package/src/lib/server/agents/agent-availability.test.ts +10 -5
  76. package/src/lib/server/agents/agent-cascade.ts +79 -59
  77. package/src/lib/server/agents/agent-registry.ts +23 -4
  78. package/src/lib/server/agents/agent-repository.ts +90 -0
  79. package/src/lib/server/agents/delegation-job-repository.ts +53 -0
  80. package/src/lib/server/agents/delegation-jobs.ts +11 -4
  81. package/src/lib/server/agents/guardian-checkpoint-repository.ts +35 -0
  82. package/src/lib/server/agents/guardian.ts +2 -2
  83. package/src/lib/server/agents/main-agent-loop.ts +14 -6
  84. package/src/lib/server/agents/main-loop-state-repository.ts +38 -0
  85. package/src/lib/server/agents/subagent-runtime.ts +9 -6
  86. package/src/lib/server/agents/subagent-swarm.ts +3 -2
  87. package/src/lib/server/agents/task-session.ts +3 -4
  88. package/src/lib/server/approvals/approval-repository.ts +30 -0
  89. package/src/lib/server/autonomy/supervisor-incident-repository.ts +42 -0
  90. package/src/lib/server/autonomy/supervisor-reflection.ts +14 -1
  91. package/src/lib/server/chat-execution/chat-execution-types.ts +38 -0
  92. package/src/lib/server/chat-execution/chat-execution-utils.ts +1 -1
  93. package/src/lib/server/chat-execution/chat-execution.ts +84 -1914
  94. package/src/lib/server/chat-execution/chat-turn-finalization.ts +620 -0
  95. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +221 -0
  96. package/src/lib/server/chat-execution/chat-turn-preflight.ts +133 -0
  97. package/src/lib/server/chat-execution/chat-turn-preparation.ts +817 -0
  98. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +296 -0
  99. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +5 -5
  100. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -3
  101. package/src/lib/server/chat-execution/continuation-limits.ts +6 -3
  102. package/src/lib/server/chat-execution/message-classifier.test.ts +329 -0
  103. package/src/lib/server/chat-execution/message-classifier.ts +5 -2
  104. package/src/lib/server/chat-execution/post-stream-finalization.ts +5 -2
  105. package/src/lib/server/chat-execution/prompt-builder.ts +22 -1
  106. package/src/lib/server/chat-execution/prompt-sections.ts +55 -13
  107. package/src/lib/server/chat-execution/response-completeness.ts +5 -2
  108. package/src/lib/server/chat-execution/situational-awareness.ts +12 -7
  109. package/src/lib/server/chat-execution/stream-agent-chat.ts +58 -25
  110. package/src/lib/server/chatrooms/chatroom-memory-bridge.ts +6 -3
  111. package/src/lib/server/chatrooms/chatroom-repository.ts +32 -0
  112. package/src/lib/server/connectors/bluebubbles.ts +7 -4
  113. package/src/lib/server/connectors/connector-inbound.ts +16 -13
  114. package/src/lib/server/connectors/connector-lifecycle.ts +11 -8
  115. package/src/lib/server/connectors/connector-outbound.ts +6 -3
  116. package/src/lib/server/connectors/connector-repository.ts +58 -0
  117. package/src/lib/server/connectors/discord.ts +10 -7
  118. package/src/lib/server/connectors/email.ts +17 -14
  119. package/src/lib/server/connectors/googlechat.ts +7 -4
  120. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -2
  121. package/src/lib/server/connectors/matrix.ts +6 -3
  122. package/src/lib/server/connectors/openclaw.ts +20 -17
  123. package/src/lib/server/connectors/outbox.ts +4 -1
  124. package/src/lib/server/connectors/runtime-state.test.ts +117 -0
  125. package/src/lib/server/connectors/runtime-state.ts +19 -0
  126. package/src/lib/server/connectors/session-consolidation.ts +5 -2
  127. package/src/lib/server/connectors/signal.ts +9 -6
  128. package/src/lib/server/connectors/slack.ts +13 -10
  129. package/src/lib/server/connectors/teams.ts +8 -5
  130. package/src/lib/server/connectors/telegram.ts +15 -12
  131. package/src/lib/server/connectors/whatsapp.ts +32 -29
  132. package/src/lib/server/credentials/credential-repository.ts +7 -0
  133. package/src/lib/server/embeddings.ts +4 -1
  134. package/src/lib/server/gateways/gateway-profile-repository.ts +4 -0
  135. package/src/lib/server/link-understanding.ts +4 -1
  136. package/src/lib/server/memory/memory-abstract.test.ts +59 -0
  137. package/src/lib/server/memory/memory-abstract.ts +59 -0
  138. package/src/lib/server/memory/memory-db.ts +40 -14
  139. package/src/lib/server/missions/mission-repository.ts +74 -0
  140. package/src/lib/server/missions/mission-service/actions.ts +6 -0
  141. package/src/lib/server/missions/mission-service/bindings.ts +9 -0
  142. package/src/lib/server/missions/mission-service/context.ts +4 -0
  143. package/src/lib/server/missions/mission-service/core.ts +2269 -0
  144. package/src/lib/server/missions/mission-service/queries.ts +12 -0
  145. package/src/lib/server/missions/mission-service/recovery.ts +5 -0
  146. package/src/lib/server/missions/mission-service/ticks.ts +9 -0
  147. package/src/lib/server/missions/mission-service.test.ts +9 -2
  148. package/src/lib/server/missions/mission-service.ts +6 -2263
  149. package/src/lib/server/openclaw/gateway.ts +8 -5
  150. package/src/lib/server/persistence/repository-utils.ts +154 -0
  151. package/src/lib/server/persistence/storage-context.ts +51 -0
  152. package/src/lib/server/persistence/transaction.ts +1 -0
  153. package/src/lib/server/project-utils.ts +13 -0
  154. package/src/lib/server/projects/project-repository.ts +36 -0
  155. package/src/lib/server/projects/project-service.ts +79 -0
  156. package/src/lib/server/protocols/protocol-agent-turn.ts +5 -2
  157. package/src/lib/server/protocols/protocol-normalization.test.ts +6 -4
  158. package/src/lib/server/protocols/protocol-run-lifecycle.ts +5 -2
  159. package/src/lib/server/protocols/protocol-step-helpers.ts +4 -1
  160. package/src/lib/server/provider-health.ts +18 -0
  161. package/src/lib/server/query-expansion.ts +4 -1
  162. package/src/lib/server/runtime/alert-dispatch.ts +8 -7
  163. package/src/lib/server/runtime/daemon-policy.ts +1 -1
  164. package/src/lib/server/runtime/daemon-state/core.ts +1570 -0
  165. package/src/lib/server/runtime/daemon-state/health.ts +6 -0
  166. package/src/lib/server/runtime/daemon-state/policy.ts +7 -0
  167. package/src/lib/server/runtime/daemon-state/supervisor.ts +6 -0
  168. package/src/lib/server/runtime/daemon-state.test.ts +48 -0
  169. package/src/lib/server/runtime/daemon-state.ts +3 -1331
  170. package/src/lib/server/runtime/estop-repository.ts +4 -0
  171. package/src/lib/server/runtime/estop.ts +3 -1
  172. package/src/lib/server/runtime/heartbeat-service.test.ts +2 -2
  173. package/src/lib/server/runtime/heartbeat-service.ts +78 -34
  174. package/src/lib/server/runtime/heartbeat-wake.ts +6 -4
  175. package/src/lib/server/runtime/idle-window.ts +6 -3
  176. package/src/lib/server/runtime/network.ts +11 -0
  177. package/src/lib/server/runtime/orchestrator-events.ts +2 -2
  178. package/src/lib/server/runtime/perf.ts +4 -1
  179. package/src/lib/server/runtime/process-manager.ts +7 -4
  180. package/src/lib/server/runtime/queue/claims.ts +4 -0
  181. package/src/lib/server/runtime/queue/core.ts +2079 -0
  182. package/src/lib/server/runtime/queue/execution.ts +7 -0
  183. package/src/lib/server/runtime/queue/followups.ts +4 -0
  184. package/src/lib/server/runtime/queue/queries.ts +12 -0
  185. package/src/lib/server/runtime/queue/recovery.ts +7 -0
  186. package/src/lib/server/runtime/queue-recovery.test.ts +48 -13
  187. package/src/lib/server/runtime/queue-repository.ts +17 -0
  188. package/src/lib/server/runtime/queue.ts +5 -2058
  189. package/src/lib/server/runtime/run-ledger.ts +6 -5
  190. package/src/lib/server/runtime/run-repository.ts +73 -0
  191. package/src/lib/server/runtime/runtime-lock-repository.ts +8 -0
  192. package/src/lib/server/runtime/runtime-settings.ts +1 -1
  193. package/src/lib/server/runtime/runtime-state.ts +99 -0
  194. package/src/lib/server/runtime/scheduler.ts +13 -8
  195. package/src/lib/server/runtime/session-run-manager/cancellation.ts +157 -0
  196. package/src/lib/server/runtime/session-run-manager/drain.ts +246 -0
  197. package/src/lib/server/runtime/session-run-manager/enqueue.ts +287 -0
  198. package/src/lib/server/runtime/session-run-manager/queries.ts +117 -0
  199. package/src/lib/server/runtime/session-run-manager/recovery.ts +238 -0
  200. package/src/lib/server/runtime/session-run-manager/state.ts +441 -0
  201. package/src/lib/server/runtime/session-run-manager/types.ts +74 -0
  202. package/src/lib/server/runtime/session-run-manager.ts +72 -1374
  203. package/src/lib/server/runtime/watch-job-repository.ts +35 -0
  204. package/src/lib/server/runtime/watch-jobs.ts +3 -1
  205. package/src/lib/server/sandbox/bridge-auth-registry.ts +6 -0
  206. package/src/lib/server/sandbox/novnc-auth.ts +10 -0
  207. package/src/lib/server/schedules/schedule-repository.ts +42 -0
  208. package/src/lib/server/session-tools/context.ts +14 -0
  209. package/src/lib/server/session-tools/discovery.ts +9 -6
  210. package/src/lib/server/session-tools/index.ts +3 -1
  211. package/src/lib/server/session-tools/platform.ts +1 -1
  212. package/src/lib/server/session-tools/subagent.ts +23 -2
  213. package/src/lib/server/session-tools/wallet.ts +4 -1
  214. package/src/lib/server/sessions/session-repository.ts +85 -0
  215. package/src/lib/server/settings/settings-repository.ts +25 -0
  216. package/src/lib/server/skills/clawhub-client.ts +4 -1
  217. package/src/lib/server/skills/runtime-skill-resolver.ts +8 -2
  218. package/src/lib/server/skills/skill-discovery.test.ts +2 -2
  219. package/src/lib/server/skills/skill-discovery.ts +2 -2
  220. package/src/lib/server/skills/skill-eligibility.ts +6 -0
  221. package/src/lib/server/skills/skill-repository.ts +14 -0
  222. package/src/lib/server/solana.ts +6 -0
  223. package/src/lib/server/storage-auth.ts +5 -5
  224. package/src/lib/server/storage-normalization.ts +4 -0
  225. package/src/lib/server/storage.ts +32 -32
  226. package/src/lib/server/tasks/task-followups.ts +4 -1
  227. package/src/lib/server/tasks/task-repository.ts +54 -0
  228. package/src/lib/server/tool-loop-detection.ts +8 -3
  229. package/src/lib/server/tool-planning.ts +226 -0
  230. package/src/lib/server/tool-retry.ts +4 -3
  231. package/src/lib/server/usage/usage-repository.ts +30 -0
  232. package/src/lib/server/wallet/wallet-portfolio.ts +29 -0
  233. package/src/lib/server/webhooks/webhook-repository.ts +10 -0
  234. package/src/lib/server/ws-hub.ts +5 -2
  235. package/src/lib/strip-internal-metadata.test.ts +78 -37
  236. package/src/lib/strip-internal-metadata.ts +20 -6
  237. package/src/stores/use-approval-store.ts +7 -1
  238. package/src/stores/use-chat-store.test.ts +54 -0
  239. package/src/stores/use-chat-store.ts +26 -6
  240. package/src/types/index.ts +6 -0
  241. /package/{bundled-skills → skills}/google-workspace/SKILL.md +0 -0
@@ -8,6 +8,9 @@ import { getGatewayProfile, getGatewayProfiles, resolvePrimaryAgentRoute } from
8
8
  import { isAgentDisabled } from '@/lib/server/agents/agent-availability'
9
9
  import { errorMessage, hmrSingleton, jitteredBackoff } from '@/lib/shared-utils'
10
10
  import type { Agent } from '@/types'
11
+ import { log } from '@/lib/server/logger'
12
+
13
+ const TAG = 'openclaw-gateway'
11
14
 
12
15
  // --- Types ---
13
16
 
@@ -165,7 +168,7 @@ export class OpenClawGateway {
165
168
  try {
166
169
  const result = await wsConnect(this.wsUrl, this.token, true, 15_000)
167
170
  if (!result.ok || !result.ws) {
168
- console.error('[openclaw-gateway] Connect failed:', result.message)
171
+ log.error(TAG, 'Connect failed:', result.message)
169
172
  this.scheduleReconnect()
170
173
  return false
171
174
  }
@@ -173,7 +176,7 @@ export class OpenClawGateway {
173
176
  this.ws = result.ws
174
177
  this._connected = true
175
178
  this.consecutiveFailures = 0
176
- console.log('[openclaw-gateway] Connected to gateway')
179
+ log.info(TAG, 'Connected to gateway')
177
180
 
178
181
  this.ws.on('message', (data) => {
179
182
  try {
@@ -195,7 +198,7 @@ export class OpenClawGateway {
195
198
 
196
199
  return true
197
200
  } catch (err: unknown) {
198
- console.error('[openclaw-gateway] Connect error:', errorMessage(err))
201
+ log.error(TAG, 'Connect error:', errorMessage(err))
199
202
  this.scheduleReconnect()
200
203
  return false
201
204
  }
@@ -213,7 +216,7 @@ export class OpenClawGateway {
213
216
  this.ws = null
214
217
  }
215
218
  this._connected = false
216
- console.log('[openclaw-gateway] Disconnected')
219
+ log.info(TAG, 'Disconnected')
217
220
  }
218
221
 
219
222
  private scheduleReconnect() {
@@ -228,7 +231,7 @@ export class OpenClawGateway {
228
231
  this.doConnect().catch(() => {})
229
232
  }, delay)
230
233
  if (this.consecutiveFailures === 1 || this.consecutiveFailures % 5 === 0) {
231
- console.log(`[openclaw-gateway] ${this.consecutiveFailures} consecutive failure${this.consecutiveFailures > 1 ? 's' : ''}, next retry in ${Math.round(delay / 1000)}s`)
234
+ log.info(TAG, `${this.consecutiveFailures} consecutive failure${this.consecutiveFailures > 1 ? 's' : ''}, next retry in ${Math.round(delay / 1000)}s`)
232
235
  }
233
236
  }
234
237
 
@@ -0,0 +1,154 @@
1
+ import { perf } from '@/lib/server/runtime/perf'
2
+
3
+ export interface RecordRepository<
4
+ T,
5
+ ListOptions = void,
6
+ UpsertValue = T | Record<string, unknown>,
7
+ > {
8
+ get(id: string, options?: ListOptions): T | null
9
+ getMany(ids: string[], options?: ListOptions): Record<string, T>
10
+ list(options?: ListOptions): Record<string, T>
11
+ upsert(id: string, value: UpsertValue): void
12
+ upsertMany(entries: Array<[string, UpsertValue]>): void
13
+ patch(id: string, updater: (current: T | null) => T | null, options?: ListOptions): T | null
14
+ replace(data: Record<string, UpsertValue>): void
15
+ delete(id: string): void
16
+ }
17
+
18
+ interface RecordRepositoryOps<
19
+ T,
20
+ ListOptions = void,
21
+ UpsertValue = T | Record<string, unknown>,
22
+ > {
23
+ get(id: string, options?: ListOptions): T | null
24
+ list(options?: ListOptions): Record<string, T>
25
+ upsert(id: string, value: UpsertValue): void
26
+ upsertMany?: (entries: Array<[string, UpsertValue]>) => void
27
+ patch?: (id: string, updater: (current: T | null) => T | null) => T | null
28
+ replace?: (data: Record<string, UpsertValue>) => void
29
+ delete?: (id: string) => void
30
+ }
31
+
32
+ export interface SingletonRepository<
33
+ T,
34
+ SaveValue = T | Record<string, unknown>,
35
+ > {
36
+ get(): T
37
+ save(value: SaveValue): void
38
+ patch(updater: (current: T) => SaveValue): T
39
+ }
40
+
41
+ interface SingletonRepositoryOps<
42
+ T,
43
+ SaveValue = T | Record<string, unknown>,
44
+ > {
45
+ get(): T
46
+ save(value: SaveValue): void
47
+ }
48
+
49
+ function uniqueIds(ids: string[]): string[] {
50
+ const out: string[] = []
51
+ const seen = new Set<string>()
52
+ for (const id of ids) {
53
+ const normalized = typeof id === 'string' ? id.trim() : ''
54
+ if (!normalized || seen.has(normalized)) continue
55
+ seen.add(normalized)
56
+ out.push(normalized)
57
+ }
58
+ return out
59
+ }
60
+
61
+ export function createRecordRepository<
62
+ T,
63
+ ListOptions = void,
64
+ UpsertValue = T | Record<string, unknown>,
65
+ >(
66
+ name: string,
67
+ ops: RecordRepositoryOps<T, ListOptions, UpsertValue>,
68
+ ): RecordRepository<T, ListOptions, UpsertValue> {
69
+ return {
70
+ get(id, options) {
71
+ return perf.measureSync('repository', `${name}.get`, () => ops.get(id, options), { id })
72
+ },
73
+ getMany(ids, options) {
74
+ return perf.measureSync('repository', `${name}.getMany`, () => {
75
+ const result: Record<string, T> = {}
76
+ for (const id of uniqueIds(ids)) {
77
+ const item = ops.get(id, options)
78
+ if (item) result[id] = item
79
+ }
80
+ return result
81
+ }, { count: ids.length })
82
+ },
83
+ list(options) {
84
+ return perf.measureSync('repository', `${name}.list`, () => ops.list(options))
85
+ },
86
+ upsert(id, value) {
87
+ perf.measureSync('repository', `${name}.upsert`, () => ops.upsert(id, value), { id })
88
+ },
89
+ upsertMany(entries) {
90
+ perf.measureSync('repository', `${name}.upsertMany`, () => {
91
+ if (ops.upsertMany) {
92
+ ops.upsertMany(entries)
93
+ return
94
+ }
95
+ for (const [id, value] of entries) ops.upsert(id, value)
96
+ }, { count: entries.length })
97
+ },
98
+ patch(id, updater, options) {
99
+ return perf.measureSync('repository', `${name}.patch`, () => {
100
+ if (ops.patch) return ops.patch(id, updater)
101
+ const current = ops.get(id, options)
102
+ const next = updater(current)
103
+ if (next === null) {
104
+ if (!ops.delete) return null
105
+ ops.delete(id)
106
+ return null
107
+ }
108
+ ops.upsert(id, next as UpsertValue)
109
+ return next
110
+ }, { id })
111
+ },
112
+ replace(data) {
113
+ perf.measureSync('repository', `${name}.replace`, () => {
114
+ if (ops.replace) {
115
+ ops.replace(data)
116
+ return
117
+ }
118
+ const entries = Object.entries(data)
119
+ if (ops.upsertMany) ops.upsertMany(entries)
120
+ else for (const [id, value] of entries) ops.upsert(id, value)
121
+ }, { count: Object.keys(data).length })
122
+ },
123
+ delete(id) {
124
+ perf.measureSync('repository', `${name}.delete`, () => {
125
+ if (!ops.delete) return
126
+ ops.delete(id)
127
+ }, { id })
128
+ },
129
+ }
130
+ }
131
+
132
+ export function createSingletonRepository<
133
+ T,
134
+ SaveValue = T | Record<string, unknown>,
135
+ >(
136
+ name: string,
137
+ ops: SingletonRepositoryOps<T, SaveValue>,
138
+ ): SingletonRepository<T, SaveValue> {
139
+ return {
140
+ get() {
141
+ return perf.measureSync('repository', `${name}.get`, () => ops.get())
142
+ },
143
+ save(value) {
144
+ perf.measureSync('repository', `${name}.save`, () => ops.save(value))
145
+ },
146
+ patch(updater) {
147
+ return perf.measureSync('repository', `${name}.patch`, () => {
148
+ const next = updater(ops.get())
149
+ ops.save(next)
150
+ return ops.get()
151
+ })
152
+ },
153
+ }
154
+ }
@@ -0,0 +1,51 @@
1
+ import { withTransaction } from '@/lib/server/storage'
2
+
3
+ import { agentRepository } from '@/lib/server/agents/agent-repository'
4
+ import { approvalRepository } from '@/lib/server/approvals/approval-repository'
5
+ import { chatroomRepository } from '@/lib/server/chatrooms/chatroom-repository'
6
+ import { connectorRepository } from '@/lib/server/connectors/connector-repository'
7
+ import { missionEventRepository, missionRepository } from '@/lib/server/missions/mission-repository'
8
+ import { projectRepository } from '@/lib/server/projects/project-repository'
9
+ import { scheduleRepository } from '@/lib/server/schedules/schedule-repository'
10
+ import { sessionRepository } from '@/lib/server/sessions/session-repository'
11
+ import { settingsRepository } from '@/lib/server/settings/settings-repository'
12
+ import { taskRepository } from '@/lib/server/tasks/task-repository'
13
+ import { runEventRepository, runRepository } from '@/lib/server/runtime/run-repository'
14
+
15
+ export interface StorageTxContext {
16
+ agents: typeof agentRepository
17
+ approvals: typeof approvalRepository
18
+ chatrooms: typeof chatroomRepository
19
+ connectors: typeof connectorRepository
20
+ missions: typeof missionRepository
21
+ missionEvents: typeof missionEventRepository
22
+ projects: typeof projectRepository
23
+ runs: typeof runRepository
24
+ runEvents: typeof runEventRepository
25
+ schedules: typeof scheduleRepository
26
+ sessions: typeof sessionRepository
27
+ settings: typeof settingsRepository
28
+ tasks: typeof taskRepository
29
+ }
30
+
31
+ export function createStorageTxContext(): StorageTxContext {
32
+ return {
33
+ agents: agentRepository,
34
+ approvals: approvalRepository,
35
+ chatrooms: chatroomRepository,
36
+ connectors: connectorRepository,
37
+ missions: missionRepository,
38
+ missionEvents: missionEventRepository,
39
+ projects: projectRepository,
40
+ runs: runRepository,
41
+ runEvents: runEventRepository,
42
+ schedules: scheduleRepository,
43
+ sessions: sessionRepository,
44
+ settings: settingsRepository,
45
+ tasks: taskRepository,
46
+ }
47
+ }
48
+
49
+ export function withStorageTx<T>(fn: (ctx: StorageTxContext) => T): T {
50
+ return withTransaction(() => fn(createStorageTxContext()))
51
+ }
@@ -0,0 +1 @@
1
+ export { withTransaction } from '@/lib/server/storage'
@@ -93,6 +93,8 @@ export interface ProjectResourceSummary {
93
93
  openTaskCount: number
94
94
  queuedTaskCount: number
95
95
  runningTaskCount: number
96
+ failedTaskCount: number
97
+ staleTaskCount: number
96
98
  activeScheduleCount: number
97
99
  secretCount: number
98
100
  skillCount: number
@@ -118,6 +120,15 @@ export function summarizeProjectResources(projectId: string): ProjectResourceSum
118
120
  const openTasks = tasks
119
121
  .filter((task) => ['backlog', 'queued', 'running'].includes(String(task.status || '').toLowerCase()))
120
122
  .sort(byUpdatedDesc)
123
+ const failedTasks = tasks.filter((task) => String(task.status || '').toLowerCase() === 'failed')
124
+ const now = Date.now()
125
+ const staleDays = 3 * 24 * 60 * 60 * 1000
126
+ const staleTasks = tasks.filter((task) => {
127
+ const status = String(task.status || '').toLowerCase()
128
+ if (['completed', 'cancelled', 'archived'].includes(status)) return false
129
+ const lastUpdate = Number(task.updatedAt || task.createdAt || 0)
130
+ return lastUpdate > 0 && (now - lastUpdate) > staleDays
131
+ })
121
132
  const activeSchedules = schedules
122
133
  .filter((schedule) => String(schedule.status || '').toLowerCase() === 'active')
123
134
  .sort(byUpdatedDesc)
@@ -129,6 +140,8 @@ export function summarizeProjectResources(projectId: string): ProjectResourceSum
129
140
  openTaskCount: openTasks.length,
130
141
  queuedTaskCount: openTasks.filter((task) => task.status === 'queued').length,
131
142
  runningTaskCount: openTasks.filter((task) => task.status === 'running').length,
143
+ failedTaskCount: failedTasks.length,
144
+ staleTaskCount: staleTasks.length,
132
145
  activeScheduleCount: activeSchedules.length,
133
146
  secretCount: secrets.length,
134
147
  skillCount: skills.length,
@@ -0,0 +1,36 @@
1
+ import type { Project } from '@/types'
2
+
3
+ import {
4
+ deleteProject as deleteStoredProject,
5
+ loadProjects as loadStoredProjects,
6
+ saveProjects as saveStoredProjects,
7
+ upsertStoredItem,
8
+ } from '@/lib/server/storage'
9
+ import { createRecordRepository } from '@/lib/server/persistence/repository-utils'
10
+
11
+ export const projectRepository = createRecordRepository<Project>(
12
+ 'projects',
13
+ {
14
+ get(id) {
15
+ return (loadStoredProjects() as Record<string, Project>)[id] || null
16
+ },
17
+ list() {
18
+ return loadStoredProjects() as Record<string, Project>
19
+ },
20
+ upsert(id, value) {
21
+ upsertStoredItem('projects', id, value)
22
+ },
23
+ replace(data) {
24
+ saveStoredProjects(data)
25
+ },
26
+ delete(id) {
27
+ deleteStoredProject(id)
28
+ },
29
+ },
30
+ )
31
+
32
+ export const loadProjects = () => projectRepository.list()
33
+ export const loadProject = (id: string) => projectRepository.get(id)
34
+ export const saveProjects = (items: Record<string, Project | Record<string, unknown>>) => projectRepository.replace(items as Record<string, Project>)
35
+ export const upsertProject = (id: string, value: Project | Record<string, unknown>) => projectRepository.upsert(id, value as Project)
36
+ export const deleteProject = (id: string) => projectRepository.delete(id)
@@ -0,0 +1,79 @@
1
+ import type { Agent, BoardTask, Project, Schedule, Skill, StoredSecret } from '@/types'
2
+
3
+ import {
4
+ deleteProject as deleteStoredProject,
5
+ loadAgents,
6
+ loadProjects,
7
+ loadSchedules,
8
+ loadSecrets,
9
+ loadSkills,
10
+ loadTasks,
11
+ saveAgents,
12
+ saveProjects,
13
+ saveSchedules,
14
+ saveSecrets,
15
+ saveSkills,
16
+ saveTasks,
17
+ } from '@/lib/server/storage'
18
+ import { ensureProjectWorkspace, normalizeProjectPatchInput } from '@/lib/server/project-utils'
19
+ import { notify } from '@/lib/server/ws-hub'
20
+
21
+ type ProjectLinkedRecord = {
22
+ projectId?: string
23
+ }
24
+
25
+ function clearProjectId<T extends ProjectLinkedRecord>(
26
+ projectId: string,
27
+ load: () => Record<string, T>,
28
+ save: (items: Record<string, T>) => void,
29
+ topic: string,
30
+ ): void {
31
+ const items = load()
32
+ let changed = false
33
+ for (const item of Object.values(items)) {
34
+ if (item.projectId !== projectId) continue
35
+ item.projectId = undefined
36
+ changed = true
37
+ }
38
+ if (!changed) return
39
+ save(items)
40
+ notify(topic)
41
+ }
42
+
43
+ export function getProject(id: string): Project | null {
44
+ return loadProjects()[id] || null
45
+ }
46
+
47
+ export function updateProject(id: string, input: Record<string, unknown>): Project | null {
48
+ const projects = loadProjects()
49
+ const existing = projects[id]
50
+ if (!existing) return null
51
+
52
+ const patch = normalizeProjectPatchInput(input)
53
+ const nextProject: Project = {
54
+ ...existing,
55
+ ...patch,
56
+ id,
57
+ updatedAt: Date.now(),
58
+ }
59
+ projects[id] = nextProject
60
+ saveProjects(projects)
61
+ ensureProjectWorkspace(id, nextProject.name)
62
+ notify('projects')
63
+ return nextProject
64
+ }
65
+
66
+ export function deleteProjectAndDetachReferences(id: string): boolean {
67
+ if (!getProject(id)) return false
68
+
69
+ deleteStoredProject(id)
70
+ notify('projects')
71
+
72
+ clearProjectId<Agent>(id, loadAgents, saveAgents, 'agents')
73
+ clearProjectId<BoardTask>(id, loadTasks, saveTasks, 'tasks')
74
+ clearProjectId<Schedule>(id, loadSchedules, saveSchedules, 'schedules')
75
+ clearProjectId<Skill>(id, loadSkills, saveSkills, 'skills')
76
+ clearProjectId<StoredSecret>(id, loadSecrets, saveSecrets, 'secrets')
77
+
78
+ return true
79
+ }
@@ -4,7 +4,10 @@
4
4
  */
5
5
  import { HumanMessage } from '@langchain/core/messages'
6
6
  import { z } from 'zod'
7
+ import { log } from '@/lib/server/logger'
7
8
  import { genId } from '@/lib/id'
9
+
10
+ const TAG = 'protocol-agent-turn'
8
11
  import type {
9
12
  Agent,
10
13
  Chatroom,
@@ -230,7 +233,7 @@ export async function defaultExecuteAgentTurn(params: {
230
233
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
231
234
  if (attempt > 0) {
232
235
  const delay = BASE_DELAY_MS * Math.pow(2, attempt - 1)
233
- console.warn(`[protocols] retrying agent turn for ${params.agentId} (attempt ${attempt + 1}/${MAX_RETRIES + 1}, waiting ${delay}ms)`)
236
+ log.warn(TAG, `retrying agent turn for ${params.agentId} (attempt ${attempt + 1}/${MAX_RETRIES + 1}, waiting ${delay}ms)`)
234
237
  await new Promise((resolve) => setTimeout(resolve, delay))
235
238
  }
236
239
  try {
@@ -271,7 +274,7 @@ export async function defaultExecuteAgentTurn(params: {
271
274
  const msg = errorMessage(err)
272
275
  const isRetryable = /\b(401|429|5\d{2}|timeout|ECONNR|ETIMEDOUT|ENOTFOUND|socket hang up|fetch failed)\b/i.test(msg)
273
276
  if (!isRetryable || attempt >= MAX_RETRIES) throw err
274
- console.warn(`[protocols] transient LLM error for agent ${params.agentId}: ${msg}`)
277
+ log.warn(TAG, `transient LLM error for agent ${params.agentId}: ${msg}`)
275
278
  }
276
279
  }
277
280
  throw lastError
@@ -3,13 +3,15 @@ import { after, before, describe, it } from 'node:test'
3
3
 
4
4
  let mod: typeof import('@/lib/server/protocols/protocol-normalization')
5
5
 
6
+ const savedBuildMode = process.env.SWARMCLAW_BUILD_MODE
6
7
  before(async () => {
7
8
  process.env.SWARMCLAW_BUILD_MODE = '1'
8
9
  mod = await import('@/lib/server/protocols/protocol-normalization')
9
10
  })
10
11
 
11
12
  after(() => {
12
- delete process.env.SWARMCLAW_BUILD_MODE
13
+ if (savedBuildMode === undefined) delete process.env.SWARMCLAW_BUILD_MODE
14
+ else process.env.SWARMCLAW_BUILD_MODE = savedBuildMode
13
15
  })
14
16
 
15
17
  describe('protocol-normalization', () => {
@@ -26,8 +28,8 @@ describe('protocol-normalization', () => {
26
28
  })
27
29
 
28
30
  it('artifact_exists with artifactKind', () => {
29
- const result = mod.normalizeCondition({ type: 'artifact_exists', artifactKind: 'report' })
30
- assert.deepEqual(result, { type: 'artifact_exists', artifactKind: 'report' })
31
+ const result = mod.normalizeCondition({ type: 'artifact_exists', artifactKind: 'summary' })
32
+ assert.deepEqual(result, { type: 'artifact_exists', artifactKind: 'summary' })
31
33
  })
32
34
 
33
35
  it('artifact_exists without artifactKind', () => {
@@ -53,7 +55,7 @@ describe('protocol-normalization', () => {
53
55
  type: 'all',
54
56
  conditions: [
55
57
  { type: 'summary_exists' },
56
- { type: 'artifact_exists', artifactKind: 'doc' },
58
+ { type: 'artifact_exists', artifactKind: 'notes' },
57
59
  ],
58
60
  })
59
61
  assert.equal(result!.type, 'all')
@@ -2,7 +2,10 @@
2
2
  * Protocol run lifecycle: create/run/action, scheduling/recovery, launch helpers.
3
3
  * Groups G10 + G18 + G19 from protocol-service.ts
4
4
  */
5
+ import { log } from '@/lib/server/logger'
5
6
  import { genId } from '@/lib/id'
7
+
8
+ const TAG = 'protocol-run-lifecycle'
6
9
  import type {
7
10
  ProtocolRun,
8
11
  ProtocolRunConfig,
@@ -58,7 +61,7 @@ export function requestProtocolRunExecution(runId: string, deps?: ProtocolRunDep
58
61
  setTimeout(() => {
59
62
  void runProtocolRun(normalizedId, deps)
60
63
  .catch((err: unknown) => {
61
- console.warn(`[protocols] execution failed for ${normalizedId}: ${errorMessage(err)}`)
64
+ log.warn(TAG, `execution failed for ${normalizedId}: ${errorMessage(err)}`)
62
65
  })
63
66
  .finally(() => {
64
67
  protocolExecutionState.pendingRunIds.delete(normalizedId)
@@ -300,7 +303,7 @@ export function createProtocolRun(input: CreateProtocolRunInput, deps?: Protocol
300
303
  export async function runProtocolRun(runId: string, deps?: ProtocolRunDeps): Promise<ProtocolRun | null> {
301
304
  const release = acquireProtocolLease(runId)
302
305
  if (!release) {
303
- console.warn(`[protocols] could not acquire lease for run ${runId}, another execution may be active`)
306
+ log.warn(TAG, `could not acquire lease for run ${runId}, another execution may be active`)
304
307
  return loadProtocolRunById(runId)
305
308
  }
306
309
  try {
@@ -4,7 +4,10 @@
4
4
  */
5
5
  import { z } from 'zod'
6
6
  import { HumanMessage } from '@langchain/core/messages'
7
+ import { log } from '@/lib/server/logger'
7
8
  import { genId } from '@/lib/id'
9
+
10
+ const TAG = 'protocol-step-helpers'
8
11
  import type {
9
12
  ProtocolBranchCase,
10
13
  ProtocolConditionDefinition,
@@ -161,7 +164,7 @@ export function syncProtocolParentFromChildRun(runOrId: ProtocolRun | string, de
161
164
  const subflowState = parent.subflowState?.[child.parentStepId]
162
165
  if (subflowState && subflowState.childRunId === child.id) {
163
166
  if (typeof subflowMod.syncSubflowParentFromChildRun !== 'function') {
164
- console.warn('[protocol] syncSubflowParentFromChildRun not available (circular dep not yet resolved), skipping subflow sync')
167
+ log.warn(TAG, 'syncSubflowParentFromChildRun not available (circular dep not yet resolved), skipping subflow sync')
165
168
  return parent
166
169
  }
167
170
  return subflowMod.syncSubflowParentFromChildRun(child, parent, subflowState, deps)
@@ -14,15 +14,32 @@ interface ProviderHealthState {
14
14
  const states: Map<string, ProviderHealthState> =
15
15
  hmrSingleton('__swarmclaw_provider_health__', () => new Map<string, ProviderHealthState>())
16
16
 
17
+ const CLI_CHECK_CACHE_MAX = 100
17
18
  const cliCheckCache = new Map<string, { at: number; ok: boolean }>()
18
19
  const delegateReadyCache = new Map<string, { at: number; ok: boolean }>()
19
20
  const CLI_CHECK_TTL_MS = 30_000
20
21
  const isWindows = process.platform === 'win32'
21
22
 
23
+ function pruneTTLCache(cache: Map<string, { at: number }>, ttlMs: number, maxSize: number): void {
24
+ const now = Date.now()
25
+ for (const [k, v] of cache) {
26
+ if (now - v.at > ttlMs) cache.delete(k)
27
+ }
28
+ if (cache.size > maxSize) {
29
+ const excess = cache.size - maxSize
30
+ const iter = cache.keys()
31
+ for (let i = 0; i < excess; i++) {
32
+ const k = iter.next().value
33
+ if (k !== undefined) cache.delete(k)
34
+ }
35
+ }
36
+ }
37
+
22
38
  function commandExists(binary: string): boolean {
23
39
  const now = Date.now()
24
40
  const cached = cliCheckCache.get(binary)
25
41
  if (cached && now - cached.at < CLI_CHECK_TTL_MS) return cached.ok
42
+ pruneTTLCache(cliCheckCache, CLI_CHECK_TTL_MS, CLI_CHECK_CACHE_MAX)
26
43
  const probe = isWindows
27
44
  ? spawnSync('where', [binary], { timeout: 2000, stdio: 'pipe' })
28
45
  : spawnSync('/bin/zsh', ['-lc', `command -v ${binary} >/dev/null 2>&1`], { timeout: 2000 })
@@ -108,6 +125,7 @@ function delegateToolReady(delegateTool: DelegateTool): boolean {
108
125
  const now = Date.now()
109
126
  const cached = delegateReadyCache.get(delegateTool)
110
127
  if (cached && now - cached.at < CLI_CHECK_TTL_MS) return cached.ok
128
+ pruneTTLCache(delegateReadyCache, CLI_CHECK_TTL_MS, CLI_CHECK_CACHE_MAX)
111
129
 
112
130
  const binary = delegateBinary(delegateTool)
113
131
  let ok = commandExists(binary)
@@ -1,5 +1,8 @@
1
1
  import { loadAgents, loadSettings, loadCredentials, decryptKey } from './storage'
2
2
  import { getProvider } from '../providers'
3
+ import { log } from '@/lib/server/logger'
4
+
5
+ const TAG = 'query-expansion'
3
6
 
4
7
  /**
5
8
  * Expands a single user query into multiple semantic search variants
@@ -58,7 +61,7 @@ Format your response as a simple newline-separated list. No numbering, no bullet
58
61
  return [query, ...variants.slice(0, 3)]
59
62
  }
60
63
  } catch (err) {
61
- console.error('[query-expansion] Failed to expand query:', err)
64
+ log.error(TAG, 'Failed to expand query:', err)
62
65
  }
63
66
 
64
67
  return [query]
@@ -1,6 +1,9 @@
1
- import { loadSettings } from '@/lib/server/storage'
1
+ import { loadSettings } from '@/lib/server/settings/settings-repository'
2
2
  import type { AppNotification } from '@/types'
3
3
  import { errorMessage } from '@/lib/shared-utils'
4
+ import { log } from '@/lib/server/logger'
5
+
6
+ const TAG = 'alert-dispatch'
4
7
 
5
8
  /** In-memory rate limiter: dedupKey → last dispatch timestamp */
6
9
  const recentDispatches = new Map<string, number>()
@@ -23,11 +26,9 @@ export async function dispatchAlert(notification: AppNotification): Promise<void
23
26
  if (lastSent && now - lastSent < DEDUP_WINDOW_MS) return
24
27
  recentDispatches.set(dedupKey, now)
25
28
 
26
- // Prune stale entries periodically
27
- if (recentDispatches.size > 200) {
28
- for (const [key, ts] of recentDispatches) {
29
- if (now - ts > DEDUP_WINDOW_MS) recentDispatches.delete(key)
30
- }
29
+ // Prune stale entries on every write to bound growth
30
+ for (const [key, ts] of recentDispatches) {
31
+ if (now - ts > DEDUP_WINDOW_MS) recentDispatches.delete(key)
31
32
  }
32
33
 
33
34
  const webhookType = settings.alertWebhookType || 'custom'
@@ -60,6 +61,6 @@ export async function dispatchAlert(notification: AppNotification): Promise<void
60
61
  signal: AbortSignal.timeout(5000),
61
62
  })
62
63
  } catch (err: unknown) {
63
- console.warn('[alert-dispatch] Webhook delivery failed:', errorMessage(err))
64
+ log.warn(TAG, 'Webhook delivery failed:', errorMessage(err))
64
65
  }
65
66
  }
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_HEARTBEAT_INTERVAL_SEC } from '@/lib/runtime/heartbeat-defaults'
2
- import { loadSettings } from '@/lib/server/storage'
2
+ import { loadSettings } from '@/lib/server/settings/settings-repository'
3
3
  import type { Session } from '@/types'
4
4
 
5
5
  const SYNTHETIC_HEALTH_SESSION_USERS = new Set(['workbench', 'comparison-bench'])