@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
@@ -49,6 +49,8 @@ import { logExecution } from '@/lib/server/execution-log'
49
49
  import { enqueueSystemEvent } from '@/lib/server/runtime/system-events'
50
50
  import { getEnabledCapabilityIds, splitCapabilityIds } from '@/lib/capability-selection'
51
51
  import { getSession, loadSessions, saveSession } from '@/lib/server/sessions/session-repository'
52
+ import { ensureRunContext } from '@/lib/server/run-context'
53
+ import { buildExecutionBrief, serializeExecutionBriefForDelegation } from '@/lib/server/execution-brief'
52
54
 
53
55
  // ---------------------------------------------------------------------------
54
56
  // Types
@@ -277,6 +279,16 @@ async function spawnSubagentImpl(
277
279
  browserProfileId,
278
280
  }
279
281
  sessions[sid] = applyResolvedRoute(nextSession, resolvePrimaryAgentRoute(agent))
282
+
283
+ // Enrich child session with parent's RunContext for delegation handoff
284
+ const delegationContext = parent ? serializeExecutionBriefForDelegation(buildExecutionBrief({ sessionId: context.sessionId })) : null
285
+ if (delegationContext) {
286
+ const childCtx = ensureRunContext(null)
287
+ childCtx.parentContext = delegationContext
288
+ childCtx.objective = input.message.slice(0, 900)
289
+ sessions[sid].runContext = childCtx
290
+ }
291
+
280
292
  saveSession(sid, sessions[sid])
281
293
 
282
294
  log.info('subagent', 'Spawning', { agentId: agent.id, agentName: agent.name, depth: depth + 1, jobId: job.id, sessionId: sid })
@@ -228,7 +228,26 @@ describe('supervisor-reflection', () => {
228
228
  model: 'gpt-test',
229
229
  claudeSessionId: null,
230
230
  messages: [
231
- { role: 'user', text: 'I am moving to Lisbon next month and prefer short check-ins while I am juggling the move.', time: 1 },
231
+ {
232
+ role: 'user',
233
+ text: 'I am moving to Lisbon next month and prefer short check-ins while I am juggling the move.',
234
+ time: 1,
235
+ semantics: {
236
+ taskIntent: 'general',
237
+ workType: 'general',
238
+ walletIntent: 'none',
239
+ isDeliverableTask: false,
240
+ isBroadGoal: false,
241
+ isResearchSynthesis: false,
242
+ hasHumanSignals: true,
243
+ hasSignificantEvent: true,
244
+ wantsScreenshots: false,
245
+ wantsOutboundDelivery: false,
246
+ wantsVoiceDelivery: false,
247
+ explicitToolRequests: [],
248
+ confidence: 0.98,
249
+ },
250
+ },
232
251
  { role: 'assistant', text: 'Understood. I will keep updates tight and remember the move timing.', time: 2 },
233
252
  ],
234
253
  createdAt: 1,
@@ -33,14 +33,16 @@ import { log } from '@/lib/server/logger'
33
33
  import { logExecution } from '@/lib/server/execution-log'
34
34
  import { logActivity } from '@/lib/server/storage'
35
35
  import { createNotification } from '@/lib/server/create-notification'
36
+ import { foldReflectionIntoRunContext } from '@/lib/server/run-context'
37
+ import { getSession, saveSession } from '@/lib/server/sessions/session-repository'
38
+ import { cleanText } from '@/lib/server/text-normalization'
39
+ import { getMessages, getMessageCount, getRecentMessages } from '@/lib/server/messages/message-repository'
36
40
 
37
41
  const TAG = 'supervisor-reflection'
38
42
 
39
43
  const MAIN_LOOP_META_LINE_RE = /\[(?:MAIN_LOOP_META|MAIN_LOOP_PLAN|MAIN_LOOP_REVIEW|AGENT_HEARTBEAT_META)\]\s*(\{[^\n]*\})?/i
40
44
  const DEFAULT_TRANSCRIPT_MESSAGES = 12
41
45
  const DEFAULT_SNIPPET_CHARS = 800
42
- const HUMAN_SIGNAL_RE = /\b(?:prefer|please|call me|don't call me|do not call me|i like|i dislike|i hate|i love|my pronouns|my partner|my wife|my husband|my kid|my child|my mom|my dad|my sister|my brother|birthday|anniversary|wedding|married|divorc|pregnan|baby|moved|moving|relocat|promotion|promoted|laid off|new job|job change|graduat|hospital|sick|illness|diagnos|passed away|funeral|grief|bereave|deadline|launch|fundraising|closing|house|home|travel)\b/i
43
- const SIGNIFICANT_EVENT_RE = /\b(?:birthday|anniversary|wedding|married|divorc|pregnan|baby|moved|moving|relocat|promotion|promoted|laid off|new job|job change|graduat|hospital|sick|illness|diagnos|passed away|funeral|grief|bereave|deadline|launch|fundraising|closing|house|home|travel)\b/i
44
46
 
45
47
  export interface SupervisorStateSnapshot {
46
48
  followupChainCount?: number | null
@@ -81,13 +83,6 @@ function now(): number {
81
83
  return Date.now()
82
84
  }
83
85
 
84
- function cleanText(value: unknown, max = 320): string | null {
85
- if (typeof value !== 'string') return null
86
- const compact = value.replace(/\s+/g, ' ').trim()
87
- if (!compact) return null
88
- return compact.slice(0, max)
89
- }
90
-
91
86
  function looksLikeHtmlErrorPayload(value: string): boolean {
92
87
  const normalized = value.toLowerCase()
93
88
  let matches = 0
@@ -189,9 +184,11 @@ function buildIncident(
189
184
  }
190
185
 
191
186
  function sessionContextPressure(session: Session | null): boolean {
192
- if (!session || !Array.isArray(session.messages)) return false
193
- if (session.messages.length >= 60) return true
194
- const totalChars = session.messages.reduce((sum, message) => sum + String(message?.text || '').length, 0)
187
+ if (!session) return false
188
+ const msgCount = getMessageCount(session.id)
189
+ if (msgCount >= 60) return true
190
+ const messages = getMessages(session.id)
191
+ const totalChars = messages.reduce((sum, message) => sum + String(message?.text || '').length, 0)
195
192
  return totalChars >= 18_000
196
193
  }
197
194
 
@@ -428,7 +425,7 @@ export async function executeSupervisorAutoActions(params: {
428
425
  }
429
426
 
430
427
  function buildSessionTranscript(session: Session, maxMessages = DEFAULT_TRANSCRIPT_MESSAGES): string {
431
- const messages = Array.isArray(session.messages) ? session.messages.slice(-maxMessages) : []
428
+ const messages = getRecentMessages(session.id, maxMessages)
432
429
  const lines: string[] = []
433
430
  for (const message of messages) {
434
431
  if (!message || message.suppressed) continue
@@ -543,10 +540,21 @@ function normalizeNoteArray(value: unknown, limit = 4): string[] {
543
540
  return out
544
541
  }
545
542
 
543
+ function transcriptHasSemanticSignal(
544
+ session: Session | null,
545
+ signal: 'hasHumanSignals' | 'hasSignificantEvent',
546
+ ): boolean {
547
+ if (!session) return false
548
+ const recentMessages = getRecentMessages(session.id, 8)
549
+ return recentMessages.some((message) => message?.role === 'user' && message?.semantics?.[signal] === true)
550
+ }
551
+
546
552
  function transcriptHasHumanSignals(session: Session | null): boolean {
547
- if (!session || !Array.isArray(session.messages)) return false
548
- const recentMessages = session.messages.slice(-8)
549
- return recentMessages.some((message) => HUMAN_SIGNAL_RE.test(stripMainLoopMeta(String(message?.text || ''))))
553
+ return transcriptHasSemanticSignal(session, 'hasHumanSignals')
554
+ }
555
+
556
+ function transcriptHasSignificantEvents(session: Session | null): boolean {
557
+ return transcriptHasSemanticSignal(session, 'hasSignificantEvent')
550
558
  }
551
559
 
552
560
  function parseReflectionResponse(raw: string): {
@@ -658,10 +666,11 @@ function shouldReflectRun(params: {
658
666
  if (!surface || !runtimeScopeIncludes(params.runtimeScope, surface)) return false
659
667
  if (params.status === 'cancelled') return false
660
668
  if (surface === 'task') return Boolean(params.resultText.trim() || params.incidents.length > 0)
661
- const meaningfulMessages = Array.isArray(params.session?.messages)
662
- ? params.session.messages.filter((message) => message && !message.suppressed && (message.text || message.toolEvents?.length)).length
669
+ const meaningfulMessages = params.session
670
+ ? getMessages(params.session.id).filter((message) => message && !message.suppressed && (message.text || message.toolEvents?.length)).length
663
671
  : 0
664
672
  if (transcriptHasHumanSignals(params.session)) return true
673
+ if (transcriptHasSignificantEvents(params.session)) return true
665
674
  if (params.incidents.length > 0) return true
666
675
  if (params.toolEvents.length > 0) return true
667
676
  if (params.resultText.trim().length >= 180) return true
@@ -788,7 +797,7 @@ function writeReflectionMemories(params: {
788
797
  }
789
798
  if (group.kind === 'significant_event') {
790
799
  metadata.memoryFacet = 'event'
791
- metadata.eventSalience = SIGNIFICANT_EVENT_RE.test(note) ? 'high' : 'medium'
800
+ metadata.eventSalience = 'high'
792
801
  }
793
802
  if (group.kind === 'open_loop') {
794
803
  metadata.memoryFacet = 'followup'
@@ -1095,6 +1104,17 @@ export async function observeAutonomyRunOutcome(
1095
1104
  reflections[reflection.id] = reflection
1096
1105
  saveRunReflections(reflections)
1097
1106
 
1107
+ // Fold reflection notes into session RunContext (non-critical)
1108
+ try {
1109
+ const freshSession = getSession(input.sessionId) as Session | undefined
1110
+ if (freshSession) {
1111
+ freshSession.runContext = foldReflectionIntoRunContext(freshSession.runContext, reflection)
1112
+ saveSession(input.sessionId, freshSession)
1113
+ }
1114
+ } catch (err: unknown) {
1115
+ log.warn(TAG, 'RunContext reflection folding failed:', err instanceof Error ? err.message : String(err))
1116
+ }
1117
+
1098
1118
  // Quality degradation alert — if recent quality trend drops below 0.5
1099
1119
  if (typeof reflection.qualityScore === 'number' && input.agentId) {
1100
1120
  checkQualityDegradation(input.agentId).catch(() => {})
@@ -1,11 +1,13 @@
1
1
  import { ChatAnthropic } from '@langchain/anthropic'
2
2
  import { ChatOpenAI } from '@langchain/openai'
3
- import { loadCredentials, decryptKey, loadAgents, loadSessions } from './storage'
4
3
  import { getProviderList } from '../providers'
5
4
  import { normalizeOpenClawEndpoint } from '@/lib/openclaw/openclaw-endpoint'
6
5
  import { NON_LANGGRAPH_PROVIDER_IDS } from '../provider-sets'
7
6
  import { resolveOllamaRuntimeConfig } from './ollama-runtime'
8
7
  import { resolveProviderApiEndpoint, resolveProviderCredentialId } from './provider-endpoint'
8
+ import { getAgent } from './agents/agent-repository'
9
+ import { resolveCredentialSecret } from './credentials/credential-service'
10
+ import { getSession } from './sessions/session-repository'
9
11
  import type { Agent } from '@/types'
10
12
 
11
13
  const OLLAMA_CLOUD_URL = 'https://ollama.com/v1'
@@ -135,15 +137,7 @@ export function buildChatModel(opts: {
135
137
  }
136
138
 
137
139
  function resolveApiKeyFromCredential(credentialId: string | null | undefined): string | null {
138
- if (!credentialId) return null
139
- const creds = loadCredentials()
140
- const cred = creds[credentialId]
141
- if (!cred?.encryptedKey) return null
142
- try {
143
- return decryptKey(cred.encryptedKey)
144
- } catch {
145
- return null
146
- }
140
+ return resolveCredentialSecret(credentialId)
147
141
  }
148
142
 
149
143
  function normalizePreferenceValue(value: string | null | undefined): string {
@@ -222,12 +216,10 @@ export function resolveGenerationModelConfig(options?: {
222
216
  excludeProviders?: string[]
223
217
  }): ResolvedGenerationModelConfig {
224
218
  const providers = getProviderList()
225
- const agents = loadAgents()
226
- const sessions = loadSessions()
227
219
  const excludeProviders = new Set((options?.excludeProviders || []).map((value) => normalizePreferenceValue(value)).filter(Boolean))
228
- const session = options?.sessionId ? sessions[options.sessionId] : null
229
- const sessionAgent = session?.agentId ? agents[session.agentId] as Agent | undefined : null
230
- const directAgent = options?.agentId ? agents[options.agentId] as Agent | undefined : null
220
+ const session = options?.sessionId ? getSession(options.sessionId) : null
221
+ const sessionAgent = session?.agentId ? getAgent(session.agentId) as Agent | null : null
222
+ const directAgent = options?.agentId ? getAgent(options.agentId) as Agent | null : null
231
223
  const resolved = resolvePreferredGenerationConfig(providers, [
232
224
  ...(Array.isArray(options?.preferred) ? options?.preferred : options?.preferred ? [options.preferred] : []),
233
225
  ...(session ? [{
@@ -1,12 +1,14 @@
1
1
  import assert from 'node:assert/strict'
2
2
  import { test } from 'node:test'
3
3
  import { routeTaskIntent } from './capability-router'
4
+ import type { MessageClassification } from '@/lib/server/chat-execution/message-classifier'
4
5
 
5
6
  test('routeTaskIntent keeps recall-style prompts as general intent', () => {
6
7
  const decision = routeTaskIntent(
7
8
  'What token did we store earlier as e2e_validation_token? Reply only with the token.',
8
9
  ['memory', 'web_search'],
9
10
  null,
11
+ makeClassification({ taskIntent: 'general' }),
10
12
  )
11
13
  assert.equal(decision.intent, 'general')
12
14
  })
@@ -16,6 +18,7 @@ test('routeTaskIntent keeps coding prompts prioritized over memory keywords', ()
16
18
  'Build and test a calculator app, then remember the final path in memory.',
17
19
  ['memory', 'shell', 'files'],
18
20
  null,
21
+ makeClassification({ taskIntent: 'coding', workType: 'coding' }),
19
22
  )
20
23
  assert.equal(decision.intent, 'coding')
21
24
  })
@@ -25,6 +28,14 @@ test('routeTaskIntent keeps hybrid research-plus-media prompts in research inten
25
28
  'Can you tell me more if there is any news related to the US-Iran war, and can you send me some screenshots and give me a summary and maybe send me a voice note about it?',
26
29
  ['web_search', 'web_fetch', 'browser', 'manage_connectors'],
27
30
  null,
31
+ makeClassification({
32
+ taskIntent: 'research',
33
+ workType: 'research',
34
+ wantsScreenshots: true,
35
+ wantsVoiceDelivery: true,
36
+ wantsOutboundDelivery: true,
37
+ isResearchSynthesis: true,
38
+ }),
28
39
  )
29
40
 
30
41
  assert.equal(decision.intent, 'research')
@@ -36,6 +47,12 @@ test('routeTaskIntent treats direct voice-note delivery as outreach', () => {
36
47
  'Send me a voice note over WhatsApp summarizing what changed.',
37
48
  ['manage_connectors'],
38
49
  null,
50
+ makeClassification({
51
+ taskIntent: 'outreach',
52
+ workType: 'writing',
53
+ wantsVoiceDelivery: true,
54
+ wantsOutboundDelivery: true,
55
+ }),
39
56
  )
40
57
 
41
58
  assert.equal(decision.intent, 'outreach')
@@ -45,10 +62,62 @@ test('routeTaskIntent treats direct voice-note delivery as outreach', () => {
45
62
  test('routeTaskIntent treats keep-watching update requests as research even without explicit news keywords', () => {
46
63
  const decision = routeTaskIntent(
47
64
  'Tell me about the Iran war, keep watching for meaningful updates, and avoid duplicate reminders.',
48
- ['web_search', 'manage_schedules'],
65
+ ['web_search', 'web_fetch', 'manage_schedules'],
49
66
  null,
67
+ makeClassification({
68
+ taskIntent: 'research',
69
+ workType: 'research',
70
+ isResearchSynthesis: true,
71
+ }),
50
72
  )
51
73
 
52
74
  assert.equal(decision.intent, 'research')
53
75
  assert.deepEqual(decision.preferredTools, ['web_search', 'web_fetch'])
54
76
  })
77
+
78
+ test('routeTaskIntent uses structured classification when available', () => {
79
+ const classification: MessageClassification = {
80
+ taskIntent: 'browsing',
81
+ isDeliverableTask: true,
82
+ isBroadGoal: false,
83
+ walletIntent: 'none',
84
+ hasHumanSignals: false,
85
+ hasSignificantEvent: false,
86
+ isResearchSynthesis: true,
87
+ workType: 'research',
88
+ wantsScreenshots: true,
89
+ wantsOutboundDelivery: false,
90
+ wantsVoiceDelivery: false,
91
+ explicitToolRequests: ['browser'],
92
+ confidence: 0.92,
93
+ }
94
+
95
+ const decision = routeTaskIntent(
96
+ 'Review this story and show me screenshots.',
97
+ ['web_search', 'web_fetch', 'browser'],
98
+ null,
99
+ classification,
100
+ )
101
+
102
+ assert.equal(decision.intent, 'browsing')
103
+ assert.deepEqual(decision.preferredTools, ['browser', 'web_fetch'])
104
+ })
105
+
106
+ function makeClassification(overrides: Partial<MessageClassification>): MessageClassification {
107
+ return {
108
+ taskIntent: 'general',
109
+ isDeliverableTask: false,
110
+ isBroadGoal: false,
111
+ walletIntent: 'none',
112
+ hasHumanSignals: false,
113
+ hasSignificantEvent: false,
114
+ isResearchSynthesis: false,
115
+ workType: 'general',
116
+ wantsScreenshots: false,
117
+ wantsOutboundDelivery: false,
118
+ wantsVoiceDelivery: false,
119
+ explicitToolRequests: [],
120
+ confidence: 0.9,
121
+ ...overrides,
122
+ }
123
+ }
@@ -1,6 +1,7 @@
1
1
  import type { AppSettings } from '@/types'
2
2
  import { dedup } from '@/lib/shared-utils'
3
- import { getToolsForCapability, matchToolCapabilitiesForMessage, TOOL_CAPABILITY } from './tool-planning'
3
+ import { getToolsForCapability, TOOL_CAPABILITY } from './tool-planning'
4
+ import type { MessageClassification } from '@/lib/server/chat-execution/message-classifier'
4
5
 
5
6
  export type TaskIntent =
6
7
  | 'coding'
@@ -25,38 +26,10 @@ function findFirstUrl(text: string): string | undefined {
25
26
  return m?.[0]
26
27
  }
27
28
 
28
- function containsAny(text: string, terms: string[]): boolean {
29
- return terms.some((term) => text.includes(term))
30
- }
31
-
32
29
  function dedupe(values: string[]): string[] {
33
30
  return dedup(values.filter(Boolean))
34
31
  }
35
32
 
36
- function isMonitoringOrCurrentEventsRequest(text: string): boolean {
37
- const normalized = text.toLowerCase()
38
- if (!normalized.trim()) return false
39
- return containsAny(normalized, [
40
- 'latest',
41
- 'news',
42
- 'headline',
43
- 'current event',
44
- 'recent update',
45
- 'recent updates',
46
- 'update me',
47
- 'updates',
48
- 'breaking',
49
- 'developments',
50
- 'keep watching',
51
- 'watch for',
52
- 'watching for',
53
- 'monitor',
54
- 'track',
55
- 'follow the situation',
56
- 'tell me if anything changes',
57
- ])
58
- }
59
-
60
33
  function preferredToolsForCapabilities(enabledExtensions: string[], capabilities: string[], fallback: string[] = []): string[] {
61
34
  const preferred = capabilities.flatMap((capability) => getToolsForCapability(enabledExtensions, capability))
62
35
  return dedupe(preferred.length > 0 ? preferred : fallback)
@@ -90,68 +63,36 @@ export function routeTaskIntent(
90
63
  message: string,
91
64
  enabledExtensions: string[],
92
65
  settings?: AppSettings | null,
66
+ classification?: MessageClassification | null,
93
67
  ): CapabilityRoutingDecision {
94
- const text = (message || '').toLowerCase()
95
68
  const url = findFirstUrl(message || '')
96
69
  const delegateOrder = normalizeDelegateOrder(settings?.autonomyPreferredDelegates)
97
- const matchedCapabilities = matchToolCapabilitiesForMessage(enabledExtensions, message)
98
- const wantsVoiceNote = matchedCapabilities.has(TOOL_CAPABILITY.deliveryVoiceNote)
99
- const wantsScreenshots = matchedCapabilities.has(TOOL_CAPABILITY.browserCapture)
100
- const wantsMediaDelivery = matchedCapabilities.has(TOOL_CAPABILITY.deliveryMedia)
101
- const wantsChannelDelivery = matchedCapabilities.has(TOOL_CAPABILITY.deliveryMessage)
102
- const researchLike = matchedCapabilities.has(TOOL_CAPABILITY.researchSearch)
103
- || matchedCapabilities.has(TOOL_CAPABILITY.researchFetch)
104
- || isMonitoringOrCurrentEventsRequest(text)
105
- || !!url
70
+ const intent = classification?.taskIntent || 'general'
71
+ const confidence = classification?.confidence ?? 0
72
+ const wantsVoiceDelivery = classification?.wantsVoiceDelivery === true
73
+ const wantsScreenshots = classification?.wantsScreenshots === true
74
+ const wantsOutboundDelivery = classification?.wantsOutboundDelivery === true
106
75
 
107
- const coding = containsAny(text, [
108
- 'build',
109
- 'implement',
110
- 'create app',
111
- 'refactor',
112
- 'fix bug',
113
- 'write code',
114
- 'codebase',
115
- 'typescript',
116
- 'javascript',
117
- 'react',
118
- 'next.js',
119
- 'unit test',
120
- 'run tests',
121
- 'compile',
122
- 'npm ',
123
- 'pnpm ',
124
- 'yarn ',
125
- ])
126
- if (coding) {
76
+ if (intent === 'coding') {
127
77
  return {
128
78
  intent: 'coding',
129
- confidence: 0.9,
79
+ confidence,
130
80
  preferredTools: ['claude_code', 'codex_cli', 'opencode_cli', 'shell', 'files', 'edit_file'],
131
81
  preferredDelegates: delegateOrder,
132
82
  primaryUrl: url,
133
83
  }
134
84
  }
135
85
 
136
- const outreach = containsAny(text, [
137
- 'send update',
138
- 'message',
139
- 'whatsapp',
140
- 'telegram',
141
- 'slack',
142
- 'discord',
143
- 'notify',
144
- 'broadcast',
145
- ]) || (!researchLike && (wantsVoiceNote || wantsMediaDelivery || wantsChannelDelivery))
146
- if (outreach) {
86
+ if (intent === 'outreach') {
147
87
  return {
148
88
  intent: 'outreach',
149
- confidence: 0.8,
89
+ confidence,
150
90
  preferredTools: preferredToolsForCapabilities(
151
91
  enabledExtensions,
152
92
  [
153
- TOOL_CAPABILITY.deliveryVoiceNote,
154
- TOOL_CAPABILITY.deliveryMedia,
93
+ ...(wantsVoiceDelivery ? [TOOL_CAPABILITY.deliveryVoiceNote] : []),
94
+ ...(wantsScreenshots ? [TOOL_CAPABILITY.deliveryMedia] : []),
95
+ ...(wantsOutboundDelivery || wantsVoiceDelivery ? [TOOL_CAPABILITY.deliveryMessage] : []),
155
96
  TOOL_CAPABILITY.deliveryMessage,
156
97
  ],
157
98
  ['connector_message_tool', 'manage_connectors', 'manage_sessions'],
@@ -161,35 +102,20 @@ export function routeTaskIntent(
161
102
  }
162
103
  }
163
104
 
164
- const scheduling = containsAny(text, [
165
- 'schedule',
166
- 'every day',
167
- 'every week',
168
- 'cron',
169
- 'recurring',
170
- 'remind',
171
- 'follow up tomorrow',
172
- ]) && !researchLike
173
- if (scheduling) {
105
+ if (intent === 'scheduling') {
174
106
  return {
175
107
  intent: 'scheduling',
176
- confidence: 0.75,
108
+ confidence,
177
109
  preferredTools: ['manage_schedules', 'manage_tasks'],
178
110
  preferredDelegates: delegateOrder,
179
111
  primaryUrl: url,
180
112
  }
181
113
  }
182
114
 
183
- const browsing = !!url && (
184
- matchedCapabilities.has(TOOL_CAPABILITY.browserNavigate)
185
- || matchedCapabilities.has(TOOL_CAPABILITY.browserCapture)
186
- || getToolsForCapability(enabledExtensions, TOOL_CAPABILITY.browserNavigate).length > 0
187
- || getToolsForCapability(enabledExtensions, TOOL_CAPABILITY.browserCapture).length > 0
188
- )
189
- if (browsing) {
115
+ if (intent === 'browsing') {
190
116
  return {
191
117
  intent: 'browsing',
192
- confidence: 0.7,
118
+ confidence,
193
119
  preferredTools: preferredToolsForCapabilities(
194
120
  enabledExtensions,
195
121
  [
@@ -204,22 +130,21 @@ export function routeTaskIntent(
204
130
  }
205
131
  }
206
132
 
207
- const research = researchLike
208
- if (research) {
133
+ if (intent === 'research') {
209
134
  const preferred = preferredToolsForCapabilities(
210
135
  enabledExtensions,
211
136
  [
212
137
  TOOL_CAPABILITY.researchSearch,
213
138
  TOOL_CAPABILITY.researchFetch,
214
139
  ...(wantsScreenshots ? [TOOL_CAPABILITY.browserCapture] : []),
215
- ...(wantsVoiceNote ? [TOOL_CAPABILITY.deliveryVoiceNote] : []),
216
- ...(wantsMediaDelivery || wantsChannelDelivery ? [TOOL_CAPABILITY.deliveryMedia, TOOL_CAPABILITY.deliveryMessage] : []),
140
+ ...(wantsVoiceDelivery ? [TOOL_CAPABILITY.deliveryVoiceNote] : []),
141
+ ...(wantsOutboundDelivery ? [TOOL_CAPABILITY.deliveryMedia, TOOL_CAPABILITY.deliveryMessage] : []),
217
142
  ],
218
143
  ['web_search', 'web_fetch', 'browser'],
219
144
  )
220
145
  return {
221
146
  intent: 'research',
222
- confidence: 0.7,
147
+ confidence,
223
148
  preferredTools: preferred,
224
149
  preferredDelegates: delegateOrder,
225
150
  primaryUrl: url,
@@ -228,7 +153,7 @@ export function routeTaskIntent(
228
153
 
229
154
  return {
230
155
  intent: 'general',
231
- confidence: 0.5,
156
+ confidence,
232
157
  preferredTools: [],
233
158
  preferredDelegates: delegateOrder,
234
159
  primaryUrl: url,
@@ -398,21 +398,6 @@ export function findFirstUrl(text: string): string | null {
398
398
  return match?.[0] || null
399
399
  }
400
400
 
401
- export function isMemoryListIntent(message: string): boolean {
402
- const text = message.toLowerCase()
403
- if (!/\bmemory|memories|remember\b/.test(text)) return false
404
- if (/\b(save|store|memorize|add to memory|write to memory|remember this)\b/.test(text)) return false
405
- if (/\bmemory_tool\b/.test(text)) return true
406
- return (
407
- /\blist\b[\s\w]{0,24}\bmemories\b/.test(text)
408
- || /\bshow\b[\s\w]{0,24}\bmemories\b/.test(text)
409
- || /\bget\b[\s\w]{0,24}\bmemories\b/.test(text)
410
- || /\bwhat\b[\s\w]{0,40}\bmemories\b/.test(text)
411
- || /\bwhat do you remember\b/.test(text)
412
- || /\brecall\b[\s\w]{0,24}\bmemories?\b/.test(text)
413
- )
414
- }
415
-
416
401
  export function extractDelegationTask(message: string, toolName: string): string | null {
417
402
  if (!message.toLowerCase().includes(toolName.toLowerCase())) return null
418
403
  const patterns = [
@@ -1,9 +1,6 @@
1
1
  import type { MessageToolEvent } from '@/types'
2
2
  import { canonicalizeExtensionId } from '@/lib/server/tool-aliases'
3
3
  import { extractSuggestions } from '@/lib/server/suggestions'
4
- import {
5
- looksLikeExternalWalletTask,
6
- } from '@/lib/server/chat-execution/stream-continuation'
7
4
  import type { MessageClassification } from '@/lib/server/chat-execution/message-classifier'
8
5
  import {
9
6
  buildSuccessfulMemoryMutationResponse,
@@ -82,7 +79,8 @@ export function shouldForceExternalServiceSummary(params: {
82
79
  toolEventCount: number
83
80
  classification?: MessageClassification | null
84
81
  }): boolean {
85
- const walletDetected = params.classification ? params.classification.walletIntent !== 'none' : looksLikeExternalWalletTask(params.userMessage)
82
+ const walletDetected = params.classification?.walletIntent !== undefined
83
+ && params.classification.walletIntent !== 'none'
86
84
  if (!walletDetected) return false
87
85
  if (!params.hasToolCalls || params.toolEventCount === 0) return false
88
86
  const trimmed = params.finalResponse.trim()