@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
@@ -5,7 +5,13 @@ import type { Extension, ExtensionHooks } from '@/types'
5
5
  import { registerNativeCapability } from '../native-capabilities'
6
6
  import { normalizeToolInputArgs } from './normalize-tool-args'
7
7
  import { errorMessage, sleep } from '@/lib/shared-utils'
8
- import { loadAgents } from '@/lib/server/storage'
8
+ import { loadAgents } from '@/lib/server/agents/agent-repository'
9
+ import { classifyMessage } from '@/lib/server/chat-execution/message-classifier'
10
+ import {
11
+ buildDelegationTaskProfile,
12
+ resolveBestDelegateTarget,
13
+ type DelegationWorkType,
14
+ } from '@/lib/server/agents/delegation-advisory'
9
15
  import {
10
16
  cancelDelegationJob,
11
17
  getDelegationJob,
@@ -57,6 +63,9 @@ const subagentToolSchema = z.object({
57
63
  action: z.enum(SUBAGENT_ACTIONS).optional(),
58
64
  agentId: z.string().optional(),
59
65
  message: z.string().optional(),
66
+ selectionMode: z.enum(['explicit', 'best_fit']).optional(),
67
+ workType: z.enum(['coding', 'research', 'writing', 'review', 'operations', 'general']).optional(),
68
+ requiredCapabilities: z.union([z.array(z.string()), z.string()]).optional(),
60
69
  cwd: z.string().optional(),
61
70
  shareBrowserProfile: z.boolean().optional(),
62
71
  jobId: z.string().optional(),
@@ -88,6 +97,7 @@ export function resolveSubagentBrowserProfileId(
88
97
  // ---------------------------------------------------------------------------
89
98
 
90
99
  interface ActionContext {
100
+ agentId?: string
91
101
  sessionId?: string
92
102
  cwd: string
93
103
  delegationTargetMode?: 'all' | 'selected'
@@ -155,10 +165,74 @@ export function coerceSubagentActionArgs(rawArgs: Record<string, unknown>): Reco
155
165
 
156
166
  const parsedJobIds = parseJsonLike(coerced.jobIds)
157
167
  if (Array.isArray(parsedJobIds)) coerced.jobIds = parsedJobIds
168
+ const parsedRequiredCapabilities = parseJsonLike(coerced.requiredCapabilities)
169
+ if (Array.isArray(parsedRequiredCapabilities)) coerced.requiredCapabilities = parsedRequiredCapabilities
158
170
 
159
171
  return coerced
160
172
  }
161
173
 
174
+ function normalizeWorkType(value: unknown): DelegationWorkType | null {
175
+ if (
176
+ value === 'coding'
177
+ || value === 'research'
178
+ || value === 'writing'
179
+ || value === 'review'
180
+ || value === 'operations'
181
+ || value === 'general'
182
+ ) {
183
+ return value
184
+ }
185
+ return null
186
+ }
187
+
188
+ function normalizeStringList(value: unknown): string[] {
189
+ if (!Array.isArray(value)) return []
190
+ const seen = new Set<string>()
191
+ const out: string[] = []
192
+ for (const entry of value) {
193
+ const trimmed = typeof entry === 'string' ? entry.trim() : ''
194
+ if (!trimmed || seen.has(trimmed.toLowerCase())) continue
195
+ seen.add(trimmed.toLowerCase())
196
+ out.push(trimmed)
197
+ }
198
+ return out
199
+ }
200
+
201
+ async function resolveBestFitAgentSelection(
202
+ args: Record<string, unknown>,
203
+ ctx: ActionContext,
204
+ ): Promise<{ agentId: string; workType: DelegationWorkType; requiredCapabilities: string[] } | null> {
205
+ const message = typeof args.message === 'string' ? args.message.trim() : ''
206
+ if (!message) return null
207
+ const explicitWorkType = normalizeWorkType(args.workType)
208
+ const explicitCapabilities = normalizeStringList(args.requiredCapabilities)
209
+ const classification = (!explicitWorkType && explicitCapabilities.length === 0 && ctx.sessionId)
210
+ ? await classifyMessage({
211
+ sessionId: ctx.sessionId,
212
+ agentId: ctx.agentId || null,
213
+ message,
214
+ }).catch(() => null)
215
+ : null
216
+ const profile = buildDelegationTaskProfile({
217
+ classification,
218
+ workType: explicitWorkType,
219
+ requiredCapabilities: explicitCapabilities,
220
+ })
221
+ const selection = resolveBestDelegateTarget({
222
+ currentAgentId: ctx.agentId || null,
223
+ agents: loadAgents(),
224
+ profile,
225
+ delegationTargetMode: ctx.delegationTargetMode,
226
+ delegationTargetAgentIds: ctx.delegationTargetAgentIds,
227
+ })
228
+ if (!selection) return null
229
+ return {
230
+ agentId: selection.agentId,
231
+ workType: profile.workType,
232
+ requiredCapabilities: profile.requiredCapabilities,
233
+ }
234
+ }
235
+
162
236
  function requireString(args: Record<string, unknown>, key: string): string {
163
237
  const val = typeof args[key] === 'string' ? (args[key] as string).trim() : ''
164
238
  if (!val) throw new Error(`${key} is required.`)
@@ -373,10 +447,21 @@ function handleSwarmCancel(args: Record<string, unknown>): string {
373
447
  }
374
448
 
375
449
  async function handleStart(args: Record<string, unknown>, ctx: ActionContext): Promise<string> {
376
- const agentId = (args.agentId ?? args.agent_id) as string | undefined
450
+ const selectionMode = args.selectionMode === 'best_fit' ? 'best_fit' : 'explicit'
451
+ let agentId = (args.agentId ?? args.agent_id) as string | undefined
377
452
  const message = args.message as string | undefined
378
- if (!agentId) return 'Error: agentId is required.'
379
453
  if (!message) return 'Error: message is required.'
454
+ let selectedProfile: { workType: DelegationWorkType; requiredCapabilities: string[] } | null = null
455
+ if (selectionMode === 'best_fit') {
456
+ const resolved = await resolveBestFitAgentSelection(args, ctx)
457
+ if (!resolved) return 'Error: no eligible delegate agent available for best_fit selection.'
458
+ agentId = resolved.agentId
459
+ selectedProfile = {
460
+ workType: resolved.workType,
461
+ requiredCapabilities: resolved.requiredCapabilities,
462
+ }
463
+ }
464
+ if (!agentId) return 'Error: agentId is required.'
380
465
  const targetError = validateAllowedSubagentTarget(agentId, ctx)
381
466
  if (targetError) return targetError
382
467
 
@@ -393,10 +478,13 @@ async function handleStart(args: Record<string, unknown>, ctx: ActionContext): P
393
478
  return JSON.stringify({
394
479
  jobId: handle.jobId,
395
480
  status: 'running',
481
+ selectionMode,
396
482
  agentId: handle.agentId,
397
483
  agentName: handle.agentName,
398
484
  sessionId: handle.sessionId,
399
485
  lineageId: handle.lineageId,
486
+ workType: selectedProfile?.workType || null,
487
+ requiredCapabilities: selectedProfile?.requiredCapabilities || [],
400
488
  })
401
489
  }
402
490
 
@@ -404,6 +492,7 @@ async function handleStart(args: Record<string, unknown>, ctx: ActionContext): P
404
492
  return JSON.stringify({
405
493
  jobId: result.jobId,
406
494
  status: result.status,
495
+ selectionMode,
407
496
  agentId: result.agentId,
408
497
  agentName: result.agentName,
409
498
  sessionId: result.sessionId,
@@ -412,6 +501,8 @@ async function handleStart(args: Record<string, unknown>, ctx: ActionContext): P
412
501
  depth: result.depth,
413
502
  childCount: result.childCount,
414
503
  durationMs: result.durationMs,
504
+ workType: selectedProfile?.workType || null,
505
+ requiredCapabilities: selectedProfile?.requiredCapabilities || [],
415
506
  })
416
507
  }
417
508
 
@@ -463,13 +554,13 @@ const SubagentExtension: Extension = {
463
554
  description: 'Delegate tasks to other specialized agents with resumable job handles.',
464
555
  hooks: {
465
556
  getCapabilityDescription: () =>
466
- 'Delegate tasks to other agents (spawn_subagent). Single task: action "start". '
557
+ 'Delegate tasks to other agents (spawn_subagent). Single task: action "start" with `agentId`, or use `selectionMode:"best_fit"` with `message` plus optional `workType`/`requiredCapabilities`. '
467
558
  + 'Multiple independent tasks: action "batch" with a tasks array. '
468
559
  + 'Event-driven parallel with status tracking: action "swarm" with a tasks array. '
469
560
  + 'Background swarms return a swarmId you can pass to swarm_status, swarm_list, and swarm_cancel.',
470
561
  getOperatingGuidance: () => [
471
562
  'SUBAGENT DISPATCH RULES:',
472
- '- Single task → action "start" with agentId + message.',
563
+ '- Single task → action "start" with `agentId` + `message`, or `selectionMode:"best_fit"` with `message` and optional `workType` / `requiredCapabilities`.',
473
564
  '- 2+ independent tasks → action "batch" with tasks array [{agentId, message}, ...]. Use `executionMode:"serial"` when local models are rate-limited.',
474
565
  '- Background coordination example → `{"action":"swarm","tasks":[...],"background":true}` and then read the returned `swarmId` before calling `swarm_status` or `swarm_cancel`.',
475
566
  '- If your final answer depends on all delegated results, set `waitForCompletion:true` and do not summarize early.',
@@ -483,8 +574,9 @@ const SubagentExtension: Extension = {
483
574
  {
484
575
  name: 'spawn_subagent',
485
576
  description: 'Delegate tasks to other agents. '
486
- + 'Actions: start (single agent), batch (2+ tasks via "tasks" array), swarm (multi-agent execution with status tracking via "tasks" array). '
577
+ + 'Actions: start (single agent, either explicit `agentId` or `selectionMode:"best_fit"`), batch (2+ tasks via "tasks" array), swarm (multi-agent execution with status tracking via "tasks" array). '
487
578
  + 'Management: status, list, wait, wait_all, cancel, lineage, aggregate, swarm_status, swarm_list, swarm_cancel. '
579
+ + 'In `best_fit` mode, provide `message` and optionally `workType` / `requiredCapabilities`; the runtime will choose the best allowed teammate and return the chosen agent in the tool output. '
488
580
  + 'For multiple independent tasks, prefer one `batch` or `swarm` call with tasks:[{agentId,message},...] over calling `start` repeatedly. '
489
581
  + 'When the final answer depends on every delegated result, keep waitForCompletion enabled so you can synthesize after all children finish. '
490
582
  + 'Use executionMode:"serial" to avoid rate limits on local models. '
@@ -495,6 +587,21 @@ const SubagentExtension: Extension = {
495
587
  action: { type: 'string', enum: ['start', 'status', 'list', 'wait', 'wait_all', 'cancel', 'lineage', 'batch', 'aggregate', 'swarm', 'swarm_status', 'swarm_list', 'swarm_cancel'] },
496
588
  agentId: { type: 'string' },
497
589
  message: { type: 'string' },
590
+ selectionMode: {
591
+ type: 'string',
592
+ enum: ['explicit', 'best_fit'],
593
+ description: 'Use "explicit" to target `agentId` directly, or "best_fit" to let the runtime choose the best allowed delegate.',
594
+ },
595
+ workType: {
596
+ type: 'string',
597
+ enum: ['coding', 'research', 'writing', 'review', 'operations', 'general'],
598
+ description: 'Optional hint for `best_fit` selection.',
599
+ },
600
+ requiredCapabilities: {
601
+ type: 'array',
602
+ items: { type: 'string' },
603
+ description: 'Optional explicit capability requirements for `best_fit` selection.',
604
+ },
498
605
  cwd: { type: 'string' },
499
606
  shareBrowserProfile: {
500
607
  type: 'boolean',
@@ -532,7 +639,17 @@ const SubagentExtension: Extension = {
532
639
  },
533
640
  required: []
534
641
  },
535
- execute: async (args, context) => executeSubagentAction(args, { sessionId: context.session.id, cwd: context.session.cwd || process.cwd() })
642
+ execute: async (args, context) => {
643
+ const sessionAgentId = context.session.agentId || undefined
644
+ const sessionAgent = sessionAgentId ? loadAgents()[sessionAgentId] : null
645
+ return executeSubagentAction(args, {
646
+ agentId: sessionAgentId,
647
+ sessionId: context.session.id,
648
+ cwd: context.session.cwd || process.cwd(),
649
+ delegationTargetMode: sessionAgent?.delegationTargetMode,
650
+ delegationTargetAgentIds: sessionAgent?.delegationTargetAgentIds,
651
+ })
652
+ }
536
653
  }
537
654
  ]
538
655
  }
@@ -562,6 +679,7 @@ export function buildSubagentTools(bctx: ToolBuildContext): StructuredToolInterf
562
679
  return [
563
680
  tool(
564
681
  async (args) => executeSubagentAction(args, {
682
+ agentId: bctx.ctx?.agentId || undefined,
565
683
  sessionId: bctx.ctx?.sessionId || undefined,
566
684
  cwd: bctx.cwd,
567
685
  delegationTargetMode: bctx.ctx?.delegationTargetMode,
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod'
2
2
  import { tool, type StructuredToolInterface } from '@langchain/core/tools'
3
3
  import { loadAgents, loadTasks, loadSessions } from '../storage'
4
+ import { getRecentMessages } from '@/lib/server/messages/message-repository'
4
5
  import { resolveTeam, resolveReachableAgentIds } from '../agents/team-resolution'
5
6
  import { getAgentDirectory } from '../agents/agent-registry'
6
7
  import { normalizeToolInputArgs } from './normalize-tool-args'
@@ -139,12 +140,12 @@ async function formatPeerContext(agentId: string, peerId: string): Promise<strin
139
140
  try {
140
141
  const sessions = allSessions || loadSessions()
141
142
  const peerSessions = Object.values(sessions)
142
- .filter((s) => s.agentId === peerId && Array.isArray(s.messages) && s.messages.length > 0)
143
+ .filter((s) => s.agentId === peerId && (s.messageCount ?? 0) > 0)
143
144
  .sort((a, b) => (b.lastActiveAt || 0) - (a.lastActiveAt || 0))
144
145
 
145
146
  const latestSession = peerSessions.length > 0 ? peerSessions[0] : null
146
- if (latestSession?.messages) {
147
- const recentMessages = latestSession.messages
147
+ if (latestSession) {
148
+ const recentMessages = getRecentMessages(latestSession.id, 20)
148
149
  .filter((m) => m.role === 'assistant' && Array.isArray(m.toolEvents) && m.toolEvents.length > 0)
149
150
  .slice(-MAX_RECENT_ACTIVITY)
150
151
  result.recentActivity = recentMessages.map((m) => {
@@ -1181,64 +1181,6 @@ const WalletExtension: Extension = {
1181
1181
  'If quote or assembly APIs keep failing, stop venue-shopping and use `wallet_tool` action `call_contract` for direct read-only onchain state or quote reads.',
1182
1182
  'Treat `wallet_tool` as a server-side wallet capability. It does not inject a browser wallet extension or complete third-party wallet-connect prompts for you.',
1183
1183
  ],
1184
- requestMatchers: [
1185
- {
1186
- capability: TOOL_CAPABILITY.walletInspect,
1187
- patterns: [
1188
- 'wallet',
1189
- 'balance',
1190
- 'address',
1191
- 'fund',
1192
- 'transfer',
1193
- 'send',
1194
- 'deposit',
1195
- 'withdraw',
1196
- 'swap',
1197
- 'bridge',
1198
- 'onchain',
1199
- 'token',
1200
- 'gas',
1201
- 'usdc',
1202
- 'eth',
1203
- 'sol',
1204
- 'solana',
1205
- 'ethereum',
1206
- 'arbitrum',
1207
- 'base',
1208
- 'wallet connect',
1209
- 'walletconnect',
1210
- 'dex',
1211
- 'erc-20',
1212
- 'spl',
1213
- 'trade on',
1214
- 'quote swap',
1215
- ],
1216
- },
1217
- {
1218
- capability: TOOL_CAPABILITY.walletExecute,
1219
- patterns: [
1220
- 'swap',
1221
- 'trade',
1222
- 'buy token',
1223
- 'sell token',
1224
- 'sign message',
1225
- 'sign typed data',
1226
- 'signature',
1227
- 'typed data',
1228
- 'eip-712',
1229
- 'sign transaction',
1230
- 'send transaction',
1231
- 'simulate transaction',
1232
- 'broadcast transaction',
1233
- 'contract call',
1234
- 'call contract',
1235
- 'read contract',
1236
- 'calldata',
1237
- 'approve token',
1238
- 'raw transaction',
1239
- ],
1240
- },
1241
- ],
1242
1184
  },
1243
1185
  parameters: {
1244
1186
  type: 'object',
@@ -0,0 +1,55 @@
1
+ import type { Session } from '@/types'
2
+ import { loadSession } from '@/lib/server/sessions/session-repository'
3
+
4
+ export interface SessionLineageIds {
5
+ parentSessionId: string | null
6
+ rootSessionId: string | null
7
+ }
8
+
9
+ export function resolveSessionLineageIds(
10
+ session: Pick<Session, 'id' | 'parentSessionId'> | null | undefined,
11
+ opts?: {
12
+ loadSessionById?: (id: string) => Session | null
13
+ maxDepth?: number
14
+ },
15
+ ): SessionLineageIds {
16
+ const parentSessionId = typeof session?.parentSessionId === 'string' && session.parentSessionId.trim()
17
+ ? session.parentSessionId.trim()
18
+ : null
19
+ const currentSessionId = typeof session?.id === 'string' && session.id.trim()
20
+ ? session.id.trim()
21
+ : null
22
+
23
+ if (!currentSessionId) {
24
+ return {
25
+ parentSessionId,
26
+ rootSessionId: parentSessionId,
27
+ }
28
+ }
29
+
30
+ const loadSessionById = opts?.loadSessionById || loadSession
31
+ const maxDepth = typeof opts?.maxDepth === 'number' && opts.maxDepth > 0 ? opts.maxDepth : 25
32
+ const seen = new Set<string>([currentSessionId])
33
+
34
+ let rootSessionId = currentSessionId
35
+ let cursor = parentSessionId
36
+ let depth = 0
37
+
38
+ while (cursor && depth < maxDepth) {
39
+ if (seen.has(cursor)) break
40
+ seen.add(cursor)
41
+ rootSessionId = cursor
42
+ const parent = loadSessionById(cursor)
43
+ const nextParentId = typeof parent?.parentSessionId === 'string' && parent.parentSessionId.trim()
44
+ ? parent.parentSessionId.trim()
45
+ : null
46
+ if (!nextParentId) break
47
+ cursor = nextParentId
48
+ depth += 1
49
+ }
50
+
51
+ return {
52
+ parentSessionId,
53
+ rootSessionId,
54
+ }
55
+ }
@@ -3,7 +3,6 @@ import type { Message, Session } from '@/types'
3
3
  import {
4
4
  deleteSession as deleteStoredSession,
5
5
  disableAllSessionHeartbeats as disableAllStoredSessionHeartbeats,
6
- getSessionMessages as getStoredSessionMessages,
7
6
  loadSession as loadStoredSession,
8
7
  loadSessions as loadStoredSessions,
9
8
  patchSession as patchStoredSession,
@@ -11,6 +10,7 @@ import {
11
10
  upsertSession as upsertStoredSession,
12
11
  } from '@/lib/server/storage'
13
12
  import { createRecordRepository } from '@/lib/server/persistence/repository-utils'
13
+ import { getMessages } from '@/lib/server/messages/message-repository'
14
14
 
15
15
  export const sessionRepository = createRecordRepository<Session>(
16
16
  'sessions',
@@ -72,7 +72,7 @@ export function deleteSession(id: string): void {
72
72
  }
73
73
 
74
74
  export function getSessionMessages(sessionId: string): Message[] {
75
- return getStoredSessionMessages(sessionId)
75
+ return getMessages(sessionId)
76
76
  }
77
77
 
78
78
  export function disableAllSessionHeartbeats(): number {
@@ -15,13 +15,15 @@ import type {
15
15
  import { errorMessage } from '@/lib/shared-utils'
16
16
  import { buildLLM } from '@/lib/server/build-llm'
17
17
  import {
18
+ loadLearnedSkill,
18
19
  loadLearnedSkills,
19
- loadRunReflections,
20
- loadSessions,
20
+ loadRunReflection,
21
21
  loadSkills,
22
- saveRunReflections,
22
+ upsertRunReflection,
23
23
  upsertLearnedSkill,
24
- } from '@/lib/server/storage'
24
+ } from '@/lib/server/skills/skill-repository'
25
+ import { loadSession } from '@/lib/server/sessions/session-repository'
26
+ import { getMessages, getMessageCount } from '@/lib/server/messages/message-repository'
25
27
  import { buildSessionTranscript } from './skill-suggestions'
26
28
  import { normalizeSkillPayload } from './skills-normalize'
27
29
  import { onNextIdleWindow } from '@/lib/server/runtime/idle-window'
@@ -169,7 +171,7 @@ function ensureHeading(name: string, content: string): string {
169
171
  }
170
172
 
171
173
  function collectRecentUserText(session: Session): string {
172
- return session.messages
174
+ return getMessages(session.id)
173
175
  .filter((message) => message?.role === 'user' && !message.suppressed && typeof message.text === 'string')
174
176
  .slice(-3)
175
177
  .map((message) => message.text)
@@ -263,7 +265,7 @@ function buildFailureSourceHash(input: {
263
265
 
264
266
  function buildObservation(input: ObserveLearnedSkillRunInput, session: Session): Observation | null {
265
267
  if (input.status === 'cancelled' || input.status === 'queued' || input.status === 'running') return null
266
- if (!Array.isArray(session.messages) || session.messages.length < 2) return null
268
+ if (getMessageCount(session.id) < 2) return null
267
269
 
268
270
  const toolEvents = Array.isArray(input.toolEvents) ? input.toolEvents : []
269
271
  const sourceSnippet = trimText(buildSessionTranscript(session), 700)
@@ -307,7 +309,7 @@ function buildDraftPrompt(params: {
307
309
  reflection: RunReflection | null
308
310
  existingSkillNames: string[]
309
311
  }): string {
310
- const toolSummary = params.session.messages
312
+ const toolSummary = getMessages(params.session.id)
311
313
  .flatMap((message) => message?.toolEvents || [])
312
314
  .slice(-8)
313
315
  .map((event) => `- ${event.name} (${event.error ? 'error' : 'ok'})`)
@@ -455,20 +457,20 @@ function appendReflectionLearnedSkillNotes(params: {
455
457
  skillIds: string[]
456
458
  }): void {
457
459
  if (!params.reflection || (params.notes.length === 0 && params.skillIds.length === 0)) return
458
- const reflections = loadRunReflections()
459
- const current = reflections[params.reflection.id]
460
+ const current = loadRunReflection(params.reflection.id)
460
461
  if (!current) return
461
- current.learnedSkillNotes = Array.from(new Set([
462
- ...(current.learnedSkillNotes || []),
463
- ...params.notes,
464
- ]))
465
- current.learnedSkillIds = Array.from(new Set([
466
- ...(current.learnedSkillIds || []),
467
- ...params.skillIds,
468
- ]))
469
- current.updatedAt = Date.now()
470
- reflections[current.id] = current
471
- saveRunReflections(reflections)
462
+ upsertRunReflection(current.id, {
463
+ ...current,
464
+ learnedSkillNotes: Array.from(new Set([
465
+ ...(current.learnedSkillNotes || []),
466
+ ...params.notes,
467
+ ])),
468
+ learnedSkillIds: Array.from(new Set([
469
+ ...(current.learnedSkillIds || []),
470
+ ...params.skillIds,
471
+ ])),
472
+ updatedAt: Date.now(),
473
+ })
472
474
  }
473
475
 
474
476
  function matchesSelectedLearnedSkill(session: Session, skill: LearnedSkill): boolean {
@@ -599,8 +601,7 @@ export async function observeLearnedSkillRunOutcome(
599
601
  ): Promise<{ notes: string[]; skillIds: string[] }> {
600
602
  const agentId = typeof input.agentId === 'string' ? input.agentId.trim() : ''
601
603
  if (!agentId) return { notes: [], skillIds: [] }
602
- const sessions = loadSessions()
603
- const session = sessions[input.sessionId] as Session | undefined
604
+ const session = loadSession(input.sessionId)
604
605
  if (!session) return { notes: [], skillIds: [] }
605
606
 
606
607
  const observation = buildObservation(input, session)
@@ -732,7 +733,7 @@ export async function observeLearnedSkillRunOutcome(
732
733
 
733
734
  if (validation.status === 'passed') {
734
735
  const parent = target.parentSkillId
735
- ? loadLearnedSkills()[target.parentSkillId] || null
736
+ ? loadLearnedSkill(target.parentSkillId)
736
737
  : null
737
738
  if (parent && parent.lifecycle === 'active') {
738
739
  target.lifecycle = 'review_ready'
@@ -11,8 +11,9 @@ import type {
11
11
  } from '@/types'
12
12
  import { dedup, hmrSingleton } from '@/lib/shared-utils'
13
13
  import { expandExtensionIds, getExtensionAliases, normalizeExtensionId } from '@/lib/server/tool-aliases'
14
- import { loadLearnedSkills, loadSettings, loadSkills } from '@/lib/server/storage'
15
14
  import { cosineSimilarity, getEmbedding } from '@/lib/server/embeddings'
15
+ import { loadSettings } from '@/lib/server/settings/settings-repository'
16
+ import { loadLearnedSkills, loadSkills } from '@/lib/server/skills/skill-repository'
16
17
  import { discoverSkills, type DiscoveredSkill } from './skill-discovery'
17
18
  import { evaluateSkillEligibility } from './skill-eligibility'
18
19
  import {
@@ -1,14 +1,137 @@
1
- export {
2
- deleteLearnedSkill,
3
- deleteSkill,
4
- loadLearnedSkill,
5
- loadLearnedSkills,
6
- loadSkillSuggestions,
7
- loadSkills,
8
- patchLearnedSkill,
9
- patchSkillSuggestion,
10
- saveLearnedSkills,
11
- saveSkills,
12
- upsertLearnedSkill,
13
- upsertSkillSuggestion,
1
+ import type { LearnedSkill, RunReflection, Skill, SkillSuggestion } from '@/types'
2
+
3
+ import {
4
+ deleteLearnedSkill as deleteStoredLearnedSkill,
5
+ deleteSkill as deleteStoredSkill,
6
+ deleteSkillSuggestion as deleteStoredSkillSuggestion,
7
+ loadLearnedSkill as loadStoredLearnedSkill,
8
+ loadLearnedSkills as loadStoredLearnedSkills,
9
+ loadRunReflection as loadStoredRunReflection,
10
+ loadRunReflections as loadStoredRunReflections,
11
+ loadSkillSuggestion as loadStoredSkillSuggestion,
12
+ loadSkillSuggestions as loadStoredSkillSuggestions,
13
+ loadSkills as loadStoredSkills,
14
+ patchLearnedSkill as patchStoredLearnedSkill,
15
+ patchSkillSuggestion as patchStoredSkillSuggestion,
16
+ saveLearnedSkills as saveStoredLearnedSkills,
17
+ saveRunReflections as saveStoredRunReflections,
18
+ saveSkillSuggestions as saveStoredSkillSuggestions,
19
+ saveSkills as saveStoredSkills,
20
+ upsertLearnedSkill as upsertStoredLearnedSkill,
21
+ upsertRunReflection as upsertStoredRunReflection,
22
+ upsertSkillSuggestion as upsertStoredSkillSuggestion,
23
+ upsertStoredItem,
14
24
  } from '@/lib/server/storage'
25
+ import { createRecordRepository } from '@/lib/server/persistence/repository-utils'
26
+
27
+ export const skillRepository = createRecordRepository<Skill>(
28
+ 'skills',
29
+ {
30
+ get(id) {
31
+ return (loadStoredSkills() as Record<string, Skill>)[id] || null
32
+ },
33
+ list() {
34
+ return loadStoredSkills() as Record<string, Skill>
35
+ },
36
+ upsert(id, value) {
37
+ upsertStoredItem('skills', id, value)
38
+ },
39
+ replace(data) {
40
+ saveStoredSkills(data as Record<string, Skill>)
41
+ },
42
+ delete(id) {
43
+ deleteStoredSkill(id)
44
+ },
45
+ },
46
+ )
47
+
48
+ export const learnedSkillRepository = createRecordRepository<LearnedSkill>(
49
+ 'learned-skills',
50
+ {
51
+ get(id) {
52
+ return loadStoredLearnedSkill(id) as LearnedSkill | null
53
+ },
54
+ list() {
55
+ return loadStoredLearnedSkills() as Record<string, LearnedSkill>
56
+ },
57
+ upsert(id, value) {
58
+ upsertStoredLearnedSkill(id, value as LearnedSkill)
59
+ },
60
+ replace(data) {
61
+ saveStoredLearnedSkills(data as Record<string, LearnedSkill>)
62
+ },
63
+ patch(id, updater) {
64
+ return patchStoredLearnedSkill(id, updater as (current: LearnedSkill | null) => LearnedSkill | null) as LearnedSkill | null
65
+ },
66
+ delete(id) {
67
+ deleteStoredLearnedSkill(id)
68
+ },
69
+ },
70
+ )
71
+
72
+ export const skillSuggestionRepository = createRecordRepository<SkillSuggestion>(
73
+ 'skill-suggestions',
74
+ {
75
+ get(id) {
76
+ return loadStoredSkillSuggestion(id) as SkillSuggestion | null
77
+ },
78
+ list() {
79
+ return loadStoredSkillSuggestions() as Record<string, SkillSuggestion>
80
+ },
81
+ upsert(id, value) {
82
+ upsertStoredSkillSuggestion(id, value as SkillSuggestion)
83
+ },
84
+ replace(data) {
85
+ saveStoredSkillSuggestions(data as Record<string, SkillSuggestion>)
86
+ },
87
+ patch(id, updater) {
88
+ return patchStoredSkillSuggestion(id, updater as (current: SkillSuggestion | null) => SkillSuggestion | null) as SkillSuggestion | null
89
+ },
90
+ delete(id) {
91
+ deleteStoredSkillSuggestion(id)
92
+ },
93
+ },
94
+ )
95
+
96
+ export const runReflectionRepository = createRecordRepository<RunReflection>(
97
+ 'run-reflections',
98
+ {
99
+ get(id) {
100
+ return loadStoredRunReflection(id) as RunReflection | null
101
+ },
102
+ list() {
103
+ return loadStoredRunReflections() as Record<string, RunReflection>
104
+ },
105
+ upsert(id, value) {
106
+ upsertStoredRunReflection(id, value as RunReflection)
107
+ },
108
+ replace(data) {
109
+ saveStoredRunReflections(data as Record<string, RunReflection>)
110
+ },
111
+ },
112
+ )
113
+
114
+ export const loadSkills = () => skillRepository.list()
115
+ export const loadSkill = (id: string) => skillRepository.get(id)
116
+ export const saveSkills = (items: Record<string, Skill | Record<string, unknown>>) => skillRepository.replace(items as Record<string, Skill>)
117
+ export const saveSkill = (id: string, value: Skill | Record<string, unknown>) => skillRepository.upsert(id, value as Skill)
118
+ export const deleteSkill = (id: string) => skillRepository.delete(id)
119
+
120
+ export const loadLearnedSkills = () => learnedSkillRepository.list()
121
+ export const loadLearnedSkill = (id: string) => learnedSkillRepository.get(id)
122
+ export const saveLearnedSkills = (items: Record<string, LearnedSkill | Record<string, unknown>>) => learnedSkillRepository.replace(items as Record<string, LearnedSkill>)
123
+ export const upsertLearnedSkill = (id: string, value: LearnedSkill | Record<string, unknown>) => learnedSkillRepository.upsert(id, value as LearnedSkill)
124
+ export const patchLearnedSkill = (id: string, updater: (current: LearnedSkill | null) => LearnedSkill | null) => learnedSkillRepository.patch(id, updater)
125
+ export const deleteLearnedSkill = (id: string) => learnedSkillRepository.delete(id)
126
+
127
+ export const loadSkillSuggestions = () => skillSuggestionRepository.list()
128
+ export const loadSkillSuggestion = (id: string) => skillSuggestionRepository.get(id)
129
+ export const saveSkillSuggestions = (items: Record<string, SkillSuggestion | Record<string, unknown>>) => skillSuggestionRepository.replace(items as Record<string, SkillSuggestion>)
130
+ export const upsertSkillSuggestion = (id: string, value: SkillSuggestion | Record<string, unknown>) => skillSuggestionRepository.upsert(id, value as SkillSuggestion)
131
+ export const patchSkillSuggestion = (id: string, updater: (current: SkillSuggestion | null) => SkillSuggestion | null) => skillSuggestionRepository.patch(id, updater)
132
+ export const deleteSkillSuggestion = (id: string) => skillSuggestionRepository.delete(id)
133
+
134
+ export const loadRunReflections = () => runReflectionRepository.list()
135
+ export const loadRunReflection = (id: string) => runReflectionRepository.get(id)
136
+ export const saveRunReflections = (items: Record<string, RunReflection | Record<string, unknown>>) => runReflectionRepository.replace(items as Record<string, RunReflection>)
137
+ export const upsertRunReflection = (id: string, value: RunReflection | Record<string, unknown>) => runReflectionRepository.upsert(id, value as RunReflection)